4d25c073ec
fix crash from qwplayers with invalid modelindexes rework allow_skybox a little. now applies immediately. try to fix the appears-outside-of-map bug, again. dir *.wav now shows a little extra info on mouse-over. try loading music file extensions that we expect to be able to play via ffmpeg, not just the ones that are directly supported. rework the hidden fps_presets, show them with tab completion. fix a possible crash with r_temporalscenecache fix lightmap updates on submodels not happening properly with the scenecache. fix the serious memory leak with scenecache. add r_glsl_pbr cvar to force use of pbr pathways in our glsl. fix bug in alsa output not supporting float output properly. preliminary work to have the mixer use floating point mixing. disabled for now. try to update sys_register_file_associations on linux, still needs work though. try to work around nquake's config quirks, so config files don't get overwritten. repackage quake's conchars in order to add padding. this should avoid extra junk on the outside of glyphs. give fteqcc(commandline version) some extra package extration/creation support. our paks should be more end-user-friendly, and our spanned pk3s are awesome. write rune state to legacy saved games, so we don't ever have the missing-runes bug ever again... git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5780 fc73d0e0-1445-4013-8a0c-d673dee63da5
1794 lines
46 KiB
C
1794 lines
46 KiB
C
#include "qcc.h"
|
|
#if !defined(MINIMAL) && !defined(OMIT_QCC)
|
|
#include <time.h>
|
|
#ifdef _WIN32
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#include <dirent.h>
|
|
#include <sys/stat.h>
|
|
#endif
|
|
void QCC_Canonicalize(char *fullname, size_t fullnamesize, const char *newfile, const char *base);
|
|
|
|
//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
|
|
|
|
/*
|
|
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
|
|
}
|
|
*/
|
|
|
|
#define quint64_t long long
|
|
#define qofs_t size_t
|
|
|
|
#define countof(x) (sizeof(x)/sizeof((x)[0]))
|
|
|
|
struct pkgctx_s
|
|
{
|
|
void (*messagecallback)(void *userctx, const char *message, ...);
|
|
void *userctx;
|
|
|
|
char *listfile;
|
|
|
|
pbool test;
|
|
pbool readoldpacks;
|
|
char gamepath[MAX_OSPATH];
|
|
char sourcepath[MAX_OSPATH];
|
|
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;
|
|
unsigned int part;
|
|
struct
|
|
{
|
|
char name[128];
|
|
|
|
unsigned short zmethod;
|
|
unsigned int zcrc;
|
|
qofs_t zhdrofs;
|
|
qofs_t rawsize;
|
|
qofs_t zipsize;
|
|
unsigned short dostime;
|
|
unsigned short dosdate;
|
|
} *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;
|
|
|
|
pbool usediffs;
|
|
unsigned int numparts;
|
|
struct oldpack_s *oldparts;
|
|
} *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;
|
|
unsigned int zdisk;
|
|
unsigned short zmethod;
|
|
unsigned int zcrc;
|
|
qofs_t zhdrofs;
|
|
qofs_t pakofs;
|
|
qofs_t rawsize;
|
|
qofs_t zipsize;
|
|
unsigned short dostime;
|
|
unsigned short dosdate;
|
|
time_t timestamp;
|
|
} write;
|
|
} *files;
|
|
} *classes;
|
|
};
|
|
|
|
#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
|
|
|
|
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;
|
|
}
|
|
static struct dataset_s *PKG_FindDataset(struct pkgctx_s *ctx, const char *code)
|
|
{
|
|
struct dataset_s *o;
|
|
for (o = ctx->datasets; o; o = o->next)
|
|
{
|
|
if (!strcmp(o->name, code))
|
|
return o;
|
|
}
|
|
return NULL;
|
|
}
|
|
static struct dataset_s *PKG_GetDataset(struct pkgctx_s *ctx, const char *code)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
static void PKG_CreateOutput(struct pkgctx_s *ctx, struct dataset_s *s, const char *code, const char *filename, pbool diff)
|
|
{
|
|
char path[MAX_OSPATH];
|
|
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);
|
|
o->usediffs = diff;
|
|
QCC_Canonicalize(o->filename, sizeof(o->filename), path, ctx->gamepath);
|
|
o->next = s->outputs;
|
|
s->outputs = o;
|
|
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void PKG_ParseOutput(struct pkgctx_s *ctx, pbool diff)
|
|
{
|
|
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");
|
|
PKG_CreateOutput(ctx, s, name, prop, diff);
|
|
}
|
|
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)))
|
|
PKG_CreateOutput(ctx, s, name, fname, diff);
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
static void PKG_AddOldPack(struct pkgctx_s *ctx, const char *fname)
|
|
{
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
static void PKG_ParseOldPack(struct pkgctx_s *ctx)
|
|
{
|
|
char token[MAX_OSPATH];
|
|
|
|
if (!PKG_GetStringToken(ctx, token, sizeof(token)))
|
|
return;
|
|
|
|
#ifdef _WIN32
|
|
{
|
|
char oldpack[MAX_OSPATH];
|
|
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;
|
|
}
|
|
static void PKG_AddClassFile(struct pkgctx_s *ctx, struct class_s *c, const char *fname, time_t mtime)
|
|
{
|
|
struct file_s *f;
|
|
struct tm *t;
|
|
|
|
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);
|
|
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);
|
|
f->next = c->files;
|
|
c->files = f;
|
|
}
|
|
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];
|
|
QCC_Canonicalize(basepath, sizeof(basepath), fname, ctx->sourcepath);
|
|
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);
|
|
PKG_AddClassFile(ctx, c, basepath, filetime_to_timet(fd.ftLastWriteTime));
|
|
} while(FindNextFile(h, &fd));
|
|
}
|
|
#else
|
|
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);
|
|
#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, '.'))
|
|
{
|
|
// if (strchr(prop, '*') || strchr(prop, '?'))
|
|
PKG_AddClassFiles(ctx, c, prop);
|
|
// else
|
|
// PKG_AddClassFile(ctx, c, prop);
|
|
}
|
|
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;
|
|
|
|
// if (strchr(prop, '*') || strchr(prop, '?'))
|
|
PKG_AddClassFiles(ctx, c, prop);
|
|
// else
|
|
// PKG_AddClassFile(ctx, c, prop);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
#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);
|
|
}
|
|
deflateEnd(&strm);
|
|
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;
|
|
|
|
QCC_Canonicalize(fullname, sizeof(fullname), file->name, ctx->sourcepath);
|
|
strcpy(file->write.name, file->name);
|
|
|
|
//WIN32 FIXME: use the utf16 version because microsoft suck and don't allow utf-8
|
|
f = fopen(fullname, "rb");
|
|
if (!f)
|
|
return NULL;
|
|
|
|
if (rule)
|
|
ctx->messagecallback(ctx->userctx, "\t\tProcessing %s (%s)\n", file->name, rule->name);
|
|
else
|
|
ctx->messagecallback(ctx->userctx, "\t\tCompressing %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);
|
|
|
|
QCC_Canonicalize(tempname, sizeof(tempname), file->write.name, ctx->sourcepath);
|
|
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))
|
|
ctx->messagecallback(ctx->userctx, "Process returned %d\n", pclose( p ));
|
|
else
|
|
{
|
|
fprintf(stderr, "Error: Failed to read the pipe to the end.\n");
|
|
pclose(p);
|
|
}
|
|
}
|
|
#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;
|
|
|
|
#ifdef _WIN32
|
|
_unlink(tempname);
|
|
#else
|
|
unlink(tempname);
|
|
#endif
|
|
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;
|
|
}
|
|
|
|
static pbool PKG_WritePackageData(struct pkgctx_s *ctx, struct output_s *out, unsigned int index, pbool directoryonly)
|
|
{
|
|
//helpers to deal with misaligned data. writes little-endian.
|
|
#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;
|
|
pbool pak = false;
|
|
|
|
struct file_s *f;
|
|
char centralheader[46+sizeof(f->write.name)];
|
|
qofs_t centraldirsize;
|
|
qofs_t centraldirofs;
|
|
qofs_t z64eocdofs;
|
|
|
|
char *filedata;
|
|
|
|
FILE *outf;
|
|
struct
|
|
{
|
|
char magic[4];
|
|
unsigned int tabofs;
|
|
unsigned int tabbytes;
|
|
} pakheader = {"PACK", 0, 0};
|
|
char *ext;
|
|
|
|
#define GPF_TRAILINGSIZE (1u<<3)
|
|
#define GPF_UTF8 (1u<<11)
|
|
#ifdef AVAIL_ZLIB
|
|
#define compmethod (pak?0:8)/*Z_DEFLATED*/
|
|
#else
|
|
#define compmethod 0/*Z_RAW*/
|
|
#endif
|
|
if (!compmethod && !directoryonly && !index)
|
|
pak = true; //might as well boost compat...
|
|
ext = strrchr(out->filename, '.');
|
|
if (ext && !QC_strcasecmp(ext, ".pak") && !index)
|
|
pak = true;
|
|
|
|
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\tNo files to write to %s\n", out->filename);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (out->usediffs && !directoryonly)
|
|
{
|
|
char newname[MAX_OSPATH];
|
|
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");
|
|
if (!outf)
|
|
{
|
|
ctx->messagecallback(ctx->userctx, "\t\tUnable to open %s\n", out->filename);
|
|
return false;
|
|
}
|
|
|
|
if (pak) //reserve space for the pak header
|
|
fwrite(&pakheader, 1, sizeof(pakheader), outf);
|
|
|
|
if (!directoryonly)
|
|
{
|
|
for (f = out->files; f ; f=f->write.nextwrite)
|
|
{
|
|
char header[32+sizeof(f->write.name)];
|
|
size_t fnamelen;
|
|
size_t hofs;
|
|
unsigned short gpflags = GPF_UTF8;
|
|
|
|
if (index != f->write.zdisk)
|
|
continue; //not in this disk...
|
|
|
|
filedata = PKG_OpenSourceFile(ctx, f, &f->write.rawsize);
|
|
if (!filedata)
|
|
{
|
|
ctx->messagecallback(ctx->userctx, "\t\tUnable to open %s\n", f->name);
|
|
}
|
|
fnamelen = strlen(f->write.name);
|
|
|
|
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
|
|
misshort(header, 28, 0);//extradata length (filled in later)
|
|
memcpy(header+30, f->write.name, fnamelen);
|
|
hofs = 30+fnamelen;
|
|
//Write extra data here...
|
|
misshort(header, 28, hofs-(30+fnamelen));//extradata length
|
|
f->write.zhdrofs = ftell(outf);
|
|
fwrite(header, 1, hofs, outf);
|
|
|
|
#ifdef AVAIL_ZLIB
|
|
if (f->write.rawsize && (compmethod == 2 || compmethod == 8))
|
|
{
|
|
gpflags |= 1u<<1;
|
|
f->write.pakofs = 0;
|
|
|
|
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);
|
|
}
|
|
|
|
//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
|
|
|
|
fseek(outf, f->write.zhdrofs, SEEK_SET);
|
|
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++;
|
|
}
|
|
}
|
|
|
|
if (pak)
|
|
{
|
|
struct
|
|
{
|
|
char name[56];
|
|
unsigned int offset;
|
|
unsigned int size;
|
|
} pakentry;
|
|
pakheader.tabofs = ftell(outf);
|
|
|
|
//write the pak file table.
|
|
for (f = out->files,num=0; f ; f=f->write.nextwrite)
|
|
{
|
|
if (index != f->write.zdisk)
|
|
continue; //not in this disk...
|
|
|
|
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++;
|
|
}
|
|
|
|
//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);
|
|
}
|
|
|
|
centraldirofs = ftell(outf);
|
|
for (f = out->files,num=0; f ; f=f->write.nextwrite)
|
|
{
|
|
size_t hofs;
|
|
size_t fnamelen;
|
|
if (!directoryonly && index != f->write.zdisk)
|
|
continue;
|
|
|
|
fnamelen = strlen(f->write.name);
|
|
misint (centralheader, 0, 0x02014b50);
|
|
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
|
|
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
|
|
misshort(centralheader, 30, 0);//extradata length (filled in later)
|
|
misshort(centralheader, 32, 0);//comment length
|
|
misshort(centralheader, 34, f->write.zdisk);//first disk number
|
|
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);
|
|
|
|
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);
|
|
num++;
|
|
}
|
|
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
|
|
misint (centralheader, 0, 0x06054b50);
|
|
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
|
|
misshort(centralheader, 20, 0); //comment length
|
|
fwrite(centralheader, 1, 22, outf);
|
|
|
|
fclose(outf);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
#include <sys/stat.h>
|
|
static time_t PKG_GetFileTime(const char *filename)
|
|
{
|
|
struct stat s;
|
|
if (stat(filename, &s) != -1)
|
|
return s.st_mtime;
|
|
}
|
|
*/
|
|
|
|
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
|
|
//assume no comment
|
|
fseek(f, -22, SEEK_END);
|
|
fread(header, 1, 22, f);
|
|
|
|
if (header[0] == 'P' && header[1] == 'K' && header[2] == 5 && header[3] == 6)
|
|
{
|
|
old->part = shortfromptr(header+4);
|
|
//centraldirstart = shortfromptr(header+6);
|
|
old->numfiles = shortfromptr(header+8);
|
|
//numfiles_all = shortfromptr(header+10);
|
|
//centaldirsize = shortfromptr(header+12);
|
|
foffset = longfromptr(header+16);
|
|
//commentength = shortfromptr(header+20);
|
|
|
|
old->file = malloc(sizeof(*old->file)*old->numfiles);
|
|
|
|
|
|
fseek(f, foffset, SEEK_SET);
|
|
for(u = 0; u < old->numfiles; u++)
|
|
{
|
|
unsigned int extra_len, comment_len;
|
|
fread(header, 1, 46, f);
|
|
//zcrc @ 16
|
|
|
|
//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);
|
|
old->file[u].dosdate = shortfromptr(header+14);
|
|
old->file[u].zcrc = longfromptr(header+16);
|
|
old->file[u].zipsize = longfromptr(header+20);
|
|
old->file[u].rawsize = longfromptr(header+24);
|
|
namelen = shortfromptr(header+28);
|
|
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);
|
|
|
|
fread(old->file[u].name, 1, namelen, f);
|
|
old->file[u].name[namelen] = 0;
|
|
i = extra_len+comment_len;
|
|
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);
|
|
old->file[u].rawsize = files[u].size;
|
|
}
|
|
free(files);
|
|
}
|
|
else
|
|
ctx->messagecallback(ctx->userctx, "%s does not appear to be a package\n", old->filename);
|
|
}
|
|
|
|
//walk central directory
|
|
fclose(f);
|
|
}
|
|
}
|
|
|
|
static pbool PKG_FileIsModified(struct pkgctx_s *ctx, struct oldpack_s *old, struct file_s *file)
|
|
{
|
|
size_t u;
|
|
|
|
for (u = 0; u < old->numfiles; u++)
|
|
{
|
|
//should check filesize etc, but rules and extension changes make that messy
|
|
if (!strcmp(old->file[u].name, file->name))
|
|
{
|
|
if(file->write.dosdate < old->file[u].dosdate || (file->write.dosdate == old->file[u].dosdate && file->write.dostime <= old->file[u].dostime))
|
|
{
|
|
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;
|
|
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);
|
|
}
|
|
|
|
for (out = set->outputs; out; out = out->next)
|
|
{
|
|
if(out->usediffs)
|
|
{
|
|
for (old = out->oldparts; old; old = old->next)
|
|
{
|
|
PKG_ReadPackContents(ctx, old);
|
|
if (out->numparts <= old->part)
|
|
out->numparts = old->part + 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
for (old = ctx->oldpacks; old; old = old->next)
|
|
{
|
|
if (!PKG_FileIsModified(ctx, old, file))
|
|
break;
|
|
}
|
|
if (old)
|
|
{
|
|
ctx->messagecallback(ctx->userctx, "\t\tFile %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))
|
|
{
|
|
file->write.zdisk = old->part;
|
|
break;
|
|
}
|
|
}
|
|
|
|
file->write.nextwrite = out->files;
|
|
file->write.rule = rule;
|
|
out->files = file;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (out = set->outputs; out; out = out->next)
|
|
{
|
|
if (!out->files)
|
|
{
|
|
ctx->messagecallback(ctx->userctx, "\tOutput %s[%s] \"%s\" has no files\n", out->code, set->name, out->filename);
|
|
continue;
|
|
}
|
|
|
|
if (ctx->test)
|
|
{
|
|
for (file = out->files; file; file = file->write.nextwrite)
|
|
{
|
|
if (file->write.rule)
|
|
ctx->messagecallback(ctx->userctx, "\t\tFile %s has changed (rule %s)\n", file->name, file->write.rule->name);
|
|
else
|
|
ctx->messagecallback(ctx->userctx, "\t\tFile %s has changed\n", file->name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ctx->messagecallback(ctx->userctx, "\tGenerating %s[%s] \"%s\"\n", out->code, set->name, out->filename);
|
|
if (PKG_WritePackageData(ctx, out, out->numparts, false))
|
|
{
|
|
if(out->usediffs)
|
|
PKG_WritePackageData(ctx, out, out->numparts+1, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
struct pkgctx_s *Packager_Create(void (*messagecallback)(void *userctx, const char *message, ...), void *userctx)
|
|
{
|
|
struct pkgctx_s *ctx;
|
|
ctx = malloc(sizeof(*ctx));
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
ctx->messagecallback = messagecallback;
|
|
ctx->userctx = userctx;
|
|
ctx->test = false;
|
|
time(&ctx->buildtime);
|
|
return ctx;
|
|
}
|
|
void Packager_ParseText(struct pkgctx_s *ctx, char *scripttext)
|
|
{
|
|
char cmd[128];
|
|
|
|
ctx->listfile = scripttext;
|
|
while (PKG_GetToken(ctx, cmd, sizeof(cmd), true))
|
|
{
|
|
// if (!strcmp(cmd, "dataset"))
|
|
// PKG_ParseDataset(ctx);
|
|
if (!strcmp(cmd, "output"))
|
|
PKG_ParseOutput(ctx, false);
|
|
else if (!strcmp(cmd, "diffoutput") || !strcmp(cmd, "splitoutput"))
|
|
PKG_ParseOutput(ctx, true);
|
|
else if (!strcmp(cmd, "inputdir"))
|
|
{
|
|
char old[MAX_OSPATH];
|
|
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);
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Packager_ParseFile(struct pkgctx_s *ctx, char *scriptname)
|
|
{
|
|
size_t remaining = 0;
|
|
char *file = qccprogfuncs->funcs.parms->ReadFile(scriptname, NULL, NULL, &remaining, true);
|
|
strcpy(ctx->gamepath, scriptname);
|
|
strcpy(ctx->sourcepath, scriptname);
|
|
Packager_ParseText(ctx, file);
|
|
free(file);
|
|
}
|
|
|
|
void Packager_Destroy(struct pkgctx_s *ctx)
|
|
{
|
|
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;
|
|
}
|
|
#endif
|