xash3d-fwgs/filesystem/wad.c

709 lines
17 KiB
C

/*
wad.c - WAD support for filesystem
Copyright (C) 2003-2006 Mathieu Olivier
Copyright (C) 2000-2007 DarkPlaces contributors
Copyright (C) 2007 Uncle Mike
Copyright (C) 2015-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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#if XASH_POSIX
#include <unistd.h>
#endif
#include <errno.h>
#include <stddef.h>
#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_*
<format>
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;
struct wfile_s
{
int infotableofs;
int numlumps;
poolhandle_t mempool; // W_ReadLump temp buffers
file_t *handle;
dlumpinfo_t *lumps;
time_t filetime;
};
// 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, "*" ) || !COM_CheckStringEmpty( 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 *wadfile, 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", wadfile, 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
}
/*
===========
FS_Close_WAD
===========
*/
void FS_Close_WAD( searchpath_t *search )
{
FS_CloseWAD( search->wad );
}
/*
===========
FS_OpenFile_WAD
===========
*/
file_t *FS_OpenFile_WAD( searchpath_t *search, const char *filename, const char *mode, int pack_ind )
{
return NULL;
}
/*
===========
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 ));
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 )
{
int ind;
searchpath_t *search = FS_FindFile( filename, &ind, NULL, 0, false );
// allow direct absolute paths
// TODO: catch them in FS_FindFile_DIR!
if( !search || ind < 0 )
{
wad->handle = FS_SysOpen( filename, "rb" );
}
else
{
wad->handle = search->pfnOpenFile( search, filename, "rb", ind );
}
}
else
{
const char *basename = COM_FileWithoutPath( filename );
wad->handle = FS_Open( basename, "rb", false );
}
if( wad->handle == NULL )
{
Con_Reportf( S_ERROR "W_Open: couldn't open %s: %s\n", filename, strerror( errno ));
if( error ) *error = WAD_LOAD_COULDNT_OPEN;
FS_CloseWAD( wad );
return NULL;
}
// copy wad name
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", 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( filename, name, wad, &srclumps[i] );
}
// release source lumps
Mem_Free( srclumps );
// and leave the file open
return wad;
}
/*
===========
FS_FileTime_WAD
===========
*/
static int FS_FileTime_WAD( searchpath_t *search, const char *filename )
{
return search->wad->filetime;
}
/*
===========
FS_PrintInfo_WAD
===========
*/
static void FS_PrintInfo_WAD( searchpath_t *search, char *dst, size_t size )
{
Q_snprintf( dst, size, "%s (%i files)", search->filename, search->wad->numlumps );
}
/*
===========
FS_FindFile_WAD
===========
*/
static int FS_FindFile_WAD( searchpath_t *search, const char *path, char *fixedname, size_t len )
{
dlumpinfo_t *lump;
signed char type = W_TypeFromExt( path );
qboolean anywadname = true;
string wadname, wadfolder;
string shortname;
// quick reject by filetype
if( type == TYP_NONE )
return -1;
COM_ExtractFilePath( path, wadname );
wadfolder[0] = '\0';
if( COM_CheckStringEmpty( wadname ))
{
string wadbasename;
COM_FileBase( wadname, wadbasename, sizeof( wadbasename ));
Q_strncpy( wadfolder, wadbasename, sizeof( wadfolder ));
Q_snprintf( wadname, sizeof( wadname ), "%s.wad", wadbasename );
anywadname = false;
}
// make wadname from wad fullpath
COM_FileBase( search->filename, shortname, sizeof( shortname ));
COM_DefaultExtension( shortname, ".wad", sizeof( shortname ));
// 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( path, shortname, sizeof( shortname ));
lump = W_FindLump( search->wad, shortname, type );
if( lump )
{
if( fixedname )
Q_strncpy( fixedname, lump->name, len );
return lump - search->wad->lumps;
}
return -1;
}
/*
===========
FS_Search_WAD
===========
*/
static void FS_Search_WAD( searchpath_t *search, stringlist_t *list, const char *pattern, int caseinsensitive )
{
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;
char buf[MAX_VA_STRING];
// quick reject by filetype
if( type == TYP_NONE )
return;
COM_ExtractFilePath( pattern, wadname );
COM_FileBase( pattern, wadpattern, sizeof( wadpattern ));
wadfolder[0] = '\0';
if( COM_CheckStringEmpty( wadname ))
{
string wadbasename;
COM_FileBase( wadname, wadbasename, sizeof( wadbasename ));
Q_strncpy( wadfolder, wadbasename, sizeof( wadfolder ));
Q_snprintf( wadname, sizeof( wadname ), "%s.wad", wadbasename );
anywadname = false;
}
// make wadname from wad fullpath
COM_FileBase( search->filename, temp2, sizeof( temp2 ));
COM_DefaultExtension( temp2, ".wad", sizeof( temp2 ));
// quick reject by wadname
if( !anywadname && Q_stricmp( wadname, temp2 ))
return;
for( i = 0; i < search->wad->numlumps; i++ )
{
// if type not matching, we already have no chance ...
if( type != TYP_ANY && search->wad->lumps[i].type != type )
continue;
// build the lumpname with image suffix (if present)
Q_strncpy( temp, search->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 );
Q_snprintf( buf, sizeof( buf ), ".%s", W_ExtFromType( search->wad->lumps[i].type ));
COM_DefaultExtension( temp2, buf, sizeof( temp2 ));
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;
}
}
}
/*
====================
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->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 ));
Q_strncpy( search->filename, wadfile, sizeof( search->filename ));
search->wad = wad;
search->type = SEARCHPATH_WAD;
search->next = fs_searchpaths;
search->flags = flags;
search->pfnPrintInfo = FS_PrintInfo_WAD;
search->pfnClose = FS_Close_WAD;
search->pfnOpenFile = FS_OpenFile_WAD;
search->pfnFileTime = FS_FileTime_WAD;
search->pfnFindFile = FS_FindFile_WAD;
search->pfnSearch = FS_Search_WAD;
fs_searchpaths = search;
Con_Reportf( "Adding wadfile: %s (%i files)\n", wadfile, wad->numlumps );
return true;
}
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_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, NULL, 0, gamedironly );
if( search && search->type == SEARCHPATH_WAD )
return W_ReadLump( search->wad, &search->wad->lumps[index], lumpsizeptr );
return NULL;
}