9e8bb446f4
attempt to implement 'simple csqc' api. handle qw+nq gunshot+blood+lightning differently - they do actually have different particle spawn patterns (qw is a single point, so spreads wider). fix q3ui logo mesh thing. work around q3ui player meshes on d3d. split video and renderer latching, so vid_reload delatches more stuff. fix autosprite+autosprite2 in 6 different renderers... added fog volumes to d3d9 renderer. using matrix hacks instead of glDepthRange, this should give more consistent behaviour, especially now that we have r_viewmodel_fov. small cleanup for gl shadowmaps to make the interface more consistent with other renderers. added patchDef2 parsing to fte's .map loader, doesn't actually use it though. some fixes for q3's shaders, including to try to get overbright working better. updated customskin api to give more control. first attempt at a packager system for fteqccgui. probably useless, but whatever. menusys changes to try to support QSS's csqc. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5200 fc73d0e0-1445-4013-8a0c-d673dee63da5
1387 lines
33 KiB
C
1387 lines
33 KiB
C
#include "qcc.h"
|
|
#include <time.h>
|
|
void QCC_Canonicalize(char *fullname, size_t fullnamesize, const char *newfile, const char *base);
|
|
|
|
/*
|
|
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 countof(x) (sizeof(x)/sizeof((x)[0]))
|
|
|
|
struct pkgctx_s
|
|
{
|
|
void (*messagecallback)(void *userctx, char *message, ...);
|
|
void *userctx;
|
|
|
|
char *listfile;
|
|
|
|
pbool readoldpacks;
|
|
char gamepath[MAX_PATH];
|
|
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;
|
|
struct
|
|
{
|
|
char name[128];
|
|
size_t size;
|
|
// unsigned int zcrc;
|
|
// timestamp;
|
|
} *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;
|
|
} *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 zcrc;
|
|
unsigned int zhdrofs;
|
|
unsigned int pakofs;
|
|
unsigned int rawsize;
|
|
unsigned int zipsize;
|
|
} write;
|
|
} *files;
|
|
} *classes;
|
|
};
|
|
|
|
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, 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, 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)
|
|
{
|
|
char path[MAX_PATH];
|
|
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);
|
|
QCC_Canonicalize(o->filename, sizeof(o->filename), path, ctx->gamepath);
|
|
o->next = s->outputs;
|
|
s->outputs = o;
|
|
}
|
|
|
|
static void PKG_ParseOutput(struct pkgctx_s *ctx)
|
|
{
|
|
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);
|
|
}
|
|
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);
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
static void PKG_ParseOldPack(struct pkgctx_s *ctx)
|
|
{
|
|
char token[MAX_PATH];
|
|
char oldpack[MAX_PATH];
|
|
|
|
if (!PKG_GetStringToken(ctx, token, sizeof(token)))
|
|
return;
|
|
|
|
#ifdef _WIN32
|
|
{
|
|
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)
|
|
{
|
|
struct file_s *f;
|
|
|
|
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->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->gamepath);
|
|
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);
|
|
} while(FindNextFile(h, &fd));
|
|
}
|
|
#else
|
|
ctx->messagecallback(ctx->userctx, "no wildcard support, sorry\n");
|
|
#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);
|
|
}
|
|
fwrite(out, 1, sizeof(out) - strm.avail_out, f);
|
|
i+=sizeof(out) - strm.avail_out;
|
|
deflateEnd(&strm);
|
|
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->gamepath);
|
|
|
|
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);
|
|
|
|
strcpy(file->write.name, 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->gamepath);
|
|
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
|
|
{
|
|
printf( "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;
|
|
|
|
_unlink(tempname);
|
|
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 void PKG_WritePackageData(struct pkgctx_s *ctx, struct output_s *out)
|
|
{
|
|
//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) misbyte((ptr),(ofs),(data));misbyte((ptr),(ofs)+1,(data)>>8);
|
|
#define misint(ptr,ofs,data) misshort((ptr),(ofs),(data));misshort((ptr),(ofs)+2,(data)>>16);
|
|
int num=0;
|
|
int ofs;
|
|
pbool pak = false;
|
|
int startofs = 0;
|
|
|
|
struct file_s *f;
|
|
char centralheader[46+sizeof(f->write.name)];
|
|
int centraldirsize;
|
|
|
|
char *filedata;
|
|
|
|
FILE *outf;
|
|
struct
|
|
{
|
|
char magic[4];
|
|
unsigned int tabofs;
|
|
unsigned int tabbytes;
|
|
} pakheader = {"PACK", 0, 0};
|
|
char *ext;
|
|
|
|
#ifdef AVAIL_ZLIB
|
|
#define compmethod (pak?0:8)/*Z_DEFLATED*/
|
|
#else
|
|
#define compmethod 0/*Z_RAW*/
|
|
#endif
|
|
if (!compmethod)
|
|
pak = true; //might as well boost compat...
|
|
ext = strrchr(out->filename, '.');
|
|
if (ext && !QC_strcasecmp(ext, ".pak"))
|
|
pak = true;
|
|
|
|
outf = fopen(out->filename, "wb");
|
|
if (!outf)
|
|
{
|
|
ctx->messagecallback(ctx->userctx, "Unable to open %s\n", out->filename);
|
|
return;
|
|
}
|
|
|
|
if (pak) //reserve space for the pak header
|
|
fwrite(&pakheader, 1, sizeof(pakheader), outf);
|
|
|
|
for (f = out->files; f ; f=f->write.nextwrite)
|
|
{
|
|
char header[32+sizeof(f->write.name)];
|
|
size_t fnamelen = strlen(f->write.name);
|
|
|
|
filedata = PKG_OpenSourceFile(ctx, f, &f->write.rawsize);
|
|
if (!filedata)
|
|
{
|
|
ctx->messagecallback(ctx->userctx, "Unable to open %s\n", f->name);
|
|
}
|
|
|
|
f->write.zcrc = QC_encodecrc(f->write.rawsize, filedata);
|
|
misint (header, 0, 0x04034b50);
|
|
misshort(header, 4, 0);//minver
|
|
misshort(header, 6, 0);//general purpose flags
|
|
misshort(header, 8, 0);//compression method, 0=store, 8=deflate
|
|
misshort(header, 10, 0);//lastmodfiletime
|
|
misshort(header, 12, 0);//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
|
|
strcpy(header+30, f->write.name);
|
|
|
|
f->write.zhdrofs = ftell(outf);
|
|
fwrite(header, 1, 30+fnamelen, outf);
|
|
|
|
#ifdef AVAIL_ZLIB
|
|
if (compmethod == 2 || compmethod == 8)
|
|
{
|
|
size_t end;
|
|
f->write.pakofs = 0;
|
|
|
|
f->write.zipsize = PKG_DeflateToFile(outf, f->write.rawsize, filedata, compmethod);
|
|
|
|
misshort(header, 8, compmethod);//compression method, 0=store, 8=deflate
|
|
misint (header, 18, f->write.zipsize);
|
|
|
|
end = ftell(outf);
|
|
fseek(outf, f->write.zhdrofs, SEEK_SET);
|
|
fwrite(header, 1, 30+fnamelen, outf);
|
|
fseek(outf, end, SEEK_SET);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
f->write.pakofs = ftell(outf);
|
|
f->write.zipsize = fwrite(filedata, 1, f->write.rawsize, outf);
|
|
}
|
|
free(filedata);
|
|
num++;
|
|
}
|
|
|
|
if (pak)
|
|
{
|
|
struct
|
|
{
|
|
char name[56];
|
|
unsigned int size;
|
|
unsigned int offset;
|
|
} pakentry;
|
|
//prepare the header
|
|
pakheader.tabbytes = num * sizeof(pakentry);
|
|
pakheader.tabofs = ftell(outf);
|
|
fseek(outf, 0, SEEK_SET);
|
|
fwrite(&pakheader, 1, sizeof(pakheader), outf);
|
|
//now go and write the file table.
|
|
fseek(outf, pakheader.tabofs, SEEK_SET);
|
|
for (f = out->files,num=0; f ; f=f->write.nextwrite)
|
|
{
|
|
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++;
|
|
}
|
|
}
|
|
|
|
ofs = ftell(outf);
|
|
for (f = out->files,num=0; f ; f=f->write.nextwrite)
|
|
{
|
|
size_t fnamelen;
|
|
fnamelen = strlen(f->write.name);
|
|
misint (centralheader, 0, 0x02014b50);
|
|
misshort(centralheader, 4, 0);//ourver
|
|
misshort(centralheader, 6, 0);//minver
|
|
misshort(centralheader, 8, 0);//general purpose flags
|
|
misshort(centralheader, 10, compmethod);//compression method, 0=store, 8=deflate
|
|
misshort(centralheader, 12, 0);//lastmodfiletime
|
|
misshort(centralheader, 14, 0);//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
|
|
misshort(centralheader, 32, 0);//comment length
|
|
misshort(centralheader, 34, 0);//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);
|
|
fwrite(centralheader, 1, 46 + fnamelen, outf);
|
|
num++;
|
|
}
|
|
|
|
centraldirsize = ftell(outf)-ofs;
|
|
misint (centralheader, 0, 0x06054b50);
|
|
misshort(centralheader, 4, 0); //this disk number
|
|
misshort(centralheader, 6, 0); //centraldir first disk
|
|
misshort(centralheader, 8, num); //centraldir entries
|
|
misshort(centralheader, 10, num); //total centraldir entries
|
|
misint (centralheader, 12, centraldirsize); //centraldir size
|
|
misint (centralheader, 16, ofs); //centraldir offset
|
|
misshort(centralheader, 20, 0); //comment length
|
|
fwrite(centralheader, 1, 22, outf);
|
|
|
|
fclose(outf);
|
|
}
|
|
|
|
#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
|
|
fseek(f, -22, SEEK_END);
|
|
fread(header, 1, 22, f);
|
|
|
|
if (header[0] == 'P' && header[1] == 'K' && header[2] == 5 && header[3] == 6)
|
|
{
|
|
old->numfiles = shortfromptr(header+8);
|
|
foffset = longfromptr(header+16);
|
|
|
|
old->file = malloc(sizeof(*old->file)*old->numfiles);
|
|
|
|
|
|
fseek(f, foffset, SEEK_SET);
|
|
for(u = 0; u < old->numfiles; u++)
|
|
{
|
|
fread(header, 1, 46, f);
|
|
//zcrc @ 16
|
|
old->file[u].size = longfromptr(header+24);
|
|
namelen = shortfromptr(header+28);
|
|
fread(old->file[u].name, 1, namelen, f);
|
|
old->file[u].name[namelen] = 0;
|
|
i = shortfromptr(header+30)+shortfromptr(header+32);
|
|
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].size = 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, const char *filename)
|
|
{
|
|
struct oldpack_s *old;
|
|
size_t u;
|
|
for (old = ctx->oldpacks; old; old = old->next)
|
|
{
|
|
for (u = 0; u < old->numfiles; u++)
|
|
{
|
|
if (!strcmp(old->file[u].name, filename))
|
|
{
|
|
ctx->messagecallback(ctx->userctx, "\t%s already contains %s\n", old->filename, filename);
|
|
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);
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (!PKG_FileIsModified(ctx, file->name))
|
|
continue;
|
|
// ctx->messagecallback(ctx->userctx, "\t\tFile %s, rule %s\n", file->name, rule?rule->name:"");
|
|
|
|
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;
|
|
}
|
|
|
|
ctx->messagecallback(ctx->userctx, "\tGenerating %s[%s] \"%s\"\n", out->code, set->name, out->filename);
|
|
PKG_WritePackageData(ctx, out);
|
|
}
|
|
}
|
|
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, char *message), void *userctx)
|
|
{
|
|
struct pkgctx_s *ctx;
|
|
ctx = malloc(sizeof(*ctx));
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
ctx->messagecallback = messagecallback;
|
|
ctx->userctx = userctx;
|
|
time(&ctx->buildtime);
|
|
return ctx;
|
|
}
|
|
void Packager_Parse(struct pkgctx_s *ctx, char *scriptname)
|
|
{
|
|
size_t remaining = 0;
|
|
char *file = QCC_ReadFile(scriptname, NULL, NULL, &remaining);
|
|
char cmd[128];
|
|
|
|
strcpy(ctx->gamepath, scriptname);
|
|
|
|
ctx->listfile = file;
|
|
while (PKG_GetToken(ctx, cmd, sizeof(cmd), true))
|
|
{
|
|
// if (!strcmp(cmd, "dataset"))
|
|
// PKG_ParseDataset(ctx);
|
|
if (!strcmp(cmd, "output"))
|
|
PKG_ParseOutput(ctx);
|
|
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;
|
|
}
|
|
}
|
|
free(file);
|
|
}
|
|
|
|
void Packager_Destroy(struct pkgctx_s *ctx)
|
|
{
|
|
}
|