Paranoia2/utils/common/filesystem.cpp
2020-08-31 19:50:41 +03:00

621 lines
14 KiB
C++

/*
filesystem.cpp - simple version of game engine filesystem for tools
Copyright (C) 2015 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 <windows.h>
#include <direct.h>
#include <fcntl.h>
#include <stdio.h>
#include <io.h>
#include "conprint.h"
#include "cmdlib.h"
#include "stringlib.h"
#include "filesystem.h"
#include <sys/stat.h>
/*
=============================================================================
FILEMATCH COMMON SYSTEM
=============================================================================
*/
int matchpattern( const char *str, const char *cmp, bool caseinsensitive )
{
int c1, c2;
while( *cmp )
{
switch( *cmp )
{
case 0: return 1; // end of pattern
case '?': // match any single character
if( *str == 0 || *str == '/' || *str == '\\' || *str == ':' )
return 0; // no match
str++;
cmp++;
break;
case '*': // match anything until following string
if( !*str ) return 1; // match
cmp++;
while( *str )
{
if( *str == '/' || *str == '\\' || *str == ':' )
break;
// see if pattern matches at this offset
if( matchpattern( str, cmp, caseinsensitive ))
return 1;
// nope, advance to next offset
str++;
}
break;
default:
if( *str != *cmp )
{
if( !caseinsensitive )
return 0; // no match
c1 = Q_tolower( *str );
c2 = Q_tolower( *cmp );
if( c1 != c2 ) return 0; // no match
}
str++;
cmp++;
break;
}
}
// reached end of pattern but not end of input?
return (*str) ? 0 : 1;
}
void stringlistinit( stringlist_t *list )
{
memset( list, 0, sizeof( *list ));
}
void stringlistfreecontents( stringlist_t *list )
{
int i;
for( i = 0; i < list->numstrings; i++ )
{
if( list->strings[i] )
Mem_Free( list->strings[i], C_STRING );
list->strings[i] = NULL;
}
if( list->strings )
Mem_Free( list->strings, C_STRING );
list->numstrings = 0;
list->maxstrings = 0;
list->strings = NULL;
}
void stringlistappend( stringlist_t *list, char *text )
{
size_t textlen;
char **oldstrings;
if( !Q_stricmp( text, "." ) || !Q_stricmp( text, ".." ))
return; // ignore the virtual directories
if( list->numstrings >= list->maxstrings )
{
oldstrings = list->strings;
list->maxstrings += 4096;
list->strings = (char **)Mem_Alloc( list->maxstrings * sizeof( *list->strings ), C_STRING );
if( list->numstrings ) memcpy( list->strings, oldstrings, list->numstrings * sizeof( *list->strings ));
if( oldstrings ) Mem_Free( oldstrings, C_STRING );
}
textlen = Q_strlen( text ) + 1;
list->strings[list->numstrings] = (char *)Mem_Alloc( textlen, C_STRING );
memcpy( list->strings[list->numstrings], text, textlen );
list->numstrings++;
}
void stringlistsort( stringlist_t *list )
{
char *temp;
int i, j;
// this is a selection sort (finds the best entry for each slot)
for( i = 0; i < list->numstrings - 1; i++ )
{
for( j = i + 1; j < list->numstrings; j++ )
{
if( Q_strcmp( list->strings[i], list->strings[j] ) > 0 )
{
temp = list->strings[i];
list->strings[i] = list->strings[j];
list->strings[j] = temp;
}
}
}
}
void listdirectory( stringlist_t *list, const char *path, bool tolower )
{
char pattern[4096];
struct _finddata_t n_file;
long hFile;
char *c;
int i;
Q_strncpy( pattern, path, sizeof( pattern ));
Q_strncat( pattern, "*", sizeof( pattern ));
// ask for the directory listing handle
hFile = _findfirst( pattern, &n_file );
if( hFile == -1 ) return;
// start a new chain with the the first name
stringlistappend( list, n_file.name );
// iterate through the directory
while( _findnext( hFile, &n_file ) == 0 )
stringlistappend( list, n_file.name );
_findclose( hFile );
if( !tolower ) return;
// convert names to lowercase because windows doesn't care, but pattern matching code often does
for( i = 0; i < list->numstrings; i++ )
{
for( c = list->strings[i]; *c; c++ )
*c = Q_tolower( *c );
}
}
/*
=============================================================================
FILESYSTEM PUBLIC BASE FUNCTIONS
=============================================================================
*/
/*
===========
FS_Search
Allocate and fill a search structure with information on matching filenames.
===========
*/
search_t *COM_Search( const char *pattern, int caseinsensitive, wfile_t *source_wad )
{
search_t *search = NULL;
int i, basepathlength, numfiles, numchars;
int resultlistindex, dirlistindex;
const char *slash, *backslash, *colon, *separator;
char netpath[1024], temp[1024], root[1204];
stringlist_t resultlist, dirlist;
char *basepath;
for( i = 0; pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\'; i++ );
if( i > 0 )
{
MsgDev( D_ERROR, "COM_Search: don't use punctuation at the beginning of a search pattern!\n" );
return NULL;
}
if( !GetCurrentDirectory( sizeof( root ), root ))
{
MsgDev( D_ERROR, "couldn't determine current directory\n" );
return NULL;
}
Q_strncat( root, "\\", sizeof( root ));
stringlistinit( &resultlist );
stringlistinit( &dirlist );
slash = Q_strrchr( pattern, '/' );
backslash = Q_strrchr( pattern, '\\' );
colon = Q_strrchr( pattern, ':' );
separator = max( slash, backslash );
separator = max( separator, colon );
basepathlength = separator ? (separator + 1 - pattern) : 0;
basepath = (char *)Mem_Alloc( basepathlength + 1 );
if( basepathlength ) memcpy( basepath, pattern, basepathlength );
basepath[basepathlength] = 0;
#ifndef IGNORE_SEARCH_IN_WADS
W_SearchForFile( source_wad, pattern, &resultlist );
#endif
// get a directory listing and look at each name
Q_sprintf( netpath, "%s%s", root, basepath );
stringlistinit( &dirlist );
listdirectory( &dirlist, netpath );
for( dirlistindex = 0; dirlistindex < dirlist.numstrings; dirlistindex++ )
{
Q_sprintf( temp, "%s%s", basepath, dirlist.strings[dirlistindex] );
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 );
}
}
stringlistfreecontents( &dirlist );
if( resultlist.numstrings )
{
stringlistsort( &resultlist );
numfiles = resultlist.numstrings;
numchars = 0;
for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ )
numchars += (int)Q_strlen( resultlist.strings[resultlistindex]) + 1;
search = (search_t *)Mem_Alloc( sizeof( search_t ) + numchars + numfiles * sizeof( char* ));
search->filenames = (char **)((char *)search + sizeof( search_t ));
search->filenamesbuffer = (char *)((char *)search + sizeof( search_t ) + numfiles * sizeof( char* ));
search->numfilenames = (int)numfiles;
numfiles = numchars = 0;
for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ )
{
search->filenames[numfiles] = search->filenamesbuffer + numchars;
size_t textlen = Q_strlen(resultlist.strings[resultlistindex]) + 1;
memcpy( search->filenames[numfiles], resultlist.strings[resultlistindex], textlen );
numchars += (int)textlen;
numfiles++;
}
}
stringlistfreecontents( &resultlist );
Mem_Free( basepath );
return search;
}
byte *COM_LoadFile( const char *filepath, size_t *filesize, bool safe )
{
long handle;
size_t size;
unsigned char *buf;
handle = open( filepath, O_RDONLY|O_BINARY, 0666 );
if( filesize ) *filesize = 0;
if( handle < 0 )
{
if( safe ) COM_FatalError( "Couldn't open %s\n", filepath );
return NULL;
}
size = lseek( handle, 0, SEEK_END );
lseek( handle, 0, SEEK_SET );
buf = (unsigned char *)Mem_Alloc( size + 1, C_FILESYSTEM );
buf[size] = '\0';
read( handle, (unsigned char *)buf, size );
close( handle );
if( filesize ) *filesize = size;
return buf;
}
bool COM_SaveFile( const char *filepath, void *buffer, size_t filesize, bool safe )
{
long handle;
size_t size;
if( buffer == NULL || filesize <= 0 )
return false;
COM_CreatePath( (char *)filepath );
handle = open( filepath, O_WRONLY|O_BINARY|O_CREAT|O_TRUNC, 0666 );
if( handle < 0 )
{
if( safe ) COM_FatalError( "Couldn't write %s\n", filepath );
return false;
}
size = write( handle, buffer, filesize );
close( handle );
if( size < 0 )
return false;
return true;
}
/*
==================
COM_CreatePath
==================
*/
void COM_CreatePath( char *path )
{
char *ofs, save;
for( ofs = path + 1; *ofs; ofs++ )
{
if( *ofs == '/' || *ofs == '\\' )
{
// create the directory
save = *ofs;
*ofs = 0;
_mkdir( path );
*ofs = save;
}
}
}
/*
==================
COM_FileExists
==================
*/
bool COM_FileExists( const char *path )
{
int desc;
if(( desc = open( path, O_RDONLY|O_BINARY )) < 0 )
return false;
close( desc );
return true;
}
/*
============
COM_FileWithoutPath
============
*/
const char *COM_FileWithoutPath( const char *in )
{
const char *separator, *backslash, *colon;
separator = Q_strrchr( in, '/' );
backslash = Q_strrchr( in, '\\' );
if( !separator || separator < backslash )
separator = backslash;
colon = Q_strrchr( in, ':' );
if( !separator || separator < colon )
separator = colon;
return separator ? separator + 1 : in;
}
/*
============
COM_ExtractFilePath
============
*/
void COM_ExtractFilePath( const char *path, char *dest )
{
const char *src = path + Q_strlen( path ) - 1;
// back up until a \ or the start
while( src != path && !(*(src - 1) == '\\' || *(src - 1) == '/' ))
src--;
if( src != path )
{
memcpy( dest, path, src - path );
dest[src - path - 1] = 0; // cutoff backslash
}
else Q_strcpy( dest, "" ); // file without path
}
/*
============
COM_FileExtension
============
*/
const char *COM_FileExtension( const char *in )
{
const char *separator, *backslash, *colon, *dot;
separator = Q_strrchr( in, '/' );
backslash = Q_strrchr( in, '\\' );
if( !separator || separator < backslash )
separator = backslash;
colon = Q_strrchr( in, ':' );
if( !separator || separator < colon )
separator = colon;
dot = Q_strrchr( in, '.' );
if( dot == NULL || ( separator && ( dot < separator )))
return "";
return dot + 1;
}
/*
==================
COM_DefaultExtension
==================
*/
void COM_DefaultExtension( char *path, const char *extension )
{
const char *src;
// if path doesn't have a .EXT, append extension
// (extension should include the .)
src = path + Q_strlen( path ) - 1;
while( *src != '/' && src != path )
{
// it has an extension
if( *src == '.' ) return;
src--;
}
Q_strcat( path, extension );
}
/*
============
COM_StripExtension
============
*/
void COM_StripExtension( char *path )
{
size_t length;
length = Q_strlen( path ) - 1;
while( length > 0 && path[length] != '.' )
{
length--;
if( path[length] == '/' || path[length] == '\\' || path[length] == ':' )
return; // no extension
}
if( length ) path[length] = 0;
}
/*
==================
COM_ReplaceExtension
==================
*/
void COM_ReplaceExtension( char *path, const char *extension )
{
COM_StripExtension( path );
COM_DefaultExtension( path, extension );
}
/*
============
COM_FileBase
Extracts the base name of a file (no path, no extension, assumes '/' as path separator)
============
*/
void COM_FileBase( const char *in, char *out )
{
int len, start, end;
len = Q_strlen( in );
if( !len ) return;
// scan backward for '.'
end = len - 1;
while( end && in[end] != '.' && in[end] != '/' && in[end] != '\\' )
end--;
if( in[end] != '.' )
end = len-1; // no '.', copy to end
else end--; // found ',', copy to left of '.'
// scan backward for '/'
start = len - 1;
while( start >= 0 && in[start] != '/' && in[start] != '\\' )
start--;
if( start < 0 || ( in[start] != '/' && in[start] != '\\' ))
start = 0;
else start++;
// length of new sting
len = end - start + 1;
// Copy partial string
Q_strncpy( out, &in[start], len + 1 );
out[len] = 0;
}
/*
==================
COM_FolderExists
==================
*/
bool COM_FolderExists( const char *path )
{
DWORD dwFlags = GetFileAttributes( path );
return ( dwFlags != -1 ) && FBitSet( dwFlags, FILE_ATTRIBUTE_DIRECTORY );
}
/*
====================
COM_FileTime
Internal function used to determine filetime
====================
*/
long COM_FileTime( const char *filename )
{
struct stat buf;
if( stat( filename, &buf ) == -1 )
return -1;
return buf.st_mtime;
}
/*
=============================================================================
OBSOLETE FUNCTIONS
=============================================================================
*/
long SafeOpenWrite( const char *filename )
{
long handle;
handle = open( filename, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0666 );
if( handle < 0 ) COM_FatalError( "couldn't open %s\n", filename );
return handle;
}
long SafeOpenRead( const char *filename )
{
long handle;
handle = open( filename, O_RDONLY|O_BINARY, 0666 );
if( handle < 0 ) COM_FatalError( "couldn't open %s\n", filename );
return handle;
}
void SafeReadExt( long handle, void *buffer, int count, const char *file, const int line )
{
size_t read_count = read( handle, buffer, count );
if( read_count != (size_t)count )
COM_FatalError( "file read failure ( %i != %i ) at %s:%i\n", read_count, count, file, line );
}
void SafeWriteExt( long handle, void *buffer, int count, const char *file, const int line )
{
size_t write_count = write( handle, buffer, count );
if( write_count != (size_t)count )
COM_FatalError( "file write failure ( %i != %i ) at %s:%i\n", write_count, count, file, line );
}