2018-01-22 19:18:04 +00:00
# include "qcc.h"
2018-09-01 04:18:08 +00:00
# if !defined(MINIMAL) && !defined(OMIT_QCC)
2018-01-22 19:18:04 +00:00
# include <time.h>
2020-10-26 06:30:35 +00:00
# ifdef _WIN32
# include <sys/types.h>
# include <sys/stat.h>
# else
2019-01-15 14:12:49 +00:00
# include <unistd.h>
2020-10-26 06:30:35 +00:00
# include <dirent.h>
# include <sys/stat.h>
2019-01-15 14:12:49 +00:00
# endif
2018-01-22 19:18:04 +00:00
void QCC_Canonicalize ( char * fullname , size_t fullnamesize , const char * newfile , const char * base ) ;
2018-03-04 14:41:16 +00:00
//package formats:
//pakzip - files are uncompressed, with both a pak header and a zip trailer, allowing it to be read as either type of file.
//zip - standard zips
//spanned zip - the full list of files is written into a separate central-directory-only zip, the actual file data comes from regular zips named foo.z##/.p## instead of foo.zip/foo.pk3
2018-01-22 19:18:04 +00:00
/*
dataset common
{
output default data . pk3
output logic textures . pk3
}
dataset desktop
{
output tex textures_pc . pk3
}
dataset mobile
{
output tex textures_mobile . pk3
}
input pak0 . pk3
rule dxt1 {
dataset desktop
output tex
newext dds
command " @ \" c:/program files/Compressonator/CompressonatorCLI \" -fd DXT1 $input $output "
}
rule etc2 {
dataset mobile
output tex
newext ktx
command " @ \" c:/program files/Compressonator/CompressonatorCLI \" -fd ETC2 $input $output "
}
logic {
progs . dat
}
class texa0 {
output tex
desktop : dxt1
mobile : etc2
}
texa0
{
gfx / conback . txt
}
*/
2018-03-04 14:41:16 +00:00
# define quint64_t long long
# define qofs_t size_t
2018-01-22 19:18:04 +00:00
# define countof(x) (sizeof(x) / sizeof((x)[0]))
struct pkgctx_s
{
2018-11-19 06:37:25 +00:00
void ( * messagecallback ) ( void * userctx , const char * message , . . . ) ;
2018-01-22 19:18:04 +00:00
void * userctx ;
char * listfile ;
2018-03-04 14:41:16 +00:00
pbool test ;
2018-01-22 19:18:04 +00:00
pbool readoldpacks ;
2019-01-15 14:12:49 +00:00
char gamepath [ MAX_OSPATH ] ;
char sourcepath [ MAX_OSPATH ] ;
2018-01-22 19:18:04 +00:00
time_t buildtime ;
//skips the file if its listed in one of these packages, unless the modification time on disk is newer.
struct oldpack_s
{
struct oldpack_s * next ;
char filename [ 128 ] ;
size_t numfiles ;
2020-10-26 06:30:35 +00:00
unsigned int part ;
2018-01-22 19:18:04 +00:00
struct
{
char name [ 128 ] ;
2018-03-04 14:41:16 +00:00
unsigned short zmethod ;
unsigned int zcrc ;
qofs_t zhdrofs ;
qofs_t rawsize ;
qofs_t zipsize ;
unsigned short dostime ;
unsigned short dosdate ;
2018-01-22 19:18:04 +00:00
} * file ;
} * oldpacks ;
struct dataset_s
{
struct dataset_s * next ;
//these are the output pk3s from this package.
struct output_s
{
struct output_s * next ;
char code [ 128 ] ;
char filename [ 128 ] ;
struct file_s * files ;
2018-03-04 14:41:16 +00:00
pbool usediffs ;
unsigned int numparts ;
struct oldpack_s * oldparts ;
2018-01-22 19:18:04 +00:00
} * outputs ;
char name [ 1 ] ;
} * datasets ;
struct rule_s
{
struct rule_s * next ;
char name [ 128 ] ;
int dropfile : 1 ;
char * newext ;
char * command ;
} * rules ;
struct class_s
{
char name [ 128 ] ;
struct class_s * next ;
//the output package codename to write to. class is skipped if the dataset doesn't include that name.
char outname [ 128 ] ;
struct
{
struct dataset_s * set ;
struct rule_s * rule ;
} dataset [ 8 ] ;
struct rule_s * defaultrule ;
struct file_s
{
struct file_s * next ;
char name [ 128 ] ;
//temp data for tracking what's getting written.
struct
{
char name [ 128 ] ;
struct file_s * nextwrite ;
struct rule_s * rule ;
2018-03-04 14:41:16 +00:00
unsigned int zdisk ;
unsigned short zmethod ;
2018-01-22 19:18:04 +00:00
unsigned int zcrc ;
2018-03-04 14:41:16 +00:00
qofs_t zhdrofs ;
qofs_t pakofs ;
qofs_t rawsize ;
qofs_t zipsize ;
unsigned short dostime ;
unsigned short dosdate ;
time_t timestamp ;
2018-01-22 19:18:04 +00:00
} write ;
} * files ;
} * classes ;
} ;
2018-03-04 14:41:16 +00:00
# ifdef _WIN32
static time_t filetime_to_timet ( FILETIME ft )
{
ULARGE_INTEGER ull ;
ull . LowPart = ft . dwLowDateTime ;
ull . HighPart = ft . dwHighDateTime ;
return ull . QuadPart / 10000000ULL - 11644473600ULL ;
}
# endif
2018-01-22 19:18:04 +00:00
static struct rule_s * PKG_FindRule ( struct pkgctx_s * ctx , char * code )
{
struct rule_s * o ;
for ( o = ctx - > rules ; o ; o = o - > next )
{
if ( ! strcmp ( o - > name , code ) )
return o ;
}
return NULL ;
}
static struct class_s * PKG_FindClass ( struct pkgctx_s * ctx , char * code )
{
struct class_s * c ;
for ( c = ctx - > classes ; c ; c = c - > next )
{
if ( ! strcmp ( c - > name , code ) )
return c ;
}
return NULL ;
}
2020-10-26 06:30:35 +00:00
static struct dataset_s * PKG_FindDataset ( struct pkgctx_s * ctx , const char * code )
2018-01-22 19:18:04 +00:00
{
struct dataset_s * o ;
for ( o = ctx - > datasets ; o ; o = o - > next )
{
if ( ! strcmp ( o - > name , code ) )
return o ;
}
return NULL ;
}
2020-10-26 06:30:35 +00:00
static struct dataset_s * PKG_GetDataset ( struct pkgctx_s * ctx , const char * code )
2018-01-22 19:18:04 +00:00
{
struct dataset_s * s = PKG_FindDataset ( ctx , code ) ;
if ( ! s )
{
s = malloc ( sizeof ( * s ) + strlen ( code ) ) ;
strcpy ( s - > name , code ) ;
s - > outputs = NULL ;
s - > next = ctx - > datasets ;
ctx - > datasets = s ;
}
return s ;
}
static pbool PKG_SkipWhite ( struct pkgctx_s * ctx , pbool linebreak )
{
for ( ; ; )
{
if ( qcc_iswhite ( * ctx - > listfile ) )
{
if ( qcc_islineending ( ctx - > listfile [ 0 ] , ctx - > listfile [ 1 ] ) & & ! linebreak )
return false ;
ctx - > listfile + + ;
continue ;
}
if ( ctx - > listfile [ 0 ] = = ' / ' & & ctx - > listfile [ 1 ] = = ' / ' )
{
while ( ! qcc_islineending ( ctx - > listfile [ 0 ] , ctx - > listfile [ 1 ] ) )
ctx - > listfile + + ;
continue ;
}
if ( ctx - > listfile [ 0 ] = = ' / ' & & ctx - > listfile [ 1 ] = = ' * ' )
{
ctx - > listfile + = 2 ;
while ( * ctx - > listfile )
{
if ( ctx - > listfile [ 0 ] = = ' * ' & & ctx - > listfile [ 1 ] = = ' / ' )
{
ctx - > listfile + = 2 ;
break ;
}
ctx - > listfile + + ;
}
continue ;
}
break ;
}
return true ;
}
static pbool PKG_GetToken ( struct pkgctx_s * ctx , char * token , size_t sizeoftoken , pbool linebreak )
{
if ( ! PKG_SkipWhite ( ctx , linebreak ) )
return false ;
if ( * ctx - > listfile )
{
while ( * ctx - > listfile )
{
if ( qcc_iswhite ( * ctx - > listfile ) )
break ;
* token = * ctx - > listfile + + ;
if ( sizeoftoken > 1 )
{
token + + ;
sizeoftoken - - ;
}
}
* token = 0 ;
return true ;
}
return false ;
}
static pbool PKG_GetStringToken ( struct pkgctx_s * ctx , char * token , size_t sizeoftoken )
{
if ( ! PKG_SkipWhite ( ctx , false ) )
return false ;
if ( * ctx - > listfile = = ' \" ' )
{
ctx - > listfile + + ;
while ( * ctx - > listfile )
{
if ( * ctx - > listfile = = ' \" ' )
{
ctx - > listfile + + ;
break ;
}
else if ( * ctx - > listfile = = ' \\ ' )
{
ctx - > listfile + + ;
switch ( * ctx - > listfile + + )
{
case ' \" ' : * token = ' \" ' ; break ;
case ' \\ ' : * token = ' \\ ' ; break ;
case ' \r ' : * token = ' \r ' ; break ;
case ' \n ' : * token = ' \n ' ; break ;
case ' \t ' : * token = ' \t ' ; break ;
default : * token = ' ? ' ; break ;
}
sizeoftoken - - ;
}
else
* token = * ctx - > listfile + + ;
if ( sizeoftoken > 1 )
{
token + + ;
sizeoftoken - - ;
}
}
* token = 0 ;
return true ;
}
return false ;
}
static pbool PKG_Expect ( struct pkgctx_s * ctx , char * token )
{
char tok [ 128 ] ;
if ( PKG_GetToken ( ctx , tok , sizeof ( tok ) , true ) )
{
if ( ! strcmp ( tok , token ) )
return true ;
}
ctx - > messagecallback ( ctx - > userctx , " Expected '%s', found '%s' \n " , token , tok ) ;
return false ;
}
static void PKG_ReplaceString ( char * str , char * find , char * newpart )
{
char * oldpart ;
size_t oldlen = strlen ( find ) ;
size_t nlen = strlen ( newpart ) ;
while ( ( oldpart = strstr ( str , find ) ) )
{
memmove ( oldpart + nlen , oldpart + oldlen , strlen ( oldpart + oldlen ) + 1 ) ;
memmove ( oldpart , newpart , nlen ) ;
str = oldpart + nlen ;
}
}
2018-03-04 14:41:16 +00:00
static void PKG_CreateOutput ( struct pkgctx_s * ctx , struct dataset_s * s , const char * code , const char * filename , pbool diff )
2018-01-22 19:18:04 +00:00
{
2019-01-15 14:12:49 +00:00
char path [ MAX_OSPATH ] ;
2018-01-22 19:18:04 +00:00
char date [ 64 ] ;
struct output_s * o ;
for ( o = s - > outputs ; o ; o = o - > next )
{
if ( ! strcmp ( o - > code , code ) )
{
ctx - > messagecallback ( ctx - > userctx , " Dataset '%s' defined with dupe output \n " , s - > name , code ) ;
return ;
}
}
if ( strlen ( code ) > = sizeof ( o - > code ) )
{
ctx - > messagecallback ( ctx - > userctx , " Output '%s' name too long \n " , code ) ;
return ;
}
strcpy ( path , filename ) ;
strftime ( date , sizeof ( date ) , " %Y%m%d " , localtime ( & ctx - > buildtime ) ) ;
PKG_ReplaceString ( path , " $date " , date ) ;
o = malloc ( sizeof ( * o ) ) ;
memset ( o , 0 , sizeof ( * o ) ) ;
strcpy ( o - > code , code ) ;
2018-03-04 14:41:16 +00:00
o - > usediffs = diff ;
2018-01-22 19:18:04 +00:00
QCC_Canonicalize ( o - > filename , sizeof ( o - > filename ) , path , ctx - > gamepath ) ;
o - > next = s - > outputs ;
s - > outputs = o ;
2020-10-26 06:30:35 +00:00
if ( diff )
{
char * end = path + strlen ( path ) - 2 ;
unsigned int i ;
for ( i = 0 ; i < = 99 ; i + + )
{
# ifdef _WIN32
struct _stat statbuf ;
# else
struct stat statbuf ;
# endif
sprintf ( end , " %02u " , i + 1 ) ;
# ifdef _WIN32
//FIXME: use the utf16 version because microsoft suck and don't allow utf-8
if ( _stat ( path , & statbuf ) = = 0 )
# else
if ( stat ( path , & statbuf ) = = 0 )
# endif
{
struct oldpack_s * span = malloc ( sizeof ( * span ) ) ;
strcpy ( span - > filename , path ) ;
span - > numfiles = 0 ;
span - > file = NULL ;
span - > next = o - > oldparts ;
span - > part = i ;
o - > oldparts = span ;
}
}
}
2018-01-22 19:18:04 +00:00
}
2018-03-04 14:41:16 +00:00
static void PKG_ParseOutput ( struct pkgctx_s * ctx , pbool diff )
2018-01-22 19:18:04 +00:00
{
struct dataset_s * s ;
char name [ 128 ] ;
char prop [ 128 ] ;
char fname [ 128 ] ;
if ( ! PKG_GetToken ( ctx , name , sizeof ( name ) , false ) )
{
ctx - > messagecallback ( ctx - > userctx , " Output: Expected name \n " ) ;
return ;
}
if ( PKG_GetStringToken ( ctx , prop , sizeof ( prop ) ) )
{
s = PKG_GetDataset ( ctx , " core " ) ;
2018-03-04 14:41:16 +00:00
PKG_CreateOutput ( ctx , s , name , prop , diff ) ;
2018-01-22 19:18:04 +00:00
}
else
{
if ( ! PKG_Expect ( ctx , " { " ) )
return ;
while ( PKG_GetToken ( ctx , prop , sizeof ( prop ) , true ) )
{
if ( ! strcmp ( prop , " } " ) )
break ;
else
{
char * e = strchr ( prop , ' : ' ) ;
if ( e & & ! e [ 1 ] )
{
* e = 0 ;
s = PKG_GetDataset ( ctx , prop ) ;
if ( PKG_GetStringToken ( ctx , fname , sizeof ( fname ) ) )
2018-03-04 14:41:16 +00:00
PKG_CreateOutput ( ctx , s , name , fname , diff ) ;
2018-01-22 19:18:04 +00:00
else
ctx - > messagecallback ( ctx - > userctx , " Output '%s[%s]' filename omitted \n " , name , prop ) ;
}
else
ctx - > messagecallback ( ctx - > userctx , " Output '%s' has unknown property '%s' \n " , name , prop ) ;
}
//skip any junk
while ( PKG_GetToken ( ctx , prop , sizeof ( prop ) , false ) )
{
if ( ! strcmp ( prop , " ; " ) )
break ;
}
}
}
}
2019-01-29 20:38:12 +00:00
# ifdef _WIN32
static void PKG_AddOldPack ( struct pkgctx_s * ctx , const char * fname )
2018-01-22 19:18:04 +00:00
{
struct oldpack_s * pack ;
pack = malloc ( sizeof ( * pack ) ) ;
strcpy ( pack - > filename , fname ) ;
pack - > numfiles = 0 ;
pack - > file = NULL ;
pack - > next = ctx - > oldpacks ;
ctx - > oldpacks = pack ;
2019-01-29 20:38:12 +00:00
}
# endif
2020-10-26 06:30:35 +00:00
2018-01-22 19:18:04 +00:00
static void PKG_ParseOldPack ( struct pkgctx_s * ctx )
{
2019-01-15 14:12:49 +00:00
char token [ MAX_OSPATH ] ;
2018-01-22 19:18:04 +00:00
if ( ! PKG_GetStringToken ( ctx , token , sizeof ( token ) ) )
return ;
# ifdef _WIN32
{
2019-01-29 19:41:31 +00:00
char oldpack [ MAX_OSPATH ] ;
2018-01-22 19:18:04 +00:00
WIN32_FIND_DATA fd ;
HANDLE h ;
QCC_Canonicalize ( oldpack , sizeof ( oldpack ) , token , ctx - > gamepath ) ;
h = FindFirstFile ( oldpack , & fd ) ;
if ( h = = INVALID_HANDLE_VALUE )
ctx - > messagecallback ( ctx - > userctx , " wildcard string '%s' found no files \n " , token ) ;
else
{
do
{
QCC_Canonicalize ( token , sizeof ( token ) , fd . cFileName , oldpack ) ;
PKG_AddOldPack ( ctx , token ) ;
} while ( FindNextFile ( h , & fd ) ) ;
}
}
# else
ctx - > messagecallback ( ctx - > userctx , " no wildcard support, sorry \n " ) ;
# endif
}
/*
static void PKG_ParseDataset ( struct pkgctx_s * ctx )
{
struct dataset_s * s ;
char name [ 128 ] ;
char prop [ 128 ] ;
if ( ! PKG_GetToken ( ctx , name , sizeof ( name ) , false ) )
{
ctx - > messagecallback ( ctx - > userctx , " Dataset: Expected name \n " ) ;
return ;
}
if ( strlen ( name ) > = sizeof ( s - > name ) )
{
ctx - > messagecallback ( ctx - > userctx , " Dataset '%s' name too long \n " , name ) ;
return ;
}
s = malloc ( sizeof ( * s ) ) ;
memset ( s , 0 , sizeof ( * s ) ) ;
strcpy ( s - > name , name ) ;
if ( PKG_Expect ( ctx , " { " ) )
{
while ( PKG_GetToken ( ctx , prop , sizeof ( prop ) , true ) )
{
if ( ! strcmp ( prop , " } " ) )
break ;
else if ( ! strcmp ( prop , " output " ) )
{
if ( PKG_GetToken ( ctx , name , sizeof ( name ) , false ) )
if ( PKG_GetStringToken ( ctx , prop , sizeof ( prop ) ) )
{
PKG_CreateOutput ( ctx , s , name , prop ) ;
}
}
else if ( ! strcmp ( prop , " base " ) )
PKG_GetStringToken ( ctx , prop , sizeof ( prop ) ) ;
else
ctx - > messagecallback ( ctx - > userctx , " Dataset '%s' has unknown property '%s' \n " , name , prop ) ;
//skip any junk
while ( PKG_GetToken ( ctx , prop , sizeof ( prop ) , false ) )
{
if ( ! strcmp ( prop , " ; " ) )
break ;
}
}
}
if ( PKG_FindDataset ( ctx , name ) )
ctx - > messagecallback ( ctx - > userctx , " Dataset '%s' is already defined \n " , name ) ;
else
{ //link it in!
s - > next = ctx - > datasets ;
ctx - > datasets = s ;
return ;
}
PKG_DestroyDataset ( s ) ;
return ;
} */
static void PKG_ParseRule ( struct pkgctx_s * ctx )
{
struct rule_s * r ;
char name [ 128 ] ;
char prop [ 128 ] ;
char newext [ 128 ] ;
char command [ 4096 ] ;
int dropfile = false ;
if ( ! PKG_GetToken ( ctx , name , sizeof ( name ) , false ) )
return ;
if ( strlen ( name ) > = sizeof ( r - > name ) )
{
ctx - > messagecallback ( ctx - > userctx , " Rule '%s' name too long \n " , name ) ;
return ;
}
* newext = * command = 0 ;
if ( PKG_Expect ( ctx , " { " ) )
{
while ( PKG_GetToken ( ctx , prop , sizeof ( prop ) , true ) )
{
if ( ! strcmp ( prop , " } " ) )
break ;
else if ( ! strcmp ( prop , " newext " ) )
PKG_GetToken ( ctx , newext , sizeof ( newext ) , false ) ;
else if ( ! strcmp ( prop , " skip " ) )
{
if ( PKG_GetToken ( ctx , prop , sizeof ( prop ) , false ) )
dropfile = atoi ( prop ) ;
else
dropfile = true ;
}
else if ( ! strcmp ( prop , " command " ) )
PKG_GetStringToken ( ctx , command , sizeof ( command ) ) ;
else
ctx - > messagecallback ( ctx - > userctx , " Rule '%s' has unknown property '%s' \n " , name , prop ) ;
//skip any junk
while ( PKG_GetToken ( ctx , prop , sizeof ( prop ) , false ) )
{
if ( ! strcmp ( prop , " ; " ) )
break ;
}
}
}
r = PKG_FindRule ( ctx , name ) ;
if ( r )
{
ctx - > messagecallback ( ctx - > userctx , " Rule %s is already defined \n " , name ) ;
return ;
}
r = malloc ( sizeof ( * r ) ) ;
memset ( r , 0 , sizeof ( * r ) ) ;
strcpy ( r - > name , name ) ;
r - > newext = strdup ( newext ) ;
r - > command = strdup ( command ) ;
r - > dropfile = dropfile ;
r - > next = ctx - > rules ;
ctx - > rules = r ;
}
2019-01-29 20:38:12 +00:00
static void PKG_AddClassFile ( struct pkgctx_s * ctx , struct class_s * c , const char * fname , time_t mtime )
2018-01-22 19:18:04 +00:00
{
struct file_s * f ;
2018-03-04 14:41:16 +00:00
struct tm * t ;
2018-01-22 19:18:04 +00:00
if ( strlen ( fname ) > = sizeof ( f - > name ) )
{
ctx - > messagecallback ( ctx - > userctx , " File name '%s' too long in class %s \n " , fname , c - > name ) ;
return ;
}
f = malloc ( sizeof ( * f ) ) ;
memset ( f , 0 , sizeof ( * f ) ) ;
strcpy ( f - > name , fname ) ;
2018-03-04 14:41:16 +00:00
f - > write . timestamp = mtime ;
t = localtime ( & f - > write . timestamp ) ;
f - > write . dostime = ( t - > tm_sec > > 1 ) | ( t - > tm_min < < 5 ) | ( t - > tm_hour < < 11 ) ;
f - > write . dosdate = ( t - > tm_mday < < 0 ) | ( t - > tm_mon < < 5 ) | ( ( t - > tm_year + 1900 - 1980 ) < < 9 ) ;
2018-01-22 19:18:04 +00:00
f - > next = c - > files ;
c - > files = f ;
2019-01-29 20:38:12 +00:00
}
2018-01-22 19:18:04 +00:00
static void PKG_AddClassFiles ( struct pkgctx_s * ctx , struct class_s * c , const char * fname )
{
# ifdef _WIN32
WIN32_FIND_DATA fd ;
HANDLE h ;
char basepath [ MAX_PATH ] ;
2018-03-04 14:41:16 +00:00
QCC_Canonicalize ( basepath , sizeof ( basepath ) , fname , ctx - > sourcepath ) ;
2018-01-22 19:18:04 +00:00
h = FindFirstFile ( basepath , & fd ) ;
if ( h = = INVALID_HANDLE_VALUE )
ctx - > messagecallback ( ctx - > userctx , " wildcard string '%s' found no files \n " , fname ) ;
else
{
do
{
QCC_Canonicalize ( basepath , sizeof ( basepath ) , fd . cFileName , fname ) ;
2018-03-04 14:41:16 +00:00
PKG_AddClassFile ( ctx , c , basepath , filetime_to_timet ( fd . ftLastWriteTime ) ) ;
2018-01-22 19:18:04 +00:00
} while ( FindNextFile ( h , & fd ) ) ;
}
# else
2020-10-26 06:30:35 +00:00
DIR * dir ;
struct dirent * ent ;
char basepath [ MAX_OSPATH ] , tmppath [ MAX_OSPATH ] ;
struct stat statbuf ;
QCC_Canonicalize ( basepath , sizeof ( basepath ) , fname , ctx - > sourcepath ) ;
QC_strlcat ( basepath , " / " , sizeof ( basepath ) ) ;
dir = opendir ( basepath ) ;
if ( ! dir )
{
ctx - > messagecallback ( ctx - > userctx , " unable to open dir %s \n " , basepath ) ;
return ;
}
while ( ( ent = readdir ( dir ) ) )
{
if ( * ent - > d_name = = ' . ' )
continue ;
QCC_Canonicalize ( basepath , sizeof ( basepath ) , ent - > d_name , fname ) ;
QCC_Canonicalize ( tmppath , sizeof ( tmppath ) , basepath , ctx - > sourcepath ) ;
if ( stat ( tmppath , & statbuf ) ! = 0 )
continue ;
switch ( statbuf . st_mode & S_IFMT )
{
default : //some weird file type. shouldn't be a symlink sadly.
// ctx->messagecallback(ctx->userctx, "found weird %s\n", basepath);
break ;
case S_IFDIR :
QC_strlcat ( basepath , " / " , sizeof ( basepath ) ) ;
// ctx->messagecallback(ctx->userctx, "found dir %s\n", basepath);
PKG_AddClassFiles ( ctx , c , basepath ) ;
break ;
case S_IFREG :
// ctx->messagecallback(ctx->userctx, "found file %s\n", basepath);
PKG_AddClassFile ( ctx , c , basepath , statbuf . st_mtime ) ;
break ;
}
}
closedir ( dir ) ;
2018-01-22 19:18:04 +00:00
# endif
}
static void PKG_ParseClass ( struct pkgctx_s * ctx , char * output )
{
struct class_s * c ;
struct rule_s * r ;
struct dataset_s * s ;
char * e ;
char name [ 128 ] ;
char prop [ 128 ] ;
size_t u ;
if ( output )
{
if ( ! PKG_Expect ( ctx , " { " ) )
return ;
* name = 0 ;
}
else if ( ! PKG_GetToken ( ctx , name , sizeof ( name ) , false ) )
return ;
if ( output | | ! strcmp ( name , " { " ) )
{
c = malloc ( sizeof ( * c ) ) ;
memset ( c , 0 , sizeof ( * c ) ) ;
strcpy ( c - > name , " " ) ;
strcpy ( c - > outname , ( output & & * output ) ? output : " default " ) ;
c - > next = ctx - > classes ;
ctx - > classes = c ;
}
else
{
if ( strlen ( name ) > = sizeof ( c - > name ) )
{
ctx - > messagecallback ( ctx - > userctx , " Class '%s' name too long \n " , name ) ;
return ;
}
c = PKG_FindClass ( ctx , name ) ;
if ( ! c )
{
c = malloc ( sizeof ( * c ) ) ;
memset ( c , 0 , sizeof ( * c ) ) ;
strcpy ( c - > name , name ) ;
strcpy ( c - > outname , ( output & & * output ) ? output : " default " ) ;
c - > next = ctx - > classes ;
ctx - > classes = c ;
}
if ( ! PKG_Expect ( ctx , " { " ) )
return ;
}
{
while ( PKG_GetToken ( ctx , prop , sizeof ( prop ) , true ) )
{
if ( ! strcmp ( prop , " } " ) )
break ;
else if ( ! strcmp ( prop , " output " ) )
PKG_GetToken ( ctx , c - > outname , sizeof ( c - > outname ) , false ) ;
else if ( ! strcmp ( prop , " rule " ) )
{
if ( PKG_GetToken ( ctx , prop , sizeof ( prop ) , false ) )
{
if ( c - > defaultrule )
ctx - > messagecallback ( ctx - > userctx , " Class '%s' already has a default rule \n " , name ) ;
c - > defaultrule = PKG_FindRule ( ctx , prop ) ;
if ( ! c - > defaultrule )
ctx - > messagecallback ( ctx - > userctx , " Class '%s' specifies unknown rule %s \n " , name , prop ) ;
}
}
else
{
e = strchr ( prop , ' : ' ) ;
if ( e & & ! e [ 1 ] )
{
* e = 0 ;
s = PKG_FindDataset ( ctx , prop ) ;
PKG_GetToken ( ctx , prop , sizeof ( prop ) , false ) ;
if ( s )
{
r = PKG_FindRule ( ctx , prop ) ;
for ( u = 0 ; ; u + + )
{
if ( u = = countof ( c - > dataset ) )
{
ctx - > messagecallback ( ctx - > userctx , " Class '%s' specialises for too many datasets \n " , c - > name , s - > name ) ;
break ;
}
if ( c - > dataset [ u ] . set = = s )
ctx - > messagecallback ( ctx - > userctx , " Class '%s' already defines a rule for dataset '%s' \n " , c - > name , s - > name ) ;
else if ( ! c - > dataset [ u ] . set )
{
c - > dataset [ u ] . set = s ;
c - > dataset [ u ] . rule = r ;
break ;
}
}
}
}
else if ( strchr ( prop , ' . ' ) )
{
2018-03-04 14:41:16 +00:00
// if (strchr(prop, '*') || strchr(prop, '?'))
2018-01-22 19:18:04 +00:00
PKG_AddClassFiles ( ctx , c , prop ) ;
2018-03-04 14:41:16 +00:00
// else
// PKG_AddClassFile(ctx, c, prop);
2018-01-22 19:18:04 +00:00
}
else
ctx - > messagecallback ( ctx - > userctx , " Class '%s' has unknown property '%s' \n " , name , prop ) ;
}
//skip any junk
while ( PKG_GetToken ( ctx , prop , sizeof ( prop ) , false ) )
{
if ( ! strcmp ( prop , " ; " ) )
break ;
}
}
}
}
static void PKG_ParseClassFiles ( struct pkgctx_s * ctx , struct class_s * c )
{
char prop [ 128 ] ;
if ( PKG_Expect ( ctx , " { " ) )
{
while ( PKG_GetToken ( ctx , prop , sizeof ( prop ) , true ) )
{
if ( ! strcmp ( prop , " } " ) )
break ;
if ( ! strcmp ( prop , " ; " ) )
continue ;
2018-03-04 14:41:16 +00:00
// if (strchr(prop, '*') || strchr(prop, '?'))
2018-01-22 19:18:04 +00:00
PKG_AddClassFiles ( ctx , c , prop ) ;
2018-03-04 14:41:16 +00:00
// else
// PKG_AddClassFile(ctx, c, prop);
2018-01-22 19:18:04 +00:00
}
}
}
# ifdef AVAIL_ZLIB
# include <zlib.h>
static unsigned int PKG_DeflateToFile ( FILE * f , unsigned int rawsize , void * in , int method )
{
char out [ 8192 ] ;
int i = 0 ;
z_stream strm = {
( char * ) in ,
rawsize ,
0 ,
out ,
sizeof ( out ) ,
0 ,
NULL ,
NULL ,
NULL ,
NULL ,
NULL ,
Z_BINARY ,
0 ,
0
} ;
if ( method = = 8 )
deflateInit2 ( & strm , 9 , Z_DEFLATED , - MAX_WBITS , 9 , Z_DEFAULT_STRATEGY ) ; //zip deflate compression
else
deflateInit ( & strm , Z_BEST_COMPRESSION ) ; //zlib compression
while ( deflate ( & strm , Z_FINISH ) = = Z_OK )
{
fwrite ( out , 1 , sizeof ( out ) - strm . avail_out , f ) ; //compress in chunks of 8192. Saves having to allocate a huge-mega-big buffer
i + = sizeof ( out ) - strm . avail_out ;
strm . next_out = out ;
strm . avail_out = sizeof ( out ) ;
}
2018-03-04 14:41:16 +00:00
deflateEnd ( & strm ) ;
2018-01-22 19:18:04 +00:00
fwrite ( out , 1 , sizeof ( out ) - strm . avail_out , f ) ;
i + = sizeof ( out ) - strm . avail_out ;
return i ;
}
# endif
# ifdef _WIN32
static void StupidWindowsPopenAlternativeCrap ( struct pkgctx_s * ctx , char * commandline )
{
PROCESS_INFORMATION piProcInfo = { 0 } ;
SECURITY_ATTRIBUTES saAttr = { sizeof ( SECURITY_ATTRIBUTES ) , NULL , TRUE } ;
STARTUPINFO siStartInfo = { sizeof ( STARTUPINFO ) } ;
HANDLE readpipe = INVALID_HANDLE_VALUE ;
HANDLE writepipe = INVALID_HANDLE_VALUE ;
siStartInfo . hStdError = siStartInfo . hStdOutput = siStartInfo . hStdInput = INVALID_HANDLE_VALUE ;
if ( CreatePipe ( & readpipe , & siStartInfo . hStdOutput , & saAttr , 0 ) )
{
if ( CreatePipe ( & siStartInfo . hStdInput , & writepipe , & saAttr , 0 ) )
{
SetHandleInformation ( readpipe , HANDLE_FLAG_INHERIT , 0 ) ;
SetHandleInformation ( writepipe , HANDLE_FLAG_INHERIT , 0 ) ;
siStartInfo . hStdError = siStartInfo . hStdOutput ;
siStartInfo . dwFlags | = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW /*ZOMGWTFBBQ*/ ;
if ( ! CreateProcess ( NULL , ( * commandline = = ' @ ' ) ? commandline + 1 : commandline , NULL , NULL , TRUE , 0 , NULL , NULL , & siStartInfo , & piProcInfo ) )
ctx - > messagecallback ( ctx - > userctx , " Unable to execute command %s \n " , commandline ) ;
else
{
CloseHandle ( piProcInfo . hProcess ) ;
CloseHandle ( piProcInfo . hThread ) ;
}
}
}
CloseHandle ( siStartInfo . hStdOutput ) ;
CloseHandle ( siStartInfo . hStdInput ) ;
CloseHandle ( writepipe ) ;
for ( ; ; )
{
char buf [ 64 ] ;
DWORD SHOUTY ;
if ( ! ReadFile ( readpipe , buf , sizeof ( buf ) - 1 , & SHOUTY , NULL ) | | SHOUTY = = 0 )
break ;
if ( * commandline = = ' @ ' )
continue ;
buf [ SHOUTY ] = 0 ;
ctx - > messagecallback ( ctx - > userctx , " %s " , buf ) ;
}
CloseHandle ( readpipe ) ;
}
# endif
static void * PKG_OpenSourceFile ( struct pkgctx_s * ctx , struct file_s * file , size_t * fsize )
{
char fullname [ 1024 ] ;
FILE * f ;
char * data ;
size_t size ;
struct rule_s * rule = file - > write . rule ;
* fsize = 0 ;
2018-03-04 14:41:16 +00:00
QCC_Canonicalize ( fullname , sizeof ( fullname ) , file - > name , ctx - > sourcepath ) ;
strcpy ( file - > write . name , file - > name ) ;
2018-01-22 19:18:04 +00:00
2020-10-26 06:30:35 +00:00
//WIN32 FIXME: use the utf16 version because microsoft suck and don't allow utf-8
2018-01-22 19:18:04 +00:00
f = fopen ( fullname , " rb " ) ;
if ( ! f )
return NULL ;
if ( rule )
ctx - > messagecallback ( ctx - > userctx , " \t \t Processing %s (%s) \n " , file - > name , rule - > name ) ;
else
ctx - > messagecallback ( ctx - > userctx , " \t \t Compressing %s \n " , file - > name ) ;
if ( rule )
{
data = strrchr ( file - > write . name , ' . ' ) ;
if ( ! data )
data = file - > write . name + strlen ( file - > write . name ) ;
if ( strchr ( rule - > newext , ' . ' ) )
strcpy ( data , rule - > newext ) ; //note: this allows weird _foo.tga postfixes.
else
{
* data = ' . ' ;
strcpy ( data + 1 , rule - > newext ) ;
}
if ( rule - > command )
{
int i ;
char commandline [ 4096 ] ;
char * cmd ;
char tempname [ 1024 ] ;
//generate a sequenced temp filename
//run the external tool to write that file
//read the temp file.
//delete temp file...
fclose ( f ) ;
2018-03-04 14:41:16 +00:00
QCC_Canonicalize ( tempname , sizeof ( tempname ) , file - > write . name , ctx - > sourcepath ) ;
2018-01-22 19:18:04 +00:00
f = fopen ( tempname , " rb " ) ;
if ( f )
{
fclose ( f ) ;
ctx - > messagecallback ( ctx - > userctx , " Temp file %s already exists... not replacing+deleting \n " , tempname ) ;
return NULL ;
}
for ( i = 0 , cmd = rule - > command ; * cmd & & i < countof ( commandline ) - 1 ; )
{
if ( ! strncmp ( cmd , " $input " , 6 ) )
{
strcpy ( & commandline [ i ] , fullname ) ;
i + = strlen ( & commandline [ i ] ) ;
cmd + = 6 ;
}
else if ( ! strncmp ( cmd , " $output " , 7 ) )
{
strcpy ( & commandline [ i ] , tempname ) ;
i + = strlen ( & commandline [ i ] ) ;
cmd + = 7 ;
}
else
commandline [ i + + ] = * cmd + + ;
}
commandline [ i ] = 0 ;
// ctx->messagecallback(ctx->userctx, "Commandline is %s\n", commandline);
# ifdef _WIN32 //windows is so fucking useless sometimes. sure, _popen 'works'... its just perverse enough that its not an option, forcing system-specific crap in anything that isn't originally from unix... maybe it is just incompetence? still feels like malice to me.
StupidWindowsPopenAlternativeCrap ( ctx , commandline ) ;
# else
{
FILE * p ;
p = popen ( ( * commandline = = ' @ ' ) ? commandline + 1 : commandline , " rt " ) ;
if ( ! p )
{
ctx - > messagecallback ( ctx - > userctx , " Unable to execute command \n " , tempname ) ;
return NULL ;
}
while ( fgets ( commandline , sizeof ( commandline ) , p ) )
ctx - > messagecallback ( ctx - > userctx , " %s " , commandline ) ;
if ( feof ( p ) )
2019-01-15 14:12:49 +00:00
ctx - > messagecallback ( ctx - > userctx , " Process returned %d \n " , pclose ( p ) ) ;
2018-01-22 19:18:04 +00:00
else
{
2019-01-15 14:12:49 +00:00
fprintf ( stderr , " Error: Failed to read the pipe to the end. \n " ) ;
pclose ( p ) ;
2018-01-22 19:18:04 +00:00
}
}
# endif
f = fopen ( tempname , " rb " ) ;
if ( ! f )
{
ctx - > messagecallback ( ctx - > userctx , " Temp file %s wasn't created \n " , tempname ) ;
return NULL ;
}
fseek ( f , 0 , SEEK_END ) ;
size = ftell ( f ) ;
fseek ( f , 0 , SEEK_SET ) ;
data = malloc ( size + 1 ) ;
fread ( data , 1 , size , f ) ;
fclose ( f ) ;
* fsize = size ;
2019-01-15 14:12:49 +00:00
# ifdef _WIN32
2018-01-22 19:18:04 +00:00
_unlink ( tempname ) ;
2019-01-15 14:12:49 +00:00
# else
unlink ( tempname ) ;
# endif
2018-01-22 19:18:04 +00:00
return data ;
}
}
fseek ( f , 0 , SEEK_END ) ;
size = ftell ( f ) ;
fseek ( f , 0 , SEEK_SET ) ;
data = malloc ( size + 1 ) ;
fread ( data , 1 , size , f ) ;
fclose ( f ) ;
* fsize = size ;
return data ;
}
2020-10-26 06:30:35 +00:00
static pbool PKG_WritePackageData ( struct pkgctx_s * ctx , struct output_s * out , unsigned int index , pbool directoryonly )
2018-01-22 19:18:04 +00:00
{
//helpers to deal with misaligned data. writes little-endian.
2018-03-04 14:41:16 +00:00
# define misbyte(ptr,ofs,data) ((unsigned char*)(ptr))[ofs] = (data)&0xff
# define misshort(ptr,ofs,data) do{misbyte((ptr),(ofs),(data));misbyte((ptr),(ofs)+1,(data)>>8);}while(0)
# define misint(ptr,ofs,data) do{misshort((ptr),(ofs),(data));misshort((ptr),(ofs)+2,(data)>>16);}while(0)
# define misint64(ptr,ofs,data) do{misint((ptr),(ofs),(data));misint((ptr),(ofs)+4,((quint64_t)(data))>>32);}while(0)
qofs_t num = 0 ;
2018-01-22 19:18:04 +00:00
pbool pak = false ;
struct file_s * f ;
char centralheader [ 46 + sizeof ( f - > write . name ) ] ;
2018-03-04 14:41:16 +00:00
qofs_t centraldirsize ;
qofs_t centraldirofs ;
qofs_t z64eocdofs ;
2018-01-22 19:18:04 +00:00
char * filedata ;
FILE * outf ;
struct
{
char magic [ 4 ] ;
unsigned int tabofs ;
unsigned int tabbytes ;
} pakheader = { " PACK " , 0 , 0 } ;
char * ext ;
2018-03-04 14:41:16 +00:00
# define GPF_TRAILINGSIZE (1u<<3)
# define GPF_UTF8 (1u<<11)
2018-01-22 19:18:04 +00:00
# ifdef AVAIL_ZLIB
# define compmethod (pak?0:8) /*Z_DEFLATED*/
# else
# define compmethod 0 /*Z_RAW*/
# endif
2020-10-26 06:30:35 +00:00
if ( ! compmethod & & ! directoryonly & & ! index )
2018-01-22 19:18:04 +00:00
pak = true ; //might as well boost compat...
ext = strrchr ( out - > filename , ' . ' ) ;
2018-03-04 14:41:16 +00:00
if ( ext & & ! QC_strcasecmp ( ext , " .pak " ) & & ! index )
2018-01-22 19:18:04 +00:00
pak = true ;
2020-10-26 06:30:35 +00:00
if ( ! directoryonly )
{
for ( f = out - > files ; f ; f = f - > write . nextwrite )
{
if ( index ! = f - > write . zdisk )
continue ; //not in this disk...
break ;
}
if ( ! f )
{
ctx - > messagecallback ( ctx - > userctx , " \t \t No files to write to %s \n " , out - > filename ) ;
return false ;
}
}
2018-03-04 14:41:16 +00:00
if ( out - > usediffs & & ! directoryonly )
{
2019-01-15 14:12:49 +00:00
char newname [ MAX_OSPATH ] ;
2018-03-04 14:41:16 +00:00
memcpy ( newname , out - > filename , sizeof ( newname ) ) ;
if ( ext )
{
ext = newname + ( ext - out - > filename ) ;
ext + = 1 ;
if ( * ext )
ext + + ;
QC_snprintfz ( ext , sizeof ( newname ) - ( ext - newname ) , " %02u " , index + 1 ) ;
}
outf = fopen ( newname , " wb " ) ;
}
else
outf = fopen ( out - > filename , " wb " ) ;
2018-01-22 19:18:04 +00:00
if ( ! outf )
{
2020-10-26 06:30:35 +00:00
ctx - > messagecallback ( ctx - > userctx , " \t \t Unable to open %s \n " , out - > filename ) ;
return false ;
2018-01-22 19:18:04 +00:00
}
if ( pak ) //reserve space for the pak header
fwrite ( & pakheader , 1 , sizeof ( pakheader ) , outf ) ;
2018-03-04 14:41:16 +00:00
if ( ! directoryonly )
2018-01-22 19:18:04 +00:00
{
2018-03-04 14:41:16 +00:00
for ( f = out - > files ; f ; f = f - > write . nextwrite )
2018-01-22 19:18:04 +00:00
{
2018-03-04 14:41:16 +00:00
char header [ 32 + sizeof ( f - > write . name ) ] ;
2020-10-26 06:30:35 +00:00
size_t fnamelen ;
2018-03-04 14:41:16 +00:00
size_t hofs ;
unsigned short gpflags = GPF_UTF8 ;
2018-01-22 19:18:04 +00:00
2018-03-04 14:41:16 +00:00
if ( index ! = f - > write . zdisk )
continue ; //not in this disk...
filedata = PKG_OpenSourceFile ( ctx , f , & f - > write . rawsize ) ;
if ( ! filedata )
{
2020-10-26 06:30:35 +00:00
ctx - > messagecallback ( ctx - > userctx , " \t \t Unable to open %s \n " , f - > name ) ;
2018-03-04 14:41:16 +00:00
}
2020-10-26 06:30:35 +00:00
fnamelen = strlen ( f - > write . name ) ;
2018-03-04 14:41:16 +00:00
f - > write . zcrc = QC_encodecrc ( f - > write . rawsize , filedata ) ;
misint ( header , 0 , 0x04034b50 ) ;
misshort ( header , 4 , 45 ) ; //minver
misshort ( header , 6 , gpflags ) ; //general purpose flags
misshort ( header , 8 , 0 ) ; //compression method, 0=store, 8=deflate
misshort ( header , 10 , f - > write . dostime ) ; //lastmodfiletime
misshort ( header , 12 , f - > write . dosdate ) ; //lastmodfiledate
misint ( header , 14 , f - > write . zcrc ) ; //crc32
misint ( header , 18 , f - > write . rawsize ) ; //compressed size
misint ( header , 22 , f - > write . rawsize ) ; //uncompressed size
misshort ( header , 26 , fnamelen ) ; //filename length
2020-10-26 06:30:35 +00:00
misshort ( header , 28 , 0 ) ; //extradata length (filled in later)
memcpy ( header + 30 , f - > write . name , fnamelen ) ;
2018-03-04 14:41:16 +00:00
hofs = 30 + fnamelen ;
2020-10-26 06:30:35 +00:00
//Write extra data here...
2018-03-04 14:41:16 +00:00
misshort ( header , 28 , hofs - ( 30 + fnamelen ) ) ; //extradata length
f - > write . zhdrofs = ftell ( outf ) ;
fwrite ( header , 1 , hofs , outf ) ;
2018-01-22 19:18:04 +00:00
# ifdef AVAIL_ZLIB
2018-03-04 14:41:16 +00:00
if ( f - > write . rawsize & & ( compmethod = = 2 | | compmethod = = 8 ) )
{
gpflags | = 1u < < 1 ;
f - > write . pakofs = 0 ;
2018-01-22 19:18:04 +00:00
2018-03-04 14:41:16 +00:00
f - > write . zmethod = compmethod ;
f - > write . zipsize = PKG_DeflateToFile ( outf , f - > write . rawsize , filedata , compmethod ) ;
}
else
# endif
{
f - > write . zmethod = 0 ;
f - > write . pakofs = ftell ( outf ) ;
f - > write . zipsize = fwrite ( filedata , 1 , f - > write . rawsize , outf ) ;
}
2018-01-22 19:18:04 +00:00
2018-03-04 14:41:16 +00:00
//update the header
misshort ( header , 8 , f - > write . zmethod ) ; //compression method, 0=store, 8=deflate
if ( f - > write . zipsize > 0xffffffff )
{
misint ( header , 18 , 0xffffffff ) ; //compressed size
gpflags | = GPF_TRAILINGSIZE ;
}
else
misint ( header , 18 , f - > write . zipsize ) ; //compressed size
if ( f - > write . rawsize > 0xffffffff )
{
misint ( header , 22 , 0xffffffff ) ; //compressed size
gpflags | = GPF_TRAILINGSIZE ;
}
else
misint ( header , 22 , f - > write . rawsize ) ; //compressed size
misshort ( header , 6 , gpflags ) ; //general purpose flags
2018-01-22 19:18:04 +00:00
fseek ( outf , f - > write . zhdrofs , SEEK_SET ) ;
2018-03-04 14:41:16 +00:00
fwrite ( header , 1 , hofs , outf ) ;
fseek ( outf , 0 , SEEK_END ) ;
if ( gpflags & GPF_TRAILINGSIZE ) //if (gpflags & GPF_TRAILINGSIZE)
{
misint ( header , 0 , 0x08074b50 ) ;
misint ( header , 4 , f - > write . zcrc ) ;
misint64 ( header , 8 , f - > write . zipsize ) ;
misint64 ( header , 16 , f - > write . rawsize ) ;
fwrite ( header , 1 , 24 , outf ) ;
}
free ( filedata ) ;
num + + ;
2018-01-22 19:18:04 +00:00
}
}
if ( pak )
{
struct
{
char name [ 56 ] ;
unsigned int offset ;
2020-10-26 06:30:35 +00:00
unsigned int size ;
2018-01-22 19:18:04 +00:00
} pakentry ;
pakheader . tabofs = ftell ( outf ) ;
2018-03-04 14:41:16 +00:00
//write the pak file table.
2018-01-22 19:18:04 +00:00
for ( f = out - > files , num = 0 ; f ; f = f - > write . nextwrite )
{
2018-03-04 14:41:16 +00:00
if ( index ! = f - > write . zdisk )
continue ; //not in this disk...
2018-01-22 19:18:04 +00:00
memset ( & pakentry , 0 , sizeof ( pakentry ) ) ;
QC_strlcpy ( pakentry . name , f - > write . name , sizeof ( pakentry . name ) ) ;
pakentry . size = ( f - > write . pakofs = = 0 ) ? 0 : f - > write . rawsize ;
pakentry . offset = f - > write . pakofs ;
fwrite ( & pakentry , 1 , sizeof ( pakentry ) , outf ) ;
num + + ;
}
2018-03-04 14:41:16 +00:00
//replace the pak header, then return to the end of the file for the zip end-of-central-directory
pakheader . tabbytes = num * sizeof ( pakentry ) ;
fseek ( outf , 0 , SEEK_SET ) ;
fwrite ( & pakheader , 1 , sizeof ( pakheader ) , outf ) ;
fseek ( outf , 0 , SEEK_END ) ;
2018-01-22 19:18:04 +00:00
}
2018-03-04 14:41:16 +00:00
centraldirofs = ftell ( outf ) ;
2018-01-22 19:18:04 +00:00
for ( f = out - > files , num = 0 ; f ; f = f - > write . nextwrite )
{
2018-03-04 14:41:16 +00:00
size_t hofs ;
2018-01-22 19:18:04 +00:00
size_t fnamelen ;
2018-03-04 14:41:16 +00:00
if ( ! directoryonly & & index ! = f - > write . zdisk )
continue ;
2018-01-22 19:18:04 +00:00
fnamelen = strlen ( f - > write . name ) ;
misint ( centralheader , 0 , 0x02014b50 ) ;
2018-03-04 14:41:16 +00:00
misshort ( centralheader , 4 , ( 3 < < 8 ) | 63 ) ; //ourver
misshort ( centralheader , 6 , 45 ) ; //minver
misshort ( centralheader , 8 , GPF_UTF8 ) ; //general purpose flags
misshort ( centralheader , 10 , f - > write . rawsize ? compmethod : 0 ) ; //compression method, 0=store, 8=deflate
misshort ( centralheader , 12 , f - > write . dostime ) ; //lastmodfiletime
misshort ( centralheader , 14 , f - > write . dosdate ) ; //lastmodfiledate
2018-01-22 19:18:04 +00:00
misint ( centralheader , 16 , f - > write . zcrc ) ; //crc32
misint ( centralheader , 20 , f - > write . zipsize ) ; //compressed size
misint ( centralheader , 24 , f - > write . rawsize ) ; //uncompressed size
misshort ( centralheader , 28 , fnamelen ) ; //filename length
2020-10-26 06:30:35 +00:00
misshort ( centralheader , 30 , 0 ) ; //extradata length (filled in later)
2018-01-22 19:18:04 +00:00
misshort ( centralheader , 32 , 0 ) ; //comment length
2018-03-04 14:41:16 +00:00
misshort ( centralheader , 34 , f - > write . zdisk ) ; //first disk number
2018-01-22 19:18:04 +00:00
misshort ( centralheader , 36 , 0 ) ; //internal file attribs
misint ( centralheader , 38 , 0 ) ; //external file attribs
misint ( centralheader , 42 , f - > write . zhdrofs ) ; //local header offset
strcpy ( centralheader + 46 , f - > write . name ) ;
2018-03-04 14:41:16 +00:00
hofs = 46 + fnamelen ;
if ( f - > write . zdisk > = 0xffff | | f - > write . zhdrofs > = 0xffffffff | | f - > write . rawsize > = 0xffffffff | | f - > write . zipsize > = 0xffffffff )
{
misshort ( centralheader , hofs , 0x0001 ) ; //zip64 tagid
misshort ( centralheader , hofs + 2 , 0x0001 ) ; //zip64 tag size
hofs + = 4 ;
if ( f - > write . rawsize > = 0xffffffff )
{
misint64 ( centralheader , hofs , f - > write . rawsize ) ; //uncompressed size
hofs + = 8 ;
}
if ( f - > write . zipsize > = 0xffffffff )
{
misint64 ( centralheader , hofs , f - > write . zipsize ) ; //compressed size
hofs + = 8 ;
}
if ( f - > write . zhdrofs > = 0xffffffff )
{
misint64 ( centralheader , hofs , f - > write . zhdrofs ) ; //localheader offset
hofs + = 8 ;
}
if ( f - > write . zdisk > = 0xffff )
{
misint ( centralheader , hofs , f - > write . zdisk ) ; //compressed size
hofs + = 4 ;
}
}
misshort ( centralheader , 30 , hofs - ( 46 + fnamelen ) ) ; //extradata length
fwrite ( centralheader , 1 , hofs , outf ) ;
2018-01-22 19:18:04 +00:00
num + + ;
}
2018-03-04 14:41:16 +00:00
centraldirsize = ftell ( outf ) - centraldirofs ;
//zip64 end of central dir
z64eocdofs = ftell ( outf ) ;
misint ( centralheader , 0 , 0x06064b50 ) ;
misint64 ( centralheader , 4 , ( qofs_t ) ( 56 - 16 ) ) ;
misshort ( centralheader , 12 , ( 3 < < 8 ) | 63 ) ; //ver made by = unix|appnote ver
misshort ( centralheader , 14 , 45 ) ; //ver needed
misint ( centralheader , 16 , index ) ; //thisdisk number
misint ( centralheader , 20 , index ) ; //centraldir start disk
misint64 ( centralheader , 24 , num ) ; //centraldir entry count (disk)
misint64 ( centralheader , 32 , num ) ; //centraldir entry count (total)
misint64 ( centralheader , 40 , centraldirsize ) ; //centraldir entry bytes
misint64 ( centralheader , 48 , centraldirofs ) ; //centraldir start offset
fwrite ( centralheader , 1 , 56 , outf ) ;
//zip64 end of central dir locator
misint ( centralheader , 0 , 0x07064b50 ) ;
misint ( centralheader , 4 , index ) ; //centraldir first disk
misint64 ( centralheader , 8 , z64eocdofs ) ;
misint ( centralheader , 16 , index + 1 ) ; //total disk count
fwrite ( centralheader , 1 , 20 , outf ) ;
// centraldirofs = ftell(outf) - centraldirofs;
//write zip end-of-central-directory
2018-01-22 19:18:04 +00:00
misint ( centralheader , 0 , 0x06054b50 ) ;
2018-03-04 14:41:16 +00:00
misshort ( centralheader , 4 , ( index > 0xffff ) ? 0xffff : index ) ; //this disk number
misshort ( centralheader , 6 , ( index > 0xffff ) ? 0xffff : index ) ; //centraldir first disk
misshort ( centralheader , 8 , ( num > 0xffff ) ? 0xffff : num ) ; //centraldir entries
misshort ( centralheader , 10 , ( num > 0xffff ) ? 0xffff : num ) ; //total centraldir entries
misint ( centralheader , 12 , ( centraldirsize > 0xffffffff ) ? 0xffffffff : centraldirsize ) ; //centraldir size
misint ( centralheader , 16 , ( centraldirofs > 0xffffffff ) ? 0xffffffff : centraldirofs ) ; //centraldir offset
2018-01-22 19:18:04 +00:00
misshort ( centralheader , 20 , 0 ) ; //comment length
fwrite ( centralheader , 1 , 22 , outf ) ;
fclose ( outf ) ;
2020-10-26 06:30:35 +00:00
return true ;
2018-01-22 19:18:04 +00:00
}
2019-01-20 01:00:18 +00:00
/*
2018-01-22 19:18:04 +00:00
# include <sys/stat.h>
static time_t PKG_GetFileTime ( const char * filename )
{
struct stat s ;
if ( stat ( filename , & s ) ! = - 1 )
return s . st_mtime ;
}
2019-01-20 01:00:18 +00:00
*/
2018-01-22 19:18:04 +00:00
static void PKG_ReadPackContents ( struct pkgctx_s * ctx , struct oldpack_s * old )
{
# define longfromptr(p) (((p)[0]<<0)|((p)[1]<<8)|((p)[2]<<16)|((p)[3]<<24))
# define shortfromptr(p) (((p)[0]<<0)|((p)[1]<<8))
size_t u , namelen ;
unsigned int foffset ;
unsigned char header [ 46 ] ;
int i ;
FILE * f ;
//ignore packages if we're going to be overwritten.
struct dataset_s * set ;
struct output_s * out ;
for ( set = ctx - > datasets ; set ; set = set - > next )
{
for ( out = set - > outputs ; out ; out = out - > next )
{
if ( ! strcmp ( out - > filename , old - > filename ) )
return ;
}
}
f = fopen ( old - > filename , " rb " ) ;
if ( f )
{
//find end-of-central-dir
2018-03-04 14:41:16 +00:00
//assume no comment
2018-01-22 19:18:04 +00:00
fseek ( f , - 22 , SEEK_END ) ;
fread ( header , 1 , 22 , f ) ;
if ( header [ 0 ] = = ' P ' & & header [ 1 ] = = ' K ' & & header [ 2 ] = = 5 & & header [ 3 ] = = 6 )
{
2020-10-26 06:30:35 +00:00
old - > part = shortfromptr ( header + 4 ) ;
2018-03-04 14:41:16 +00:00
//centraldirstart = shortfromptr(header+6);
2018-01-22 19:18:04 +00:00
old - > numfiles = shortfromptr ( header + 8 ) ;
2018-03-04 14:41:16 +00:00
//numfiles_all = shortfromptr(header+10);
//centaldirsize = shortfromptr(header+12);
2018-01-22 19:18:04 +00:00
foffset = longfromptr ( header + 16 ) ;
2018-03-04 14:41:16 +00:00
//commentength = shortfromptr(header+20);
2018-01-22 19:18:04 +00:00
old - > file = malloc ( sizeof ( * old - > file ) * old - > numfiles ) ;
fseek ( f , foffset , SEEK_SET ) ;
for ( u = 0 ; u < old - > numfiles ; u + + )
{
2018-03-04 14:41:16 +00:00
unsigned int extra_len , comment_len ;
2018-01-22 19:18:04 +00:00
fread ( header , 1 , 46 , f ) ;
//zcrc @ 16
2018-03-04 14:41:16 +00:00
//version_madeby = shortfromptr(header+4);
//version_needed = shortfromptr(header+6);
//gflags = shortfromptr(header+8);
old - > file [ u ] . zmethod = shortfromptr ( header + 10 ) ;
old - > file [ u ] . dostime = shortfromptr ( header + 12 ) ;
2020-10-26 06:30:35 +00:00
old - > file [ u ] . dosdate = shortfromptr ( header + 14 ) ;
2018-03-04 14:41:16 +00:00
old - > file [ u ] . zcrc = longfromptr ( header + 16 ) ;
old - > file [ u ] . zipsize = longfromptr ( header + 20 ) ;
old - > file [ u ] . rawsize = longfromptr ( header + 24 ) ;
2018-01-22 19:18:04 +00:00
namelen = shortfromptr ( header + 28 ) ;
2018-03-04 14:41:16 +00:00
extra_len = shortfromptr ( header + 30 ) ;
comment_len = shortfromptr ( header + 32 ) ;
//disknum = shortfromptr(header+34);
//iattributes = shortfromptr(header+36);
//eattributes = longfromptr(header+38);
//localheaderoffset = longfromptr(header+42);
2018-01-22 19:18:04 +00:00
fread ( old - > file [ u ] . name , 1 , namelen , f ) ;
old - > file [ u ] . name [ namelen ] = 0 ;
2018-03-04 14:41:16 +00:00
i = extra_len + comment_len ;
2018-01-22 19:18:04 +00:00
if ( i )
fseek ( f , i , SEEK_CUR ) ;
}
}
else
{
fseek ( f , 0 , SEEK_SET ) ;
fread ( header , 1 , 12 , f ) ;
if ( header [ 0 ] = = ' P ' & & header [ 1 ] = = ' A ' & & header [ 2 ] = = ' C ' & & header [ 3 ] = = ' K ' )
{
unsigned int ofs = longfromptr ( header + 4 ) ;
unsigned int dsz = longfromptr ( header + 8 ) ;
struct
{
char name [ 56 ] ;
unsigned int size ;
unsigned int offset ;
} * files ;
files = malloc ( dsz ) ;
fseek ( f , ofs , SEEK_SET ) ;
fread ( files , 1 , dsz , f ) ;
old - > numfiles = dsz / sizeof ( * files ) ;
old - > file = malloc ( sizeof ( * old - > file ) * old - > numfiles ) ;
for ( u = 0 ; u < old - > numfiles ; u + + )
{
strcpy ( old - > file [ u ] . name , files [ u ] . name ) ;
2018-03-04 14:41:16 +00:00
old - > file [ u ] . rawsize = files [ u ] . size ;
2018-01-22 19:18:04 +00:00
}
free ( files ) ;
}
else
ctx - > messagecallback ( ctx - > userctx , " %s does not appear to be a package \n " , old - > filename ) ;
}
//walk central directory
fclose ( f ) ;
}
}
2018-03-04 14:41:16 +00:00
static pbool PKG_FileIsModified ( struct pkgctx_s * ctx , struct oldpack_s * old , struct file_s * file )
2018-01-22 19:18:04 +00:00
{
size_t u ;
2018-03-04 14:41:16 +00:00
for ( u = 0 ; u < old - > numfiles ; u + + )
2018-01-22 19:18:04 +00:00
{
2018-03-04 14:41:16 +00:00
//should check filesize etc, but rules and extension changes make that messy
if ( ! strcmp ( old - > file [ u ] . name , file - > name ) )
2018-01-22 19:18:04 +00:00
{
2018-03-04 14:41:16 +00:00
if ( file - > write . dosdate < old - > file [ u ] . dosdate | | ( file - > write . dosdate = = old - > file [ u ] . dosdate & & file - > write . dostime < = old - > file [ u ] . dostime ) )
2018-01-22 19:18:04 +00:00
{
2018-03-04 14:41:16 +00:00
file - > write . zmethod = old - > file [ u ] . zmethod ;
//char name[128];
file - > write . zcrc = old - > file [ u ] . zcrc ;
file - > write . zhdrofs = old - > file [ u ] . zhdrofs ;
file - > write . pakofs = 0 ;
file - > write . rawsize = old - > file [ u ] . rawsize ;
file - > write . zipsize = old - > file [ u ] . zipsize ;
file - > write . dostime = old - > file [ u ] . dostime ;
file - > write . dosdate = old - > file [ u ] . dosdate ;
2018-01-22 19:18:04 +00:00
return false ;
}
}
}
return true ;
}
static void PKG_WriteDataset ( struct pkgctx_s * ctx , struct dataset_s * set )
{
struct class_s * cls ;
struct output_s * out ;
struct file_s * file ;
struct rule_s * rule ;
struct oldpack_s * old ;
size_t u ;
if ( ! ctx - > readoldpacks )
{
ctx - > readoldpacks = true ;
for ( old = ctx - > oldpacks ; old ; old = old - > next )
{ //fixme: strip any wildcarded paks that match an output, to avoid weirdness.
PKG_ReadPackContents ( ctx , old ) ;
}
2018-03-04 14:41:16 +00:00
for ( out = set - > outputs ; out ; out = out - > next )
{
if ( out - > usediffs )
2020-10-26 06:30:35 +00:00
{
for ( old = out - > oldparts ; old ; old = old - > next )
{
PKG_ReadPackContents ( ctx , old ) ;
if ( out - > numparts < = old - > part )
out - > numparts = old - > part + 1 ;
}
2018-03-04 14:41:16 +00:00
}
}
2018-01-22 19:18:04 +00:00
}
ctx - > messagecallback ( ctx - > userctx , " Building dataset %s \n " , set - > name ) ;
for ( cls = ctx - > classes ; cls ; cls = cls - > next )
{
for ( out = set - > outputs ; out ; out = out - > next )
{
if ( ! strcmp ( out - > code , cls - > outname ) )
break ;
}
if ( ! out ) //dataset doesn't name this.
continue ;
rule = cls - > defaultrule ;
for ( u = 0 ; u < countof ( cls - > dataset ) ; u + + )
{
if ( cls - > dataset [ u ] . set = = set )
{
rule = cls - > dataset [ u ] . rule ;
break ;
}
}
if ( rule & & rule - > dropfile )
continue ;
for ( file = cls - > files ; file ; file = file - > next )
{
2018-03-04 14:41:16 +00:00
for ( old = ctx - > oldpacks ; old ; old = old - > next )
{
if ( ! PKG_FileIsModified ( ctx , old , file ) )
break ;
}
if ( old )
{
ctx - > messagecallback ( ctx - > userctx , " \t \t File %s found inside %s \n " , file - > name , old - > filename ) ;
file - > write . zdisk = ~ 0u ;
}
else
{
// ctx->messagecallback(ctx->userctx, "\t\tFile %s, rule %s\n", file->name, rule?rule->name:"");
file - > write . zdisk = out - > numparts ;
for ( old = out - > oldparts ; old ; old = old - > next )
{
if ( ! PKG_FileIsModified ( ctx , old , file ) )
2020-10-26 06:30:35 +00:00
{
file - > write . zdisk = old - > part ;
2018-03-04 14:41:16 +00:00
break ;
2020-10-26 06:30:35 +00:00
}
2018-03-04 14:41:16 +00:00
}
2018-01-22 19:18:04 +00:00
2018-03-04 14:41:16 +00:00
file - > write . nextwrite = out - > files ;
file - > write . rule = rule ;
out - > files = file ;
}
2018-01-22 19:18:04 +00:00
}
}
for ( out = set - > outputs ; out ; out = out - > next )
{
if ( ! out - > files )
{
ctx - > messagecallback ( ctx - > userctx , " \t Output %s[%s] \" %s \" has no files \n " , out - > code , set - > name , out - > filename ) ;
continue ;
}
2018-03-04 14:41:16 +00:00
if ( ctx - > test )
{
for ( file = out - > files ; file ; file = file - > write . nextwrite )
{
if ( file - > write . rule )
ctx - > messagecallback ( ctx - > userctx , " \t \t File %s has changed (rule %s) \n " , file - > name , file - > write . rule - > name ) ;
else
ctx - > messagecallback ( ctx - > userctx , " \t \t File %s has changed \n " , file - > name ) ;
}
}
else
{
ctx - > messagecallback ( ctx - > userctx , " \t Generating %s[%s] \" %s \" \n " , out - > code , set - > name , out - > filename ) ;
2020-10-26 06:30:35 +00:00
if ( PKG_WritePackageData ( ctx , out , out - > numparts , false ) )
{
if ( out - > usediffs )
PKG_WritePackageData ( ctx , out , out - > numparts + 1 , true ) ;
}
2018-03-04 14:41:16 +00:00
}
2018-01-22 19:18:04 +00:00
}
}
void Packager_WriteDataset ( struct pkgctx_s * ctx , char * setname )
{
struct dataset_s * dataset ;
if ( setname & & strcmp ( setname , " * " ) )
{
dataset = PKG_FindDataset ( ctx , setname ) ;
if ( dataset )
PKG_WriteDataset ( ctx , dataset ) ;
else
ctx - > messagecallback ( ctx - > userctx , " Dataset %s not known \n " , setname ) ;
}
else
{
for ( dataset = ctx - > datasets ; dataset ; dataset = dataset - > next )
PKG_WriteDataset ( ctx , dataset ) ;
}
}
2018-11-19 06:37:25 +00:00
struct pkgctx_s * Packager_Create ( void ( * messagecallback ) ( void * userctx , const char * message , . . . ) , void * userctx )
2018-01-22 19:18:04 +00:00
{
struct pkgctx_s * ctx ;
ctx = malloc ( sizeof ( * ctx ) ) ;
memset ( ctx , 0 , sizeof ( * ctx ) ) ;
ctx - > messagecallback = messagecallback ;
ctx - > userctx = userctx ;
2018-03-04 14:41:16 +00:00
ctx - > test = false ;
2018-01-22 19:18:04 +00:00
time ( & ctx - > buildtime ) ;
return ctx ;
}
2018-03-04 14:41:16 +00:00
void Packager_ParseText ( struct pkgctx_s * ctx , char * scripttext )
2018-01-22 19:18:04 +00:00
{
char cmd [ 128 ] ;
2018-03-04 14:41:16 +00:00
ctx - > listfile = scripttext ;
2018-01-22 19:18:04 +00:00
while ( PKG_GetToken ( ctx , cmd , sizeof ( cmd ) , true ) )
{
// if (!strcmp(cmd, "dataset"))
// PKG_ParseDataset(ctx);
if ( ! strcmp ( cmd , " output " ) )
2018-03-04 14:41:16 +00:00
PKG_ParseOutput ( ctx , false ) ;
else if ( ! strcmp ( cmd , " diffoutput " ) | | ! strcmp ( cmd , " splitoutput " ) )
PKG_ParseOutput ( ctx , true ) ;
else if ( ! strcmp ( cmd , " inputdir " ) )
{
2019-01-15 14:12:49 +00:00
char old [ MAX_OSPATH ] ;
2018-03-04 14:41:16 +00:00
memcpy ( old , ctx - > sourcepath , sizeof ( old ) ) ;
if ( PKG_GetStringToken ( ctx , cmd , sizeof ( cmd ) ) )
{
QC_strlcat ( cmd , " / " , sizeof ( cmd ) ) ;
QCC_Canonicalize ( ctx - > sourcepath , sizeof ( ctx - > sourcepath ) , cmd , old ) ;
}
}
2018-01-22 19:18:04 +00:00
else if ( ! strcmp ( cmd , " rule " ) )
PKG_ParseRule ( ctx ) ;
else if ( ! strcmp ( cmd , " class " ) )
PKG_ParseClass ( ctx , NULL ) ;
else if ( ! strcmp ( cmd , " ignore " ) | | ! strcmp ( cmd , " oldpack " ) )
PKG_ParseOldPack ( ctx ) ;
else
{
char * e = strchr ( cmd , ' : ' ) ;
if ( e & & ! e [ 1 ] )
{
* e = 0 ;
PKG_ParseClass ( ctx , cmd ) ;
}
else
{
struct class_s * c = PKG_FindClass ( ctx , cmd ) ;
if ( c )
PKG_ParseClassFiles ( ctx , c ) ;
else
ctx - > messagecallback ( ctx - > userctx , " Unrecognised token at global scope '%s' \n " , cmd ) ;
}
}
//skip any junk
while ( PKG_GetToken ( ctx , cmd , sizeof ( cmd ) , false ) )
{
if ( ! strcmp ( cmd , " ; " ) )
break ;
}
}
2018-03-04 14:41:16 +00:00
}
void Packager_ParseFile ( struct pkgctx_s * ctx , char * scriptname )
{
size_t remaining = 0 ;
2018-04-20 19:09:14 +00:00
char * file = qccprogfuncs - > funcs . parms - > ReadFile ( scriptname , NULL , NULL , & remaining , true ) ;
2018-03-04 14:41:16 +00:00
strcpy ( ctx - > gamepath , scriptname ) ;
strcpy ( ctx - > sourcepath , scriptname ) ;
Packager_ParseText ( ctx , file ) ;
2018-01-22 19:18:04 +00:00
free ( file ) ;
}
void Packager_Destroy ( struct pkgctx_s * ctx )
{
2020-10-26 06:30:35 +00:00
free ( ctx ) ;
}
pbool Packager_CompressDir ( const char * dirname , enum pkgtype_e type , void ( * messagecallback ) ( void * userctx , const char * message , . . . ) , void * userctx )
{
char * ext ;
char filename [ MAX_QPATH ] ;
struct pkgctx_s * ctx = Packager_Create ( messagecallback , userctx ) ;
struct dataset_s * s ;
struct class_s * c ;
QC_strlcpy ( ctx - > sourcepath , dirname , sizeof ( ctx - > sourcepath ) ) ;
ext = strrchr ( ctx - > sourcepath , ' / ' ) ;
if ( * ctx - > sourcepath & & ( ! ext | | ext [ 1 ] ) )
QC_strlcat ( ctx - > sourcepath , " / " , sizeof ( ctx - > sourcepath ) ) ;
QC_strlcpy ( filename , dirname , sizeof ( filename ) ) ;
for ( ; ( ext = strrchr ( filename , ' / ' ) ) & & ! ext [ 1 ] ; * ext = 0 )
;
ext = strrchr ( filename , ' . ' ) ;
if ( ext )
* ext = 0 ;
if ( type = = PACKAGER_PAK )
QC_strlcat ( filename , " .pak " , sizeof ( filename ) ) ;
else
QC_strlcat ( filename , " .pk3 " , sizeof ( filename ) ) ;
s = PKG_GetDataset ( ctx , " default " ) ;
PKG_CreateOutput ( ctx , s , " default " , filename , type = = PACKAGER_PK3_SPANNED ) ;
c = malloc ( sizeof ( * c ) ) ;
memset ( c , 0 , sizeof ( * c ) ) ;
strcpy ( c - > name , " file " ) ;
strcpy ( c - > outname , " default " ) ;
c - > next = ctx - > classes ;
ctx - > classes = c ;
PKG_AddClassFiles ( ctx , c , " " ) ;
Packager_WriteDataset ( ctx , NULL ) ;
Packager_Destroy ( ctx ) ;
return true ;
2018-01-22 19:18:04 +00:00
}
2018-09-01 04:18:08 +00:00
# endif