diff options
| author | Paweł Redman <pawel.redman@gmail.com> | 2017-03-22 17:56:34 +0100 | 
|---|---|---|
| committer | Paweł Redman <pawel.redman@gmail.com> | 2017-03-22 17:56:34 +0100 | 
| commit | 6a777afc079c2a8d3af3ecd2145fe8dd50567a39 (patch) | |
| tree | 520f4489cebf8564ef6cb27064ceea45cbc005b3 /src/qcommon | |
Diffstat (limited to 'src/qcommon')
37 files changed, 39210 insertions, 0 deletions
diff --git a/src/qcommon/IpToCountryResolver.c b/src/qcommon/IpToCountryResolver.c new file mode 100644 index 0000000..f2fd97e --- /dev/null +++ b/src/qcommon/IpToCountryResolver.c @@ -0,0 +1,152 @@ +#include "IpToCountryResolver.h" +#include "q_shared.h" + +void convertToCamelCase(char *str) +{ +    int upper = 1; +    char *pos = str; +    while (*pos != '\0') { +        if (*pos != ' ') { +            if (upper) { +                (*pos) = toupper(*pos); +                upper = 0; +            } +            else +                (*pos) = tolower(*pos); +        } else { +            upper = 1; +        } +        pos++; +    } +} + +IpToCountryInfo *FindCountryInfoUI(unsigned int ip, IpToCountryList *infoList) +{ +    int abound = 0; +    int zbound = infoList->size-1; +    int tbound = 0; +    IpToCountryInfo *telement; + +    if (infoList->size == 0) +        return NULL; + +    while (abound <= zbound) +    { +        tbound = (abound+zbound)/2; +        telement = &infoList->infoArray[tbound]; + +        if (ip < telement->ipFrom) +            zbound = tbound-1; +        else if (ip > telement->ipTo) +            abound = tbound+1; +        else +            return telement; +    } +    return NULL; +} + +IpToCountryInfo *FindCountryInfoB(unsigned int parts[4], unsigned int length, IpToCountryList *infoList) +{ +    return FindCountryInfoUI(ipbyte_to_int(parts, length),infoList); +} + +IpToCountryInfo *FindCountryInfoS(const char *ip_addr, IpToCountryList *infoList) +{ +    return FindCountryInfoUI(ipstr_to_int(ip_addr),infoList); +} + +unsigned int ipbyte_to_int(unsigned int parts[4], unsigned int length) +{ +    unsigned int val = parts[length-1]; + +    switch (length) { + +	case 1:				/* a -- 32 bits */ +		break; + +	case 2:				/* a.b -- 8.24 bits */ +		if (val > 0xffffff) +			return (0); +		val |= parts[0] << 24; +		break; + +	case 3:				/* a.b.c -- 8.8.16 bits */ +		if (val > 0xffff) +			return (0); +		val |= (parts[0] << 24) | (parts[1] << 16); +		break; + +	case 4:				/* a.b.c.d -- 8.8.8.8 bits */ +		if (val > 0xff) +			return (0); +		val |= (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8); +		break; +	} + +	return val; +} + +unsigned int ipstr_to_int(const char *cp_arg) +{ +	register unsigned long val; +	register int base, n; +	register unsigned char c; +	register unsigned const char *cp = (unsigned const char *) cp_arg; +	unsigned int parts[4]; +	register unsigned int *pp = parts; + +	for (;;) { +		/* +		 * Collect number up to ``.''. +		 * Values are specified as for C: +		 * 0x=hex, 0=octal, other=decimal. +		 */ +		val = 0; base = 10; +		if (*cp == '0') { +			if (*++cp == 'x' || *cp == 'X') +				base = 16, cp++; +			else +				base = 8; +		} +		while ((c = *cp) != '\0') { +			if (isascii(c) && isdigit(c)) { +				val = (val * base) + (c - '0'); +				cp++; +				continue; +			} +			if (base == 16 && isascii(c) && isxdigit(c)) { +				val = (val << 4) + +					(c + 10 - (islower(c) ? 'a' : 'A')); +				cp++; +				continue; +			} +			break; +		} +		if (*cp == '.') { +			/* +			 * Internet format: +			 *	a.b.c.d +			 *	a.b.c	(with c treated as 16-bits) +			 *	a.b	(with b treated as 24 bits) +			 */ +			if (pp >= parts + 3 || val > 0xff) +				return (0); +			*pp++ = val, cp++; +		} else { +		    *pp = val; +			break; +		} +	} +	/* +	 * Check for trailing characters. +	 */ +	if (*cp && (!isascii(*cp) || !isspace(*cp))) +		return (0); +	/* +	 * Concoct the address according to +	 * the number of parts specified. +	 */ +	n = pp - parts + 1; + +    return ipbyte_to_int(parts,n); +} diff --git a/src/qcommon/IpToCountryResolver.h b/src/qcommon/IpToCountryResolver.h new file mode 100644 index 0000000..cf97cf8 --- /dev/null +++ b/src/qcommon/IpToCountryResolver.h @@ -0,0 +1,30 @@ +#ifndef IPTOCOUNTRYRESOLVER_H_INCLUDED
 +#define IPTOCOUNTRYRESOLVER_H_INCLUDED
 +
 +#define FULL_NAME_LENGTH 64
 +
 +typedef struct {
 +        unsigned int ipFrom;
 +        unsigned int ipTo;
 +        char name2[2+1]; //1 for zero character (line end)
 +        char name3[3+1];
 +        char nameFull[FULL_NAME_LENGTH];
 +} IpToCountryInfo;
 +
 +typedef struct {
 +    unsigned int size;
 +    IpToCountryInfo *infoArray;
 +} IpToCountryList;
 +
 +unsigned int ipstr_to_int (const char *cp_arg);
 +unsigned int ipbyte_to_int(unsigned int parts[4], unsigned int length);
 +unsigned int LoadIp2CountryInfo(const char *fileName, IpToCountryList *infoList, int camelCaseNames);
 +IpToCountryInfo *FindCountryInfoS(const char *ip_addr, IpToCountryList *infoList);
 +IpToCountryInfo *FindCountryInfoB(unsigned int parts[4], unsigned int length, IpToCountryList *infoList);
 +IpToCountryInfo *FindCountryInfoUI(unsigned int ip, IpToCountryList *infoList);
 +
 +#endif // IPTOCOUNTRYRESOLVER_H_INCLUDED
 +
 +
 +
 +
 diff --git a/src/qcommon/cm_load.c b/src/qcommon/cm_load.c new file mode 100644 index 0000000..06a037a --- /dev/null +++ b/src/qcommon/cm_load.c @@ -0,0 +1,1021 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ +// cmodel.c -- model loading + +#include "cm_local.h" + +#ifdef BSPC + +#include "../bspc/l_qfiles.h" + +void SetPlaneSignbits (cplane_t *out) { +	int	bits, j; + +	// for fast box on planeside test +	bits = 0; +	for (j=0 ; j<3 ; j++) { +		if (out->normal[j] < 0) { +			bits |= 1<<j; +		} +	} +	out->signbits = bits; +} +#endif //BSPC + +// to allow boxes to be treated as brush models, we allocate +// some extra indexes along with those needed by the map +#define	BOX_BRUSHES		1 +#define	BOX_SIDES		6 +#define	BOX_LEAFS		2 +#define	BOX_PLANES		12 + +#define	LL(x) x=LittleLong(x) + + +clipMap_t	cm; +int			c_pointcontents; +int			c_traces, c_brush_traces, c_patch_traces; + + +byte		*cmod_base; + +#ifndef BSPC +cvar_t		*cm_noAreas; +cvar_t		*cm_noCurves; +cvar_t		*cm_playerCurveClip; +#endif + +cmodel_t	box_model; +cplane_t	*box_planes; +cbrush_t	*box_brush; + + + +void	CM_InitBoxHull (void); +void	CM_FloodAreaConnections (void); + + +/* +=============================================================================== + +					MAP LOADING + +=============================================================================== +*/ + +/* +================= +CMod_LoadShaders +================= +*/ +void CMod_LoadShaders( lump_t *l ) { +	dshader_t	*in, *out; +	int			i, count; + +	in = (void *)(cmod_base + l->fileofs); +	if (l->filelen % sizeof(*in)) { +		Com_Error (ERR_DROP, "CMod_LoadShaders: funny lump size"); +	} +	count = l->filelen / sizeof(*in); + +	if (count < 1) { +		Com_Error (ERR_DROP, "Map with no shaders"); +	} +	cm.shaders = Hunk_Alloc( count * sizeof( *cm.shaders ), h_high ); +	cm.numShaders = count; + +	Com_Memcpy( cm.shaders, in, count * sizeof( *cm.shaders ) ); + +	out = cm.shaders; +	for ( i=0 ; i<count ; i++, in++, out++ ) { +		out->contentFlags = LittleLong( out->contentFlags ); +		out->surfaceFlags = LittleLong( out->surfaceFlags ); +	} +} + + +/* +================= +CMod_LoadSubmodels +================= +*/ +void CMod_LoadSubmodels( lump_t *l ) { +	dmodel_t	*in; +	cmodel_t	*out; +	int			i, j, count; +	int			*indexes; + +	in = (void *)(cmod_base + l->fileofs); +	if (l->filelen % sizeof(*in)) +		Com_Error (ERR_DROP, "CMod_LoadSubmodels: funny lump size"); +	count = l->filelen / sizeof(*in); + +	if (count < 1) +		Com_Error (ERR_DROP, "Map with no models"); +	cm.cmodels = Hunk_Alloc( count * sizeof( *cm.cmodels ), h_high ); +	cm.numSubModels = count; + +	if ( count > MAX_SUBMODELS ) { +		Com_Error( ERR_DROP, "MAX_SUBMODELS exceeded" ); +	} + +	for ( i=0 ; i<count ; i++, in++, out++) +	{ +		out = &cm.cmodels[i]; + +		for (j=0 ; j<3 ; j++) +		{	// spread the mins / maxs by a pixel +			out->mins[j] = LittleFloat (in->mins[j]) - 1; +			out->maxs[j] = LittleFloat (in->maxs[j]) + 1; +		} + +		if ( i == 0 ) { +			continue;	// world model doesn't need other info +		} + +		// make a "leaf" just to hold the model's brushes and surfaces +		out->leaf.numLeafBrushes = LittleLong( in->numBrushes ); +		indexes = Hunk_Alloc( out->leaf.numLeafBrushes * 4, h_high ); +		out->leaf.firstLeafBrush = indexes - cm.leafbrushes; +		for ( j = 0 ; j < out->leaf.numLeafBrushes ; j++ ) { +			indexes[j] = LittleLong( in->firstBrush ) + j; +		} + +		out->leaf.numLeafSurfaces = LittleLong( in->numSurfaces ); +		indexes = Hunk_Alloc( out->leaf.numLeafSurfaces * 4, h_high ); +		out->leaf.firstLeafSurface = indexes - cm.leafsurfaces; +		for ( j = 0 ; j < out->leaf.numLeafSurfaces ; j++ ) { +			indexes[j] = LittleLong( in->firstSurface ) + j; +		} +	} +} + + +/* +================= +CMod_LoadNodes + +================= +*/ +void CMod_LoadNodes( lump_t *l ) { +	dnode_t		*in; +	int			child; +	cNode_t		*out; +	int			i, j, count; +	 +	in = (void *)(cmod_base + l->fileofs); +	if (l->filelen % sizeof(*in)) +		Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); +	count = l->filelen / sizeof(*in); + +	if (count < 1) +		Com_Error (ERR_DROP, "Map has no nodes"); +	cm.nodes = Hunk_Alloc( count * sizeof( *cm.nodes ), h_high ); +	cm.numNodes = count; + +	out = cm.nodes; + +	for (i=0 ; i<count ; i++, out++, in++) +	{ +		out->plane = cm.planes + LittleLong( in->planeNum ); +		for (j=0 ; j<2 ; j++) +		{ +			child = LittleLong (in->children[j]); +			out->children[j] = child; +		} +	} + +} + +/* +================= +CM_BoundBrush + +================= +*/ +void CM_BoundBrush( cbrush_t *b ) { +	b->bounds[0][0] = -b->sides[0].plane->dist; +	b->bounds[1][0] = b->sides[1].plane->dist; + +	b->bounds[0][1] = -b->sides[2].plane->dist; +	b->bounds[1][1] = b->sides[3].plane->dist; + +	b->bounds[0][2] = -b->sides[4].plane->dist; +	b->bounds[1][2] = b->sides[5].plane->dist; +} + + +/* +================= +CMod_LoadBrushes + +================= +*/ +void CMod_LoadBrushes( lump_t *l ) { +	dbrush_t	*in; +	cbrush_t	*out; +	int			i, count; + +	in = (void *)(cmod_base + l->fileofs); +	if (l->filelen % sizeof(*in)) { +		Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); +	} +	count = l->filelen / sizeof(*in); + +	cm.brushes = Hunk_Alloc( ( BOX_BRUSHES + count ) * sizeof( *cm.brushes ), h_high ); +	cm.numBrushes = count; + +	out = cm.brushes; + +	for ( i=0 ; i<count ; i++, out++, in++ ) { +		out->sides = cm.brushsides + LittleLong(in->firstSide); +		out->numsides = LittleLong(in->numSides); + +		out->shaderNum = LittleLong( in->shaderNum ); +		if ( out->shaderNum < 0 || out->shaderNum >= cm.numShaders ) { +			Com_Error( ERR_DROP, "CMod_LoadBrushes: bad shaderNum: %i", out->shaderNum ); +		} +		out->contents = cm.shaders[out->shaderNum].contentFlags; + +		CM_BoundBrush( out ); +	} + +} + +/* +================= +CMod_LoadLeafs +================= +*/ +void CMod_LoadLeafs (lump_t *l) +{ +	int			i; +	cLeaf_t		*out; +	dleaf_t 	*in; +	int			count; +	 +	in = (void *)(cmod_base + l->fileofs); +	if (l->filelen % sizeof(*in)) +		Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); +	count = l->filelen / sizeof(*in); + +	if (count < 1) +		Com_Error (ERR_DROP, "Map with no leafs"); + +	cm.leafs = Hunk_Alloc( ( BOX_LEAFS + count ) * sizeof( *cm.leafs ), h_high ); +	cm.numLeafs = count; + +	out = cm.leafs;	 +	for ( i=0 ; i<count ; i++, in++, out++) +	{ +		out->cluster = LittleLong (in->cluster); +		out->area = LittleLong (in->area); +		out->firstLeafBrush = LittleLong (in->firstLeafBrush); +		out->numLeafBrushes = LittleLong (in->numLeafBrushes); +		out->firstLeafSurface = LittleLong (in->firstLeafSurface); +		out->numLeafSurfaces = LittleLong (in->numLeafSurfaces); + +		if (out->cluster >= cm.numClusters) +			cm.numClusters = out->cluster + 1; +		if (out->area >= cm.numAreas) +			cm.numAreas = out->area + 1; +	} + +	cm.areas = Hunk_Alloc( cm.numAreas * sizeof( *cm.areas ), h_high ); +	cm.areaPortals = Hunk_Alloc( cm.numAreas * cm.numAreas * sizeof( *cm.areaPortals ), h_high ); +} + +/* +================= +CMod_LoadPlanes +================= +*/ +void CMod_LoadPlanes (lump_t *l) +{ +	int			i, j; +	cplane_t	*out; +	dplane_t 	*in; +	int			count; +	int			bits; +	 +	in = (void *)(cmod_base + l->fileofs); +	if (l->filelen % sizeof(*in)) +		Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); +	count = l->filelen / sizeof(*in); + +	if (count < 1) +		Com_Error (ERR_DROP, "Map with no planes"); +	cm.planes = Hunk_Alloc( ( BOX_PLANES + count ) * sizeof( *cm.planes ), h_high ); +	cm.numPlanes = count; + +	out = cm.planes;	 + +	for ( i=0 ; i<count ; i++, in++, out++) +	{ +		bits = 0; +		for (j=0 ; j<3 ; j++) +		{ +			out->normal[j] = LittleFloat (in->normal[j]); +			if (out->normal[j] < 0) +				bits |= 1<<j; +		} + +		out->dist = LittleFloat (in->dist); +		out->type = PlaneTypeForNormal( out->normal ); +		out->signbits = bits; +	} +} + +/* +================= +CMod_LoadLeafBrushes +================= +*/ +void CMod_LoadLeafBrushes (lump_t *l) +{ +	int			i; +	int			*out; +	int		 	*in; +	int			count; +	 +	in = (void *)(cmod_base + l->fileofs); +	if (l->filelen % sizeof(*in)) +		Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); +	count = l->filelen / sizeof(*in); + +	cm.leafbrushes = Hunk_Alloc( (count + BOX_BRUSHES) * sizeof( *cm.leafbrushes ), h_high ); +	cm.numLeafBrushes = count; + +	out = cm.leafbrushes; + +	for ( i=0 ; i<count ; i++, in++, out++) { +		*out = LittleLong (*in); +	} +} + +/* +================= +CMod_LoadLeafSurfaces +================= +*/ +void CMod_LoadLeafSurfaces( lump_t *l ) +{ +	int			i; +	int			*out; +	int		 	*in; +	int			count; +	 +	in = (void *)(cmod_base + l->fileofs); +	if (l->filelen % sizeof(*in)) +		Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); +	count = l->filelen / sizeof(*in); + +	cm.leafsurfaces = Hunk_Alloc( count * sizeof( *cm.leafsurfaces ), h_high ); +	cm.numLeafSurfaces = count; + +	out = cm.leafsurfaces; + +	for ( i=0 ; i<count ; i++, in++, out++) { +		*out = LittleLong (*in); +	} +} + +/* +================= +CMod_LoadBrushSides +================= +*/ +void CMod_LoadBrushSides (lump_t *l) +{ +	int				i; +	cbrushside_t	*out; +	dbrushside_t 	*in; +	int				count; +	int				num; + +	in = (void *)(cmod_base + l->fileofs); +	if ( l->filelen % sizeof(*in) ) { +		Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); +	} +	count = l->filelen / sizeof(*in); + +	cm.brushsides = Hunk_Alloc( ( BOX_SIDES + count ) * sizeof( *cm.brushsides ), h_high ); +	cm.numBrushSides = count; + +	out = cm.brushsides;	 + +	for ( i=0 ; i<count ; i++, in++, out++) { +		num = LittleLong( in->planeNum ); +		out->planeNum = num; +		out->plane = &cm.planes[num]; +		out->shaderNum = LittleLong( in->shaderNum ); +		if ( out->shaderNum < 0 || out->shaderNum >= cm.numShaders ) { +			Com_Error( ERR_DROP, "CMod_LoadBrushSides: bad shaderNum: %i", out->shaderNum ); +		} +		out->surfaceFlags = cm.shaders[out->shaderNum].surfaceFlags; +	} +} + +#define CM_EDGE_VERTEX_EPSILON 0.1f + +/* +================= +CMod_BrushEdgesAreTheSame +================= +*/ +static qboolean CMod_BrushEdgesAreTheSame( const vec3_t p0, const vec3_t p1, +		const vec3_t q0, const vec3_t q1 ) +{ +	if( VectorCompareEpsilon( p0, q0, CM_EDGE_VERTEX_EPSILON ) && +			VectorCompareEpsilon( p1, q1, CM_EDGE_VERTEX_EPSILON ) ) +		return qtrue; + +	if( VectorCompareEpsilon( p1, q0, CM_EDGE_VERTEX_EPSILON ) && +			VectorCompareEpsilon( p0, q1, CM_EDGE_VERTEX_EPSILON ) ) +		return qtrue; + +	return qfalse; +} + +/* +================= +CMod_AddEdgeToBrush +================= +*/ +static qboolean CMod_AddEdgeToBrush( const vec3_t p0, const vec3_t p1, +		cbrushedge_t *edges, int *numEdges ) +{ +	int i; + +	if( !edges || !numEdges ) +		return qfalse; + +	for( i = 0; i < *numEdges; i++ ) +	{ +		if( CMod_BrushEdgesAreTheSame( p0, p1, +					edges[ i ].p0, edges[ i ].p1 ) ) +			return qfalse; +	} + +	VectorCopy( p0, edges[ *numEdges ].p0 ); +	VectorCopy( p1, edges[ *numEdges ].p1 ); +	(*numEdges)++; + +	return qtrue; +} + +/* +================= +CMod_CreateBrushSideWindings +================= +*/ +static void CMod_CreateBrushSideWindings( void ) +{ +	int						i, j, k; +	winding_t			*w; +	cbrushside_t	*side, *chopSide; +	cplane_t			*plane; +	cbrush_t			*brush; +	cbrushedge_t	*tempEdges; +	int						numEdges; +	int						edgesAlloc; +	int						totalEdgesAlloc = 0; +	int						totalEdges = 0; +	 +	for( i = 0; i < cm.numBrushes; i++ ) +	{ +		brush = &cm.brushes[ i ]; +		numEdges = 0; + +		// walk the list of brush sides +		for( j = 0; j < brush->numsides; j++ ) +		{ +			// get side and plane +			side = &brush->sides[ j ]; +			plane = side->plane; + +			w = BaseWindingForPlane( plane->normal, plane->dist ); + +			// walk the list of brush sides +			for( k = 0; k < brush->numsides && w != NULL; k++ ) +			{ +				chopSide = &brush->sides[ k ]; + +				if( chopSide == side ) +					continue; + +				if( chopSide->planeNum == ( side->planeNum ^ 1 ) ) +					continue;		// back side clipaway + +				plane = &cm.planes[ chopSide->planeNum ^ 1 ]; +				ChopWindingInPlace( &w, plane->normal, plane->dist, 0 ); +			} + +			if( w ) +				numEdges += w->numpoints; + +			// set side winding +			side->winding = w; +		} + +		// Allocate a temporary buffer of the maximal size +		tempEdges = (cbrushedge_t *)Z_Malloc( sizeof( cbrushedge_t ) * numEdges ); +		brush->numEdges = 0; + +		// compose the points into edges +		for( j = 0; j < brush->numsides; j++ ) +		{ +			side = &brush->sides[ j ]; + +			if( side->winding ) +			{ +				for( k = 0; k < side->winding->numpoints - 1; k++ ) +				{ +					if( brush->numEdges == numEdges ) +						Com_Error( ERR_FATAL, +								"Insufficient memory allocated for collision map edges" ); + +					CMod_AddEdgeToBrush( side->winding->p[ k ], +							side->winding->p[ k + 1 ], tempEdges, &brush->numEdges ); +				} + +				FreeWinding( side->winding ); +				side->winding = NULL; +			} +		} + +		// Allocate a buffer of the actual size +		edgesAlloc = sizeof( cbrushedge_t ) * brush->numEdges; +		totalEdgesAlloc += edgesAlloc; +		brush->edges = (cbrushedge_t *)Hunk_Alloc( edgesAlloc, h_low ); + +		// Copy temporary buffer to permanent buffer +		Com_Memcpy( brush->edges, tempEdges, edgesAlloc ); + +		// Free temporary buffer +		Z_Free( tempEdges ); + +		totalEdges += brush->numEdges; +	} + +	Com_DPrintf( "Allocated %d bytes for %d collision map edges...\n", +			totalEdgesAlloc, totalEdges ); +} + +/* +================= +CMod_LoadEntityString +================= +*/ +void CMod_LoadEntityString( lump_t *l ) { +	cm.entityString = Hunk_Alloc( l->filelen, h_high ); +	cm.numEntityChars = l->filelen; +	Com_Memcpy (cm.entityString, cmod_base + l->fileofs, l->filelen); +} + +/* +================= +CMod_LoadVisibility +================= +*/ +#define	VIS_HEADER	8 +void CMod_LoadVisibility( lump_t *l ) { +	int		len; +	byte	*buf; + +    len = l->filelen; +	if ( !len ) { +		cm.clusterBytes = ( cm.numClusters + 31 ) & ~31; +		cm.visibility = Hunk_Alloc( cm.clusterBytes, h_high ); +		Com_Memset( cm.visibility, 255, cm.clusterBytes ); +		return; +	} +	buf = cmod_base + l->fileofs; + +	cm.vised = qtrue; +	cm.visibility = Hunk_Alloc( len, h_high ); +	cm.numClusters = LittleLong( ((int *)buf)[0] ); +	cm.clusterBytes = LittleLong( ((int *)buf)[1] ); +	Com_Memcpy (cm.visibility, buf + VIS_HEADER, len - VIS_HEADER ); +} + +//================================================================== + + +/* +================= +CMod_LoadPatches +================= +*/ +#define	MAX_PATCH_VERTS		1024 +void CMod_LoadPatches( lump_t *surfs, lump_t *verts ) { +	drawVert_t	*dv, *dv_p; +	dsurface_t	*in; +	int			count; +	int			i, j; +	int			c; +	cPatch_t	*patch; +	vec3_t		points[MAX_PATCH_VERTS]; +	int			width, height; +	int			shaderNum; + +	in = (void *)(cmod_base + surfs->fileofs); +	if (surfs->filelen % sizeof(*in)) +		Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); +	cm.numSurfaces = count = surfs->filelen / sizeof(*in); +	cm.surfaces = Hunk_Alloc( cm.numSurfaces * sizeof( cm.surfaces[0] ), h_high ); + +	dv = (void *)(cmod_base + verts->fileofs); +	if (verts->filelen % sizeof(*dv)) +		Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + +	// scan through all the surfaces, but only load patches, +	// not planar faces +	for ( i = 0 ; i < count ; i++, in++ ) { +		if ( LittleLong( in->surfaceType ) != MST_PATCH ) { +			continue;		// ignore other surfaces +		} +		// FIXME: check for non-colliding patches + +		cm.surfaces[ i ] = patch = Hunk_Alloc( sizeof( *patch ), h_high ); + +		// load the full drawverts onto the stack +		width = LittleLong( in->patchWidth ); +		height = LittleLong( in->patchHeight ); +		c = width * height; +		if ( c > MAX_PATCH_VERTS ) { +			Com_Error( ERR_DROP, "ParseMesh: MAX_PATCH_VERTS" ); +		} + +		dv_p = dv + LittleLong( in->firstVert ); +		for ( j = 0 ; j < c ; j++, dv_p++ ) { +			points[j][0] = LittleFloat( dv_p->xyz[0] ); +			points[j][1] = LittleFloat( dv_p->xyz[1] ); +			points[j][2] = LittleFloat( dv_p->xyz[2] ); +		} + +		shaderNum = LittleLong( in->shaderNum ); +		patch->contents = cm.shaders[shaderNum].contentFlags; +		patch->surfaceFlags = cm.shaders[shaderNum].surfaceFlags; + +		// create the internal facet structure +		patch->pc = CM_GeneratePatchCollide( width, height, points ); +	} +} + +//================================================================== + +unsigned CM_LumpChecksum(lump_t *lump) { +	return LittleLong (Com_BlockChecksum (cmod_base + lump->fileofs, lump->filelen)); +} + +unsigned CM_Checksum(dheader_t *header) { +	unsigned checksums[16]; +	checksums[0] = CM_LumpChecksum(&header->lumps[LUMP_SHADERS]); +	checksums[1] = CM_LumpChecksum(&header->lumps[LUMP_LEAFS]); +	checksums[2] = CM_LumpChecksum(&header->lumps[LUMP_LEAFBRUSHES]); +	checksums[3] = CM_LumpChecksum(&header->lumps[LUMP_LEAFSURFACES]); +	checksums[4] = CM_LumpChecksum(&header->lumps[LUMP_PLANES]); +	checksums[5] = CM_LumpChecksum(&header->lumps[LUMP_BRUSHSIDES]); +	checksums[6] = CM_LumpChecksum(&header->lumps[LUMP_BRUSHES]); +	checksums[7] = CM_LumpChecksum(&header->lumps[LUMP_MODELS]); +	checksums[8] = CM_LumpChecksum(&header->lumps[LUMP_NODES]); +	checksums[9] = CM_LumpChecksum(&header->lumps[LUMP_SURFACES]); +	checksums[10] = CM_LumpChecksum(&header->lumps[LUMP_DRAWVERTS]); + +	return LittleLong(Com_BlockChecksum(checksums, 11 * 4)); +} + +/* +================== +CM_LoadMap + +Loads in the map and all submodels +================== +*/ +void CM_LoadMap( const char *name, qboolean clientload, int *checksum ) { +	int				*buf; +	int				i; +	dheader_t		header; +	int				length; +	static unsigned	last_checksum; + +	if ( !name || !name[0] ) { +		Com_Error( ERR_DROP, "CM_LoadMap: NULL name" ); +	} + +#ifndef BSPC +	cm_noAreas = Cvar_Get ("cm_noAreas", "0", CVAR_CHEAT); +	cm_noCurves = Cvar_Get ("cm_noCurves", "0", CVAR_CHEAT); +	cm_playerCurveClip = Cvar_Get ("cm_playerCurveClip", "1", CVAR_ARCHIVE|CVAR_CHEAT ); +#endif +	Com_DPrintf( "CM_LoadMap( %s, %i )\n", name, clientload ); + +	if ( !strcmp( cm.name, name ) && clientload ) { +		*checksum = last_checksum; +		return; +	} + +	// free old stuff +	Com_Memset( &cm, 0, sizeof( cm ) ); +	CM_ClearLevelPatches(); + +	if ( !name[0] ) { +		cm.numLeafs = 1; +		cm.numClusters = 1; +		cm.numAreas = 1; +		cm.cmodels = Hunk_Alloc( sizeof( *cm.cmodels ), h_high ); +		*checksum = 0; +		return; +	} + +	// +	// load the file +	// +#ifndef BSPC +	length = FS_ReadFile( name, (void **)&buf ); +#else +	length = LoadQuakeFile((quakefile_t *) name, (void **)&buf); +#endif + +	if ( !buf ) { +		Com_Error (ERR_DROP, "Couldn't load %s", name); +	} + +	last_checksum = LittleLong (Com_BlockChecksum (buf, length)); +	*checksum = last_checksum; + +	header = *(dheader_t *)buf; +	for (i=0 ; i<sizeof(dheader_t)/4 ; i++) { +		((int *)&header)[i] = LittleLong ( ((int *)&header)[i]); +	} + +	if ( header.version != BSP_VERSION ) { +		Com_Error (ERR_DROP, "CM_LoadMap: %s has wrong version number (%i should be %i)" +		, name, header.version, BSP_VERSION ); +	} + +	cmod_base = (byte *)buf; + +	// load into heap +	CMod_LoadShaders( &header.lumps[LUMP_SHADERS] ); +	CMod_LoadLeafs (&header.lumps[LUMP_LEAFS]); +	CMod_LoadLeafBrushes (&header.lumps[LUMP_LEAFBRUSHES]); +	CMod_LoadLeafSurfaces (&header.lumps[LUMP_LEAFSURFACES]); +	CMod_LoadPlanes (&header.lumps[LUMP_PLANES]); +	CMod_LoadBrushSides (&header.lumps[LUMP_BRUSHSIDES]); +	CMod_LoadBrushes (&header.lumps[LUMP_BRUSHES]); +	CMod_LoadSubmodels (&header.lumps[LUMP_MODELS]); +	CMod_LoadNodes (&header.lumps[LUMP_NODES]); +	CMod_LoadEntityString (&header.lumps[LUMP_ENTITIES]); +	CMod_LoadVisibility( &header.lumps[LUMP_VISIBILITY] ); +	CMod_LoadPatches( &header.lumps[LUMP_SURFACES], &header.lumps[LUMP_DRAWVERTS] ); + +	CMod_CreateBrushSideWindings( ); + +	// we are NOT freeing the file, because it is cached for the ref +	FS_FreeFile (buf); + +	CM_InitBoxHull (); + +	CM_FloodAreaConnections (); + +	// allow this to be cached if it is loaded by the server +	if ( !clientload ) { +		Q_strncpyz( cm.name, name, sizeof( cm.name ) ); +	} +} + +/* +================== +CM_ClearMap +================== +*/ +void CM_ClearMap( void ) { +	Com_Memset( &cm, 0, sizeof( cm ) ); +	CM_ClearLevelPatches(); +} + +/* +================== +CM_ClipHandleToModel +================== +*/ +cmodel_t	*CM_ClipHandleToModel( clipHandle_t handle ) { +	if ( handle < 0 ) { +		Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i", handle ); +	} +	if ( handle < cm.numSubModels ) { +		return &cm.cmodels[handle]; +	} +	if ( handle == BOX_MODEL_HANDLE ) { +		return &box_model; +	} +	if ( handle < MAX_SUBMODELS ) { +		Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i < %i < %i",  +			cm.numSubModels, handle, MAX_SUBMODELS ); +	} +	Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i", handle + MAX_SUBMODELS ); + +	return NULL; + +} + +/* +================== +CM_InlineModel +================== +*/ +clipHandle_t	CM_InlineModel( int index ) { +	if ( index < 0 || index >= cm.numSubModels ) { +		Com_Error (ERR_DROP, "CM_InlineModel: bad number"); +	} +	return index; +} + +int		CM_NumClusters( void ) { +	return cm.numClusters; +} + +int		CM_NumInlineModels( void ) { +	return cm.numSubModels; +} + +char	*CM_EntityString( void ) { +	return cm.entityString; +} + +int		CM_LeafCluster( int leafnum ) { +	if (leafnum < 0 || leafnum >= cm.numLeafs) { +		Com_Error (ERR_DROP, "CM_LeafCluster: bad number"); +	} +	return cm.leafs[leafnum].cluster; +} + +int		CM_LeafArea( int leafnum ) { +	if ( leafnum < 0 || leafnum >= cm.numLeafs ) { +		Com_Error (ERR_DROP, "CM_LeafArea: bad number"); +	} +	return cm.leafs[leafnum].area; +} + +//======================================================================= + + +/* +=================== +CM_InitBoxHull + +Set up the planes and nodes so that the six floats of a bounding box +can just be stored out and get a proper clipping hull structure. +=================== +*/ +void CM_InitBoxHull (void) +{ +	int			i; +	int			side; +	cplane_t	*p; +	cbrushside_t	*s; + +	box_planes = &cm.planes[cm.numPlanes]; + +	box_brush = &cm.brushes[cm.numBrushes]; +	box_brush->numsides = 6; +	box_brush->sides = cm.brushsides + cm.numBrushSides; +	box_brush->contents = CONTENTS_BODY; +	box_brush->edges = (cbrushedge_t *)Hunk_Alloc( +			sizeof( cbrushedge_t ) * 12, h_low ); +	box_brush->numEdges = 12; + +	box_model.leaf.numLeafBrushes = 1; +//	box_model.leaf.firstLeafBrush = cm.numBrushes; +	box_model.leaf.firstLeafBrush = cm.numLeafBrushes; +	cm.leafbrushes[cm.numLeafBrushes] = cm.numBrushes; + +	for (i=0 ; i<6 ; i++) +	{ +		side = i&1; + +		// brush sides +		s = &cm.brushsides[cm.numBrushSides+i]; +		s->plane = 	cm.planes + (cm.numPlanes+i*2+side); +		s->surfaceFlags = 0; + +		// planes +		p = &box_planes[i*2]; +		p->type = i>>1; +		p->signbits = 0; +		VectorClear (p->normal); +		p->normal[i>>1] = 1; + +		p = &box_planes[i*2+1]; +		p->type = 3 + (i>>1); +		p->signbits = 0; +		VectorClear (p->normal); +		p->normal[i>>1] = -1; + +		SetPlaneSignbits( p ); +	}	 +} + +/* +=================== +CM_TempBoxModel + +To keep everything totally uniform, bounding boxes are turned into small +BSP trees instead of being compared directly. +Capsules are handled differently though. +=================== +*/ +clipHandle_t CM_TempBoxModel( const vec3_t mins, const vec3_t maxs, int capsule ) { + +	VectorCopy( mins, box_model.mins ); +	VectorCopy( maxs, box_model.maxs ); + +	if ( capsule ) { +		return CAPSULE_MODEL_HANDLE; +	} + +	box_planes[0].dist = maxs[0]; +	box_planes[1].dist = -maxs[0]; +	box_planes[2].dist = mins[0]; +	box_planes[3].dist = -mins[0]; +	box_planes[4].dist = maxs[1]; +	box_planes[5].dist = -maxs[1]; +	box_planes[6].dist = mins[1]; +	box_planes[7].dist = -mins[1]; +	box_planes[8].dist = maxs[2]; +	box_planes[9].dist = -maxs[2]; +	box_planes[10].dist = mins[2]; +	box_planes[11].dist = -mins[2]; + +	// First side +	VectorSet( box_brush->edges[ 0 ].p0,  mins[ 0 ], mins[ 1 ], mins[ 2 ] ); +	VectorSet( box_brush->edges[ 0 ].p1,  mins[ 0 ], maxs[ 1 ], mins[ 2 ] ); +	VectorSet( box_brush->edges[ 1 ].p0,  mins[ 0 ], maxs[ 1 ], mins[ 2 ] ); +	VectorSet( box_brush->edges[ 1 ].p1,  mins[ 0 ], maxs[ 1 ], maxs[ 2 ] ); +	VectorSet( box_brush->edges[ 2 ].p0,  mins[ 0 ], maxs[ 1 ], maxs[ 2 ] ); +	VectorSet( box_brush->edges[ 2 ].p1,  mins[ 0 ], mins[ 1 ], maxs[ 2 ] ); +	VectorSet( box_brush->edges[ 3 ].p0,  mins[ 0 ], mins[ 1 ], maxs[ 2 ] ); +	VectorSet( box_brush->edges[ 3 ].p1,  mins[ 0 ], mins[ 1 ], mins[ 2 ] ); + +	// Opposite side +	VectorSet( box_brush->edges[ 4 ].p0,  maxs[ 0 ], mins[ 1 ], mins[ 2 ] ); +	VectorSet( box_brush->edges[ 4 ].p1,  maxs[ 0 ], maxs[ 1 ], mins[ 2 ] ); +	VectorSet( box_brush->edges[ 5 ].p0,  maxs[ 0 ], maxs[ 1 ], mins[ 2 ] ); +	VectorSet( box_brush->edges[ 5 ].p1,  maxs[ 0 ], maxs[ 1 ], maxs[ 2 ] ); +	VectorSet( box_brush->edges[ 6 ].p0,  maxs[ 0 ], maxs[ 1 ], maxs[ 2 ] ); +	VectorSet( box_brush->edges[ 6 ].p1,  maxs[ 0 ], mins[ 1 ], maxs[ 2 ] ); +	VectorSet( box_brush->edges[ 7 ].p0,  maxs[ 0 ], mins[ 1 ], maxs[ 2 ] ); +	VectorSet( box_brush->edges[ 7 ].p1,  maxs[ 0 ], mins[ 1 ], mins[ 2 ] ); + +	// Connecting edges +	VectorSet( box_brush->edges[ 8 ].p0,  mins[ 0 ], mins[ 1 ], mins[ 2 ] ); +	VectorSet( box_brush->edges[ 8 ].p1,  maxs[ 0 ], mins[ 1 ], mins[ 2 ] ); +	VectorSet( box_brush->edges[ 9 ].p0,  mins[ 0 ], maxs[ 1 ], mins[ 2 ] ); +	VectorSet( box_brush->edges[ 9 ].p1,  maxs[ 0 ], maxs[ 1 ], mins[ 2 ] ); +	VectorSet( box_brush->edges[ 10 ].p0, mins[ 0 ], maxs[ 1 ], maxs[ 2 ] ); +	VectorSet( box_brush->edges[ 10 ].p1, maxs[ 0 ], maxs[ 1 ], maxs[ 2 ] ); +	VectorSet( box_brush->edges[ 11 ].p0, mins[ 0 ], mins[ 1 ], maxs[ 2 ] ); +	VectorSet( box_brush->edges[ 11 ].p1, maxs[ 0 ], mins[ 1 ], maxs[ 2 ] ); + +	VectorCopy( mins, box_brush->bounds[0] ); +	VectorCopy( maxs, box_brush->bounds[1] ); + +	return BOX_MODEL_HANDLE; +} + +/* +=================== +CM_ModelBounds +=================== +*/ +void CM_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { +	cmodel_t	*cmod; + +	cmod = CM_ClipHandleToModel( model ); +	VectorCopy( cmod->mins, mins ); +	VectorCopy( cmod->maxs, maxs ); +} + + diff --git a/src/qcommon/cm_local.h b/src/qcommon/cm_local.h new file mode 100644 index 0000000..cc69482 --- /dev/null +++ b/src/qcommon/cm_local.h @@ -0,0 +1,214 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ + +#include "q_shared.h" +#include "qcommon.h" +#include "cm_polylib.h" + +#define	MAX_SUBMODELS			256 +#define	BOX_MODEL_HANDLE		255 +#define CAPSULE_MODEL_HANDLE	254 + + +typedef struct { +	cplane_t	*plane; +	int			children[2];		// negative numbers are leafs +} cNode_t; + +typedef struct { +	int			cluster; +	int			area; + +	int			firstLeafBrush; +	int			numLeafBrushes; + +	int			firstLeafSurface; +	int			numLeafSurfaces; +} cLeaf_t; + +typedef struct cmodel_s { +	vec3_t		mins, maxs; +	cLeaf_t		leaf;			// submodels don't reference the main tree +} cmodel_t; + +typedef struct cbrushedge_s +{ +	vec3_t	p0; +	vec3_t	p1; +} cbrushedge_t; + +typedef struct { +	cplane_t			*plane; +	int						planeNum; +	int						surfaceFlags; +	int						shaderNum; +	winding_t			*winding; +} cbrushside_t; + +typedef struct { +	int			shaderNum;		// the shader that determined the contents +	int			contents; +	vec3_t		bounds[2]; +	int			numsides; +	cbrushside_t	*sides; +	int			checkcount;		// to avoid repeated testings +	qboolean	collided; // marker for optimisation +	cbrushedge_t	*edges; +	int						numEdges; +} cbrush_t; + + +typedef struct { +	int			checkcount;				// to avoid repeated testings +	int			surfaceFlags; +	int			contents; +	struct patchCollide_s	*pc; +} cPatch_t; + + +typedef struct { +	int			floodnum; +	int			floodvalid; +} cArea_t; + +typedef struct { +	char		name[MAX_QPATH]; + +	int			numShaders; +	dshader_t	*shaders; + +	int			numBrushSides; +	cbrushside_t *brushsides; + +	int			numPlanes; +	cplane_t	*planes; + +	int			numNodes; +	cNode_t		*nodes; + +	int			numLeafs; +	cLeaf_t		*leafs; + +	int			numLeafBrushes; +	int			*leafbrushes; + +	int			numLeafSurfaces; +	int			*leafsurfaces; + +	int			numSubModels; +	cmodel_t	*cmodels; + +	int			numBrushes; +	cbrush_t	*brushes; + +	int			numClusters; +	int			clusterBytes; +	byte		*visibility; +	qboolean	vised;			// if false, visibility is just a single cluster of ffs + +	int			numEntityChars; +	char		*entityString; + +	int			numAreas; +	cArea_t		*areas; +	int			*areaPortals;	// [ numAreas*numAreas ] reference counts + +	int			numSurfaces; +	cPatch_t	**surfaces;			// non-patches will be NULL + +	int			floodvalid; +	int			checkcount;					// incremented on each trace +} clipMap_t; + + +// keep 1/8 unit away to keep the position valid before network snapping +// and to avoid various numeric issues +#define	SURFACE_CLIP_EPSILON	(0.125) + +extern	clipMap_t	cm; +extern	int			c_pointcontents; +extern	int			c_traces, c_brush_traces, c_patch_traces; +extern	cvar_t		*cm_noAreas; +extern	cvar_t		*cm_noCurves; +extern	cvar_t		*cm_playerCurveClip; + +// cm_test.c + +typedef struct +{ +	float		startRadius; +	float		endRadius; +} biSphere_t; + +// Used for oriented capsule collision detection +typedef struct +{ +	float		radius; +	float		halfheight; +	vec3_t		offset; +} sphere_t; + +typedef struct { +	traceType_t	type; +	vec3_t			start; +	vec3_t			end; +	vec3_t			size[2];	// size of the box being swept through the model +	vec3_t			offsets[8];	// [signbits][x] = either size[0][x] or size[1][x] +	float				maxOffset;	// longest corner length from origin +	vec3_t			extents;	// greatest of abs(size[0]) and abs(size[1]) +	vec3_t			bounds[2];	// enclosing box of start and end surrounding by size +	vec3_t			modelOrigin;// origin of the model tracing through +	int					contents;	// ored contents of the model tracing through +	qboolean		isPoint;	// optimized case +	trace_t			trace;		// returned from trace call +	sphere_t		sphere;		// sphere for oriendted capsule collision +	biSphere_t	biSphere; +	qboolean		testLateralCollision; // whether or not to test for lateral collision +} traceWork_t; + +typedef struct leafList_s { +	int		count; +	int		maxcount; +	qboolean	overflowed; +	int		*list; +	vec3_t	bounds[2]; +	int		lastLeaf;		// for overflows where each leaf can't be stored individually +	void	(*storeLeafs)( struct leafList_s *ll, int nodenum ); +} leafList_t; + + +int CM_BoxBrushes( const vec3_t mins, const vec3_t maxs, cbrush_t **list, int listsize ); + +void CM_StoreLeafs( leafList_t *ll, int nodenum ); +void CM_StoreBrushes( leafList_t *ll, int nodenum ); + +void CM_BoxLeafnums_r( leafList_t *ll, int nodenum ); + +cmodel_t	*CM_ClipHandleToModel( clipHandle_t handle ); + +// cm_patch.c + +struct patchCollide_s	*CM_GeneratePatchCollide( int width, int height, vec3_t *points ); +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +void CM_ClearLevelPatches( void ); diff --git a/src/qcommon/cm_patch.c b/src/qcommon/cm_patch.c new file mode 100644 index 0000000..f262db9 --- /dev/null +++ b/src/qcommon/cm_patch.c @@ -0,0 +1,1768 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ + +#include "cm_local.h" +#include "cm_patch.h" + +/* + +This file does not reference any globals, and has these entry points: + +void CM_ClearLevelPatches( void ); +struct patchCollide_s	*CM_GeneratePatchCollide( int width, int height, const vec3_t *points ); +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, flaot *points) ); + + +WARNING: this may misbehave with meshes that have rows or columns that only +degenerate a few triangles.  Completely degenerate rows and columns are handled +properly. +*/ + +/* +#define	MAX_FACETS			1024 +#define	MAX_PATCH_PLANES	2048 + +typedef struct { +	float	plane[4]; +	int		signbits;		// signx + (signy<<1) + (signz<<2), used as lookup during collision +} patchPlane_t; + +typedef struct { +	int			surfacePlane; +	int			numBorders;		// 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels +	int			borderPlanes[4+6+16]; +	int			borderInward[4+6+16]; +	qboolean	borderNoAdjust[4+6+16]; +} facet_t; + +typedef struct patchCollide_s { +	vec3_t	bounds[2]; +	int		numPlanes;			// surface planes plus edge planes +	patchPlane_t	*planes; +	int		numFacets; +	facet_t	*facets; +} patchCollide_t; + + +#define	MAX_GRID_SIZE	129 + +typedef struct { +	int			width; +	int			height; +	qboolean	wrapWidth; +	qboolean	wrapHeight; +	vec3_t	points[MAX_GRID_SIZE][MAX_GRID_SIZE];	// [width][height] +} cGrid_t; + +#define	SUBDIVIDE_DISTANCE	16	//4	// never more than this units away from curve +#define	PLANE_TRI_EPSILON	0.1 +#define	WRAP_POINT_EPSILON	0.1 +*/ + +int	c_totalPatchBlocks; +int	c_totalPatchSurfaces; +int	c_totalPatchEdges; + +static const patchCollide_t	*debugPatchCollide; +static const facet_t		*debugFacet; +static qboolean		debugBlock; +static vec3_t		debugBlockPoints[4]; + +/* +================= +CM_ClearLevelPatches +================= +*/ +void CM_ClearLevelPatches( void ) { +	debugPatchCollide = NULL; +	debugFacet = NULL; +} + +/* +================= +CM_SignbitsForNormal +================= +*/ +static int CM_SignbitsForNormal( vec3_t normal ) { +	int	bits, j; + +	bits = 0; +	for (j=0 ; j<3 ; j++) { +		if ( normal[j] < 0 ) { +			bits |= 1<<j; +		} +	} +	return bits; +} + +/* +===================== +CM_PlaneFromPoints + +Returns false if the triangle is degenrate. +The normal will point out of the clock for clockwise ordered points +===================== +*/ +static qboolean CM_PlaneFromPoints( vec4_t plane, vec3_t a, vec3_t b, vec3_t c ) { +	vec3_t	d1, d2; + +	VectorSubtract( b, a, d1 ); +	VectorSubtract( c, a, d2 ); +	CrossProduct( d2, d1, plane ); +	if ( VectorNormalize( plane ) == 0 ) { +		return qfalse; +	} + +	plane[3] = DotProduct( a, plane ); +	return qtrue; +} + + +/* +================================================================================ + +GRID SUBDIVISION + +================================================================================ +*/ + +/* +================= +CM_NeedsSubdivision + +Returns true if the given quadratic curve is not flat enough for our +collision detection purposes +================= +*/ +static qboolean	CM_NeedsSubdivision( vec3_t a, vec3_t b, vec3_t c ) { +	vec3_t		cmid; +	vec3_t		lmid; +	vec3_t		delta; +	float		dist; +	int			i; + +	// calculate the linear midpoint +	for ( i = 0 ; i < 3 ; i++ ) { +		lmid[i] = 0.5*(a[i] + c[i]); +	} + +	// calculate the exact curve midpoint +	for ( i = 0 ; i < 3 ; i++ ) { +		cmid[i] = 0.5 * ( 0.5*(a[i] + b[i]) + 0.5*(b[i] + c[i]) ); +	} + +	// see if the curve is far enough away from the linear mid +	VectorSubtract( cmid, lmid, delta ); +	dist = VectorLength( delta ); +	 +	return dist >= SUBDIVIDE_DISTANCE; +} + +/* +=============== +CM_Subdivide + +a, b, and c are control points. +the subdivided sequence will be: a, out1, out2, out3, c +=============== +*/ +static void CM_Subdivide( vec3_t a, vec3_t b, vec3_t c, vec3_t out1, vec3_t out2, vec3_t out3 ) { +	int		i; + +	for ( i = 0 ; i < 3 ; i++ ) { +		out1[i] = 0.5 * (a[i] + b[i]); +		out3[i] = 0.5 * (b[i] + c[i]); +		out2[i] = 0.5 * (out1[i] + out3[i]); +	} +} + +/* +================= +CM_TransposeGrid + +Swaps the rows and columns in place +================= +*/ +static void CM_TransposeGrid( cGrid_t *grid ) { +	int			i, j, l; +	vec3_t		temp; +	qboolean	tempWrap; + +	if ( grid->width > grid->height ) { +		for ( i = 0 ; i < grid->height ; i++ ) { +			for ( j = i + 1 ; j < grid->width ; j++ ) { +				if ( j < grid->height ) { +					// swap the value +					VectorCopy( grid->points[i][j], temp ); +					VectorCopy( grid->points[j][i], grid->points[i][j] ); +					VectorCopy( temp, grid->points[j][i] ); +				} else { +					// just copy +					VectorCopy( grid->points[j][i], grid->points[i][j] ); +				} +			} +		} +	} else { +		for ( i = 0 ; i < grid->width ; i++ ) { +			for ( j = i + 1 ; j < grid->height ; j++ ) { +				if ( j < grid->width ) { +					// swap the value +					VectorCopy( grid->points[j][i], temp ); +					VectorCopy( grid->points[i][j], grid->points[j][i] ); +					VectorCopy( temp, grid->points[i][j] ); +				} else { +					// just copy +					VectorCopy( grid->points[i][j], grid->points[j][i] ); +				} +			} +		} +	} + +	l = grid->width; +	grid->width = grid->height; +	grid->height = l; + +	tempWrap = grid->wrapWidth; +	grid->wrapWidth = grid->wrapHeight; +	grid->wrapHeight = tempWrap; +} + +/* +=================== +CM_SetGridWrapWidth + +If the left and right columns are exactly equal, set grid->wrapWidth qtrue +=================== +*/ +static void CM_SetGridWrapWidth( cGrid_t *grid ) { +	int		i, j; +	float	d; + +	for ( i = 0 ; i < grid->height ; i++ ) { +		for ( j = 0 ; j < 3 ; j++ ) { +			d = grid->points[0][i][j] - grid->points[grid->width-1][i][j]; +			if ( d < -WRAP_POINT_EPSILON || d > WRAP_POINT_EPSILON ) { +				break; +			} +		} +		if ( j != 3 ) { +			break; +		} +	} +	if ( i == grid->height ) { +		grid->wrapWidth = qtrue; +	} else { +		grid->wrapWidth = qfalse; +	} +} + +/* +================= +CM_SubdivideGridColumns + +Adds columns as necessary to the grid until +all the aproximating points are within SUBDIVIDE_DISTANCE +from the true curve +================= +*/ +static void CM_SubdivideGridColumns( cGrid_t *grid ) { +	int		i, j, k; + +	for ( i = 0 ; i < grid->width - 2 ;  ) { +		// grid->points[i][x] is an interpolating control point +		// grid->points[i+1][x] is an aproximating control point +		// grid->points[i+2][x] is an interpolating control point + +		// +		// first see if we can collapse the aproximating collumn away +		// +		for ( j = 0 ; j < grid->height ; j++ ) { +			if ( CM_NeedsSubdivision( grid->points[i][j], grid->points[i+1][j], grid->points[i+2][j] ) ) { +				break; +			} +		} +		if ( j == grid->height ) { +			// all of the points were close enough to the linear midpoints +			// that we can collapse the entire column away +			for ( j = 0 ; j < grid->height ; j++ ) { +				// remove the column +				for ( k = i + 2 ; k < grid->width ; k++ ) { +					VectorCopy( grid->points[k][j], grid->points[k-1][j] ); +				} +			} + +			grid->width--; + +			// go to the next curve segment +			i++; +			continue; +		} + +		// +		// we need to subdivide the curve +		// +		for ( j = 0 ; j < grid->height ; j++ ) { +			vec3_t	prev, mid, next; + +			// save the control points now +			VectorCopy( grid->points[i][j], prev ); +			VectorCopy( grid->points[i+1][j], mid ); +			VectorCopy( grid->points[i+2][j], next ); + +			// make room for two additional columns in the grid +			// columns i+1 will be replaced, column i+2 will become i+4 +			// i+1, i+2, and i+3 will be generated +			for ( k = grid->width - 1 ; k > i + 1 ; k-- ) { +				VectorCopy( grid->points[k][j], grid->points[k+2][j] ); +			} + +			// generate the subdivided points +			CM_Subdivide( prev, mid, next, grid->points[i+1][j], grid->points[i+2][j], grid->points[i+3][j] ); +		} + +		grid->width += 2; + +		// the new aproximating point at i+1 may need to be removed +		// or subdivided farther, so don't advance i +	} +} + +/* +====================== +CM_ComparePoints +====================== +*/ +#define	POINT_EPSILON	0.1 +static qboolean CM_ComparePoints( float *a, float *b ) { +	float		d; + +	d = a[0] - b[0]; +	if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { +		return qfalse; +	} +	d = a[1] - b[1]; +	if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { +		return qfalse; +	} +	d = a[2] - b[2]; +	if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { +		return qfalse; +	} +	return qtrue; +} + +/* +================= +CM_RemoveDegenerateColumns + +If there are any identical columns, remove them +================= +*/ +static void CM_RemoveDegenerateColumns( cGrid_t *grid ) { +	int		i, j, k; + +	for ( i = 0 ; i < grid->width - 1 ; i++ ) { +		for ( j = 0 ; j < grid->height ; j++ ) { +			if ( !CM_ComparePoints( grid->points[i][j], grid->points[i+1][j] ) ) { +				break; +			} +		} + +		if ( j != grid->height ) { +			continue;	// not degenerate +		} + +		for ( j = 0 ; j < grid->height ; j++ ) { +			// remove the column +			for ( k = i + 2 ; k < grid->width ; k++ ) { +				VectorCopy( grid->points[k][j], grid->points[k-1][j] ); +			} +		} +		grid->width--; + +		// check against the next column +		i--; +	} +} + +/* +================================================================================ + +PATCH COLLIDE GENERATION + +================================================================================ +*/ + +static	int				numPlanes; +static	patchPlane_t	planes[MAX_PATCH_PLANES]; + +static	int				numFacets; +static	facet_t			facets[MAX_PATCH_PLANES]; //maybe MAX_FACETS ?? + +#define	NORMAL_EPSILON	0.0001 +#define	DIST_EPSILON	0.02 + +/* +================== +CM_PlaneEqual +================== +*/ +int CM_PlaneEqual(patchPlane_t *p, float plane[4], int *flipped) { +	float invplane[4]; + +	if ( +	   fabs(p->plane[0] - plane[0]) < NORMAL_EPSILON +	&& fabs(p->plane[1] - plane[1]) < NORMAL_EPSILON +	&& fabs(p->plane[2] - plane[2]) < NORMAL_EPSILON +	&& fabs(p->plane[3] - plane[3]) < DIST_EPSILON ) +	{ +		*flipped = qfalse; +		return qtrue; +	} + +	VectorNegate(plane, invplane); +	invplane[3] = -plane[3]; + +	if ( +	   fabs(p->plane[0] - invplane[0]) < NORMAL_EPSILON +	&& fabs(p->plane[1] - invplane[1]) < NORMAL_EPSILON +	&& fabs(p->plane[2] - invplane[2]) < NORMAL_EPSILON +	&& fabs(p->plane[3] - invplane[3]) < DIST_EPSILON ) +	{ +		*flipped = qtrue; +		return qtrue; +	} + +	return qfalse; +} + +/* +================== +CM_SnapVector +================== +*/ +void CM_SnapVector(vec3_t normal) { +	int		i; + +	for (i=0 ; i<3 ; i++) +	{ +		if ( fabs(normal[i] - 1) < NORMAL_EPSILON ) +		{ +			VectorClear (normal); +			normal[i] = 1; +			break; +		} +		if ( fabs(normal[i] - -1) < NORMAL_EPSILON ) +		{ +			VectorClear (normal); +			normal[i] = -1; +			break; +		} +	} +} + +/* +================== +CM_FindPlane2 +================== +*/ +int CM_FindPlane2(float plane[4], int *flipped) { +	int i; + +	// see if the points are close enough to an existing plane +	for ( i = 0 ; i < numPlanes ; i++ ) { +		if (CM_PlaneEqual(&planes[i], plane, flipped)) return i; +	} + +	// add a new plane +	if ( numPlanes == MAX_PATCH_PLANES ) { +		Com_Error( ERR_DROP, "MAX_PATCH_PLANES" ); +	} + +	Vector4Copy( plane, planes[numPlanes].plane ); +	planes[numPlanes].signbits = CM_SignbitsForNormal( plane ); + +	numPlanes++; + +	*flipped = qfalse; + +	return numPlanes-1; +} + +/* +================== +CM_FindPlane +================== +*/ +static int CM_FindPlane( float *p1, float *p2, float *p3 ) { +	float	plane[4]; +	int		i; +	float	d; + +	if ( !CM_PlaneFromPoints( plane, p1, p2, p3 ) ) { +		return -1; +	} + +	// see if the points are close enough to an existing plane +	for ( i = 0 ; i < numPlanes ; i++ ) { +		if ( DotProduct( plane, planes[i].plane ) < 0 ) { +			continue;	// allow backwards planes? +		} + +		d = DotProduct( p1, planes[i].plane ) - planes[i].plane[3]; +		if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { +			continue; +		} + +		d = DotProduct( p2, planes[i].plane ) - planes[i].plane[3]; +		if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { +			continue; +		} + +		d = DotProduct( p3, planes[i].plane ) - planes[i].plane[3]; +		if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { +			continue; +		} + +		// found it +		return i; +	} + +	// add a new plane +	if ( numPlanes == MAX_PATCH_PLANES ) { +		Com_Error( ERR_DROP, "MAX_PATCH_PLANES" ); +	} + +	Vector4Copy( plane, planes[numPlanes].plane ); +	planes[numPlanes].signbits = CM_SignbitsForNormal( plane ); + +	numPlanes++; + +	return numPlanes-1; +} + +/* +================== +CM_PointOnPlaneSide +================== +*/ +static int CM_PointOnPlaneSide( float *p, int planeNum ) { +	float	*plane; +	float	d; + +	if ( planeNum == -1 ) { +		return SIDE_ON; +	} +	plane = planes[ planeNum ].plane; + +	d = DotProduct( p, plane ) - plane[3]; + +	if ( d > PLANE_TRI_EPSILON ) { +		return SIDE_FRONT; +	} + +	if ( d < -PLANE_TRI_EPSILON ) { +		return SIDE_BACK; +	} + +	return SIDE_ON; +} + +/* +================== +CM_GridPlane +================== +*/ +static int	CM_GridPlane( int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2], int i, int j, int tri ) { +	int		p; + +	p = gridPlanes[i][j][tri]; +	if ( p != -1 ) { +		return p; +	} +	p = gridPlanes[i][j][!tri]; +	if ( p != -1 ) { +		return p; +	} + +	// should never happen +	Com_Printf( "WARNING: CM_GridPlane unresolvable\n" ); +	return -1; +} + +/* +================== +CM_EdgePlaneNum +================== +*/ +static int CM_EdgePlaneNum( cGrid_t *grid, int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2], int i, int j, int k ) { +	float	*p1, *p2; +	vec3_t		up; +	int			p; + +	switch ( k ) { +	case 0:	// top border +		p1 = grid->points[i][j]; +		p2 = grid->points[i+1][j]; +		p = CM_GridPlane( gridPlanes, i, j, 0 ); +		VectorMA( p1, 4, planes[ p ].plane, up ); +		return CM_FindPlane( p1, p2, up ); + +	case 2:	// bottom border +		p1 = grid->points[i][j+1]; +		p2 = grid->points[i+1][j+1]; +		p = CM_GridPlane( gridPlanes, i, j, 1 ); +		VectorMA( p1, 4, planes[ p ].plane, up ); +		return CM_FindPlane( p2, p1, up ); + +	case 3: // left border +		p1 = grid->points[i][j]; +		p2 = grid->points[i][j+1]; +		p = CM_GridPlane( gridPlanes, i, j, 1 ); +		VectorMA( p1, 4, planes[ p ].plane, up ); +		return CM_FindPlane( p2, p1, up ); + +	case 1:	// right border +		p1 = grid->points[i+1][j]; +		p2 = grid->points[i+1][j+1]; +		p = CM_GridPlane( gridPlanes, i, j, 0 ); +		VectorMA( p1, 4, planes[ p ].plane, up ); +		return CM_FindPlane( p1, p2, up ); + +	case 4:	// diagonal out of triangle 0 +		p1 = grid->points[i+1][j+1]; +		p2 = grid->points[i][j]; +		p = CM_GridPlane( gridPlanes, i, j, 0 ); +		VectorMA( p1, 4, planes[ p ].plane, up ); +		return CM_FindPlane( p1, p2, up ); + +	case 5:	// diagonal out of triangle 1 +		p1 = grid->points[i][j]; +		p2 = grid->points[i+1][j+1]; +		p = CM_GridPlane( gridPlanes, i, j, 1 ); +		VectorMA( p1, 4, planes[ p ].plane, up ); +		return CM_FindPlane( p1, p2, up ); + +	} + +	Com_Error( ERR_DROP, "CM_EdgePlaneNum: bad k" ); +	return -1; +} + +/* +=================== +CM_SetBorderInward +=================== +*/ +static void CM_SetBorderInward( facet_t *facet, cGrid_t *grid, int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2], +						  int i, int j, int which ) { +	int		k, l; +	float	*points[4]; +	int		numPoints; + +	switch ( which ) { +	case -1: +		points[0] = grid->points[i][j]; +		points[1] = grid->points[i+1][j]; +		points[2] = grid->points[i+1][j+1]; +		points[3] = grid->points[i][j+1]; +		numPoints = 4; +		break; +	case 0: +		points[0] = grid->points[i][j]; +		points[1] = grid->points[i+1][j]; +		points[2] = grid->points[i+1][j+1]; +		numPoints = 3; +		break; +	case 1: +		points[0] = grid->points[i+1][j+1]; +		points[1] = grid->points[i][j+1]; +		points[2] = grid->points[i][j]; +		numPoints = 3; +		break; +	default: +		Com_Error( ERR_FATAL, "CM_SetBorderInward: bad parameter" ); +		numPoints = 0; +		break; +	} + +	for ( k = 0 ; k < facet->numBorders ; k++ ) { +		int		front, back; + +		front = 0; +		back = 0; + +		for ( l = 0 ; l < numPoints ; l++ ) { +			int		side; + +			side = CM_PointOnPlaneSide( points[l], facet->borderPlanes[k] ); +			if ( side == SIDE_FRONT ) { +				front++; +			} if ( side == SIDE_BACK ) { +				back++; +			} +		} + +		if ( front && !back ) { +			facet->borderInward[k] = qtrue; +		} else if ( back && !front ) { +			facet->borderInward[k] = qfalse; +		} else if ( !front && !back ) { +			// flat side border +			facet->borderPlanes[k] = -1; +		} else { +			// bisecting side border +			Com_DPrintf( "WARNING: CM_SetBorderInward: mixed plane sides\n" ); +			facet->borderInward[k] = qfalse; +			if ( !debugBlock ) { +				debugBlock = qtrue; +				VectorCopy( grid->points[i][j], debugBlockPoints[0] ); +				VectorCopy( grid->points[i+1][j], debugBlockPoints[1] ); +				VectorCopy( grid->points[i+1][j+1], debugBlockPoints[2] ); +				VectorCopy( grid->points[i][j+1], debugBlockPoints[3] ); +			} +		} +	} +} + +/* +================== +CM_ValidateFacet + +If the facet isn't bounded by its borders, we screwed up. +================== +*/ +static qboolean CM_ValidateFacet( facet_t *facet ) { +	float		plane[4]; +	int			j; +	winding_t	*w; +	vec3_t		bounds[2]; + +	if ( facet->surfacePlane == -1 ) { +		return qfalse; +	} + +	Vector4Copy( planes[ facet->surfacePlane ].plane, plane ); +	w = BaseWindingForPlane( plane,  plane[3] ); +	for ( j = 0 ; j < facet->numBorders && w ; j++ ) { +		if ( facet->borderPlanes[j] == -1 ) { +			FreeWinding( w ); +			return qfalse; +		} +		Vector4Copy( planes[ facet->borderPlanes[j] ].plane, plane ); +		if ( !facet->borderInward[j] ) { +			VectorSubtract( vec3_origin, plane, plane ); +			plane[3] = -plane[3]; +		} +		ChopWindingInPlace( &w, plane, plane[3], 0.1f ); +	} + +	if ( !w ) { +		return qfalse;		// winding was completely chopped away +	} + +	// see if the facet is unreasonably large +	WindingBounds( w, bounds[0], bounds[1] ); +	FreeWinding( w ); +	 +	for ( j = 0 ; j < 3 ; j++ ) { +		if ( bounds[1][j] - bounds[0][j] > MAX_MAP_BOUNDS ) { +			return qfalse;		// we must be missing a plane +		} +		if ( bounds[0][j] >= MAX_MAP_BOUNDS ) { +			return qfalse; +		} +		if ( bounds[1][j] <= -MAX_MAP_BOUNDS ) { +			return qfalse; +		} +	} +	return qtrue;		// winding is fine +} + +/* +================== +CM_AddFacetBevels +================== +*/ +void CM_AddFacetBevels( facet_t *facet ) { + +	int i, j, k, l; +	int axis, dir, order, flipped; +	float plane[4], d, newplane[4]; +	winding_t *w, *w2; +	vec3_t mins, maxs, vec, vec2; + +	Vector4Copy( planes[ facet->surfacePlane ].plane, plane ); + +	w = BaseWindingForPlane( plane,  plane[3] ); +	for ( j = 0 ; j < facet->numBorders && w ; j++ ) { +		if (facet->borderPlanes[j] == facet->surfacePlane) continue; +		Vector4Copy( planes[ facet->borderPlanes[j] ].plane, plane ); + +		if ( !facet->borderInward[j] ) { +			VectorSubtract( vec3_origin, plane, plane ); +			plane[3] = -plane[3]; +		} + +		ChopWindingInPlace( &w, plane, plane[3], 0.1f ); +	} +	if ( !w ) { +		return; +	} + +	WindingBounds(w, mins, maxs); + +	// add the axial planes +	order = 0; +	for ( axis = 0 ; axis < 3 ; axis++ ) +	{ +		for ( dir = -1 ; dir <= 1 ; dir += 2, order++ ) +		{ +			VectorClear(plane); +			plane[axis] = dir; +			if (dir == 1) { +				plane[3] = maxs[axis]; +			} +			else { +				plane[3] = -mins[axis]; +			} +			//if it's the surface plane +			if (CM_PlaneEqual(&planes[facet->surfacePlane], plane, &flipped)) { +				continue; +			} +			// see if the plane is allready present +			for ( i = 0 ; i < facet->numBorders ; i++ ) { +				if (CM_PlaneEqual(&planes[facet->borderPlanes[i]], plane, &flipped)) +					break; +			} + +			if ( i == facet->numBorders ) { +				if (facet->numBorders > 4 + 6 + 16) Com_Printf("ERROR: too many bevels\n"); +				facet->borderPlanes[facet->numBorders] = CM_FindPlane2(plane, &flipped); +				facet->borderNoAdjust[facet->numBorders] = 0; +				facet->borderInward[facet->numBorders] = flipped; +				facet->numBorders++; +			} +		} +	} +	// +	// add the edge bevels +	// +	// test the non-axial plane edges +	for ( j = 0 ; j < w->numpoints ; j++ ) +	{ +		k = (j+1)%w->numpoints; +		VectorSubtract (w->p[j], w->p[k], vec); +		//if it's a degenerate edge +		if (VectorNormalize (vec) < 0.5) +			continue; +		CM_SnapVector(vec); +		for ( k = 0; k < 3 ; k++ ) +			if ( vec[k] == -1 || vec[k] == 1 ) +				break;	// axial +		if ( k < 3 ) +			continue;	// only test non-axial edges + +		// try the six possible slanted axials from this edge +		for ( axis = 0 ; axis < 3 ; axis++ ) +		{ +			for ( dir = -1 ; dir <= 1 ; dir += 2 ) +			{ +				// construct a plane +				VectorClear (vec2); +				vec2[axis] = dir; +				CrossProduct (vec, vec2, plane); +				if (VectorNormalize (plane) < 0.5) +					continue; +				plane[3] = DotProduct (w->p[j], plane); + +				// if all the points of the facet winding are +				// behind this plane, it is a proper edge bevel +				for ( l = 0 ; l < w->numpoints ; l++ ) +				{ +					d = DotProduct (w->p[l], plane) - plane[3]; +					if (d > 0.1) +						break;	// point in front +				} +				if ( l < w->numpoints ) +					continue; + +				//if it's the surface plane +				if (CM_PlaneEqual(&planes[facet->surfacePlane], plane, &flipped)) { +					continue; +				} +				// see if the plane is allready present +				for ( i = 0 ; i < facet->numBorders ; i++ ) { +					if (CM_PlaneEqual(&planes[facet->borderPlanes[i]], plane, &flipped)) { +							break; +					} +				} + +				if ( i == facet->numBorders ) { +					if (facet->numBorders > 4 + 6 + 16) Com_Printf("ERROR: too many bevels\n"); +					facet->borderPlanes[facet->numBorders] = CM_FindPlane2(plane, &flipped); + +					for ( k = 0 ; k < facet->numBorders ; k++ ) { +						if (facet->borderPlanes[facet->numBorders] == +							facet->borderPlanes[k]) Com_Printf("WARNING: bevel plane already used\n"); +					} + +					facet->borderNoAdjust[facet->numBorders] = 0; +					facet->borderInward[facet->numBorders] = flipped; +					// +					w2 = CopyWinding(w); +					Vector4Copy(planes[facet->borderPlanes[facet->numBorders]].plane, newplane); +					if (!facet->borderInward[facet->numBorders]) +					{ +						VectorNegate(newplane, newplane); +						newplane[3] = -newplane[3]; +					} //end if +					ChopWindingInPlace( &w2, newplane, newplane[3], 0.1f ); +					if (!w2) { +						Com_DPrintf("WARNING: CM_AddFacetBevels... invalid bevel\n"); +						continue; +					} +					else { +						FreeWinding(w2); +					} +					// +					facet->numBorders++; +					//already got a bevel +//					break; +				} +			} +		} +	} +	FreeWinding( w ); + +#ifndef BSPC +	//add opposite plane +	facet->borderPlanes[facet->numBorders] = facet->surfacePlane; +	facet->borderNoAdjust[facet->numBorders] = 0; +	facet->borderInward[facet->numBorders] = qtrue; +	facet->numBorders++; +#endif //BSPC + +} + +typedef enum { +	EN_TOP, +	EN_RIGHT, +	EN_BOTTOM, +	EN_LEFT +} edgeName_t; + +/* +================== +CM_PatchCollideFromGrid +================== +*/ +static void CM_PatchCollideFromGrid( cGrid_t *grid, patchCollide_t *pf ) { +	int				i, j; +	float			*p1, *p2, *p3; +	int				gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2]; +	facet_t			*facet; +	int				borders[4]; +	int				noAdjust[4]; + +	numPlanes = 0; +	numFacets = 0; + +	// find the planes for each triangle of the grid +	for ( i = 0 ; i < grid->width - 1 ; i++ ) { +		for ( j = 0 ; j < grid->height - 1 ; j++ ) { +			p1 = grid->points[i][j]; +			p2 = grid->points[i+1][j]; +			p3 = grid->points[i+1][j+1]; +			gridPlanes[i][j][0] = CM_FindPlane( p1, p2, p3 ); + +			p1 = grid->points[i+1][j+1]; +			p2 = grid->points[i][j+1]; +			p3 = grid->points[i][j]; +			gridPlanes[i][j][1] = CM_FindPlane( p1, p2, p3 ); +		} +	} + +	// create the borders for each facet +	for ( i = 0 ; i < grid->width - 1 ; i++ ) { +		for ( j = 0 ; j < grid->height - 1 ; j++ ) { +			  +			borders[EN_TOP] = -1; +			if ( j > 0 ) { +				borders[EN_TOP] = gridPlanes[i][j-1][1]; +			} else if ( grid->wrapHeight ) { +				borders[EN_TOP] = gridPlanes[i][grid->height-2][1]; +			}  +			noAdjust[EN_TOP] = ( borders[EN_TOP] == gridPlanes[i][j][0] ); +			if ( borders[EN_TOP] == -1 || noAdjust[EN_TOP] ) { +				borders[EN_TOP] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 0 ); +			} + +			borders[EN_BOTTOM] = -1; +			if ( j < grid->height - 2 ) { +				borders[EN_BOTTOM] = gridPlanes[i][j+1][0]; +			} else if ( grid->wrapHeight ) { +				borders[EN_BOTTOM] = gridPlanes[i][0][0]; +			} +			noAdjust[EN_BOTTOM] = ( borders[EN_BOTTOM] == gridPlanes[i][j][1] ); +			if ( borders[EN_BOTTOM] == -1 || noAdjust[EN_BOTTOM] ) { +				borders[EN_BOTTOM] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 2 ); +			} + +			borders[EN_LEFT] = -1; +			if ( i > 0 ) { +				borders[EN_LEFT] = gridPlanes[i-1][j][0]; +			} else if ( grid->wrapWidth ) { +				borders[EN_LEFT] = gridPlanes[grid->width-2][j][0]; +			} +			noAdjust[EN_LEFT] = ( borders[EN_LEFT] == gridPlanes[i][j][1] ); +			if ( borders[EN_LEFT] == -1 || noAdjust[EN_LEFT] ) { +				borders[EN_LEFT] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 3 ); +			} + +			borders[EN_RIGHT] = -1; +			if ( i < grid->width - 2 ) { +				borders[EN_RIGHT] = gridPlanes[i+1][j][1]; +			} else if ( grid->wrapWidth ) { +				borders[EN_RIGHT] = gridPlanes[0][j][1]; +			} +			noAdjust[EN_RIGHT] = ( borders[EN_RIGHT] == gridPlanes[i][j][0] ); +			if ( borders[EN_RIGHT] == -1 || noAdjust[EN_RIGHT] ) { +				borders[EN_RIGHT] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 1 ); +			} + +			if ( numFacets == MAX_FACETS ) { +				Com_Error( ERR_DROP, "MAX_FACETS" ); +			} +			facet = &facets[numFacets]; +			Com_Memset( facet, 0, sizeof( *facet ) ); + +			if ( gridPlanes[i][j][0] == gridPlanes[i][j][1] ) { +				if ( gridPlanes[i][j][0] == -1 ) { +					continue;		// degenrate +				} +				facet->surfacePlane = gridPlanes[i][j][0]; +				facet->numBorders = 4; +				facet->borderPlanes[0] = borders[EN_TOP]; +				facet->borderNoAdjust[0] = noAdjust[EN_TOP]; +				facet->borderPlanes[1] = borders[EN_RIGHT]; +				facet->borderNoAdjust[1] = noAdjust[EN_RIGHT]; +				facet->borderPlanes[2] = borders[EN_BOTTOM]; +				facet->borderNoAdjust[2] = noAdjust[EN_BOTTOM]; +				facet->borderPlanes[3] = borders[EN_LEFT]; +				facet->borderNoAdjust[3] = noAdjust[EN_LEFT]; +				CM_SetBorderInward( facet, grid, gridPlanes, i, j, -1 ); +				if ( CM_ValidateFacet( facet ) ) { +					CM_AddFacetBevels( facet ); +					numFacets++; +				} +			} else { +				// two seperate triangles +				facet->surfacePlane = gridPlanes[i][j][0]; +				facet->numBorders = 3; +				facet->borderPlanes[0] = borders[EN_TOP]; +				facet->borderNoAdjust[0] = noAdjust[EN_TOP]; +				facet->borderPlanes[1] = borders[EN_RIGHT]; +				facet->borderNoAdjust[1] = noAdjust[EN_RIGHT]; +				facet->borderPlanes[2] = gridPlanes[i][j][1]; +				if ( facet->borderPlanes[2] == -1 ) { +					facet->borderPlanes[2] = borders[EN_BOTTOM]; +					if ( facet->borderPlanes[2] == -1 ) { +						facet->borderPlanes[2] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 4 ); +					} +				} + 				CM_SetBorderInward( facet, grid, gridPlanes, i, j, 0 ); +				if ( CM_ValidateFacet( facet ) ) { +					CM_AddFacetBevels( facet ); +					numFacets++; +				} + +				if ( numFacets == MAX_FACETS ) { +					Com_Error( ERR_DROP, "MAX_FACETS" ); +				} +				facet = &facets[numFacets]; +				Com_Memset( facet, 0, sizeof( *facet ) ); + +				facet->surfacePlane = gridPlanes[i][j][1]; +				facet->numBorders = 3; +				facet->borderPlanes[0] = borders[EN_BOTTOM]; +				facet->borderNoAdjust[0] = noAdjust[EN_BOTTOM]; +				facet->borderPlanes[1] = borders[EN_LEFT]; +				facet->borderNoAdjust[1] = noAdjust[EN_LEFT]; +				facet->borderPlanes[2] = gridPlanes[i][j][0]; +				if ( facet->borderPlanes[2] == -1 ) { +					facet->borderPlanes[2] = borders[EN_TOP]; +					if ( facet->borderPlanes[2] == -1 ) { +						facet->borderPlanes[2] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 5 ); +					} +				} +				CM_SetBorderInward( facet, grid, gridPlanes, i, j, 1 ); +				if ( CM_ValidateFacet( facet ) ) { +					CM_AddFacetBevels( facet ); +					numFacets++; +				} +			} +		} +	} + +	// copy the results out +	pf->numPlanes = numPlanes; +	pf->numFacets = numFacets; +	pf->facets = Hunk_Alloc( numFacets * sizeof( *pf->facets ), h_high ); +	Com_Memcpy( pf->facets, facets, numFacets * sizeof( *pf->facets ) ); +	pf->planes = Hunk_Alloc( numPlanes * sizeof( *pf->planes ), h_high ); +	Com_Memcpy( pf->planes, planes, numPlanes * sizeof( *pf->planes ) ); +} + + +/* +=================== +CM_GeneratePatchCollide + +Creates an internal structure that will be used to perform +collision detection with a patch mesh. + +Points is packed as concatenated rows. +=================== +*/ +struct patchCollide_s	*CM_GeneratePatchCollide( int width, int height, vec3_t *points ) { +	patchCollide_t	*pf; +	cGrid_t			grid; +	int				i, j; + +	if ( width <= 2 || height <= 2 || !points ) { +		Com_Error( ERR_DROP, "CM_GeneratePatchFacets: bad parameters: (%i, %i, %p)", +			width, height, points ); +	} + +	if ( !(width & 1) || !(height & 1) ) { +		Com_Error( ERR_DROP, "CM_GeneratePatchFacets: even sizes are invalid for quadratic meshes" ); +	} + +	if ( width > MAX_GRID_SIZE || height > MAX_GRID_SIZE ) { +		Com_Error( ERR_DROP, "CM_GeneratePatchFacets: source is > MAX_GRID_SIZE" ); +	} + +	// build a grid +	grid.width = width; +	grid.height = height; +	grid.wrapWidth = qfalse; +	grid.wrapHeight = qfalse; +	for ( i = 0 ; i < width ; i++ ) { +		for ( j = 0 ; j < height ; j++ ) { +			VectorCopy( points[j*width + i], grid.points[i][j] ); +		} +	} + +	// subdivide the grid +	CM_SetGridWrapWidth( &grid ); +	CM_SubdivideGridColumns( &grid ); +	CM_RemoveDegenerateColumns( &grid ); + +	CM_TransposeGrid( &grid ); + +	CM_SetGridWrapWidth( &grid ); +	CM_SubdivideGridColumns( &grid ); +	CM_RemoveDegenerateColumns( &grid ); + +	// we now have a grid of points exactly on the curve +	// the aproximate surface defined by these points will be +	// collided against +	pf = Hunk_Alloc( sizeof( *pf ), h_high ); +	ClearBounds( pf->bounds[0], pf->bounds[1] ); +	for ( i = 0 ; i < grid.width ; i++ ) { +		for ( j = 0 ; j < grid.height ; j++ ) { +			AddPointToBounds( grid.points[i][j], pf->bounds[0], pf->bounds[1] ); +		} +	} + +	c_totalPatchBlocks += ( grid.width - 1 ) * ( grid.height - 1 ); + +	// generate a bsp tree for the surface +	CM_PatchCollideFromGrid( &grid, pf ); + +	// expand by one unit for epsilon purposes +	pf->bounds[0][0] -= 1; +	pf->bounds[0][1] -= 1; +	pf->bounds[0][2] -= 1; + +	pf->bounds[1][0] += 1; +	pf->bounds[1][1] += 1; +	pf->bounds[1][2] += 1; + +	return pf; +} + +/* +================================================================================ + +TRACE TESTING + +================================================================================ +*/ + +/* +==================== +CM_TracePointThroughPatchCollide + +  special case for point traces because the patch collide "brushes" have no volume +==================== +*/ +void CM_TracePointThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) { +	qboolean	frontFacing[MAX_PATCH_PLANES]; +	float		intersection[MAX_PATCH_PLANES]; +	float		intersect; +	const patchPlane_t	*planes; +	const facet_t	*facet; +	int			i, j, k; +	float		offset; +	float		d1, d2; +#ifndef BSPC +	static cvar_t *cv; +#endif //BSPC + +#ifndef BSPC +	if ( !cm_playerCurveClip->integer || !tw->isPoint ) { +		return; +	} +#endif + +	// determine the trace's relationship to all planes +	planes = pc->planes; +	for ( i = 0 ; i < pc->numPlanes ; i++, planes++ ) { +		offset = DotProduct( tw->offsets[ planes->signbits ], planes->plane ); +		d1 = DotProduct( tw->start, planes->plane ) - planes->plane[3] + offset; +		d2 = DotProduct( tw->end, planes->plane ) - planes->plane[3] + offset; +		if ( d1 <= 0 ) { +			frontFacing[i] = qfalse; +		} else { +			frontFacing[i] = qtrue; +		} +		if ( d1 == d2 ) { +			intersection[i] = 99999; +		} else { +			intersection[i] = d1 / ( d1 - d2 ); +			if ( intersection[i] <= 0 ) { +				intersection[i] = 99999; +			} +		} +	} + + +	// see if any of the surface planes are intersected +	facet = pc->facets; +	for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { +		if ( !frontFacing[facet->surfacePlane] ) { +			continue; +		} +		intersect = intersection[facet->surfacePlane]; +		if ( intersect < 0 ) { +			continue;		// surface is behind the starting point +		} +		if ( intersect > tw->trace.fraction ) { +			continue;		// already hit something closer +		} +		for ( j = 0 ; j < facet->numBorders ; j++ ) { +			k = facet->borderPlanes[j]; +			if ( frontFacing[k] ^ facet->borderInward[j] ) { +				if ( intersection[k] > intersect ) { +					break; +				} +			} else { +				if ( intersection[k] < intersect ) { +					break; +				} +			} +		} +		if ( j == facet->numBorders ) { +			// we hit this facet +#ifndef BSPC +			if (!cv) { +				cv = Cvar_Get( "r_debugSurfaceUpdate", "1", 0 ); +			} +			if (cv->integer) { +				debugPatchCollide = pc; +				debugFacet = facet; +			} +#endif //BSPC +			planes = &pc->planes[facet->surfacePlane]; + +			// calculate intersection with a slight pushoff +			offset = DotProduct( tw->offsets[ planes->signbits ], planes->plane ); +			d1 = DotProduct( tw->start, planes->plane ) - planes->plane[3] + offset; +			d2 = DotProduct( tw->end, planes->plane ) - planes->plane[3] + offset; +			tw->trace.fraction = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 ); + +			if ( tw->trace.fraction < 0 ) { +				tw->trace.fraction = 0; +			} + +			VectorCopy( planes->plane,  tw->trace.plane.normal ); +			tw->trace.plane.dist = planes->plane[3]; +		} +	} +} + +/* +==================== +CM_CheckFacetPlane +==================== +*/ +int CM_CheckFacetPlane(float *plane, vec3_t start, vec3_t end, float *enterFrac, float *leaveFrac, int *hit) { +	float d1, d2, f; + +	*hit = qfalse; + +	d1 = DotProduct( start, plane ) - plane[3]; +	d2 = DotProduct( end, plane ) - plane[3]; + +	// if completely in front of face, no intersection with the entire facet +	if (d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 )  ) { +		return qfalse; +	} + +	// if it doesn't cross the plane, the plane isn't relevent +	if (d1 <= 0 && d2 <= 0 ) { +		return qtrue; +	} + +	// crosses face +	if (d1 > d2) {	// enter +		f = (d1-SURFACE_CLIP_EPSILON) / (d1-d2); +		if ( f < 0 ) { +			f = 0; +		} +		//always favor previous plane hits and thus also the surface plane hit +		if (f > *enterFrac) { +			*enterFrac = f; +			*hit = qtrue; +		} +	} else {	// leave +		f = (d1+SURFACE_CLIP_EPSILON) / (d1-d2); +		if ( f > 1 ) { +			f = 1; +		} +		if (f < *leaveFrac) { +			*leaveFrac = f; +		} +	} +	return qtrue; +} + +/* +==================== +CM_TraceThroughPatchCollide +==================== +*/ +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) { +	int i, j, hit, hitnum; +	float offset, enterFrac, leaveFrac, t; +	patchPlane_t *planes; +	facet_t	*facet; +	float plane[4] = {0, 0, 0, 0}, bestplane[4] = {0, 0, 0, 0}; +	vec3_t startp, endp; +#ifndef BSPC +	static cvar_t *cv; +#endif //BSPC + +	if (tw->isPoint) { +		CM_TracePointThroughPatchCollide( tw, pc ); +		return; +	} + +	facet = pc->facets; +	for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { +		enterFrac = -1.0; +		leaveFrac = 1.0; +		hitnum = -1; +		// +		planes = &pc->planes[ facet->surfacePlane ]; +		VectorCopy(planes->plane, plane); +		plane[3] = planes->plane[3]; +		if ( tw->type == TT_CAPSULE ) { +			// adjust the plane distance apropriately for radius +			plane[3] += tw->sphere.radius; + +			// find the closest point on the capsule to the plane +			t = DotProduct( plane, tw->sphere.offset ); +			if ( t > 0.0f ) { +				VectorSubtract( tw->start, tw->sphere.offset, startp ); +				VectorSubtract( tw->end, tw->sphere.offset, endp ); +			} +			else { +				VectorAdd( tw->start, tw->sphere.offset, startp ); +				VectorAdd( tw->end, tw->sphere.offset, endp ); +			} +		} +		else { +			offset = DotProduct( tw->offsets[ planes->signbits ], plane); +			plane[3] -= offset; +			VectorCopy( tw->start, startp ); +			VectorCopy( tw->end, endp ); +		} + +		if (!CM_CheckFacetPlane(plane, startp, endp, &enterFrac, &leaveFrac, &hit)) { +			continue; +		} +		if (hit) { +			Vector4Copy(plane, bestplane); +		} + +		for ( j = 0; j < facet->numBorders; j++ ) { +			planes = &pc->planes[ facet->borderPlanes[j] ]; +			if (facet->borderInward[j]) { +				VectorNegate(planes->plane, plane); +				plane[3] = -planes->plane[3]; +			} +			else { +				VectorCopy(planes->plane, plane); +				plane[3] = planes->plane[3]; +			} +			if ( tw->type == TT_CAPSULE ) { +				// adjust the plane distance apropriately for radius +				plane[3] += tw->sphere.radius; + +				// find the closest point on the capsule to the plane +				t = DotProduct( plane, tw->sphere.offset ); +				if ( t > 0.0f ) { +					VectorSubtract( tw->start, tw->sphere.offset, startp ); +					VectorSubtract( tw->end, tw->sphere.offset, endp ); +				} +				else { +					VectorAdd( tw->start, tw->sphere.offset, startp ); +					VectorAdd( tw->end, tw->sphere.offset, endp ); +				} +			} +			else { +				// NOTE: this works even though the plane might be flipped because the bbox is centered +				offset = DotProduct( tw->offsets[ planes->signbits ], plane); +				plane[3] += fabs(offset); +				VectorCopy( tw->start, startp ); +				VectorCopy( tw->end, endp ); +			} + +			if (!CM_CheckFacetPlane(plane, startp, endp, &enterFrac, &leaveFrac, &hit)) { +				break; +			} +			if (hit) { +				hitnum = j; +				Vector4Copy(plane, bestplane); +			} +		} +		if (j < facet->numBorders) continue; +		//never clip against the back side +		if (hitnum == facet->numBorders - 1) continue; + +		if (enterFrac < leaveFrac && enterFrac >= 0) { +			if (enterFrac < tw->trace.fraction) { +				if (enterFrac < 0) { +					enterFrac = 0; +				} +#ifndef BSPC +				if (!cv) { +					cv = Cvar_Get( "r_debugSurfaceUpdate", "1", 0 ); +				} +				if (cv && cv->integer) { +					debugPatchCollide = pc; +					debugFacet = facet; +				} +#endif //BSPC + +				tw->trace.fraction = enterFrac; +				VectorCopy( bestplane, tw->trace.plane.normal ); +				tw->trace.plane.dist = bestplane[3]; +			} +		} +	} +} + + +/* +======================================================================= + +POSITION TEST + +======================================================================= +*/ + +/* +==================== +CM_PositionTestInPatchCollide +==================== +*/ +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) { +	int i, j; +	float offset, t; +	patchPlane_t *planes; +	facet_t	*facet; +	float plane[4]; +	vec3_t startp; + +	if (tw->isPoint) { +		return qfalse; +	} +	// +	facet = pc->facets; +	for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { +		planes = &pc->planes[ facet->surfacePlane ]; +		VectorCopy(planes->plane, plane); +		plane[3] = planes->plane[3]; +		if ( tw->type == TT_CAPSULE ) { +			// adjust the plane distance apropriately for radius +			plane[3] += tw->sphere.radius; + +			// find the closest point on the capsule to the plane +			t = DotProduct( plane, tw->sphere.offset ); +			if ( t > 0 ) { +				VectorSubtract( tw->start, tw->sphere.offset, startp ); +			} +			else { +				VectorAdd( tw->start, tw->sphere.offset, startp ); +			} +		} +		else { +			offset = DotProduct( tw->offsets[ planes->signbits ], plane); +			plane[3] -= offset; +			VectorCopy( tw->start, startp ); +		} + +		if ( DotProduct( plane, startp ) - plane[3] > 0.0f ) { +			continue; +		} + +		for ( j = 0; j < facet->numBorders; j++ ) { +			planes = &pc->planes[ facet->borderPlanes[j] ]; +			if (facet->borderInward[j]) { +				VectorNegate(planes->plane, plane); +				plane[3] = -planes->plane[3]; +			} +			else { +				VectorCopy(planes->plane, plane); +				plane[3] = planes->plane[3]; +			} +			if ( tw->type == TT_CAPSULE ) { +				// adjust the plane distance apropriately for radius +				plane[3] += tw->sphere.radius; + +				// find the closest point on the capsule to the plane +				t = DotProduct( plane, tw->sphere.offset ); +				if ( t > 0.0f ) { +					VectorSubtract( tw->start, tw->sphere.offset, startp ); +				} +				else { +					VectorAdd( tw->start, tw->sphere.offset, startp ); +				} +			} +			else { +				// NOTE: this works even though the plane might be flipped because the bbox is centered +				offset = DotProduct( tw->offsets[ planes->signbits ], plane); +				plane[3] += fabs(offset); +				VectorCopy( tw->start, startp ); +			} + +			if ( DotProduct( plane, startp ) - plane[3] > 0.0f ) { +				break; +			} +		} +		if (j < facet->numBorders) { +			continue; +		} +		// inside this patch facet +		return qtrue; +	} +	return qfalse; +} + +/* +======================================================================= + +DEBUGGING + +======================================================================= +*/ + + +/* +================== +CM_DrawDebugSurface + +Called from the renderer +================== +*/ +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, float *points) ) { +	static cvar_t	*cv; +#ifndef BSPC +	static cvar_t	*cv2; +#endif +	const patchCollide_t	*pc; +	facet_t			*facet; +	winding_t		*w; +	int				i, j, k, n; +	int				curplanenum, planenum, curinward, inward; +	float			plane[4]; +	vec3_t mins = {-15, -15, -28}, maxs = {15, 15, 28}; +	//vec3_t mins = {0, 0, 0}, maxs = {0, 0, 0}; +	vec3_t v1, v2; + +#ifndef BSPC +	if ( !cv2 ) +	{ +		cv2 = Cvar_Get( "r_debugSurface", "0", 0 ); +	} + +	if (cv2->integer != 1) +	{ +		return; +	} +#endif + +	if ( !debugPatchCollide ) { +		return; +	} + +#ifndef BSPC +	if ( !cv ) { +		cv = Cvar_Get( "cm_debugSize", "2", 0 ); +	} +#endif +	pc = debugPatchCollide; + +	for ( i = 0, facet = pc->facets ; i < pc->numFacets ; i++, facet++ ) { + +		for ( k = 0 ; k < facet->numBorders + 1; k++ ) { +			// +			if (k < facet->numBorders) { +				planenum = facet->borderPlanes[k]; +				inward = facet->borderInward[k]; +			} +			else { +				planenum = facet->surfacePlane; +				inward = qfalse; +				//continue; +			} + +			Vector4Copy( pc->planes[ planenum ].plane, plane ); + +			//planenum = facet->surfacePlane; +			if ( inward ) { +				VectorSubtract( vec3_origin, plane, plane ); +				plane[3] = -plane[3]; +			} + +			plane[3] += cv->value; +			//* +			for (n = 0; n < 3; n++) +			{ +				if (plane[n] > 0) v1[n] = maxs[n]; +				else v1[n] = mins[n]; +			} //end for +			VectorNegate(plane, v2); +			plane[3] += fabs(DotProduct(v1, v2)); +			//*/ + +			w = BaseWindingForPlane( plane,  plane[3] ); +			for ( j = 0 ; j < facet->numBorders + 1 && w; j++ ) { +				// +				if (j < facet->numBorders) { +					curplanenum = facet->borderPlanes[j]; +					curinward = facet->borderInward[j]; +				} +				else { +					curplanenum = facet->surfacePlane; +					curinward = qfalse; +					//continue; +				} +				// +				if (curplanenum == planenum) continue; + +				Vector4Copy( pc->planes[ curplanenum ].plane, plane ); +				if ( !curinward ) { +					VectorSubtract( vec3_origin, plane, plane ); +					plane[3] = -plane[3]; +				} +		//			if ( !facet->borderNoAdjust[j] ) { +					plane[3] -= cv->value; +		//			} +				for (n = 0; n < 3; n++) +				{ +					if (plane[n] > 0) v1[n] = maxs[n]; +					else v1[n] = mins[n]; +				} //end for +				VectorNegate(plane, v2); +				plane[3] -= fabs(DotProduct(v1, v2)); + +				ChopWindingInPlace( &w, plane, plane[3], 0.1f ); +			} +			if ( w ) { +				if ( facet == debugFacet ) { +					drawPoly( 4, w->numpoints, w->p[0] ); +					//Com_Printf("blue facet has %d border planes\n", facet->numBorders); +				} else { +					drawPoly( 1, w->numpoints, w->p[0] ); +				} +				FreeWinding( w ); +			} +			else +				Com_Printf("winding chopped away by border planes\n"); +		} +	} + +	// draw the debug block +	{ +		vec3_t			v[3]; + +		VectorCopy( debugBlockPoints[0], v[0] ); +		VectorCopy( debugBlockPoints[1], v[1] ); +		VectorCopy( debugBlockPoints[2], v[2] ); +		drawPoly( 2, 3, v[0] ); + +		VectorCopy( debugBlockPoints[2], v[0] ); +		VectorCopy( debugBlockPoints[3], v[1] ); +		VectorCopy( debugBlockPoints[0], v[2] ); +		drawPoly( 2, 3, v[0] ); +	} + +#if 0 +	vec3_t			v[4]; + +	v[0][0] = pc->bounds[1][0]; +	v[0][1] = pc->bounds[1][1]; +	v[0][2] = pc->bounds[1][2]; + +	v[1][0] = pc->bounds[1][0]; +	v[1][1] = pc->bounds[0][1]; +	v[1][2] = pc->bounds[1][2]; + +	v[2][0] = pc->bounds[0][0]; +	v[2][1] = pc->bounds[0][1]; +	v[2][2] = pc->bounds[1][2]; + +	v[3][0] = pc->bounds[0][0]; +	v[3][1] = pc->bounds[1][1]; +	v[3][2] = pc->bounds[1][2]; + +	drawPoly( 4, v[0] ); +#endif +} diff --git a/src/qcommon/cm_patch.h b/src/qcommon/cm_patch.h new file mode 100644 index 0000000..826fa17 --- /dev/null +++ b/src/qcommon/cm_patch.h @@ -0,0 +1,104 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ + +//#define	CULL_BBOX + +/* + +This file does not reference any globals, and has these entry points: + +void CM_ClearLevelPatches( void ); +struct patchCollide_s	*CM_GeneratePatchCollide( int width, int height, const vec3_t *points ); +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, flaot *points) ); + + +Issues for collision against curved surfaces: + +Surface edges need to be handled differently than surface planes + +Plane expansion causes raw surfaces to expand past expanded bounding box + +Position test of a volume against a surface is tricky. + +Position test of a point against a surface is not well defined, because the surface has no volume. + + +Tracing leading edge points instead of volumes? +Position test by tracing corner to corner? (8*7 traces -- ouch) + +coplanar edges +triangulated patches +degenerate patches + +  endcaps +  degenerate + +WARNING: this may misbehave with meshes that have rows or columns that only +degenerate a few triangles.  Completely degenerate rows and columns are handled +properly. +*/ + + +#define	MAX_FACETS			1024 +#define	MAX_PATCH_PLANES	2048 + +typedef struct { +	float	plane[4]; +	int		signbits;		// signx + (signy<<1) + (signz<<2), used as lookup during collision +} patchPlane_t; + +typedef struct { +	int			surfacePlane; +	int			numBorders;		// 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels +	int			borderPlanes[4+6+16]; +	int			borderInward[4+6+16]; +	qboolean	borderNoAdjust[4+6+16]; +} facet_t; + +typedef struct patchCollide_s { +	vec3_t	bounds[2]; +	int		numPlanes;			// surface planes plus edge planes +	patchPlane_t	*planes; +	int		numFacets; +	facet_t	*facets; +} patchCollide_t; + + +#define	MAX_GRID_SIZE	129 + +typedef struct { +	int			width; +	int			height; +	qboolean	wrapWidth; +	qboolean	wrapHeight; +	vec3_t	points[MAX_GRID_SIZE][MAX_GRID_SIZE];	// [width][height] +} cGrid_t; + +#define	SUBDIVIDE_DISTANCE	16	//4	// never more than this units away from curve +#define	PLANE_TRI_EPSILON	0.1 +#define	WRAP_POINT_EPSILON	0.1 + + +struct patchCollide_s	*CM_GeneratePatchCollide( int width, int height, vec3_t *points ); diff --git a/src/qcommon/cm_polylib.c b/src/qcommon/cm_polylib.c new file mode 100644 index 0000000..1a93754 --- /dev/null +++ b/src/qcommon/cm_polylib.c @@ -0,0 +1,738 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ + +// this is only used for visualization tools in cm_ debug functions + + +#include "cm_local.h" + + +// counters are only bumped when running single threaded, +// because they are an awful coherence problem +int	c_active_windings; +int	c_peak_windings; +int	c_winding_allocs; +int	c_winding_points; + +void pw(winding_t *w) +{ +	int		i; +	for (i=0 ; i<w->numpoints ; i++) +		printf ("(%5.1f, %5.1f, %5.1f)\n",w->p[i][0], w->p[i][1],w->p[i][2]); +} + + +/* +============= +AllocWinding +============= +*/ +winding_t	*AllocWinding (int points) +{ +	winding_t	*w; +	int			s; + +	c_winding_allocs++; +	c_winding_points += points; +	c_active_windings++; +	if (c_active_windings > c_peak_windings) +		c_peak_windings = c_active_windings; + +	s = sizeof(vec_t)*3*points + sizeof(int); +	w = Z_Malloc (s); +	Com_Memset (w, 0, s);  +	return w; +} + +void FreeWinding (winding_t *w) +{ +	if (*(unsigned *)w == 0xdeaddead) +		Com_Error (ERR_FATAL, "FreeWinding: freed a freed winding"); +	*(unsigned *)w = 0xdeaddead; + +	c_active_windings--; +	Z_Free (w); +} + +/* +============ +RemoveColinearPoints +============ +*/ +int	c_removed; + +void	RemoveColinearPoints (winding_t *w) +{ +	int		i, j, k; +	vec3_t	v1, v2; +	int		nump; +	vec3_t	p[MAX_POINTS_ON_WINDING]; + +	nump = 0; +	for (i=0 ; i<w->numpoints ; i++) +	{ +		j = (i+1)%w->numpoints; +		k = (i+w->numpoints-1)%w->numpoints; +		VectorSubtract (w->p[j], w->p[i], v1); +		VectorSubtract (w->p[i], w->p[k], v2); +		VectorNormalize2(v1,v1); +		VectorNormalize2(v2,v2); +		if (DotProduct(v1, v2) < 0.999) +		{ +			VectorCopy (w->p[i], p[nump]); +			nump++; +		} +	} + +	if (nump == w->numpoints) +		return; + +	c_removed += w->numpoints - nump; +	w->numpoints = nump; +	Com_Memcpy (w->p, p, nump*sizeof(p[0])); +} + +/* +============ +WindingPlane +============ +*/ +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist) +{ +	vec3_t	v1, v2; + +	VectorSubtract (w->p[1], w->p[0], v1); +	VectorSubtract (w->p[2], w->p[0], v2); +	CrossProduct (v2, v1, normal); +	VectorNormalize2(normal, normal); +	*dist = DotProduct (w->p[0], normal); + +} + +/* +============= +WindingArea +============= +*/ +vec_t	WindingArea (winding_t *w) +{ +	int		i; +	vec3_t	d1, d2, cross; +	vec_t	total; + +	total = 0; +	for (i=2 ; i<w->numpoints ; i++) +	{ +		VectorSubtract (w->p[i-1], w->p[0], d1); +		VectorSubtract (w->p[i], w->p[0], d2); +		CrossProduct (d1, d2, cross); +		total += 0.5 * VectorLength ( cross ); +	} +	return total; +} + +/* +============= +WindingBounds +============= +*/ +void	WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs) +{ +	vec_t	v; +	int		i,j; + +	mins[0] = mins[1] = mins[2] = MAX_MAP_BOUNDS; +	maxs[0] = maxs[1] = maxs[2] = -MAX_MAP_BOUNDS; + +	for (i=0 ; i<w->numpoints ; i++) +	{ +		for (j=0 ; j<3 ; j++) +		{ +			v = w->p[i][j]; +			if (v < mins[j]) +				mins[j] = v; +			if (v > maxs[j]) +				maxs[j] = v; +		} +	} +} + +/* +============= +WindingCenter +============= +*/ +void	WindingCenter (winding_t *w, vec3_t center) +{ +	int		i; +	float	scale; + +	VectorCopy (vec3_origin, center); +	for (i=0 ; i<w->numpoints ; i++) +		VectorAdd (w->p[i], center, center); + +	scale = 1.0/w->numpoints; +	VectorScale (center, scale, center); +} + +/* +================= +BaseWindingForPlane +================= +*/ +winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist) +{ +	int		i, x; +	vec_t	max, v; +	vec3_t	org, vright, vup; +	winding_t	*w; +	 +// find the major axis + +	max = -MAX_MAP_BOUNDS; +	x = -1; +	for (i=0 ; i<3; i++) +	{ +		v = fabs(normal[i]); +		if (v > max) +		{ +			x = i; +			max = v; +		} +	} +	if (x==-1) +		Com_Error (ERR_DROP, "BaseWindingForPlane: no axis found"); +		 +	VectorCopy (vec3_origin, vup);	 +	switch (x) +	{ +	case 0: +	case 1: +		vup[2] = 1; +		break;		 +	case 2: +		vup[0] = 1; +		break;		 +	} + +	v = DotProduct (vup, normal); +	VectorMA (vup, -v, normal, vup); +	VectorNormalize2(vup, vup); +		 +	VectorScale (normal, dist, org); +	 +	CrossProduct (vup, normal, vright); +	 +	VectorScale (vup, MAX_MAP_BOUNDS, vup); +	VectorScale (vright, MAX_MAP_BOUNDS, vright); + +// project a really big	axis aligned box onto the plane +	w = AllocWinding (4); +	 +	VectorSubtract (org, vright, w->p[0]); +	VectorAdd (w->p[0], vup, w->p[0]); +	 +	VectorAdd (org, vright, w->p[1]); +	VectorAdd (w->p[1], vup, w->p[1]); +	 +	VectorAdd (org, vright, w->p[2]); +	VectorSubtract (w->p[2], vup, w->p[2]); +	 +	VectorSubtract (org, vright, w->p[3]); +	VectorSubtract (w->p[3], vup, w->p[3]); +	 +	w->numpoints = 4; +	 +	return w;	 +} + +/* +================== +CopyWinding +================== +*/ +winding_t	*CopyWinding (winding_t *w) +{ +	unsigned long	size; +	winding_t	*c; + +	c = AllocWinding (w->numpoints); +	size = (long)((winding_t *)0)->p[w->numpoints]; +	Com_Memcpy (c, w, size); +	return c; +} + +/* +================== +ReverseWinding +================== +*/ +winding_t	*ReverseWinding (winding_t *w) +{ +	int			i; +	winding_t	*c; + +	c = AllocWinding (w->numpoints); +	for (i=0 ; i<w->numpoints ; i++) +	{ +		VectorCopy (w->p[w->numpoints-1-i], c->p[i]); +	} +	c->numpoints = w->numpoints; +	return c; +} + + +/* +============= +ClipWindingEpsilon +============= +*/ +void	ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist,  +				vec_t epsilon, winding_t **front, winding_t **back) +{ +	vec_t	dists[MAX_POINTS_ON_WINDING+4]; +	int		sides[MAX_POINTS_ON_WINDING+4]; +	int		counts[3]; +	static	vec_t	dot;		// VC 4.2 optimizer bug if not static +	int		i, j; +	vec_t	*p1, *p2; +	vec3_t	mid; +	winding_t	*f, *b; +	int		maxpts; +	 +	counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point +	for (i=0 ; i<in->numpoints ; i++) +	{ +		dot = DotProduct (in->p[i], normal); +		dot -= dist; +		dists[i] = dot; +		if (dot > epsilon) +			sides[i] = SIDE_FRONT; +		else if (dot < -epsilon) +			sides[i] = SIDE_BACK; +		else +		{ +			sides[i] = SIDE_ON; +		} +		counts[sides[i]]++; +	} +	sides[i] = sides[0]; +	dists[i] = dists[0]; +	 +	*front = *back = NULL; + +	if (!counts[0]) +	{ +		*back = CopyWinding (in); +		return; +	} +	if (!counts[1]) +	{ +		*front = CopyWinding (in); +		return; +	} + +	maxpts = in->numpoints+4;	// cant use counts[0]+2 because +								// of fp grouping errors + +	*front = f = AllocWinding (maxpts); +	*back = b = AllocWinding (maxpts); +		 +	for (i=0 ; i<in->numpoints ; i++) +	{ +		p1 = in->p[i]; +		 +		if (sides[i] == SIDE_ON) +		{ +			VectorCopy (p1, f->p[f->numpoints]); +			f->numpoints++; +			VectorCopy (p1, b->p[b->numpoints]); +			b->numpoints++; +			continue; +		} +	 +		if (sides[i] == SIDE_FRONT) +		{ +			VectorCopy (p1, f->p[f->numpoints]); +			f->numpoints++; +		} +		if (sides[i] == SIDE_BACK) +		{ +			VectorCopy (p1, b->p[b->numpoints]); +			b->numpoints++; +		} + +		if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) +			continue; +			 +	// generate a split point +		p2 = in->p[(i+1)%in->numpoints]; +		 +		dot = dists[i] / (dists[i]-dists[i+1]); +		for (j=0 ; j<3 ; j++) +		{	// avoid round off error when possible +			if (normal[j] == 1) +				mid[j] = dist; +			else if (normal[j] == -1) +				mid[j] = -dist; +			else +				mid[j] = p1[j] + dot*(p2[j]-p1[j]); +		} +			 +		VectorCopy (mid, f->p[f->numpoints]); +		f->numpoints++; +		VectorCopy (mid, b->p[b->numpoints]); +		b->numpoints++; +	} +	 +	if (f->numpoints > maxpts || b->numpoints > maxpts) +		Com_Error (ERR_DROP, "ClipWinding: points exceeded estimate"); +	if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) +		Com_Error (ERR_DROP, "ClipWinding: MAX_POINTS_ON_WINDING"); +} + + +/* +============= +ChopWindingInPlace +============= +*/ +void ChopWindingInPlace (winding_t **inout, vec3_t normal, vec_t dist, vec_t epsilon) +{ +	winding_t	*in; +	vec_t	dists[MAX_POINTS_ON_WINDING+4]; +	int		sides[MAX_POINTS_ON_WINDING+4]; +	int		counts[3]; +	static	vec_t	dot;		// VC 4.2 optimizer bug if not static +	int		i, j; +	vec_t	*p1, *p2; +	vec3_t	mid; +	winding_t	*f; +	int		maxpts; + +	in = *inout; +	counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point +	for (i=0 ; i<in->numpoints ; i++) +	{ +		dot = DotProduct (in->p[i], normal); +		dot -= dist; +		dists[i] = dot; +		if (dot > epsilon) +			sides[i] = SIDE_FRONT; +		else if (dot < -epsilon) +			sides[i] = SIDE_BACK; +		else +		{ +			sides[i] = SIDE_ON; +		} +		counts[sides[i]]++; +	} +	sides[i] = sides[0]; +	dists[i] = dists[0]; +	 +	if (!counts[0]) +	{ +		FreeWinding (in); +		*inout = NULL; +		return; +	} +	if (!counts[1]) +		return;		// inout stays the same + +	maxpts = in->numpoints+4;	// cant use counts[0]+2 because +								// of fp grouping errors + +	f = AllocWinding (maxpts); +		 +	for (i=0 ; i<in->numpoints ; i++) +	{ +		p1 = in->p[i]; +		 +		if (sides[i] == SIDE_ON) +		{ +			VectorCopy (p1, f->p[f->numpoints]); +			f->numpoints++; +			continue; +		} +	 +		if (sides[i] == SIDE_FRONT) +		{ +			VectorCopy (p1, f->p[f->numpoints]); +			f->numpoints++; +		} + +		if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) +			continue; +			 +	// generate a split point +		p2 = in->p[(i+1)%in->numpoints]; +		 +		dot = dists[i] / (dists[i]-dists[i+1]); +		for (j=0 ; j<3 ; j++) +		{	// avoid round off error when possible +			if (normal[j] == 1) +				mid[j] = dist; +			else if (normal[j] == -1) +				mid[j] = -dist; +			else +				mid[j] = p1[j] + dot*(p2[j]-p1[j]); +		} +			 +		VectorCopy (mid, f->p[f->numpoints]); +		f->numpoints++; +	} +	 +	if (f->numpoints > maxpts) +		Com_Error (ERR_DROP, "ClipWinding: points exceeded estimate"); +	if (f->numpoints > MAX_POINTS_ON_WINDING) +		Com_Error (ERR_DROP, "ClipWinding: MAX_POINTS_ON_WINDING"); + +	FreeWinding (in); +	*inout = f; +} + + +/* +================= +ChopWinding + +Returns the fragment of in that is on the front side +of the cliping plane.  The original is freed. +================= +*/ +winding_t	*ChopWinding (winding_t *in, vec3_t normal, vec_t dist) +{ +	winding_t	*f, *b; + +	ClipWindingEpsilon (in, normal, dist, ON_EPSILON, &f, &b); +	FreeWinding (in); +	if (b) +		FreeWinding (b); +	return f; +} + + +/* +================= +CheckWinding + +================= +*/ +void CheckWinding (winding_t *w) +{ +	int		i, j; +	vec_t	*p1, *p2; +	vec_t	d, edgedist; +	vec3_t	dir, edgenormal, facenormal; +	vec_t	area; +	vec_t	facedist; + +	if (w->numpoints < 3) +		Com_Error (ERR_DROP, "CheckWinding: %i points",w->numpoints); +	 +	area = WindingArea(w); +	if (area < 1) +		Com_Error (ERR_DROP, "CheckWinding: %f area", area); + +	WindingPlane (w, facenormal, &facedist); +	 +	for (i=0 ; i<w->numpoints ; i++) +	{ +		p1 = w->p[i]; + +		for (j=0 ; j<3 ; j++) +			if (p1[j] > MAX_MAP_BOUNDS || p1[j] < -MAX_MAP_BOUNDS) +				Com_Error (ERR_DROP, "CheckFace: BUGUS_RANGE: %f",p1[j]); + +		j = i+1 == w->numpoints ? 0 : i+1; +		 +	// check the point is on the face plane +		d = DotProduct (p1, facenormal) - facedist; +		if (d < -ON_EPSILON || d > ON_EPSILON) +			Com_Error (ERR_DROP, "CheckWinding: point off plane"); +	 +	// check the edge isnt degenerate +		p2 = w->p[j]; +		VectorSubtract (p2, p1, dir); +		 +		if (VectorLength (dir) < ON_EPSILON) +			Com_Error (ERR_DROP, "CheckWinding: degenerate edge"); +			 +		CrossProduct (facenormal, dir, edgenormal); +		VectorNormalize2 (edgenormal, edgenormal); +		edgedist = DotProduct (p1, edgenormal); +		edgedist += ON_EPSILON; +		 +	// all other points must be on front side +		for (j=0 ; j<w->numpoints ; j++) +		{ +			if (j == i) +				continue; +			d = DotProduct (w->p[j], edgenormal); +			if (d > edgedist) +				Com_Error (ERR_DROP, "CheckWinding: non-convex"); +		} +	} +} + + +/* +============ +WindingOnPlaneSide +============ +*/ +int		WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist) +{ +	qboolean	front, back; +	int			i; +	vec_t		d; + +	front = qfalse; +	back = qfalse; +	for (i=0 ; i<w->numpoints ; i++) +	{ +		d = DotProduct (w->p[i], normal) - dist; +		if (d < -ON_EPSILON) +		{ +			if (front) +				return SIDE_CROSS; +			back = qtrue; +			continue; +		} +		if (d > ON_EPSILON) +		{ +			if (back) +				return SIDE_CROSS; +			front = qtrue; +			continue; +		} +	} + +	if (back) +		return SIDE_BACK; +	if (front) +		return SIDE_FRONT; +	return SIDE_ON; +} + + +/* +================= +AddWindingToConvexHull + +Both w and *hull are on the same plane +================= +*/ +#define	MAX_HULL_POINTS		128 +void	AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal ) { +	int			i, j, k; +	float		*p, *copy; +	vec3_t		dir; +	float		d; +	int			numHullPoints, numNew; +	vec3_t		hullPoints[MAX_HULL_POINTS]; +	vec3_t		newHullPoints[MAX_HULL_POINTS]; +	vec3_t		hullDirs[MAX_HULL_POINTS]; +	qboolean	hullSide[MAX_HULL_POINTS]; +	qboolean	outside; + +	if ( !*hull ) { +		*hull = CopyWinding( w ); +		return; +	} + +	numHullPoints = (*hull)->numpoints; +	Com_Memcpy( hullPoints, (*hull)->p, numHullPoints * sizeof(vec3_t) ); + +	for ( i = 0 ; i < w->numpoints ; i++ ) { +		p = w->p[i]; + +		// calculate hull side vectors +		for ( j = 0 ; j < numHullPoints ; j++ ) { +			k = ( j + 1 ) % numHullPoints; + +			VectorSubtract( hullPoints[k], hullPoints[j], dir ); +			VectorNormalize2( dir, dir ); +			CrossProduct( normal, dir, hullDirs[j] ); +		} + +		outside = qfalse; +		for ( j = 0 ; j < numHullPoints ; j++ ) { +			VectorSubtract( p, hullPoints[j], dir ); +			d = DotProduct( dir, hullDirs[j] ); +			if ( d >= ON_EPSILON ) { +				outside = qtrue; +			} +			if ( d >= -ON_EPSILON ) { +				hullSide[j] = qtrue; +			} else { +				hullSide[j] = qfalse; +			} +		} + +		// if the point is effectively inside, do nothing +		if ( !outside ) { +			continue; +		} + +		// find the back side to front side transition +		for ( j = 0 ; j < numHullPoints ; j++ ) { +			if ( !hullSide[ j % numHullPoints ] && hullSide[ (j + 1) % numHullPoints ] ) { +				break; +			} +		} +		if ( j == numHullPoints ) { +			continue; +		} + +		// insert the point here +		VectorCopy( p, newHullPoints[0] ); +		numNew = 1; + +		// copy over all points that aren't double fronts +		j = (j+1)%numHullPoints; +		for ( k = 0 ; k < numHullPoints ; k++ ) { +			if ( hullSide[ (j+k) % numHullPoints ] && hullSide[ (j+k+1) % numHullPoints ] ) { +				continue; +			} +			copy = hullPoints[ (j+k+1) % numHullPoints ]; +			VectorCopy( copy, newHullPoints[numNew] ); +			numNew++; +		} + +		numHullPoints = numNew; +		Com_Memcpy( hullPoints, newHullPoints, numHullPoints * sizeof(vec3_t) ); +	} + +	FreeWinding( *hull ); +	w = AllocWinding( numHullPoints ); +	w->numpoints = numHullPoints; +	*hull = w; +	Com_Memcpy( w->p, hullPoints, numHullPoints * sizeof(vec3_t) ); +} + + diff --git a/src/qcommon/cm_polylib.h b/src/qcommon/cm_polylib.h new file mode 100644 index 0000000..f1edc5c --- /dev/null +++ b/src/qcommon/cm_polylib.h @@ -0,0 +1,69 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ + +// this is only used for visualization tools in cm_ debug functions + +typedef struct +{ +	int		numpoints; +	vec3_t	p[4];		// variable sized +} winding_t; + +#define	MAX_POINTS_ON_WINDING	64 + +#define	SIDE_FRONT	0 +#define	SIDE_BACK	1 +#define	SIDE_ON		2 +#define	SIDE_CROSS	3 + +#define	CLIP_EPSILON	0.1f + +#define MAX_MAP_BOUNDS			65535 + +// you can define on_epsilon in the makefile as tighter +#ifndef	ON_EPSILON +#define	ON_EPSILON	0.1f +#endif + +winding_t	*AllocWinding (int points); +vec_t	WindingArea (winding_t *w); +void	WindingCenter (winding_t *w, vec3_t center); +void	ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist,  +				vec_t epsilon, winding_t **front, winding_t **back); +winding_t	*ChopWinding (winding_t *in, vec3_t normal, vec_t dist); +winding_t	*CopyWinding (winding_t *w); +winding_t	*ReverseWinding (winding_t *w); +winding_t	*BaseWindingForPlane (vec3_t normal, vec_t dist); +void	CheckWinding (winding_t *w); +void	WindingPlane (winding_t *w, vec3_t normal, vec_t *dist); +void	RemoveColinearPoints (winding_t *w); +int		WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist); +void	FreeWinding (winding_t *w); +void	WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs); + +void	AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal ); + +void	ChopWindingInPlace (winding_t **w, vec3_t normal, vec_t dist, vec_t epsilon); +// frees the original if clipped + +void pw(winding_t *w); diff --git a/src/qcommon/cm_public.h b/src/qcommon/cm_public.h new file mode 100644 index 0000000..68d04af --- /dev/null +++ b/src/qcommon/cm_public.h @@ -0,0 +1,84 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ + +#include "qfiles.h" + + +void		CM_LoadMap( const char *name, qboolean clientload, int *checksum); +void		CM_ClearMap( void ); +clipHandle_t CM_InlineModel( int index );		// 0 = world, 1 + are bmodels +clipHandle_t CM_TempBoxModel( const vec3_t mins, const vec3_t maxs, int capsule ); + +void		CM_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); + +int			CM_NumClusters (void); +int			CM_NumInlineModels( void ); +char		*CM_EntityString (void); + +// returns an ORed contents mask +int			CM_PointContents( const vec3_t p, clipHandle_t model ); +int			CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ); + +void		CM_BoxTrace ( trace_t *results, const vec3_t start, const vec3_t end, +						  vec3_t mins, vec3_t maxs, +						  clipHandle_t model, int brushmask, traceType_t type ); +void		CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, +						  vec3_t mins, vec3_t maxs, +						  clipHandle_t model, int brushmask, +						  const vec3_t origin, const vec3_t angles, traceType_t type ); +void		CM_BiSphereTrace( trace_t *results, const vec3_t start, +							const vec3_t end, float startRad, float endRad, +							clipHandle_t model, int mask ); +void		CM_TransformedBiSphereTrace( trace_t *results, const vec3_t start, +							const vec3_t end, float startRad, float endRad, +							clipHandle_t model, int mask, +							const vec3_t origin ); + +byte		*CM_ClusterPVS (int cluster); + +int			CM_PointLeafnum( const vec3_t p ); + +// only returns non-solid leafs +// overflow if return listsize and if *lastLeaf != list[listsize-1] +int			CM_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *list, +		 					int listsize, int *lastLeaf ); + +int			CM_LeafCluster (int leafnum); +int			CM_LeafArea (int leafnum); + +void		CM_AdjustAreaPortalState( int area1, int area2, qboolean open ); +qboolean	CM_AreasConnected( int area1, int area2 ); + +int			CM_WriteAreaBits( byte *buffer, int area ); + +// cm_tag.c +int			CM_LerpTag( orientation_t *tag,  clipHandle_t model, int startFrame, int endFrame,  +					 float frac, const char *tagName ); + + +// cm_marks.c +int	CM_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, +				   int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ); + +// cm_patch.c +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, float *points) ); diff --git a/src/qcommon/cm_test.c b/src/qcommon/cm_test.c new file mode 100644 index 0000000..9e95060 --- /dev/null +++ b/src/qcommon/cm_test.c @@ -0,0 +1,479 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ +#include "cm_local.h" + + +/* +================== +CM_PointLeafnum_r + +================== +*/ +int CM_PointLeafnum_r( const vec3_t p, int num ) { +	float		d; +	cNode_t		*node; +	cplane_t	*plane; + +	while (num >= 0) +	{ +		node = cm.nodes + num; +		plane = node->plane; +		 +		if (plane->type < 3) +			d = p[plane->type] - plane->dist; +		else +			d = DotProduct (plane->normal, p) - plane->dist; +		if (d < 0) +			num = node->children[1]; +		else +			num = node->children[0]; +	} + +	c_pointcontents++;		// optimize counter + +	return -1 - num; +} + +int CM_PointLeafnum( const vec3_t p ) { +	if ( !cm.numNodes ) {	// map not loaded +		return 0; +	} +	return CM_PointLeafnum_r (p, 0); +} + + +/* +====================================================================== + +LEAF LISTING + +====================================================================== +*/ + + +void CM_StoreLeafs( leafList_t *ll, int nodenum ) { +	int		leafNum; + +	leafNum = -1 - nodenum; + +	// store the lastLeaf even if the list is overflowed +	if ( cm.leafs[ leafNum ].cluster != -1 ) { +		ll->lastLeaf = leafNum; +	} + +	if ( ll->count >= ll->maxcount) { +		ll->overflowed = qtrue; +		return; +	} +	ll->list[ ll->count++ ] = leafNum; +} + +void CM_StoreBrushes( leafList_t *ll, int nodenum ) { +	int			i, k; +	int			leafnum; +	int			brushnum; +	cLeaf_t		*leaf; +	cbrush_t	*b; + +	leafnum = -1 - nodenum; + +	leaf = &cm.leafs[leafnum]; + +	for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) { +		brushnum = cm.leafbrushes[leaf->firstLeafBrush+k]; +		b = &cm.brushes[brushnum]; +		if ( b->checkcount == cm.checkcount ) { +			continue;	// already checked this brush in another leaf +		} +		b->checkcount = cm.checkcount; +		for ( i = 0 ; i < 3 ; i++ ) { +			if ( b->bounds[0][i] >= ll->bounds[1][i] || b->bounds[1][i] <= ll->bounds[0][i] ) { +				break; +			} +		} +		if ( i != 3 ) { +			continue; +		} +		if ( ll->count >= ll->maxcount) { +			ll->overflowed = qtrue; +			return; +		} +		((cbrush_t **)ll->list)[ ll->count++ ] = b; +	} +#if 0 +	// store patches? +	for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { +		patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstleafsurface + k ] ]; +		if ( !patch ) { +			continue; +		} +	} +#endif +} + +/* +============= +CM_BoxLeafnums + +Fills in a list of all the leafs touched +============= +*/ +void CM_BoxLeafnums_r( leafList_t *ll, int nodenum ) { +	cplane_t	*plane; +	cNode_t		*node; +	int			s; + +	while (1) { +		if (nodenum < 0) { +			ll->storeLeafs( ll, nodenum ); +			return; +		} +	 +		node = &cm.nodes[nodenum]; +		plane = node->plane; +		s = BoxOnPlaneSide( ll->bounds[0], ll->bounds[1], plane ); +		if (s == 1) { +			nodenum = node->children[0]; +		} else if (s == 2) { +			nodenum = node->children[1]; +		} else { +			// go down both +			CM_BoxLeafnums_r( ll, node->children[0] ); +			nodenum = node->children[1]; +		} + +	} +} + +/* +================== +CM_BoxLeafnums +================== +*/ +int	CM_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *list, int listsize, int *lastLeaf) { +	leafList_t	ll; + +	cm.checkcount++; + +	VectorCopy( mins, ll.bounds[0] ); +	VectorCopy( maxs, ll.bounds[1] ); +	ll.count = 0; +	ll.maxcount = listsize; +	ll.list = list; +	ll.storeLeafs = CM_StoreLeafs; +	ll.lastLeaf = 0; +	ll.overflowed = qfalse; + +	CM_BoxLeafnums_r( &ll, 0 ); + +	*lastLeaf = ll.lastLeaf; +	return ll.count; +} + +/* +================== +CM_BoxBrushes +================== +*/ +int CM_BoxBrushes( const vec3_t mins, const vec3_t maxs, cbrush_t **list, int listsize ) { +	leafList_t	ll; + +	cm.checkcount++; + +	VectorCopy( mins, ll.bounds[0] ); +	VectorCopy( maxs, ll.bounds[1] ); +	ll.count = 0; +	ll.maxcount = listsize; +	ll.list = (void *)list; +	ll.storeLeafs = CM_StoreBrushes; +	ll.lastLeaf = 0; +	ll.overflowed = qfalse; +	 +	CM_BoxLeafnums_r( &ll, 0 ); + +	return ll.count; +} + + +//==================================================================== + + +/* +================== +CM_PointContents + +================== +*/ +int CM_PointContents( const vec3_t p, clipHandle_t model ) { +	int			leafnum; +	int			i, k; +	int			brushnum; +	cLeaf_t		*leaf; +	cbrush_t	*b; +	int			contents; +	float		d; +	cmodel_t	*clipm; + +	if (!cm.numNodes) {	// map not loaded +		return 0; +	} + +	if ( model ) { +		clipm = CM_ClipHandleToModel( model ); +		leaf = &clipm->leaf; +	} else { +		leafnum = CM_PointLeafnum_r (p, 0); +		leaf = &cm.leafs[leafnum]; +	} + +	contents = 0; +	for (k=0 ; k<leaf->numLeafBrushes ; k++) { +		brushnum = cm.leafbrushes[leaf->firstLeafBrush+k]; +		b = &cm.brushes[brushnum]; + +		// see if the point is in the brush +		for ( i = 0 ; i < b->numsides ; i++ ) { +			d = DotProduct( p, b->sides[i].plane->normal ); +// FIXME test for Cash +//			if ( d >= b->sides[i].plane->dist ) { +			if ( d > b->sides[i].plane->dist ) { +				break; +			} +		} + +		if ( i == b->numsides ) { +			contents |= b->contents; +		} +	} + +	return contents; +} + +/* +================== +CM_TransformedPointContents + +Handles offseting and rotation of the end points for moving and +rotating entities +================== +*/ +int	CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles) { +	vec3_t		p_l; +	vec3_t		temp; +	vec3_t		forward, right, up; + +	// subtract origin offset +	VectorSubtract (p, origin, p_l); + +	// rotate start and end into the models frame of reference +	if ( model != BOX_MODEL_HANDLE &&  +	(angles[0] || angles[1] || angles[2]) ) +	{ +		AngleVectors (angles, forward, right, up); + +		VectorCopy (p_l, temp); +		p_l[0] = DotProduct (temp, forward); +		p_l[1] = -DotProduct (temp, right); +		p_l[2] = DotProduct (temp, up); +	} + +	return CM_PointContents( p_l, model ); +} + + + +/* +=============================================================================== + +PVS + +=============================================================================== +*/ + +byte	*CM_ClusterPVS (int cluster) { +	if (cluster < 0 || cluster >= cm.numClusters || !cm.vised ) { +		return cm.visibility; +	} + +	return cm.visibility + cluster * cm.clusterBytes; +} + + + +/* +=============================================================================== + +AREAPORTALS + +=============================================================================== +*/ + +void CM_FloodArea_r( int areaNum, int floodnum) { +	int		i; +	cArea_t *area; +	int		*con; + +	area = &cm.areas[ areaNum ]; + +	if ( area->floodvalid == cm.floodvalid ) { +		if (area->floodnum == floodnum) +			return; +		Com_Error (ERR_DROP, "FloodArea_r: reflooded"); +	} + +	area->floodnum = floodnum; +	area->floodvalid = cm.floodvalid; +	con = cm.areaPortals + areaNum * cm.numAreas; +	for ( i=0 ; i < cm.numAreas  ; i++ ) { +		if ( con[i] > 0 ) { +			CM_FloodArea_r( i, floodnum ); +		} +	} +} + +/* +==================== +CM_FloodAreaConnections + +==================== +*/ +void	CM_FloodAreaConnections( void ) { +	int		i; +	cArea_t	*area; +	int		floodnum; + +	// all current floods are now invalid +	cm.floodvalid++; +	floodnum = 0; + +	for (i = 0 ; i < cm.numAreas ; i++) { +		area = &cm.areas[i]; +		if (area->floodvalid == cm.floodvalid) { +			continue;		// already flooded into +		} +		floodnum++; +		CM_FloodArea_r (i, floodnum); +	} + +} + +/* +==================== +CM_AdjustAreaPortalState + +==================== +*/ +void	CM_AdjustAreaPortalState( int area1, int area2, qboolean open ) { +	if ( area1 < 0 || area2 < 0 ) { +		return; +	} + +	if ( area1 >= cm.numAreas || area2 >= cm.numAreas ) { +		Com_Error (ERR_DROP, "CM_ChangeAreaPortalState: bad area number"); +	} + +	if ( open ) { +		cm.areaPortals[ area1 * cm.numAreas + area2 ]++; +		cm.areaPortals[ area2 * cm.numAreas + area1 ]++; +	} else { +		cm.areaPortals[ area1 * cm.numAreas + area2 ]--; +		cm.areaPortals[ area2 * cm.numAreas + area1 ]--; +		if ( cm.areaPortals[ area2 * cm.numAreas + area1 ] < 0 ) { +			Com_Error (ERR_DROP, "CM_AdjustAreaPortalState: negative reference count"); +		} +	} + +	CM_FloodAreaConnections (); +} + +/* +==================== +CM_AreasConnected + +==================== +*/ +qboolean	CM_AreasConnected( int area1, int area2 ) { +#ifndef BSPC +	if ( cm_noAreas->integer ) { +		return qtrue; +	} +#endif + +	if ( area1 < 0 || area2 < 0 ) { +		return qfalse; +	} + +	if (area1 >= cm.numAreas || area2 >= cm.numAreas) { +		Com_Error (ERR_DROP, "area >= cm.numAreas"); +	} + +	if (cm.areas[area1].floodnum == cm.areas[area2].floodnum) { +		return qtrue; +	} +	return qfalse; +} + + +/* +================= +CM_WriteAreaBits + +Writes a bit vector of all the areas +that are in the same flood as the area parameter +Returns the number of bytes needed to hold all the bits. + +The bits are OR'd in, so you can CM_WriteAreaBits from multiple +viewpoints and get the union of all visible areas. + +This is used to cull non-visible entities from snapshots +================= +*/ +int CM_WriteAreaBits (byte *buffer, int area) +{ +	int		i; +	int		floodnum; +	int		bytes; + +	bytes = (cm.numAreas+7)>>3; + +#ifndef BSPC +	if (cm_noAreas->integer || area == -1) +#else +	if ( area == -1) +#endif +	{	// for debugging, send everything +		Com_Memset (buffer, 255, bytes); +	} +	else +	{ +		floodnum = cm.areas[area].floodnum; +		for (i=0 ; i<cm.numAreas ; i++) +		{ +			if (cm.areas[i].floodnum == floodnum || area == -1) +				buffer[i>>3] |= 1<<(i&7); +		} +	} + +	return bytes; +} + diff --git a/src/qcommon/cm_trace.c b/src/qcommon/cm_trace.c new file mode 100644 index 0000000..ee9540e --- /dev/null +++ b/src/qcommon/cm_trace.c @@ -0,0 +1,1802 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ +#include "cm_local.h" + +// always use bbox vs. bbox collision and never capsule vs. bbox or vice versa +//#define ALWAYS_BBOX_VS_BBOX +// always use capsule vs. capsule collision and never capsule vs. bbox or vice versa +//#define ALWAYS_CAPSULE_VS_CAPSULE + +//#define CAPSULE_DEBUG + +/* +=============================================================================== + +BASIC MATH + +=============================================================================== +*/ + +/* +================ +RotatePoint +================ +*/ +void RotatePoint(vec3_t point, /*const*/ vec3_t matrix[3]) { // bk: FIXME  +	vec3_t tvec; + +	VectorCopy(point, tvec); +	point[0] = DotProduct(matrix[0], tvec); +	point[1] = DotProduct(matrix[1], tvec); +	point[2] = DotProduct(matrix[2], tvec); +} + +/* +================ +TransposeMatrix +================ +*/ +void TransposeMatrix(/*const*/ vec3_t matrix[3], vec3_t transpose[3]) { // bk: FIXME +	int i, j; +	for (i = 0; i < 3; i++) { +		for (j = 0; j < 3; j++) { +			transpose[i][j] = matrix[j][i]; +		} +	} +} + +/* +================ +CreateRotationMatrix +================ +*/ +void CreateRotationMatrix(const vec3_t angles, vec3_t matrix[3]) { +	AngleVectors(angles, matrix[0], matrix[1], matrix[2]); +	VectorInverse(matrix[1]); +} + +/* +================ +CM_ProjectPointOntoVector +================ +*/ +void CM_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vDir, vec3_t vProj ) +{ +	vec3_t pVec; + +	VectorSubtract( point, vStart, pVec ); +	// project onto the directional vector for this segment +	VectorMA( vStart, DotProduct( pVec, vDir ), vDir, vProj ); +} + +/* +================ +CM_DistanceFromLineSquared +================ +*/ +float CM_DistanceFromLineSquared(vec3_t p, vec3_t lp1, vec3_t lp2, vec3_t dir) { +	vec3_t proj, t; +	int j; + +	CM_ProjectPointOntoVector(p, lp1, dir, proj); +	for (j = 0; j < 3; j++)  +		if ((proj[j] > lp1[j] && proj[j] > lp2[j]) || +			(proj[j] < lp1[j] && proj[j] < lp2[j])) +			break; +	if (j < 3) { +		if (fabs(proj[j] - lp1[j]) < fabs(proj[j] - lp2[j])) +			VectorSubtract(p, lp1, t); +		else +			VectorSubtract(p, lp2, t); +		return VectorLengthSquared(t); +	} +	VectorSubtract(p, proj, t); +	return VectorLengthSquared(t); +} + +/* +================ +CM_VectorDistanceSquared +================ +*/ +float CM_VectorDistanceSquared(vec3_t p1, vec3_t p2) { +	vec3_t dir; + +	VectorSubtract(p2, p1, dir); +	return VectorLengthSquared(dir); +} + +/* +================ +SquareRootFloat +================ +*/ +float SquareRootFloat(float number) { +	union { +		float f; +		int i; +	} t; +	float x, y; +	const float f = 1.5F; + +	x = number * 0.5F; +	t.f  = number; +	t.i  = 0x5f3759df - ( t.i >> 1 ); +	y  = t.f; +	y  = y * ( f - ( x * y * y ) ); +	y  = y * ( f - ( x * y * y ) ); +	return number * y; +} + + +/* +=============================================================================== + +POSITION TESTING + +=============================================================================== +*/ + +/* +================ +CM_TestBoxInBrush +================ +*/ +void CM_TestBoxInBrush( traceWork_t *tw, cbrush_t *brush ) { +	int			i; +	cplane_t	*plane; +	float		dist; +	float		d1; +	cbrushside_t	*side; +	float		t; +	vec3_t		startp; + +	if (!brush->numsides) { +		return; +	} + +	// special test for axial +	if ( tw->bounds[0][0] > brush->bounds[1][0] +		|| tw->bounds[0][1] > brush->bounds[1][1] +		|| tw->bounds[0][2] > brush->bounds[1][2] +		|| tw->bounds[1][0] < brush->bounds[0][0] +		|| tw->bounds[1][1] < brush->bounds[0][1] +		|| tw->bounds[1][2] < brush->bounds[0][2] +		) { +		return; +	} + +   if ( tw->type == TT_CAPSULE ) { +		// the first six planes are the axial planes, so we only +		// need to test the remainder +		for ( i = 6 ; i < brush->numsides ; i++ ) { +			side = brush->sides + i; +			plane = side->plane; + +			// adjust the plane distance apropriately for radius +			dist = plane->dist + tw->sphere.radius; +			// find the closest point on the capsule to the plane +			t = DotProduct( plane->normal, tw->sphere.offset ); +			if ( t > 0 ) +			{ +				VectorSubtract( tw->start, tw->sphere.offset, startp ); +			} +			else +			{ +				VectorAdd( tw->start, tw->sphere.offset, startp ); +			} +			d1 = DotProduct( startp, plane->normal ) - dist; +			// if completely in front of face, no intersection +			if ( d1 > 0 ) { +				return; +			} +		} +	} else { +		// the first six planes are the axial planes, so we only +		// need to test the remainder +		for ( i = 6 ; i < brush->numsides ; i++ ) { +			side = brush->sides + i; +			plane = side->plane; + +			// adjust the plane distance apropriately for mins/maxs +			dist = plane->dist - DotProduct( tw->offsets[ plane->signbits ], plane->normal ); + +			d1 = DotProduct( tw->start, plane->normal ) - dist; + +			// if completely in front of face, no intersection +			if ( d1 > 0 ) { +				return; +			} +		} +	} + +	// inside this brush +	tw->trace.startsolid = tw->trace.allsolid = qtrue; +	tw->trace.fraction = 0; +	tw->trace.contents = brush->contents; +} + + + +/* +================ +CM_TestInLeaf +================ +*/ +void CM_TestInLeaf( traceWork_t *tw, cLeaf_t *leaf ) { +	int			k; +	int			brushnum; +	cbrush_t	*b; +	cPatch_t	*patch; + +	// test box position against all brushes in the leaf +	for (k=0 ; k<leaf->numLeafBrushes ; k++) { +		brushnum = cm.leafbrushes[leaf->firstLeafBrush+k]; +		b = &cm.brushes[brushnum]; +		if (b->checkcount == cm.checkcount) { +			continue;	// already checked this brush in another leaf +		} +		b->checkcount = cm.checkcount; + +		if ( !(b->contents & tw->contents)) { +			continue; +		} +		 +		CM_TestBoxInBrush( tw, b ); +		if ( tw->trace.allsolid ) { +			return; +		} +	} + +	// test against all patches +#ifdef BSPC +	if (1) { +#else +	if ( !cm_noCurves->integer ) { +#endif //BSPC +		for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { +			patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ]; +			if ( !patch ) { +				continue; +			} +			if ( patch->checkcount == cm.checkcount ) { +				continue;	// already checked this brush in another leaf +			} +			patch->checkcount = cm.checkcount; + +			if ( !(patch->contents & tw->contents)) { +				continue; +			} +			 +			if ( CM_PositionTestInPatchCollide( tw, patch->pc ) ) { +				tw->trace.startsolid = tw->trace.allsolid = qtrue; +				tw->trace.fraction = 0; +				tw->trace.contents = patch->contents; +				return; +			} +		} +	} +} + +/* +================== +CM_TestCapsuleInCapsule + +capsule inside capsule check +================== +*/ +void CM_TestCapsuleInCapsule( traceWork_t *tw, clipHandle_t model ) { +	int i; +	vec3_t mins, maxs; +	vec3_t top, bottom; +	vec3_t p1, p2, tmp; +	vec3_t offset, symetricSize[2]; +	float radius, halfwidth, halfheight, offs, r; + +	CM_ModelBounds(model, mins, maxs); + +	VectorAdd(tw->start, tw->sphere.offset, top); +	VectorSubtract(tw->start, tw->sphere.offset, bottom); +	for ( i = 0 ; i < 3 ; i++ ) { +		offset[i] = ( mins[i] + maxs[i] ) * 0.5; +		symetricSize[0][i] = mins[i] - offset[i]; +		symetricSize[1][i] = maxs[i] - offset[i]; +	} +	halfwidth = symetricSize[ 1 ][ 0 ]; +	halfheight = symetricSize[ 1 ][ 2 ]; +	radius = ( halfwidth > halfheight ) ? halfheight : halfwidth; +	offs = halfheight - radius; + +	r = Square(tw->sphere.radius + radius); +	// check if any of the spheres overlap +	VectorCopy(offset, p1); +	p1[2] += offs; +	VectorSubtract(p1, top, tmp); +	if ( VectorLengthSquared(tmp) < r ) { +		tw->trace.startsolid = tw->trace.allsolid = qtrue; +		tw->trace.fraction = 0; +	} +	VectorSubtract(p1, bottom, tmp); +	if ( VectorLengthSquared(tmp) < r ) { +		tw->trace.startsolid = tw->trace.allsolid = qtrue; +		tw->trace.fraction = 0; +	} +	VectorCopy(offset, p2); +	p2[2] -= offs; +	VectorSubtract(p2, top, tmp); +	if ( VectorLengthSquared(tmp) < r ) { +		tw->trace.startsolid = tw->trace.allsolid = qtrue; +		tw->trace.fraction = 0; +	} +	VectorSubtract(p2, bottom, tmp); +	if ( VectorLengthSquared(tmp) < r ) { +		tw->trace.startsolid = tw->trace.allsolid = qtrue; +		tw->trace.fraction = 0; +	} +	// if between cylinder up and lower bounds +	if ( (top[2] >= p1[2] && top[2] <= p2[2]) || +		(bottom[2] >= p1[2] && bottom[2] <= p2[2]) ) { +		// 2d coordinates +		top[2] = p1[2] = 0; +		// if the cylinders overlap +		VectorSubtract(top, p1, tmp); +		if ( VectorLengthSquared(tmp) < r ) { +			tw->trace.startsolid = tw->trace.allsolid = qtrue; +			tw->trace.fraction = 0; +		} +	} +} + +/* +================== +CM_TestBoundingBoxInCapsule + +bounding box inside capsule check +================== +*/ +void CM_TestBoundingBoxInCapsule( traceWork_t *tw, clipHandle_t model ) { +	vec3_t mins, maxs, offset, size[2]; +	clipHandle_t h; +	cmodel_t *cmod; +	int i; + +	// mins maxs of the capsule +	CM_ModelBounds(model, mins, maxs); + +	// offset for capsule center +	for ( i = 0 ; i < 3 ; i++ ) { +		offset[i] = ( mins[i] + maxs[i] ) * 0.5; +		size[0][i] = mins[i] - offset[i]; +		size[1][i] = maxs[i] - offset[i]; +		tw->start[i] -= offset[i]; +		tw->end[i] -= offset[i]; +	} + +	// replace the bounding box with the capsule +	tw->type = TT_CAPSULE; +	tw->sphere.radius = ( size[1][0] > size[1][2] ) ? size[1][2]: size[1][0]; +	tw->sphere.halfheight = size[1][2]; +	VectorSet( tw->sphere.offset, 0, 0, size[1][2] - tw->sphere.radius ); + +	// replace the capsule with the bounding box +	h = CM_TempBoxModel(tw->size[0], tw->size[1], qfalse); +	// calculate collision +	cmod = CM_ClipHandleToModel( h ); +	CM_TestInLeaf( tw, &cmod->leaf ); +} + +/* +================== +CM_PositionTest +================== +*/ +#define	MAX_POSITION_LEAFS	1024 +void CM_PositionTest( traceWork_t *tw ) { +	int		leafs[MAX_POSITION_LEAFS]; +	int		i; +	leafList_t	ll; + +	// identify the leafs we are touching +	VectorAdd( tw->start, tw->size[0], ll.bounds[0] ); +	VectorAdd( tw->start, tw->size[1], ll.bounds[1] ); + +	for (i=0 ; i<3 ; i++) { +		ll.bounds[0][i] -= 1; +		ll.bounds[1][i] += 1; +	} + +	ll.count = 0; +	ll.maxcount = MAX_POSITION_LEAFS; +	ll.list = leafs; +	ll.storeLeafs = CM_StoreLeafs; +	ll.lastLeaf = 0; +	ll.overflowed = qfalse; + +	cm.checkcount++; + +	CM_BoxLeafnums_r( &ll, 0 ); + + +	cm.checkcount++; + +	// test the contents of the leafs +	for (i=0 ; i < ll.count ; i++) { +		CM_TestInLeaf( tw, &cm.leafs[leafs[i]] ); +		if ( tw->trace.allsolid ) { +			break; +		} +	} +} + +/* +=============================================================================== + +TRACING + +=============================================================================== +*/ + +/* +================ +CM_TraceThroughPatch +================ +*/ + +void CM_TraceThroughPatch( traceWork_t *tw, cPatch_t *patch ) { +	float		oldFrac; + +	c_patch_traces++; + +	oldFrac = tw->trace.fraction; + +	CM_TraceThroughPatchCollide( tw, patch->pc ); + +	if ( tw->trace.fraction < oldFrac ) { +		tw->trace.surfaceFlags = patch->surfaceFlags; +		tw->trace.contents = patch->contents; +	} +} + +/* +================ +CM_TraceThroughBrush +================ +*/ +void CM_TraceThroughBrush( traceWork_t *tw, cbrush_t *brush ) { +	int			i; +	cplane_t	*plane, *clipplane; +	float		dist; +	float		enterFrac, leaveFrac; +	float		d1, d2; +	qboolean	getout, startout; +	float		f; +	cbrushside_t	*side, *leadside; +	float		t; +	vec3_t		startp; +	vec3_t		endp; + +	enterFrac = -1.0; +	leaveFrac = 1.0; +	clipplane = NULL; + +	if ( !brush->numsides ) { +		return; +	} + +	c_brush_traces++; + +	getout = qfalse; +	startout = qfalse; + +	leadside = NULL; + +	if( tw->type == TT_BISPHERE ) +	{ +		// +		// compare the trace against all planes of the brush +		// find the latest time the trace crosses a plane towards the interior +		// and the earliest time the trace crosses a plane towards the exterior +		// +		for( i = 0; i < brush->numsides; i++ ) +		{ +			side = brush->sides + i; +			plane = side->plane; + +			// adjust the plane distance apropriately for radius +			d1 = DotProduct( tw->start, plane->normal ) - +				( plane->dist + tw->biSphere.startRadius ); +			d2 = DotProduct( tw->end, plane->normal ) - +				( plane->dist + tw->biSphere.endRadius ); + +			if( d2 > 0 ) +				getout = qtrue;	// endpoint is not in solid +			 +			if( d1 > 0 ) +				startout = qtrue; + +			// if completely in front of face, no intersection with the entire brush +			if( d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) +				return; + +			// if it doesn't cross the plane, the plane isn't relevent +			if( d1 <= 0 && d2 <= 0 ) +				continue; + +			brush->collided = qtrue; + +			// crosses face +			if( d1 > d2 ) +			{ +				// enter +				f = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 ); + +				if( f < 0 ) +					f = 0; + +				if( f > enterFrac ) +				{ +					enterFrac = f; +					clipplane = plane; +					leadside = side; +				} +			} +			else +			{ +				// leave +				f = ( d1 + SURFACE_CLIP_EPSILON ) / ( d1 - d2 ); + +				if( f > 1 ) +					f = 1; + +				if( f < leaveFrac ) +					leaveFrac = f; +			} +		} +	} +	else if ( tw->type == TT_CAPSULE ) { +		// +		// compare the trace against all planes of the brush +		// find the latest time the trace crosses a plane towards the interior +		// and the earliest time the trace crosses a plane towards the exterior +		// +		for (i = 0; i < brush->numsides; i++) { +			side = brush->sides + i; +			plane = side->plane; + +			// adjust the plane distance apropriately for radius +			dist = plane->dist + tw->sphere.radius; + +			// find the closest point on the capsule to the plane +			t = DotProduct( plane->normal, tw->sphere.offset ); +			if ( t > 0 ) +			{ +				VectorSubtract( tw->start, tw->sphere.offset, startp ); +				VectorSubtract( tw->end, tw->sphere.offset, endp ); +			} +			else +			{ +				VectorAdd( tw->start, tw->sphere.offset, startp ); +				VectorAdd( tw->end, tw->sphere.offset, endp ); +			} + +			d1 = DotProduct( startp, plane->normal ) - dist; +			d2 = DotProduct( endp, plane->normal ) - dist; + +			if (d2 > 0) { +				getout = qtrue;	// endpoint is not in solid +			} +			if (d1 > 0) { +				startout = qtrue; +			} + +			// if completely in front of face, no intersection with the entire brush +			if (d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 )  ) { +				return; +			} + +			// if it doesn't cross the plane, the plane isn't relevent +			if (d1 <= 0 && d2 <= 0 ) { +				continue; +			} + +			brush->collided = qtrue; + +			// crosses face +			if (d1 > d2) {	// enter +				f = (d1-SURFACE_CLIP_EPSILON) / (d1-d2); +				if ( f < 0 ) { +					f = 0; +				} +				if (f > enterFrac) { +					enterFrac = f; +					clipplane = plane; +					leadside = side; +				} +			} else {	// leave +				f = (d1+SURFACE_CLIP_EPSILON) / (d1-d2); +				if ( f > 1 ) { +					f = 1; +				} +				if (f < leaveFrac) { +					leaveFrac = f; +				} +			} +		} +	} else { +		// +		// compare the trace against all planes of the brush +		// find the latest time the trace crosses a plane towards the interior +		// and the earliest time the trace crosses a plane towards the exterior +		// +		for (i = 0; i < brush->numsides; i++) { +			side = brush->sides + i; +			plane = side->plane; + +			// adjust the plane distance apropriately for mins/maxs +			dist = plane->dist - DotProduct( tw->offsets[ plane->signbits ], plane->normal ); + +			d1 = DotProduct( tw->start, plane->normal ) - dist; +			d2 = DotProduct( tw->end, plane->normal ) - dist; + +			if (d2 > 0) { +				getout = qtrue;	// endpoint is not in solid +			} +			if (d1 > 0) { +				startout = qtrue; +			} + +			// if completely in front of face, no intersection with the entire brush +			if (d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 )  ) { +				return; +			} + +			// if it doesn't cross the plane, the plane isn't relevent +			if (d1 <= 0 && d2 <= 0 ) { +				continue; +			} + +			brush->collided = qtrue; + +			// crosses face +			if (d1 > d2) {	// enter +				f = (d1-SURFACE_CLIP_EPSILON) / (d1-d2); +				if ( f < 0 ) { +					f = 0; +				} +				if (f > enterFrac) { +					enterFrac = f; +					clipplane = plane; +					leadside = side; +				} +			} else {	// leave +				f = (d1+SURFACE_CLIP_EPSILON) / (d1-d2); +				if ( f > 1 ) { +					f = 1; +				} +				if (f < leaveFrac) { +					leaveFrac = f; +				} +			} +		} +	} + +	// +	// all planes have been checked, and the trace was not +	// completely outside the brush +	// +	if (!startout) {	// original point was inside brush +		tw->trace.startsolid = qtrue; +		if (!getout) { +			tw->trace.allsolid = qtrue; +			tw->trace.fraction = 0; +			tw->trace.contents = brush->contents; +		} +		return; +	} +	 +	if (enterFrac < leaveFrac) { +		if (enterFrac > -1 && enterFrac < tw->trace.fraction) { +			if (enterFrac < 0) { +				enterFrac = 0; +			} +			tw->trace.fraction = enterFrac; +			tw->trace.plane = *clipplane; +			tw->trace.surfaceFlags = leadside->surfaceFlags; +			tw->trace.contents = brush->contents; +		} +	} +} + +/* +================ +CM_ProximityToBrush +================ +*/ +static void CM_ProximityToBrush( traceWork_t *tw, cbrush_t *brush ) +{ +	int						i; +	cbrushedge_t	*edge; +	float					dist, minDist = 1e+10f; +	float					s, t; +	float					sAtMin = 0.0f; +	float					radius = 0.0f, fraction; +	traceWork_t		tw2; + +	// cheapish purely linear trace to test for intersection +	Com_Memset( &tw2, 0, sizeof( tw2 ) ); +	tw2.trace.fraction = 1.0f; +	tw2.type = TT_CAPSULE; +	tw2.sphere.radius = 0.0f; +	VectorClear( tw2.sphere.offset ); +	VectorCopy( tw->start, tw2.start ); +	VectorCopy( tw->end, tw2.end ); + +	CM_TraceThroughBrush( &tw2, brush ); + +	if( tw2.trace.fraction == 1.0f && !tw2.trace.allsolid && !tw2.trace.startsolid ) +	{ +		for( i = 0; i < brush->numEdges; i++ ) +		{ +			edge = &brush->edges[ i ]; + +			dist = DistanceBetweenLineSegmentsSquared( tw->start, tw->end, +					edge->p0, edge->p1, &s, &t ); + +			if( dist < minDist ) +			{ +				minDist = dist; +				sAtMin = s; +			} +		} + +		if( tw->type == TT_BISPHERE ) +		{ +			radius = tw->biSphere.startRadius + +				( sAtMin * ( tw->biSphere.endRadius - tw->biSphere.startRadius ) ); +		} +		else if( tw->type == TT_CAPSULE ) +		{ +			radius = tw->sphere.radius; +		} +		else if( tw->type == TT_AABB ) +		{ +			//FIXME +		} + +		fraction = minDist / ( radius * radius ); + +		if( fraction < tw->trace.lateralFraction ) +			tw->trace.lateralFraction = fraction; +	} +	else +		tw->trace.lateralFraction = 0.0f; +} + +/* +================ +CM_ProximityToPatch +================ +*/ +static void CM_ProximityToPatch( traceWork_t *tw, cPatch_t *patch ) +{ +	traceWork_t		tw2; + +	// cheapish purely linear trace to test for intersection +	Com_Memset( &tw2, 0, sizeof( tw2 ) ); +	tw2.trace.fraction = 1.0f; +	tw2.type = TT_CAPSULE; +	tw2.sphere.radius = 0.0f; +	VectorClear( tw2.sphere.offset ); +	VectorCopy( tw->start, tw2.start ); +	VectorCopy( tw->end, tw2.end ); + +	CM_TraceThroughPatch( &tw2, patch ); + +	if( tw2.trace.fraction == 1.0f && !tw2.trace.allsolid && !tw2.trace.startsolid ) +	{ +		//FIXME: implement me +	} +	else +		tw->trace.lateralFraction = 0.0f; +} + +/* +================ +CM_TraceThroughLeaf +================ +*/ +void CM_TraceThroughLeaf( traceWork_t *tw, cLeaf_t *leaf ) { +	int			k; +	int			brushnum; +	cbrush_t	*b; +	cPatch_t	*patch; + +	// trace line against all brushes in the leaf +	for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) { +		brushnum = cm.leafbrushes[leaf->firstLeafBrush+k]; + +		b = &cm.brushes[brushnum]; +		if ( b->checkcount == cm.checkcount ) { +			continue;	// already checked this brush in another leaf +		} +		b->checkcount = cm.checkcount; + +		if ( !(b->contents & tw->contents) ) { +			continue; +		} + +		b->collided = qfalse; + +		CM_TraceThroughBrush( tw, b ); +		if ( !tw->trace.fraction ) { +			tw->trace.lateralFraction = 0.0f; +			return; +		} +	} + +	// trace line against all patches in the leaf +#ifdef BSPC +	if (1) { +#else +	if ( !cm_noCurves->integer ) { +#endif +		for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { +			patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ]; +			if ( !patch ) { +				continue; +			} +			if ( patch->checkcount == cm.checkcount ) { +				continue;	// already checked this patch in another leaf +			} +			patch->checkcount = cm.checkcount; + +			if ( !(patch->contents & tw->contents) ) { +				continue; +			} +			 +			CM_TraceThroughPatch( tw, patch ); + +			if ( !tw->trace.fraction ) { +				tw->trace.lateralFraction = 0.0f; +				return; +			} +		} +	} + +	if( tw->testLateralCollision && tw->trace.fraction < 1.0f ) +	{ +		for( k = 0; k < leaf->numLeafBrushes; k++ ) +		{ +			brushnum = cm.leafbrushes[ leaf->firstLeafBrush + k ]; + +			b = &cm.brushes[ brushnum ]; + +			// This brush never collided, so don't bother +			if( !b->collided ) +				continue; + +			if( !( b->contents & tw->contents ) ) +				continue; + +			CM_ProximityToBrush( tw, b ); + +			if( !tw->trace.lateralFraction ) +				return; +		} + +		for( k = 0; k < leaf->numLeafSurfaces; k++ ) +		{ +			patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ]; +			if( !patch ) +				continue; + +			if( !( patch->contents & tw->contents ) ) +				continue; +			 +			CM_ProximityToPatch( tw, patch ); + +			if( !tw->trace.lateralFraction ) +				return; +		} +	} +} + +#define RADIUS_EPSILON		1.0f + +/* +================ +CM_TraceThroughSphere + +get the first intersection of the ray with the sphere +================ +*/ +void CM_TraceThroughSphere( traceWork_t *tw, vec3_t origin, float radius, vec3_t start, vec3_t end ) { +	float l1, l2, length, scale, fraction; +	float a, b, c, d, sqrtd; +	vec3_t v1, dir, intersection; + +	// if inside the sphere +	VectorSubtract(start, origin, dir); +	l1 = VectorLengthSquared(dir); +	if (l1 < Square(radius)) { +		tw->trace.fraction = 0; +		tw->trace.startsolid = qtrue; +		// test for allsolid +		VectorSubtract(end, origin, dir); +		l1 = VectorLengthSquared(dir); +		if (l1 < Square(radius)) { +			tw->trace.allsolid = qtrue; +		} +		return; +	} +	// +	VectorSubtract(end, start, dir); +	length = VectorNormalize(dir); +	// +	l1 = CM_DistanceFromLineSquared(origin, start, end, dir); +	VectorSubtract(end, origin, v1); +	l2 = VectorLengthSquared(v1); +	// if no intersection with the sphere and the end point is at least an epsilon away +	if (l1 >= Square(radius) && l2 > Square(radius+SURFACE_CLIP_EPSILON)) { +		return; +	} +	// +	//	| origin - (start + t * dir) | = radius +	//	a = dir[0]^2 + dir[1]^2 + dir[2]^2; +	//	b = 2 * (dir[0] * (start[0] - origin[0]) + dir[1] * (start[1] - origin[1]) + dir[2] * (start[2] - origin[2])); +	//	c = (start[0] - origin[0])^2 + (start[1] - origin[1])^2 + (start[2] - origin[2])^2 - radius^2; +	// +	VectorSubtract(start, origin, v1); +	// dir is normalized so a = 1 +	a = 1.0f;//dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]; +	b = 2.0f * (dir[0] * v1[0] + dir[1] * v1[1] + dir[2] * v1[2]); +	c = v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2] - (radius+RADIUS_EPSILON) * (radius+RADIUS_EPSILON); + +	d = b * b - 4.0f * c;// * a; +	if (d > 0) { +		sqrtd = SquareRootFloat(d); +		// = (- b + sqrtd) * 0.5f; // / (2.0f * a); +		fraction = (- b - sqrtd) * 0.5f; // / (2.0f * a); +		// +		if (fraction < 0) { +			fraction = 0; +		} +		else { +			fraction /= length; +		} +		if ( fraction < tw->trace.fraction ) { +			tw->trace.fraction = fraction; +			VectorSubtract(end, start, dir); +			VectorMA(start, fraction, dir, intersection); +			VectorSubtract(intersection, origin, dir); +			#ifdef CAPSULE_DEBUG +				l2 = VectorLength(dir); +				if (l2 < radius) { +					int bah = 1; +				} +			#endif +			scale = 1 / (radius+RADIUS_EPSILON); +			VectorScale(dir, scale, dir); +			VectorCopy(dir, tw->trace.plane.normal); +			VectorAdd( tw->modelOrigin, intersection, intersection); +			tw->trace.plane.dist = DotProduct(tw->trace.plane.normal, intersection); +			tw->trace.contents = CONTENTS_BODY; +		} +	} +	else if (d == 0) { +		//t1 = (- b ) / 2; +		// slide along the sphere +	} +	// no intersection at all +} + +/* +================ +CM_TraceThroughVerticalCylinder + +get the first intersection of the ray with the cylinder +the cylinder extends halfheight above and below the origin +================ +*/ +void CM_TraceThroughVerticalCylinder( traceWork_t *tw, vec3_t origin, float radius, float halfheight, vec3_t start, vec3_t end) { +	float length, scale, fraction, l1, l2; +	float a, b, c, d, sqrtd; +	vec3_t v1, dir, start2d, end2d, org2d, intersection; + +	// 2d coordinates +	VectorSet(start2d, start[0], start[1], 0); +	VectorSet(end2d, end[0], end[1], 0); +	VectorSet(org2d, origin[0], origin[1], 0); +	// if between lower and upper cylinder bounds +	if (start[2] <= origin[2] + halfheight && +				start[2] >= origin[2] - halfheight) { +		// if inside the cylinder +		VectorSubtract(start2d, org2d, dir); +		l1 = VectorLengthSquared(dir); +		if (l1 < Square(radius)) { +			tw->trace.fraction = 0; +			tw->trace.startsolid = qtrue; +			VectorSubtract(end2d, org2d, dir); +			l1 = VectorLengthSquared(dir); +			if (l1 < Square(radius)) { +				tw->trace.allsolid = qtrue; +			} +			return; +		} +	} +	// +	VectorSubtract(end2d, start2d, dir); +	length = VectorNormalize(dir); +	// +	l1 = CM_DistanceFromLineSquared(org2d, start2d, end2d, dir); +	VectorSubtract(end2d, org2d, v1); +	l2 = VectorLengthSquared(v1); +	// if no intersection with the cylinder and the end point is at least an epsilon away +	if (l1 >= Square(radius) && l2 > Square(radius+SURFACE_CLIP_EPSILON)) { +		return; +	} +	// +	// +	// (start[0] - origin[0] - t * dir[0]) ^ 2 + (start[1] - origin[1] - t * dir[1]) ^ 2 = radius ^ 2 +	// (v1[0] + t * dir[0]) ^ 2 + (v1[1] + t * dir[1]) ^ 2 = radius ^ 2; +	// v1[0] ^ 2 + 2 * v1[0] * t * dir[0] + (t * dir[0]) ^ 2 + +	//						v1[1] ^ 2 + 2 * v1[1] * t * dir[1] + (t * dir[1]) ^ 2 = radius ^ 2 +	// t ^ 2 * (dir[0] ^ 2 + dir[1] ^ 2) + t * (2 * v1[0] * dir[0] + 2 * v1[1] * dir[1]) + +	//						v1[0] ^ 2 + v1[1] ^ 2 - radius ^ 2 = 0 +	// +	VectorSubtract(start, origin, v1); +	// dir is normalized so we can use a = 1 +	a = 1.0f;// * (dir[0] * dir[0] + dir[1] * dir[1]); +	b = 2.0f * (v1[0] * dir[0] + v1[1] * dir[1]); +	c = v1[0] * v1[0] + v1[1] * v1[1] - (radius+RADIUS_EPSILON) * (radius+RADIUS_EPSILON); + +	d = b * b - 4.0f * c;// * a; +	if (d > 0) { +		sqrtd = SquareRootFloat(d); +		// = (- b + sqrtd) * 0.5f;// / (2.0f * a); +		fraction = (- b - sqrtd) * 0.5f;// / (2.0f * a); +		// +		if (fraction < 0) { +			fraction = 0; +		} +		else { +			fraction /= length; +		} +		if ( fraction < tw->trace.fraction ) { +			VectorSubtract(end, start, dir); +			VectorMA(start, fraction, dir, intersection); +			// if the intersection is between the cylinder lower and upper bound +			if (intersection[2] <= origin[2] + halfheight && +						intersection[2] >= origin[2] - halfheight) { +				// +				tw->trace.fraction = fraction; +				VectorSubtract(intersection, origin, dir); +				dir[2] = 0; +				#ifdef CAPSULE_DEBUG +					l2 = VectorLength(dir); +					if (l2 <= radius) { +						int bah = 1; +					} +				#endif +				scale = 1 / (radius+RADIUS_EPSILON); +				VectorScale(dir, scale, dir); +				VectorCopy(dir, tw->trace.plane.normal); +				VectorAdd( tw->modelOrigin, intersection, intersection); +				tw->trace.plane.dist = DotProduct(tw->trace.plane.normal, intersection); +				tw->trace.contents = CONTENTS_BODY; +			} +		} +	} +	else if (d == 0) { +		//t[0] = (- b ) / 2 * a; +		// slide along the cylinder +	} +	// no intersection at all +} + +/* +================ +CM_TraceCapsuleThroughCapsule + +capsule vs. capsule collision (not rotated) +================ +*/ +void CM_TraceCapsuleThroughCapsule( traceWork_t *tw, clipHandle_t model ) { +	int i; +	vec3_t mins, maxs; +	vec3_t top, bottom, starttop, startbottom, endtop, endbottom; +	vec3_t offset, symetricSize[2]; +	float radius, halfwidth, halfheight, offs, h; + +	CM_ModelBounds(model, mins, maxs); +	// test trace bounds vs. capsule bounds +	if ( tw->bounds[0][0] > maxs[0] + RADIUS_EPSILON +		|| tw->bounds[0][1] > maxs[1] + RADIUS_EPSILON +		|| tw->bounds[0][2] > maxs[2] + RADIUS_EPSILON +		|| tw->bounds[1][0] < mins[0] - RADIUS_EPSILON +		|| tw->bounds[1][1] < mins[1] - RADIUS_EPSILON +		|| tw->bounds[1][2] < mins[2] - RADIUS_EPSILON +		) { +		return; +	} +	// top origin and bottom origin of each sphere at start and end of trace +	VectorAdd(tw->start, tw->sphere.offset, starttop); +	VectorSubtract(tw->start, tw->sphere.offset, startbottom); +	VectorAdd(tw->end, tw->sphere.offset, endtop); +	VectorSubtract(tw->end, tw->sphere.offset, endbottom); + +	// calculate top and bottom of the capsule spheres to collide with +	for ( i = 0 ; i < 3 ; i++ ) { +		offset[i] = ( mins[i] + maxs[i] ) * 0.5; +		symetricSize[0][i] = mins[i] - offset[i]; +		symetricSize[1][i] = maxs[i] - offset[i]; +	} +	halfwidth = symetricSize[ 1 ][ 0 ]; +	halfheight = symetricSize[ 1 ][ 2 ]; +	radius = ( halfwidth > halfheight ) ? halfheight : halfwidth; +	offs = halfheight - radius; +	VectorCopy(offset, top); +	top[2] += offs; +	VectorCopy(offset, bottom); +	bottom[2] -= offs; +	// expand radius of spheres +	radius += tw->sphere.radius; +	// if there is horizontal movement +	if ( tw->start[0] != tw->end[0] || tw->start[1] != tw->end[1] ) { +		// height of the expanded cylinder is the height of both cylinders minus the radius of both spheres +		h = halfheight + tw->sphere.halfheight - radius; +		// if the cylinder has a height +		if ( h > 0 ) { +			// test for collisions between the cylinders +			CM_TraceThroughVerticalCylinder(tw, offset, radius, h, tw->start, tw->end); +		} +	} +	// test for collision between the spheres +	CM_TraceThroughSphere(tw, top, radius, startbottom, endbottom); +	CM_TraceThroughSphere(tw, bottom, radius, starttop, endtop); +} + +/* +================ +CM_TraceBoundingBoxThroughCapsule + +bounding box vs. capsule collision +================ +*/ +void CM_TraceBoundingBoxThroughCapsule( traceWork_t *tw, clipHandle_t model ) { +	vec3_t mins, maxs, offset, size[2]; +	clipHandle_t h; +	cmodel_t *cmod; +	int i; + +	// mins maxs of the capsule +	CM_ModelBounds(model, mins, maxs); + +	// offset for capsule center +	for ( i = 0 ; i < 3 ; i++ ) { +		offset[i] = ( mins[i] + maxs[i] ) * 0.5; +		size[0][i] = mins[i] - offset[i]; +		size[1][i] = maxs[i] - offset[i]; +		tw->start[i] -= offset[i]; +		tw->end[i] -= offset[i]; +	} + +	// replace the bounding box with the capsule +	tw->type = TT_CAPSULE; +	tw->sphere.radius = ( size[1][0] > size[1][2] ) ? size[1][2]: size[1][0]; +	tw->sphere.halfheight = size[1][2]; +	VectorSet( tw->sphere.offset, 0, 0, size[1][2] - tw->sphere.radius ); + +	// replace the capsule with the bounding box +	h = CM_TempBoxModel(tw->size[0], tw->size[1], qfalse); +	// calculate collision +	cmod = CM_ClipHandleToModel( h ); +	CM_TraceThroughLeaf( tw, &cmod->leaf ); +} + +//========================================================================================= + +/* +================== +CM_TraceThroughTree + +Traverse all the contacted leafs from the start to the end position. +If the trace is a point, they will be exactly in order, but for larger +trace volumes it is possible to hit something in a later leaf with +a smaller intercept fraction. +================== +*/ +void CM_TraceThroughTree( traceWork_t *tw, int num, float p1f, float p2f, vec3_t p1, vec3_t p2) { +	cNode_t		*node; +	cplane_t	*plane; +	float		t1, t2, offset; +	float		frac, frac2; +	float		idist; +	vec3_t		mid; +	int			side; +	float		midf; + +	if (tw->trace.fraction <= p1f) { +		return;		// already hit something nearer +	} + +	// if < 0, we are in a leaf node +	if (num < 0) { +		CM_TraceThroughLeaf( tw, &cm.leafs[-1-num] ); +		return; +	} + +	// +	// find the point distances to the seperating plane +	// and the offset for the size of the box +	// +	node = cm.nodes + num; +	plane = node->plane; + +	// adjust the plane distance apropriately for mins/maxs +	if ( plane->type < 3 ) { +		t1 = p1[plane->type] - plane->dist; +		t2 = p2[plane->type] - plane->dist; +		offset = tw->extents[plane->type]; +	} else { +		t1 = DotProduct (plane->normal, p1) - plane->dist; +		t2 = DotProduct (plane->normal, p2) - plane->dist; +		if ( tw->isPoint ) { +			offset = 0; +		} else { +#if 0 // bk010201 - DEAD +			// an axial brush right behind a slanted bsp plane +			// will poke through when expanded, so adjust +			// by sqrt(3) +			offset = fabs(tw->extents[0]*plane->normal[0]) + +				fabs(tw->extents[1]*plane->normal[1]) + +				fabs(tw->extents[2]*plane->normal[2]); + +			offset *= 2; +			offset = tw->maxOffset; +#endif +			// this is silly +			offset = 2048; +		} +	} + +	// see which sides we need to consider +	if ( t1 >= offset + 1 && t2 >= offset + 1 ) { +		CM_TraceThroughTree( tw, node->children[0], p1f, p2f, p1, p2 ); +		return; +	} +	if ( t1 < -offset - 1 && t2 < -offset - 1 ) { +		CM_TraceThroughTree( tw, node->children[1], p1f, p2f, p1, p2 ); +		return; +	} + +	// put the crosspoint SURFACE_CLIP_EPSILON pixels on the near side +	if ( t1 < t2 ) { +		idist = 1.0/(t1-t2); +		side = 1; +		frac2 = (t1 + offset + SURFACE_CLIP_EPSILON)*idist; +		frac = (t1 - offset + SURFACE_CLIP_EPSILON)*idist; +	} else if (t1 > t2) { +		idist = 1.0/(t1-t2); +		side = 0; +		frac2 = (t1 - offset - SURFACE_CLIP_EPSILON)*idist; +		frac = (t1 + offset + SURFACE_CLIP_EPSILON)*idist; +	} else { +		side = 0; +		frac = 1; +		frac2 = 0; +	} + +	// move up to the node +	if ( frac < 0 ) { +		frac = 0; +	} +	if ( frac > 1 ) { +		frac = 1; +	} +		 +	midf = p1f + (p2f - p1f)*frac; + +	mid[0] = p1[0] + frac*(p2[0] - p1[0]); +	mid[1] = p1[1] + frac*(p2[1] - p1[1]); +	mid[2] = p1[2] + frac*(p2[2] - p1[2]); + +	CM_TraceThroughTree( tw, node->children[side], p1f, midf, p1, mid ); + + +	// go past the node +	if ( frac2 < 0 ) { +		frac2 = 0; +	} +	if ( frac2 > 1 ) { +		frac2 = 1; +	} +		 +	midf = p1f + (p2f - p1f)*frac2; + +	mid[0] = p1[0] + frac2*(p2[0] - p1[0]); +	mid[1] = p1[1] + frac2*(p2[1] - p1[1]); +	mid[2] = p1[2] + frac2*(p2[2] - p1[2]); + +	CM_TraceThroughTree( tw, node->children[side^1], midf, p2f, mid, p2 ); +} + +//====================================================================== + + +/* +================== +CM_Trace +================== +*/ +void CM_Trace( trace_t *results, const vec3_t start, +		const vec3_t end, vec3_t mins, vec3_t maxs, +		clipHandle_t model, const vec3_t origin, int brushmask, +		traceType_t type, sphere_t *sphere ) { +	int			i; +	traceWork_t	tw; +	vec3_t		offset; +	cmodel_t	*cmod; + +	cmod = CM_ClipHandleToModel( model ); + +	cm.checkcount++;		// for multi-check avoidance + +	c_traces++;				// for statistics, may be zeroed + +	// fill in a default trace +	Com_Memset( &tw, 0, sizeof(tw) ); +	tw.trace.fraction = 1;	// assume it goes the entire distance until shown otherwise +	VectorCopy(origin, tw.modelOrigin); +	tw.type = type; + +	if (!cm.numNodes) { +		*results = tw.trace; + +		return;	// map not loaded, shouldn't happen +	} + +	// allow NULL to be passed in for 0,0,0 +	if ( !mins ) { +		mins = vec3_origin; +	} +	if ( !maxs ) { +		maxs = vec3_origin; +	} + +	// set basic parms +	tw.contents = brushmask; + +	// adjust so that mins and maxs are always symetric, which +	// avoids some complications with plane expanding of rotated +	// bmodels +	for ( i = 0 ; i < 3 ; i++ ) { +		offset[i] = ( mins[i] + maxs[i] ) * 0.5; +		tw.size[0][i] = mins[i] - offset[i]; +		tw.size[1][i] = maxs[i] - offset[i]; +		tw.start[i] = start[i] + offset[i]; +		tw.end[i] = end[i] + offset[i]; +	} + +	// if a sphere is already specified +	if ( sphere ) { +		tw.sphere = *sphere; +	} +	else { +		tw.sphere.radius = ( tw.size[1][0] > tw.size[1][2] ) ? tw.size[1][2]: tw.size[1][0]; +		tw.sphere.halfheight = tw.size[1][2]; +		VectorSet( tw.sphere.offset, 0, 0, tw.size[1][2] - tw.sphere.radius ); +	} + +	tw.maxOffset = tw.size[1][0] + tw.size[1][1] + tw.size[1][2]; + +	// tw.offsets[signbits] = vector to apropriate corner from origin +	tw.offsets[0][0] = tw.size[0][0]; +	tw.offsets[0][1] = tw.size[0][1]; +	tw.offsets[0][2] = tw.size[0][2]; + +	tw.offsets[1][0] = tw.size[1][0]; +	tw.offsets[1][1] = tw.size[0][1]; +	tw.offsets[1][2] = tw.size[0][2]; + +	tw.offsets[2][0] = tw.size[0][0]; +	tw.offsets[2][1] = tw.size[1][1]; +	tw.offsets[2][2] = tw.size[0][2]; + +	tw.offsets[3][0] = tw.size[1][0]; +	tw.offsets[3][1] = tw.size[1][1]; +	tw.offsets[3][2] = tw.size[0][2]; + +	tw.offsets[4][0] = tw.size[0][0]; +	tw.offsets[4][1] = tw.size[0][1]; +	tw.offsets[4][2] = tw.size[1][2]; + +	tw.offsets[5][0] = tw.size[1][0]; +	tw.offsets[5][1] = tw.size[0][1]; +	tw.offsets[5][2] = tw.size[1][2]; + +	tw.offsets[6][0] = tw.size[0][0]; +	tw.offsets[6][1] = tw.size[1][1]; +	tw.offsets[6][2] = tw.size[1][2]; + +	tw.offsets[7][0] = tw.size[1][0]; +	tw.offsets[7][1] = tw.size[1][1]; +	tw.offsets[7][2] = tw.size[1][2]; + +	// +	// calculate bounds +	// +	if ( tw.type == TT_CAPSULE ) { +		for ( i = 0 ; i < 3 ; i++ ) { +			if ( tw.start[i] < tw.end[i] ) { +				tw.bounds[0][i] = tw.start[i] - fabs(tw.sphere.offset[i]) - tw.sphere.radius; +				tw.bounds[1][i] = tw.end[i] + fabs(tw.sphere.offset[i]) + tw.sphere.radius; +			} else { +				tw.bounds[0][i] = tw.end[i] - fabs(tw.sphere.offset[i]) - tw.sphere.radius; +				tw.bounds[1][i] = tw.start[i] + fabs(tw.sphere.offset[i]) + tw.sphere.radius; +			} +		} +	} +	else { +		for ( i = 0 ; i < 3 ; i++ ) { +			if ( tw.start[i] < tw.end[i] ) { +				tw.bounds[0][i] = tw.start[i] + tw.size[0][i]; +				tw.bounds[1][i] = tw.end[i] + tw.size[1][i]; +			} else { +				tw.bounds[0][i] = tw.end[i] + tw.size[0][i]; +				tw.bounds[1][i] = tw.start[i] + tw.size[1][i]; +			} +		} +	} + +	// +	// check for position test special case +	// +	if (start[0] == end[0] && start[1] == end[1] && start[2] == end[2]) { +		if ( model ) { +#ifdef ALWAYS_BBOX_VS_BBOX // bk010201 - FIXME - compile time flag? +			if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) { +				tw.type = TT_AABB; +				CM_TestInLeaf( &tw, &cmod->leaf ); +			} +			else +#elif defined(ALWAYS_CAPSULE_VS_CAPSULE) +			if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) { +				CM_TestCapsuleInCapsule( &tw, model ); +			} +			else +#endif +			if ( model == CAPSULE_MODEL_HANDLE ) { +				if ( tw.type == TT_CAPSULE ) { +					CM_TestCapsuleInCapsule( &tw, model ); +				} +				else { +					CM_TestBoundingBoxInCapsule( &tw, model ); +				} +			} +			else { +				CM_TestInLeaf( &tw, &cmod->leaf ); +			} +		} else { +			CM_PositionTest( &tw ); +		} +	} else { +		// +		// check for point special case +		// +		if ( tw.size[0][0] == 0 && tw.size[0][1] == 0 && tw.size[0][2] == 0 ) { +			tw.isPoint = qtrue; +			VectorClear( tw.extents ); +		} else { +			tw.isPoint = qfalse; +			tw.extents[0] = tw.size[1][0]; +			tw.extents[1] = tw.size[1][1]; +			tw.extents[2] = tw.size[1][2]; +		} + +		// +		// general sweeping through world +		// +		if ( model ) { +#ifdef ALWAYS_BBOX_VS_BBOX +			if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) { +				tw.type = TT_AABB; +				CM_TraceThroughLeaf( &tw, &cmod->leaf ); +			} +			else +#elif defined(ALWAYS_CAPSULE_VS_CAPSULE) +			if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) { +				CM_TraceCapsuleThroughCapsule( &tw, model ); +			} +			else +#endif +			if ( model == CAPSULE_MODEL_HANDLE ) { +				if ( tw.type == TT_CAPSULE ) { +					CM_TraceCapsuleThroughCapsule( &tw, model ); +				} +				else { +					CM_TraceBoundingBoxThroughCapsule( &tw, model ); +				} +			} +			else { +				CM_TraceThroughLeaf( &tw, &cmod->leaf ); +			} +		} else { +			CM_TraceThroughTree( &tw, 0, 0, 1, tw.start, tw.end ); +		} +	} + +	// generate endpos from the original, unmodified start/end +	if ( tw.trace.fraction == 1 ) { +		VectorCopy (end, tw.trace.endpos); +	} else { +		for ( i=0 ; i<3 ; i++ ) { +			tw.trace.endpos[i] = start[i] + tw.trace.fraction * (end[i] - start[i]); +		} +	} + +        // If allsolid is set (was entirely inside something solid), the plane is not valid. +        // If fraction == 1.0, we never hit anything, and thus the plane is not valid. +        // Otherwise, the normal on the plane should have unit length +        assert(tw.trace.allsolid || +               tw.trace.fraction == 1.0 || +               VectorLengthSquared(tw.trace.plane.normal) > 0.9999); +	*results = tw.trace; +} + +/* +================== +CM_BoxTrace +================== +*/ +void CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, +						  vec3_t mins, vec3_t maxs, +						  clipHandle_t model, int brushmask, traceType_t type ) { +	CM_Trace( results, start, end, mins, maxs, model, vec3_origin, brushmask, type, NULL ); +} + +/* +================== +CM_TransformedBoxTrace + +Handles offseting and rotation of the end points for moving and +rotating entities +================== +*/ +void CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, +						  vec3_t mins, vec3_t maxs, +						  clipHandle_t model, int brushmask, +						  const vec3_t origin, const vec3_t angles, traceType_t type ) { +	trace_t		trace; +	vec3_t		start_l, end_l; +	qboolean	rotated; +	vec3_t		offset; +	vec3_t		symetricSize[2]; +	vec3_t		matrix[3], transpose[3]; +	int			i; +	float		halfwidth; +	float		halfheight; +	float		t; +	sphere_t	sphere; + +	if ( !mins ) { +		mins = vec3_origin; +	} +	if ( !maxs ) { +		maxs = vec3_origin; +	} + +	// adjust so that mins and maxs are always symetric, which +	// avoids some complications with plane expanding of rotated +	// bmodels +	for ( i = 0 ; i < 3 ; i++ ) { +		offset[i] = ( mins[i] + maxs[i] ) * 0.5; +		symetricSize[0][i] = mins[i] - offset[i]; +		symetricSize[1][i] = maxs[i] - offset[i]; +		start_l[i] = start[i] + offset[i]; +		end_l[i] = end[i] + offset[i]; +	} + +	// subtract origin offset +	VectorSubtract( start_l, origin, start_l ); +	VectorSubtract( end_l, origin, end_l ); + +	// rotate start and end into the models frame of reference +	if ( model != BOX_MODEL_HANDLE &&  +		(angles[0] || angles[1] || angles[2]) ) { +		rotated = qtrue; +	} else { +		rotated = qfalse; +	} + +	halfwidth = symetricSize[ 1 ][ 0 ]; +	halfheight = symetricSize[ 1 ][ 2 ]; + +	sphere.radius = ( halfwidth > halfheight ) ? halfheight : halfwidth; +	sphere.halfheight = halfheight; +	t = halfheight - sphere.radius; + +	if (rotated) { +		// rotation on trace line (start-end) instead of rotating the bmodel +		// NOTE: This is still incorrect for bounding boxes because the actual bounding +		//		 box that is swept through the model is not rotated. We cannot rotate +		//		 the bounding box or the bmodel because that would make all the brush +		//		 bevels invalid. +		//		 However this is correct for capsules since a capsule itself is rotated too. +		CreateRotationMatrix(angles, matrix); +		RotatePoint(start_l, matrix); +		RotatePoint(end_l, matrix); +		// rotated sphere offset for capsule +		sphere.offset[0] = matrix[0][ 2 ] * t; +		sphere.offset[1] = -matrix[1][ 2 ] * t; +		sphere.offset[2] = matrix[2][ 2 ] * t; +	} +	else { +		VectorSet( sphere.offset, 0, 0, t ); +	} + +	// sweep the box through the model +	CM_Trace( &trace, start_l, end_l, symetricSize[0], symetricSize[1], +			model, origin, brushmask, type, &sphere ); + +	// if the bmodel was rotated and there was a collision +	if ( rotated && trace.fraction != 1.0 ) { +		// rotation of bmodel collision plane +		TransposeMatrix(matrix, transpose); +		RotatePoint(trace.plane.normal, transpose); +	} + +	// re-calculate the end position of the trace because the trace.endpos +	// calculated by CM_Trace could be rotated and have an offset +	trace.endpos[0] = start[0] + trace.fraction * (end[0] - start[0]); +	trace.endpos[1] = start[1] + trace.fraction * (end[1] - start[1]); +	trace.endpos[2] = start[2] + trace.fraction * (end[2] - start[2]); + +	*results = trace; +} + +/* +================== +CM_BiSphereTrace +================== +*/ +void CM_BiSphereTrace( trace_t *results, const vec3_t start, +		const vec3_t end, float startRad, float endRad, +		clipHandle_t model, int mask ) +{ +	int					i; +	traceWork_t	tw; +	float				largestRadius = startRad > endRad ? startRad : endRad; +	cmodel_t		*cmod; + +	cmod = CM_ClipHandleToModel( model ); + +	cm.checkcount++;		// for multi-check avoidance + +	c_traces++;				// for statistics, may be zeroed + +	// fill in a default trace +	Com_Memset( &tw, 0, sizeof( tw ) ); +	tw.trace.fraction = 1.0f; // assume it goes the entire distance until shown otherwise +	VectorCopy( vec3_origin, tw.modelOrigin ); +	tw.type = TT_BISPHERE; +	tw.testLateralCollision = qtrue; +	tw.trace.lateralFraction = 1.0f; + +	if( !cm.numNodes ) +	{ +		*results = tw.trace; + +		return;	// map not loaded, shouldn't happen +	} + +	// set basic parms +	tw.contents = mask; + +	VectorCopy( start, tw.start ); +	VectorCopy( end, tw.end ); + +	tw.biSphere.startRadius = startRad; +	tw.biSphere.endRadius = endRad; + +	// +	// calculate bounds +	// +	for( i = 0 ; i < 3 ; i++ ) +	{ +		if( tw.start[ i ] < tw.end[ i ] ) +		{ +			tw.bounds[ 0 ][ i ] = tw.start[ i ] - tw.biSphere.startRadius; +			tw.bounds[ 1 ][ i ] = tw.end[ i ] + tw.biSphere.endRadius; +		} +		else +		{ +			tw.bounds[ 0 ][ i ] = tw.end[ i ] + tw.biSphere.endRadius; +			tw.bounds[ 1 ][ i ] = tw.start[ i ] - tw.biSphere.startRadius; +		} +	} + +	tw.isPoint = qfalse; +	tw.extents[ 0 ] = largestRadius; +	tw.extents[ 1 ] = largestRadius; +	tw.extents[ 2 ] = largestRadius; + +	// +	// general sweeping through world +	// +	if( model ) +		CM_TraceThroughLeaf( &tw, &cmod->leaf ); +	else +		CM_TraceThroughTree( &tw, 0, 0.0f, 1.0f, tw.start, tw.end ); + +	// generate endpos from the original, unmodified start/end +	if( tw.trace.fraction == 1.0f ) +	{ +		VectorCopy( end, tw.trace.endpos ); +	} +	else +	{ +		for( i = 0; i < 3; i++ ) +			tw.trace.endpos[ i ] = start[ i ] + tw.trace.fraction * ( end[ i ] - start[ i ] ); +	} + +	// If allsolid is set (was entirely inside something solid), the plane is not valid. +	// If fraction == 1.0, we never hit anything, and thus the plane is not valid. +	// Otherwise, the normal on the plane should have unit length +	assert( tw.trace.allsolid || +			tw.trace.fraction == 1.0 || +			VectorLengthSquared(tw.trace.plane.normal ) > 0.9999 ); + +	*results = tw.trace; +} + +/* +================== +CM_TransformedBiSphereTrace + +Handles offseting and rotation of the end points for moving and +rotating entities +================== +*/ +void CM_TransformedBiSphereTrace( trace_t *results, const vec3_t start, +		const vec3_t end, float startRad, float endRad, +		clipHandle_t model, int mask, +		const vec3_t origin ) +{ +	trace_t		trace; +	vec3_t		start_l, end_l; + +	// subtract origin offset +	VectorSubtract( start, origin, start_l ); +	VectorSubtract( end, origin, end_l ); + +	CM_BiSphereTrace( &trace, start_l, end_l, startRad, endRad, model, mask ); + +	// re-calculate the end position of the trace because the trace.endpos +	// calculated by CM_BiSphereTrace could be rotated and have an offset +	trace.endpos[0] = start[0] + trace.fraction * (end[0] - start[0]); +	trace.endpos[1] = start[1] + trace.fraction * (end[1] - start[1]); +	trace.endpos[2] = start[2] + trace.fraction * (end[2] - start[2]); + +	*results = trace; +} diff --git a/src/qcommon/cmd.c b/src/qcommon/cmd.c new file mode 100644 index 0000000..7495bf1 --- /dev/null +++ b/src/qcommon/cmd.c @@ -0,0 +1,771 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ +// cmd.c -- Quake script command processing module + +#include "q_shared.h" +#include "qcommon.h" + +#define	MAX_CMD_BUFFER	16384 +#define	MAX_CMD_LINE	1024 + +typedef struct { +	byte	*data; +	int		maxsize; +	int		cursize; +} cmd_t; + +int			cmd_wait; +cmd_t		cmd_text; +byte		cmd_text_buf[MAX_CMD_BUFFER]; + + +//============================================================================= + +/* +============ +Cmd_Wait_f + +Causes execution of the remainder of the command buffer to be delayed until +next frame.  This allows commands like: +bind g "cmd use rocket ; +attack ; wait ; -attack ; cmd use blaster" +============ +*/ +void Cmd_Wait_f( void ) { +	if ( Cmd_Argc() == 2 ) { +		cmd_wait = atoi( Cmd_Argv( 1 ) ); +	} else { +		cmd_wait = 1; +	} +} + + +/* +============================================================================= + +						COMMAND BUFFER + +============================================================================= +*/ + +/* +============ +Cbuf_Init +============ +*/ +void Cbuf_Init (void) +{ +	cmd_text.data = cmd_text_buf; +	cmd_text.maxsize = MAX_CMD_BUFFER; +	cmd_text.cursize = 0; +} + +/* +============ +Cbuf_AddText + +Adds command text at the end of the buffer, does NOT add a final \n +============ +*/ +void Cbuf_AddText( const char *text ) { +	int		l; +	 +	l = strlen (text); + +	if (cmd_text.cursize + l >= cmd_text.maxsize) +	{ +		Com_Printf ("Cbuf_AddText: overflow\n"); +		return; +	} +	Com_Memcpy(&cmd_text.data[cmd_text.cursize], text, l); +	cmd_text.cursize += l; +} + + +/* +============ +Cbuf_InsertText + +Adds command text immediately after the current command +Adds a \n to the text +============ +*/ +void Cbuf_InsertText( const char *text ) { +	int		len; +	int		i; + +	len = strlen( text ) + 1; +	if ( len + cmd_text.cursize > cmd_text.maxsize ) { +		Com_Printf( "Cbuf_InsertText overflowed\n" ); +		return; +	} + +	// move the existing command text +	for ( i = cmd_text.cursize - 1 ; i >= 0 ; i-- ) { +		cmd_text.data[ i + len ] = cmd_text.data[ i ]; +	} + +	// copy the new text in +	Com_Memcpy( cmd_text.data, text, len - 1 ); + +	// add a \n +	cmd_text.data[ len - 1 ] = '\n'; + +	cmd_text.cursize += len; +} + + +/* +============ +Cbuf_ExecuteText +============ +*/ +void Cbuf_ExecuteText (int exec_when, const char *text) +{ +	switch (exec_when) +	{ +	case EXEC_NOW: +		if (text && strlen(text) > 0) { +			Cmd_ExecuteString (text); +		} else { +			Cbuf_Execute(); +		} +		break; +	case EXEC_INSERT: +		Cbuf_InsertText (text); +		break; +	case EXEC_APPEND: +		Cbuf_AddText (text); +		break; +	default: +		Com_Error (ERR_FATAL, "Cbuf_ExecuteText: bad exec_when"); +	} +} + +/* +============ +Cbuf_Execute +============ +*/ +void Cbuf_Execute (void) +{ +	int		i; +	char	*text; +	char	line[MAX_CMD_LINE]; +	int		quotes; + +	while (cmd_text.cursize) +	{ +		if ( cmd_wait )	{ +			// skip out while text still remains in buffer, leaving it +			// for next frame +			cmd_wait--; +			break; +		} + +		// find a \n or ; line break +		text = (char *)cmd_text.data; + +		quotes = 0; +		for (i=0 ; i< cmd_text.cursize ; i++) +		{ +			if (text[i] == '"') +				quotes++; +			if ( !(quotes&1) &&  text[i] == ';') +				break;	// don't break if inside a quoted string +			if (text[i] == '\n' || text[i] == '\r' ) +				break; +		} + +		if( i >= (MAX_CMD_LINE - 1)) { +			i = MAX_CMD_LINE - 1; +		} +				 +		Com_Memcpy (line, text, i); +		line[i] = 0; +		 +// delete the text from the command buffer and move remaining commands down +// this is necessary because commands (exec) can insert data at the +// beginning of the text buffer + +		if (i == cmd_text.cursize) +			cmd_text.cursize = 0; +		else +		{ +			i++; +			cmd_text.cursize -= i; +			memmove (text, text+i, cmd_text.cursize); +		} + +// execute the command line + +		Cmd_ExecuteString (line);		 +	} +} + + +/* +============================================================================== + +						SCRIPT COMMANDS + +============================================================================== +*/ + + +/* +=============== +Cmd_Exec_f +=============== +*/ +void Cmd_Exec_f( void ) { +	char	*f; +	int		len; +	char	filename[MAX_QPATH]; + +	if (Cmd_Argc () != 2) { +		Com_Printf ("exec <filename> : execute a script file\n"); +		return; +	} + +	Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) ); +	COM_DefaultExtension( filename, sizeof( filename ), ".cfg" );  +	len = FS_ReadFile( filename, (void **)&f); +	if (!f) { +		Com_Printf ("couldn't exec %s\n",Cmd_Argv(1)); +		return; +	} +	Com_Printf ("execing %s\n",Cmd_Argv(1)); +	 +	Cbuf_InsertText (f); + +	FS_FreeFile (f); +} + + +/* +=============== +Cmd_Vstr_f + +Inserts the current value of a variable as command text +=============== +*/ +void Cmd_Vstr_f( void ) { +	char	*v; + +	if (Cmd_Argc () != 2) { +		Com_Printf ("vstr <variablename> : execute a variable command\n"); +		return; +	} + +	v = Cvar_VariableString( Cmd_Argv( 1 ) ); +	Cbuf_InsertText( va("%s\n", v ) ); +} + + +/* +=============== +Cmd_Echo_f + +Just prints the rest of the line to the console +=============== +*/ +void Cmd_Echo_f (void) +{ +	int		i; +	 +	for (i=1 ; i<Cmd_Argc() ; i++) +		Com_Printf ("%s ",Cmd_Argv(i)); +	Com_Printf ("\n"); +} + + +/* +============================================================================= + +					COMMAND EXECUTION + +============================================================================= +*/ + +typedef struct cmd_function_s +{ +	struct cmd_function_s	*next; +	char					*name; +	xcommand_t				function; +} cmd_function_t; + + +typedef struct cmdContext_s +{ +	int		argc; +	char	*argv[ MAX_STRING_TOKENS ];	// points into cmd.tokenized +	char	tokenized[ BIG_INFO_STRING + MAX_STRING_TOKENS ];	// will have 0 bytes inserted +	char	cmd[ BIG_INFO_STRING ]; // the original command we received (no token processing) +} cmdContext_t; + +static cmdContext_t		cmd; +static cmdContext_t		savedCmd; +static cmd_function_t	*cmd_functions;		// possible commands to execute + +/* +============ +Cmd_SaveCmdContext +============ +*/ +void Cmd_SaveCmdContext( void ) +{ +	Com_Memcpy( &savedCmd, &cmd, sizeof( cmdContext_t ) ); +} + +/* +============ +Cmd_RestoreCmdContext +============ +*/ +void Cmd_RestoreCmdContext( void ) +{ +	Com_Memcpy( &cmd, &savedCmd, sizeof( cmdContext_t ) ); +} + +/* +============ +Cmd_Argc +============ +*/ +int		Cmd_Argc( void ) { +	return cmd.argc; +} + +/* +============ +Cmd_Argv +============ +*/ +char	*Cmd_Argv( int arg ) { +	if ( (unsigned)arg >= cmd.argc ) { +		return ""; +	} +	return cmd.argv[arg];	 +} + +/* +============ +Cmd_ArgvBuffer + +The interpreted versions use this because +they can't have pointers returned to them +============ +*/ +void	Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength ) { +	Q_strncpyz( buffer, Cmd_Argv( arg ), bufferLength ); +} + + +/* +============ +Cmd_Args + +Returns a single string containing argv(1) to argv(argc()-1) +============ +*/ +char	*Cmd_Args( void ) { +	static	char		cmd_args[MAX_STRING_CHARS]; +	int		i; + +	cmd_args[0] = 0; +	for ( i = 1 ; i < cmd.argc ; i++ ) { +		strcat( cmd_args, cmd.argv[i] ); +		if ( i != cmd.argc-1 ) { +			strcat( cmd_args, " " ); +		} +	} + +	return cmd_args; +} + +/* +============ +Cmd_Args + +Returns a single string containing argv(arg) to argv(argc()-1) +============ +*/ +char *Cmd_ArgsFrom( int arg ) { +	static	char		cmd_args[BIG_INFO_STRING]; +	int		i; + +	cmd_args[0] = 0; +	if (arg < 0) +		arg = 0; +	for ( i = arg ; i < cmd.argc ; i++ ) { +		strcat( cmd_args, cmd.argv[i] ); +		if ( i != cmd.argc-1 ) { +			strcat( cmd_args, " " ); +		} +	} + +	return cmd_args; +} + +/* +============ +Cmd_ArgsBuffer + +The interpreted versions use this because +they can't have pointers returned to them +============ +*/ +void	Cmd_ArgsBuffer( char *buffer, int bufferLength ) { +	Q_strncpyz( buffer, Cmd_Args(), bufferLength ); +} + +/* +============ +Cmd_LiteralArgsBuffer + +The interpreted versions use this because +they can't have pointers returned to them +============ +*/ +void	Cmd_LiteralArgsBuffer( char *buffer, int bufferLength ) { +	Q_strncpyz( buffer, cmd.cmd, bufferLength ); +} + +/* +============ +Cmd_Cmd + +Retrieve the unmodified command string +For rcon use when you want to transmit without altering quoting +https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 +============ +*/ +char *Cmd_Cmd(void) +{ +	return cmd.cmd; +} + +/* +============ +Cmd_TokenizeString + +Parses the given string into command line tokens. +The text is copied to a seperate buffer and 0 characters +are inserted in the apropriate place, The argv array +will point into this temporary buffer. +============ +*/ +// NOTE TTimo define that to track tokenization issues +//#define TKN_DBG +static void Cmd_TokenizeString2( const char *text_in, qboolean ignoreQuotes ) { +	const char	*text; +	char	*textOut; + +#ifdef TKN_DBG +  // FIXME TTimo blunt hook to try to find the tokenization of userinfo +  Com_DPrintf("Cmd_TokenizeString: %s\n", text_in); +#endif + +	// clear previous args +	cmd.argc = 0; +	cmd.cmd[ 0 ] = '\0'; + +	if ( !text_in ) { +		return; +	} +	 +	Q_strncpyz( cmd.cmd, text_in, sizeof(cmd.cmd) ); + +	text = text_in; +	textOut = cmd.tokenized; + +	while ( 1 ) { +		if ( cmd.argc == MAX_STRING_TOKENS ) { +			return;			// this is usually something malicious +		} + +		while ( 1 ) { +			// skip whitespace +			while ( *text && *text <= ' ' ) { +				text++; +			} +			if ( !*text ) { +				return;			// all tokens parsed +			} + +			// skip // comments +			if ( text[0] == '/' && text[1] == '/' ) { +				return;			// all tokens parsed +			} + +			// skip /* */ comments +			if ( text[0] == '/' && text[1] =='*' ) { +				while ( *text && ( text[0] != '*' || text[1] != '/' ) ) { +					text++; +				} +				if ( !*text ) { +					return;		// all tokens parsed +				} +				text += 2; +			} else { +				break;			// we are ready to parse a token +			} +		} + +		// handle quoted strings +    // NOTE TTimo this doesn't handle \" escaping +		if ( !ignoreQuotes && *text == '"' ) { +			cmd.argv[cmd.argc] = textOut; +			cmd.argc++; +			text++; +			while ( *text && *text != '"' ) { +				*textOut++ = *text++; +			} +			*textOut++ = 0; +			if ( !*text ) { +				return;		// all tokens parsed +			} +			text++; +			continue; +		} + +		// regular token +		cmd.argv[cmd.argc] = textOut; +		cmd.argc++; + +		// skip until whitespace, quote, or command +		while ( *text > ' ' ) { +			if ( !ignoreQuotes && text[0] == '"' ) { +				break; +			} + +			if ( text[0] == '/' && text[1] == '/' ) { +				break; +			} + +			// skip /* */ comments +			if ( text[0] == '/' && text[1] =='*' ) { +				break; +			} + +			*textOut++ = *text++; +		} + +		*textOut++ = 0; + +		if ( !*text ) { +			return;		// all tokens parsed +		} +	} +	 +} + +/* +============ +Cmd_TokenizeString +============ +*/ +void Cmd_TokenizeString( const char *text_in ) { +	Cmd_TokenizeString2( text_in, qfalse ); +} + +/* +============ +Cmd_TokenizeStringIgnoreQuotes +============ +*/ +void Cmd_TokenizeStringIgnoreQuotes( const char *text_in ) { +	Cmd_TokenizeString2( text_in, qtrue ); +} + +/* +============ +Cmd_AddCommand +============ +*/ +void	Cmd_AddCommand( const char *cmd_name, xcommand_t function ) { +	cmd_function_t	*cmd; +	 +	// fail if the command already exists +	for ( cmd = cmd_functions ; cmd ; cmd=cmd->next ) { +		if ( !strcmp( cmd_name, cmd->name ) ) { +			// allow completion-only commands to be silently doubled +			if ( function != NULL ) { +				Com_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name); +			} +			return; +		} +	} + +	// use a small malloc to avoid zone fragmentation +	cmd = S_Malloc (sizeof(cmd_function_t)); +	cmd->name = CopyString( cmd_name ); +	cmd->function = function; +	cmd->next = cmd_functions; +	cmd_functions = cmd; +} + +/* +============ +Cmd_RemoveCommand +============ +*/ +void	Cmd_RemoveCommand( const char *cmd_name ) { +	cmd_function_t	*cmd, **back; + +	back = &cmd_functions; +	while( 1 ) { +		cmd = *back; +		if ( !cmd ) { +			// command wasn't active +			return; +		} +		if ( !strcmp( cmd_name, cmd->name ) ) { +			*back = cmd->next; +			if (cmd->name) { +				Z_Free(cmd->name); +			} +			Z_Free (cmd); +			return; +		} +		back = &cmd->next; +	} +} + + +/* +============ +Cmd_CommandCompletion +============ +*/ +void	Cmd_CommandCompletion( void(*callback)(const char *s) ) { +	cmd_function_t	*cmd; +	 +	for (cmd=cmd_functions ; cmd ; cmd=cmd->next) { +		callback( cmd->name ); +	} +} + + +/* +============ +Cmd_ExecuteString + +A complete command line has been parsed, so try to execute it +============ +*/ +void	Cmd_ExecuteString( const char *text ) {	 +	cmd_function_t	*cmdFunc, **prev; + +	// execute the command line +	Cmd_TokenizeString( text );		 +	if ( !Cmd_Argc() ) { +		return;		// no tokens +	} + +	// check registered command functions	 +	for ( prev = &cmd_functions ; *prev ; prev = &cmdFunc->next ) { +		cmdFunc = *prev; +		if ( !Q_stricmp( cmd.argv[0], cmdFunc->name ) ) { +			// rearrange the links so that the command will be +			// near the head of the list next time it is used +			*prev = cmdFunc->next; +			cmdFunc->next = cmd_functions; +			cmd_functions = cmdFunc; + +			// perform the action +			if ( !cmdFunc->function ) { +				// let the cgame or game handle it +				break; +			} else { +				cmdFunc->function (); +			} +			return; +		} +	} +	 +	// check cvars +	if ( Cvar_Command() ) { +		return; +	} + +	// check client game commands +	if ( com_cl_running && com_cl_running->integer && CL_GameCommand() ) { +		return; +	} + +	// check server game commands +	if ( com_sv_running && com_sv_running->integer && SV_GameCommand() ) { +		return; +	} + +	// check ui commands +	if ( com_cl_running && com_cl_running->integer && UI_GameCommand() ) { +		return; +	} + +	// send it as a server command if we are connected +	// this will usually result in a chat message +	CL_ForwardCommandToServer ( text ); +} + +/* +============ +Cmd_List_f +============ +*/ +void Cmd_List_f (void) +{ +	cmd_function_t	*cmd; +	int				i; +	char			*match; + +	if ( Cmd_Argc() > 1 ) { +		match = Cmd_Argv( 1 ); +	} else { +		match = NULL; +	} + +	i = 0; +	for (cmd=cmd_functions ; cmd ; cmd=cmd->next) { +		if (match && !Com_Filter(match, cmd->name, qfalse)) continue; + +		Com_Printf ("%s\n", cmd->name); +		i++; +	} +	Com_Printf ("%i commands\n", i); +} + +/* +============ +Cmd_Init +============ +*/ +void Cmd_Init (void) { +	Cmd_AddCommand ("cmdlist",Cmd_List_f); +	Cmd_AddCommand ("exec",Cmd_Exec_f); +	Cmd_AddCommand ("vstr",Cmd_Vstr_f); +	Cmd_AddCommand ("echo",Cmd_Echo_f); +	Cmd_AddCommand ("wait", Cmd_Wait_f); +} + diff --git a/src/qcommon/common.c b/src/qcommon/common.c new file mode 100644 index 0000000..e1484f9 --- /dev/null +++ b/src/qcommon/common.c @@ -0,0 +1,3102 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ +// common.c -- misc functions used in client and server + +#include "q_shared.h" +#include "qcommon.h" +#include <setjmp.h> +#ifndef _WIN32 +#include <netinet/in.h> +#include <sys/stat.h> // umask +#else +#include <winsock.h> +#endif + +int demo_protocols[] = +{ 66, 67, 68, 69, 0 }; + +#define MAX_NUM_ARGVS	50 + +#define MIN_DEDICATED_COMHUNKMEGS 1 +#define MIN_COMHUNKMEGS		128 +#define DEF_COMHUNKMEGS		128 +#define DEF_COMZONEMEGS		24 +#define XSTRING(x)				STRING(x) +#define STRING(x)					#x +#define DEF_COMHUNKMEGS_S	XSTRING(DEF_COMHUNKMEGS) +#define DEF_COMZONEMEGS_S	XSTRING(DEF_COMZONEMEGS) + +int		com_argc; +char	*com_argv[MAX_NUM_ARGVS+1]; + +jmp_buf abortframe;		// an ERR_DROP occured, exit the entire frame + + +FILE *debuglogfile; +static fileHandle_t logfile; +fileHandle_t	com_journalFile;			// events are written here +fileHandle_t	com_journalDataFile;		// config files are written here + +cvar_t	*com_viewlog; +cvar_t	*com_speeds; +cvar_t	*com_developer; +cvar_t	*com_dedicated; +cvar_t	*com_timescale; +cvar_t	*com_fixedtime; +cvar_t	*com_dropsim;		// 0.0 to 1.0, simulated packet drops +cvar_t	*com_journal; +cvar_t	*com_maxfps; +cvar_t	*com_altivec; +cvar_t	*com_timedemo; +cvar_t	*com_sv_running; +cvar_t	*com_cl_running; +cvar_t	*com_logfile;		// 1 = buffer log, 2 = flush after each print +cvar_t	*com_showtrace; +cvar_t	*com_version; +cvar_t	*com_blood; +cvar_t	*com_buildScript;	// for automated data building scripts +cvar_t	*cl_paused; +cvar_t	*sv_paused; +cvar_t  *cl_packetdelay; +cvar_t  *sv_packetdelay; +cvar_t	*com_cameraMode; +#if defined(_WIN32) && defined(_DEBUG) +cvar_t	*com_noErrorInterrupt; +#endif + +// com_speeds times +int		time_game; +int		time_frontend;		// renderer frontend time +int		time_backend;		// renderer backend time + +int			com_frameTime; +int			com_frameMsec; +int			com_frameNumber; + +qboolean	com_errorEntered; +qboolean	com_fullyInitialized; + +char	com_errorMessage[MAXPRINTMSG]; + +void Com_WriteConfig_f( void ); +void CIN_CloseAllVideos( void ); + +//============================================================================ + +static char	*rd_buffer; +static int	rd_buffersize; +static void	(*rd_flush)( char *buffer ); + +void Com_BeginRedirect (char *buffer, int buffersize, void (*flush)( char *) ) +{ +	if (!buffer || !buffersize || !flush) +		return; +	rd_buffer = buffer; +	rd_buffersize = buffersize; +	rd_flush = flush; + +	*rd_buffer = 0; +} + +void Com_EndRedirect (void) +{ +	if ( rd_flush ) { +		rd_flush(rd_buffer); +	} + +	rd_buffer = NULL; +	rd_buffersize = 0; +	rd_flush = NULL; +} + +/* +============= +Com_Printf + +Both client and server can use this, and it will output +to the apropriate place. + +A raw string should NEVER be passed as fmt, because of "%f" type crashers. +============= +*/ +void QDECL Com_Printf( const char *fmt, ... ) { +	va_list		argptr; +	char		msg[MAXPRINTMSG]; +  static qboolean opening_qconsole = qfalse; + + +	va_start (argptr,fmt); +	Q_vsnprintf (msg, sizeof(msg), fmt, argptr); +	va_end (argptr); + +	if ( rd_buffer ) { +		if ((strlen (msg) + strlen(rd_buffer)) > (rd_buffersize - 1)) { +			rd_flush(rd_buffer); +			*rd_buffer = 0; +		} +		Q_strcat(rd_buffer, rd_buffersize, msg); +    // TTimo nooo .. that would defeat the purpose +		//rd_flush(rd_buffer);			 +		//*rd_buffer = 0; +		return; +	} + +	// echo to console if we're not a dedicated server +	if ( com_dedicated && !com_dedicated->integer ) { +		CL_ConsolePrint( msg ); +	} + +	// echo to dedicated console and early console +	Sys_Print( msg ); + +	// logfile +	if ( com_logfile && com_logfile->integer ) { +    // TTimo: only open the qconsole.log if the filesystem is in an initialized state +    //   also, avoid recursing in the qconsole.log opening (i.e. if fs_debug is on) +		if ( !logfile && FS_Initialized() && !opening_qconsole) { +			struct tm *newtime; +			time_t aclock; + +      opening_qconsole = qtrue; + +			time( &aclock ); +			newtime = localtime( &aclock ); + +			logfile = FS_FOpenFileWrite( "qconsole.log" ); +			 +			if(logfile) +			{ +				Com_Printf( "logfile opened on %s\n", asctime( newtime ) ); +			 +				if ( com_logfile->integer > 1 ) +				{ +					// force it to not buffer so we get valid +					// data even if we are crashing +					FS_ForceFlush(logfile); +				} +			} +			else +			{ +				Com_Printf("Opening qconsole.log failed!\n"); +				Cvar_SetValue("logfile", 0); +			} + +      opening_qconsole = qfalse; +		} +		if ( logfile && FS_Initialized()) { +			FS_Write(msg, strlen(msg), logfile); +		} +	} +} + + +/* +================ +Com_DPrintf + +A Com_Printf that only shows up if the "developer" cvar is set +================ +*/ +void QDECL Com_DPrintf( const char *fmt, ...) { +	va_list		argptr; +	char		msg[MAXPRINTMSG]; +		 +	if ( !com_developer || !com_developer->integer ) { +		return;			// don't confuse non-developers with techie stuff... +	} + +	va_start (argptr,fmt);	 +	Q_vsnprintf (msg, sizeof(msg), fmt, argptr); +	va_end (argptr); +	 +	Com_Printf ("%s", msg); +} + +/* +============= +Com_Error + +Both client and server can use this, and it will +do the apropriate things. +============= +*/ +void QDECL Com_Error( int code, const char *fmt, ... ) { +	va_list		argptr; +	static int	lastErrorTime; +	static int	errorCount; +	int			currentTime; + +#if defined(_WIN32) && defined(_DEBUG) +	if ( code != ERR_DISCONNECT && code != ERR_NEED_CD ) { +		if (!com_noErrorInterrupt->integer) { +			__asm { +				int 0x03 +			} +		} +	} +#endif +	Cvar_Set("com_errorCode", va("%i", code)); + +	// when we are running automated scripts, make sure we +	// know if anything failed +	if ( com_buildScript && com_buildScript->integer ) { +		code = ERR_FATAL; +	} + +	// if we are getting a solid stream of ERR_DROP, do an ERR_FATAL +	currentTime = Sys_Milliseconds(); +	if ( currentTime - lastErrorTime < 100 ) { +		if ( ++errorCount > 3 ) { +			code = ERR_FATAL; +		} +	} else { +		errorCount = 0; +	} +	lastErrorTime = currentTime; + +	if ( com_errorEntered ) { +		Sys_Error( "recursive error after: %s", com_errorMessage ); +	} +	com_errorEntered = qtrue; + +	va_start (argptr,fmt); +	vsprintf (com_errorMessage,fmt,argptr); +	va_end (argptr); + +	if (code != ERR_DISCONNECT && code != ERR_NEED_CD) +		Cvar_Set("com_errorMessage", com_errorMessage); + +	if (code == ERR_DISCONNECT || code == ERR_SERVERDISCONNECT) { +		CL_Disconnect( qtrue ); +		CL_FlushMemory( ); +		// make sure we can get at our local stuff +		FS_PureServerSetLoadedPaks("", ""); +		com_errorEntered = qfalse; +		longjmp (abortframe, -1); +	} else if (code == ERR_DROP) { +		Com_Printf ("********************\nERROR: %s\n********************\n", com_errorMessage); +		SV_Shutdown (va("Server crashed: %s",  com_errorMessage)); +		CL_Disconnect( qtrue ); +		CL_FlushMemory( ); +		FS_PureServerSetLoadedPaks("", ""); +		com_errorEntered = qfalse; +		longjmp (abortframe, -1); +	} else if ( code == ERR_NEED_CD ) { +		SV_Shutdown( "Server didn't have CD" ); +		if ( com_cl_running && com_cl_running->integer ) { +			CL_Disconnect( qtrue ); +			CL_FlushMemory( ); +			com_errorEntered = qfalse; +			CL_CDDialog(); +		} else { +			Com_Printf("Server didn't have CD\n" ); +		} +		FS_PureServerSetLoadedPaks("", ""); +		longjmp (abortframe, -1); +	} else { +		CL_Shutdown (); +		SV_Shutdown (va("Server fatal crashed: %s", com_errorMessage)); +	} + +	Com_Shutdown (); + +	Sys_Error ("%s", com_errorMessage); +} + + +/* +============= +Com_Quit_f + +Both client and server can use this, and it will +do the apropriate things. +============= +*/ +void Com_Quit_f( void ) { +	// don't try to shutdown if we are in a recursive error +	if ( !com_errorEntered ) { +		SV_Shutdown ("Server quit"); +		CL_Shutdown (); +		Com_Shutdown (); +		FS_Shutdown(qtrue); +	} +	Sys_Quit (); +} + + + +/* +============================================================================ + +COMMAND LINE FUNCTIONS + ++ characters seperate the commandLine string into multiple console +command lines. + +All of these are valid: + +tremulous +set test blah +map test +tremulous set test blah+map test +tremulous set test blah + map test + +============================================================================ +*/ + +#define	MAX_CONSOLE_LINES	32 +int		com_numConsoleLines; +char	*com_consoleLines[MAX_CONSOLE_LINES]; + +/* +================== +Com_ParseCommandLine + +Break it up into multiple console lines +================== +*/ +void Com_ParseCommandLine( char *commandLine ) { +    int inq = 0; +    com_consoleLines[0] = commandLine; +    com_numConsoleLines = 1; + +    while ( *commandLine ) { +        if (*commandLine == '"') { +            inq = !inq; +        } +        // look for a + seperating character +        // if commandLine came from a file, we might have real line seperators +        if ( (*commandLine == '+' && !inq) || *commandLine == '\n'  || *commandLine == '\r' ) { +            if ( com_numConsoleLines == MAX_CONSOLE_LINES ) { +                return; +            } +            com_consoleLines[com_numConsoleLines] = commandLine + 1; +            com_numConsoleLines++; +            *commandLine = 0; +        } +        commandLine++; +    } +} + + +/* +=================== +Com_SafeMode + +Check for "safe" on the command line, which will +skip loading of autogen.cfg +=================== +*/ +qboolean Com_SafeMode( void ) { +	int		i; + +	for ( i = 0 ; i < com_numConsoleLines ; i++ ) { +		Cmd_TokenizeString( com_consoleLines[i] ); +		if ( !Q_stricmp( Cmd_Argv(0), "safe" ) +			|| !Q_stricmp( Cmd_Argv(0), "cvar_restart" ) ) { +			com_consoleLines[i][0] = 0; +			return qtrue; +		} +	} +	return qfalse; +} + + +/* +=============== +Com_StartupVariable + +Searches for command line parameters that are set commands. +If match is not NULL, only that cvar will be looked for. +That is necessary because cddir and basedir need to be set +before the filesystem is started, but all other sets should +be after execing the config and default. +=============== +*/ +void Com_StartupVariable( const char *match ) { +	int		i; +	char	*s; +	cvar_t	*cv; + +	for (i=0 ; i < com_numConsoleLines ; i++) { +		Cmd_TokenizeString( com_consoleLines[i] ); +		if ( strcmp( Cmd_Argv(0), "set" ) ) { +			continue; +		} + +		s = Cmd_Argv(1); +		if ( !match || !strcmp( s, match ) ) { +			Cvar_Set( s, Cmd_Argv(2) ); +			cv = Cvar_Get( s, "", 0 ); +			cv->flags |= CVAR_USER_CREATED; +//			com_consoleLines[i] = 0; +		} +	} +} + + +/* +================= +Com_AddStartupCommands + +Adds command line parameters as script statements +Commands are seperated by + signs + +Returns qtrue if any late commands were added, which +will keep the demoloop from immediately starting +================= +*/ +qboolean Com_AddStartupCommands( void ) { +	int		i; +	qboolean	added; + +	added = qfalse; +	// quote every token, so args with semicolons can work +	for (i=0 ; i < com_numConsoleLines ; i++) { +		if ( !com_consoleLines[i] || !com_consoleLines[i][0] ) { +			continue; +		} + +		// set commands won't override menu startup +		if ( Q_stricmpn( com_consoleLines[i], "set", 3 ) ) { +			added = qtrue; +		} +		Cbuf_AddText( com_consoleLines[i] ); +		Cbuf_AddText( "\n" ); +	} + +	return added; +} + + +//============================================================================ + +void Info_Print( const char *s ) { +	char	key[512]; +	char	value[512]; +	char	*o; +	int		l; + +	if (*s == '\\') +		s++; +	while (*s) +	{ +		o = key; +		while (*s && *s != '\\') +			*o++ = *s++; + +		l = o - key; +		if (l < 20) +		{ +			Com_Memset (o, ' ', 20-l); +			key[20] = 0; +		} +		else +			*o = 0; +		Com_Printf ("%s", key); + +		if (!*s) +		{ +			Com_Printf ("MISSING VALUE\n"); +			return; +		} + +		o = value; +		s++; +		while (*s && *s != '\\') +			*o++ = *s++; +		*o = 0; + +		if (*s) +			s++; +		Com_Printf ("%s\n", value); +	} +} + +/* +============ +Com_StringContains +============ +*/ +char *Com_StringContains(char *str1, char *str2, int casesensitive) { +	int len, i, j; + +	len = strlen(str1) - strlen(str2); +	for (i = 0; i <= len; i++, str1++) { +		for (j = 0; str2[j]; j++) { +			if (casesensitive) { +				if (str1[j] != str2[j]) { +					break; +				} +			} +			else { +				if (toupper(str1[j]) != toupper(str2[j])) { +					break; +				} +			} +		} +		if (!str2[j]) { +			return str1; +		} +	} +	return NULL; +} + +/* +============ +Com_Filter +============ +*/ +int Com_Filter(char *filter, char *name, int casesensitive) +{ +	char buf[MAX_TOKEN_CHARS]; +	char *ptr; +	int i, found; + +	while(*filter) { +		if (*filter == '*') { +			filter++; +			for (i = 0; *filter; i++) { +				if (*filter == '*' || *filter == '?') break; +				buf[i] = *filter; +				filter++; +			} +			buf[i] = '\0'; +			if (strlen(buf)) { +				ptr = Com_StringContains(name, buf, casesensitive); +				if (!ptr) return qfalse; +				name = ptr + strlen(buf); +			} +		} +		else if (*filter == '?') { +			filter++; +			name++; +		} +		else if (*filter == '[' && *(filter+1) == '[') { +			filter++; +		} +		else if (*filter == '[') { +			filter++; +			found = qfalse; +			while(*filter && !found) { +				if (*filter == ']' && *(filter+1) != ']') break; +				if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']')) { +					if (casesensitive) { +						if (*name >= *filter && *name <= *(filter+2)) found = qtrue; +					} +					else { +						if (toupper(*name) >= toupper(*filter) && +							toupper(*name) <= toupper(*(filter+2))) found = qtrue; +					} +					filter += 3; +				} +				else { +					if (casesensitive) { +						if (*filter == *name) found = qtrue; +					} +					else { +						if (toupper(*filter) == toupper(*name)) found = qtrue; +					} +					filter++; +				} +			} +			if (!found) return qfalse; +			while(*filter) { +				if (*filter == ']' && *(filter+1) != ']') break; +				filter++; +			} +			filter++; +			name++; +		} +		else { +			if (casesensitive) { +				if (*filter != *name) return qfalse; +			} +			else { +				if (toupper(*filter) != toupper(*name)) return qfalse; +			} +			filter++; +			name++; +		} +	} +	return qtrue; +} + +/* +============ +Com_FilterPath +============ +*/ +int Com_FilterPath(char *filter, char *name, int casesensitive) +{ +	int i; +	char new_filter[MAX_QPATH]; +	char new_name[MAX_QPATH]; + +	for (i = 0; i < MAX_QPATH-1 && filter[i]; i++) { +		if ( filter[i] == '\\' || filter[i] == ':' ) { +			new_filter[i] = '/'; +		} +		else { +			new_filter[i] = filter[i]; +		} +	} +	new_filter[i] = '\0'; +	for (i = 0; i < MAX_QPATH-1 && name[i]; i++) { +		if ( name[i] == '\\' || name[i] == ':' ) { +			new_name[i] = '/'; +		} +		else { +			new_name[i] = name[i]; +		} +	} +	new_name[i] = '\0'; +	return Com_Filter(new_filter, new_name, casesensitive); +} + +/* +============ +Com_HashKey +============ +*/ +int Com_HashKey(char *string, int maxlen) { +	int register hash, i; + +	hash = 0; +	for (i = 0; i < maxlen && string[i] != '\0'; i++) { +		hash += string[i] * (119 + i); +	} +	hash = (hash ^ (hash >> 10) ^ (hash >> 20)); +	return hash; +} + +/* +================ +Com_RealTime +================ +*/ +int Com_RealTime(qtime_t *qtime) { +	time_t t; +	struct tm *tms; + +	t = time(NULL); +	if (!qtime) +		return t; +	tms = localtime(&t); +	if (tms) { +		qtime->tm_sec = tms->tm_sec; +		qtime->tm_min = tms->tm_min; +		qtime->tm_hour = tms->tm_hour; +		qtime->tm_mday = tms->tm_mday; +		qtime->tm_mon = tms->tm_mon; +		qtime->tm_year = tms->tm_year; +		qtime->tm_wday = tms->tm_wday; +		qtime->tm_yday = tms->tm_yday; +		qtime->tm_isdst = tms->tm_isdst; +	} +	return t; +} + + +/* +============================================================================== + +						ZONE MEMORY ALLOCATION + +There is never any space between memblocks, and there will never be two +contiguous free memblocks. + +The rover can be left pointing at a non-empty block + +The zone calls are pretty much only used for small strings and structures, +all big things are allocated on the hunk. +============================================================================== +*/ + +#define	ZONEID	0x1d4a11 +#define MINFRAGMENT	64 + +typedef struct zonedebug_s { +	char *label; +	char *file; +	int line; +	int allocSize; +} zonedebug_t; + +typedef struct memblock_s { +	int		size;           // including the header and possibly tiny fragments +	int     tag;            // a tag of 0 is a free block +	struct memblock_s       *next, *prev; +	int     id;        		// should be ZONEID +#ifdef ZONE_DEBUG +	zonedebug_t d; +#endif +} memblock_t; + +typedef struct { +	int		size;			// total bytes malloced, including header +	int		used;			// total bytes used +	memblock_t	blocklist;	// start / end cap for linked list +	memblock_t	*rover; +} memzone_t; + +// main zone for all "dynamic" memory allocation +memzone_t	*mainzone; +// we also have a small zone for small allocations that would only +// fragment the main zone (think of cvar and cmd strings) +memzone_t	*smallzone; + +void Z_CheckHeap( void ); + +/* +======================== +Z_ClearZone +======================== +*/ +void Z_ClearZone( memzone_t *zone, int size ) { +	memblock_t	*block; +	 +	// set the entire zone to one free block + +	zone->blocklist.next = zone->blocklist.prev = block = +		(memblock_t *)( (byte *)zone + sizeof(memzone_t) ); +	zone->blocklist.tag = 1;	// in use block +	zone->blocklist.id = 0; +	zone->blocklist.size = 0; +	zone->rover = block; +	zone->size = size; +	zone->used = 0; +	 +	block->prev = block->next = &zone->blocklist; +	block->tag = 0;			// free block +	block->id = ZONEID; +	block->size = size - sizeof(memzone_t); +} + +/* +======================== +Z_AvailableZoneMemory +======================== +*/ +int Z_AvailableZoneMemory( memzone_t *zone ) { +	return zone->size - zone->used; +} + +/* +======================== +Z_AvailableMemory +======================== +*/ +int Z_AvailableMemory( void ) { +	return Z_AvailableZoneMemory( mainzone ); +} + +/* +======================== +Z_Free +======================== +*/ +void Z_Free( void *ptr ) { +	memblock_t	*block, *other; +	memzone_t *zone; +	 +	if (!ptr) { +		Com_Error( ERR_DROP, "Z_Free: NULL pointer" ); +	} + +	block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t)); +	if (block->id != ZONEID) { +		Com_Error( ERR_FATAL, "Z_Free: freed a pointer without ZONEID" ); +	} +	if (block->tag == 0) { +		Com_Error( ERR_FATAL, "Z_Free: freed a freed pointer" ); +	} +	// if static memory +	if (block->tag == TAG_STATIC) { +		return; +	} + +	// check the memory trash tester +	if ( *(int *)((byte *)block + block->size - 4 ) != ZONEID ) { +		Com_Error( ERR_FATAL, "Z_Free: memory block wrote past end" ); +	} + +	if (block->tag == TAG_SMALL) { +		zone = smallzone; +	} +	else { +		zone = mainzone; +	} + +	zone->used -= block->size; +	// set the block to something that should cause problems +	// if it is referenced... +	Com_Memset( ptr, 0xaa, block->size - sizeof( *block ) ); + +	block->tag = 0;		// mark as free +	 +	other = block->prev; +	if (!other->tag) { +		// merge with previous free block +		other->size += block->size; +		other->next = block->next; +		other->next->prev = other; +		if (block == zone->rover) { +			zone->rover = other; +		} +		block = other; +	} + +	zone->rover = block; + +	other = block->next; +	if ( !other->tag ) { +		// merge the next free block onto the end +		block->size += other->size; +		block->next = other->next; +		block->next->prev = block; +		if (other == zone->rover) { +			zone->rover = block; +		} +	} +} + + +/* +================ +Z_FreeTags +================ +*/ +void Z_FreeTags( int tag ) { +	int			count; +	memzone_t	*zone; + +	if ( tag == TAG_SMALL ) { +		zone = smallzone; +	} +	else { +		zone = mainzone; +	} +	count = 0; +	// use the rover as our pointer, because +	// Z_Free automatically adjusts it +	zone->rover = zone->blocklist.next; +	do { +		if ( zone->rover->tag == tag ) { +			count++; +			Z_Free( (void *)(zone->rover + 1) ); +			continue; +		} +		zone->rover = zone->rover->next; +	} while ( zone->rover != &zone->blocklist ); +} + + +/* +================ +Z_TagMalloc +================ +*/ +#ifdef ZONE_DEBUG +void *Z_TagMallocDebug( int size, int tag, char *label, char *file, int line ) { +#else +void *Z_TagMalloc( int size, int tag ) { +#endif +	int		extra, allocSize; +	memblock_t	*start, *rover, *new, *base; +	memzone_t *zone; + +	if (!tag) { +		Com_Error( ERR_FATAL, "Z_TagMalloc: tried to use a 0 tag" ); +	} + +	if ( tag == TAG_SMALL ) { +		zone = smallzone; +	} +	else { +		zone = mainzone; +	} + +	allocSize = size; +	// +	// scan through the block list looking for the first free block +	// of sufficient size +	// +	size += sizeof(memblock_t);	// account for size of block header +	size += 4;					// space for memory trash tester +	size = PAD(size, sizeof(intptr_t));		// align to 32/64 bit boundary +	 +	base = rover = zone->rover; +	start = base->prev; +	 +	do { +		if (rover == start)	{ +#ifdef ZONE_DEBUG +			Z_LogHeap(); +#endif +			// scaned all the way around the list +			Com_Error( ERR_FATAL, "Z_Malloc: failed on allocation of %i bytes from the %s zone", +								size, zone == smallzone ? "small" : "main"); +			return NULL; +		} +		if (rover->tag) { +			base = rover = rover->next; +		} else { +			rover = rover->next; +		} +	} while (base->tag || base->size < size); +	 +	// +	// found a block big enough +	// +	extra = base->size - size; +	if (extra > MINFRAGMENT) { +		// there will be a free fragment after the allocated block +		new = (memblock_t *) ((byte *)base + size ); +		new->size = extra; +		new->tag = 0;			// free block +		new->prev = base; +		new->id = ZONEID; +		new->next = base->next; +		new->next->prev = new; +		base->next = new; +		base->size = size; +	} +	 +	base->tag = tag;			// no longer a free block +	 +	zone->rover = base->next;	// next allocation will start looking here +	zone->used += base->size;	// +	 +	base->id = ZONEID; + +#ifdef ZONE_DEBUG +	base->d.label = label; +	base->d.file = file; +	base->d.line = line; +	base->d.allocSize = allocSize; +#endif + +	// marker for memory trash testing +	*(int *)((byte *)base + base->size - 4) = ZONEID; + +	return (void *) ((byte *)base + sizeof(memblock_t)); +} + +/* +======================== +Z_Malloc +======================== +*/ +#ifdef ZONE_DEBUG +void *Z_MallocDebug( int size, char *label, char *file, int line ) { +#else +void *Z_Malloc( int size ) { +#endif +	void	*buf; +	 +  //Z_CheckHeap ();	// DEBUG + +#ifdef ZONE_DEBUG +	buf = Z_TagMallocDebug( size, TAG_GENERAL, label, file, line ); +#else +	buf = Z_TagMalloc( size, TAG_GENERAL ); +#endif +	Com_Memset( buf, 0, size ); + +	return buf; +} + +#ifdef ZONE_DEBUG +void *S_MallocDebug( int size, char *label, char *file, int line ) { +	return Z_TagMallocDebug( size, TAG_SMALL, label, file, line ); +} +#else +void *S_Malloc( int size ) { +	return Z_TagMalloc( size, TAG_SMALL ); +} +#endif + +/* +======================== +Z_CheckHeap +======================== +*/ +void Z_CheckHeap( void ) { +	memblock_t	*block; +	 +	for (block = mainzone->blocklist.next ; ; block = block->next) { +		if (block->next == &mainzone->blocklist) { +			break;			// all blocks have been hit +		} +		if ( (byte *)block + block->size != (byte *)block->next) +			Com_Error( ERR_FATAL, "Z_CheckHeap: block size does not touch the next block\n" ); +		if ( block->next->prev != block) { +			Com_Error( ERR_FATAL, "Z_CheckHeap: next block doesn't have proper back link\n" ); +		} +		if ( !block->tag && !block->next->tag ) { +			Com_Error( ERR_FATAL, "Z_CheckHeap: two consecutive free blocks\n" ); +		} +	} +} + +/* +======================== +Z_LogZoneHeap +======================== +*/ +void Z_LogZoneHeap( memzone_t *zone, char *name ) { +#ifdef ZONE_DEBUG +	char dump[32], *ptr; +	int  i, j; +#endif +	memblock_t	*block; +	char		buf[4096]; +	int size, allocSize, numBlocks; + +	if (!logfile || !FS_Initialized()) +		return; +	size = allocSize = numBlocks = 0; +	Com_sprintf(buf, sizeof(buf), "\r\n================\r\n%s log\r\n================\r\n", name); +	FS_Write(buf, strlen(buf), logfile); +	for (block = zone->blocklist.next ; block->next != &zone->blocklist; block = block->next) { +		if (block->tag) { +#ifdef ZONE_DEBUG +			ptr = ((char *) block) + sizeof(memblock_t); +			j = 0; +			for (i = 0; i < 20 && i < block->d.allocSize; i++) { +				if (ptr[i] >= 32 && ptr[i] < 127) { +					dump[j++] = ptr[i]; +				} +				else { +					dump[j++] = '_'; +				} +			} +			dump[j] = '\0'; +			Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s) [%s]\r\n", block->d.allocSize, block->d.file, block->d.line, block->d.label, dump); +			FS_Write(buf, strlen(buf), logfile); +			allocSize += block->d.allocSize; +#endif +			size += block->size; +			numBlocks++; +		} +	} +#ifdef ZONE_DEBUG +	// subtract debug memory +	size -= numBlocks * sizeof(zonedebug_t); +#else +	allocSize = numBlocks * sizeof(memblock_t); // + 32 bit alignment +#endif +	Com_sprintf(buf, sizeof(buf), "%d %s memory in %d blocks\r\n", size, name, numBlocks); +	FS_Write(buf, strlen(buf), logfile); +	Com_sprintf(buf, sizeof(buf), "%d %s memory overhead\r\n", size - allocSize, name); +	FS_Write(buf, strlen(buf), logfile); +} + +/* +======================== +Z_LogHeap +======================== +*/ +void Z_LogHeap( void ) { +	Z_LogZoneHeap( mainzone, "MAIN" ); +	Z_LogZoneHeap( smallzone, "SMALL" ); +} + +// static mem blocks to reduce a lot of small zone overhead +typedef struct memstatic_s { +	memblock_t b; +	byte mem[2]; +} memstatic_t; + +// bk001204 - initializer brackets +memstatic_t emptystring = +	{ {(sizeof(memblock_t)+2 + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'\0', '\0'} }; +memstatic_t numberstring[] = { +	{ {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'0', '\0'} }, +	{ {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'1', '\0'} }, +	{ {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'2', '\0'} }, +	{ {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'3', '\0'} }, +	{ {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'4', '\0'} }, +	{ {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'5', '\0'} }, +	{ {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'6', '\0'} }, +	{ {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'7', '\0'} }, +	{ {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'8', '\0'} },  +	{ {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'9', '\0'} } +}; + +/* +======================== +CopyString + + NOTE:	never write over the memory CopyString returns because +		memory from a memstatic_t might be returned +======================== +*/ +char *CopyString( const char *in ) { +	char	*out; + +	if (!in[0]) { +		return ((char *)&emptystring) + sizeof(memblock_t); +	} +	else if (!in[1]) { +		if (in[0] >= '0' && in[0] <= '9') { +			return ((char *)&numberstring[in[0]-'0']) + sizeof(memblock_t); +		} +	} +	out = S_Malloc (strlen(in)+1); +	strcpy (out, in); +	return out; +} + +/* +============================================================================== + +Goals: +	reproducable without history effects -- no out of memory errors on weird map to map changes +	allow restarting of the client without fragmentation +	minimize total pages in use at run time +	minimize total pages needed during load time + +  Single block of memory with stack allocators coming from both ends towards the middle. + +  One side is designated the temporary memory allocator. + +  Temporary memory can be allocated and freed in any order. + +  A highwater mark is kept of the most in use at any time. + +  When there is no temporary memory allocated, the permanent and temp sides +  can be switched, allowing the already touched temp memory to be used for +  permanent storage. + +  Temp memory must never be allocated on two ends at once, or fragmentation +  could occur. + +  If we have any in-use temp memory, additional temp allocations must come from +  that side. + +  If not, we can choose to make either side the new temp side and push future +  permanent allocations to the other side.  Permanent allocations should be +  kept on the side that has the current greatest wasted highwater mark. + +============================================================================== +*/ + + +#define	HUNK_MAGIC	0x89537892 +#define	HUNK_FREE_MAGIC	0x89537893 + +typedef struct { +	int		magic; +	int		size; +} hunkHeader_t; + +typedef struct { +	int		mark; +	int		permanent; +	int		temp; +	int		tempHighwater; +} hunkUsed_t; + +typedef struct hunkblock_s { +	int size; +	byte printed; +	struct hunkblock_s *next; +	char *label; +	char *file; +	int line; +} hunkblock_t; + +static	hunkblock_t *hunkblocks; + +static	hunkUsed_t	hunk_low, hunk_high; +static	hunkUsed_t	*hunk_permanent, *hunk_temp; + +static	byte	*s_hunkData = NULL; +static	int		s_hunkTotal; + +static	int		s_zoneTotal; +static	int		s_smallZoneTotal; + + +/* +================= +Com_Meminfo_f +================= +*/ +void Com_Meminfo_f( void ) { +	memblock_t	*block; +	int			zoneBytes, zoneBlocks; +	int			smallZoneBytes, smallZoneBlocks; +	int			botlibBytes, rendererBytes; +	int			unused; + +	zoneBytes = 0; +	botlibBytes = 0; +	rendererBytes = 0; +	zoneBlocks = 0; +	for (block = mainzone->blocklist.next ; ; block = block->next) { +		if ( Cmd_Argc() != 1 ) { +			Com_Printf ("block:%p    size:%7i    tag:%3i\n", +				block, block->size, block->tag); +		} +		if ( block->tag ) { +			zoneBytes += block->size; +			zoneBlocks++; +			if ( block->tag == TAG_BOTLIB ) { +				botlibBytes += block->size; +			} else if ( block->tag == TAG_RENDERER ) { +				rendererBytes += block->size; +			} +		} + +		if (block->next == &mainzone->blocklist) { +			break;			// all blocks have been hit	 +		} +		if ( (byte *)block + block->size != (byte *)block->next) { +			Com_Printf ("ERROR: block size does not touch the next block\n"); +		} +		if ( block->next->prev != block) { +			Com_Printf ("ERROR: next block doesn't have proper back link\n"); +		} +		if ( !block->tag && !block->next->tag ) { +			Com_Printf ("ERROR: two consecutive free blocks\n"); +		} +	} + +	smallZoneBytes = 0; +	smallZoneBlocks = 0; +	for (block = smallzone->blocklist.next ; ; block = block->next) { +		if ( block->tag ) { +			smallZoneBytes += block->size; +			smallZoneBlocks++; +		} + +		if (block->next == &smallzone->blocklist) { +			break;			// all blocks have been hit	 +		} +	} + +	Com_Printf( "%8i bytes total hunk\n", s_hunkTotal ); +	Com_Printf( "%8i bytes total zone\n", s_zoneTotal ); +	Com_Printf( "\n" ); +	Com_Printf( "%8i low mark\n", hunk_low.mark ); +	Com_Printf( "%8i low permanent\n", hunk_low.permanent ); +	if ( hunk_low.temp != hunk_low.permanent ) { +		Com_Printf( "%8i low temp\n", hunk_low.temp ); +	} +	Com_Printf( "%8i low tempHighwater\n", hunk_low.tempHighwater ); +	Com_Printf( "\n" ); +	Com_Printf( "%8i high mark\n", hunk_high.mark ); +	Com_Printf( "%8i high permanent\n", hunk_high.permanent ); +	if ( hunk_high.temp != hunk_high.permanent ) { +		Com_Printf( "%8i high temp\n", hunk_high.temp ); +	} +	Com_Printf( "%8i high tempHighwater\n", hunk_high.tempHighwater ); +	Com_Printf( "\n" ); +	Com_Printf( "%8i total hunk in use\n", hunk_low.permanent + hunk_high.permanent ); +	unused = 0; +	if ( hunk_low.tempHighwater > hunk_low.permanent ) { +		unused += hunk_low.tempHighwater - hunk_low.permanent; +	} +	if ( hunk_high.tempHighwater > hunk_high.permanent ) { +		unused += hunk_high.tempHighwater - hunk_high.permanent; +	} +	Com_Printf( "%8i unused highwater\n", unused ); +	Com_Printf( "\n" ); +	Com_Printf( "%8i bytes in %i zone blocks\n", zoneBytes, zoneBlocks	); +	Com_Printf( "        %8i bytes in dynamic botlib\n", botlibBytes ); +	Com_Printf( "        %8i bytes in dynamic renderer\n", rendererBytes ); +	Com_Printf( "        %8i bytes in dynamic other\n", zoneBytes - ( botlibBytes + rendererBytes ) ); +	Com_Printf( "        %8i bytes in small Zone memory\n", smallZoneBytes ); +} + +/* +=============== +Com_TouchMemory + +Touch all known used data to make sure it is paged in +=============== +*/ +void Com_TouchMemory( void ) { +	int		start, end; +	int		i, j; +	int		sum; +	memblock_t	*block; + +	Z_CheckHeap(); + +	start = Sys_Milliseconds(); + +	sum = 0; + +	j = hunk_low.permanent >> 2; +	for ( i = 0 ; i < j ; i+=64 ) {			// only need to touch each page +		sum += ((int *)s_hunkData)[i]; +	} + +	i = ( s_hunkTotal - hunk_high.permanent ) >> 2; +	j = hunk_high.permanent >> 2; +	for (  ; i < j ; i+=64 ) {			// only need to touch each page +		sum += ((int *)s_hunkData)[i]; +	} + +	for (block = mainzone->blocklist.next ; ; block = block->next) { +		if ( block->tag ) { +			j = block->size >> 2; +			for ( i = 0 ; i < j ; i+=64 ) {				// only need to touch each page +				sum += ((int *)block)[i]; +			} +		} +		if ( block->next == &mainzone->blocklist ) { +			break;			// all blocks have been hit	 +		} +	} + +	end = Sys_Milliseconds(); + +	Com_Printf( "Com_TouchMemory: %i msec\n", end - start ); +} + + + +/* +================= +Com_InitZoneMemory +================= +*/ +void Com_InitSmallZoneMemory( void ) { +	s_smallZoneTotal = 512 * 1024; +	// bk001205 - was malloc +	smallzone = calloc( s_smallZoneTotal, 1 ); +	if ( !smallzone ) { +		Com_Error( ERR_FATAL, "Small zone data failed to allocate %1.1f megs", (float)s_smallZoneTotal / (1024*1024) ); +	} +	Z_ClearZone( smallzone, s_smallZoneTotal ); +	 +	return; +} + +void Com_InitZoneMemory( void ) { +	cvar_t	*cv; + +	//FIXME: 05/01/06 com_zoneMegs is useless right now as neither q3config.cfg nor +	// Com_StartupVariable have been executed by this point. The net result is that +	// s_zoneTotal will always be set to the default value. + +	// allocate the random block zone +	cv = Cvar_Get( "com_zoneMegs", DEF_COMZONEMEGS_S, CVAR_LATCH | CVAR_ARCHIVE ); + +	if ( cv->integer < DEF_COMZONEMEGS ) { +		s_zoneTotal = 1024 * 1024 * DEF_COMZONEMEGS; +	} else { +		s_zoneTotal = cv->integer * 1024 * 1024; +	} + +	// bk001205 - was malloc +	mainzone = calloc( s_zoneTotal, 1 ); +	if ( !mainzone ) { +		Com_Error( ERR_FATAL, "Zone data failed to allocate %i megs", s_zoneTotal / (1024*1024) ); +	} +	Z_ClearZone( mainzone, s_zoneTotal ); + +} + +/* +================= +Hunk_Log +================= +*/ +void Hunk_Log( void) { +	hunkblock_t	*block; +	char		buf[4096]; +	int size, numBlocks; + +	if (!logfile || !FS_Initialized()) +		return; +	size = 0; +	numBlocks = 0; +	Com_sprintf(buf, sizeof(buf), "\r\n================\r\nHunk log\r\n================\r\n"); +	FS_Write(buf, strlen(buf), logfile); +	for (block = hunkblocks ; block; block = block->next) { +#ifdef HUNK_DEBUG +		Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s)\r\n", block->size, block->file, block->line, block->label); +		FS_Write(buf, strlen(buf), logfile); +#endif +		size += block->size; +		numBlocks++; +	} +	Com_sprintf(buf, sizeof(buf), "%d Hunk memory\r\n", size); +	FS_Write(buf, strlen(buf), logfile); +	Com_sprintf(buf, sizeof(buf), "%d hunk blocks\r\n", numBlocks); +	FS_Write(buf, strlen(buf), logfile); +} + +/* +================= +Hunk_SmallLog +================= +*/ +void Hunk_SmallLog( void) { +	hunkblock_t	*block, *block2; +	char		buf[4096]; +	int size, locsize, numBlocks; + +	if (!logfile || !FS_Initialized()) +		return; +	for (block = hunkblocks ; block; block = block->next) { +		block->printed = qfalse; +	} +	size = 0; +	numBlocks = 0; +	Com_sprintf(buf, sizeof(buf), "\r\n================\r\nHunk Small log\r\n================\r\n"); +	FS_Write(buf, strlen(buf), logfile); +	for (block = hunkblocks; block; block = block->next) { +		if (block->printed) { +			continue; +		} +		locsize = block->size; +		for (block2 = block->next; block2; block2 = block2->next) { +			if (block->line != block2->line) { +				continue; +			} +			if (Q_stricmp(block->file, block2->file)) { +				continue; +			} +			size += block2->size; +			locsize += block2->size; +			block2->printed = qtrue; +		} +#ifdef HUNK_DEBUG +		Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s)\r\n", locsize, block->file, block->line, block->label); +		FS_Write(buf, strlen(buf), logfile); +#endif +		size += block->size; +		numBlocks++; +	} +	Com_sprintf(buf, sizeof(buf), "%d Hunk memory\r\n", size); +	FS_Write(buf, strlen(buf), logfile); +	Com_sprintf(buf, sizeof(buf), "%d hunk blocks\r\n", numBlocks); +	FS_Write(buf, strlen(buf), logfile); +} + +/* +================= +Com_InitZoneMemory +================= +*/ +void Com_InitHunkMemory( void ) { +	cvar_t	*cv; +	int nMinAlloc; +	char *pMsg = NULL; + +	// make sure the file system has allocated and "not" freed any temp blocks +	// this allows the config and product id files ( journal files too ) to be loaded +	// by the file system without redunant routines in the file system utilizing different  +	// memory systems +	if (FS_LoadStack() != 0) { +		Com_Error( ERR_FATAL, "Hunk initialization failed. File system load stack not zero"); +	} + +	// allocate the stack based hunk allocator +	cv = Cvar_Get( "com_hunkMegs", DEF_COMHUNKMEGS_S, CVAR_LATCH | CVAR_ARCHIVE ); + +	// if we are not dedicated min allocation is 56, otherwise min is 1 +	if (com_dedicated && com_dedicated->integer) { +		nMinAlloc = MIN_DEDICATED_COMHUNKMEGS; +		pMsg = "Minimum com_hunkMegs for a dedicated server is %i, allocating %i megs.\n"; +	} +	else { +		nMinAlloc = MIN_COMHUNKMEGS; +		pMsg = "Minimum com_hunkMegs is %i, allocating %i megs.\n"; +	} + +	if ( cv->integer < nMinAlloc ) { +		s_hunkTotal = 1024 * 1024 * nMinAlloc; +	    Com_Printf(pMsg, nMinAlloc, s_hunkTotal / (1024 * 1024)); +	} else { +		s_hunkTotal = cv->integer * 1024 * 1024; +	} + + +	// bk001205 - was malloc +	s_hunkData = calloc( s_hunkTotal + 31, 1 ); +	if ( !s_hunkData ) { +		Com_Error( ERR_FATAL, "Hunk data failed to allocate %i megs", s_hunkTotal / (1024*1024) ); +	} +	// cacheline align +	s_hunkData = (byte *) ( ( (intptr_t)s_hunkData + 31 ) & ~31 ); +	Hunk_Clear(); + +	Cmd_AddCommand( "meminfo", Com_Meminfo_f ); +#ifdef ZONE_DEBUG +	Cmd_AddCommand( "zonelog", Z_LogHeap ); +#endif +#ifdef HUNK_DEBUG +	Cmd_AddCommand( "hunklog", Hunk_Log ); +	Cmd_AddCommand( "hunksmalllog", Hunk_SmallLog ); +#endif +} + +/* +==================== +Hunk_MemoryRemaining +==================== +*/ +int	Hunk_MemoryRemaining( void ) { +	int		low, high; + +	low = hunk_low.permanent > hunk_low.temp ? hunk_low.permanent : hunk_low.temp; +	high = hunk_high.permanent > hunk_high.temp ? hunk_high.permanent : hunk_high.temp; + +	return s_hunkTotal - ( low + high ); +} + +/* +=================== +Hunk_SetMark + +The server calls this after the level and game VM have been loaded +=================== +*/ +void Hunk_SetMark( void ) { +	hunk_low.mark = hunk_low.permanent; +	hunk_high.mark = hunk_high.permanent; +} + +/* +================= +Hunk_ClearToMark + +The client calls this before starting a vid_restart or snd_restart +================= +*/ +void Hunk_ClearToMark( void ) { +	hunk_low.permanent = hunk_low.temp = hunk_low.mark; +	hunk_high.permanent = hunk_high.temp = hunk_high.mark; +} + +/* +================= +Hunk_CheckMark +================= +*/ +qboolean Hunk_CheckMark( void ) { +	if( hunk_low.mark || hunk_high.mark ) { +		return qtrue; +	} +	return qfalse; +} + +void CL_ShutdownCGame( void ); +void CL_ShutdownUI( void ); +void SV_ShutdownGameProgs( void ); + +/* +================= +Hunk_Clear + +The server calls this before shutting down or loading a new map +================= +*/ +void Hunk_Clear( void ) { + +#ifndef DEDICATED +	CL_ShutdownCGame(); +	CL_ShutdownUI(); +#endif +	SV_ShutdownGameProgs(); +#ifndef DEDICATED +	CIN_CloseAllVideos(); +#endif +	hunk_low.mark = 0; +	hunk_low.permanent = 0; +	hunk_low.temp = 0; +	hunk_low.tempHighwater = 0; + +	hunk_high.mark = 0; +	hunk_high.permanent = 0; +	hunk_high.temp = 0; +	hunk_high.tempHighwater = 0; + +	hunk_permanent = &hunk_low; +	hunk_temp = &hunk_high; + +	Com_Printf( "Hunk_Clear: reset the hunk ok\n" ); +	VM_Clear(); +#ifdef HUNK_DEBUG +	hunkblocks = NULL; +#endif +} + +static void Hunk_SwapBanks( void ) { +	hunkUsed_t	*swap; + +	// can't swap banks if there is any temp already allocated +	if ( hunk_temp->temp != hunk_temp->permanent ) { +		return; +	} + +	// if we have a larger highwater mark on this side, start making +	// our permanent allocations here and use the other side for temp +	if ( hunk_temp->tempHighwater - hunk_temp->permanent > +		hunk_permanent->tempHighwater - hunk_permanent->permanent ) { +		swap = hunk_temp; +		hunk_temp = hunk_permanent; +		hunk_permanent = swap; +	} +} + +/* +================= +Hunk_Alloc + +Allocate permanent (until the hunk is cleared) memory +================= +*/ +#ifdef HUNK_DEBUG +void *Hunk_AllocDebug( int size, ha_pref preference, char *label, char *file, int line ) { +#else +void *Hunk_Alloc( int size, ha_pref preference ) { +#endif +	void	*buf; + +	if ( s_hunkData == NULL) +	{ +		Com_Error( ERR_FATAL, "Hunk_Alloc: Hunk memory system not initialized" ); +	} + +	// can't do preference if there is any temp allocated +	if (preference == h_dontcare || hunk_temp->temp != hunk_temp->permanent) { +		Hunk_SwapBanks(); +	} else { +		if (preference == h_low && hunk_permanent != &hunk_low) { +			Hunk_SwapBanks(); +		} else if (preference == h_high && hunk_permanent != &hunk_high) { +			Hunk_SwapBanks(); +		} +	} + +#ifdef HUNK_DEBUG +	size += sizeof(hunkblock_t); +#endif + +	// round to cacheline +	size = (size+31)&~31; + +	if ( hunk_low.temp + hunk_high.temp + size > s_hunkTotal ) { +#ifdef HUNK_DEBUG +		Hunk_Log(); +		Hunk_SmallLog(); +#endif +		Com_Error( ERR_DROP, "Hunk_Alloc failed on %i", size ); +	} + +	if ( hunk_permanent == &hunk_low ) { +		buf = (void *)(s_hunkData + hunk_permanent->permanent); +		hunk_permanent->permanent += size; +	} else { +		hunk_permanent->permanent += size; +		buf = (void *)(s_hunkData + s_hunkTotal - hunk_permanent->permanent ); +	} + +	hunk_permanent->temp = hunk_permanent->permanent; + +	Com_Memset( buf, 0, size ); + +#ifdef HUNK_DEBUG +	{ +		hunkblock_t *block; + +		block = (hunkblock_t *) buf; +		block->size = size - sizeof(hunkblock_t); +		block->file = file; +		block->label = label; +		block->line = line; +		block->next = hunkblocks; +		hunkblocks = block; +		buf = ((byte *) buf) + sizeof(hunkblock_t); +	} +#endif +	return buf; +} + +/* +================= +Hunk_AllocateTempMemory + +This is used by the file loading system. +Multiple files can be loaded in temporary memory. +When the files-in-use count reaches zero, all temp memory will be deleted +================= +*/ +void *Hunk_AllocateTempMemory( int size ) { +	void		*buf; +	hunkHeader_t	*hdr; + +	// return a Z_Malloc'd block if the hunk has not been initialized +	// this allows the config and product id files ( journal files too ) to be loaded +	// by the file system without redunant routines in the file system utilizing different  +	// memory systems +	if ( s_hunkData == NULL ) +	{ +		return Z_Malloc(size); +	} + +	Hunk_SwapBanks(); + +	size = PAD(size, sizeof(intptr_t)) + sizeof( hunkHeader_t ); + +	if ( hunk_temp->temp + hunk_permanent->permanent + size > s_hunkTotal ) { +		Com_Error( ERR_DROP, "Hunk_AllocateTempMemory: failed on %i", size ); +	} + +	if ( hunk_temp == &hunk_low ) { +		buf = (void *)(s_hunkData + hunk_temp->temp); +		hunk_temp->temp += size; +	} else { +		hunk_temp->temp += size; +		buf = (void *)(s_hunkData + s_hunkTotal - hunk_temp->temp ); +	} + +	if ( hunk_temp->temp > hunk_temp->tempHighwater ) { +		hunk_temp->tempHighwater = hunk_temp->temp; +	} + +	hdr = (hunkHeader_t *)buf; +	buf = (void *)(hdr+1); + +	hdr->magic = HUNK_MAGIC; +	hdr->size = size; + +	// don't bother clearing, because we are going to load a file over it +	return buf; +} + + +/* +================== +Hunk_FreeTempMemory +================== +*/ +void Hunk_FreeTempMemory( void *buf ) { +	hunkHeader_t	*hdr; + +	  // free with Z_Free if the hunk has not been initialized +	  // this allows the config and product id files ( journal files too ) to be loaded +	  // by the file system without redunant routines in the file system utilizing different  +	  // memory systems +	if ( s_hunkData == NULL ) +	{ +		Z_Free(buf); +		return; +	} + + +	hdr = ( (hunkHeader_t *)buf ) - 1; +	if ( hdr->magic != HUNK_MAGIC ) { +		Com_Error( ERR_FATAL, "Hunk_FreeTempMemory: bad magic" ); +	} + +	hdr->magic = HUNK_FREE_MAGIC; + +	// this only works if the files are freed in stack order, +	// otherwise the memory will stay around until Hunk_ClearTempMemory +	if ( hunk_temp == &hunk_low ) { +		if ( hdr == (void *)(s_hunkData + hunk_temp->temp - hdr->size ) ) { +			hunk_temp->temp -= hdr->size; +		} else { +			Com_Printf( "Hunk_FreeTempMemory: not the final block\n" ); +		} +	} else { +		if ( hdr == (void *)(s_hunkData + s_hunkTotal - hunk_temp->temp ) ) { +			hunk_temp->temp -= hdr->size; +		} else { +			Com_Printf( "Hunk_FreeTempMemory: not the final block\n" ); +		} +	} +} + + +/* +================= +Hunk_ClearTempMemory + +The temp space is no longer needed.  If we have left more +touched but unused memory on this side, have future +permanent allocs use this side. +================= +*/ +void Hunk_ClearTempMemory( void ) { +	if ( s_hunkData != NULL ) { +		hunk_temp->temp = hunk_temp->permanent; +	} +} + +/* +================= +Hunk_Trash +================= +*/ +void Hunk_Trash( void ) { +	int length, i, rnd; +	char *buf, value; + +	return; + +	if ( s_hunkData == NULL ) +		return; + +#ifdef _DEBUG +	Com_Error(ERR_DROP, "hunk trashed\n"); +	return; +#endif + +	Cvar_Set("com_jp", "1"); +	Hunk_SwapBanks(); + +	if ( hunk_permanent == &hunk_low ) { +		buf = (void *)(s_hunkData + hunk_permanent->permanent); +	} else { +		buf = (void *)(s_hunkData + s_hunkTotal - hunk_permanent->permanent ); +	} +	length = hunk_permanent->permanent; + +	if (length > 0x7FFFF) { +		//randomly trash data within buf +		rnd = random() * (length - 0x7FFFF); +		value = 31; +		for (i = 0; i < 0x7FFFF; i++) { +			value *= 109; +			buf[rnd+i] ^= value; +		} +	} +} + +/* +=================================================================== + +EVENTS AND JOURNALING + +In addition to these events, .cfg files are also copied to the +journaled file +=================================================================== +*/ + +// bk001129 - here we go again: upped from 64 +// FIXME TTimo blunt upping from 256 to 1024 +// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=5 +#define	MAX_PUSHED_EVENTS	            1024 +// bk001129 - init, also static +static int com_pushedEventsHead = 0; +static int com_pushedEventsTail = 0; +// bk001129 - static +static sysEvent_t	com_pushedEvents[MAX_PUSHED_EVENTS]; + +/* +================= +Com_InitJournaling +================= +*/ +void Com_InitJournaling( void ) { +	Com_StartupVariable( "journal" ); +	com_journal = Cvar_Get ("journal", "0", CVAR_INIT); +	if ( !com_journal->integer ) { +		return; +	} + +	if ( com_journal->integer == 1 ) { +		Com_Printf( "Journaling events\n"); +		com_journalFile = FS_FOpenFileWrite( "journal.dat" ); +		com_journalDataFile = FS_FOpenFileWrite( "journaldata.dat" ); +	} else if ( com_journal->integer == 2 ) { +		Com_Printf( "Replaying journaled events\n"); +		FS_FOpenFileRead( "journal.dat", &com_journalFile, qtrue ); +		FS_FOpenFileRead( "journaldata.dat", &com_journalDataFile, qtrue ); +	} + +	if ( !com_journalFile || !com_journalDataFile ) { +		Cvar_Set( "com_journal", "0" ); +		com_journalFile = 0; +		com_journalDataFile = 0; +		Com_Printf( "Couldn't open journal files\n" ); +	} +} + +/* +================= +Com_GetRealEvent +================= +*/ +sysEvent_t	Com_GetRealEvent( void ) { +	int			r; +	sysEvent_t	ev; + +	// either get an event from the system or the journal file +	if ( com_journal->integer == 2 ) { +		r = FS_Read( &ev, sizeof(ev), com_journalFile ); +		if ( r != sizeof(ev) ) { +			Com_Error( ERR_FATAL, "Error reading from journal file" ); +		} +		if ( ev.evPtrLength ) { +			ev.evPtr = Z_Malloc( ev.evPtrLength ); +			r = FS_Read( ev.evPtr, ev.evPtrLength, com_journalFile ); +			if ( r != ev.evPtrLength ) { +				Com_Error( ERR_FATAL, "Error reading from journal file" ); +			} +		} +	} else { +		ev = Sys_GetEvent(); + +		// write the journal value out if needed +		if ( com_journal->integer == 1 ) { +			r = FS_Write( &ev, sizeof(ev), com_journalFile ); +			if ( r != sizeof(ev) ) { +				Com_Error( ERR_FATAL, "Error writing to journal file" ); +			} +			if ( ev.evPtrLength ) { +				r = FS_Write( ev.evPtr, ev.evPtrLength, com_journalFile ); +				if ( r != ev.evPtrLength ) { +					Com_Error( ERR_FATAL, "Error writing to journal file" ); +				} +			} +		} +	} + +	return ev; +} + + +/* +================= +Com_InitPushEvent +================= +*/ +// bk001129 - added +void Com_InitPushEvent( void ) { +  // clear the static buffer array +  // this requires SE_NONE to be accepted as a valid but NOP event +  memset( com_pushedEvents, 0, sizeof(com_pushedEvents) ); +  // reset counters while we are at it +  // beware: GetEvent might still return an SE_NONE from the buffer +  com_pushedEventsHead = 0; +  com_pushedEventsTail = 0; +} + + +/* +================= +Com_PushEvent +================= +*/ +void Com_PushEvent( sysEvent_t *event ) { +	sysEvent_t		*ev; +	static int printedWarning = 0; // bk001129 - init, bk001204 - explicit int + +	ev = &com_pushedEvents[ com_pushedEventsHead & (MAX_PUSHED_EVENTS-1) ]; + +	if ( com_pushedEventsHead - com_pushedEventsTail >= MAX_PUSHED_EVENTS ) { + +		// don't print the warning constantly, or it can give time for more... +		if ( !printedWarning ) { +			printedWarning = qtrue; +			Com_Printf( "WARNING: Com_PushEvent overflow\n" ); +		} + +		if ( ev->evPtr ) { +			Z_Free( ev->evPtr ); +		} +		com_pushedEventsTail++; +	} else { +		printedWarning = qfalse; +	} + +	*ev = *event; +	com_pushedEventsHead++; +} + +/* +================= +Com_GetEvent +================= +*/ +sysEvent_t	Com_GetEvent( void ) { +	if ( com_pushedEventsHead > com_pushedEventsTail ) { +		com_pushedEventsTail++; +		return com_pushedEvents[ (com_pushedEventsTail-1) & (MAX_PUSHED_EVENTS-1) ]; +	} +	return Com_GetRealEvent(); +} + +/* +================= +Com_RunAndTimeServerPacket +================= +*/ +void Com_RunAndTimeServerPacket( netadr_t *evFrom, msg_t *buf ) { +	int		t1, t2, msec; + +	t1 = 0; + +	if ( com_speeds->integer ) { +		t1 = Sys_Milliseconds (); +	} + +	SV_PacketEvent( *evFrom, buf ); + +	if ( com_speeds->integer ) { +		t2 = Sys_Milliseconds (); +		msec = t2 - t1; +		if ( com_speeds->integer == 3 ) { +			Com_Printf( "SV_PacketEvent time: %i\n", msec ); +		} +	} +} + +/* +================= +Com_EventLoop + +Returns last event time +================= +*/ +int Com_EventLoop( void ) { +	sysEvent_t	ev; +	netadr_t	evFrom; +	byte		bufData[MAX_MSGLEN]; +	msg_t		buf; + +	MSG_Init( &buf, bufData, sizeof( bufData ) ); + +	while ( 1 ) { +		NET_FlushPacketQueue(); +		ev = Com_GetEvent(); + +		// if no more events are available +		if ( ev.evType == SE_NONE ) { +			// manually send packet events for the loopback channel +			while ( NET_GetLoopPacket( NS_CLIENT, &evFrom, &buf ) ) { +				CL_PacketEvent( evFrom, &buf ); +			} + +			while ( NET_GetLoopPacket( NS_SERVER, &evFrom, &buf ) ) { +				// if the server just shut down, flush the events +				if ( com_sv_running->integer ) { +					Com_RunAndTimeServerPacket( &evFrom, &buf ); +				} +			} + +			return ev.evTime; +		} + + +		switch ( ev.evType ) { +		default: +		  // bk001129 - was ev.evTime +			Com_Error( ERR_FATAL, "Com_EventLoop: bad event type %i", ev.evType ); +			break; +        case SE_NONE: +            break; +		case SE_KEY: +			CL_KeyEvent( ev.evValue, ev.evValue2, ev.evTime ); +			break; +		case SE_CHAR: +			CL_CharEvent( ev.evValue ); +			break; +		case SE_MOUSE: +			CL_MouseEvent( ev.evValue, ev.evValue2, ev.evTime ); +			break; +		case SE_JOYSTICK_AXIS: +			CL_JoystickEvent( ev.evValue, ev.evValue2, ev.evTime ); +			break; +		case SE_CONSOLE: +			Cbuf_AddText( (char *)ev.evPtr ); +			Cbuf_AddText( "\n" ); +			break; +		case SE_PACKET: +			// this cvar allows simulation of connections that +			// drop a lot of packets.  Note that loopback connections +			// don't go through here at all. +			if ( com_dropsim->value > 0 ) { +				static int seed; + +				if ( Q_random( &seed ) < com_dropsim->value ) { +					break;		// drop this packet +				} +			} + +			evFrom = *(netadr_t *)ev.evPtr; +			buf.cursize = ev.evPtrLength - sizeof( evFrom ); + +			// we must copy the contents of the message out, because +			// the event buffers are only large enough to hold the +			// exact payload, but channel messages need to be large +			// enough to hold fragment reassembly +			if ( (unsigned)buf.cursize > buf.maxsize ) { +				Com_Printf("Com_EventLoop: oversize packet\n"); +				continue; +			} +			Com_Memcpy( buf.data, (byte *)((netadr_t *)ev.evPtr + 1), buf.cursize ); +			if ( com_sv_running->integer ) { +				Com_RunAndTimeServerPacket( &evFrom, &buf ); +			} else { +				CL_PacketEvent( evFrom, &buf ); +			} +			break; +		} + +		// free any block data +		if ( ev.evPtr ) { +			Z_Free( ev.evPtr ); +		} +	} + +	return 0;	// never reached +} + +/* +================ +Com_Milliseconds + +Can be used for profiling, but will be journaled accurately +================ +*/ +int Com_Milliseconds (void) { +	sysEvent_t	ev; + +	// get events and push them until we get a null event with the current time +	do { + +		ev = Com_GetRealEvent(); +		if ( ev.evType != SE_NONE ) { +			Com_PushEvent( &ev ); +		} +	} while ( ev.evType != SE_NONE ); +	 +	return ev.evTime; +} + +//============================================================================ + +/* +============= +Com_Error_f + +Just throw a fatal error to +test error shutdown procedures +============= +*/ +static void Com_Error_f (void) { +	if ( Cmd_Argc() > 1 ) { +		Com_Error( ERR_DROP, "Testing drop error" ); +	} else { +		Com_Error( ERR_FATAL, "Testing fatal error" ); +	} +} + + +/* +============= +Com_Freeze_f + +Just freeze in place for a given number of seconds to test +error recovery +============= +*/ +static void Com_Freeze_f (void) { +	float	s; +	int		start, now; + +	if ( Cmd_Argc() != 2 ) { +		Com_Printf( "freeze <seconds>\n" ); +		return; +	} +	s = atof( Cmd_Argv(1) ); + +	start = Com_Milliseconds(); + +	while ( 1 ) { +		now = Com_Milliseconds(); +		if ( ( now - start ) * 0.001 > s ) { +			break; +		} +	} +} + +/* +================= +Com_Crash_f + +A way to force a bus error for development reasons +================= +*/ +static void Com_Crash_f( void ) { +	* ( int * ) 0 = 0x12345678; +} + +static void Com_DetectAltivec(void) +{ +	// Only detect if user hasn't forcibly disabled it. +	if (com_altivec->integer) { +		static qboolean altivec = qfalse; +		static qboolean detected = qfalse; +		if (!detected) { +			altivec = Sys_DetectAltivec(); +			detected = qtrue; +		} + +		if (!altivec) { +			Cvar_Set( "com_altivec", "0" );  // we don't have it! Disable support! +		} +	} +} + + +/* +================= +Com_Init +================= +*/ +void Com_Init( char *commandLine ) { +	char	*s; + +	Com_Printf( "%s %s %s\n", SVN_VERSION, PLATFORM_STRING, __DATE__ ); + +	if ( setjmp (abortframe) ) { +		Sys_Error ("Error during initialization"); +	} + +  // bk001129 - do this before anything else decides to push events +  Com_InitPushEvent(); + +	Com_InitSmallZoneMemory(); +	Cvar_Init (); + +	// prepare enough of the subsystems to handle +	// cvar and command buffer management +	Com_ParseCommandLine( commandLine ); + +//	Swap_Init (); +	Cbuf_Init (); + +	Com_InitZoneMemory(); +	Cmd_Init (); + +	// override anything from the config files with command line args +	Com_StartupVariable( NULL ); + +	// get the developer cvar set as early as possible +	Com_StartupVariable( "developer" ); + +	// done early so bind command exists +	CL_InitKeyCommands(); + +	FS_InitFilesystem (); + +	Com_InitJournaling(); + +	Cbuf_AddText ("exec default.cfg\n"); + +	// skip the autogen.cfg if "safe" is on the command line +	if ( !Com_SafeMode() ) { +		Cbuf_AddText ("exec autogen.cfg\n"); +	} + +	Cbuf_AddText ("exec autoexec.cfg\n"); + +	Cbuf_Execute (); + +	// override anything from the config files with command line args +	Com_StartupVariable( NULL ); + +  // get dedicated here for proper hunk megs initialization +#ifdef DEDICATED +	com_dedicated = Cvar_Get ("dedicated", "1", CVAR_ROM); +#else +	com_dedicated = Cvar_Get ("dedicated", "0", CVAR_LATCH); +#endif +	// allocate the stack based hunk allocator +	Com_InitHunkMemory(); + +	// if any archived cvars are modified after this, we will trigger a writing +	// of the config file +	cvar_modifiedFlags &= ~CVAR_ARCHIVE; + +	// +	// init commands and vars +	// +	com_altivec = Cvar_Get ("com_altivec", "1", CVAR_ARCHIVE); +	com_maxfps = Cvar_Get ("com_maxfps", "85", CVAR_ARCHIVE); +	com_blood = Cvar_Get ("com_blood", "1", CVAR_ARCHIVE); + +	com_developer = Cvar_Get ("developer", "0", CVAR_TEMP ); +	com_logfile = Cvar_Get ("logfile", "0", CVAR_TEMP ); + +	com_timescale = Cvar_Get ("timescale", "1", CVAR_CHEAT | CVAR_SYSTEMINFO ); +	com_fixedtime = Cvar_Get ("fixedtime", "0", CVAR_CHEAT); +	com_showtrace = Cvar_Get ("com_showtrace", "0", CVAR_CHEAT); +	com_dropsim = Cvar_Get ("com_dropsim", "0", CVAR_CHEAT); +	com_viewlog = Cvar_Get( "viewlog", "0", CVAR_CHEAT ); +	com_speeds = Cvar_Get ("com_speeds", "0", 0); +	com_timedemo = Cvar_Get ("timedemo", "0", CVAR_CHEAT); +	com_cameraMode = Cvar_Get ("com_cameraMode", "0", CVAR_CHEAT); + +	cl_paused = Cvar_Get ("cl_paused", "0", CVAR_ROM); +	sv_paused = Cvar_Get ("sv_paused", "0", CVAR_ROM); +	cl_packetdelay = Cvar_Get ("cl_packetdelay", "0", CVAR_CHEAT); +	sv_packetdelay = Cvar_Get ("sv_packetdelay", "0", CVAR_CHEAT); +	com_sv_running = Cvar_Get ("sv_running", "0", CVAR_ROM); +	com_cl_running = Cvar_Get ("cl_running", "0", CVAR_ROM); +	com_buildScript = Cvar_Get( "com_buildScript", "0", 0 ); + +#if defined(_WIN32) && defined(_DEBUG) +	com_noErrorInterrupt = Cvar_Get( "com_noErrorInterrupt", "0", 0 ); +#endif + +	if ( com_dedicated->integer ) { +		if ( !com_viewlog->integer ) { +			Cvar_Set( "viewlog", "1" ); +		} +	} + +	if ( com_developer && com_developer->integer ) { +		Cmd_AddCommand ("error", Com_Error_f); +		Cmd_AddCommand ("crash", Com_Crash_f ); +		Cmd_AddCommand ("freeze", Com_Freeze_f); +	} +	Cmd_AddCommand ("quit", Com_Quit_f); +	Cmd_AddCommand ("changeVectors", MSG_ReportChangeVectors_f ); +	Cmd_AddCommand ("writeconfig", Com_WriteConfig_f ); + +	s = va("%s %s %s", Q3_VERSION, PLATFORM_STRING, __DATE__ ); +	com_version = Cvar_Get ("version", s, CVAR_ROM | CVAR_SERVERINFO ); + +	Sys_Init(); +	Netchan_Init( Com_Milliseconds() & 0xffff );	// pick a port value that should be nice and random +	VM_Init(); +	SV_Init(); + +	com_dedicated->modified = qfalse; +	if ( !com_dedicated->integer ) { +		CL_Init(); +		Sys_ShowConsole( com_viewlog->integer, qfalse ); +	} + +	// set com_frameTime so that if a map is started on the +	// command line it will still be able to count on com_frameTime +	// being random enough for a serverid +	com_frameTime = Com_Milliseconds(); + +	// add + commands from command line +	if ( !Com_AddStartupCommands() ) { +		// if the user didn't give any commands, run default action +		if ( !com_dedicated->integer ) { +			Cbuf_AddText ("cinematic splash.RoQ\n"); +		} +	} + +	// start in full screen ui mode +	Cvar_Set("r_uiFullScreen", "1"); + +	CL_StartHunkUsers(); + +	// make sure single player is off by default +	Cvar_Set("ui_singlePlayerActive", "0"); + +	com_fullyInitialized = qtrue; + +	// always set the cvar, but only print the info if it makes sense. +	Com_DetectAltivec(); +	#if idppc +	Com_Printf ("Altivec support is %s\n", com_altivec->integer ? "enabled" : "disabled"); +	#endif + +	Com_Printf ("--- Common Initialization Complete ---\n"); +} + +//================================================================== + +void Com_WriteConfigToFile( const char *filename ) { +	fileHandle_t	f; + +	f = FS_FOpenFileWrite( filename ); +	if ( !f ) { +		Com_Printf ("Couldn't write %s.\n", filename ); +		return; +	} + +	FS_Printf (f, "// generated by tremulous, do not modify\n"); +	Key_WriteBindings (f); +	Cvar_WriteVariables (f); +	FS_FCloseFile( f ); +} + + +/* +=============== +Com_WriteConfiguration + +Writes key bindings and archived cvars to config file if modified +=============== +*/ +void Com_WriteConfiguration( void ) { +	// if we are quiting without fully initializing, make sure +	// we don't write out anything +	if ( !com_fullyInitialized ) { +		return; +	} + +	if ( !(cvar_modifiedFlags & CVAR_ARCHIVE ) ) { +		return; +	} +	cvar_modifiedFlags &= ~CVAR_ARCHIVE; + +	Com_WriteConfigToFile( "autogen.cfg" ); +} + + +/* +=============== +Com_WriteConfig_f + +Write the config file to a specific name +=============== +*/ +void Com_WriteConfig_f( void ) { +	char	filename[MAX_QPATH]; + +	if ( Cmd_Argc() != 2 ) { +		Com_Printf( "Usage: writeconfig <filename>\n" ); +		return; +	} + +	Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) ); +	COM_DefaultExtension( filename, sizeof( filename ), ".cfg" ); +	Com_Printf( "Writing %s.\n", filename ); +	Com_WriteConfigToFile( filename ); +} + +/* +================ +Com_ModifyMsec +================ +*/ +int Com_ModifyMsec( int msec ) { +	int		clampTime; + +	// +	// modify time for debugging values +	// +	if ( com_fixedtime->integer ) { +		msec = com_fixedtime->integer; +	} else if ( com_timescale->value ) { +		msec *= com_timescale->value; +	} else if (com_cameraMode->integer) { +		msec *= com_timescale->value; +	} +	 +	// don't let it scale below 1 msec +	if ( msec < 1 && com_timescale->value) { +		msec = 1; +	} + +	if ( com_dedicated->integer ) { +		// dedicated servers don't want to clamp for a much longer +		// period, because it would mess up all the client's views +		// of time. +		if (com_sv_running->integer && msec > 500) +			Com_Printf( "Hitch warning: %i msec frame time\n", msec ); + +		clampTime = 5000; +	} else  +	if ( !com_sv_running->integer ) { +		// clients of remote servers do not want to clamp time, because +		// it would skew their view of the server's time temporarily +		clampTime = 5000; +	} else { +		// for local single player gaming +		// we may want to clamp the time to prevent players from +		// flying off edges when something hitches. +		clampTime = 200; +	} + +	if ( msec > clampTime ) { +		msec = clampTime; +	} + +	return msec; +} + +/* +================= +Com_Frame +================= +*/ +void Com_Frame( void ) { + +	int		msec, minMsec; +	static int	lastTime; +	int key; +  +	int		timeBeforeFirstEvents; +	int           timeBeforeServer; +	int           timeBeforeEvents; +	int           timeBeforeClient; +	int           timeAfter; +   + + + + +	if ( setjmp (abortframe) ) { +		return;			// an ERR_DROP was thrown +	} + +	// bk001204 - init to zero. +	//  also:  might be clobbered by `longjmp' or `vfork' +	timeBeforeFirstEvents =0; +	timeBeforeServer =0; +	timeBeforeEvents =0; +	timeBeforeClient = 0; +	timeAfter = 0; + + +	// old net chan encryption key +	key = 0x87243987; + +	// write config file if anything changed +	Com_WriteConfiguration();  + +	// if "viewlog" has been modified, show or hide the log console +	if ( com_viewlog->modified ) { +		if ( !com_dedicated->value ) { +			Sys_ShowConsole( com_viewlog->integer, qfalse ); +		} +		com_viewlog->modified = qfalse; +	} + +	// +	// main event loop +	// +	if ( com_speeds->integer ) { +		timeBeforeFirstEvents = Sys_Milliseconds (); +	} + +	// we may want to spin here if things are going too fast +	if ( !com_dedicated->integer && com_maxfps->integer > 0 && !com_timedemo->integer ) { +		minMsec = 1000 / com_maxfps->integer; +	} else { +		minMsec = 1; +	} +	do { +		com_frameTime = Com_EventLoop(); +		if ( lastTime > com_frameTime ) { +			lastTime = com_frameTime;		// possible on first frame +		} +		msec = com_frameTime - lastTime; +	} while ( msec < minMsec ); +	Cbuf_Execute (); + +	if (com_altivec->modified) +	{ +		Com_DetectAltivec(); +		com_altivec->modified = qfalse; +	} + +	lastTime = com_frameTime; + +	// mess with msec if needed +	com_frameMsec = msec; +	msec = Com_ModifyMsec( msec ); + +	// +	// server side +	// +	if ( com_speeds->integer ) { +		timeBeforeServer = Sys_Milliseconds (); +	} + +	SV_Frame( msec ); + +	// if "dedicated" has been modified, start up +	// or shut down the client system. +	// Do this after the server may have started, +	// but before the client tries to auto-connect +	if ( com_dedicated->modified ) { +		// get the latched value +		Cvar_Get( "dedicated", "0", 0 ); +		com_dedicated->modified = qfalse; +		if ( !com_dedicated->integer ) { +			CL_Init(); +			Sys_ShowConsole( com_viewlog->integer, qfalse ); +		} else { +			CL_Shutdown(); +			Sys_ShowConsole( 1, qtrue ); +		} +	} + +	// +	// client system +	// +	if ( !com_dedicated->integer ) { +		// +		// run event loop a second time to get server to client packets +		// without a frame of latency +		// +		if ( com_speeds->integer ) { +			timeBeforeEvents = Sys_Milliseconds (); +		} +		Com_EventLoop(); +		Cbuf_Execute (); + + +		// +		// client side +		// +		if ( com_speeds->integer ) { +			timeBeforeClient = Sys_Milliseconds (); +		} + +		CL_Frame( msec ); + +		if ( com_speeds->integer ) { +			timeAfter = Sys_Milliseconds (); +		} +	} + +	// +	// report timing information +	// +	if ( com_speeds->integer ) { +		int			all, sv, ev, cl; + +		all = timeAfter - timeBeforeServer; +		sv = timeBeforeEvents - timeBeforeServer; +		ev = timeBeforeServer - timeBeforeFirstEvents + timeBeforeClient - timeBeforeEvents; +		cl = timeAfter - timeBeforeClient; +		sv -= time_game; +		cl -= time_frontend + time_backend; + +		Com_Printf ("frame:%i all:%3i sv:%3i ev:%3i cl:%3i gm:%3i rf:%3i bk:%3i\n",  +					 com_frameNumber, all, sv, ev, cl, time_game, time_frontend, time_backend ); +	}	 + +	// +	// trace optimization tracking +	// +	if ( com_showtrace->integer ) { +	 +		extern	int c_traces, c_brush_traces, c_patch_traces; +		extern	int	c_pointcontents; + +		Com_Printf ("%4i traces  (%ib %ip) %4i points\n", c_traces, +			c_brush_traces, c_patch_traces, c_pointcontents); +		c_traces = 0; +		c_brush_traces = 0; +		c_patch_traces = 0; +		c_pointcontents = 0; +	} + +	// old net chan encryption key +	key = lastTime * 0x87243987; + +	com_frameNumber++; +} + +/* +================= +Com_Shutdown +================= +*/ +void Com_Shutdown (void) { +	if (logfile) { +		FS_FCloseFile (logfile); +		logfile = 0; +	} + +	if ( com_journalFile ) { +		FS_FCloseFile( com_journalFile ); +		com_journalFile = 0; +	} + +} + +//------------------------------------------------------------------------ + + +/* +===================== +Q_acos + +the msvc acos doesn't always return a value between -PI and PI: + +int i; +i = 1065353246; +acos(*(float*) &i) == -1.#IND0 + +	This should go in q_math but it is too late to add new traps +	to game and ui +===================== +*/ +float Q_acos(float c) { +	float angle; + +	angle = acos(c); + +	if (angle > M_PI) { +		return (float)M_PI; +	} +	if (angle < -M_PI) { +		return (float)M_PI; +	} +	return angle; +} + +/* +=========================================== +command line completion +=========================================== +*/ + +/* +================== +Field_Clear +================== +*/ +void Field_Clear( field_t *edit ) { +  memset(edit->buffer, 0, MAX_EDIT_LINE); +	edit->cursor = 0; +	edit->scroll = 0; +} + +static const char *completionString; +static char shortestMatch[MAX_TOKEN_CHARS]; +static int	matchCount; +// field we are working on, passed to Field_AutoComplete(&g_consoleCommand for instance) +static field_t *completionField; + +/* +=============== +FindMatches + +=============== +*/ +static void FindMatches( const char *s ) { +	int		i; + +	if ( Q_stricmpn( s, completionString, strlen( completionString ) ) ) { +		return; +	} +	matchCount++; +	if ( matchCount == 1 ) { +		Q_strncpyz( shortestMatch, s, sizeof( shortestMatch ) ); +		return; +	} + +	// cut shortestMatch to the amount common with s +	for ( i = 0 ; shortestMatch[i] ; i++ ) { +		if ( i >= strlen( s ) ) { +			shortestMatch[i] = 0; +			break; +		} + +		if ( tolower(shortestMatch[i]) != tolower(s[i]) ) { +			shortestMatch[i] = 0; +		} +	} +} + +/* +=============== +PrintMatches + +=============== +*/ +static void PrintMatches( const char *s ) { +	if ( !Q_stricmpn( s, shortestMatch, strlen( shortestMatch ) ) ) { +		Com_Printf( "    %s\n", s ); +	} +} + +/* +=============== +PrintCvarMatches + +=============== +*/ +static void PrintCvarMatches( const char *s ) { +	char value[ TRUNCATE_LENGTH ]; + +	if ( !Q_stricmpn( s, shortestMatch, strlen( shortestMatch ) ) ) { +		Com_TruncateLongString( value, Cvar_VariableString( s ) ); +		Com_Printf( "    %s = \"%s\"\n", s, value ); +	} +} + +/* +=============== +Field_FindFirstSeparator +=============== +*/ +static char *Field_FindFirstSeparator( char *s ) +{ +	int i; + +	for( i = 0; i < strlen( s ); i++ ) +	{ +		if( s[ i ] == ';' ) +			return &s[ i ]; +	} + +	return NULL; +} + +/* +=============== +Field_CompleteFilename +=============== +*/ +static void Field_CompleteFilename( const char *dir, +		const char *ext, qboolean stripExt ) +{ +	matchCount = 0; +	shortestMatch[ 0 ] = 0; + +	FS_FilenameCompletion( dir, ext, stripExt, FindMatches ); + +	if( matchCount == 0 ) +		return; + +	Q_strcat( completionField->buffer, sizeof( completionField->buffer ), +			shortestMatch + strlen( completionString ) ); +	completionField->cursor = strlen( completionField->buffer ); + +	if( matchCount == 1 ) +	{ +		Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " ); +		completionField->cursor++; +		return; +	} + +	Com_Printf( "]%s\n", completionField->buffer ); +	 +	FS_FilenameCompletion( dir, ext, stripExt, PrintMatches ); +} + +/* +=============== +Field_CompleteCommand +=============== +*/ +static void Field_CompleteCommand( char *cmd, +		qboolean doCommands, qboolean doCvars ) +{ +	int		completionArgument = 0; +	char	*p; + +	// Skip leading whitespace and quotes +	cmd = Com_SkipCharset( cmd, " \"" ); + +	Cmd_TokenizeStringIgnoreQuotes( cmd ); +	completionArgument = Cmd_Argc( ); + +	// If there is trailing whitespace on the cmd +	if( *( cmd + strlen( cmd ) - 1 ) == ' ' ) +	{ +		completionString = ""; +		completionArgument++; +	} +	else +		completionString = Cmd_Argv( completionArgument - 1 ); + +	if( completionArgument > 1 ) +	{ +		const char *baseCmd = Cmd_Argv( 0 ); + +#ifndef DEDICATED +		// If the very first token does not have a leading \ or /, +		// refuse to autocomplete +		if( cmd == completionField->buffer ) +		{ +			if( baseCmd[ 0 ] != '\\' && baseCmd[ 0 ] != '/' ) +				return; + +			baseCmd++; +		} +#endif + +		if( ( p = Field_FindFirstSeparator( cmd ) ) ) +		{ +			// Compound command +			Field_CompleteCommand( p + 1, qtrue, qtrue ); +		} +		else +		{ +			// FIXME: all this junk should really be associated with the respective +			// commands, instead of being hard coded here +			if( ( !Q_stricmp( baseCmd, "map" ) || +						!Q_stricmp( baseCmd, "devmap" ) || +						!Q_stricmp( baseCmd, "spmap" ) || +						!Q_stricmp( baseCmd, "spdevmap" ) ) && +					completionArgument == 2 ) +			{ +				Field_CompleteFilename( "maps", "bsp", qtrue ); +			} +			else if( ( !Q_stricmp( baseCmd, "exec" ) || +						!Q_stricmp( baseCmd, "writeconfig" ) ) && +					completionArgument == 2 ) +			{ +				Field_CompleteFilename( "", "cfg", qfalse ); +			} +			else if( !Q_stricmp( baseCmd, "condump" ) && +					completionArgument == 2 ) +			{ +				Field_CompleteFilename( "", "txt", qfalse ); +			} +			else if( !Q_stricmp( baseCmd, "demo" ) && completionArgument == 2 ) +			{ +				char demoExt[ 16 ]; + +				Com_sprintf( demoExt, sizeof( demoExt ), ".dm_%d", PROTOCOL_VERSION ); +				Field_CompleteFilename( "demos", demoExt, qtrue ); +			} +			else if( ( !Q_stricmp( baseCmd, "toggle" ) || +						!Q_stricmp( baseCmd, "vstr" ) || +						!Q_stricmp( baseCmd, "set" ) || +						!Q_stricmp( baseCmd, "seta" ) || +						!Q_stricmp( baseCmd, "setu" ) || +						!Q_stricmp( baseCmd, "sets" ) ) && +					completionArgument == 2 ) +			{ +				// Skip "<cmd> " +				p = Com_SkipTokens( cmd, 1, " " ); + +				if( p > cmd ) +					Field_CompleteCommand( p, qfalse, qtrue ); +			} +			else if( !Q_stricmp( baseCmd, "rcon" ) && completionArgument == 2 ) +			{ +				// Skip "rcon " +				p = Com_SkipTokens( cmd, 1, " " ); + +				if( p > cmd ) +					Field_CompleteCommand( p, qtrue, qtrue ); +			} +			else if( !Q_stricmp( baseCmd, "bind" ) && completionArgument >= 3 ) +			{ +				// Skip "bind <key> " +				p = Com_SkipTokens( cmd, 2, " " ); + +				if( p > cmd ) +					Field_CompleteCommand( p, qtrue, qtrue ); +			} +		} +	} +	else +	{ +		if( completionString[0] == '\\' || completionString[0] == '/' ) +			completionString++; + +		matchCount = 0; +		shortestMatch[ 0 ] = 0; + +		if( strlen( completionString ) == 0 ) +			return; + +		if( doCommands ) +			Cmd_CommandCompletion( FindMatches ); + +		if( doCvars ) +			Cvar_CommandCompletion( FindMatches ); + +		if( matchCount == 0 ) +			return;	// no matches + +		if( cmd == completionField->buffer ) +		{ +#ifndef DEDICATED +			Com_sprintf( completionField->buffer, +					sizeof( completionField->buffer ), "\\%s", shortestMatch ); +#else +			Com_sprintf( completionField->buffer, +					sizeof( completionField->buffer ), "%s", shortestMatch ); +#endif +		} +		else +		{ +			Q_strcat( completionField->buffer, sizeof( completionField->buffer ), +					shortestMatch + strlen( completionString ) ); +		} + +		completionField->cursor = strlen( completionField->buffer ); + +		if( matchCount == 1 ) +		{ +			Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " ); +			completionField->cursor++; +			return; +		} + +		Com_Printf( "]%s\n", completionField->buffer ); + +		// run through again, printing matches +		if( doCommands ) +			Cmd_CommandCompletion( PrintMatches ); + +		if( doCvars ) +			Cvar_CommandCompletion( PrintCvarMatches ); +	} +} + +/* +=============== +Field_AutoComplete + +Perform Tab expansion +=============== +*/ +void Field_AutoComplete( field_t *field ) +{ +	completionField = field; + +	Field_CompleteCommand( completionField->buffer, qtrue, qtrue ); +} + +/* +================== +Com_RandomBytes + +fills string array with len radom bytes, peferably from the OS randomizer +================== +*/ +void Com_RandomBytes( byte *string, int len ) +{ +	int i; + +	if( Sys_RandomBytes( string, len ) ) +		return; + +	Com_Printf( "Com_RandomBytes: using weak randomization\n" ); +	srand( time( 0 ) ); +	for( i = 0; i < len; i++ ) +		string[i] = (unsigned char)( rand() % 255 ); +} diff --git a/src/qcommon/cvar.c b/src/qcommon/cvar.c new file mode 100644 index 0000000..91d3dfb --- /dev/null +++ b/src/qcommon/cvar.c @@ -0,0 +1,947 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ +// cvar.c -- dynamic variable tracking + +#include "q_shared.h" +#include "qcommon.h" + +cvar_t		*cvar_vars; +cvar_t		*cvar_cheats; +int			cvar_modifiedFlags; + +#define	MAX_CVARS	1024 +cvar_t		cvar_indexes[MAX_CVARS]; +int			cvar_numIndexes; + +#define FILE_HASH_SIZE		256 +static	cvar_t*		hashTable[FILE_HASH_SIZE]; + +cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force); + +/* +================ +return a hash value for the filename +================ +*/ +static long generateHashValue( const char *fname ) { +	int		i; +	long	hash; +	char	letter; + +	hash = 0; +	i = 0; +	while (fname[i] != '\0') { +		letter = tolower(fname[i]); +		hash+=(long)(letter)*(i+119); +		i++; +	} +	hash &= (FILE_HASH_SIZE-1); +	return hash; +} + +/* +============ +Cvar_ValidateString +============ +*/ +static qboolean Cvar_ValidateString( const char *s ) { +	if ( !s ) { +		return qfalse; +	} +	if ( strchr( s, '\\' ) ) { +		return qfalse; +	} +	if ( strchr( s, '\"' ) ) { +		return qfalse; +	} +	if ( strchr( s, ';' ) ) { +		return qfalse; +	} +	return qtrue; +} + +/* +============ +Cvar_FindVar +============ +*/ +static cvar_t *Cvar_FindVar( const char *var_name ) { +	cvar_t	*var; +	long hash; + +	hash = generateHashValue(var_name); +	 +	for (var=hashTable[hash] ; var ; var=var->hashNext) { +		if (!Q_stricmp(var_name, var->name)) { +			return var; +		} +	} + +	return NULL; +} + +/* +============ +Cvar_VariableValue +============ +*/ +float Cvar_VariableValue( const char *var_name ) { +	cvar_t	*var; +	 +	var = Cvar_FindVar (var_name); +	if (!var) +		return 0; +	return var->value; +} + + +/* +============ +Cvar_VariableIntegerValue +============ +*/ +int Cvar_VariableIntegerValue( const char *var_name ) { +	cvar_t	*var; +	 +	var = Cvar_FindVar (var_name); +	if (!var) +		return 0; +	return var->integer; +} + + +/* +============ +Cvar_VariableString +============ +*/ +char *Cvar_VariableString( const char *var_name ) { +	cvar_t *var; +	 +	var = Cvar_FindVar (var_name); +	if (!var) +		return ""; +	return var->string; +} + + +/* +============ +Cvar_VariableStringBuffer +============ +*/ +void Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { +	cvar_t *var; +	 +	var = Cvar_FindVar (var_name); +	if (!var) { +		*buffer = 0; +	} +	else { +		Q_strncpyz( buffer, var->string, bufsize ); +	} +} + +/* +============ +Cvar_Flags +============ +*/ +int Cvar_Flags(const char *var_name) +{ +	cvar_t *var; +	 +	if(! (var = Cvar_FindVar(var_name)) ) +		return CVAR_NONEXISTENT; +	else +		return var->flags; +} + +/* +============ +Cvar_CommandCompletion +============ +*/ +void	Cvar_CommandCompletion( void(*callback)(const char *s) ) { +	cvar_t		*cvar; +	 +	for ( cvar = cvar_vars ; cvar ; cvar = cvar->next ) { +		callback( cvar->name ); +	} +} + + +/* +============ +Cvar_Get + +If the variable already exists, the value will not be set unless CVAR_ROM +The flags will be or'ed in if the variable exists. +============ +*/ +cvar_t *Cvar_Get( const char *var_name, const char *var_value, int flags ) { +	cvar_t	*var; +	long	hash; + +  if ( !var_name || ! var_value ) { +		Com_Error( ERR_FATAL, "Cvar_Get: NULL parameter" ); +  } + +	if ( !Cvar_ValidateString( var_name ) ) { +		Com_Printf("invalid cvar name string: %s\n", var_name ); +		var_name = "BADNAME"; +	} + +#if 0		// FIXME: values with backslash happen +	if ( !Cvar_ValidateString( var_value ) ) { +		Com_Printf("invalid cvar value string: %s\n", var_value ); +		var_value = "BADVALUE"; +	} +#endif + +	var = Cvar_FindVar (var_name); +	if ( var ) { +		// if the C code is now specifying a variable that the user already +		// set a value for, take the new value as the reset value +		if ( ( var->flags & CVAR_USER_CREATED ) && !( flags & CVAR_USER_CREATED ) +			&& var_value[0] ) { +			var->flags &= ~CVAR_USER_CREATED; +			Z_Free( var->resetString ); +			var->resetString = CopyString( var_value ); + +			// ZOID--needs to be set so that cvars the game sets as  +			// SERVERINFO get sent to clients +			cvar_modifiedFlags |= flags; +		} + +		var->flags |= flags; +		// only allow one non-empty reset string without a warning +		if ( !var->resetString[0] ) { +			// we don't have a reset string yet +			Z_Free( var->resetString ); +			var->resetString = CopyString( var_value ); +		} else if ( var_value[0] && strcmp( var->resetString, var_value ) ) { +			Com_DPrintf( "Warning: cvar \"%s\" given initial values: \"%s\" and \"%s\"\n", +				var_name, var->resetString, var_value ); +		} +		// if we have a latched string, take that value now +		if ( var->latchedString ) { +			char *s; + +			s = var->latchedString; +			var->latchedString = NULL;	// otherwise cvar_set2 would free it +			Cvar_Set2( var_name, s, qtrue ); +			Z_Free( s ); +		} + +// use a CVAR_SET for rom sets, get won't override +#if 0 +		// CVAR_ROM always overrides +		if ( flags & CVAR_ROM ) { +			Cvar_Set2( var_name, var_value, qtrue ); +		} +#endif +		return var; +	} + +	// +	// allocate a new cvar +	// +	if ( cvar_numIndexes >= MAX_CVARS ) { +		Com_Error( ERR_FATAL, "MAX_CVARS" ); +	} +	var = &cvar_indexes[cvar_numIndexes]; +	cvar_numIndexes++; +	var->name = CopyString (var_name); +	var->string = CopyString (var_value); +	var->modified = qtrue; +	var->modificationCount = 1; +	var->value = atof (var->string); +	var->integer = atoi(var->string); +	var->resetString = CopyString( var_value ); + +	// link the variable in +	var->next = cvar_vars; +	cvar_vars = var; + +	var->flags = flags; + +	hash = generateHashValue(var_name); +	var->hashNext = hashTable[hash]; +	hashTable[hash] = var; + +	return var; +} + +/* +============ +Cvar_Set2 +============ +*/ +cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force ) { +	cvar_t	*var; + +//	Com_DPrintf( "Cvar_Set2: %s %s\n", var_name, value ); + +	if ( !Cvar_ValidateString( var_name ) ) { +		Com_Printf("invalid cvar name string: %s\n", var_name ); +		var_name = "BADNAME"; +	} + +#if 0	// FIXME +	if ( value && !Cvar_ValidateString( value ) ) { +		Com_Printf("invalid cvar value string: %s\n", value ); +		var_value = "BADVALUE"; +	} +#endif + +	var = Cvar_FindVar (var_name); +	if (!var) { +		if ( !value ) { +			return NULL; +		} +		// create it +		if ( !force ) { +			return Cvar_Get( var_name, value, CVAR_USER_CREATED ); +		} else { +			return Cvar_Get (var_name, value, 0); +		} +	} + +	if (!value ) { +		value = var->resetString; +	} + +	if((var->flags & CVAR_LATCH) && var->latchedString) { +		if(!strcmp(value,var->latchedString)) +			return var; +	} +	else if (!strcmp(value,var->string)) { +		return var; +	} +	// note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo) +	cvar_modifiedFlags |= var->flags; + +	if (!force) +	{ +		if (var->flags & CVAR_ROM) +		{ +			Com_Printf ("%s is read only.\n", var_name); +			return var; +		} + +		if (var->flags & CVAR_INIT) +		{ +			Com_Printf ("%s is write protected.\n", var_name); +			return var; +		} + +		if (var->flags & CVAR_LATCH) +		{ +			if (var->latchedString) +			{ +				if (strcmp(value, var->latchedString) == 0) +					return var; +				Z_Free (var->latchedString); +			} +			else +			{ +				if (strcmp(value, var->string) == 0) +					return var; +			} + +			Com_Printf ("%s will be changed upon restarting.\n", var_name); +			var->latchedString = CopyString(value); +			var->modified = qtrue; +			var->modificationCount++; +			return var; +		} + +		if ( (var->flags & CVAR_CHEAT) && !cvar_cheats->integer ) +		{ +			Com_Printf ("%s is cheat protected.\n", var_name); +			return var; +		} + +	} +	else +	{ +		if (var->latchedString) +		{ +			Z_Free (var->latchedString); +			var->latchedString = NULL; +		} +	} + +	if (!strcmp(value, var->string)) +		return var;		// not changed + +	var->modified = qtrue; +	var->modificationCount++; +	 +	Z_Free (var->string);	// free the old value string +	 +	var->string = CopyString(value); +	var->value = atof (var->string); +	var->integer = atoi (var->string); + +	return var; +} + +/* +============ +Cvar_Set +============ +*/ +void Cvar_Set( const char *var_name, const char *value) { +	Cvar_Set2 (var_name, value, qtrue); +} + +/* +============ +Cvar_SetLatched +============ +*/ +void Cvar_SetLatched( const char *var_name, const char *value) { +	Cvar_Set2 (var_name, value, qfalse); +} + +/* +============ +Cvar_SetValue +============ +*/ +void Cvar_SetValue( const char *var_name, float value) { +	char	val[32]; + +	if ( value == (int)value ) { +		Com_sprintf (val, sizeof(val), "%i",(int)value); +	} else { +		Com_sprintf (val, sizeof(val), "%f",value); +	} +	Cvar_Set (var_name, val); +} + + +/* +============ +Cvar_Reset +============ +*/ +void Cvar_Reset( const char *var_name ) { +	Cvar_Set2( var_name, NULL, qfalse ); +} + +/* +============ +Cvar_ForceReset +============ +*/ +void Cvar_ForceReset(const char *var_name) +{ +	Cvar_Set2(var_name, NULL, qtrue); +} + +/* +============ +Cvar_SetCheatState + +Any testing variables will be reset to the safe values +============ +*/ +void Cvar_SetCheatState( void ) { +	cvar_t	*var; + +	// set all default vars to the safe value +	for ( var = cvar_vars ; var ; var = var->next ) { +		if ( var->flags & CVAR_CHEAT ) { +      // the CVAR_LATCHED|CVAR_CHEAT vars might escape the reset here  +      // because of a different var->latchedString +      if (var->latchedString) +      { +        Z_Free(var->latchedString); +        var->latchedString = NULL; +      } +			if (strcmp(var->resetString,var->string)) { +        Cvar_Set( var->name, var->resetString ); +			} +		} +	} +} + +/* +============ +Cvar_Command + +Handles variable inspection and changing from the console +============ +*/ +qboolean Cvar_Command( void ) { +	cvar_t	*v; +	char		string[ TRUNCATE_LENGTH ]; +	char		resetString[ TRUNCATE_LENGTH ]; +	char		latchedString[ TRUNCATE_LENGTH ]; + +	// check variables +	v = Cvar_FindVar (Cmd_Argv(0)); +	if (!v) { +		return qfalse; +	} + +	// perform a variable print or set +	if ( Cmd_Argc() == 1 ) { +		Com_TruncateLongString( string, v->string ); +		Com_TruncateLongString( resetString, v->resetString ); +		Com_Printf ("\"%s\" is:\"%s" S_COLOR_WHITE "\" default:\"%s" S_COLOR_WHITE "\"\n", +				v->name, string, resetString ); +		if ( v->latchedString ) { +			Com_TruncateLongString( latchedString, v->latchedString ); +			Com_Printf( "latched: \"%s\"\n", latchedString ); +		} +		return qtrue; +	} + +	// set the value if forcing isn't required +	Cvar_Set2 (v->name, Cmd_Argv(1), qfalse); +	return qtrue; +} + + +/* +============ +Cvar_Toggle_f + +Toggles a cvar for easy single key binding +============ +*/ +void Cvar_Toggle_f( void ) { +	int		v; + +	if ( Cmd_Argc() != 2 ) { +		Com_Printf ("usage: toggle <variable>\n"); +		return; +	} + +	v = Cvar_VariableValue( Cmd_Argv( 1 ) ); +	v = !v; + +	Cvar_Set2 (Cmd_Argv(1), va("%i", v), qfalse); +} + +/* +============ +Cvar_Set_f + +Allows setting and defining of arbitrary cvars from console, even if they +weren't declared in C code. +============ +*/ +void Cvar_Set_f( void ) { +	int		i, c, l, len; +	char	combined[MAX_STRING_TOKENS]; + +	c = Cmd_Argc(); +	if ( c < 3 ) { +		Com_Printf ("usage: set <variable> <value>\n"); +		return; +	} + +	combined[0] = 0; +	l = 0; +	for ( i = 2 ; i < c ; i++ ) { +		len = strlen ( Cmd_Argv( i ) + 1 ); +		if ( l + len >= MAX_STRING_TOKENS - 2 ) { +			break; +		} +		strcat( combined, Cmd_Argv( i ) ); +		if ( i != c-1 ) { +			strcat( combined, " " ); +		} +		l += len; +	} +	Cvar_Set2 (Cmd_Argv(1), combined, qfalse); +} + +/* +============ +Cvar_SetU_f + +As Cvar_Set, but also flags it as userinfo +============ +*/ +void Cvar_SetU_f( void ) { +	cvar_t	*v; + +	if ( Cmd_Argc() != 3 ) { +		Com_Printf ("usage: setu <variable> <value>\n"); +		return; +	} +	Cvar_Set_f(); +	v = Cvar_FindVar( Cmd_Argv( 1 ) ); +	if ( !v ) { +		return; +	} +	v->flags |= CVAR_USERINFO; +} + +/* +============ +Cvar_SetS_f + +As Cvar_Set, but also flags it as userinfo +============ +*/ +void Cvar_SetS_f( void ) { +	cvar_t	*v; + +	if ( Cmd_Argc() != 3 ) { +		Com_Printf ("usage: sets <variable> <value>\n"); +		return; +	} +	Cvar_Set_f(); +	v = Cvar_FindVar( Cmd_Argv( 1 ) ); +	if ( !v ) { +		return; +	} +	v->flags |= CVAR_SERVERINFO; +} + +/* +============ +Cvar_SetA_f + +As Cvar_Set, but also flags it as archived +============ +*/ +void Cvar_SetA_f( void ) { +	cvar_t	*v; + +	if ( Cmd_Argc() != 3 ) { +		Com_Printf ("usage: seta <variable> <value>\n"); +		return; +	} +	Cvar_Set_f(); +	v = Cvar_FindVar( Cmd_Argv( 1 ) ); +	if ( !v ) { +		return; +	} +	v->flags |= CVAR_ARCHIVE; +} + +/* +============ +Cvar_Reset_f +============ +*/ +void Cvar_Reset_f( void ) { +	if ( Cmd_Argc() != 2 ) { +		Com_Printf ("usage: reset <variable>\n"); +		return; +	} +	Cvar_Reset( Cmd_Argv( 1 ) ); +} + +/* +============ +Cvar_WriteVariables + +Appends lines containing "set variable value" for all variables +with the archive flag set to qtrue. +============ +*/ +void Cvar_WriteVariables( fileHandle_t f ) { +	cvar_t	*var; +	char	buffer[1024]; + +	for (var = cvar_vars ; var ; var = var->next) { +		if( var->flags & CVAR_ARCHIVE ) { +			// write the latched value, even if it hasn't taken effect yet +			if ( var->latchedString ) { +				if( strlen( var->name ) + strlen( var->latchedString ) + 10 > sizeof( buffer ) ) { +					Com_Printf( S_COLOR_YELLOW "WARNING: value of variable " +							"\"%s\" too long to write to file\n", var->name ); +					continue; +				} +				Com_sprintf (buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->latchedString); +			} else { +				if( strlen( var->name ) + strlen( var->string ) + 10 > sizeof( buffer ) ) { +					Com_Printf( S_COLOR_YELLOW "WARNING: value of variable " +							"\"%s\" too long to write to file\n", var->name ); +					continue; +				} +				Com_sprintf (buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->string); +			} +			FS_Write( buffer, strlen( buffer ), f ); +		} +	} +} + +/* +============ +Cvar_List_f +============ +*/ +void Cvar_List_f( void ) { +	cvar_t	*var; +	int		i; +	char	*match; + +	if ( Cmd_Argc() > 1 ) { +		match = Cmd_Argv( 1 ); +	} else { +		match = NULL; +	} + +	i = 0; +	for (var = cvar_vars ; var ; var = var->next, i++) +	{ +		if (match && !Com_Filter(match, var->name, qfalse)) continue; + +		if (var->flags & CVAR_SERVERINFO) { +			Com_Printf("S"); +		} else { +			Com_Printf(" "); +		} +		if (var->flags & CVAR_USERINFO) { +			Com_Printf("U"); +		} else { +			Com_Printf(" "); +		} +		if (var->flags & CVAR_ROM) { +			Com_Printf("R"); +		} else { +			Com_Printf(" "); +		} +		if (var->flags & CVAR_INIT) { +			Com_Printf("I"); +		} else { +			Com_Printf(" "); +		} +		if (var->flags & CVAR_ARCHIVE) { +			Com_Printf("A"); +		} else { +			Com_Printf(" "); +		} +		if (var->flags & CVAR_LATCH) { +			Com_Printf("L"); +		} else { +			Com_Printf(" "); +		} +		if (var->flags & CVAR_CHEAT) { +			Com_Printf("C"); +		} else { +			Com_Printf(" "); +		} + +		Com_Printf (" %s \"%s\"\n", var->name, var->string); +	} + +	Com_Printf ("\n%i total cvars\n", i); +	Com_Printf ("%i cvar indexes\n", cvar_numIndexes); +} + +/* +============ +Cvar_Restart_f + +Resets all cvars to their hardcoded values +============ +*/ +void Cvar_Restart_f( void ) { +	cvar_t	*var; +	cvar_t	**prev; + +	prev = &cvar_vars; +	while ( 1 ) { +		var = *prev; +		if ( !var ) { +			break; +		} + +		// don't mess with rom values, or some inter-module +		// communication will get broken (com_cl_running, etc) +		if ( var->flags & ( CVAR_ROM | CVAR_INIT | CVAR_NORESTART ) ) { +			prev = &var->next; +			continue; +		} + +		// throw out any variables the user created +		if ( var->flags & CVAR_USER_CREATED ) { +			*prev = var->next; +			if ( var->name ) { +				Z_Free( var->name ); +			} +			if ( var->string ) { +				Z_Free( var->string ); +			} +			if ( var->latchedString ) { +				Z_Free( var->latchedString ); +			} +			if ( var->resetString ) { +				Z_Free( var->resetString ); +			} +			// clear the var completely, since we +			// can't remove the index from the list +			Com_Memset( var, 0, sizeof( var ) ); +			continue; +		} + +		Cvar_Set( var->name, var->resetString ); + +		prev = &var->next; +	} +} + + + +/* +===================== +Cvar_InfoString +===================== +*/ +char	*Cvar_InfoString( int bit ) { +	static char	info[MAX_INFO_STRING]; +	cvar_t	*var; + +	info[0] = 0; + +	for (var = cvar_vars ; var ; var = var->next) { +		if (var->flags & bit) { +			Info_SetValueForKey (info, var->name, var->string); +		} +	} +	return info; +} + +/* +===================== +Cvar_InfoString_Big + +  handles large info strings ( CS_SYSTEMINFO ) +===================== +*/ +char	*Cvar_InfoString_Big( int bit ) { +	static char	info[BIG_INFO_STRING]; +	cvar_t	*var; + +	info[0] = 0; + +	for (var = cvar_vars ; var ; var = var->next) { +		if (var->flags & bit) { +			Info_SetValueForKey_Big (info, var->name, var->string); +		} +	} +	return info; +} + + + +/* +===================== +Cvar_InfoStringBuffer +===================== +*/ +void Cvar_InfoStringBuffer( int bit, char* buff, int buffsize ) { +	Q_strncpyz(buff,Cvar_InfoString(bit),buffsize); +} + +/* +===================== +Cvar_Register + +basically a slightly modified Cvar_Get for the interpreted modules +===================== +*/ +void	Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ) { +	cvar_t	*cv; + +	cv = Cvar_Get( varName, defaultValue, flags ); +	if ( !vmCvar ) { +		return; +	} +	vmCvar->handle = cv - cvar_indexes; +	vmCvar->modificationCount = -1; +	Cvar_Update( vmCvar ); +} + + +/* +===================== +Cvar_Register + +updates an interpreted modules' version of a cvar +===================== +*/ +void	Cvar_Update( vmCvar_t *vmCvar ) { +	cvar_t	*cv = NULL; // bk001129 +	assert(vmCvar); // bk + +	if ( (unsigned)vmCvar->handle >= cvar_numIndexes ) { +		Com_Error( ERR_DROP, "Cvar_Update: handle out of range" ); +	} + +	cv = cvar_indexes + vmCvar->handle; + +	if ( cv->modificationCount == vmCvar->modificationCount ) { +		return; +	} +	if ( !cv->string ) { +		return;		// variable might have been cleared by a cvar_restart +	} +	vmCvar->modificationCount = cv->modificationCount; +	// bk001129 - mismatches. +	if ( strlen(cv->string)+1 > MAX_CVAR_VALUE_STRING )  +	  Com_Error( ERR_DROP, "Cvar_Update: src %s length %zd exceeds MAX_CVAR_VALUE_STRING", +		     cv->string,  +		     strlen(cv->string)); +	// bk001212 - Q_strncpyz guarantees zero padding and dest[MAX_CVAR_VALUE_STRING-1]==0  +	// bk001129 - paranoia. Never trust the destination string. +	// bk001129 - beware, sizeof(char*) is always 4 (for cv->string).  +	//            sizeof(vmCvar->string) always MAX_CVAR_VALUE_STRING +	//Q_strncpyz( vmCvar->string, cv->string, sizeof( vmCvar->string ) ); // id +	Q_strncpyz( vmCvar->string, cv->string,  MAX_CVAR_VALUE_STRING );  + +	vmCvar->value = cv->value; +	vmCvar->integer = cv->integer; +} + + +/* +============ +Cvar_Init + +Reads in all archived cvars +============ +*/ +void Cvar_Init (void) { +	cvar_cheats = Cvar_Get("sv_cheats", "1", CVAR_ROM | CVAR_SYSTEMINFO ); + +	Cmd_AddCommand ("toggle", Cvar_Toggle_f); +	Cmd_AddCommand ("set", Cvar_Set_f); +	Cmd_AddCommand ("sets", Cvar_SetS_f); +	Cmd_AddCommand ("setu", Cvar_SetU_f); +	Cmd_AddCommand ("seta", Cvar_SetA_f); +	Cmd_AddCommand ("reset", Cvar_Reset_f); +	Cmd_AddCommand ("cvarlist", Cvar_List_f); +	Cmd_AddCommand ("cvar_restart", Cvar_Restart_f); +} diff --git a/src/qcommon/files.c b/src/qcommon/files.c new file mode 100644 index 0000000..cd36990 --- /dev/null +++ b/src/qcommon/files.c @@ -0,0 +1,3428 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ +/***************************************************************************** + * name:		files.c + * + * desc:		handle based filesystem for Quake III Arena  + * + * $Archive: /MissionPack/code/qcommon/files.c $ + * + *****************************************************************************/ + + +#include "q_shared.h" +#include "qcommon.h" +#include "unzip.h" + +/* +============================================================================= + +QUAKE3 FILESYSTEM + +All of Quake's data access is through a hierarchical file system, but the contents of  +the file system can be transparently merged from several sources. + +A "qpath" is a reference to game file data.  MAX_ZPATH is 256 characters, which must include +a terminating zero. "..", "\\", and ":" are explicitly illegal in qpaths to prevent any +references outside the quake directory system. + +The "base path" is the path to the directory holding all the game directories and usually +the executable.  It defaults to ".", but can be overridden with a "+set fs_basepath c:\quake3" +command line to allow code debugging in a different directory.  Basepath cannot +be modified at all after startup.  Any files that are created (demos, screenshots, +etc) will be created reletive to the base path, so base path should usually be writable. + +The "cd path" is the path to an alternate hierarchy that will be searched if a file +is not located in the base path.  A user can do a partial install that copies some +data to a base path created on their hard drive and leave the rest on the cd.  Files +are never writen to the cd path.  It defaults to a value set by the installer, like +"e:\quake3", but it can be overridden with "+set fs_cdpath g:\quake3". + +If a user runs the game directly from a CD, the base path would be on the CD.  This +should still function correctly, but all file writes will fail (harmlessly). + +The "home path" is the path used for all write access. On win32 systems we have "base path" +== "home path", but on *nix systems the base installation is usually readonly, and +"home path" points to ~/.q3a or similar + +The user can also install custom mods and content in "home path", so it should be searched +along with "home path" and "cd path" for game content. + + +The "base game" is the directory under the paths where data comes from by default, and +can be "base". + +The "current game" may be the same as the base game, or it may be the name of another +directory under the paths that should be searched for files before looking in the base game. +This is the basis for addons. + +Clients automatically set the game directory after receiving a gamestate from a server, +so only servers need to worry about +set fs_game. + +No other directories outside of the base game and current game will ever be referenced by +filesystem functions. + +To save disk space and speed loading, directory trees can be collapsed into zip files. +The files use a ".pk3" extension to prevent users from unzipping them accidentally, but +otherwise the are simply normal uncompressed zip files.  A game directory can have multiple +zip files of the form "pak0.pk3", "pak1.pk3", etc.  Zip files are searched in decending order +from the highest number to the lowest, and will always take precedence over the filesystem. +This allows a pk3 distributed as a patch to override all existing data. + +Because we will have updated executables freely available online, there is no point to +trying to restrict demo / oem versions of the game with code changes.  Demo / oem versions +should be exactly the same executables as release versions, but with different data that +automatically restricts where game media can come from to prevent add-ons from working. + +After the paths are initialized, quake will look for the product.txt file.  If not +found and verified, the game will run in restricted mode.  In restricted mode, only  +files contained in demoq3/pak0.pk3 will be available for loading, and only if the zip header is +verified to not have been modified.  A single exception is made for autogen.cfg.  Files +can still be written out in restricted mode, so screenshots and demos are allowed. +Restricted mode can be tested by setting "+set fs_restrict 1" on the command line, even +if there is a valid product.txt under the basepath or cdpath. + +If not running in restricted mode, and a file is not found in any local filesystem, +an attempt will be made to download it and save it under the base path. + +If the "fs_copyfiles" cvar is set to 1, then every time a file is sourced from the cd +path, it will be copied over to the base path.  This is a development aid to help build +test releases and to copy working sets over slow network links. + +File search order: when FS_FOpenFileRead gets called it will go through the fs_searchpaths +structure and stop on the first successful hit. fs_searchpaths is built with successive +calls to FS_AddGameDirectory + +Additionaly, we search in several subdirectories: +current game is the current mode +base game is a variable to allow mods based on other mods +(such as base + missionpack content combination in a mod for instance) +BASEGAME is the hardcoded base game ("base") + +e.g. the qpath "sound/newstuff/test.wav" would be searched for in the following places: + +home path + current game's zip files +home path + current game's directory +base path + current game's zip files +base path + current game's directory +cd path + current game's zip files +cd path + current game's directory + +home path + base game's zip file +home path + base game's directory +base path + base game's zip file +base path + base game's directory +cd path + base game's zip file +cd path + base game's directory + +home path + BASEGAME's zip file +home path + BASEGAME's directory +base path + BASEGAME's zip file +base path + BASEGAME's directory +cd path + BASEGAME's zip file +cd path + BASEGAME's directory + +server download, to be written to home path + current game's directory + + +The filesystem can be safely shutdown and reinitialized with different +basedir / cddir / game combinations, but all other subsystems that rely on it +(sound, video) must also be forced to restart. + +Because the same files are loaded by both the clip model (CM_) and renderer (TR_) +subsystems, a simple single-file caching scheme is used.  The CM_ subsystems will +load the file with a request to cache.  Only one file will be kept cached at a time, +so any models that are going to be referenced by both subsystems should alternate +between the CM_ load function and the ref load function. + +TODO: A qpath that starts with a leading slash will always refer to the base game, even if another +game is currently active.  This allows character models, skins, and sounds to be downloaded +to a common directory no matter which game is active. + +How to prevent downloading zip files? +Pass pk3 file names in systeminfo, and download before FS_Restart()? + +Aborting a download disconnects the client from the server. + +How to mark files as downloadable?  Commercial add-ons won't be downloadable. + +Non-commercial downloads will want to download the entire zip file. +the game would have to be reset to actually read the zip in + +Auto-update information + +Path separators + +Casing + +  separate server gamedir and client gamedir, so if the user starts +  a local game after having connected to a network game, it won't stick +  with the network game. + +  allow menu options for game selection? + +Read / write config to floppy option. + +Different version coexistance? + +When building a pak file, make sure a autogen.cfg isn't present in it, +or configs will never get loaded from disk! + +  todo: + +  downloading (outside fs?) +  game directory passing and restarting + +============================================================================= + +*/ + +// every time a new demo pk3 file is built, this checksum must be updated. +// the easiest way to get it is to just run the game and see what it spits out +#define	DEMO_PAK0_CHECKSUM	2985612116u +static const unsigned pak_checksums[] = { +	1566731103u, +	298122907u, +	412165236u, +	2991495316u, +	1197932710u, +	4087071573u, +	3709064859u, +	908855077u, +	977125798u +}; + +// if this is defined, the executable positively won't work with any paks other +// than the demo pak, even if productid is present.  This is only used for our +// last demo release to prevent the mac and linux users from using the demo +// executable with the production windows pak before the mac/linux products +// hit the shelves a little later +// NOW defined in build files +//#define PRE_RELEASE_TADEMO + +#define MAX_ZPATH			256 +#define	MAX_SEARCH_PATHS	4096 +#define MAX_FILEHASH_SIZE	1024 + +typedef struct fileInPack_s { +	char					*name;		// name of the file +	unsigned long			pos;		// file info position in zip +	struct	fileInPack_s*	next;		// next file in the hash +} fileInPack_t; + +typedef struct { +	char			pakFilename[MAX_OSPATH];	// /tremulous/base/pak0.pk3 +	char			pakBasename[MAX_OSPATH];	// pak0 +	char			pakGamename[MAX_OSPATH];	// base +	unzFile			handle;						// handle to zip file +	int				checksum;					// regular checksum +	int				pure_checksum;				// checksum for pure +	int				numfiles;					// number of files in pk3 +	int				referenced;					// referenced file flags +	int				hashSize;					// hash table size (power of 2) +	fileInPack_t*	*hashTable;					// hash table +	fileInPack_t*	buildBuffer;				// buffer with the filenames etc. +} pack_t; + +typedef struct { +	char		path[MAX_OSPATH]; +	char		gamedir[MAX_OSPATH];	// base +} directory_t; + +typedef struct searchpath_s { +	struct searchpath_s *next; + +	pack_t		*pack;		// only one of pack / dir will be non NULL +	directory_t	*dir; +} searchpath_t; + +static	char		fs_gamedir[MAX_OSPATH];	// this will be a single file name with no separators +static	cvar_t		*fs_debug; +static	cvar_t		*fs_homepath; +static	cvar_t		*fs_basepath; +static	cvar_t		*fs_basegame; +static	cvar_t		*fs_cdpath; +static	cvar_t		*fs_copyfiles; +static	cvar_t		*fs_gamedirvar; +static	cvar_t		*fs_restrict; +static	searchpath_t	*fs_searchpaths; +static	int			fs_readCount;			// total bytes read +static	int			fs_loadCount;			// total files read +static	int			fs_loadStack;			// total files in memory +static	int			fs_packFiles;			// total number of files in packs + +static int fs_fakeChkSum; +static int fs_checksumFeed; + +typedef union qfile_gus { +	FILE*		o; +	unzFile		z; +} qfile_gut; + +typedef struct qfile_us { +	qfile_gut	file; +	qboolean	unique; +} qfile_ut; + +typedef struct { +	qfile_ut	handleFiles; +	qboolean	handleSync; +	int			baseOffset; +	int			fileSize; +	int			zipFilePos; +	qboolean	zipFile; +	qboolean	streamed; +	char		name[MAX_ZPATH]; +} fileHandleData_t; + +static fileHandleData_t	fsh[MAX_FILE_HANDLES]; + +// TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540 +// wether we did a reorder on the current search path when joining the server +static qboolean fs_reordered; + +// never load anything from pk3 files that are not present at the server when pure +static int		fs_numServerPaks; +static int		fs_serverPaks[MAX_SEARCH_PATHS];				// checksums +static char		*fs_serverPakNames[MAX_SEARCH_PATHS];			// pk3 names + +// only used for autodownload, to make sure the client has at least +// all the pk3 files that are referenced at the server side +static int		fs_numServerReferencedPaks; +static int		fs_serverReferencedPaks[MAX_SEARCH_PATHS];			// checksums +static char		*fs_serverReferencedPakNames[MAX_SEARCH_PATHS];		// pk3 names + +// last valid game folder used +char lastValidBase[MAX_OSPATH]; +char lastValidGame[MAX_OSPATH]; + +#ifdef FS_MISSING +FILE*		missingFiles = NULL; +#endif + +/* +============== +FS_Initialized +============== +*/ + +qboolean FS_Initialized( void ) { +	return (fs_searchpaths != NULL); +} + +/* +================= +FS_PakIsPure +================= +*/ +qboolean FS_PakIsPure( pack_t *pack ) { +	int i; + +	if ( fs_numServerPaks ) { +		for ( i = 0 ; i < fs_numServerPaks ; i++ ) { +			// FIXME: also use hashed file names +			// NOTE TTimo: a pk3 with same checksum but different name would be validated too +			//   I don't see this as allowing for any exploit, it would only happen if the client does manips of it's file names 'not a bug' +			if ( pack->checksum == fs_serverPaks[i] ) { +				return qtrue;		// on the aproved list +			} +		} +		return qfalse;	// not on the pure server pak list +	} +	return qtrue; +} + + +/* +================= +FS_LoadStack +return load stack +================= +*/ +int FS_LoadStack( void ) +{ +	return fs_loadStack; +} +                       +/* +================ +return a hash value for the filename +================ +*/ +static long FS_HashFileName( const char *fname, int hashSize ) { +	int		i; +	long	hash; +	char	letter; + +	hash = 0; +	i = 0; +	while (fname[i] != '\0') { +		letter = tolower(fname[i]); +		if (letter =='.') break;				// don't include extension +		if (letter =='\\') letter = '/';		// damn path names +		if (letter == PATH_SEP) letter = '/';		// damn path names +		hash+=(long)(letter)*(i+119); +		i++; +	} +	hash = (hash ^ (hash >> 10) ^ (hash >> 20)); +	hash &= (hashSize-1); +	return hash; +} + +static fileHandle_t	FS_HandleForFile(void) { +	int		i; + +	for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) { +		if ( fsh[i].handleFiles.file.o == NULL ) { +			return i; +		} +	} +	Com_Error( ERR_DROP, "FS_HandleForFile: none free" ); +	return 0; +} + +static FILE	*FS_FileForHandle( fileHandle_t f ) { +	if ( f < 0 || f > MAX_FILE_HANDLES ) { +		Com_Error( ERR_DROP, "FS_FileForHandle: out of reange" ); +	} +	if (fsh[f].zipFile == qtrue) { +		Com_Error( ERR_DROP, "FS_FileForHandle: can't get FILE on zip file" ); +	} +	if ( ! fsh[f].handleFiles.file.o ) { +		Com_Error( ERR_DROP, "FS_FileForHandle: NULL" ); +	} +	 +	return fsh[f].handleFiles.file.o; +} + +void	FS_ForceFlush( fileHandle_t f ) { +	FILE *file; + +	file = FS_FileForHandle(f); +	setvbuf( file, NULL, _IONBF, 0 ); +} + +/* +================ +FS_filelength + +If this is called on a non-unique FILE (from a pak file), +it will return the size of the pak file, not the expected +size of the file. +================ +*/ +int FS_filelength( fileHandle_t f ) { +	int		pos; +	int		end; +	FILE*	h; + +	h = FS_FileForHandle(f); +	pos = ftell (h); +	fseek (h, 0, SEEK_END); +	end = ftell (h); +	fseek (h, pos, SEEK_SET); + +	return end; +} + +/* +==================== +FS_ReplaceSeparators + +Fix things up differently for win/unix/mac +==================== +*/ +static void FS_ReplaceSeparators( char *path ) { +	char	*s; + +	for ( s = path ; *s ; s++ ) { +		if ( *s == '/' || *s == '\\' ) { +			*s = PATH_SEP; +		} +	} +} + +/* +=================== +FS_BuildOSPath + +Qpath may have either forward or backwards slashes +=================== +*/ +char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ) { +	char	temp[MAX_OSPATH]; +	static char ospath[2][MAX_OSPATH]; +	static int toggle; +	 +	toggle ^= 1;		// flip-flop to allow two returns without clash + +	if( !game || !game[0] ) { +		game = fs_gamedir; +	} + +	Com_sprintf( temp, sizeof(temp), "/%s/%s", game, qpath ); +	FS_ReplaceSeparators( temp );	 +	Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s", base, temp ); +	 +	return ospath[toggle]; +} + + +/* +============ +FS_CreatePath + +Creates any directories needed to store the given filename +============ +*/ +static qboolean FS_CreatePath (char *OSPath) { +	char	*ofs; +	 +	// make absolutely sure that it can't back up the path +	// FIXME: is c: allowed??? +	if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) { +		Com_Printf( "WARNING: refusing to create relative path \"%s\"\n", OSPath ); +		return qtrue; +	} + +	for (ofs = OSPath+1 ; *ofs ; ofs++) { +		if (*ofs == PATH_SEP) {	 +			// create the directory +			*ofs = 0; +			Sys_Mkdir (OSPath); +			*ofs = PATH_SEP; +		} +	} +	return qfalse; +} + +/* +================= +FS_CopyFile + +Copy a fully specified file from one place to another +================= +*/ +static void FS_CopyFile( char *fromOSPath, char *toOSPath ) { +	FILE	*f; +	int		len; +	byte	*buf; + +	Com_Printf( "copy %s to %s\n", fromOSPath, toOSPath ); + +	if (strstr(fromOSPath, "journal.dat") || strstr(fromOSPath, "journaldata.dat")) { +		Com_Printf( "Ignoring journal files\n"); +		return; +	} + +	f = fopen( fromOSPath, "rb" ); +	if ( !f ) { +		return; +	} +	fseek (f, 0, SEEK_END); +	len = ftell (f); +	fseek (f, 0, SEEK_SET); + +	// we are using direct malloc instead of Z_Malloc here, so it +	// probably won't work on a mac... Its only for developers anyway... +	buf = malloc( len ); +	if (fread( buf, 1, len, f ) != len) +		Com_Error( ERR_FATAL, "Short read in FS_Copyfiles()\n" ); +	fclose( f ); + +	if( FS_CreatePath( toOSPath ) ) { +		return; +	} + +	f = fopen( toOSPath, "wb" ); +	if ( !f ) { +		return; +	} +	if (fwrite( buf, 1, len, f ) != len) +		Com_Error( ERR_FATAL, "Short write in FS_Copyfiles()\n" ); +	fclose( f ); +	free( buf ); +} + +/* +=========== +FS_Remove + +=========== +*/ +void FS_Remove( const char *osPath ) { +	remove( osPath ); +} + +/* +=========== +FS_HomeRemove + +=========== +*/ +void FS_HomeRemove( const char *homePath ) { +	remove( FS_BuildOSPath( fs_homepath->string, +			fs_gamedir, homePath ) ); +} + +/* +================ +FS_FileExists + +Tests if the file exists in the current gamedir, this DOES NOT +search the paths.  This is to determine if opening a file to write +(which always goes into the current gamedir) will cause any overwrites. +NOTE TTimo: this goes with FS_FOpenFileWrite for opening the file afterwards +================ +*/ +qboolean FS_FileExists( const char *file ) +{ +	FILE *f; +	char *testpath; + +	testpath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, file ); + +	f = fopen( testpath, "rb" ); +	if (f) { +		fclose( f ); +		return qtrue; +	} +	return qfalse; +} + +/* +================ +FS_SV_FileExists + +Tests if the file exists  +================ +*/ +qboolean FS_SV_FileExists( const char *file ) +{ +	FILE *f; +	char *testpath; + +	testpath = FS_BuildOSPath( fs_homepath->string, file, ""); +	testpath[strlen(testpath)-1] = '\0'; + +	f = fopen( testpath, "rb" ); +	if (f) { +		fclose( f ); +		return qtrue; +	} +	return qfalse; +} + + +/* +=========== +FS_SV_FOpenFileWrite + +=========== +*/ +fileHandle_t FS_SV_FOpenFileWrite( const char *filename ) { +	char *ospath; +	fileHandle_t	f; + +	if ( !fs_searchpaths ) { +		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); +	} + +	ospath = FS_BuildOSPath( fs_homepath->string, filename, "" ); +	ospath[strlen(ospath)-1] = '\0'; + +	f = FS_HandleForFile(); +	fsh[f].zipFile = qfalse; + +	if ( fs_debug->integer ) { +		Com_Printf( "FS_SV_FOpenFileWrite: %s\n", ospath ); +	} + +	if( FS_CreatePath( ospath ) ) { +		return 0; +	} + +	Com_DPrintf( "writing to: %s\n", ospath ); +	fsh[f].handleFiles.file.o = fopen( ospath, "wb" ); + +	Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); + +	fsh[f].handleSync = qfalse; +	if (!fsh[f].handleFiles.file.o) { +		f = 0; +	} +	return f; +} + +/* +=========== +FS_SV_FOpenFileRead +search for a file somewhere below the home path, base path or cd path +we search in that order, matching FS_SV_FOpenFileRead order +=========== +*/ +int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ) { +	char *ospath; +	fileHandle_t	f = 0; + +	if ( !fs_searchpaths ) { +		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); +	} + +	f = FS_HandleForFile(); +	fsh[f].zipFile = qfalse; + +	Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); + +	// don't let sound stutter +	S_ClearSoundBuffer(); + +  // search homepath +	ospath = FS_BuildOSPath( fs_homepath->string, filename, "" ); +	// remove trailing slash +	ospath[strlen(ospath)-1] = '\0'; + +	if ( fs_debug->integer ) { +		Com_Printf( "FS_SV_FOpenFileRead (fs_homepath): %s\n", ospath ); +	} + +	fsh[f].handleFiles.file.o = fopen( ospath, "rb" ); +	fsh[f].handleSync = qfalse; +  if (!fsh[f].handleFiles.file.o) +  { +    // NOTE TTimo on non *nix systems, fs_homepath == fs_basepath, might want to avoid +    if (Q_stricmp(fs_homepath->string,fs_basepath->string)) +    { +      // search basepath +      ospath = FS_BuildOSPath( fs_basepath->string, filename, "" ); +      ospath[strlen(ospath)-1] = '\0'; + +      if ( fs_debug->integer ) +      { +        Com_Printf( "FS_SV_FOpenFileRead (fs_basepath): %s\n", ospath ); +      } + +      fsh[f].handleFiles.file.o = fopen( ospath, "rb" ); +      fsh[f].handleSync = qfalse; + +      if ( !fsh[f].handleFiles.file.o ) +      { +        f = 0; +      } +    } +  } + +	if (!fsh[f].handleFiles.file.o) { +    // search cd path +    ospath = FS_BuildOSPath( fs_cdpath->string, filename, "" ); +    ospath[strlen(ospath)-1] = '\0'; + +    if (fs_debug->integer) +    { +      Com_Printf( "FS_SV_FOpenFileRead (fs_cdpath) : %s\n", ospath ); +    } + +	  fsh[f].handleFiles.file.o = fopen( ospath, "rb" ); +	  fsh[f].handleSync = qfalse; + +	  if( !fsh[f].handleFiles.file.o ) { +	    f = 0; +	  } +  } +   +	*fp = f; +	if (f) { +		return FS_filelength(f); +	} +	return 0; +} + + +/* +=========== +FS_SV_Rename + +=========== +*/ +void FS_SV_Rename( const char *from, const char *to ) { +	char			*from_ospath, *to_ospath; + +	if ( !fs_searchpaths ) { +		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); +	} + +	// don't let sound stutter +	S_ClearSoundBuffer(); + +	from_ospath = FS_BuildOSPath( fs_homepath->string, from, "" ); +	to_ospath = FS_BuildOSPath( fs_homepath->string, to, "" ); +	from_ospath[strlen(from_ospath)-1] = '\0'; +	to_ospath[strlen(to_ospath)-1] = '\0'; + +	if ( fs_debug->integer ) { +		Com_Printf( "FS_SV_Rename: %s --> %s\n", from_ospath, to_ospath ); +	} + +	if (rename( from_ospath, to_ospath )) { +		// Failed, try copying it and deleting the original +		FS_CopyFile ( from_ospath, to_ospath ); +		FS_Remove ( from_ospath ); +	} +} + + + +/* +=========== +FS_Rename + +=========== +*/ +void FS_Rename( const char *from, const char *to ) { +	char			*from_ospath, *to_ospath; + +	if ( !fs_searchpaths ) { +		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); +	} + +	// don't let sound stutter +	S_ClearSoundBuffer(); + +	from_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, from ); +	to_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, to ); + +	if ( fs_debug->integer ) { +		Com_Printf( "FS_Rename: %s --> %s\n", from_ospath, to_ospath ); +	} + +	if (rename( from_ospath, to_ospath )) { +		// Failed, try copying it and deleting the original +		FS_CopyFile ( from_ospath, to_ospath ); +		FS_Remove ( from_ospath ); +	} +} + +/* +============== +FS_FCloseFile + +If the FILE pointer is an open pak file, leave it open. + +For some reason, other dll's can't just cal fclose() +on files returned by FS_FOpenFile... +============== +*/ +void FS_FCloseFile( fileHandle_t f ) { +	if ( !fs_searchpaths ) { +		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); +	} + +	if (fsh[f].streamed) { +		Sys_EndStreamedFile(f); +	} +	if (fsh[f].zipFile == qtrue) { +		unzCloseCurrentFile( fsh[f].handleFiles.file.z ); +		if ( fsh[f].handleFiles.unique ) { +			unzClose( fsh[f].handleFiles.file.z ); +		} +		Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) ); +		return; +	} + +	// we didn't find it as a pak, so close it as a unique file +	if (fsh[f].handleFiles.file.o) { +		fclose (fsh[f].handleFiles.file.o); +	} +	Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) ); +} + +/* +=========== +FS_FOpenFileWrite + +=========== +*/ +fileHandle_t FS_FOpenFileWrite( const char *filename ) { +	char			*ospath; +	fileHandle_t	f; + +	if ( !fs_searchpaths ) { +		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); +	} + +	f = FS_HandleForFile(); +	fsh[f].zipFile = qfalse; + +	ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename ); + +	if ( fs_debug->integer ) { +		Com_Printf( "FS_FOpenFileWrite: %s\n", ospath ); +	} + +	if( FS_CreatePath( ospath ) ) { +		return 0; +	} + +	// enabling the following line causes a recursive function call loop +	// when running with +set logfile 1 +set developer 1 +	//Com_DPrintf( "writing to: %s\n", ospath ); +	fsh[f].handleFiles.file.o = fopen( ospath, "wb" ); + +	Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); + +	fsh[f].handleSync = qfalse; +	if (!fsh[f].handleFiles.file.o) { +		f = 0; +	} +	return f; +} + +/* +=========== +FS_FOpenFileAppend + +=========== +*/ +fileHandle_t FS_FOpenFileAppend( const char *filename ) { +	char			*ospath; +	fileHandle_t	f; + +	if ( !fs_searchpaths ) { +		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); +	} + +	f = FS_HandleForFile(); +	fsh[f].zipFile = qfalse; + +	Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); + +	// don't let sound stutter +	S_ClearSoundBuffer(); + +	ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename ); + +	if ( fs_debug->integer ) { +		Com_Printf( "FS_FOpenFileAppend: %s\n", ospath ); +	} + +	if( FS_CreatePath( ospath ) ) { +		return 0; +	} + +	fsh[f].handleFiles.file.o = fopen( ospath, "ab" ); +	fsh[f].handleSync = qfalse; +	if (!fsh[f].handleFiles.file.o) { +		f = 0; +	} +	return f; +} + +/* +=========== +FS_FilenameCompare + +Ignore case and seprator char distinctions +=========== +*/ +qboolean FS_FilenameCompare( const char *s1, const char *s2 ) { +	int		c1, c2; +	 +	do { +		c1 = *s1++; +		c2 = *s2++; + +		if (c1 >= 'a' && c1 <= 'z') { +			c1 -= ('a' - 'A'); +		} +		if (c2 >= 'a' && c2 <= 'z') { +			c2 -= ('a' - 'A'); +		} + +		if ( c1 == '\\' || c1 == ':' ) { +			c1 = '/'; +		} +		if ( c2 == '\\' || c2 == ':' ) { +			c2 = '/'; +		} +		 +		if (c1 != c2) { +			return qtrue;		// strings not equal +		} +	} while (c1); +	 +	return qfalse;		// strings are equal +} + +/* +=========== +FS_ShiftedStrStr +=========== +*/ +char *FS_ShiftedStrStr(const char *string, const char *substring, int shift) { +	char buf[MAX_STRING_TOKENS]; +	int i; + +	for (i = 0; substring[i]; i++) { +		buf[i] = substring[i] + shift; +	} +	buf[i] = '\0'; +	return strstr(string, buf); +} + +/* +=========== +FS_FOpenFileRead + +Finds the file in the search path. +Returns filesize and an open FILE pointer. +Used for streaming data out of either a +separate file or a ZIP file. +=========== +*/ +extern qboolean		com_fullyInitialized; + +int FS_FOpenFileRead( const char *filename, fileHandle_t *file, qboolean uniqueFILE ) { +	searchpath_t	*search; +	char			*netpath; +	pack_t			*pak; +	fileInPack_t	*pakFile; +	directory_t		*dir; +	long			hash; +	unz_s			*zfi; +	FILE			*temp; +	int				l; +	char demoExt[16]; + +	hash = 0; + +	if ( !fs_searchpaths ) { +		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); +	} + +	if ( file == NULL ) { +		// just wants to see if file is there +		for ( search = fs_searchpaths ; search ; search = search->next ) { +			// +			if ( search->pack ) { +				hash = FS_HashFileName(filename, search->pack->hashSize); +			} +			// is the element a pak file? +			if ( search->pack && search->pack->hashTable[hash] ) { +				// look through all the pak file elements +				pak = search->pack; +				pakFile = pak->hashTable[hash]; +				do { +					// case and separator insensitive comparisons +					if ( !FS_FilenameCompare( pakFile->name, filename ) ) { +						// found it! +						return qtrue; +					} +					pakFile = pakFile->next; +				} while(pakFile != NULL); +			} else if ( search->dir ) { +				dir = search->dir; +			 +				netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename ); +				temp = fopen (netpath, "rb"); +				if ( !temp ) { +					continue; +				} +				fclose(temp); +				return qtrue; +			} +		} +		return qfalse; +	} + +	if ( !filename ) { +		Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" ); +	} + +	Com_sprintf (demoExt, sizeof(demoExt), ".dm_%d",PROTOCOL_VERSION ); +	// qpaths are not supposed to have a leading slash +	if ( filename[0] == '/' || filename[0] == '\\' ) { +		filename++; +	} + +	// make absolutely sure that it can't back up the path. +	// The searchpaths do guarantee that something will always +	// be prepended, so we don't need to worry about "c:" or "//limbo"  +	if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) { +		*file = 0; +		return -1; +	} + +	// +	// search through the path, one element at a time +	// + +	*file = FS_HandleForFile(); +	fsh[*file].handleFiles.unique = uniqueFILE; + +	for ( search = fs_searchpaths ; search ; search = search->next ) { +		// +		if ( search->pack ) { +			hash = FS_HashFileName(filename, search->pack->hashSize); +		} +		// is the element a pak file? +		if ( search->pack && search->pack->hashTable[hash] ) { +			// disregard if it doesn't match one of the allowed pure pak files +			if ( !FS_PakIsPure(search->pack) ) { +				continue; +			} + +			// look through all the pak file elements +			pak = search->pack; +			pakFile = pak->hashTable[hash]; +			do { +				// case and separator insensitive comparisons +				if ( !FS_FilenameCompare( pakFile->name, filename ) ) { +					// found it! + +					// mark the pak as having been referenced and mark specifics on cgame and ui +					// shaders, txt, arena files  by themselves do not count as a reference as  +					// these are loaded from all pk3s  +					// from every pk3 file..  +					l = strlen( filename ); +					if ( !(pak->referenced & FS_GENERAL_REF)) { +						if ( Q_stricmp(filename + l - 7, ".shader") != 0 && +							Q_stricmp(filename + l - 4, ".txt") != 0 && +							Q_stricmp(filename + l - 4, ".cfg") != 0 && +							Q_stricmp(filename + l - 7, ".config") != 0 && +							strstr(filename, "levelshots") == NULL && +							Q_stricmp(filename + l - 4, ".bot") != 0 && +							Q_stricmp(filename + l - 6, ".arena") != 0 && +							Q_stricmp(filename + l - 5, ".menu") != 0) { +							pak->referenced |= FS_GENERAL_REF; +						} +					} + +					// game.qvm	- 13 +					// ZT`X!di` +					if (!(pak->referenced & FS_QAGAME_REF) && FS_ShiftedStrStr(filename, "ZT`X!di`", 13)) { +						pak->referenced |= FS_QAGAME_REF; +					} +					// cgame.qvm	- 7 +					// \`Zf^'jof +					if (!(pak->referenced & FS_CGAME_REF) && FS_ShiftedStrStr(filename , "\\`Zf^'jof", 7)) { +						pak->referenced |= FS_CGAME_REF; +					} +					// ui.qvm		- 5 +					// pd)lqh +					if (!(pak->referenced & FS_UI_REF) && FS_ShiftedStrStr(filename , "pd)lqh", 5)) { +						pak->referenced |= FS_UI_REF; +					} + +					if ( uniqueFILE ) { +						// open a new file on the pakfile +						fsh[*file].handleFiles.file.z = unzReOpen (pak->pakFilename, pak->handle); +						if (fsh[*file].handleFiles.file.z == NULL) { +							Com_Error (ERR_FATAL, "Couldn't reopen %s", pak->pakFilename); +						} +					} else { +						fsh[*file].handleFiles.file.z = pak->handle; +					} +					Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) ); +					fsh[*file].zipFile = qtrue; +					zfi = (unz_s *)fsh[*file].handleFiles.file.z; +					// in case the file was new +					temp = zfi->file; +					// set the file position in the zip file (also sets the current file info) +					unzSetCurrentFileInfoPosition(pak->handle, pakFile->pos); +					// copy the file info into the unzip structure +					Com_Memcpy( zfi, pak->handle, sizeof(unz_s) ); +					// we copy this back into the structure +					zfi->file = temp; +					// open the file in the zip +					unzOpenCurrentFile( fsh[*file].handleFiles.file.z ); +					fsh[*file].zipFilePos = pakFile->pos; + +					if ( fs_debug->integer ) { +						Com_Printf( "FS_FOpenFileRead: %s (found in '%s')\n",  +							filename, pak->pakFilename ); +					} +					return zfi->cur_file_info.uncompressed_size; +				} +				pakFile = pakFile->next; +			} while(pakFile != NULL); +		} else if ( search->dir ) { +			// check a file in the directory tree + +			// if we are running restricted, the only files we +			// will allow to come from the directory are .cfg files +			l = strlen( filename ); +      // FIXME TTimo I'm not sure about the fs_numServerPaks test +      // if you are using FS_ReadFile to find out if a file exists, +      //   this test can make the search fail although the file is in the directory +      // I had the problem on https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=8 +      // turned out I used FS_FileExists instead +			if ( fs_restrict->integer || fs_numServerPaks ) { + +				if ( Q_stricmp( filename + l - 4, ".cfg" )		// for config files +					&& Q_stricmp( filename + l - 5, ".menu" )	// menu files +					&& Q_stricmp( filename + l - 5, ".game" )	// menu files +					&& Q_stricmp( filename + l - strlen(demoExt), demoExt )	// menu files +					&& Q_stricmp( filename + l - 4, ".dat" ) ) {	// for journal files +					continue; +				} +			} + +			dir = search->dir; +			 +			netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename ); +			fsh[*file].handleFiles.file.o = fopen (netpath, "rb"); +			if ( !fsh[*file].handleFiles.file.o ) { +				continue; +			} + +			if ( Q_stricmp( filename + l - 4, ".cfg" )		// for config files +				&& Q_stricmp( filename + l - 5, ".menu" )	// menu files +				&& Q_stricmp( filename + l - 5, ".game" )	// menu files +				&& Q_stricmp( filename + l - strlen(demoExt), demoExt )	// menu files +				&& Q_stricmp( filename + l - 4, ".dat" ) ) {	// for journal files +				fs_fakeChkSum = random(); +			} +       +			Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) ); +			fsh[*file].zipFile = qfalse; +			if ( fs_debug->integer ) { +				Com_Printf( "FS_FOpenFileRead: %s (found in '%s/%s')\n", filename, +					dir->path, dir->gamedir ); +			} + +			// if we are getting it from the cdpath, optionally copy it +			//  to the basepath +			if ( fs_copyfiles->integer && !Q_stricmp( dir->path, fs_cdpath->string ) ) { +				char	*copypath; + +				copypath = FS_BuildOSPath( fs_basepath->string, dir->gamedir, filename ); +				FS_CopyFile( netpath, copypath ); +			} + +			return FS_filelength (*file); +		}		 +	} +	 +	Com_DPrintf ("Can't find %s\n", filename); +#ifdef FS_MISSING +	if (missingFiles) { +		fprintf(missingFiles, "%s\n", filename); +	} +#endif +	*file = 0; +	return -1; +} + + +/* +================= +FS_Read + +Properly handles partial reads +================= +*/ +int FS_Read2( void *buffer, int len, fileHandle_t f ) { +	if ( !fs_searchpaths ) { +		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); +	} + +	if ( !f ) { +		return 0; +	} +	if (fsh[f].streamed) { +		int r; +		fsh[f].streamed = qfalse; +		r = Sys_StreamedRead( buffer, len, 1, f); +		fsh[f].streamed = qtrue; +		return r; +	} else { +		return FS_Read( buffer, len, f); +	} +} + +int FS_Read( void *buffer, int len, fileHandle_t f ) { +	int		block, remaining; +	int		read; +	byte	*buf; +	int		tries; + +	if ( !fs_searchpaths ) { +		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); +	} + +	if ( !f ) { +		return 0; +	} + +	buf = (byte *)buffer; +	fs_readCount += len; + +	if (fsh[f].zipFile == qfalse) { +		remaining = len; +		tries = 0; +		while (remaining) { +			block = remaining; +			read = fread (buf, 1, block, fsh[f].handleFiles.file.o); +			if (read == 0) { +				// we might have been trying to read from a CD, which +				// sometimes returns a 0 read on windows +				if (!tries) { +					tries = 1; +				} else { +					return len-remaining;	//Com_Error (ERR_FATAL, "FS_Read: 0 bytes read"); +				} +			} + +			if (read == -1) { +				Com_Error (ERR_FATAL, "FS_Read: -1 bytes read"); +			} + +			remaining -= read; +			buf += read; +		} +		return len; +	} else { +		return unzReadCurrentFile(fsh[f].handleFiles.file.z, buffer, len); +	} +} + +/* +================= +FS_Write + +Properly handles partial writes +================= +*/ +int FS_Write( const void *buffer, int len, fileHandle_t h ) { +	int		block, remaining; +	int		written; +	byte	*buf; +	int		tries; +	FILE	*f; + +	if ( !fs_searchpaths ) { +		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); +	} + +	if ( !h ) { +		return 0; +	} + +	f = FS_FileForHandle(h); +	buf = (byte *)buffer; + +	remaining = len; +	tries = 0; +	while (remaining) { +		block = remaining; +		written = fwrite (buf, 1, block, f); +		if (written == 0) { +			if (!tries) { +				tries = 1; +			} else { +				Com_Printf( "FS_Write: 0 bytes written\n" ); +				return 0; +			} +		} + +		if (written == -1) { +			Com_Printf( "FS_Write: -1 bytes written\n" ); +			return 0; +		} + +		remaining -= written; +		buf += written; +	} +	if ( fsh[h].handleSync ) { +		fflush( f ); +	} +	return len; +} + +void QDECL FS_Printf( fileHandle_t h, const char *fmt, ... ) { +	va_list		argptr; +	char		msg[MAXPRINTMSG]; + +	va_start (argptr,fmt); +	Q_vsnprintf (msg, sizeof(msg), fmt, argptr); +	va_end (argptr); + +	FS_Write(msg, strlen(msg), h); +} + +#define PK3_SEEK_BUFFER_SIZE 65536 + +/* +================= +FS_Seek + +================= +*/ +int FS_Seek( fileHandle_t f, long offset, int origin ) { +	int		_origin; + +	if ( !fs_searchpaths ) { +		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); +		return -1; +	} + +	if (fsh[f].streamed) { +		fsh[f].streamed = qfalse; +		Sys_StreamSeek( f, offset, origin ); +		fsh[f].streamed = qtrue; +	} + +	if (fsh[f].zipFile == qtrue) { +		//FIXME: this is incomplete and really, really +		//crappy (but better than what was here before) +		byte	buffer[PK3_SEEK_BUFFER_SIZE]; +		int		remainder = offset; + +		if( offset < 0 || origin == FS_SEEK_END ) { +			Com_Error( ERR_FATAL, "Negative offsets and FS_SEEK_END not implemented " +					"for FS_Seek on pk3 file contents\n" ); +			return -1; +		} + +		switch( origin ) { +			case FS_SEEK_SET: +				unzSetCurrentFileInfoPosition(fsh[f].handleFiles.file.z, fsh[f].zipFilePos); +				unzOpenCurrentFile(fsh[f].handleFiles.file.z); +				//fallthrough + +			case FS_SEEK_CUR: +				while( remainder > PK3_SEEK_BUFFER_SIZE ) { +					FS_Read( buffer, PK3_SEEK_BUFFER_SIZE, f ); +					remainder -= PK3_SEEK_BUFFER_SIZE; +				} +				FS_Read( buffer, remainder, f ); +				return offset; +				break; + +			default: +				Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" ); +				return -1; +				break; +		} +	} else { +		FILE *file; +		file = FS_FileForHandle(f); +		switch( origin ) { +		case FS_SEEK_CUR: +			_origin = SEEK_CUR; +			break; +		case FS_SEEK_END: +			_origin = SEEK_END; +			break; +		case FS_SEEK_SET: +			_origin = SEEK_SET; +			break; +		default: +			_origin = SEEK_CUR; +			Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" ); +			break; +		} + +		return fseek( file, offset, _origin ); +	} +} + + +/* +====================================================================================== + +CONVENIENCE FUNCTIONS FOR ENTIRE FILES + +====================================================================================== +*/ + +int	FS_FileIsInPAK(const char *filename, int *pChecksum ) { +	searchpath_t	*search; +	pack_t			*pak; +	fileInPack_t	*pakFile; +	long			hash = 0; + +	if ( !fs_searchpaths ) { +		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); +	} + +	if ( !filename ) { +		Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" ); +	} + +	// qpaths are not supposed to have a leading slash +	if ( filename[0] == '/' || filename[0] == '\\' ) { +		filename++; +	} + +	// make absolutely sure that it can't back up the path. +	// The searchpaths do guarantee that something will always +	// be prepended, so we don't need to worry about "c:" or "//limbo"  +	if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) { +		return -1; +	} + +	// +	// search through the path, one element at a time +	// + +	for ( search = fs_searchpaths ; search ; search = search->next ) { +		// +		if (search->pack) { +			hash = FS_HashFileName(filename, search->pack->hashSize); +		} +		// is the element a pak file? +		if ( search->pack && search->pack->hashTable[hash] ) { +			// disregard if it doesn't match one of the allowed pure pak files +			if ( !FS_PakIsPure(search->pack) ) { +				continue; +			} + +			// look through all the pak file elements +			pak = search->pack; +			pakFile = pak->hashTable[hash]; +			do { +				// case and separator insensitive comparisons +				if ( !FS_FilenameCompare( pakFile->name, filename ) ) { +					if (pChecksum) { +						*pChecksum = pak->pure_checksum; +					} +					return 1; +				} +				pakFile = pakFile->next; +			} while(pakFile != NULL); +		} +	} +	return -1; +} + +/* +============ +FS_ReadFile + +Filename are relative to the quake search path +a null buffer will just return the file length without loading +============ +*/ +int FS_ReadFile( const char *qpath, void **buffer ) { +	fileHandle_t	h; +	byte*			buf; +	qboolean		isConfig; +	int				len; + +	if ( !fs_searchpaths ) { +		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); +	} + +	if ( !qpath || !qpath[0] ) { +		Com_Error( ERR_FATAL, "FS_ReadFile with empty name\n" ); +	} + +	buf = NULL;	// quiet compiler warning + +	// if this is a .cfg file and we are playing back a journal, read +	// it from the journal file +	if ( strstr( qpath, ".cfg" ) ) { +		isConfig = qtrue; +		if ( com_journal && com_journal->integer == 2 ) { +			int		r; + +			Com_DPrintf( "Loading %s from journal file.\n", qpath ); +			r = FS_Read( &len, sizeof( len ), com_journalDataFile ); +			if ( r != sizeof( len ) ) { +				if (buffer != NULL) *buffer = NULL; +				return -1; +			} +			// if the file didn't exist when the journal was created +			if (!len) { +				if (buffer == NULL) { +					return 1;			// hack for old journal files +				} +				*buffer = NULL; +				return -1; +			} +			if (buffer == NULL) { +				return len; +			} + +			buf = Hunk_AllocateTempMemory(len+1); +			*buffer = buf; + +			r = FS_Read( buf, len, com_journalDataFile ); +			if ( r != len ) { +				Com_Error( ERR_FATAL, "Read from journalDataFile failed" ); +			} + +			fs_loadCount++; +			fs_loadStack++; + +			// guarantee that it will have a trailing 0 for string operations +			buf[len] = 0; + +			return len; +		} +	} else { +		isConfig = qfalse; +	} + +	// look for it in the filesystem or pack files +	len = FS_FOpenFileRead( qpath, &h, qfalse ); +	if ( h == 0 ) { +		if ( buffer ) { +			*buffer = NULL; +		} +		// if we are journalling and it is a config file, write a zero to the journal file +		if ( isConfig && com_journal && com_journal->integer == 1 ) { +			Com_DPrintf( "Writing zero for %s to journal file.\n", qpath ); +			len = 0; +			FS_Write( &len, sizeof( len ), com_journalDataFile ); +			FS_Flush( com_journalDataFile ); +		} +		return -1; +	} +	 +	if ( !buffer ) { +		if ( isConfig && com_journal && com_journal->integer == 1 ) { +			Com_DPrintf( "Writing len for %s to journal file.\n", qpath ); +			FS_Write( &len, sizeof( len ), com_journalDataFile ); +			FS_Flush( com_journalDataFile ); +		} +		FS_FCloseFile( h); +		return len; +	} + +	fs_loadCount++; +	fs_loadStack++; + +	buf = Hunk_AllocateTempMemory(len+1); +	*buffer = buf; + +	FS_Read (buf, len, h); + +	// guarantee that it will have a trailing 0 for string operations +	buf[len] = 0; +	FS_FCloseFile( h ); + +	// if we are journalling and it is a config file, write it to the journal file +	if ( isConfig && com_journal && com_journal->integer == 1 ) { +		Com_DPrintf( "Writing %s to journal file.\n", qpath ); +		FS_Write( &len, sizeof( len ), com_journalDataFile ); +		FS_Write( buf, len, com_journalDataFile ); +		FS_Flush( com_journalDataFile ); +	} +	return len; +} + +/* +============= +FS_FreeFile +============= +*/ +void FS_FreeFile( void *buffer ) { +	if ( !fs_searchpaths ) { +		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); +	} +	if ( !buffer ) { +		Com_Error( ERR_FATAL, "FS_FreeFile( NULL )" ); +	} +	fs_loadStack--; + +	Hunk_FreeTempMemory( buffer ); + +	// if all of our temp files are free, clear all of our space +	if ( fs_loadStack == 0 ) { +		Hunk_ClearTempMemory(); +	} +} + +/* +============ +FS_WriteFile + +Filename are reletive to the quake search path +============ +*/ +void FS_WriteFile( const char *qpath, const void *buffer, int size ) { +	fileHandle_t f; + +	if ( !fs_searchpaths ) { +		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); +	} + +	if ( !qpath || !buffer ) { +		Com_Error( ERR_FATAL, "FS_WriteFile: NULL parameter" ); +	} + +	f = FS_FOpenFileWrite( qpath ); +	if ( !f ) { +		Com_Printf( "Failed to open %s\n", qpath ); +		return; +	} + +	FS_Write( buffer, size, f ); + +	FS_FCloseFile( f ); +} + + + +/* +========================================================================== + +ZIP FILE LOADING + +========================================================================== +*/ + +/* +================= +FS_LoadZipFile + +Creates a new pak_t in the search chain for the contents +of a zip file. +================= +*/ +static pack_t *FS_LoadZipFile( char *zipfile, const char *basename ) +{ +	fileInPack_t	*buildBuffer; +	pack_t			*pack; +	unzFile			uf; +	int				err; +	unz_global_info gi; +	char			filename_inzip[MAX_ZPATH]; +	unz_file_info	file_info; +	int				i, len; +	long			hash; +	int				fs_numHeaderLongs; +	int				*fs_headerLongs; +	char			*namePtr; + +	fs_numHeaderLongs = 0; + +	uf = unzOpen(zipfile); +	err = unzGetGlobalInfo (uf,&gi); + +	if (err != UNZ_OK) +		return NULL; + +	fs_packFiles += gi.number_entry; + +	len = 0; +	unzGoToFirstFile(uf); +	for (i = 0; i < gi.number_entry; i++) +	{ +		err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0); +		if (err != UNZ_OK) { +			break; +		} +		len += strlen(filename_inzip) + 1; +		unzGoToNextFile(uf); +	} + +	buildBuffer = Z_Malloc( (gi.number_entry * sizeof( fileInPack_t )) + len ); +	namePtr = ((char *) buildBuffer) + gi.number_entry * sizeof( fileInPack_t ); +	fs_headerLongs = Z_Malloc( ( gi.number_entry + 1 ) * sizeof(int) ); +	fs_headerLongs[ fs_numHeaderLongs++ ] = LittleLong( fs_checksumFeed ); + +	// get the hash table size from the number of files in the zip +	// because lots of custom pk3 files have less than 32 or 64 files +	for (i = 1; i <= MAX_FILEHASH_SIZE; i <<= 1) { +		if (i > gi.number_entry) { +			break; +		} +	} + +	pack = Z_Malloc( sizeof( pack_t ) + i * sizeof(fileInPack_t *) ); +	pack->hashSize = i; +	pack->hashTable = (fileInPack_t **) (((char *) pack) + sizeof( pack_t )); +	for(i = 0; i < pack->hashSize; i++) { +		pack->hashTable[i] = NULL; +	} + +	Q_strncpyz( pack->pakFilename, zipfile, sizeof( pack->pakFilename ) ); +	Q_strncpyz( pack->pakBasename, basename, sizeof( pack->pakBasename ) ); + +	// strip .pk3 if needed +	if ( strlen( pack->pakBasename ) > 4 && !Q_stricmp( pack->pakBasename + strlen( pack->pakBasename ) - 4, ".pk3" ) ) { +		pack->pakBasename[strlen( pack->pakBasename ) - 4] = 0; +	} + +	pack->handle = uf; +	pack->numfiles = gi.number_entry; +	unzGoToFirstFile(uf); + +	for (i = 0; i < gi.number_entry; i++) +	{ +		err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0); +		if (err != UNZ_OK) { +			break; +		} +		if (file_info.uncompressed_size > 0) { +			fs_headerLongs[fs_numHeaderLongs++] = LittleLong(file_info.crc); +		} +		Q_strlwr( filename_inzip ); +		hash = FS_HashFileName(filename_inzip, pack->hashSize); +		buildBuffer[i].name = namePtr; +		strcpy( buildBuffer[i].name, filename_inzip ); +		namePtr += strlen(filename_inzip) + 1; +		// store the file position in the zip +		unzGetCurrentFileInfoPosition(uf, &buildBuffer[i].pos); +		// +		buildBuffer[i].next = pack->hashTable[hash]; +		pack->hashTable[hash] = &buildBuffer[i]; +		unzGoToNextFile(uf); +	} + +	pack->checksum = Com_BlockChecksum( &fs_headerLongs[ 1 ], 4 * ( fs_numHeaderLongs - 1 ) ); +	pack->pure_checksum = Com_BlockChecksum( fs_headerLongs, 4 * fs_numHeaderLongs ); +	pack->checksum = LittleLong( pack->checksum ); +	pack->pure_checksum = LittleLong( pack->pure_checksum ); + +	Z_Free(fs_headerLongs); + +	pack->buildBuffer = buildBuffer; +	return pack; +} + +/* +================================================================================= + +DIRECTORY SCANNING FUNCTIONS + +================================================================================= +*/ + +#define	MAX_FOUND_FILES	0x1000 + +static int FS_ReturnPath( const char *zname, char *zpath, int *depth ) { +	int len, at, newdep; + +	newdep = 0; +	zpath[0] = 0; +	len = 0; +	at = 0; + +	while(zname[at] != 0) +	{ +		if (zname[at]=='/' || zname[at]=='\\') { +			len = at; +			newdep++; +		} +		at++; +	} +	strcpy(zpath, zname); +	zpath[len] = 0; +	*depth = newdep; + +	return len; +} + +/* +================== +FS_AddFileToList +================== +*/ +static int FS_AddFileToList( char *name, char *list[MAX_FOUND_FILES], int nfiles ) { +	int		i; + +	if ( nfiles == MAX_FOUND_FILES - 1 ) { +		return nfiles; +	} +	for ( i = 0 ; i < nfiles ; i++ ) { +		if ( !Q_stricmp( name, list[i] ) ) { +			return nfiles;		// allready in list +		} +	} +	list[nfiles] = CopyString( name ); +	nfiles++; + +	return nfiles; +} + +/* +=============== +FS_ListFilteredFiles + +Returns a uniqued list of files that match the given criteria +from all search paths +=============== +*/ +char **FS_ListFilteredFiles( const char *path, const char *extension, char *filter, int *numfiles ) { +	int				nfiles; +	char			**listCopy; +	char			*list[MAX_FOUND_FILES]; +	searchpath_t	*search; +	int				i; +	int				pathLength; +	int				extensionLength; +	int				length, pathDepth, temp; +	pack_t			*pak; +	fileInPack_t	*buildBuffer; +	char			zpath[MAX_ZPATH]; + +	if ( !fs_searchpaths ) { +		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); +	} + +	if ( !path ) { +		*numfiles = 0; +		return NULL; +	} +	if ( !extension ) { +		extension = ""; +	} + +	pathLength = strlen( path ); +	if ( path[pathLength-1] == '\\' || path[pathLength-1] == '/' ) { +		pathLength--; +	} +	extensionLength = strlen( extension ); +	nfiles = 0; +	FS_ReturnPath(path, zpath, &pathDepth); + +	// +	// search through the path, one element at a time, adding to list +	// +	for (search = fs_searchpaths ; search ; search = search->next) { +		// is the element a pak file? +		if (search->pack) { + +			//ZOID:  If we are pure, don't search for files on paks that +			// aren't on the pure list +			if ( !FS_PakIsPure(search->pack) ) { +				continue; +			} + +			// look through all the pak file elements +			pak = search->pack; +			buildBuffer = pak->buildBuffer; +			for (i = 0; i < pak->numfiles; i++) { +				char	*name; +				int		zpathLen, depth; + +				// check for directory match +				name = buildBuffer[i].name; +				// +				if (filter) { +					// case insensitive +					if (!Com_FilterPath( filter, name, qfalse )) +						continue; +					// unique the match +					nfiles = FS_AddFileToList( name, list, nfiles ); +				} +				else { + +					zpathLen = FS_ReturnPath(name, zpath, &depth); + +					if ( (depth-pathDepth)>2 || pathLength > zpathLen || Q_stricmpn( name, path, pathLength ) ) { +						continue; +					} + +					// check for extension match +					length = strlen( name ); +					if ( length < extensionLength ) { +						continue; +					} + +					if ( Q_stricmp( name + length - extensionLength, extension ) ) { +						continue; +					} +					// unique the match + +					temp = pathLength; +					if (pathLength) { +						temp++;		// include the '/' +					} +					nfiles = FS_AddFileToList( name + temp, list, nfiles ); +				} +			} +		} else if (search->dir) { // scan for files in the filesystem +			char	*netpath; +			int		numSysFiles; +			char	**sysFiles; +			char	*name; + +			// don't scan directories for files if we are pure or restricted +			if ( fs_restrict->integer || fs_numServerPaks ) { +		        continue; +		    } else { +				netpath = FS_BuildOSPath( search->dir->path, search->dir->gamedir, path ); +				sysFiles = Sys_ListFiles( netpath, extension, filter, &numSysFiles, qfalse ); +				for ( i = 0 ; i < numSysFiles ; i++ ) { +					// unique the match +					name = sysFiles[i]; +					nfiles = FS_AddFileToList( name, list, nfiles ); +				} +				Sys_FreeFileList( sysFiles ); +			} +		}		 +	} + +	// return a copy of the list +	*numfiles = nfiles; + +	if ( !nfiles ) { +		return NULL; +	} + +	listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) ); +	for ( i = 0 ; i < nfiles ; i++ ) { +		listCopy[i] = list[i]; +	} +	listCopy[i] = NULL; + +	return listCopy; +} + +/* +================= +FS_ListFiles +================= +*/ +char **FS_ListFiles( const char *path, const char *extension, int *numfiles ) { +	return FS_ListFilteredFiles( path, extension, NULL, numfiles ); +} + +/* +================= +FS_FreeFileList +================= +*/ +void FS_FreeFileList( char **list ) { +	int		i; + +	if ( !fs_searchpaths ) { +		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); +	} + +	if ( !list ) { +		return; +	} + +	for ( i = 0 ; list[i] ; i++ ) { +		Z_Free( list[i] ); +	} + +	Z_Free( list ); +} + + +/* +================ +FS_GetFileList +================ +*/ +int	FS_GetFileList(  const char *path, const char *extension, char *listbuf, int bufsize ) { +	int		nFiles, i, nTotal, nLen; +	char **pFiles = NULL; + +	*listbuf = 0; +	nFiles = 0; +	nTotal = 0; + +	if (Q_stricmp(path, "$modlist") == 0) { +		return FS_GetModList(listbuf, bufsize); +	} + +	pFiles = FS_ListFiles(path, extension, &nFiles); + +	for (i =0; i < nFiles; i++) { +		nLen = strlen(pFiles[i]) + 1; +		if (nTotal + nLen + 1 < bufsize) { +			strcpy(listbuf, pFiles[i]); +			listbuf += nLen; +			nTotal += nLen; +		} +		else { +			nFiles = i; +			break; +		} +	} + +	FS_FreeFileList(pFiles); + +	return nFiles; +} + +/* +======================= +Sys_ConcatenateFileLists + +mkv: Naive implementation. Concatenates three lists into a +     new list, and frees the old lists from the heap. +bk001129 - from cvs1.17 (mkv) + +FIXME TTimo those two should move to common.c next to Sys_ListFiles +======================= + */ +static unsigned int Sys_CountFileList(char **list) +{ +  int i = 0; + +  if (list) +  { +    while (*list) +    { +      list++; +      i++; +    } +  } +  return i; +} + +static char** Sys_ConcatenateFileLists( char **list0, char **list1, char **list2 ) +{ +  int totalLength = 0; +  char** cat = NULL, **dst, **src; + +  totalLength += Sys_CountFileList(list0); +  totalLength += Sys_CountFileList(list1); +  totalLength += Sys_CountFileList(list2); + +  /* Create new list. */ +  dst = cat = Z_Malloc( ( totalLength + 1 ) * sizeof( char* ) ); + +  /* Copy over lists. */ +  if (list0) +  { +    for (src = list0; *src; src++, dst++) +      *dst = *src; +  } +  if (list1) +  { +    for (src = list1; *src; src++, dst++) +      *dst = *src; +  } +  if (list2) +  { +    for (src = list2; *src; src++, dst++) +      *dst = *src; +  } + +  // Terminate the list +  *dst = NULL; + +  // Free our old lists. +  // NOTE: not freeing their content, it's been merged in dst and still being used +  if (list0) Z_Free( list0 ); +  if (list1) Z_Free( list1 ); +  if (list2) Z_Free( list2 ); + +  return cat; +} + +/* +================ +FS_GetModList + +Returns a list of mod directory names +A mod directory is a peer to base with a pk3 in it +The directories are searched in base path, cd path and home path +================ +*/ +int	FS_GetModList( char *listbuf, int bufsize ) { +  int		nMods, i, j, nTotal, nLen, nPaks, nPotential, nDescLen; +  char **pFiles = NULL; +  char **pPaks = NULL; +  char *name, *path; +  char descPath[MAX_OSPATH]; +  fileHandle_t descHandle; + +  int dummy; +  char **pFiles0 = NULL; +  char **pFiles1 = NULL; +  char **pFiles2 = NULL; +  qboolean bDrop = qfalse; + +  *listbuf = 0; +  nMods = nPotential = nTotal = 0; + +  pFiles0 = Sys_ListFiles( fs_homepath->string, NULL, NULL, &dummy, qtrue ); +  pFiles1 = Sys_ListFiles( fs_basepath->string, NULL, NULL, &dummy, qtrue ); +  pFiles2 = Sys_ListFiles( fs_cdpath->string, NULL, NULL, &dummy, qtrue ); +  // we searched for mods in the three paths +  // it is likely that we have duplicate names now, which we will cleanup below +  pFiles = Sys_ConcatenateFileLists( pFiles0, pFiles1, pFiles2 ); +  nPotential = Sys_CountFileList(pFiles); + +  for ( i = 0 ; i < nPotential ; i++ ) { +    name = pFiles[i]; +    // NOTE: cleaner would involve more changes +    // ignore duplicate mod directories +    if (i!=0) { +      bDrop = qfalse; +      for(j=0; j<i; j++) +      { +        if (Q_stricmp(pFiles[j],name)==0) { +          // this one can be dropped +          bDrop = qtrue; +          break; +        } +      } +    } +    if (bDrop) { +      continue; +    } +    // we drop BASEGAME "." and ".." +    if (Q_stricmp(name, BASEGAME) && Q_stricmpn(name, ".", 1)) { +      // now we need to find some .pk3 files to validate the mod +      // NOTE TTimo: (actually I'm not sure why .. what if it's a mod under developement with no .pk3?) +      // we didn't keep the information when we merged the directory names, as to what OS Path it was found under +      //   so it could be in base path, cd path or home path +      //   we will try each three of them here (yes, it's a bit messy) +      path = FS_BuildOSPath( fs_basepath->string, name, "" ); +      nPaks = 0; +      pPaks = Sys_ListFiles(path, ".pk3", NULL, &nPaks, qfalse);  +      Sys_FreeFileList( pPaks ); // we only use Sys_ListFiles to check wether .pk3 files are present + +      /* Try on cd path */ +      if( nPaks <= 0 ) { +        path = FS_BuildOSPath( fs_cdpath->string, name, "" ); +        nPaks = 0; +        pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse ); +        Sys_FreeFileList( pPaks ); +      } + +      /* try on home path */ +      if ( nPaks <= 0 ) +      { +        path = FS_BuildOSPath( fs_homepath->string, name, "" ); +        nPaks = 0; +        pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse ); +        Sys_FreeFileList( pPaks ); +      } + +      if (nPaks > 0) { +        nLen = strlen(name) + 1; +        // nLen is the length of the mod path +        // we need to see if there is a description available +        descPath[0] = '\0'; +        strcpy(descPath, name); +        strcat(descPath, "/description.txt"); +        nDescLen = FS_SV_FOpenFileRead( descPath, &descHandle ); +        if ( nDescLen > 0 && descHandle) { +          FILE *file; +          file = FS_FileForHandle(descHandle); +          Com_Memset( descPath, 0, sizeof( descPath ) ); +          nDescLen = fread(descPath, 1, 48, file); +          if (nDescLen >= 0) { +            descPath[nDescLen] = '\0'; +          } +          FS_FCloseFile(descHandle); +        } else { +          strcpy(descPath, name); +        } +        nDescLen = strlen(descPath) + 1; + +        if (nTotal + nLen + 1 + nDescLen + 1 < bufsize) { +          strcpy(listbuf, name); +          listbuf += nLen; +          strcpy(listbuf, descPath); +          listbuf += nDescLen; +          nTotal += nLen + nDescLen; +          nMods++; +        } +        else { +          break; +        } +      } +    } +  } +  Sys_FreeFileList( pFiles ); + +  return nMods; +} + + + + +//============================================================================ + +/* +================ +FS_Dir_f +================ +*/ +void FS_Dir_f( void ) { +	char	*path; +	char	*extension; +	char	**dirnames; +	int		ndirs; +	int		i; + +	if ( Cmd_Argc() < 2 || Cmd_Argc() > 3 ) { +		Com_Printf( "usage: dir <directory> [extension]\n" ); +		return; +	} + +	if ( Cmd_Argc() == 2 ) { +		path = Cmd_Argv( 1 ); +		extension = ""; +	} else { +		path = Cmd_Argv( 1 ); +		extension = Cmd_Argv( 2 ); +	} + +	Com_Printf( "Directory of %s %s\n", path, extension ); +	Com_Printf( "---------------\n" ); + +	dirnames = FS_ListFiles( path, extension, &ndirs ); + +	for ( i = 0; i < ndirs; i++ ) { +		Com_Printf( "%s\n", dirnames[i] ); +	} +	FS_FreeFileList( dirnames ); +} + +/* +=========== +FS_ConvertPath +=========== +*/ +void FS_ConvertPath( char *s ) { +	while (*s) { +		if ( *s == '\\' || *s == ':' ) { +			*s = '/'; +		} +		s++; +	} +} + +/* +=========== +FS_PathCmp + +Ignore case and seprator char distinctions +=========== +*/ +int FS_PathCmp( const char *s1, const char *s2 ) { +	int		c1, c2; +	 +	do { +		c1 = *s1++; +		c2 = *s2++; + +		if (c1 >= 'a' && c1 <= 'z') { +			c1 -= ('a' - 'A'); +		} +		if (c2 >= 'a' && c2 <= 'z') { +			c2 -= ('a' - 'A'); +		} + +		if ( c1 == '\\' || c1 == ':' ) { +			c1 = '/'; +		} +		if ( c2 == '\\' || c2 == ':' ) { +			c2 = '/'; +		} +		 +		if (c1 < c2) { +			return -1;		// strings not equal +		} +		if (c1 > c2) { +			return 1; +		} +	} while (c1); +	 +	return 0;		// strings are equal +} + +/* +================ +FS_SortFileList +================ +*/ +void FS_SortFileList(char **filelist, int numfiles) { +	int i, j, k, numsortedfiles; +	char **sortedlist; + +	sortedlist = Z_Malloc( ( numfiles + 1 ) * sizeof( *sortedlist ) ); +	sortedlist[0] = NULL; +	numsortedfiles = 0; +	for (i = 0; i < numfiles; i++) { +		for (j = 0; j < numsortedfiles; j++) { +			if (FS_PathCmp(filelist[i], sortedlist[j]) < 0) { +				break; +			} +		} +		for (k = numsortedfiles; k > j; k--) { +			sortedlist[k] = sortedlist[k-1]; +		} +		sortedlist[j] = filelist[i]; +		numsortedfiles++; +	} +	Com_Memcpy(filelist, sortedlist, numfiles * sizeof( *filelist ) ); +	Z_Free(sortedlist); +} + +/* +================ +FS_NewDir_f +================ +*/ +void FS_NewDir_f( void ) { +	char	*filter; +	char	**dirnames; +	int		ndirs; +	int		i; + +	if ( Cmd_Argc() < 2 ) { +		Com_Printf( "usage: fdir <filter>\n" ); +		Com_Printf( "example: fdir *q3dm*.bsp\n"); +		return; +	} + +	filter = Cmd_Argv( 1 ); + +	Com_Printf( "---------------\n" ); + +	dirnames = FS_ListFilteredFiles( "", "", filter, &ndirs ); + +	FS_SortFileList(dirnames, ndirs); + +	for ( i = 0; i < ndirs; i++ ) { +		FS_ConvertPath(dirnames[i]); +		Com_Printf( "%s\n", dirnames[i] ); +	} +	Com_Printf( "%d files listed\n", ndirs ); +	FS_FreeFileList( dirnames ); +} + +/* +============ +FS_Path_f + +============ +*/ +void FS_Path_f( void ) { +	searchpath_t	*s; +	int				i; + +	Com_Printf ("Current search path:\n"); +	for (s = fs_searchpaths; s; s = s->next) { +		if (s->pack) { +			Com_Printf ("%s (%i files)\n", s->pack->pakFilename, s->pack->numfiles); +			if ( fs_numServerPaks ) { +				if ( !FS_PakIsPure(s->pack) ) { +					Com_Printf( "    not on the pure list\n" ); +				} else { +					Com_Printf( "    on the pure list\n" ); +				} +			} +		} else { +			Com_Printf ("%s/%s\n", s->dir->path, s->dir->gamedir ); +		} +	} + + +	Com_Printf( "\n" ); +	for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) { +		if ( fsh[i].handleFiles.file.o ) { +			Com_Printf( "handle %i: %s\n", i, fsh[i].name ); +		} +	} +} + +/* +============ +FS_TouchFile_f + +The only purpose of this function is to allow game script files to copy +arbitrary files furing an "fs_copyfiles 1" run. +============ +*/ +void FS_TouchFile_f( void ) { +	fileHandle_t	f; + +	if ( Cmd_Argc() != 2 ) { +		Com_Printf( "Usage: touchFile <file>\n" ); +		return; +	} + +	FS_FOpenFileRead( Cmd_Argv( 1 ), &f, qfalse ); +	if ( f ) { +		FS_FCloseFile( f ); +	} +} + +//=========================================================================== + + +static int QDECL paksort( const void *a, const void *b ) { +	char	*aa, *bb; + +	aa = *(char **)a; +	bb = *(char **)b; + +	return FS_PathCmp( aa, bb ); +} + +/* +================ +FS_AddGameDirectory + +Sets fs_gamedir, adds the directory to the head of the path, +then loads the zip headers +================ +*/ +static void FS_AddGameDirectory( const char *path, const char *dir ) { +	searchpath_t	*sp; +	int				i; +	searchpath_t	*search; +	pack_t			*pak; +	char			*pakfile; +	int				numfiles; +	char			**pakfiles; + +	// this fixes the case where fs_basepath is the same as fs_cdpath +	// which happens on full installs +	for ( sp = fs_searchpaths ; sp ; sp = sp->next ) { +		if ( sp->dir && !Q_stricmp(sp->dir->path, path) && !Q_stricmp(sp->dir->gamedir, dir)) { +			return;			// we've already got this one +		} +	} +	 +	Q_strncpyz( fs_gamedir, dir, sizeof( fs_gamedir ) ); + +	// +	// add the directory to the search path +	// +	search = Z_Malloc (sizeof(searchpath_t)); +	search->dir = Z_Malloc( sizeof( *search->dir ) ); + +	Q_strncpyz( search->dir->path, path, sizeof( search->dir->path ) ); +	Q_strncpyz( search->dir->gamedir, dir, sizeof( search->dir->gamedir ) ); +	search->next = fs_searchpaths; +	fs_searchpaths = search; + +	// find all pak files in this directory +	pakfile = FS_BuildOSPath( path, dir, "" ); +	pakfile[ strlen(pakfile) - 1 ] = 0;	// strip the trailing slash + +	pakfiles = Sys_ListFiles( pakfile, ".pk3", NULL, &numfiles, qfalse ); + +	qsort( pakfiles, numfiles, sizeof(char*), paksort ); + +	for ( i = 0 ; i < numfiles ; i++ ) { +		pakfile = FS_BuildOSPath( path, dir, pakfiles[i] ); +		if ( ( pak = FS_LoadZipFile( pakfile, pakfiles[i] ) ) == 0 ) +			continue; +		// store the game name for downloading +		strcpy(pak->pakGamename, dir); + +		search = Z_Malloc (sizeof(searchpath_t)); +		search->pack = pak; +		search->next = fs_searchpaths; +		fs_searchpaths = search; +	} + +	// done +	Sys_FreeFileList( pakfiles ); +} + +/* +================ +FS_idPak +================ +*/ +qboolean FS_idPak( char *pak, char *base ) { +	int i; + +	for (i = 0; i < NUM_ID_PAKS; i++) { +		if ( !FS_FilenameCompare(pak, va("%s/pak%d", base, i)) ) { +			break; +		} +	} +	if (i < NUM_ID_PAKS) { +		return qtrue; +	} +	return qfalse; +} + +/* +================ +FS_idPak + +Check whether the string contains stuff like "../" to prevent directory traversal bugs +and return qtrue if it does. +================ +*/ + +qboolean FS_CheckDirTraversal(const char *checkdir) +{ +	if(strstr(checkdir, "../") || strstr(checkdir, "..\\")) +		return qtrue; +	 +	return qfalse; +} + +/* +================ +FS_ComparePaks + +---------------- +dlstring == qtrue + +Returns a list of pak files that we should download from the server. They all get stored +in the current gamedir and an FS_Restart will be fired up after we download them all. + +The string is the format: + +@remotename@localname [repeat] + +static int		fs_numServerReferencedPaks; +static int		fs_serverReferencedPaks[MAX_SEARCH_PATHS]; +static char		*fs_serverReferencedPakNames[MAX_SEARCH_PATHS]; + +---------------- +dlstring == qfalse + +we are not interested in a download string format, we want something human-readable +(this is used for diagnostics while connecting to a pure server) + +================ +*/ +qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) { +	searchpath_t	*sp; +	qboolean havepak, badchecksum; +	char *origpos = neededpaks; +	int i; + +	if (!fs_numServerReferencedPaks) +		return qfalse; // Server didn't send any pack information along + +	*neededpaks = 0; + +	for ( i = 0 ; i < fs_numServerReferencedPaks ; i++ ) +	{ +		// Ok, see if we have this pak file +		badchecksum = qfalse; +		havepak = qfalse; + +		// never autodownload any of the tremulous paks +		if ( FS_idPak(fs_serverReferencedPakNames[i], BASEGAME) ) { +			continue; +		} + +		// Make sure the server cannot make us write to non-quake3 directories. +		if(FS_CheckDirTraversal(fs_serverReferencedPakNames[i])) +                { +			Com_Printf("WARNING: Invalid download name %s\n", fs_serverReferencedPakNames[i]); +                        continue; +                } + +		for ( sp = fs_searchpaths ; sp ; sp = sp->next ) { +			if ( sp->pack && sp->pack->checksum == fs_serverReferencedPaks[i] ) { +				havepak = qtrue; // This is it! +				break; +			} +		} + +		if ( !havepak && fs_serverReferencedPakNames[i] && *fs_serverReferencedPakNames[i] ) {  +			// Don't got it + +      if (dlstring) +      { +	// We need this to make sure we won't hit the end of the buffer or the server could +	// overwrite non-pk3 files on clients by writing so much crap into neededpaks that +	// Q_strcat cuts off the .pk3 extension. +	 +	origpos += strlen(origpos); +	 +        // Remote name +        Q_strcat( neededpaks, len, "@"); +        Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] ); +        Q_strcat( neededpaks, len, ".pk3" ); + +        // Local name +        Q_strcat( neededpaks, len, "@"); +        // Do we have one with the same name? +        if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) ) +        { +          char st[MAX_ZPATH]; +          // We already have one called this, we need to download it to another name +          // Make something up with the checksum in it +          Com_sprintf( st, sizeof( st ), "%s.%08x.pk3", fs_serverReferencedPakNames[i], fs_serverReferencedPaks[i] ); +          Q_strcat( neededpaks, len, st ); +        } else +        { +          Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] ); +          Q_strcat( neededpaks, len, ".pk3" ); +        } +         +        // Find out whether it might have overflowed the buffer and don't add this file to the +        // list if that is the case. +        if(strlen(origpos) + (origpos - neededpaks) >= len - 1) +	{ +		*origpos = '\0'; +		break; +	} +      } +      else +      { +        Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] ); +			  Q_strcat( neededpaks, len, ".pk3" ); +        // Do we have one with the same name? +        if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) ) +        { +          Q_strcat( neededpaks, len, " (local file exists with wrong checksum)"); +        } +        Q_strcat( neededpaks, len, "\n"); +      } +		} +	} + +	if ( *neededpaks ) { +		return qtrue; +	} + +	return qfalse; // We have them all +} + +/* +================ +FS_Shutdown + +Frees all resources. +================ +*/ +void FS_Shutdown( qboolean closemfp ) { +	searchpath_t	*p, *next; +	int	i; + +	for(i = 0; i < MAX_FILE_HANDLES; i++) { +		if (fsh[i].fileSize) { +			FS_FCloseFile(i); +		} +	} + +	// free everything +	for ( p = fs_searchpaths ; p ; p = next ) { +		next = p->next; + +		if ( p->pack ) { +			unzClose(p->pack->handle); +			Z_Free( p->pack->buildBuffer ); +			Z_Free( p->pack ); +		} +		if ( p->dir ) { +			Z_Free( p->dir ); +		} +		Z_Free( p ); +	} + +	// any FS_ calls will now be an error until reinitialized +	fs_searchpaths = NULL; + +	Cmd_RemoveCommand( "path" ); +	Cmd_RemoveCommand( "dir" ); +	Cmd_RemoveCommand( "fdir" ); +	Cmd_RemoveCommand( "touchFile" ); + +#ifdef FS_MISSING +	if (closemfp) { +		fclose(missingFiles); +	} +#endif +} + +/* +================ +FS_ReorderPurePaks +NOTE TTimo: the reordering that happens here is not reflected in the cvars (\cvarlist *pak*) +  this can lead to misleading situations, see https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540 +================ +*/ +static void FS_ReorderPurePaks( void ) +{ +	searchpath_t *s; +	int i; +	searchpath_t **p_insert_index, // for linked list reordering +		**p_previous; // when doing the scan +	 +	// only relevant when connected to pure server +	if ( !fs_numServerPaks ) +		return; +	 +	fs_reordered = qfalse; +	 +	p_insert_index = &fs_searchpaths; // we insert in order at the beginning of the list  +	for ( i = 0 ; i < fs_numServerPaks ; i++ ) { +		p_previous = p_insert_index; // track the pointer-to-current-item +		for (s = *p_insert_index; s; s = s->next) { +			// the part of the list before p_insert_index has been sorted already +			if (s->pack && fs_serverPaks[i] == s->pack->checksum) { +				fs_reordered = qtrue; +				// move this element to the insert list +				*p_previous = s->next; +				s->next = *p_insert_index; +				*p_insert_index = s; +				// increment insert list +				p_insert_index = &s->next; +				break; // iterate to next server pack +			} +			p_previous = &s->next;  +		} +	} +} + +/* +================ +FS_Startup +================ +*/ +static void FS_Startup( const char *gameName ) { +        const char *homePath; + +	Com_Printf( "----- FS_Startup -----\n" ); + +	fs_debug = Cvar_Get( "fs_debug", "0", 0 ); +	fs_copyfiles = Cvar_Get( "fs_copyfiles", "0", CVAR_INIT ); +	fs_cdpath = Cvar_Get ("fs_cdpath", Sys_DefaultCDPath(), CVAR_INIT ); +	fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultInstallPath(), CVAR_INIT ); +	fs_basegame = Cvar_Get ("fs_basegame", "", CVAR_INIT ); +  homePath = Sys_DefaultHomePath(); +  if (!homePath || !homePath[0]) { +		homePath = fs_basepath->string; +	} +	fs_homepath = Cvar_Get ("fs_homepath", homePath, CVAR_INIT ); +	fs_gamedirvar = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); +	fs_restrict = Cvar_Get ("fs_restrict", "", CVAR_INIT ); + +	// add search path elements in reverse priority order +	if (fs_cdpath->string[0]) { +		FS_AddGameDirectory( fs_cdpath->string, gameName ); +	} +	if (fs_basepath->string[0]) { +		FS_AddGameDirectory( fs_basepath->string, gameName ); +	} +  // fs_homepath is somewhat particular to *nix systems, only add if relevant +  // NOTE: same filtering below for mods and basegame +	if (fs_basepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) { +		FS_AddGameDirectory ( fs_homepath->string, gameName ); +	} +         +	// check for additional base game so mods can be based upon other mods +	if ( fs_basegame->string[0] && !Q_stricmp( gameName, BASEGAME ) && Q_stricmp( fs_basegame->string, gameName ) ) { +		if (fs_cdpath->string[0]) { +			FS_AddGameDirectory(fs_cdpath->string, fs_basegame->string); +		} +		if (fs_basepath->string[0]) { +			FS_AddGameDirectory(fs_basepath->string, fs_basegame->string); +		} +		if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) { +			FS_AddGameDirectory(fs_homepath->string, fs_basegame->string); +		} +	} + +	// check for additional game folder for mods +	if ( fs_gamedirvar->string[0] && !Q_stricmp( gameName, BASEGAME ) && Q_stricmp( fs_gamedirvar->string, gameName ) ) { +		if (fs_cdpath->string[0]) { +			FS_AddGameDirectory(fs_cdpath->string, fs_gamedirvar->string); +		} +		if (fs_basepath->string[0]) { +			FS_AddGameDirectory(fs_basepath->string, fs_gamedirvar->string); +		} +		if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) { +			FS_AddGameDirectory(fs_homepath->string, fs_gamedirvar->string); +		} +	} + +	// add our commands +	Cmd_AddCommand ("path", FS_Path_f); +	Cmd_AddCommand ("dir", FS_Dir_f ); +	Cmd_AddCommand ("fdir", FS_NewDir_f ); +	Cmd_AddCommand ("touchFile", FS_TouchFile_f ); + +	// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=506 +	// reorder the pure pk3 files according to server order +	FS_ReorderPurePaks(); +	 +	// print the current search paths +	FS_Path_f(); + +	fs_gamedirvar->modified = qfalse; // We just loaded, it's not modified + +	Com_Printf( "----------------------\n" ); + +#ifdef FS_MISSING +	if (missingFiles == NULL) { +		missingFiles = fopen( "\\missing.txt", "ab" ); +	} +#endif +	Com_Printf( "%d files in pk3 files\n", fs_packFiles ); +} + +/* +===================== +FS_GamePureChecksum + +Returns the checksum of the pk3 from which the server loaded the qagame.qvm +===================== +*/ +const char *FS_GamePureChecksum( void ) { +	static char	info[MAX_STRING_TOKENS]; +	searchpath_t *search; + +	info[0] = 0; + +	for ( search = fs_searchpaths ; search ; search = search->next ) { +		// is the element a pak file? +		if ( search->pack ) { +			if (search->pack->referenced & FS_QAGAME_REF) { +				Com_sprintf(info, sizeof(info), "%d", search->pack->checksum); +			} +		} +	} + +	return info; +} + +/* +===================== +FS_LoadedPakChecksums + +Returns a space separated string containing the checksums of all loaded pk3 files. +Servers with sv_pure set will get this string and pass it to clients. +===================== +*/ +const char *FS_LoadedPakChecksums( void ) { +	static char	info[BIG_INFO_STRING]; +	searchpath_t	*search; + +	info[0] = 0; + +	for ( search = fs_searchpaths ; search ; search = search->next ) { +		// is the element a pak file?  +		if ( !search->pack ) { +			continue; +		} + +		Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) ); +	} + +	return info; +} + +/* +===================== +FS_LoadedPakNames + +Returns a space separated string containing the names of all loaded pk3 files. +Servers with sv_pure set will get this string and pass it to clients. +===================== +*/ +const char *FS_LoadedPakNames( void ) { +	static char	info[BIG_INFO_STRING]; +	searchpath_t	*search; + +	info[0] = 0; + +	for ( search = fs_searchpaths ; search ; search = search->next ) { +		// is the element a pak file? +		if ( !search->pack ) { +			continue; +		} + +		if (*info) { +			Q_strcat(info, sizeof( info ), " " ); +		} +		Q_strcat( info, sizeof( info ), search->pack->pakBasename ); +	} + +	return info; +} + +/* +===================== +FS_LoadedPakPureChecksums + +Returns a space separated string containing the pure checksums of all loaded pk3 files. +Servers with sv_pure use these checksums to compare with the checksums the clients send +back to the server. +===================== +*/ +const char *FS_LoadedPakPureChecksums( void ) { +	static char	info[BIG_INFO_STRING]; +	searchpath_t	*search; + +	info[0] = 0; + +	for ( search = fs_searchpaths ; search ; search = search->next ) { +		// is the element a pak file?  +		if ( !search->pack ) { +			continue; +		} + +		Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) ); +	} + +	return info; +} + +/* +===================== +FS_ReferencedPakChecksums + +Returns a space separated string containing the checksums of all referenced pk3 files. +The server will send this to the clients so they can check which files should be auto-downloaded.  +===================== +*/ +const char *FS_ReferencedPakChecksums( void ) { +	static char	info[BIG_INFO_STRING]; +	searchpath_t *search; + +	info[0] = 0; + + +	for ( search = fs_searchpaths ; search ; search = search->next ) { +		// is the element a pak file? +		if ( search->pack ) { +			if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) { +				Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) ); +			} +		} +	} + +	return info; +} + +/* +===================== +FS_ReferencedPakPureChecksums + +Returns a space separated string containing the pure checksums of all referenced pk3 files. +Servers with sv_pure set will get this string back from clients for pure validation  + +The string has a specific order, "cgame ui @ ref1 ref2 ref3 ..." +===================== +*/ +const char *FS_ReferencedPakPureChecksums( void ) { +	static char	info[BIG_INFO_STRING]; +	searchpath_t	*search; +	int nFlags, numPaks, checksum; + +	info[0] = 0; + +	checksum = fs_checksumFeed; +	numPaks = 0; +	for (nFlags = FS_CGAME_REF; nFlags; nFlags = nFlags >> 1) { +		if (nFlags & FS_GENERAL_REF) { +			// add a delimter between must haves and general refs +			//Q_strcat(info, sizeof(info), "@ "); +			info[strlen(info)+1] = '\0'; +			info[strlen(info)+2] = '\0'; +			info[strlen(info)] = '@'; +			info[strlen(info)] = ' '; +		} +		for ( search = fs_searchpaths ; search ; search = search->next ) { +			// is the element a pak file and has it been referenced based on flag? +			if ( search->pack && (search->pack->referenced & nFlags)) { +				Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) ); +				if (nFlags & (FS_CGAME_REF | FS_UI_REF)) { +					break; +				} +				checksum ^= search->pack->pure_checksum; +				numPaks++; +			} +		} +		if (fs_fakeChkSum != 0) { +			// only added if a non-pure file is referenced +			Q_strcat( info, sizeof( info ), va("%i ", fs_fakeChkSum ) ); +		} +	} +	// last checksum is the encoded number of referenced pk3s +	checksum ^= numPaks; +	Q_strcat( info, sizeof( info ), va("%i ", checksum ) ); + +	return info; +} + +/* +===================== +FS_ReferencedPakNames + +Returns a space separated string containing the names of all referenced pk3 files. +The server will send this to the clients so they can check which files should be auto-downloaded.  +===================== +*/ +const char *FS_ReferencedPakNames( void ) { +	static char	info[BIG_INFO_STRING]; +	searchpath_t	*search; + +	info[0] = 0; + +	// we want to return ALL pk3's from the fs_game path +	// and referenced one's from base +	for ( search = fs_searchpaths ; search ; search = search->next ) { +		// is the element a pak file? +		if ( search->pack ) { +			if (*info) { +				Q_strcat(info, sizeof( info ), " " ); +			} +			if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) { +				Q_strcat( info, sizeof( info ), search->pack->pakGamename ); +				Q_strcat( info, sizeof( info ), "/" ); +				Q_strcat( info, sizeof( info ), search->pack->pakBasename ); +			} +		} +	} + +	return info; +} + +/* +===================== +FS_ClearPakReferences +===================== +*/ +void FS_ClearPakReferences( int flags ) { +	searchpath_t *search; + +	if ( !flags ) { +		flags = -1; +	} +	for ( search = fs_searchpaths; search; search = search->next ) { +		// is the element a pak file and has it been referenced? +		if ( search->pack ) { +			search->pack->referenced &= ~flags; +		} +	} +} + + +/* +===================== +FS_PureServerSetLoadedPaks + +If the string is empty, all data sources will be allowed. +If not empty, only pk3 files that match one of the space +separated checksums will be checked for files, with the +exception of .cfg and .dat files. +===================== +*/ +void FS_PureServerSetLoadedPaks( const char *pakSums, const char *pakNames ) { +	int		i, c, d; + +	Cmd_TokenizeString( pakSums ); + +	c = Cmd_Argc(); +	if ( c > MAX_SEARCH_PATHS ) { +		c = MAX_SEARCH_PATHS; +	} + +	fs_numServerPaks = c; + +	for ( i = 0 ; i < c ; i++ ) { +		fs_serverPaks[i] = atoi( Cmd_Argv( i ) ); +	} + +	if (fs_numServerPaks) { +		Com_DPrintf( "Connected to a pure server.\n" ); +	} +	else +	{ +		if (fs_reordered) +		{ +			// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540 +			// force a restart to make sure the search order will be correct +			Com_DPrintf( "FS search reorder is required\n" ); +			FS_Restart(fs_checksumFeed); +			return; +		} +	} + +	for ( i = 0 ; i < c ; i++ ) { +		if (fs_serverPakNames[i]) { +			Z_Free(fs_serverPakNames[i]); +		} +		fs_serverPakNames[i] = NULL; +	} +	if ( pakNames && *pakNames ) { +		Cmd_TokenizeString( pakNames ); + +		d = Cmd_Argc(); +		if ( d > MAX_SEARCH_PATHS ) { +			d = MAX_SEARCH_PATHS; +		} + +		for ( i = 0 ; i < d ; i++ ) { +			fs_serverPakNames[i] = CopyString( Cmd_Argv( i ) ); +		} +	} +} + +/* +===================== +FS_PureServerSetReferencedPaks + +The checksums and names of the pk3 files referenced at the server +are sent to the client and stored here. The client will use these +checksums to see if any pk3 files need to be auto-downloaded.  +===================== +*/ +void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames ) { +	int		i, c, d = 0; + +	Cmd_TokenizeString( pakSums ); + +	c = Cmd_Argc(); +	if ( c > MAX_SEARCH_PATHS ) { +		c = MAX_SEARCH_PATHS; +	} + +	for ( i = 0 ; i < c ; i++ ) { +		fs_serverReferencedPaks[i] = atoi( Cmd_Argv( i ) ); +	} + +	for (i = 0 ; i < sizeof(fs_serverReferencedPakNames) / sizeof(*fs_serverReferencedPakNames); i++) +	{ +		if(fs_serverReferencedPakNames[i]) +			Z_Free(fs_serverReferencedPakNames[i]); + +		fs_serverReferencedPakNames[i] = NULL; +	} + +	if ( pakNames && *pakNames ) { +		Cmd_TokenizeString( pakNames ); + +		d = Cmd_Argc(); + +		if(d > c) +			d = c; + +		for ( i = 0 ; i < d ; i++ ) { +			fs_serverReferencedPakNames[i] = CopyString( Cmd_Argv( i ) ); +		} +	} +	 +	// ensure that there are as many checksums as there are pak names. +	if(d < c) +		c = d; +	 +	fs_numServerReferencedPaks = c;	 +} + +/* +================ +FS_InitFilesystem + +Called only at inital startup, not when the filesystem +is resetting due to a game change +================ +*/ +void FS_InitFilesystem( void ) { +	// allow command line parms to override our defaults +	// we have to specially handle this, because normal command +	// line variable sets don't happen until after the filesystem +	// has already been initialized +	Com_StartupVariable( "fs_cdpath" ); +	Com_StartupVariable( "fs_basepath" ); +	Com_StartupVariable( "fs_homepath" ); +	Com_StartupVariable( "fs_game" ); +	Com_StartupVariable( "fs_copyfiles" ); +	Com_StartupVariable( "fs_restrict" ); + +	// try to start up normally +	FS_Startup( BASEGAME ); + +	// if we can't find default.cfg, assume that the paths are +	// busted and error out now, rather than getting an unreadable +	// graphics screen when the font fails to load +	if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) { +		Com_Error( ERR_FATAL, "Couldn't load default.cfg" ); +		// bk001208 - SafeMode see below, FIXME? +	} + +	Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase)); +	Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame)); + +  // bk001208 - SafeMode see below, FIXME? +} + + +/* +================ +FS_Restart +================ +*/ +void FS_Restart( int checksumFeed ) { + +	// free anything we currently have loaded +	FS_Shutdown(qfalse); + +	// set the checksum feed +	fs_checksumFeed = checksumFeed; + +	// clear pak references +	FS_ClearPakReferences(0); + +	// try to start up normally +	FS_Startup( BASEGAME ); + +	// if we can't find default.cfg, assume that the paths are +	// busted and error out now, rather than getting an unreadable +	// graphics screen when the font fails to load +	if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) { +		// this might happen when connecting to a pure server not using BASEGAME/pak0.pk3 +		// (for instance a TA demo server) +		if (lastValidBase[0]) { +			FS_PureServerSetLoadedPaks("", ""); +			Cvar_Set("fs_basepath", lastValidBase); +			Cvar_Set("fs_gamedirvar", lastValidGame); +			lastValidBase[0] = '\0'; +			lastValidGame[0] = '\0'; +			Cvar_Set( "fs_restrict", "0" ); +			FS_Restart(checksumFeed); +			Com_Error( ERR_DROP, "Invalid game folder\n" ); +			return; +		} +		Com_Error( ERR_FATAL, "Couldn't load default.cfg" ); +	} + +	// bk010116 - new check before safeMode +	if ( Q_stricmp(fs_gamedirvar->string, lastValidGame) ) { +		// skip the autogen.cfg if "safe" is on the command line +		if ( !Com_SafeMode() ) { +			Cbuf_AddText ("exec autogen.cfg\n"); +		} +	} + +	Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase)); +	Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame)); + +} + +/* +================= +FS_ConditionalRestart +restart if necessary +================= +*/ +qboolean FS_ConditionalRestart( int checksumFeed ) { +	if( fs_gamedirvar->modified || checksumFeed != fs_checksumFeed ) { +		FS_Restart( checksumFeed ); +		return qtrue; +	} +	return qfalse; +} + +/* +======================================================================================== + +Handle based file calls for virtual machines + +======================================================================================== +*/ + +int		FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) { +	int		r; +	qboolean	sync; + +	sync = qfalse; + +	switch( mode ) { +	case FS_READ: +		r = FS_FOpenFileRead( qpath, f, qtrue ); +		break; +	case FS_WRITE: +		*f = FS_FOpenFileWrite( qpath ); +		r = 0; +		if (*f == 0) { +			r = -1; +		} +		break; +	case FS_APPEND_SYNC: +		sync = qtrue; +	case FS_APPEND: +		*f = FS_FOpenFileAppend( qpath ); +		r = 0; +		if (*f == 0) { +			r = -1; +		} +		break; +	default: +		Com_Error( ERR_FATAL, "FSH_FOpenFile: bad mode" ); +		return -1; +	} + +	if (!f) { +		return r; +	} + +	if ( *f ) { +		if (fsh[*f].zipFile == qtrue) { +			fsh[*f].baseOffset = unztell(fsh[*f].handleFiles.file.z); +		} else { +			fsh[*f].baseOffset = ftell(fsh[*f].handleFiles.file.o); +		} +		fsh[*f].fileSize = r; +		fsh[*f].streamed = qfalse; + +		if (mode == FS_READ) { +			Sys_BeginStreamedFile( *f, 0x4000 ); +			fsh[*f].streamed = qtrue; +		} +	} +	fsh[*f].handleSync = sync; + +	return r; +} + +int		FS_FTell( fileHandle_t f ) { +	int pos; +	if (fsh[f].zipFile == qtrue) { +		pos = unztell(fsh[f].handleFiles.file.z); +	} else { +		pos = ftell(fsh[f].handleFiles.file.o); +	} +	return pos; +} + +void	FS_Flush( fileHandle_t f ) { +	fflush(fsh[f].handleFiles.file.o); +} + +void	FS_FilenameCompletion( const char *dir, const char *ext, +		qboolean stripExt, void(*callback)(const char *s) ) { +	char	**filenames; +	int		nfiles; +	int		i; +	char	filename[ MAX_STRING_CHARS ]; + +	filenames = FS_ListFilteredFiles( dir, ext, NULL, &nfiles ); + +	FS_SortFileList( filenames, nfiles ); + +	for( i = 0; i < nfiles; i++ ) { +		FS_ConvertPath( filenames[ i ] ); +		Q_strncpyz( filename, filenames[ i ], MAX_STRING_CHARS ); + +		if( stripExt ) { +			COM_StripExtension(filename, filename, sizeof(filename)); +		} + +		callback( filename ); +	} +	FS_FreeFileList( filenames ); +} diff --git a/src/qcommon/huffman.c b/src/qcommon/huffman.c new file mode 100644 index 0000000..a579944 --- /dev/null +++ b/src/qcommon/huffman.c @@ -0,0 +1,438 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ + +/* This is based on the Adaptive Huffman algorithm described in Sayood's Data + * Compression book.  The ranks are not actually stored, but implicitly defined + * by the location of a node within a doubly-linked list */ + +#include "q_shared.h" +#include "qcommon.h" + +static int			bloc = 0; + +void	Huff_putBit( int bit, byte *fout, int *offset) { +	bloc = *offset; +	if ((bloc&7) == 0) { +		fout[(bloc>>3)] = 0; +	} +	fout[(bloc>>3)] |= bit << (bloc&7); +	bloc++; +	*offset = bloc; +} + +int		Huff_getBit( byte *fin, int *offset) { +	int t; +	bloc = *offset; +	t = (fin[(bloc>>3)] >> (bloc&7)) & 0x1; +	bloc++; +	*offset = bloc; +	return t; +} + +/* Add a bit to the output file (buffered) */ +static void add_bit (char bit, byte *fout) { +	if ((bloc&7) == 0) { +		fout[(bloc>>3)] = 0; +	} +	fout[(bloc>>3)] |= bit << (bloc&7); +	bloc++; +} + +/* Receive one bit from the input file (buffered) */ +static int get_bit (byte *fin) { +	int t; +	t = (fin[(bloc>>3)] >> (bloc&7)) & 0x1; +	bloc++; +	return t; +} + +static node_t **get_ppnode(huff_t* huff) { +	node_t **tppnode; +	if (!huff->freelist) { +		return &(huff->nodePtrs[huff->blocPtrs++]); +	} else { +		tppnode = huff->freelist; +		huff->freelist = (node_t **)*tppnode; +		return tppnode; +	} +} + +static void free_ppnode(huff_t* huff, node_t **ppnode) { +	*ppnode = (node_t *)huff->freelist; +	huff->freelist = ppnode; +} + +/* Swap the location of these two nodes in the tree */ +static void swap (huff_t* huff, node_t *node1, node_t *node2) {  +	node_t *par1, *par2; + +	par1 = node1->parent; +	par2 = node2->parent; + +	if (par1) { +		if (par1->left == node1) { +			par1->left = node2; +		} else { +	      par1->right = node2; +		} +	} else { +		huff->tree = node2; +	} + +	if (par2) { +		if (par2->left == node2) { +			par2->left = node1; +		} else { +			par2->right = node1; +		} +	} else { +		huff->tree = node1; +	} +   +	node1->parent = par2; +	node2->parent = par1; +} + +/* Swap these two nodes in the linked list (update ranks) */ +static void swaplist(node_t *node1, node_t *node2) { +	node_t *par1; + +	par1 = node1->next; +	node1->next = node2->next; +	node2->next = par1; + +	par1 = node1->prev; +	node1->prev = node2->prev; +	node2->prev = par1; + +	if (node1->next == node1) { +		node1->next = node2; +	} +	if (node2->next == node2) { +		node2->next = node1; +	} +	if (node1->next) { +		node1->next->prev = node1; +	} +	if (node2->next) { +		node2->next->prev = node2; +	} +	if (node1->prev) { +		node1->prev->next = node1; +	} +	if (node2->prev) { +		node2->prev->next = node2; +	} +} + +/* Do the increments */ +static void increment(huff_t* huff, node_t *node) { +	node_t *lnode; + +	if (!node) { +		return; +	} + +	if (node->next != NULL && node->next->weight == node->weight) { +	    lnode = *node->head; +		if (lnode != node->parent) { +			swap(huff, lnode, node); +		} +		swaplist(lnode, node); +	} +	if (node->prev && node->prev->weight == node->weight) { +		*node->head = node->prev; +	} else { +	    *node->head = NULL; +		free_ppnode(huff, node->head); +	} +	node->weight++; +	if (node->next && node->next->weight == node->weight) { +		node->head = node->next->head; +	} else {  +		node->head = get_ppnode(huff); +		*node->head = node; +	} +	if (node->parent) { +		increment(huff, node->parent); +		if (node->prev == node->parent) { +			swaplist(node, node->parent); +			if (*node->head == node) { +				*node->head = node->parent; +			} +		} +	} +} + +void Huff_addRef(huff_t* huff, byte ch) { +	node_t *tnode, *tnode2; +	if (huff->loc[ch] == NULL) { /* if this is the first transmission of this node */ +		tnode = &(huff->nodeList[huff->blocNode++]); +		tnode2 = &(huff->nodeList[huff->blocNode++]); + +		tnode2->symbol = INTERNAL_NODE; +		tnode2->weight = 1; +		tnode2->next = huff->lhead->next; +		if (huff->lhead->next) { +			huff->lhead->next->prev = tnode2; +			if (huff->lhead->next->weight == 1) { +				tnode2->head = huff->lhead->next->head; +			} else { +				tnode2->head = get_ppnode(huff); +				*tnode2->head = tnode2; +			} +		} else { +			tnode2->head = get_ppnode(huff); +			*tnode2->head = tnode2; +		} +		huff->lhead->next = tnode2; +		tnode2->prev = huff->lhead; +  +		tnode->symbol = ch; +		tnode->weight = 1; +		tnode->next = huff->lhead->next; +		if (huff->lhead->next) { +			huff->lhead->next->prev = tnode; +			if (huff->lhead->next->weight == 1) { +				tnode->head = huff->lhead->next->head; +			} else { +				/* this should never happen */ +				tnode->head = get_ppnode(huff); +				*tnode->head = tnode2; +		    } +		} else { +			/* this should never happen */ +			tnode->head = get_ppnode(huff); +			*tnode->head = tnode; +		} +		huff->lhead->next = tnode; +		tnode->prev = huff->lhead; +		tnode->left = tnode->right = NULL; +  +		if (huff->lhead->parent) { +			if (huff->lhead->parent->left == huff->lhead) { /* lhead is guaranteed to by the NYT */ +				huff->lhead->parent->left = tnode2; +			} else { +				huff->lhead->parent->right = tnode2; +			} +		} else { +			huff->tree = tnode2;  +		} +  +		tnode2->right = tnode; +		tnode2->left = huff->lhead; +  +		tnode2->parent = huff->lhead->parent; +		huff->lhead->parent = tnode->parent = tnode2; +      +		huff->loc[ch] = tnode; +  +		increment(huff, tnode2->parent); +	} else { +		increment(huff, huff->loc[ch]); +	} +} + +/* Get a symbol */ +int Huff_Receive (node_t *node, int *ch, byte *fin) { +	while (node && node->symbol == INTERNAL_NODE) { +		if (get_bit(fin)) { +			node = node->right; +		} else { +			node = node->left; +		} +	} +	if (!node) { +		return 0; +//		Com_Error(ERR_DROP, "Illegal tree!\n"); +	} +	return (*ch = node->symbol); +} + +/* Get a symbol */ +void Huff_offsetReceive (node_t *node, int *ch, byte *fin, int *offset) { +	bloc = *offset; +	while (node && node->symbol == INTERNAL_NODE) { +		if (get_bit(fin)) { +			node = node->right; +		} else { +			node = node->left; +		} +	} +	if (!node) { +		*ch = 0; +		return; +//		Com_Error(ERR_DROP, "Illegal tree!\n"); +	} +	*ch = node->symbol; +	*offset = bloc; +} + +/* Send the prefix code for this node */ +static void send(node_t *node, node_t *child, byte *fout) { +	if (node->parent) { +		send(node->parent, node, fout); +	} +	if (child) { +		if (node->right == child) { +			add_bit(1, fout); +		} else { +			add_bit(0, fout); +		} +	} +} + +/* Send a symbol */ +void Huff_transmit (huff_t *huff, int ch, byte *fout) { +	int i; +	if (huff->loc[ch] == NULL) {  +		/* node_t hasn't been transmitted, send a NYT, then the symbol */ +		Huff_transmit(huff, NYT, fout); +		for (i = 7; i >= 0; i--) { +			add_bit((char)((ch >> i) & 0x1), fout); +		} +	} else { +		send(huff->loc[ch], NULL, fout); +	} +} + +void Huff_offsetTransmit (huff_t *huff, int ch, byte *fout, int *offset) { +	bloc = *offset; +	send(huff->loc[ch], NULL, fout); +	*offset = bloc; +} + +void Huff_Decompress(msg_t *mbuf, int offset) { +	int			ch, cch, i, j, size; +	byte		seq[65536]; +	byte*		buffer; +	huff_t		huff; + +	size = mbuf->cursize - offset; +	buffer = mbuf->data + offset; + +	if ( size <= 0 ) { +		return; +	} + +	Com_Memset(&huff, 0, sizeof(huff_t)); +	// Initialize the tree & list with the NYT node  +	huff.tree = huff.lhead = huff.ltail = huff.loc[NYT] = &(huff.nodeList[huff.blocNode++]); +	huff.tree->symbol = NYT; +	huff.tree->weight = 0; +	huff.lhead->next = huff.lhead->prev = NULL; +	huff.tree->parent = huff.tree->left = huff.tree->right = NULL; + +	cch = buffer[0]*256 + buffer[1]; +	// don't overflow with bad messages +	if ( cch > mbuf->maxsize - offset ) { +		cch = mbuf->maxsize - offset; +	} +	bloc = 16; + +	for ( j = 0; j < cch; j++ ) { +		ch = 0; +		// don't overflow reading from the messages +		// FIXME: would it be better to have a overflow check in get_bit ? +		if ( (bloc >> 3) > size ) { +			seq[j] = 0; +			break; +		} +		Huff_Receive(huff.tree, &ch, buffer);				/* Get a character */ +		if ( ch == NYT ) {								/* We got a NYT, get the symbol associated with it */ +			ch = 0; +			for ( i = 0; i < 8; i++ ) { +				ch = (ch<<1) + get_bit(buffer); +			} +		} +     +		seq[j] = ch;									/* Write symbol */ + +		Huff_addRef(&huff, (byte)ch);								/* Increment node */ +	} +	mbuf->cursize = cch + offset; +	Com_Memcpy(mbuf->data + offset, seq, cch); +} + +extern 	int oldsize; + +void Huff_Compress(msg_t *mbuf, int offset) { +	int			i, ch, size; +	byte		seq[65536]; +	byte*		buffer; +	huff_t		huff; + +	size = mbuf->cursize - offset; +	buffer = mbuf->data+ + offset; + +	if (size<=0) { +		return; +	} + +	Com_Memset(&huff, 0, sizeof(huff_t)); +	// Add the NYT (not yet transmitted) node into the tree/list */ +	huff.tree = huff.lhead = huff.loc[NYT] =  &(huff.nodeList[huff.blocNode++]); +	huff.tree->symbol = NYT; +	huff.tree->weight = 0; +	huff.lhead->next = huff.lhead->prev = NULL; +	huff.tree->parent = huff.tree->left = huff.tree->right = NULL; +	huff.loc[NYT] = huff.tree; + +	seq[0] = (size>>8); +	seq[1] = size&0xff; + +	bloc = 16; + +	for (i=0; i<size; i++ ) { +		ch = buffer[i]; +		Huff_transmit(&huff, ch, seq);						/* Transmit symbol */ +		Huff_addRef(&huff, (byte)ch);								/* Do update */ +	} + +	bloc += 8;												// next byte + +	mbuf->cursize = (bloc>>3) + offset; +	Com_Memcpy(mbuf->data+offset, seq, (bloc>>3)); +} + +void Huff_Init(huffman_t *huff) { + +	Com_Memset(&huff->compressor, 0, sizeof(huff_t)); +	Com_Memset(&huff->decompressor, 0, sizeof(huff_t)); + +	// Initialize the tree & list with the NYT node  +	huff->decompressor.tree = huff->decompressor.lhead = huff->decompressor.ltail = huff->decompressor.loc[NYT] = &(huff->decompressor.nodeList[huff->decompressor.blocNode++]); +	huff->decompressor.tree->symbol = NYT; +	huff->decompressor.tree->weight = 0; +	huff->decompressor.lhead->next = huff->decompressor.lhead->prev = NULL; +	huff->decompressor.tree->parent = huff->decompressor.tree->left = huff->decompressor.tree->right = NULL; + +	// Add the NYT (not yet transmitted) node into the tree/list */ +	huff->compressor.tree = huff->compressor.lhead = huff->compressor.loc[NYT] =  &(huff->compressor.nodeList[huff->compressor.blocNode++]); +	huff->compressor.tree->symbol = NYT; +	huff->compressor.tree->weight = 0; +	huff->compressor.lhead->next = huff->compressor.lhead->prev = NULL; +	huff->compressor.tree->parent = huff->compressor.tree->left = huff->compressor.tree->right = NULL; +	huff->compressor.loc[NYT] = huff->compressor.tree; +} + diff --git a/src/qcommon/md4.c b/src/qcommon/md4.c new file mode 100644 index 0000000..838b062 --- /dev/null +++ b/src/qcommon/md4.c @@ -0,0 +1,208 @@ +/* +	mdfour.c + +	An implementation of MD4 designed for use in the samba SMB +	authentication protocol + +	Copyright (C) 1997-1998  Andrew Tridgell + +	This program is free software; you can redistribute it and/or +	modify it under the terms of the GNU General Public License +	as published by the Free Software Foundation; either version 2 +	of the License, or (at your option) any later version. + +	This program is distributed in the hope that it will be useful, +	but WITHOUT ANY WARRANTY; without even the implied warranty of +	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +	See the GNU General Public License for more details. + +	You should have received a copy of the GNU General Public License +	along with this program; if not, write to: + +		Free Software Foundation, Inc. +		59 Temple Place - Suite 330 +		Boston, MA  02111-1307, USA + +	$Id: mdfour.c,v 1.1 2002/08/23 22:03:27 abster Exp $ +*/ + +#include "q_shared.h" +#include "qcommon.h" + +struct mdfour { +	uint32_t A, B, C, D; +	uint32_t totalN; +}; + + +/* NOTE: This code makes no attempt to be fast! + +   It assumes that a int is at least 32 bits long +*/ + +static struct mdfour *m; + +#define F(X,Y,Z) (((X)&(Y)) | ((~(X))&(Z))) +#define G(X,Y,Z) (((X)&(Y)) | ((X)&(Z)) | ((Y)&(Z))) +#define H(X,Y,Z) ((X)^(Y)^(Z)) +#define lshift(x,s) (((x)<<(s)) | ((x)>>(32-(s)))) + +#define ROUND1(a,b,c,d,k,s) a = lshift(a + F(b,c,d) + X[k], s) +#define ROUND2(a,b,c,d,k,s) a = lshift(a + G(b,c,d) + X[k] + 0x5A827999,s) +#define ROUND3(a,b,c,d,k,s) a = lshift(a + H(b,c,d) + X[k] + 0x6ED9EBA1,s) + +/* this applies md4 to 64 byte chunks */ +static void mdfour64(uint32_t *M) +{ +	int j; +	uint32_t AA, BB, CC, DD; +	uint32_t X[16]; +	uint32_t A,B,C,D; + +	for (j=0;j<16;j++) +		X[j] = M[j]; + +	A = m->A; B = m->B; C = m->C; D = m->D; +	AA = A; BB = B; CC = C; DD = D; + +        ROUND1(A,B,C,D,  0,  3);  ROUND1(D,A,B,C,  1,  7); +	ROUND1(C,D,A,B,  2, 11);  ROUND1(B,C,D,A,  3, 19); +        ROUND1(A,B,C,D,  4,  3);  ROUND1(D,A,B,C,  5,  7); +	ROUND1(C,D,A,B,  6, 11);  ROUND1(B,C,D,A,  7, 19); +        ROUND1(A,B,C,D,  8,  3);  ROUND1(D,A,B,C,  9,  7); +	ROUND1(C,D,A,B, 10, 11);  ROUND1(B,C,D,A, 11, 19); +        ROUND1(A,B,C,D, 12,  3);  ROUND1(D,A,B,C, 13,  7); +	ROUND1(C,D,A,B, 14, 11);  ROUND1(B,C,D,A, 15, 19); + +        ROUND2(A,B,C,D,  0,  3);  ROUND2(D,A,B,C,  4,  5); +	ROUND2(C,D,A,B,  8,  9);  ROUND2(B,C,D,A, 12, 13); +        ROUND2(A,B,C,D,  1,  3);  ROUND2(D,A,B,C,  5,  5); +	ROUND2(C,D,A,B,  9,  9);  ROUND2(B,C,D,A, 13, 13); +        ROUND2(A,B,C,D,  2,  3);  ROUND2(D,A,B,C,  6,  5); +	ROUND2(C,D,A,B, 10,  9);  ROUND2(B,C,D,A, 14, 13); +        ROUND2(A,B,C,D,  3,  3);  ROUND2(D,A,B,C,  7,  5); +	ROUND2(C,D,A,B, 11,  9);  ROUND2(B,C,D,A, 15, 13); + +	ROUND3(A,B,C,D,  0,  3);  ROUND3(D,A,B,C,  8,  9); +	ROUND3(C,D,A,B,  4, 11);  ROUND3(B,C,D,A, 12, 15); +        ROUND3(A,B,C,D,  2,  3);  ROUND3(D,A,B,C, 10,  9); +	ROUND3(C,D,A,B,  6, 11);  ROUND3(B,C,D,A, 14, 15); +        ROUND3(A,B,C,D,  1,  3);  ROUND3(D,A,B,C,  9,  9); +	ROUND3(C,D,A,B,  5, 11);  ROUND3(B,C,D,A, 13, 15); +        ROUND3(A,B,C,D,  3,  3);  ROUND3(D,A,B,C, 11,  9); +	ROUND3(C,D,A,B,  7, 11);  ROUND3(B,C,D,A, 15, 15); + +	A += AA; B += BB; C += CC; D += DD; + +	for (j=0;j<16;j++) +		X[j] = 0; + +	m->A = A; m->B = B; m->C = C; m->D = D; +} + +static void copy64(uint32_t *M, byte *in) +{ +	int i; + +	for (i=0;i<16;i++) +		M[i] = (in[i*4+3]<<24) | (in[i*4+2]<<16) | +			(in[i*4+1]<<8) | (in[i*4+0]<<0); +} + +static void copy4(byte *out,uint32_t x) +{ +	out[0] = x&0xFF; +	out[1] = (x>>8)&0xFF; +	out[2] = (x>>16)&0xFF; +	out[3] = (x>>24)&0xFF; +} + +void mdfour_begin(struct mdfour *md) +{ +	md->A = 0x67452301; +	md->B = 0xefcdab89; +	md->C = 0x98badcfe; +	md->D = 0x10325476; +	md->totalN = 0; +} + + +static void mdfour_tail(byte *in, int n) +{ +	byte buf[128]; +	uint32_t M[16]; +	uint32_t b; + +	m->totalN += n; + +	b = m->totalN * 8; + +	Com_Memset(buf, 0, 128); +	if (n) Com_Memcpy(buf, in, n); +	buf[n] = 0x80; + +	if (n <= 55) { +		copy4(buf+56, b); +		copy64(M, buf); +		mdfour64(M); +	} else { +		copy4(buf+120, b); +		copy64(M, buf); +		mdfour64(M); +		copy64(M, buf+64); +		mdfour64(M); +	} +} + +static void mdfour_update(struct mdfour *md, byte *in, int n) +{ +	uint32_t M[16]; + +	m = md; + +	if (n == 0) mdfour_tail(in, n); + +	while (n >= 64) { +		copy64(M, in); +		mdfour64(M); +		in += 64; +		n -= 64; +		m->totalN += 64; +	} + +	mdfour_tail(in, n); +} + + +static void mdfour_result(struct mdfour *md, byte *out) +{ +	m = md; + +	copy4(out, m->A); +	copy4(out+4, m->B); +	copy4(out+8, m->C); +	copy4(out+12, m->D); +} + +static void mdfour(byte *out, byte *in, int n) +{ +	struct mdfour md; +	mdfour_begin(&md); +	mdfour_update(&md, in, n); +	mdfour_result(&md, out); +} + +//=================================================================== + +unsigned Com_BlockChecksum (const void *buffer, int length) +{ +	int				digest[4]; +	unsigned	val; + +	mdfour( (byte *)digest, (byte *)buffer, length ); +	 +	val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; + +	return val; +} diff --git a/src/qcommon/md5.c b/src/qcommon/md5.c new file mode 100644 index 0000000..5cf12bb --- /dev/null +++ b/src/qcommon/md5.c @@ -0,0 +1,310 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest.  This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ +#include "q_shared.h" +#include "qcommon.h" + +typedef struct MD5Context { +	uint32_t buf[4]; +	uint32_t bits[2]; +	unsigned char in[64]; +} MD5_CTX; + +#ifndef Q3_BIG_ENDIAN +	#define byteReverse(buf, len)	/* Nothing */ +#else +	static void byteReverse(unsigned char *buf, unsigned longs); + +	/* +	 * Note: this code is harmless on little-endian machines. +	 */ +	static void byteReverse(unsigned char *buf, unsigned longs) +	{ +	    uint32_t t; +	    do { +		t = (uint32_t) +			((unsigned) buf[3] << 8 | buf[2]) << 16 | +			((unsigned) buf[1] << 8 | buf[0]); +		*(uint32_t *) buf = t; +		buf += 4; +	    } while (--longs); +	} +#endif // Q3_BIG_ENDIAN + +/* + * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +static void MD5Init(struct MD5Context *ctx) +{ +    ctx->buf[0] = 0x67452301; +    ctx->buf[1] = 0xefcdab89; +    ctx->buf[2] = 0x98badcfe; +    ctx->buf[3] = 0x10325476; + +    ctx->bits[0] = 0; +    ctx->bits[1] = 0; +} +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ +	( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data.  MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void MD5Transform(uint32_t buf[4], +	uint32_t const in[16]) +{ +    register uint32_t a, b, c, d; + +    a = buf[0]; +    b = buf[1]; +    c = buf[2]; +    d = buf[3]; + +    MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); +    MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); +    MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); +    MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); +    MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); +    MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); +    MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); +    MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); +    MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); +    MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); +    MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); +    MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); +    MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); +    MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); +    MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); +    MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + +    MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); +    MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); +    MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); +    MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); +    MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); +    MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); +    MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); +    MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); +    MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); +    MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); +    MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); +    MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); +    MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); +    MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); +    MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); +    MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + +    MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); +    MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); +    MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); +    MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); +    MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); +    MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); +    MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); +    MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); +    MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); +    MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); +    MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); +    MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); +    MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); +    MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); +    MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); +    MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + +    MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); +    MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); +    MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); +    MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); +    MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); +    MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); +    MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); +    MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); +    MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); +    MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); +    MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); +    MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); +    MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); +    MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); +    MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); +    MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + +    buf[0] += a; +    buf[1] += b; +    buf[2] += c; +    buf[3] += d; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +static void MD5Update(struct MD5Context *ctx, unsigned char const *buf, +	unsigned len) +{ +    uint32_t t; + +    /* Update bitcount */ + +    t = ctx->bits[0]; +    if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) +	ctx->bits[1]++;		/* Carry from low to high */ +    ctx->bits[1] += len >> 29; + +    t = (t >> 3) & 0x3f;	/* Bytes already in shsInfo->data */ + +    /* Handle any leading odd-sized chunks */ + +    if (t) { +	unsigned char *p = (unsigned char *) ctx->in + t; + +	t = 64 - t; +	if (len < t) { +	    memcpy(p, buf, len); +	    return; +	} +	memcpy(p, buf, t); +	byteReverse(ctx->in, 16); +	MD5Transform(ctx->buf, (uint32_t *) ctx->in); +	buf += t; +	len -= t; +    } +    /* Process data in 64-byte chunks */ + +    while (len >= 64) { +	memcpy(ctx->in, buf, 64); +	byteReverse(ctx->in, 16); +	MD5Transform(ctx->buf, (uint32_t *) ctx->in); +	buf += 64; +	len -= 64; +    } + +    /* Handle any remaining bytes of data. */ + +    memcpy(ctx->in, buf, len); +} + + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern  + * 1 0* (64-bit count of bits processed, MSB-first) + */ +static void MD5Final(struct MD5Context *ctx, unsigned char *digest) +{ +    unsigned count; +    unsigned char *p; + +    /* Compute number of bytes mod 64 */ +    count = (ctx->bits[0] >> 3) & 0x3F; + +    /* Set the first char of padding to 0x80.  This is safe since there is +       always at least one byte free */ +    p = ctx->in + count; +    *p++ = 0x80; + +    /* Bytes of padding needed to make 64 bytes */ +    count = 64 - 1 - count; + +    /* Pad out to 56 mod 64 */ +    if (count < 8) { +	/* Two lots of padding:  Pad the first block to 64 bytes */ +	memset(p, 0, count); +	byteReverse(ctx->in, 16); +	MD5Transform(ctx->buf, (uint32_t *) ctx->in); + +	/* Now fill the next block with 56 bytes */ +	memset(ctx->in, 0, 56); +    } else { +	/* Pad block to 56 bytes */ +	memset(p, 0, count - 8); +    } +    byteReverse(ctx->in, 14); + +    /* Append length in bits and transform */ +    ((uint32_t *) ctx->in)[14] = ctx->bits[0]; +    ((uint32_t *) ctx->in)[15] = ctx->bits[1]; + +    MD5Transform(ctx->buf, (uint32_t *) ctx->in); +    byteReverse((unsigned char *) ctx->buf, 4); +     +    if (digest!=NULL) +	    memcpy(digest, ctx->buf, 16); +    memset(ctx, 0, sizeof(ctx));	/* In case it's sensitive */ +} + + +char *Com_MD5File( const char *fn, int length, const char *prefix, int prefix_len ) +{ +	static char final[33] = {""}; +	unsigned char digest[16] = {""};  +	fileHandle_t f; +	MD5_CTX md5; +	byte buffer[2048]; +	int i; +	int filelen = 0; +	int r = 0; +	int total = 0; + +	Q_strncpyz( final, "", sizeof( final ) ); + +	filelen = FS_SV_FOpenFileRead( fn, &f ); + +	if( !f ) { +		return final; +	} +	if( filelen < 1 ) { +		FS_FCloseFile( f ); +		return final; +	} +	if(filelen < length || !length) { +		length = filelen; +	} + +	MD5Init(&md5); + +	if( prefix_len && *prefix ) +		MD5Update(&md5 , (unsigned char *)prefix, prefix_len); + +	for(;;) { +		r = FS_Read2(buffer, sizeof(buffer), f); +		if(r < 1) +			break; +		if(r + total > length) +			r = length - total; +		total += r; +		MD5Update(&md5 , buffer, r); +		if(r < sizeof(buffer) || total >= length) +			break; +	} +	FS_FCloseFile(f); +	MD5Final(&md5, digest); +	final[0] = '\0'; +	for(i = 0; i < 16; i++) { +		Q_strcat(final, sizeof(final), va("%02X", digest[i])); +	} +	return final; +} diff --git a/src/qcommon/msg.c b/src/qcommon/msg.c new file mode 100644 index 0000000..b30b2ba --- /dev/null +++ b/src/qcommon/msg.c @@ -0,0 +1,1767 @@ +/*
 +===========================================================================
 +Copyright (C) 1999-2005 Id Software, Inc.
 +Copyright (C) 2000-2006 Tim Angus
 +
 +This file is part of Tremulous.
 +
 +Tremulous is free software; you can redistribute it
 +and/or modify it under the terms of the GNU General Public License as
 +published by the Free Software Foundation; either version 2 of the License,
 +or (at your option) any later version.
 +
 +Tremulous is distributed in the hope that it will be
 +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +GNU General Public License for more details.
 +
 +You should have received a copy of the GNU General Public License
 +along with Tremulous; if not, write to the Free Software
 +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 +===========================================================================
 +*/
 +#include "q_shared.h"
 +#include "qcommon.h"
 +
 +static huffman_t		msgHuff;
 +
 +static qboolean			msgInit = qfalse;
 +
 +int pcount[256];
 +
 +/*
 +==============================================================================
 +
 +			MESSAGE IO FUNCTIONS
 +
 +Handles byte ordering and avoids alignment errors
 +==============================================================================
 +*/
 +
 +int oldsize = 0;
 +
 +void MSG_initHuffman( void );
 +
 +void MSG_Init( msg_t *buf, byte *data, int length ) {
 +	if (!msgInit) {
 +		MSG_initHuffman();
 +	}
 +	Com_Memset (buf, 0, sizeof(*buf));
 +	buf->data = data;
 +	buf->maxsize = length;
 +}
 +
 +void MSG_InitOOB( msg_t *buf, byte *data, int length ) {
 +	if (!msgInit) {
 +		MSG_initHuffman();
 +	}
 +	Com_Memset (buf, 0, sizeof(*buf));
 +	buf->data = data;
 +	buf->maxsize = length;
 +	buf->oob = qtrue;
 +}
 +
 +void MSG_Clear( msg_t *buf ) {
 +	buf->cursize = 0;
 +	buf->overflowed = qfalse;
 +	buf->bit = 0;					//<- in bits
 +}
 +
 +
 +void MSG_Bitstream( msg_t *buf ) {
 +	buf->oob = qfalse;
 +}
 +
 +void MSG_BeginReading( msg_t *msg ) {
 +	msg->readcount = 0;
 +	msg->bit = 0;
 +	msg->oob = qfalse;
 +}
 +
 +void MSG_BeginReadingOOB( msg_t *msg ) {
 +	msg->readcount = 0;
 +	msg->bit = 0;
 +	msg->oob = qtrue;
 +}
 +
 +void MSG_Copy(msg_t *buf, byte *data, int length, msg_t *src)
 +{
 +	if (length<src->cursize) {
 +		Com_Error( ERR_DROP, "MSG_Copy: can't copy into a smaller msg_t buffer");
 +	}
 +	Com_Memcpy(buf, src, sizeof(msg_t));
 +	buf->data = data;
 +	Com_Memcpy(buf->data, src->data, src->cursize);
 +}
 +
 +/*
 +=============================================================================
 +
 +bit functions
 +  
 +=============================================================================
 +*/
 +
 +int	overflows;
 +
 +// negative bit values include signs
 +void MSG_WriteBits( msg_t *msg, int value, int bits ) {
 +	int	i;
 +//	FILE*	fp;
 +
 +	oldsize += bits;
 +
 +	// this isn't an exact overflow check, but close enough
 +	if ( msg->maxsize - msg->cursize < 4 ) {
 +		msg->overflowed = qtrue;
 +		return;
 +	}
 +
 +	if ( bits == 0 || bits < -31 || bits > 32 ) {
 +		Com_Error( ERR_DROP, "MSG_WriteBits: bad bits %i", bits );
 +	}
 +
 +	// check for overflows
 +	if ( bits != 32 ) {
 +		if ( bits > 0 ) {
 +			if ( value > ( ( 1 << bits ) - 1 ) || value < 0 ) {
 +				overflows++;
 +			}
 +		} else {
 +			int	r;
 +
 +			r = 1 << (bits-1);
 +
 +			if ( value >  r - 1 || value < -r ) {
 +				overflows++;
 +			}
 +		}
 +	}
 +	if ( bits < 0 ) {
 +		bits = -bits;
 +	}
 +	if (msg->oob) {
 +		if (bits==8) {
 +			msg->data[msg->cursize] = value;
 +			msg->cursize += 1;
 +			msg->bit += 8;
 +		} else if (bits==16) {
 +			unsigned short *sp = (unsigned short *)&msg->data[msg->cursize];
 +			*sp = LittleShort(value);
 +			msg->cursize += 2;
 +			msg->bit += 16;
 +		} else if (bits==32) {
 +			unsigned int *ip = (unsigned int *)&msg->data[msg->cursize];
 +			*ip = LittleLong(value);
 +			msg->cursize += 4;
 +			msg->bit += 32;
 +		} else {
 +			Com_Error(ERR_DROP, "can't read %d bits\n", bits);
 +		}
 +	} else {
 +//		fp = fopen("c:\\netchan.bin", "a");
 +		value &= (0xffffffff>>(32-bits));
 +		if (bits&7) {
 +			int nbits;
 +			nbits = bits&7;
 +			for(i=0;i<nbits;i++) {
 +				Huff_putBit((value&1), msg->data, &msg->bit);
 +				value = (value>>1);
 +			}
 +			bits = bits - nbits;
 +		}
 +		if (bits) {
 +			for(i=0;i<bits;i+=8) {
 +//				fwrite(bp, 1, 1, fp);
 +				Huff_offsetTransmit (&msgHuff.compressor, (value&0xff), msg->data, &msg->bit);
 +				value = (value>>8);
 +			}
 +		}
 +		msg->cursize = (msg->bit>>3)+1;
 +//		fclose(fp);
 +	}
 +}
 +
 +int MSG_ReadBits( msg_t *msg, int bits ) {
 +	int			value;
 +	int			get;
 +	qboolean	sgn;
 +	int			i, nbits;
 +//	FILE*	fp;
 +
 +	value = 0;
 +
 +	if ( bits < 0 ) {
 +		bits = -bits;
 +		sgn = qtrue;
 +	} else {
 +		sgn = qfalse;
 +	}
 +
 +	if (msg->oob) {
 +		if (bits==8) {
 +			value = msg->data[msg->readcount];
 +			msg->readcount += 1;
 +			msg->bit += 8;
 +		} else if (bits==16) {
 +			unsigned short *sp = (unsigned short *)&msg->data[msg->readcount];
 +			value = LittleShort(*sp);
 +			msg->readcount += 2;
 +			msg->bit += 16;
 +		} else if (bits==32) {
 +			unsigned int *ip = (unsigned int *)&msg->data[msg->readcount];
 +			value = LittleLong(*ip);
 +			msg->readcount += 4;
 +			msg->bit += 32;
 +		} else {
 +			Com_Error(ERR_DROP, "can't read %d bits\n", bits);
 +		}
 +	} else {
 +		nbits = 0;
 +		if (bits&7) {
 +			nbits = bits&7;
 +			for(i=0;i<nbits;i++) {
 +				value |= (Huff_getBit(msg->data, &msg->bit)<<i);
 +			}
 +			bits = bits - nbits;
 +		}
 +		if (bits) {
 +//			fp = fopen("c:\\netchan.bin", "a");
 +			for(i=0;i<bits;i+=8) {
 +				Huff_offsetReceive (msgHuff.decompressor.tree, &get, msg->data, &msg->bit);
 +//				fwrite(&get, 1, 1, fp);
 +				value |= (get<<(i+nbits));
 +			}
 +//			fclose(fp);
 +		}
 +		msg->readcount = (msg->bit>>3)+1;
 +	}
 +	if ( sgn ) {
 +		if ( value & ( 1 << ( bits - 1 ) ) ) {
 +			value |= -1 ^ ( ( 1 << bits ) - 1 );
 +		}
 +	}
 +
 +	return value;
 +}
 +
 +
 +
 +//================================================================================
 +
 +//
 +// writing functions
 +//
 +
 +void MSG_WriteChar( msg_t *sb, int c ) {
 +#ifdef PARANOID
 +	if (c < -128 || c > 127)
 +		Com_Error (ERR_FATAL, "MSG_WriteChar: range error");
 +#endif
 +
 +	MSG_WriteBits( sb, c, 8 );
 +}
 +
 +void MSG_WriteByte( msg_t *sb, int c ) {
 +#ifdef PARANOID
 +	if (c < 0 || c > 255)
 +		Com_Error (ERR_FATAL, "MSG_WriteByte: range error");
 +#endif
 +
 +	MSG_WriteBits( sb, c, 8 );
 +}
 +
 +void MSG_WriteData( msg_t *buf, const void *data, int length ) {
 +	int i;
 +	for(i=0;i<length;i++) {
 +		MSG_WriteByte(buf, ((byte *)data)[i]);
 +	}
 +}
 +
 +void MSG_WriteShort( msg_t *sb, int c ) {
 +#ifdef PARANOID
 +	if (c < ((short)0x8000) || c > (short)0x7fff)
 +		Com_Error (ERR_FATAL, "MSG_WriteShort: range error");
 +#endif
 +
 +	MSG_WriteBits( sb, c, 16 );
 +}
 +
 +void MSG_WriteLong( msg_t *sb, int c ) {
 +	MSG_WriteBits( sb, c, 32 );
 +}
 +
 +void MSG_WriteFloat( msg_t *sb, float f ) {
 +	union {
 +		float	f;
 +		int	l;
 +	} dat;
 +	
 +	dat.f = f;
 +	MSG_WriteBits( sb, dat.l, 32 );
 +}
 +
 +void MSG_WriteString( msg_t *sb, const char *s ) {
 +	if ( !s ) {
 +		MSG_WriteData (sb, "", 1);
 +	} else {
 +		int		l,i;
 +		char	string[MAX_STRING_CHARS];
 +
 +		l = strlen( s );
 +		if ( l >= MAX_STRING_CHARS ) {
 +			Com_Printf( "MSG_WriteString: MAX_STRING_CHARS" );
 +			MSG_WriteData (sb, "", 1);
 +			return;
 +		}
 +		Q_strncpyz( string, s, sizeof( string ) );
 +
 +		// get rid of 0xff chars, because old clients don't like them
 +		for ( i = 0 ; i < l ; i++ ) {
 +			if ( ((byte *)string)[i] > 127 ) {
 +				string[i] = '.';
 +			}
 +		}
 +
 +		MSG_WriteData (sb, string, l+1);
 +	}
 +}
 +
 +void MSG_WriteBigString( msg_t *sb, const char *s ) {
 +	if ( !s ) {
 +		MSG_WriteData (sb, "", 1);
 +	} else {
 +		int		l,i;
 +		char	string[BIG_INFO_STRING];
 +
 +		l = strlen( s );
 +		if ( l >= BIG_INFO_STRING ) {
 +			Com_Printf( "MSG_WriteString: BIG_INFO_STRING" );
 +			MSG_WriteData (sb, "", 1);
 +			return;
 +		}
 +		Q_strncpyz( string, s, sizeof( string ) );
 +
 +		// get rid of 0xff chars, because old clients don't like them
 +		for ( i = 0 ; i < l ; i++ ) {
 +			if ( ((byte *)string)[i] > 127 ) {
 +				string[i] = '.';
 +			}
 +		}
 +
 +		MSG_WriteData (sb, string, l+1);
 +	}
 +}
 +
 +void MSG_WriteAngle( msg_t *sb, float f ) {
 +	MSG_WriteByte (sb, (int)(f*256/360) & 255);
 +}
 +
 +void MSG_WriteAngle16( msg_t *sb, float f ) {
 +	MSG_WriteShort (sb, ANGLE2SHORT(f));
 +}
 +
 +
 +//============================================================
 +
 +//
 +// reading functions
 +//
 +
 +// returns -1 if no more characters are available
 +int MSG_ReadChar (msg_t *msg ) {
 +	int	c;
 +	
 +	c = (signed char)MSG_ReadBits( msg, 8 );
 +	if ( msg->readcount > msg->cursize ) {
 +		c = -1;
 +	}	
 +	
 +	return c;
 +}
 +
 +int MSG_ReadByte( msg_t *msg ) {
 +	int	c;
 +	
 +	c = (unsigned char)MSG_ReadBits( msg, 8 );
 +	if ( msg->readcount > msg->cursize ) {
 +		c = -1;
 +	}	
 +	return c;
 +}
 +
 +int MSG_ReadShort( msg_t *msg ) {
 +	int	c;
 +	
 +	c = (short)MSG_ReadBits( msg, 16 );
 +	if ( msg->readcount > msg->cursize ) {
 +		c = -1;
 +	}	
 +
 +	return c;
 +}
 +
 +int MSG_ReadLong( msg_t *msg ) {
 +	int	c;
 +	
 +	c = MSG_ReadBits( msg, 32 );
 +	if ( msg->readcount > msg->cursize ) {
 +		c = -1;
 +	}	
 +	
 +	return c;
 +}
 +
 +float MSG_ReadFloat( msg_t *msg ) {
 +	union {
 +		byte	b[4];
 +		float	f;
 +		int	l;
 +	} dat;
 +	
 +	dat.l = MSG_ReadBits( msg, 32 );
 +	if ( msg->readcount > msg->cursize ) {
 +		dat.f = -1;
 +	}	
 +	
 +	return dat.f;	
 +}
 +
 +char *MSG_ReadString( msg_t *msg ) {
 +	static char	string[MAX_STRING_CHARS];
 +	int		l,c;
 +	
 +	l = 0;
 +	do {
 +		c = MSG_ReadByte(msg);		// use ReadByte so -1 is out of bounds
 +		if ( c == -1 || c == 0 ) {
 +			break;
 +		}
 +		// translate all fmt spec to avoid crash bugs
 +		if ( c == '%' ) {
 +			c = '.';
 +		}
 +		// don't allow higher ascii values
 +		if ( c > 127 ) {
 +			c = '.';
 +		}
 +
 +		string[l] = c;
 +		l++;
 +	} while (l < sizeof(string)-1);
 +	
 +	string[l] = 0;
 +	
 +	return string;
 +}
 +
 +char *MSG_ReadBigString( msg_t *msg ) {
 +	static char	string[BIG_INFO_STRING];
 +	int		l,c;
 +	
 +	l = 0;
 +	do {
 +		c = MSG_ReadByte(msg);		// use ReadByte so -1 is out of bounds
 +		if ( c == -1 || c == 0 ) {
 +			break;
 +		}
 +		// translate all fmt spec to avoid crash bugs
 +		if ( c == '%' ) {
 +			c = '.';
 +		}
 +		// don't allow higher ascii values
 +		if ( c > 127 ) {
 +			c = '.';
 +		}
 +
 +		string[l] = c;
 +		l++;
 +	} while (l < sizeof(string)-1);
 +	
 +	string[l] = 0;
 +	
 +	return string;
 +}
 +
 +char *MSG_ReadStringLine( msg_t *msg ) {
 +	static char	string[MAX_STRING_CHARS];
 +	int		l,c;
 +
 +	l = 0;
 +	do {
 +		c = MSG_ReadByte(msg);		// use ReadByte so -1 is out of bounds
 +		if (c == -1 || c == 0 || c == '\n') {
 +			break;
 +		}
 +		// translate all fmt spec to avoid crash bugs
 +		if ( c == '%' ) {
 +			c = '.';
 +		}
 +		// don't allow higher ascii values
 +		if ( c > 127 ) {
 +			c = '.';
 +		}
 +
 +		string[l] = c;
 +		l++;
 +	} while (l < sizeof(string)-1);
 +	
 +	string[l] = 0;
 +	
 +	return string;
 +}
 +
 +float MSG_ReadAngle16( msg_t *msg ) {
 +	return SHORT2ANGLE(MSG_ReadShort(msg));
 +}
 +
 +void MSG_ReadData( msg_t *msg, void *data, int len ) {
 +	int		i;
 +
 +	for (i=0 ; i<len ; i++) {
 +		((byte *)data)[i] = MSG_ReadByte (msg);
 +	}
 +}
 +
 +
 +/*
 +=============================================================================
 +
 +delta functions
 +  
 +=============================================================================
 +*/
 +
 +extern cvar_t *cl_shownet;
 +
 +#define	LOG(x) if( cl_shownet->integer == 4 ) { Com_Printf("%s ", x ); };
 +
 +void MSG_WriteDelta( msg_t *msg, int oldV, int newV, int bits ) {
 +	if ( oldV == newV ) {
 +		MSG_WriteBits( msg, 0, 1 );
 +		return;
 +	}
 +	MSG_WriteBits( msg, 1, 1 );
 +	MSG_WriteBits( msg, newV, bits );
 +}
 +
 +int	MSG_ReadDelta( msg_t *msg, int oldV, int bits ) {
 +	if ( MSG_ReadBits( msg, 1 ) ) {
 +		return MSG_ReadBits( msg, bits );
 +	}
 +	return oldV;
 +}
 +
 +void MSG_WriteDeltaFloat( msg_t *msg, float oldV, float newV ) {
 +	if ( oldV == newV ) {
 +		MSG_WriteBits( msg, 0, 1 );
 +		return;
 +	}
 +	MSG_WriteBits( msg, 1, 1 );
 +	MSG_WriteBits( msg, *(int *)&newV, 32 );
 +}
 +
 +float MSG_ReadDeltaFloat( msg_t *msg, float oldV ) {
 +	if ( MSG_ReadBits( msg, 1 ) ) {
 +		float	newV;
 +
 +		*(int *)&newV = MSG_ReadBits( msg, 32 );
 +		return newV;
 +	}
 +	return oldV;
 +}
 +
 +/*
 +=============================================================================
 +
 +delta functions with keys
 +  
 +=============================================================================
 +*/
 +
 +int kbitmask[32] = {
 +	0x00000001, 0x00000003, 0x00000007, 0x0000000F,
 +	0x0000001F,	0x0000003F,	0x0000007F,	0x000000FF,
 +	0x000001FF,	0x000003FF,	0x000007FF,	0x00000FFF,
 +	0x00001FFF,	0x00003FFF,	0x00007FFF,	0x0000FFFF,
 +	0x0001FFFF,	0x0003FFFF,	0x0007FFFF,	0x000FFFFF,
 +	0x001FFFFf,	0x003FFFFF,	0x007FFFFF,	0x00FFFFFF,
 +	0x01FFFFFF,	0x03FFFFFF,	0x07FFFFFF,	0x0FFFFFFF,
 +	0x1FFFFFFF,	0x3FFFFFFF,	0x7FFFFFFF,	0xFFFFFFFF,
 +};
 +
 +void MSG_WriteDeltaKey( msg_t *msg, int key, int oldV, int newV, int bits ) {
 +	if ( oldV == newV ) {
 +		MSG_WriteBits( msg, 0, 1 );
 +		return;
 +	}
 +	MSG_WriteBits( msg, 1, 1 );
 +	MSG_WriteBits( msg, newV ^ key, bits );
 +}
 +
 +int	MSG_ReadDeltaKey( msg_t *msg, int key, int oldV, int bits ) {
 +	if ( MSG_ReadBits( msg, 1 ) ) {
 +		return MSG_ReadBits( msg, bits ) ^ (key & kbitmask[bits]);
 +	}
 +	return oldV;
 +}
 +
 +void MSG_WriteDeltaKeyFloat( msg_t *msg, int key, float oldV, float newV ) {
 +	if ( oldV == newV ) {
 +		MSG_WriteBits( msg, 0, 1 );
 +		return;
 +	}
 +	MSG_WriteBits( msg, 1, 1 );
 +	MSG_WriteBits( msg, (*(int *)&newV) ^ key, 32 );
 +}
 +
 +float MSG_ReadDeltaKeyFloat( msg_t *msg, int key, float oldV ) {
 +	if ( MSG_ReadBits( msg, 1 ) ) {
 +		float	newV;
 +
 +		*(int *)&newV = MSG_ReadBits( msg, 32 ) ^ key;
 +		return newV;
 +	}
 +	return oldV;
 +}
 +
 +
 +/*
 +============================================================================
 +
 +usercmd_t communication
 +
 +============================================================================
 +*/
 +
 +// ms is allways sent, the others are optional
 +#define	CM_ANGLE1 	(1<<0)
 +#define	CM_ANGLE2 	(1<<1)
 +#define	CM_ANGLE3 	(1<<2)
 +#define	CM_FORWARD	(1<<3)
 +#define	CM_SIDE		(1<<4)
 +#define	CM_UP		(1<<5)
 +#define	CM_BUTTONS	(1<<6)
 +#define CM_WEAPON	(1<<7)
 +
 +/*
 +=====================
 +MSG_WriteDeltaUsercmd
 +=====================
 +*/
 +void MSG_WriteDeltaUsercmd( msg_t *msg, usercmd_t *from, usercmd_t *to ) {
 +	if ( to->serverTime - from->serverTime < 256 ) {
 +		MSG_WriteBits( msg, 1, 1 );
 +		MSG_WriteBits( msg, to->serverTime - from->serverTime, 8 );
 +	} else {
 +		MSG_WriteBits( msg, 0, 1 );
 +		MSG_WriteBits( msg, to->serverTime, 32 );
 +	}
 +	MSG_WriteDelta( msg, from->angles[0], to->angles[0], 16 );
 +	MSG_WriteDelta( msg, from->angles[1], to->angles[1], 16 );
 +	MSG_WriteDelta( msg, from->angles[2], to->angles[2], 16 );
 +	MSG_WriteDelta( msg, from->forwardmove, to->forwardmove, 8 );
 +	MSG_WriteDelta( msg, from->rightmove, to->rightmove, 8 );
 +	MSG_WriteDelta( msg, from->upmove, to->upmove, 8 );
 +	MSG_WriteDelta( msg, from->buttons, to->buttons, 16 );
 +	MSG_WriteDelta( msg, from->weapon, to->weapon, 8 );
 +}
 +
 +
 +/*
 +=====================
 +MSG_ReadDeltaUsercmd
 +=====================
 +*/
 +void MSG_ReadDeltaUsercmd( msg_t *msg, usercmd_t *from, usercmd_t *to ) {
 +	if ( MSG_ReadBits( msg, 1 ) ) {
 +		to->serverTime = from->serverTime + MSG_ReadBits( msg, 8 );
 +	} else {
 +		to->serverTime = MSG_ReadBits( msg, 32 );
 +	}
 +	to->angles[0] = MSG_ReadDelta( msg, from->angles[0], 16);
 +	to->angles[1] = MSG_ReadDelta( msg, from->angles[1], 16);
 +	to->angles[2] = MSG_ReadDelta( msg, from->angles[2], 16);
 +	to->forwardmove = MSG_ReadDelta( msg, from->forwardmove, 8);
 +	to->rightmove = MSG_ReadDelta( msg, from->rightmove, 8);
 +	to->upmove = MSG_ReadDelta( msg, from->upmove, 8);
 +	to->buttons = MSG_ReadDelta( msg, from->buttons, 16);
 +	to->weapon = MSG_ReadDelta( msg, from->weapon, 8);
 +}
 +
 +/*
 +=====================
 +MSG_WriteDeltaUsercmd
 +=====================
 +*/
 +void MSG_WriteDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *to ) {
 +	if ( to->serverTime - from->serverTime < 256 ) {
 +		MSG_WriteBits( msg, 1, 1 );
 +		MSG_WriteBits( msg, to->serverTime - from->serverTime, 8 );
 +	} else {
 +		MSG_WriteBits( msg, 0, 1 );
 +		MSG_WriteBits( msg, to->serverTime, 32 );
 +	}
 +	if (from->angles[0] == to->angles[0] &&
 +		from->angles[1] == to->angles[1] &&
 +		from->angles[2] == to->angles[2] &&
 +		from->forwardmove == to->forwardmove &&
 +		from->rightmove == to->rightmove &&
 +		from->upmove == to->upmove &&
 +		from->buttons == to->buttons &&
 +		from->weapon == to->weapon) {
 +			MSG_WriteBits( msg, 0, 1 );				// no change
 +			oldsize += 7;
 +			return;
 +	}
 +	key ^= to->serverTime;
 +	MSG_WriteBits( msg, 1, 1 );
 +	MSG_WriteDeltaKey( msg, key, from->angles[0], to->angles[0], 16 );
 +	MSG_WriteDeltaKey( msg, key, from->angles[1], to->angles[1], 16 );
 +	MSG_WriteDeltaKey( msg, key, from->angles[2], to->angles[2], 16 );
 +	MSG_WriteDeltaKey( msg, key, from->forwardmove, to->forwardmove, 8 );
 +	MSG_WriteDeltaKey( msg, key, from->rightmove, to->rightmove, 8 );
 +	MSG_WriteDeltaKey( msg, key, from->upmove, to->upmove, 8 );
 +	MSG_WriteDeltaKey( msg, key, from->buttons, to->buttons, 16 );
 +	MSG_WriteDeltaKey( msg, key, from->weapon, to->weapon, 8 );
 +}
 +
 +
 +/*
 +=====================
 +MSG_ReadDeltaUsercmd
 +=====================
 +*/
 +void MSG_ReadDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *to ) {
 +	if ( MSG_ReadBits( msg, 1 ) ) {
 +		to->serverTime = from->serverTime + MSG_ReadBits( msg, 8 );
 +	} else {
 +		to->serverTime = MSG_ReadBits( msg, 32 );
 +	}
 +	if ( MSG_ReadBits( msg, 1 ) ) {
 +		key ^= to->serverTime;
 +		to->angles[0] = MSG_ReadDeltaKey( msg, key, from->angles[0], 16);
 +		to->angles[1] = MSG_ReadDeltaKey( msg, key, from->angles[1], 16);
 +		to->angles[2] = MSG_ReadDeltaKey( msg, key, from->angles[2], 16);
 +		to->forwardmove = MSG_ReadDeltaKey( msg, key, from->forwardmove, 8);
 +		to->rightmove = MSG_ReadDeltaKey( msg, key, from->rightmove, 8);
 +		to->upmove = MSG_ReadDeltaKey( msg, key, from->upmove, 8);
 +		to->buttons = MSG_ReadDeltaKey( msg, key, from->buttons, 16);
 +		to->weapon = MSG_ReadDeltaKey( msg, key, from->weapon, 8);
 +	} else {
 +		to->angles[0] = from->angles[0];
 +		to->angles[1] = from->angles[1];
 +		to->angles[2] = from->angles[2];
 +		to->forwardmove = from->forwardmove;
 +		to->rightmove = from->rightmove;
 +		to->upmove = from->upmove;
 +		to->buttons = from->buttons;
 +		to->weapon = from->weapon;
 +	}
 +}
 +
 +/*
 +=============================================================================
 +
 +entityState_t communication
 +  
 +=============================================================================
 +*/
 +
 +/*
 +=================
 +MSG_ReportChangeVectors_f
 +
 +Prints out a table from the current statistics for copying to code
 +=================
 +*/
 +void MSG_ReportChangeVectors_f( void ) {
 +	int i;
 +	for(i=0;i<256;i++) {
 +		if (pcount[i]) {
 +			Com_Printf("%d used %d\n", i, pcount[i]);
 +		}
 +	}
 +}
 +
 +typedef struct {
 +	char	*name;
 +	int		offset;
 +	int		bits;		// 0 = float
 +} netField_t;
 +
 +// using the stringizing operator to save typing...
 +#define	NETF(x) #x,(int)&((entityState_t*)0)->x
 +
 +netField_t	entityStateFields[] = 
 +{
 +{ NETF(pos.trTime), 32 },
 +{ NETF(pos.trBase[0]), 0 },
 +{ NETF(pos.trBase[1]), 0 },
 +{ NETF(pos.trDelta[0]), 0 },
 +{ NETF(pos.trDelta[1]), 0 },
 +{ NETF(pos.trBase[2]), 0 },
 +{ NETF(apos.trBase[1]), 0 },
 +{ NETF(pos.trDelta[2]), 0 },
 +{ NETF(apos.trBase[0]), 0 },
 +{ NETF(event), 10 },
 +{ NETF(angles2[1]), 0 },
 +{ NETF(eType), 8 },
 +{ NETF(torsoAnim), 8 },
 +{ NETF(eventParm), 8 },
 +{ NETF(legsAnim), 8 },
 +{ NETF(groundEntityNum), GENTITYNUM_BITS },
 +{ NETF(pos.trType), 8 },
 +{ NETF(eFlags), 19 },
 +{ NETF(otherEntityNum), GENTITYNUM_BITS },
 +{ NETF(weapon), 8 },
 +{ NETF(clientNum), 8 },
 +{ NETF(angles[1]), 0 },
 +{ NETF(pos.trDuration), 32 },
 +{ NETF(apos.trType), 8 },
 +{ NETF(origin[0]), 0 },
 +{ NETF(origin[1]), 0 },
 +{ NETF(origin[2]), 0 },
 +{ NETF(solid), 24 },
 +{ NETF(powerups), MAX_POWERUPS },
 +{ NETF(modelindex), 8 },
 +{ NETF(otherEntityNum2), GENTITYNUM_BITS },
 +{ NETF(loopSound), 8 },
 +{ NETF(generic1), 8 },
 +{ NETF(origin2[2]), 0 },
 +{ NETF(origin2[0]), 0 },
 +{ NETF(origin2[1]), 0 },
 +{ NETF(modelindex2), 8 },
 +{ NETF(angles[0]), 0 },
 +{ NETF(time), 32 },
 +{ NETF(apos.trTime), 32 },
 +{ NETF(apos.trDuration), 32 },
 +{ NETF(apos.trBase[2]), 0 },
 +{ NETF(apos.trDelta[0]), 0 },
 +{ NETF(apos.trDelta[1]), 0 },
 +{ NETF(apos.trDelta[2]), 0 },
 +{ NETF(time2), 32 },
 +{ NETF(angles[2]), 0 },
 +{ NETF(angles2[0]), 0 },
 +{ NETF(angles2[2]), 0 },
 +{ NETF(constantLight), 32 },
 +{ NETF(frame), 16 }
 +};
 +
 +
 +// if (int)f == f and (int)f + ( 1<<(FLOAT_INT_BITS-1) ) < ( 1 << FLOAT_INT_BITS )
 +// the float will be sent with FLOAT_INT_BITS, otherwise all 32 bits will be sent
 +#define	FLOAT_INT_BITS	13
 +#define	FLOAT_INT_BIAS	(1<<(FLOAT_INT_BITS-1))
 +
 +/*
 +==================
 +MSG_WriteDeltaEntity
 +
 +Writes part of a packetentities message, including the entity number.
 +Can delta from either a baseline or a previous packet_entity
 +If to is NULL, a remove entity update will be sent
 +If force is not set, then nothing at all will be generated if the entity is
 +identical, under the assumption that the in-order delta code will catch it.
 +==================
 +*/
 +void MSG_WriteDeltaEntity( msg_t *msg, struct entityState_s *from, struct entityState_s *to, 
 +						   qboolean force ) {
 +	int			i, lc;
 +	int			numFields;
 +	netField_t	*field;
 +	int			trunc;
 +	float		fullFloat;
 +	int			*fromF, *toF;
 +
 +	numFields = sizeof(entityStateFields)/sizeof(entityStateFields[0]);
 +
 +	// all fields should be 32 bits to avoid any compiler packing issues
 +	// the "number" field is not part of the field list
 +	// if this assert fails, someone added a field to the entityState_t
 +	// struct without updating the message fields
 +	assert( numFields + 1 == sizeof( *from )/4 );
 +
 +	// a NULL to is a delta remove message
 +	if ( to == NULL ) {
 +		if ( from == NULL ) {
 +			return;
 +		}
 +		MSG_WriteBits( msg, from->number, GENTITYNUM_BITS );
 +		MSG_WriteBits( msg, 1, 1 );
 +		return;
 +	}
 +
 +	if ( to->number < 0 || to->number >= MAX_GENTITIES ) {
 +		Com_Error (ERR_FATAL, "MSG_WriteDeltaEntity: Bad entity number: %i", to->number );
 +	}
 +
 +	lc = 0;
 +	// build the change vector as bytes so it is endien independent
 +	for ( i = 0, field = entityStateFields ; i < numFields ; i++, field++ ) {
 +		fromF = (int *)( (byte *)from + field->offset );
 +		toF = (int *)( (byte *)to + field->offset );
 +		if ( *fromF != *toF ) {
 +			lc = i+1;
 +		}
 +	}
 +
 +	if ( lc == 0 ) {
 +		// nothing at all changed
 +		if ( !force ) {
 +			return;		// nothing at all
 +		}
 +		// write two bits for no change
 +		MSG_WriteBits( msg, to->number, GENTITYNUM_BITS );
 +		MSG_WriteBits( msg, 0, 1 );		// not removed
 +		MSG_WriteBits( msg, 0, 1 );		// no delta
 +		return;
 +	}
 +
 +	MSG_WriteBits( msg, to->number, GENTITYNUM_BITS );
 +	MSG_WriteBits( msg, 0, 1 );			// not removed
 +	MSG_WriteBits( msg, 1, 1 );			// we have a delta
 +
 +	MSG_WriteByte( msg, lc );	// # of changes
 +
 +	oldsize += numFields;
 +
 +	for ( i = 0, field = entityStateFields ; i < lc ; i++, field++ ) {
 +		fromF = (int *)( (byte *)from + field->offset );
 +		toF = (int *)( (byte *)to + field->offset );
 +
 +		if ( *fromF == *toF ) {
 +			MSG_WriteBits( msg, 0, 1 );	// no change
 +			continue;
 +		}
 +
 +		MSG_WriteBits( msg, 1, 1 );	// changed
 +
 +		if ( field->bits == 0 ) {
 +			// float
 +			fullFloat = *(float *)toF;
 +			trunc = (int)fullFloat;
 +
 +			if (fullFloat == 0.0f) {
 +					MSG_WriteBits( msg, 0, 1 );
 +					oldsize += FLOAT_INT_BITS;
 +			} else {
 +				MSG_WriteBits( msg, 1, 1 );
 +				if ( trunc == fullFloat && trunc + FLOAT_INT_BIAS >= 0 && 
 +					trunc + FLOAT_INT_BIAS < ( 1 << FLOAT_INT_BITS ) ) {
 +					// send as small integer
 +					MSG_WriteBits( msg, 0, 1 );
 +					MSG_WriteBits( msg, trunc + FLOAT_INT_BIAS, FLOAT_INT_BITS );
 +				} else {
 +					// send as full floating point value
 +					MSG_WriteBits( msg, 1, 1 );
 +					MSG_WriteBits( msg, *toF, 32 );
 +				}
 +			}
 +		} else {
 +			if (*toF == 0) {
 +				MSG_WriteBits( msg, 0, 1 );
 +			} else {
 +				MSG_WriteBits( msg, 1, 1 );
 +				// integer
 +				MSG_WriteBits( msg, *toF, field->bits );
 +			}
 +		}
 +	}
 +}
 +
 +/*
 +==================
 +MSG_ReadDeltaEntity
 +
 +The entity number has already been read from the message, which
 +is how the from state is identified.
 +
 +If the delta removes the entity, entityState_t->number will be set to MAX_GENTITIES-1
 +
 +Can go from either a baseline or a previous packet_entity
 +==================
 +*/
 +extern	cvar_t	*cl_shownet;
 +
 +void MSG_ReadDeltaEntity( msg_t *msg, entityState_t *from, entityState_t *to, 
 +						 int number) {
 +	int			i, lc;
 +	int			numFields;
 +	netField_t	*field;
 +	int			*fromF, *toF;
 +	int			print;
 +	int			trunc;
 +	int			startBit, endBit;
 +
 +	if ( number < 0 || number >= MAX_GENTITIES) {
 +		Com_Error( ERR_DROP, "Bad delta entity number: %i", number );
 +	}
 +
 +	if ( msg->bit == 0 ) {
 +		startBit = msg->readcount * 8 - GENTITYNUM_BITS;
 +	} else {
 +		startBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS;
 +	}
 +
 +	// check for a remove
 +	if ( MSG_ReadBits( msg, 1 ) == 1 ) {
 +		Com_Memset( to, 0, sizeof( *to ) );	
 +		to->number = MAX_GENTITIES - 1;
 +		if ( cl_shownet->integer >= 2 || cl_shownet->integer == -1 ) {
 +			Com_Printf( "%3i: #%-3i remove\n", msg->readcount, number );
 +		}
 +		return;
 +	}
 +
 +	// check for no delta
 +	if ( MSG_ReadBits( msg, 1 ) == 0 ) {
 +		*to = *from;
 +		to->number = number;
 +		return;
 +	}
 +
 +	numFields = sizeof(entityStateFields)/sizeof(entityStateFields[0]);
 +	lc = MSG_ReadByte(msg);
 +
 +	// shownet 2/3 will interleave with other printed info, -1 will
 +	// just print the delta records`
 +	if ( cl_shownet->integer >= 2 || cl_shownet->integer == -1 ) {
 +		print = 1;
 +		Com_Printf( "%3i: #%-3i ", msg->readcount, to->number );
 +	} else {
 +		print = 0;
 +	}
 +
 +	to->number = number;
 +
 +	for ( i = 0, field = entityStateFields ; i < lc ; i++, field++ ) {
 +		fromF = (int *)( (byte *)from + field->offset );
 +		toF = (int *)( (byte *)to + field->offset );
 +
 +		if ( ! MSG_ReadBits( msg, 1 ) ) {
 +			// no change
 +			*toF = *fromF;
 +		} else {
 +			if ( field->bits == 0 ) {
 +				// float
 +				if ( MSG_ReadBits( msg, 1 ) == 0 ) {
 +						*(float *)toF = 0.0f; 
 +				} else {
 +					if ( MSG_ReadBits( msg, 1 ) == 0 ) {
 +						// integral float
 +						trunc = MSG_ReadBits( msg, FLOAT_INT_BITS );
 +						// bias to allow equal parts positive and negative
 +						trunc -= FLOAT_INT_BIAS;
 +						*(float *)toF = trunc; 
 +						if ( print ) {
 +							Com_Printf( "%s:%i ", field->name, trunc );
 +						}
 +					} else {
 +						// full floating point value
 +						*toF = MSG_ReadBits( msg, 32 );
 +						if ( print ) {
 +							Com_Printf( "%s:%f ", field->name, *(float *)toF );
 +						}
 +					}
 +				}
 +			} else {
 +				if ( MSG_ReadBits( msg, 1 ) == 0 ) {
 +					*toF = 0;
 +				} else {
 +					// integer
 +					*toF = MSG_ReadBits( msg, field->bits );
 +					if ( print ) {
 +						Com_Printf( "%s:%i ", field->name, *toF );
 +					}
 +				}
 +			}
 +//			pcount[i]++;
 +		}
 +	}
 +	for ( i = lc, field = &entityStateFields[lc] ; i < numFields ; i++, field++ ) {
 +		fromF = (int *)( (byte *)from + field->offset );
 +		toF = (int *)( (byte *)to + field->offset );
 +		// no change
 +		*toF = *fromF;
 +	}
 +
 +	if ( print ) {
 +		if ( msg->bit == 0 ) {
 +			endBit = msg->readcount * 8 - GENTITYNUM_BITS;
 +		} else {
 +			endBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS;
 +		}
 +		Com_Printf( " (%i bits)\n", endBit - startBit  );
 +	}
 +}
 +
 +
 +/*
 +============================================================================
 +
 +plyer_state_t communication
 +
 +============================================================================
 +*/
 +
 +// using the stringizing operator to save typing...
 +#define	PSF(x) #x,(int)&((playerState_t*)0)->x
 +
 +netField_t	playerStateFields[] = 
 +{
 +{ PSF(commandTime), 32 },				
 +{ PSF(origin[0]), 0 },
 +{ PSF(origin[1]), 0 },
 +{ PSF(bobCycle), 8 },
 +{ PSF(velocity[0]), 0 },
 +{ PSF(velocity[1]), 0 },
 +{ PSF(viewangles[1]), 0 },
 +{ PSF(viewangles[0]), 0 },
 +{ PSF(weaponTime), -16 },
 +{ PSF(origin[2]), 0 },
 +{ PSF(velocity[2]), 0 },
 +{ PSF(legsTimer), 8 },
 +{ PSF(pm_time), -16 },
 +{ PSF(eventSequence), 16 },
 +{ PSF(torsoAnim), 8 },
 +{ PSF(movementDir), 4 },
 +{ PSF(events[0]), 8 },
 +{ PSF(legsAnim), 8 },
 +{ PSF(events[1]), 8 },
 +{ PSF(pm_flags), 16 },
 +{ PSF(groundEntityNum), GENTITYNUM_BITS },
 +{ PSF(weaponstate), 4 },
 +{ PSF(eFlags), 16 },
 +{ PSF(externalEvent), 10 },
 +{ PSF(gravity), 16 },
 +{ PSF(speed), 16 },
 +{ PSF(delta_angles[1]), 16 },
 +{ PSF(externalEventParm), 8 },
 +{ PSF(viewheight), -8 },
 +{ PSF(damageEvent), 8 },
 +{ PSF(damageYaw), 8 },
 +{ PSF(damagePitch), 8 },
 +{ PSF(damageCount), 8 },
 +{ PSF(generic1), 8 },
 +{ PSF(pm_type), 8 },					
 +{ PSF(delta_angles[0]), 16 },
 +{ PSF(delta_angles[2]), 16 },
 +{ PSF(torsoTimer), 12 },
 +{ PSF(eventParms[0]), 8 },
 +{ PSF(eventParms[1]), 8 },
 +{ PSF(clientNum), 8 },
 +{ PSF(weapon), 5 },
 +{ PSF(viewangles[2]), 0 },
 +{ PSF(grapplePoint[0]), 0 },
 +{ PSF(grapplePoint[1]), 0 },
 +{ PSF(grapplePoint[2]), 0 },
 +{ PSF(otherEntityNum), 10 },
 +{ PSF(loopSound), 16 }
 +};
 +
 +/*
 +=============
 +MSG_WriteDeltaPlayerstate
 +
 +=============
 +*/
 +void MSG_WriteDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to ) {
 +	int				i;
 +	playerState_t	dummy;
 +	int				statsbits;
 +	int				persistantbits;
 +	int				ammobits;
 +	int				powerupbits;
 +	int				numFields;
 +	int				c;
 +	netField_t		*field;
 +	int				*fromF, *toF;
 +	float			fullFloat;
 +	int				trunc, lc;
 +
 +	if (!from) {
 +		from = &dummy;
 +		Com_Memset (&dummy, 0, sizeof(dummy));
 +	}
 +
 +	c = msg->cursize;
 +
 +	numFields = sizeof( playerStateFields ) / sizeof( playerStateFields[0] );
 +
 +	lc = 0;
 +	for ( i = 0, field = playerStateFields ; i < numFields ; i++, field++ ) {
 +		fromF = (int *)( (byte *)from + field->offset );
 +		toF = (int *)( (byte *)to + field->offset );
 +		if ( *fromF != *toF ) {
 +			lc = i+1;
 +		}
 +	}
 +
 +	MSG_WriteByte( msg, lc );	// # of changes
 +
 +	oldsize += numFields - lc;
 +
 +	for ( i = 0, field = playerStateFields ; i < lc ; i++, field++ ) {
 +		fromF = (int *)( (byte *)from + field->offset );
 +		toF = (int *)( (byte *)to + field->offset );
 +
 +		if ( *fromF == *toF ) {
 +			MSG_WriteBits( msg, 0, 1 );	// no change
 +			continue;
 +		}
 +
 +		MSG_WriteBits( msg, 1, 1 );	// changed
 +//		pcount[i]++;
 +
 +		if ( field->bits == 0 ) {
 +			// float
 +			fullFloat = *(float *)toF;
 +			trunc = (int)fullFloat;
 +
 +			if ( trunc == fullFloat && trunc + FLOAT_INT_BIAS >= 0 && 
 +				trunc + FLOAT_INT_BIAS < ( 1 << FLOAT_INT_BITS ) ) {
 +				// send as small integer
 +				MSG_WriteBits( msg, 0, 1 );
 +				MSG_WriteBits( msg, trunc + FLOAT_INT_BIAS, FLOAT_INT_BITS );
 +			} else {
 +				// send as full floating point value
 +				MSG_WriteBits( msg, 1, 1 );
 +				MSG_WriteBits( msg, *toF, 32 );
 +			}
 +		} else {
 +			// integer
 +			MSG_WriteBits( msg, *toF, field->bits );
 +		}
 +	}
 +	c = msg->cursize - c;
 +
 +
 +	//
 +	// send the arrays
 +	//
 +	statsbits = 0;
 +	for (i=0 ; i<MAX_STATS ; i++) {
 +		if (to->stats[i] != from->stats[i]) {
 +			statsbits |= 1<<i;
 +		}
 +	}
 +	persistantbits = 0;
 +	for (i=0 ; i<MAX_PERSISTANT ; i++) {
 +		if (to->persistant[i] != from->persistant[i]) {
 +			persistantbits |= 1<<i;
 +		}
 +	}
 +	ammobits = 0;
 +	for (i=0 ; i<MAX_WEAPONS ; i++) {
 +		if (to->ammo[i] != from->ammo[i]) {
 +			ammobits |= 1<<i;
 +		}
 +	}
 +	powerupbits = 0;
 +	for (i=0 ; i<MAX_POWERUPS ; i++) {
 +		if (to->powerups[i] != from->powerups[i]) {
 +			powerupbits |= 1<<i;
 +		}
 +	}
 +
 +	if (!statsbits && !persistantbits && !ammobits && !powerupbits) {
 +		MSG_WriteBits( msg, 0, 1 );	// no change
 +		oldsize += 4;
 +		return;
 +	}
 +	MSG_WriteBits( msg, 1, 1 );	// changed
 +
 +	if ( statsbits ) {
 +		MSG_WriteBits( msg, 1, 1 );	// changed
 +		MSG_WriteBits( msg, statsbits, MAX_STATS );
 +		for (i=0 ; i<MAX_STATS ; i++)
 +			if (statsbits & (1<<i) )
 +				MSG_WriteShort (msg, to->stats[i]);
 +	} else {
 +		MSG_WriteBits( msg, 0, 1 );	// no change
 +	}
 +
 +
 +	if ( persistantbits ) {
 +		MSG_WriteBits( msg, 1, 1 );	// changed
 +		MSG_WriteBits( msg, persistantbits, MAX_PERSISTANT );
 +		for (i=0 ; i<MAX_PERSISTANT ; i++)
 +			if (persistantbits & (1<<i) )
 +				MSG_WriteShort (msg, to->persistant[i]);
 +	} else {
 +		MSG_WriteBits( msg, 0, 1 );	// no change
 +	}
 +
 +
 +	if ( ammobits ) {
 +		MSG_WriteBits( msg, 1, 1 );	// changed
 +		MSG_WriteBits( msg, ammobits, MAX_WEAPONS );
 +		for (i=0 ; i<MAX_WEAPONS ; i++)
 +			if (ammobits & (1<<i) )
 +				MSG_WriteShort (msg, to->ammo[i]);
 +	} else {
 +		MSG_WriteBits( msg, 0, 1 );	// no change
 +	}
 +
 +
 +	if ( powerupbits ) {
 +		MSG_WriteBits( msg, 1, 1 );	// changed
 +		MSG_WriteBits( msg, powerupbits, MAX_POWERUPS );
 +		for (i=0 ; i<MAX_POWERUPS ; i++)
 +			if (powerupbits & (1<<i) )
 +				MSG_WriteLong( msg, to->powerups[i] );
 +	} else {
 +		MSG_WriteBits( msg, 0, 1 );	// no change
 +	}
 +}
 +
 +
 +/*
 +===================
 +MSG_ReadDeltaPlayerstate
 +===================
 +*/
 +void MSG_ReadDeltaPlayerstate (msg_t *msg, playerState_t *from, playerState_t *to ) {
 +	int			i, lc;
 +	int			bits;
 +	netField_t	*field;
 +	int			numFields;
 +	int			startBit, endBit;
 +	int			print;
 +	int			*fromF, *toF;
 +	int			trunc;
 +	playerState_t	dummy;
 +
 +	if ( !from ) {
 +		from = &dummy;
 +		Com_Memset( &dummy, 0, sizeof( dummy ) );
 +	}
 +	*to = *from;
 +
 +	if ( msg->bit == 0 ) {
 +		startBit = msg->readcount * 8 - GENTITYNUM_BITS;
 +	} else {
 +		startBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS;
 +	}
 +
 +	// shownet 2/3 will interleave with other printed info, -2 will
 +	// just print the delta records
 +	if ( cl_shownet->integer >= 2 || cl_shownet->integer == -2 ) {
 +		print = 1;
 +		Com_Printf( "%3i: playerstate ", msg->readcount );
 +	} else {
 +		print = 0;
 +	}
 +
 +	numFields = sizeof( playerStateFields ) / sizeof( playerStateFields[0] );
 +	lc = MSG_ReadByte(msg);
 +
 +	for ( i = 0, field = playerStateFields ; i < lc ; i++, field++ ) {
 +		fromF = (int *)( (byte *)from + field->offset );
 +		toF = (int *)( (byte *)to + field->offset );
 +
 +		if ( ! MSG_ReadBits( msg, 1 ) ) {
 +			// no change
 +			*toF = *fromF;
 +		} else {
 +			if ( field->bits == 0 ) {
 +				// float
 +				if ( MSG_ReadBits( msg, 1 ) == 0 ) {
 +					// integral float
 +					trunc = MSG_ReadBits( msg, FLOAT_INT_BITS );
 +					// bias to allow equal parts positive and negative
 +					trunc -= FLOAT_INT_BIAS;
 +					*(float *)toF = trunc; 
 +					if ( print ) {
 +						Com_Printf( "%s:%i ", field->name, trunc );
 +					}
 +				} else {
 +					// full floating point value
 +					*toF = MSG_ReadBits( msg, 32 );
 +					if ( print ) {
 +						Com_Printf( "%s:%f ", field->name, *(float *)toF );
 +					}
 +				}
 +			} else {
 +				// integer
 +				*toF = MSG_ReadBits( msg, field->bits );
 +				if ( print ) {
 +					Com_Printf( "%s:%i ", field->name, *toF );
 +				}
 +			}
 +		}
 +	}
 +	for ( i=lc,field = &playerStateFields[lc];i<numFields; i++, field++) {
 +		fromF = (int *)( (byte *)from + field->offset );
 +		toF = (int *)( (byte *)to + field->offset );
 +		// no change
 +		*toF = *fromF;
 +	}
 +
 +
 +	// read the arrays
 +	if (MSG_ReadBits( msg, 1 ) ) {
 +		// parse stats
 +		if ( MSG_ReadBits( msg, 1 ) ) {
 +			LOG("PS_STATS");
 +			bits = MSG_ReadBits (msg, MAX_STATS);
 +			for (i=0 ; i<MAX_STATS ; i++) {
 +				if (bits & (1<<i) ) {
 +					to->stats[i] = MSG_ReadShort(msg);
 +				}
 +			}
 +		}
 +
 +		// parse persistant stats
 +		if ( MSG_ReadBits( msg, 1 ) ) {
 +			LOG("PS_PERSISTANT");
 +			bits = MSG_ReadBits (msg, MAX_PERSISTANT);
 +			for (i=0 ; i<MAX_PERSISTANT ; i++) {
 +				if (bits & (1<<i) ) {
 +					to->persistant[i] = MSG_ReadShort(msg);
 +				}
 +			}
 +		}
 +
 +		// parse ammo
 +		if ( MSG_ReadBits( msg, 1 ) ) {
 +			LOG("PS_AMMO");
 +			bits = MSG_ReadBits (msg, MAX_WEAPONS);
 +			for (i=0 ; i<MAX_WEAPONS ; i++) {
 +				if (bits & (1<<i) ) {
 +					to->ammo[i] = MSG_ReadShort(msg);
 +				}
 +			}
 +		}
 +
 +		// parse powerups
 +		if ( MSG_ReadBits( msg, 1 ) ) {
 +			LOG("PS_POWERUPS");
 +			bits = MSG_ReadBits (msg, MAX_POWERUPS);
 +			for (i=0 ; i<MAX_POWERUPS ; i++) {
 +				if (bits & (1<<i) ) {
 +					to->powerups[i] = MSG_ReadLong(msg);
 +				}
 +			}
 +		}
 +	}
 +
 +	if ( print ) {
 +		if ( msg->bit == 0 ) {
 +			endBit = msg->readcount * 8 - GENTITYNUM_BITS;
 +		} else {
 +			endBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS;
 +		}
 +		Com_Printf( " (%i bits)\n", endBit - startBit  );
 +	}
 +}
 +
 +int msg_hData[256] = {
 +250315,			// 0
 +41193,			// 1
 +6292,			// 2
 +7106,			// 3
 +3730,			// 4
 +3750,			// 5
 +6110,			// 6
 +23283,			// 7
 +33317,			// 8
 +6950,			// 9
 +7838,			// 10
 +9714,			// 11
 +9257,			// 12
 +17259,			// 13
 +3949,			// 14
 +1778,			// 15
 +8288,			// 16
 +1604,			// 17
 +1590,			// 18
 +1663,			// 19
 +1100,			// 20
 +1213,			// 21
 +1238,			// 22
 +1134,			// 23
 +1749,			// 24
 +1059,			// 25
 +1246,			// 26
 +1149,			// 27
 +1273,			// 28
 +4486,			// 29
 +2805,			// 30
 +3472,			// 31
 +21819,			// 32
 +1159,			// 33
 +1670,			// 34
 +1066,			// 35
 +1043,			// 36
 +1012,			// 37
 +1053,			// 38
 +1070,			// 39
 +1726,			// 40
 +888,			// 41
 +1180,			// 42
 +850,			// 43
 +960,			// 44
 +780,			// 45
 +1752,			// 46
 +3296,			// 47
 +10630,			// 48
 +4514,			// 49
 +5881,			// 50
 +2685,			// 51
 +4650,			// 52
 +3837,			// 53
 +2093,			// 54
 +1867,			// 55
 +2584,			// 56
 +1949,			// 57
 +1972,			// 58
 +940,			// 59
 +1134,			// 60
 +1788,			// 61
 +1670,			// 62
 +1206,			// 63
 +5719,			// 64
 +6128,			// 65
 +7222,			// 66
 +6654,			// 67
 +3710,			// 68
 +3795,			// 69
 +1492,			// 70
 +1524,			// 71
 +2215,			// 72
 +1140,			// 73
 +1355,			// 74
 +971,			// 75
 +2180,			// 76
 +1248,			// 77
 +1328,			// 78
 +1195,			// 79
 +1770,			// 80
 +1078,			// 81
 +1264,			// 82
 +1266,			// 83
 +1168,			// 84
 +965,			// 85
 +1155,			// 86
 +1186,			// 87
 +1347,			// 88
 +1228,			// 89
 +1529,			// 90
 +1600,			// 91
 +2617,			// 92
 +2048,			// 93
 +2546,			// 94
 +3275,			// 95
 +2410,			// 96
 +3585,			// 97
 +2504,			// 98
 +2800,			// 99
 +2675,			// 100
 +6146,			// 101
 +3663,			// 102
 +2840,			// 103
 +14253,			// 104
 +3164,			// 105
 +2221,			// 106
 +1687,			// 107
 +3208,			// 108
 +2739,			// 109
 +3512,			// 110
 +4796,			// 111
 +4091,			// 112
 +3515,			// 113
 +5288,			// 114
 +4016,			// 115
 +7937,			// 116
 +6031,			// 117
 +5360,			// 118
 +3924,			// 119
 +4892,			// 120
 +3743,			// 121
 +4566,			// 122
 +4807,			// 123
 +5852,			// 124
 +6400,			// 125
 +6225,			// 126
 +8291,			// 127
 +23243,			// 128
 +7838,			// 129
 +7073,			// 130
 +8935,			// 131
 +5437,			// 132
 +4483,			// 133
 +3641,			// 134
 +5256,			// 135
 +5312,			// 136
 +5328,			// 137
 +5370,			// 138
 +3492,			// 139
 +2458,			// 140
 +1694,			// 141
 +1821,			// 142
 +2121,			// 143
 +1916,			// 144
 +1149,			// 145
 +1516,			// 146
 +1367,			// 147
 +1236,			// 148
 +1029,			// 149
 +1258,			// 150
 +1104,			// 151
 +1245,			// 152
 +1006,			// 153
 +1149,			// 154
 +1025,			// 155
 +1241,			// 156
 +952,			// 157
 +1287,			// 158
 +997,			// 159
 +1713,			// 160
 +1009,			// 161
 +1187,			// 162
 +879,			// 163
 +1099,			// 164
 +929,			// 165
 +1078,			// 166
 +951,			// 167
 +1656,			// 168
 +930,			// 169
 +1153,			// 170
 +1030,			// 171
 +1262,			// 172
 +1062,			// 173
 +1214,			// 174
 +1060,			// 175
 +1621,			// 176
 +930,			// 177
 +1106,			// 178
 +912,			// 179
 +1034,			// 180
 +892,			// 181
 +1158,			// 182
 +990,			// 183
 +1175,			// 184
 +850,			// 185
 +1121,			// 186
 +903,			// 187
 +1087,			// 188
 +920,			// 189
 +1144,			// 190
 +1056,			// 191
 +3462,			// 192
 +2240,			// 193
 +4397,			// 194
 +12136,			// 195
 +7758,			// 196
 +1345,			// 197
 +1307,			// 198
 +3278,			// 199
 +1950,			// 200
 +886,			// 201
 +1023,			// 202
 +1112,			// 203
 +1077,			// 204
 +1042,			// 205
 +1061,			// 206
 +1071,			// 207
 +1484,			// 208
 +1001,			// 209
 +1096,			// 210
 +915,			// 211
 +1052,			// 212
 +995,			// 213
 +1070,			// 214
 +876,			// 215
 +1111,			// 216
 +851,			// 217
 +1059,			// 218
 +805,			// 219
 +1112,			// 220
 +923,			// 221
 +1103,			// 222
 +817,			// 223
 +1899,			// 224
 +1872,			// 225
 +976,			// 226
 +841,			// 227
 +1127,			// 228
 +956,			// 229
 +1159,			// 230
 +950,			// 231
 +7791,			// 232
 +954,			// 233
 +1289,			// 234
 +933,			// 235
 +1127,			// 236
 +3207,			// 237
 +1020,			// 238
 +927,			// 239
 +1355,			// 240
 +768,			// 241
 +1040,			// 242
 +745,			// 243
 +952,			// 244
 +805,			// 245
 +1073,			// 246
 +740,			// 247
 +1013,			// 248
 +805,			// 249
 +1008,			// 250
 +796,			// 251
 +996,			// 252
 +1057,			// 253
 +11457,			// 254
 +13504,			// 255
 +};
 +
 +void MSG_initHuffman( void ) {
 +	int i,j;
 +
 +	msgInit = qtrue;
 +	Huff_Init(&msgHuff);
 +	for(i=0;i<256;i++) {
 +		for (j=0;j<msg_hData[i];j++) {
 +			Huff_addRef(&msgHuff.compressor,	(byte)i);			// Do update
 +			Huff_addRef(&msgHuff.decompressor,	(byte)i);			// Do update
 +		}
 +	}
 +}
 +
 +/*
 +void MSG_NUinitHuffman() {
 +	byte	*data;
 +	int		size, i, ch;
 +	int		array[256];
 +
 +	msgInit = qtrue;
 +
 +	Huff_Init(&msgHuff);
 +	// load it in
 +	size = FS_ReadFile( "netchan/netchan.bin", (void **)&data );
 +
 +	for(i=0;i<256;i++) {
 +		array[i] = 0;
 +	}
 +	for(i=0;i<size;i++) {
 +		ch = data[i];
 +		Huff_addRef(&msgHuff.compressor,	ch);			// Do update
 +		Huff_addRef(&msgHuff.decompressor,	ch);			// Do update
 +		array[ch]++;
 +	}
 +	Com_Printf("msg_hData {\n");
 +	for(i=0;i<256;i++) {
 +		if (array[i] == 0) {
 +			Huff_addRef(&msgHuff.compressor,	i);			// Do update
 +			Huff_addRef(&msgHuff.decompressor,	i);			// Do update
 +		}
 +		Com_Printf("%d,			// %d\n", array[i], i);
 +	}
 +	Com_Printf("};\n");
 +	FS_FreeFile( data );
 +	Cbuf_AddText( "condump dump.txt\n" );
 +}
 +*/
 +
 +//===========================================================================
 diff --git a/src/qcommon/net_chan.c b/src/qcommon/net_chan.c new file mode 100644 index 0000000..869d87a --- /dev/null +++ b/src/qcommon/net_chan.c @@ -0,0 +1,801 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ + +#include "q_shared.h" +#include "qcommon.h" + +/* + +packet header +------------- +4	outgoing sequence.  high bit will be set if this is a fragmented message +[2	qport (only for client to server)] +[2	fragment start byte] +[2	fragment length. if < FRAGMENT_SIZE, this is the last fragment] + +if the sequence number is -1, the packet should be handled as an out-of-band +message instead of as part of a netcon. + +All fragments will have the same sequence numbers. + +The qport field is a workaround for bad address translating routers that +sometimes remap the client's source port on a packet during gameplay. + +If the base part of the net address matches and the qport matches, then the +channel matches even if the IP port differs.  The IP port should be updated +to the new value before sending out any replies. + +*/ + + +#define	MAX_PACKETLEN			1400		// max size of a network packet + +#define	FRAGMENT_SIZE			(MAX_PACKETLEN - 100) +#define	PACKET_HEADER			10			// two ints and a short + +#define	FRAGMENT_BIT	(1<<31) + +cvar_t		*showpackets; +cvar_t		*showdrop; +cvar_t		*qport; + +static char *netsrcString[2] = { +	"client", +	"server" +}; + +/* +=============== +Netchan_Init + +=============== +*/ +void Netchan_Init( int port ) { +	port &= 0xffff; +	showpackets = Cvar_Get ("showpackets", "0", CVAR_TEMP ); +	showdrop = Cvar_Get ("showdrop", "0", CVAR_TEMP ); +	qport = Cvar_Get ("net_qport", va("%i", port), CVAR_INIT ); +} + +/* +============== +Netchan_Setup + +called to open a channel to a remote system +============== +*/ +void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport ) { +	Com_Memset (chan, 0, sizeof(*chan)); +	 +	chan->sock = sock; +	chan->remoteAddress = adr; +	chan->qport = qport; +	chan->incomingSequence = 0; +	chan->outgoingSequence = 1; +} + +// TTimo: unused, commenting out to make gcc happy +#if 0 +/* +============== +Netchan_ScramblePacket + +A probably futile attempt to make proxy hacking somewhat +more difficult. +============== +*/ +#define	SCRAMBLE_START	6 +static void Netchan_ScramblePacket( msg_t *buf ) { +	unsigned	seed; +	int			i, j, c, mask, temp; +	int			seq[MAX_PACKETLEN]; + +	seed = ( LittleLong( *(unsigned *)buf->data ) * 3 ) ^ ( buf->cursize * 123 ); +	c = buf->cursize; +	if ( c <= SCRAMBLE_START ) { +		return; +	} +	if ( c > MAX_PACKETLEN ) { +		Com_Error( ERR_DROP, "MAX_PACKETLEN" ); +	} + +	// generate a sequence of "random" numbers +	for (i = 0 ; i < c ; i++) { +		seed = (119 * seed + 1); +		seq[i] = seed; +	} + +	// transpose each character +	for ( mask = 1 ; mask < c-SCRAMBLE_START ; mask = ( mask << 1 ) + 1 ) { +	} +	mask >>= 1; +	for (i = SCRAMBLE_START ; i < c ; i++) { +		j = SCRAMBLE_START + ( seq[i] & mask ); +		temp = buf->data[j]; +		buf->data[j] = buf->data[i]; +		buf->data[i] = temp; +	} + +	// byte xor the data after the header +	for (i = SCRAMBLE_START ; i < c ; i++) { +		buf->data[i] ^= seq[i]; +	} +} + +static void Netchan_UnScramblePacket( msg_t *buf ) { +	unsigned	seed; +	int			i, j, c, mask, temp; +	int			seq[MAX_PACKETLEN]; + +	seed = ( LittleLong( *(unsigned *)buf->data ) * 3 ) ^ ( buf->cursize * 123 ); +	c = buf->cursize; +	if ( c <= SCRAMBLE_START ) { +		return; +	} +	if ( c > MAX_PACKETLEN ) { +		Com_Error( ERR_DROP, "MAX_PACKETLEN" ); +	} + +	// generate a sequence of "random" numbers +	for (i = 0 ; i < c ; i++) { +		seed = (119 * seed + 1); +		seq[i] = seed; +	} + +	// byte xor the data after the header +	for (i = SCRAMBLE_START ; i < c ; i++) { +		buf->data[i] ^= seq[i]; +	} + +	// transpose each character in reverse order +	for ( mask = 1 ; mask < c-SCRAMBLE_START ; mask = ( mask << 1 ) + 1 ) { +	} +	mask >>= 1; +	for (i = c-1 ; i >= SCRAMBLE_START ; i--) { +		j = SCRAMBLE_START + ( seq[i] & mask ); +		temp = buf->data[j]; +		buf->data[j] = buf->data[i]; +		buf->data[i] = temp; +	} +} +#endif + +/* +================= +Netchan_TransmitNextFragment + +Send one fragment of the current message +================= +*/ +void Netchan_TransmitNextFragment( netchan_t *chan ) { +	msg_t		send; +	byte		send_buf[MAX_PACKETLEN]; +	int			fragmentLength; + +	// write the packet header +	MSG_InitOOB (&send, send_buf, sizeof(send_buf));				// <-- only do the oob here + +	MSG_WriteLong( &send, chan->outgoingSequence | FRAGMENT_BIT ); + +	// send the qport if we are a client +	if ( chan->sock == NS_CLIENT ) { +		MSG_WriteShort( &send, qport->integer ); +	} + +	// copy the reliable message to the packet first +	fragmentLength = FRAGMENT_SIZE; +	if ( chan->unsentFragmentStart  + fragmentLength > chan->unsentLength ) { +		fragmentLength = chan->unsentLength - chan->unsentFragmentStart; +	} + +	MSG_WriteShort( &send, chan->unsentFragmentStart ); +	MSG_WriteShort( &send, fragmentLength ); +	MSG_WriteData( &send, chan->unsentBuffer + chan->unsentFragmentStart, fragmentLength ); + +	// send the datagram +	NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress ); + +	if ( showpackets->integer ) { +		Com_Printf ("%s send %4i : s=%i fragment=%i,%i\n" +			, netsrcString[ chan->sock ] +			, send.cursize +			, chan->outgoingSequence +			, chan->unsentFragmentStart, fragmentLength); +	} + +	chan->unsentFragmentStart += fragmentLength; + +	// this exit condition is a little tricky, because a packet +	// that is exactly the fragment length still needs to send +	// a second packet of zero length so that the other side +	// can tell there aren't more to follow +	if ( chan->unsentFragmentStart == chan->unsentLength && fragmentLength != FRAGMENT_SIZE ) { +		chan->outgoingSequence++; +		chan->unsentFragments = qfalse; +	} +} + + +/* +=============== +Netchan_Transmit + +Sends a message to a connection, fragmenting if necessary +A 0 length will still generate a packet. +================ +*/ +void Netchan_Transmit( netchan_t *chan, int length, const byte *data ) { +	msg_t		send; +	byte		send_buf[MAX_PACKETLEN]; + +	if ( length > MAX_MSGLEN ) { +		Com_Error( ERR_DROP, "Netchan_Transmit: length = %i", length ); +	} +	chan->unsentFragmentStart = 0; + +	// fragment large reliable messages +	if ( length >= FRAGMENT_SIZE ) { +		chan->unsentFragments = qtrue; +		chan->unsentLength = length; +		Com_Memcpy( chan->unsentBuffer, data, length ); + +		// only send the first fragment now +		Netchan_TransmitNextFragment( chan ); + +		return; +	} + +	// write the packet header +	MSG_InitOOB (&send, send_buf, sizeof(send_buf)); + +	MSG_WriteLong( &send, chan->outgoingSequence ); +	chan->outgoingSequence++; + +	// send the qport if we are a client +	if ( chan->sock == NS_CLIENT ) { +		MSG_WriteShort( &send, qport->integer ); +	} + +	MSG_WriteData( &send, data, length ); + +	// send the datagram +	NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress ); + +	if ( showpackets->integer ) { +		Com_Printf( "%s send %4i : s=%i ack=%i\n" +			, netsrcString[ chan->sock ] +			, send.cursize +			, chan->outgoingSequence - 1 +			, chan->incomingSequence ); +	} +} + +/* +================= +Netchan_Process + +Returns qfalse if the message should not be processed due to being +out of order or a fragment. + +Msg must be large enough to hold MAX_MSGLEN, because if this is the +final fragment of a multi-part message, the entire thing will be +copied out. +================= +*/ +qboolean Netchan_Process( netchan_t *chan, msg_t *msg ) { +	int			sequence; +	int			qport; +	int			fragmentStart, fragmentLength; +	qboolean	fragmented; + +	// XOR unscramble all data in the packet after the header +//	Netchan_UnScramblePacket( msg ); + +	// get sequence numbers		 +	MSG_BeginReadingOOB( msg ); +	sequence = MSG_ReadLong( msg ); + +	// check for fragment information +	if ( sequence & FRAGMENT_BIT ) { +		sequence &= ~FRAGMENT_BIT; +		fragmented = qtrue; +	} else { +		fragmented = qfalse; +	} + +	// read the qport if we are a server +	if ( chan->sock == NS_SERVER ) { +		qport = MSG_ReadShort( msg ); +	} + +	// read the fragment information +	if ( fragmented ) { +		fragmentStart = MSG_ReadShort( msg ); +		fragmentLength = MSG_ReadShort( msg ); +	} else { +		fragmentStart = 0;		// stop warning message +		fragmentLength = 0; +	} + +	if ( showpackets->integer ) { +		if ( fragmented ) { +			Com_Printf( "%s recv %4i : s=%i fragment=%i,%i\n" +				, netsrcString[ chan->sock ] +				, msg->cursize +				, sequence +				, fragmentStart, fragmentLength ); +		} else { +			Com_Printf( "%s recv %4i : s=%i\n" +				, netsrcString[ chan->sock ] +				, msg->cursize +				, sequence ); +		} +	} + +	// +	// discard out of order or duplicated packets +	// +	if ( sequence <= chan->incomingSequence ) { +		if ( showdrop->integer || showpackets->integer ) { +			Com_Printf( "%s:Out of order packet %i at %i\n" +				, NET_AdrToString( chan->remoteAddress ) +				,  sequence +				, chan->incomingSequence ); +		} +		return qfalse; +	} + +	// +	// dropped packets don't keep the message from being used +	// +	chan->dropped = sequence - (chan->incomingSequence+1); +	if ( chan->dropped > 0 ) { +		if ( showdrop->integer || showpackets->integer ) { +			Com_Printf( "%s:Dropped %i packets at %i\n" +			, NET_AdrToString( chan->remoteAddress ) +			, chan->dropped +			, sequence ); +		} +	} +	 + +	// +	// if this is the final framgent of a reliable message, +	// bump incoming_reliable_sequence  +	// +	if ( fragmented ) { +		// TTimo +		// make sure we add the fragments in correct order +		// either a packet was dropped, or we received this one too soon +		// we don't reconstruct the fragments. we will wait till this fragment gets to us again +		// (NOTE: we could probably try to rebuild by out of order chunks if needed) +		if ( sequence != chan->fragmentSequence ) { +			chan->fragmentSequence = sequence; +			chan->fragmentLength = 0; +		} + +		// if we missed a fragment, dump the message +		if ( fragmentStart != chan->fragmentLength ) { +			if ( showdrop->integer || showpackets->integer ) { +				Com_Printf( "%s:Dropped a message fragment\n" +				, NET_AdrToString( chan->remoteAddress )); +			} +			// we can still keep the part that we have so far, +			// so we don't need to clear chan->fragmentLength +			return qfalse; +		} + +		// copy the fragment to the fragment buffer +		if ( fragmentLength < 0 || msg->readcount + fragmentLength > msg->cursize || +			chan->fragmentLength + fragmentLength > sizeof( chan->fragmentBuffer ) ) { +			if ( showdrop->integer || showpackets->integer ) { +				Com_Printf ("%s:illegal fragment length\n" +				, NET_AdrToString (chan->remoteAddress ) ); +			} +			return qfalse; +		} + +		Com_Memcpy( chan->fragmentBuffer + chan->fragmentLength,  +			msg->data + msg->readcount, fragmentLength ); + +		chan->fragmentLength += fragmentLength; + +		// if this wasn't the last fragment, don't process anything +		if ( fragmentLength == FRAGMENT_SIZE ) { +			return qfalse; +		} + +		if ( chan->fragmentLength > msg->maxsize ) { +			Com_Printf( "%s:fragmentLength %i > msg->maxsize\n" +				, NET_AdrToString (chan->remoteAddress ), +				chan->fragmentLength ); +			return qfalse; +		} + +		// copy the full message over the partial fragment + +		// make sure the sequence number is still there +		*(int *)msg->data = LittleLong( sequence ); + +		Com_Memcpy( msg->data + 4, chan->fragmentBuffer, chan->fragmentLength ); +		msg->cursize = chan->fragmentLength + 4; +		chan->fragmentLength = 0; +		msg->readcount = 4;	// past the sequence number +		msg->bit = 32;	// past the sequence number + +		// TTimo +		// clients were not acking fragmented messages +		chan->incomingSequence = sequence; +		 +		return qtrue; +	} + +	// +	// the message can now be read from the current message pointer +	// +	chan->incomingSequence = sequence; + +	return qtrue; +} + + +//============================================================================== + +/* +=================== +NET_CompareBaseAdr + +Compares without the port +=================== +*/ +qboolean	NET_CompareBaseAdr (netadr_t a, netadr_t b) +{ +	if (a.type != b.type) +		return qfalse; + +	if (a.type == NA_LOOPBACK) +		return qtrue; + +	if (a.type == NA_IP) +	{ +		if (a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3]) +			return qtrue; +		return qfalse; +	} + +	if (a.type == NA_IPX) +	{ +		if ((memcmp(a.ipx, b.ipx, 10) == 0)) +			return qtrue; +		return qfalse; +	} + + +	Com_Printf ("NET_CompareBaseAdr: bad address type\n"); +	return qfalse; +} + +const char	*NET_AdrToString (netadr_t a) +{ +	static	char	s[64]; + +	if (a.type == NA_LOOPBACK) { +		Com_sprintf (s, sizeof(s), "loopback"); +	} else if (a.type == NA_IP) { +		Com_sprintf (s, sizeof(s), "%i.%i.%i.%i:%hu", +			a.ip[0], a.ip[1], a.ip[2], a.ip[3], BigShort(a.port)); +	} else { +		Com_sprintf (s, sizeof(s), "%02x%02x%02x%02x.%02x%02x%02x%02x%02x%02x:%hu", +		a.ipx[0], a.ipx[1], a.ipx[2], a.ipx[3], a.ipx[4], a.ipx[5], a.ipx[6], a.ipx[7], a.ipx[8], a.ipx[9],  +		BigShort(a.port)); +	} + +	return s; +} + + +qboolean	NET_CompareAdr (netadr_t a, netadr_t b) +{ +	if (a.type != b.type) +		return qfalse; + +	if (a.type == NA_LOOPBACK) +		return qtrue; + +	if (a.type == NA_IP) +	{ +		if (a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3] && a.port == b.port) +			return qtrue; +		return qfalse; +	} + +	if (a.type == NA_IPX) +	{ +		if ((memcmp(a.ipx, b.ipx, 10) == 0) && a.port == b.port) +			return qtrue; +		return qfalse; +	} + +	Com_Printf ("NET_CompareAdr: bad address type\n"); +	return qfalse; +} + + +qboolean	NET_IsLocalAddress( netadr_t adr ) { +	return adr.type == NA_LOOPBACK; +} + + + +/* +============================================================================= + +LOOPBACK BUFFERS FOR LOCAL PLAYER + +============================================================================= +*/ + +// there needs to be enough loopback messages to hold a complete +// gamestate of maximum size +#define	MAX_LOOPBACK	16 + +typedef struct { +	byte	data[MAX_PACKETLEN]; +	int		datalen; +} loopmsg_t; + +typedef struct { +	loopmsg_t	msgs[MAX_LOOPBACK]; +	int			get, send; +} loopback_t; + +loopback_t	loopbacks[2]; + + +qboolean	NET_GetLoopPacket (netsrc_t sock, netadr_t *net_from, msg_t *net_message) +{ +	int		i; +	loopback_t	*loop; + +	loop = &loopbacks[sock]; + +	if (loop->send - loop->get > MAX_LOOPBACK) +		loop->get = loop->send - MAX_LOOPBACK; + +	if (loop->get >= loop->send) +		return qfalse; + +	i = loop->get & (MAX_LOOPBACK-1); +	loop->get++; + +	Com_Memcpy (net_message->data, loop->msgs[i].data, loop->msgs[i].datalen); +	net_message->cursize = loop->msgs[i].datalen; +	Com_Memset (net_from, 0, sizeof(*net_from)); +	net_from->type = NA_LOOPBACK; +	return qtrue; + +} + + +void NET_SendLoopPacket (netsrc_t sock, int length, const void *data, netadr_t to) +{ +	int		i; +	loopback_t	*loop; + +	loop = &loopbacks[sock^1]; + +	i = loop->send & (MAX_LOOPBACK-1); +	loop->send++; + +	Com_Memcpy (loop->msgs[i].data, data, length); +	loop->msgs[i].datalen = length; +} + +//============================================================================= + +typedef struct packetQueue_s { +        struct packetQueue_s *next; +        int length; +        byte *data; +        netadr_t to; +        int release; +} packetQueue_t; + +packetQueue_t *packetQueue = NULL; + +static void NET_QueuePacket( int length, const void *data, netadr_t to, +	int offset ) +{ +	packetQueue_t *new, *next = packetQueue; + +	if(offset > 999) +		offset = 999; + +	new = S_Malloc(sizeof(packetQueue_t)); +	new->data = S_Malloc(length); +	Com_Memcpy(new->data, data, length); +	new->length = length; +	new->to = to; +	new->release = Sys_Milliseconds() + offset;	 +	new->next = NULL; + +	if(!packetQueue) { +		packetQueue = new; +		return; +	} +	while(next) { +		if(!next->next) { +			next->next = new; +			return; +		} +		next = next->next; +	} +} + +void NET_FlushPacketQueue(void) +{ +	packetQueue_t *last; +	int now; + +	while(packetQueue) { +		now = Sys_Milliseconds(); +		if(packetQueue->release >= now) +			break; +		Sys_SendPacket(packetQueue->length, packetQueue->data, +			packetQueue->to); +		last = packetQueue; +		packetQueue = packetQueue->next; +		Z_Free(last->data); +		Z_Free(last); +	} +} + +void NET_SendPacket( netsrc_t sock, int length, const void *data, netadr_t to ) { + +	// sequenced packets are shown in netchan, so just show oob +	if ( showpackets->integer && *(int *)data == -1 )	{ +		Com_Printf ("send packet %4i\n", length); +	} + +	if ( to.type == NA_LOOPBACK ) { +		NET_SendLoopPacket (sock, length, data, to); +		return; +	} +	if ( to.type == NA_BAD ) { +		return; +	} + +	if ( sock == NS_CLIENT && cl_packetdelay->integer > 0 ) { +		NET_QueuePacket( length, data, to, cl_packetdelay->integer ); +	} +	else if ( sock == NS_SERVER && sv_packetdelay->integer > 0 ) { +		NET_QueuePacket( length, data, to, sv_packetdelay->integer ); +	} +	else { +		Sys_SendPacket( length, data, to ); +	} +} + +/* +=============== +NET_OutOfBandPrint + +Sends a text message in an out-of-band datagram +================ +*/ +void QDECL NET_OutOfBandPrint( netsrc_t sock, netadr_t adr, const char *format, ... ) { +	va_list		argptr; +	char		string[MAX_MSGLEN]; + + +	// set the header +	string[0] = -1; +	string[1] = -1; +	string[2] = -1; +	string[3] = -1; + +	va_start( argptr, format ); +	Q_vsnprintf( string+4, sizeof(string)-4, format, argptr ); +	va_end( argptr ); + +	// send the datagram +	NET_SendPacket( sock, strlen( string ), string, adr ); +} + +/* +=============== +NET_OutOfBandPrint + +Sends a data message in an out-of-band datagram (only used for "connect") +================ +*/ +void QDECL NET_OutOfBandData( netsrc_t sock, netadr_t adr, byte *format, int len ) { +	byte		string[MAX_MSGLEN*2]; +	int			i; +	msg_t		mbuf; + +	// set the header +	string[0] = 0xff; +	string[1] = 0xff; +	string[2] = 0xff; +	string[3] = 0xff; + +	for(i=0;i<len;i++) { +		string[i+4] = format[i]; +	} + +	mbuf.data = string; +	mbuf.cursize = len+4; +	Huff_Compress( &mbuf, 12); +	// send the datagram +	NET_SendPacket( sock, mbuf.cursize, mbuf.data, adr ); +} + +/* +============= +NET_StringToAdr + +Traps "localhost" for loopback, passes everything else to system +============= +*/ +qboolean	NET_StringToAdr( const char *s, netadr_t *a ) { +	qboolean	r; +	char	base[MAX_STRING_CHARS]; +	char	*port; + +	if (!strcmp (s, "localhost")) { +		Com_Memset (a, 0, sizeof(*a)); +		a->type = NA_LOOPBACK; +		return qtrue; +	} + +	// look for a port number +	Q_strncpyz( base, s, sizeof( base ) ); +	port = strstr( base, ":" ); +	if ( port ) { +		*port = 0; +		port++; +	} + +	r = Sys_StringToAdr( base, a ); + +	if ( !r ) { +		a->type = NA_BAD; +		return qfalse; +	} + +	// inet_addr returns this if out of range +	if ( a->ip[0] == 255 && a->ip[1] == 255 && a->ip[2] == 255 && a->ip[3] == 255 ) { +		a->type = NA_BAD; +		return qfalse; +	} + +	if ( port ) { +		a->port = BigShort( (short)atoi( port ) ); +	} else { +		a->port = BigShort( PORT_SERVER ); +	} + +	return qtrue; +} + diff --git a/src/qcommon/parse.c b/src/qcommon/parse.c new file mode 100644 index 0000000..136ba0b --- /dev/null +++ b/src/qcommon/parse.c @@ -0,0 +1,3585 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ + +#include "q_shared.h" +#include "qcommon.h" + +//script flags +#define SCFL_NOERRORS             0x0001 +#define SCFL_NOWARNINGS           0x0002 +#define SCFL_NOSTRINGWHITESPACES  0x0004 +#define SCFL_NOSTRINGESCAPECHARS  0x0008 +#define SCFL_PRIMITIVE            0x0010 +#define SCFL_NOBINARYNUMBERS      0x0020 +#define SCFL_NONUMBERVALUES       0x0040 + +//token types +#define TT_STRING           1     // string +#define TT_LITERAL          2     // literal +#define TT_NUMBER           3     // number +#define TT_NAME             4     // name +#define TT_PUNCTUATION      5     // punctuation + +//string sub type +//--------------- +//    the length of the string +//literal sub type +//---------------- +//    the ASCII code of the literal +//number sub type +//--------------- +#define TT_DECIMAL          0x0008  // decimal number +#define TT_HEX              0x0100  // hexadecimal number +#define TT_OCTAL            0x0200  // octal number +#define TT_BINARY           0x0400  // binary number +#define TT_FLOAT            0x0800  // floating point number +#define TT_INTEGER          0x1000  // integer number +#define TT_LONG             0x2000  // long number +#define TT_UNSIGNED         0x4000  // unsigned number +//punctuation sub type +//-------------------- +#define P_RSHIFT_ASSIGN       1 +#define P_LSHIFT_ASSIGN       2 +#define P_PARMS               3 +#define P_PRECOMPMERGE        4 + +#define P_LOGIC_AND           5 +#define P_LOGIC_OR            6 +#define P_LOGIC_GEQ           7 +#define P_LOGIC_LEQ           8 +#define P_LOGIC_EQ            9 +#define P_LOGIC_UNEQ          10 + +#define P_MUL_ASSIGN          11 +#define P_DIV_ASSIGN          12 +#define P_MOD_ASSIGN          13 +#define P_ADD_ASSIGN          14 +#define P_SUB_ASSIGN          15 +#define P_INC                 16 +#define P_DEC                 17 + +#define P_BIN_AND_ASSIGN      18 +#define P_BIN_OR_ASSIGN       19 +#define P_BIN_XOR_ASSIGN      20 +#define P_RSHIFT              21 +#define P_LSHIFT              22 + +#define P_POINTERREF          23 +#define P_CPP1                24 +#define P_CPP2                25 +#define P_MUL                 26 +#define P_DIV                 27 +#define P_MOD                 28 +#define P_ADD                 29 +#define P_SUB                 30 +#define P_ASSIGN              31 + +#define P_BIN_AND             32 +#define P_BIN_OR              33 +#define P_BIN_XOR             34 +#define P_BIN_NOT             35 + +#define P_LOGIC_NOT           36 +#define P_LOGIC_GREATER       37 +#define P_LOGIC_LESS          38 + +#define P_REF                 39 +#define P_COMMA               40 +#define P_SEMICOLON           41 +#define P_COLON               42 +#define P_QUESTIONMARK        43 + +#define P_PARENTHESESOPEN     44 +#define P_PARENTHESESCLOSE    45 +#define P_BRACEOPEN           46 +#define P_BRACECLOSE          47 +#define P_SQBRACKETOPEN       48 +#define P_SQBRACKETCLOSE      49 +#define P_BACKSLASH           50 + +#define P_PRECOMP             51 +#define P_DOLLAR              52 + +//name sub type +//------------- +//    the length of the name + +//punctuation +typedef struct punctuation_s +{ +  char *p;                    //punctuation character(s) +  int n;                      //punctuation indication +  struct punctuation_s *next; //next punctuation +} punctuation_t; + +//token +typedef struct token_s +{ +  char string[MAX_TOKEN_CHARS]; //available token +  int type;                     //last read token type +  int subtype;                  //last read token sub type +  unsigned long int intvalue;   //integer value +  double floatvalue;            //floating point value +  char *whitespace_p;           //start of white space before token +  char *endwhitespace_p;        //start of white space before token +  int line;                     //line the token was on +  int linescrossed;             //lines crossed in white space +  struct token_s *next;         //next token in chain +} token_t; + +//script file +typedef struct script_s +{ +  char filename[1024];            //file name of the script +  char *buffer;                   //buffer containing the script +  char *script_p;                 //current pointer in the script +  char *end_p;                    //pointer to the end of the script +  char *lastscript_p;             //script pointer before reading token +  char *whitespace_p;             //begin of the white space +  char *endwhitespace_p;          +  int length;                     //length of the script in bytes +  int line;                       //current line in script +  int lastline;                   //line before reading token +  int tokenavailable;             //set by UnreadLastToken +  int flags;                      //several script flags +  punctuation_t *punctuations;    //the punctuations used in the script +  punctuation_t **punctuationtable; +  token_t token;                  //available token +  struct script_s *next;          //next script in a chain +} script_t; + + +#define DEFINE_FIXED      0x0001 + +#define BUILTIN_LINE      1 +#define BUILTIN_FILE      2 +#define BUILTIN_DATE      3 +#define BUILTIN_TIME      4 +#define BUILTIN_STDC      5 + +#define INDENT_IF         0x0001 +#define INDENT_ELSE       0x0002 +#define INDENT_ELIF       0x0004 +#define INDENT_IFDEF      0x0008 +#define INDENT_IFNDEF     0x0010 + +//macro definitions +typedef struct define_s +{ +  char *name;                 //define name +  int flags;                  //define flags +  int builtin;                // > 0 if builtin define +  int numparms;               //number of define parameters +  token_t *parms;             //define parameters +  token_t *tokens;            //macro tokens (possibly containing parm tokens) +  struct define_s *next;      //next defined macro in a list +  struct define_s *hashnext;  //next define in the hash chain +} define_t; + +//indents +//used for conditional compilation directives: +//#if, #else, #elif, #ifdef, #ifndef +typedef struct indent_s +{ +  int type;               //indent type +  int skip;               //true if skipping current indent +  script_t *script;       //script the indent was in +  struct indent_s *next;  //next indent on the indent stack +} indent_t; + +//source file +typedef struct source_s +{ +  char filename[MAX_QPATH];     //file name of the script +  char includepath[MAX_QPATH];  //path to include files +  punctuation_t *punctuations;  //punctuations to use +  script_t *scriptstack;        //stack with scripts of the source +  token_t *tokens;              //tokens to read first +  define_t *defines;            //list with macro definitions +  define_t **definehash;        //hash chain with defines +  indent_t *indentstack;        //stack with indents +  int skip;                     // > 0 if skipping conditional code +  token_t token;                //last read token +} source_t; + +#define MAX_DEFINEPARMS     128 + +//directive name with parse function +typedef struct directive_s +{ +  char *name; +  int (*func)(source_t *source); +} directive_t; + +#define DEFINEHASHSIZE    1024 + +static int Parse_ReadToken(source_t *source, token_t *token); + +int numtokens; + +//list with global defines added to every source loaded +define_t *globaldefines; + +//longer punctuations first +punctuation_t default_punctuations[] = +{ +  //binary operators +  {">>=",P_RSHIFT_ASSIGN, NULL}, +  {"<<=",P_LSHIFT_ASSIGN, NULL}, +  // +  {"...",P_PARMS, NULL}, +  //define merge operator +  {"##",P_PRECOMPMERGE, NULL}, +  //logic operators +  {"&&",P_LOGIC_AND, NULL}, +  {"||",P_LOGIC_OR, NULL}, +  {">=",P_LOGIC_GEQ, NULL}, +  {"<=",P_LOGIC_LEQ, NULL}, +  {"==",P_LOGIC_EQ, NULL}, +  {"!=",P_LOGIC_UNEQ, NULL}, +  //arithmatic operators +  {"*=",P_MUL_ASSIGN, NULL}, +  {"/=",P_DIV_ASSIGN, NULL}, +  {"%=",P_MOD_ASSIGN, NULL}, +  {"+=",P_ADD_ASSIGN, NULL}, +  {"-=",P_SUB_ASSIGN, NULL}, +  {"++",P_INC, NULL}, +  {"--",P_DEC, NULL}, +  //binary operators +  {"&=",P_BIN_AND_ASSIGN, NULL}, +  {"|=",P_BIN_OR_ASSIGN, NULL}, +  {"^=",P_BIN_XOR_ASSIGN, NULL}, +  {">>",P_RSHIFT, NULL}, +  {"<<",P_LSHIFT, NULL}, +  //reference operators +  {"->",P_POINTERREF, NULL}, +  //C++ +  {"::",P_CPP1, NULL}, +  {".*",P_CPP2, NULL}, +  //arithmatic operators +  {"*",P_MUL, NULL}, +  {"/",P_DIV, NULL}, +  {"%",P_MOD, NULL}, +  {"+",P_ADD, NULL}, +  {"-",P_SUB, NULL}, +  {"=",P_ASSIGN, NULL}, +  //binary operators +  {"&",P_BIN_AND, NULL}, +  {"|",P_BIN_OR, NULL}, +  {"^",P_BIN_XOR, NULL}, +  {"~",P_BIN_NOT, NULL}, +  //logic operators +  {"!",P_LOGIC_NOT, NULL}, +  {">",P_LOGIC_GREATER, NULL}, +  {"<",P_LOGIC_LESS, NULL}, +  //reference operator +  {".",P_REF, NULL}, +  //seperators +  {",",P_COMMA, NULL}, +  {";",P_SEMICOLON, NULL}, +  //label indication +  {":",P_COLON, NULL}, +  //if statement +  {"?",P_QUESTIONMARK, NULL}, +  //embracements +  {"(",P_PARENTHESESOPEN, NULL}, +  {")",P_PARENTHESESCLOSE, NULL}, +  {"{",P_BRACEOPEN, NULL}, +  {"}",P_BRACECLOSE, NULL}, +  {"[",P_SQBRACKETOPEN, NULL}, +  {"]",P_SQBRACKETCLOSE, NULL}, +  // +  {"\\",P_BACKSLASH, NULL}, +  //precompiler operator +  {"#",P_PRECOMP, NULL}, +  {"$",P_DOLLAR, NULL}, +  {NULL, 0} +}; + +char basefolder[MAX_QPATH]; + +/* +=============== +Parse_CreatePunctuationTable +=============== +*/ +static void Parse_CreatePunctuationTable(script_t *script, punctuation_t *punctuations) +{ +  int i; +  punctuation_t *p, *lastp, *newp; + +  //get memory for the table +  if (!script->punctuationtable) script->punctuationtable = (punctuation_t **) +                        Z_Malloc(256 * sizeof(punctuation_t *)); +  Com_Memset(script->punctuationtable, 0, 256 * sizeof(punctuation_t *)); +  //add the punctuations in the list to the punctuation table +  for (i = 0; punctuations[i].p; i++) +  { +    newp = &punctuations[i]; +    lastp = NULL; +    //sort the punctuations in this table entry on length (longer punctuations first) +    for (p = script->punctuationtable[(unsigned int) newp->p[0]]; p; p = p->next) +    { +      if (strlen(p->p) < strlen(newp->p)) +      { +        newp->next = p; +        if (lastp) lastp->next = newp; +        else script->punctuationtable[(unsigned int) newp->p[0]] = newp; +        break; +      } +      lastp = p; +    } +    if (!p) +    { +      newp->next = NULL; +      if (lastp) lastp->next = newp; +      else script->punctuationtable[(unsigned int) newp->p[0]] = newp; +    } +  } +} + +/* +=============== +Parse_ScriptError +=============== +*/ +static void QDECL Parse_ScriptError(script_t *script, char *str, ...) +{ +  char text[1024]; +  va_list ap; + +  if (script->flags & SCFL_NOERRORS) return; + +  va_start(ap, str); +  vsprintf(text, str, ap); +  va_end(ap); +  Com_Printf( "file %s, line %d: %s\n", script->filename, script->line, text); +} + +/* +=============== +Parse_ScriptWarning +=============== +*/ +static void QDECL Parse_ScriptWarning(script_t *script, char *str, ...) +{ +  char text[1024]; +  va_list ap; + +  if (script->flags & SCFL_NOWARNINGS) return; + +  va_start(ap, str); +  vsprintf(text, str, ap); +  va_end(ap); +  Com_Printf( "file %s, line %d: %s\n", script->filename, script->line, text); +} + +/* +=============== +Parse_SetScriptPunctuations +=============== +*/ +static void Parse_SetScriptPunctuations(script_t *script, punctuation_t *p) +{ +  if (p) Parse_CreatePunctuationTable(script, p); +  else  Parse_CreatePunctuationTable(script, default_punctuations); +  if (p) script->punctuations = p; +  else script->punctuations = default_punctuations; +} + +/* +=============== +Parse_ReadWhiteSpace +=============== +*/ +static int Parse_ReadWhiteSpace(script_t *script) +{ +  while(1) +  { +    //skip white space +    while(*script->script_p <= ' ') +    { +      if (!*script->script_p) return 0; +      if (*script->script_p == '\n') script->line++; +      script->script_p++; +    } +    //skip comments +    if (*script->script_p == '/') +    { +      //comments // +      if (*(script->script_p+1) == '/') +      { +        script->script_p++; +        do +        { +          script->script_p++; +          if (!*script->script_p) return 0; +        } +        while(*script->script_p != '\n'); +        script->line++; +        script->script_p++; +        if (!*script->script_p) return 0; +        continue; +      } +      //comments /* */ +      else if (*(script->script_p+1) == '*') +      { +        script->script_p++; +        do +        { +          script->script_p++; +          if (!*script->script_p) return 0; +          if (*script->script_p == '\n') script->line++; +        } +        while(!(*script->script_p == '*' && *(script->script_p+1) == '/')); +        script->script_p++; +        if (!*script->script_p) return 0; +        script->script_p++; +        if (!*script->script_p) return 0; +        continue; +      } +    } +    break; +  } +  return 1; +} + +/* +=============== +Parse_ReadEscapeCharacter +=============== +*/ +static int Parse_ReadEscapeCharacter(script_t *script, char *ch) +{ +  int c, val, i; + +  //step over the leading '\\' +  script->script_p++; +  //determine the escape character +  switch(*script->script_p) +  { +    case '\\': c = '\\'; break; +    case 'n': c = '\n'; break; +    case 'r': c = '\r'; break; +    case 't': c = '\t'; break; +    case 'v': c = '\v'; break; +    case 'b': c = '\b'; break; +    case 'f': c = '\f'; break; +    case 'a': c = '\a'; break; +    case '\'': c = '\''; break; +    case '\"': c = '\"'; break; +    case '\?': c = '\?'; break; +    case 'x': +    { +      script->script_p++; +      for (i = 0, val = 0; ; i++, script->script_p++) +      { +        c = *script->script_p; +        if (c >= '0' && c <= '9') c = c - '0'; +        else if (c >= 'A' && c <= 'Z') c = c - 'A' + 10; +        else if (c >= 'a' && c <= 'z') c = c - 'a' + 10; +        else break; +        val = (val << 4) + c; +      } +      script->script_p--; +      if (val > 0xFF) +      { +        Parse_ScriptWarning(script, "too large value in escape character"); +        val = 0xFF; +      } +      c = val; +      break; +    } +    default: //NOTE: decimal ASCII code, NOT octal +    { +      if (*script->script_p < '0' || *script->script_p > '9') Parse_ScriptError(script, "unknown escape char"); +      for (i = 0, val = 0; ; i++, script->script_p++) +      { +        c = *script->script_p; +        if (c >= '0' && c <= '9') c = c - '0'; +        else break; +        val = val * 10 + c; +      } +      script->script_p--; +      if (val > 0xFF) +      { +        Parse_ScriptWarning(script, "too large value in escape character"); +        val = 0xFF; +      } +      c = val; +      break; +    } +  } +  //step over the escape character or the last digit of the number +  script->script_p++; +  //store the escape character +  *ch = c; +  //succesfully read escape character +  return 1; +} + +/* +=============== +Parse_ReadString + +Reads C-like string. Escape characters are interpretted. +Quotes are included with the string. +Reads two strings with a white space between them as one string. +=============== +*/ +static int Parse_ReadString(script_t *script, token_t *token, int quote) +{ +  int len, tmpline; +  char *tmpscript_p; + +  if (quote == '\"') token->type = TT_STRING; +  else token->type = TT_LITERAL; + +  len = 0; +  //leading quote +  token->string[len++] = *script->script_p++; +  // +  while(1) +  { +    //minus 2 because trailing double quote and zero have to be appended +    if (len >= MAX_TOKEN_CHARS - 2) +    { +      Parse_ScriptError(script, "string longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS); +      return 0; +    } +    //if there is an escape character and +    //if escape characters inside a string are allowed +    if (*script->script_p == '\\' && !(script->flags & SCFL_NOSTRINGESCAPECHARS)) +    { +      if (!Parse_ReadEscapeCharacter(script, &token->string[len])) +      { +        token->string[len] = 0; +        return 0; +      } +      len++; +    } +    //if a trailing quote +    else if (*script->script_p == quote) +    { +      //step over the double quote +      script->script_p++; +      //if white spaces in a string are not allowed +      if (script->flags & SCFL_NOSTRINGWHITESPACES) break; +      // +      tmpscript_p = script->script_p; +      tmpline = script->line; +      //read unusefull stuff between possible two following strings +      if (!Parse_ReadWhiteSpace(script)) +      { +        script->script_p = tmpscript_p; +        script->line = tmpline; +        break; +      } +      //if there's no leading double qoute +      if (*script->script_p != quote) +      { +        script->script_p = tmpscript_p; +        script->line = tmpline; +        break; +      } +      //step over the new leading double quote +      script->script_p++; +    } +    else +    { +      if (*script->script_p == '\0') +      { +        token->string[len] = 0; +        Parse_ScriptError(script, "missing trailing quote"); +        return 0; +      } +        if (*script->script_p == '\n') +      { +        token->string[len] = 0; +        Parse_ScriptError(script, "newline inside string %s", token->string); +        return 0; +      } +      token->string[len++] = *script->script_p++; +    } +  } +  //trailing quote +  token->string[len++] = quote; +  //end string with a zero +  token->string[len] = '\0'; +  //the sub type is the length of the string +  token->subtype = len; +  return 1; +} + +/* +=============== +Parse_ReadName +=============== +*/ +static int Parse_ReadName(script_t *script, token_t *token) +{ +  int len = 0; +  char c; + +  token->type = TT_NAME; +  do +  { +    token->string[len++] = *script->script_p++; +    if (len >= MAX_TOKEN_CHARS) +    { +      Parse_ScriptError(script, "name longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS); +      return 0; +    } +    c = *script->script_p; +   } while ((c >= 'a' && c <= 'z') || +        (c >= 'A' && c <= 'Z') || +        (c >= '0' && c <= '9') || +        c == '_'); +  token->string[len] = '\0'; +  //the sub type is the length of the name +  token->subtype = len; +  return 1; +} + +/* +=============== +Parse_NumberValue +=============== +*/ +static void Parse_NumberValue(char *string, int subtype, unsigned long int *intvalue, +                              double *floatvalue) +{ +  unsigned long int dotfound = 0; + +  *intvalue = 0; +  *floatvalue = 0; +  //floating point number +  if (subtype & TT_FLOAT) +  { +    while(*string) +    { +      if (*string == '.') +      { +        if (dotfound) return; +        dotfound = 10; +        string++; +      } +      if (dotfound) +      { +        *floatvalue = *floatvalue + (double) (*string - '0') / +                                  (double) dotfound; +        dotfound *= 10; +      } +      else +      { +        *floatvalue = *floatvalue * 10.0 + (double) (*string - '0'); +      } +      string++; +    } +    *intvalue = (unsigned long) *floatvalue; +  } +  else if (subtype & TT_DECIMAL) +  { +    while(*string) *intvalue = *intvalue * 10 + (*string++ - '0'); +    *floatvalue = *intvalue; +  } +  else if (subtype & TT_HEX) +  { +    //step over the leading 0x or 0X +    string += 2; +    while(*string) +    { +      *intvalue <<= 4; +      if (*string >= 'a' && *string <= 'f') *intvalue += *string - 'a' + 10; +      else if (*string >= 'A' && *string <= 'F') *intvalue += *string - 'A' + 10; +      else *intvalue += *string - '0'; +      string++; +    } +    *floatvalue = *intvalue; +  } +  else if (subtype & TT_OCTAL) +  { +    //step over the first zero +    string += 1; +    while(*string) *intvalue = (*intvalue << 3) + (*string++ - '0'); +    *floatvalue = *intvalue; +  } +  else if (subtype & TT_BINARY) +  { +    //step over the leading 0b or 0B +    string += 2; +    while(*string) *intvalue = (*intvalue << 1) + (*string++ - '0'); +    *floatvalue = *intvalue; +  } +} + +/* +=============== +Parse_ReadNumber +=============== +*/ +static int Parse_ReadNumber(script_t *script, token_t *token) +{ +  int len = 0, i; +  int octal, dot; +  char c; +//  unsigned long int intvalue = 0; +//  double floatvalue = 0; + +  token->type = TT_NUMBER; +  //check for a hexadecimal number +  if (*script->script_p == '0' && +    (*(script->script_p + 1) == 'x' || +    *(script->script_p + 1) == 'X')) +  { +    token->string[len++] = *script->script_p++; +    token->string[len++] = *script->script_p++; +    c = *script->script_p; +    //hexadecimal +    while((c >= '0' && c <= '9') || +          (c >= 'a' && c <= 'f') || +          (c >= 'A' && c <= 'A')) +    { +      token->string[len++] = *script->script_p++; +      if (len >= MAX_TOKEN_CHARS) +      { +        Parse_ScriptError(script, "hexadecimal number longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS); +        return 0; +      } +      c = *script->script_p; +    } +    token->subtype |= TT_HEX; +  } +#ifdef BINARYNUMBERS +  //check for a binary number +  else if (*script->script_p == '0' && +    (*(script->script_p + 1) == 'b' || +    *(script->script_p + 1) == 'B')) +  { +    token->string[len++] = *script->script_p++; +    token->string[len++] = *script->script_p++; +    c = *script->script_p; +    //binary +    while(c == '0' || c == '1') +    { +      token->string[len++] = *script->script_p++; +      if (len >= MAX_TOKEN_CHARS) +      { +        Parse_ScriptError(script, "binary number longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS); +        return 0; +      } +      c = *script->script_p; +    } +    token->subtype |= TT_BINARY; +  } +#endif //BINARYNUMBERS +  else //decimal or octal integer or floating point number +  { +    octal = qfalse; +    dot = qfalse; +    if (*script->script_p == '0') octal = qtrue; +    while(1) +    { +      c = *script->script_p; +      if (c == '.') dot = qtrue; +      else if (c == '8' || c == '9') octal = qfalse; +      else if (c < '0' || c > '9') break; +      token->string[len++] = *script->script_p++; +      if (len >= MAX_TOKEN_CHARS - 1) +      { +        Parse_ScriptError(script, "number longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS); +        return 0; +      } +    } +    if (octal) token->subtype |= TT_OCTAL; +    else token->subtype |= TT_DECIMAL; +    if (dot) token->subtype |= TT_FLOAT; +  } +  for (i = 0; i < 2; i++) +  { +    c = *script->script_p; +    //check for a LONG number +    if ( (c == 'l' || c == 'L') // bk001204 - brackets +         && !(token->subtype & TT_LONG)) +    { +      script->script_p++; +      token->subtype |= TT_LONG; +    } +    //check for an UNSIGNED number +    else if ( (c == 'u' || c == 'U') // bk001204 - brackets +        && !(token->subtype & (TT_UNSIGNED | TT_FLOAT))) +    { +      script->script_p++; +      token->subtype |= TT_UNSIGNED; +    } +  } +  token->string[len] = '\0'; +  Parse_NumberValue(token->string, token->subtype, &token->intvalue, &token->floatvalue); +  if (!(token->subtype & TT_FLOAT)) token->subtype |= TT_INTEGER; +  return 1; +} + +/* +=============== +Parse_ReadPunctuation +=============== +*/ +static int Parse_ReadPunctuation(script_t *script, token_t *token) +{ +  int len; +  char *p; +  punctuation_t *punc; + +  for (punc = script->punctuationtable[(unsigned int)*script->script_p]; punc; punc = punc->next) +  { +    p = punc->p; +    len = strlen(p); +    //if the script contains at least as much characters as the punctuation +    if (script->script_p + len <= script->end_p) +    { +      //if the script contains the punctuation +      if (!strncmp(script->script_p, p, len)) +      { +        strncpy(token->string, p, MAX_TOKEN_CHARS); +        script->script_p += len; +        token->type = TT_PUNCTUATION; +        //sub type is the number of the punctuation +        token->subtype = punc->n; +        return 1; +      } +    } +  } +  return 0; +} + +/* +=============== +Parse_ReadPrimitive +=============== +*/ +static int Parse_ReadPrimitive(script_t *script, token_t *token) +{ +  int len; + +  len = 0; +  while(*script->script_p > ' ' && *script->script_p != ';') +  { +    if (len >= MAX_TOKEN_CHARS) +    { +      Parse_ScriptError(script, "primitive token longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS); +      return 0; +    } +    token->string[len++] = *script->script_p++; +  } +  token->string[len] = 0; +  //copy the token into the script structure +  Com_Memcpy(&script->token, token, sizeof(token_t)); +  //primitive reading successfull +  return 1; +} + +/* +=============== +Parse_ReadSciptToken +=============== +*/ +static int Parse_ReadSciptToken(script_t *script, token_t *token) +{ +  //if there is a token available (from UnreadToken) +  if (script->tokenavailable) +  { +    script->tokenavailable = 0; +    Com_Memcpy(token, &script->token, sizeof(token_t)); +    return 1; +  } +  //save script pointer +  script->lastscript_p = script->script_p; +  //save line counter +  script->lastline = script->line; +  //clear the token stuff +  Com_Memset(token, 0, sizeof(token_t)); +  //start of the white space +  script->whitespace_p = script->script_p; +  token->whitespace_p = script->script_p; +  //read unusefull stuff +  if (!Parse_ReadWhiteSpace(script)) return 0; + +  script->endwhitespace_p = script->script_p; +  token->endwhitespace_p = script->script_p; +  //line the token is on +  token->line = script->line; +  //number of lines crossed before token +  token->linescrossed = script->line - script->lastline; +  //if there is a leading double quote +  if (*script->script_p == '\"') +  { +    if (!Parse_ReadString(script, token, '\"')) return 0; +  } +  //if an literal +  else if (*script->script_p == '\'') +  { +    //if (!Parse_ReadLiteral(script, token)) return 0; +    if (!Parse_ReadString(script, token, '\'')) return 0; +  } +  //if there is a number +  else if ((*script->script_p >= '0' && *script->script_p <= '9') || +        (*script->script_p == '.' && +        (*(script->script_p + 1) >= '0' && *(script->script_p + 1) <= '9'))) +  { +    if (!Parse_ReadNumber(script, token)) return 0; +  } +  //if this is a primitive script +  else if (script->flags & SCFL_PRIMITIVE) +  { +    return Parse_ReadPrimitive(script, token); +  } +  //if there is a name +  else if ((*script->script_p >= 'a' && *script->script_p <= 'z') || +    (*script->script_p >= 'A' && *script->script_p <= 'Z') || +    *script->script_p == '_') +  { +    if (!Parse_ReadName(script, token)) return 0; +  } +  //check for punctuations +  else if (!Parse_ReadPunctuation(script, token)) +  { +    Parse_ScriptError(script, "can't read token"); +    return 0; +  } +  //copy the token into the script structure +  Com_Memcpy(&script->token, token, sizeof(token_t)); +  //succesfully read a token +  return 1; +} + +/* +=============== +Parse_StripDoubleQuotes +=============== +*/ +static void Parse_StripDoubleQuotes(char *string) +{ +  if (*string == '\"') +  { +    strcpy(string, string+1); +  } +  if (string[strlen(string)-1] == '\"') +  { +    string[strlen(string)-1] = '\0'; +  } +} + +/* +=============== +Parse_EndOfScript +=============== +*/ +static int Parse_EndOfScript(script_t *script) +{ +  return script->script_p >= script->end_p; +} + +/* +=============== +Parse_LoadScriptFile +=============== +*/ +static script_t *Parse_LoadScriptFile(const char *filename) +{ +  fileHandle_t fp; +  char pathname[MAX_QPATH]; +  int length; +  void *buffer; +  script_t *script; + +  if (strlen(basefolder)) +    Com_sprintf(pathname, sizeof(pathname), "%s/%s", basefolder, filename); +  else +    Com_sprintf(pathname, sizeof(pathname), "%s", filename); +  length = FS_FOpenFileRead( pathname, &fp, qfalse ); +  if (!fp) return NULL; + +  buffer = Z_Malloc(sizeof(script_t) + length + 1); +  Com_Memset( buffer, 0, sizeof(script_t) + length + 1 ); + +  script = (script_t *) buffer; +  Com_Memset(script, 0, sizeof(script_t)); +  strcpy(script->filename, filename); +  script->buffer = (char *) buffer + sizeof(script_t); +  script->buffer[length] = 0; +  script->length = length; +  //pointer in script buffer +  script->script_p = script->buffer; +  //pointer in script buffer before reading token +  script->lastscript_p = script->buffer; +  //pointer to end of script buffer +  script->end_p = &script->buffer[length]; +  //set if there's a token available in script->token +  script->tokenavailable = 0; +  // +  script->line = 1; +  script->lastline = 1; +  // +  Parse_SetScriptPunctuations(script, NULL); +  // +  FS_Read(script->buffer, length, fp); +  FS_FCloseFile(fp); +  // +  script->length = COM_Compress(script->buffer); + +  return script; +} + +/* +=============== +Parse_LoadScriptMemory +=============== +*/ +static script_t *Parse_LoadScriptMemory(char *ptr, int length, char *name) +{ +  void *buffer; +  script_t *script; + +  buffer = Z_Malloc(sizeof(script_t) + length + 1); +  Com_Memset( buffer, 0, sizeof(script_t) + length + 1 ); + +  script = (script_t *) buffer; +  Com_Memset(script, 0, sizeof(script_t)); +  strcpy(script->filename, name); +  script->buffer = (char *) buffer + sizeof(script_t); +  script->buffer[length] = 0; +  script->length = length; +  //pointer in script buffer +  script->script_p = script->buffer; +  //pointer in script buffer before reading token +  script->lastscript_p = script->buffer; +  //pointer to end of script buffer +  script->end_p = &script->buffer[length]; +  //set if there's a token available in script->token +  script->tokenavailable = 0; +  // +  script->line = 1; +  script->lastline = 1; +  // +  Parse_SetScriptPunctuations(script, NULL); +  // +  Com_Memcpy(script->buffer, ptr, length); +  // +  return script; +} + +/* +=============== +Parse_FreeScript +=============== +*/ +static void Parse_FreeScript(script_t *script) +{ +  if (script->punctuationtable) Z_Free(script->punctuationtable); +  Z_Free(script); +} + +/* +=============== +Parse_SetBaseFolder +=============== +*/ +static void Parse_SetBaseFolder(char *path) +{ +  Com_sprintf(basefolder, sizeof(basefolder), path); +} + +/* +=============== +Parse_SourceError +=============== +*/ +static void QDECL Parse_SourceError(source_t *source, char *str, ...) +{ +  char text[1024]; +  va_list ap; + +  va_start(ap, str); +  vsprintf(text, str, ap); +  va_end(ap); +  Com_Printf( "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +} + +/* +=============== +Parse_SourceWarning +=============== +*/ +static void QDECL Parse_SourceWarning(source_t *source, char *str, ...) +{ +  char text[1024]; +  va_list ap; + +  va_start(ap, str); +  vsprintf(text, str, ap); +  va_end(ap); +  Com_Printf( "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +} + +/* +=============== +Parse_PushIndent +=============== +*/ +static void Parse_PushIndent(source_t *source, int type, int skip) +{ +  indent_t *indent; + +  indent = (indent_t *) Z_Malloc(sizeof(indent_t)); +  indent->type = type; +  indent->script = source->scriptstack; +  indent->skip = (skip != 0); +  source->skip += indent->skip; +  indent->next = source->indentstack; +  source->indentstack = indent; +} + +/* +=============== +Parse_PopIndent +=============== +*/ +static void Parse_PopIndent(source_t *source, int *type, int *skip) +{ +  indent_t *indent; + +  *type = 0; +  *skip = 0; + +  indent = source->indentstack; +  if (!indent) return; + +  //must be an indent from the current script +  if (source->indentstack->script != source->scriptstack) return; + +  *type = indent->type; +  *skip = indent->skip; +  source->indentstack = source->indentstack->next; +  source->skip -= indent->skip; +  Z_Free(indent); +} + +/* +=============== +Parse_PushScript +=============== +*/ +static void Parse_PushScript(source_t *source, script_t *script) +{ +  script_t *s; + +  for (s = source->scriptstack; s; s = s->next) +  { +    if (!Q_stricmp(s->filename, script->filename)) +    { +      Parse_SourceError(source, "%s recursively included", script->filename); +      return; +    } +  } +  //push the script on the script stack +  script->next = source->scriptstack; +  source->scriptstack = script; +} + +/* +=============== +Parse_CopyToken +=============== +*/ +static token_t *Parse_CopyToken(token_t *token) +{ +  token_t *t; + +//  t = (token_t *) malloc(sizeof(token_t)); +  t = (token_t *) Z_Malloc(sizeof(token_t)); +//  t = freetokens; +  if (!t) +  { +    Com_Error(ERR_FATAL, "out of token space\n"); +    return NULL; +  } +//  freetokens = freetokens->next; +  Com_Memcpy(t, token, sizeof(token_t)); +  t->next = NULL; +  numtokens++; +  return t; +} + +/* +=============== +Parse_FreeToken +=============== +*/ +static void Parse_FreeToken(token_t *token) +{ +  //free(token); +  Z_Free(token); +//  token->next = freetokens; +//  freetokens = token; +  numtokens--; +} + +/* +=============== +Parse_ReadSourceToken +=============== +*/ +static int Parse_ReadSourceToken(source_t *source, token_t *token) +{ +  token_t *t; +  script_t *script; +  int type, skip; + +  //if there's no token already available +  while(!source->tokens) +  { +    //if there's a token to read from the script +    if (Parse_ReadSciptToken(source->scriptstack, token)) return qtrue; +    //if at the end of the script +    if (Parse_EndOfScript(source->scriptstack)) +    { +      //remove all indents of the script +      while(source->indentstack && +          source->indentstack->script == source->scriptstack) +      { +        Parse_SourceWarning(source, "missing #endif"); +        Parse_PopIndent(source, &type, &skip); +      } +    } +    //if this was the initial script +    if (!source->scriptstack->next) return qfalse; +    //remove the script and return to the last one +    script = source->scriptstack; +    source->scriptstack = source->scriptstack->next; +    Parse_FreeScript(script); +  } +  //copy the already available token +  Com_Memcpy(token, source->tokens, sizeof(token_t)); +  //free the read token +  t = source->tokens; +  source->tokens = source->tokens->next; +  Parse_FreeToken(t); +  return qtrue; +} + +/* +=============== +Parse_UnreadSourceToken +=============== +*/ +static int Parse_UnreadSourceToken(source_t *source, token_t *token) +{ +  token_t *t; + +  t = Parse_CopyToken(token); +  t->next = source->tokens; +  source->tokens = t; +  return qtrue; +} + +/* +=============== +Parse_ReadDefineParms +=============== +*/ +static int Parse_ReadDefineParms(source_t *source, define_t *define, token_t **parms, int maxparms) +{ +  token_t token, *t, *last; +  int i, done, lastcomma, numparms, indent; + +  if (!Parse_ReadSourceToken(source, &token)) +  { +    Parse_SourceError(source, "define %s missing parms", define->name); +    return qfalse; +  } +  // +  if (define->numparms > maxparms) +  { +    Parse_SourceError(source, "define with more than %d parameters", maxparms); +    return qfalse; +  } +  // +  for (i = 0; i < define->numparms; i++) parms[i] = NULL; +  //if no leading "(" +  if (strcmp(token.string, "(")) +  { +    Parse_UnreadSourceToken(source, &token); +    Parse_SourceError(source, "define %s missing parms", define->name); +    return qfalse; +  } +  //read the define parameters +  for (done = 0, numparms = 0, indent = 0; !done;) +  { +    if (numparms >= maxparms) +    { +      Parse_SourceError(source, "define %s with too many parms", define->name); +      return qfalse; +    } +    if (numparms >= define->numparms) +    { +      Parse_SourceWarning(source, "define %s has too many parms", define->name); +      return qfalse; +    } +    parms[numparms] = NULL; +    lastcomma = 1; +    last = NULL; +    while(!done) +    { +      // +      if (!Parse_ReadSourceToken(source, &token)) +      { +        Parse_SourceError(source, "define %s incomplete", define->name); +        return qfalse; +      } +      // +      if (!strcmp(token.string, ",")) +      { +        if (indent <= 0) +        { +          if (lastcomma) Parse_SourceWarning(source, "too many comma's"); +          lastcomma = 1; +          break; +        } +      } +      lastcomma = 0; +      // +      if (!strcmp(token.string, "(")) +      { +        indent++; +        continue; +      } +      else if (!strcmp(token.string, ")")) +      { +        if (--indent <= 0) +        { +          if (!parms[define->numparms-1]) +          { +            Parse_SourceWarning(source, "too few define parms"); +          } +          done = 1; +          break; +        } +      } +      // +      if (numparms < define->numparms) +      { +        // +        t = Parse_CopyToken(&token); +        t->next = NULL; +        if (last) last->next = t; +        else parms[numparms] = t; +        last = t; +      } +    } +    numparms++; +  } +  return qtrue; +} + +/* +=============== +Parse_StringizeTokens +=============== +*/ +static int Parse_StringizeTokens(token_t *tokens, token_t *token) +{ +  token_t *t; + +  token->type = TT_STRING; +  token->whitespace_p = NULL; +  token->endwhitespace_p = NULL; +  token->string[0] = '\0'; +  strcat(token->string, "\""); +  for (t = tokens; t; t = t->next) +  { +    strncat(token->string, t->string, MAX_TOKEN_CHARS - strlen(token->string)); +  } +  strncat(token->string, "\"", MAX_TOKEN_CHARS - strlen(token->string)); +  return qtrue; +} + +/* +=============== +Parse_MergeTokens +=============== +*/ +static int Parse_MergeTokens(token_t *t1, token_t *t2) +{ +  //merging of a name with a name or number +  if (t1->type == TT_NAME && (t2->type == TT_NAME || t2->type == TT_NUMBER)) +  { +    strcat(t1->string, t2->string); +    return qtrue; +  } +  //merging of two strings +  if (t1->type == TT_STRING && t2->type == TT_STRING) +  { +    //remove trailing double quote +    t1->string[strlen(t1->string)-1] = '\0'; +    //concat without leading double quote +    strcat(t1->string, &t2->string[1]); +    return qtrue; +  } +  //FIXME: merging of two number of the same sub type +  return qfalse; +} + +/* +=============== +Parse_NameHash +=============== +*/ +//char primes[16] = {1, 3, 5, 7, 11, 13, 17, 19, 23, 27, 29, 31, 37, 41, 43, 47}; +static int Parse_NameHash(char *name) +{ +  int register hash, i; + +  hash = 0; +  for (i = 0; name[i] != '\0'; i++) +  { +    hash += name[i] * (119 + i); +    //hash += (name[i] << 7) + i; +    //hash += (name[i] << (i&15)); +  } +  hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (DEFINEHASHSIZE-1); +  return hash; +} + +/* +=============== +Parse_AddDefineToHash +=============== +*/ +static void Parse_AddDefineToHash(define_t *define, define_t **definehash) +{ +  int hash; + +  hash = Parse_NameHash(define->name); +  define->hashnext = definehash[hash]; +  definehash[hash] = define; +} + +/* +=============== +Parse_FindHashedDefine +=============== +*/ +static define_t *Parse_FindHashedDefine(define_t **definehash, char *name) +{ +  define_t *d; +  int hash; + +  hash = Parse_NameHash(name); +  for (d = definehash[hash]; d; d = d->hashnext) +  { +    if (!strcmp(d->name, name)) return d; +  } +  return NULL; +} + +/* +=============== +Parse_FindDefineParm +=============== +*/ +static int Parse_FindDefineParm(define_t *define, char *name) +{ +  token_t *p; +  int i; + +  i = 0; +  for (p = define->parms; p; p = p->next) +  { +    if (!strcmp(p->string, name)) return i; +    i++; +  } +  return -1; +} + +/* +=============== +Parse_FreeDefine +=============== +*/ +static void Parse_FreeDefine(define_t *define) +{ +  token_t *t, *next; + +  //free the define parameters +  for (t = define->parms; t; t = next) +  { +    next = t->next; +    Parse_FreeToken(t); +  } +  //free the define tokens +  for (t = define->tokens; t; t = next) +  { +    next = t->next; +    Parse_FreeToken(t); +  } +  //free the define +  Z_Free(define); +} + +/* +=============== +Parse_ExpandBuiltinDefine +=============== +*/ +static int Parse_ExpandBuiltinDefine(source_t *source, token_t *deftoken, define_t *define, +                    token_t **firsttoken, token_t **lasttoken) +{ +  token_t *token; +#ifdef _WIN32 +  unsigned long t;  //  time_t t; //to prevent LCC warning +#else +  time_t t; +#endif + +  char *curtime; + +  token = Parse_CopyToken(deftoken); +  switch(define->builtin) +  { +    case BUILTIN_LINE: +    { +      sprintf(token->string, "%d", deftoken->line); +      token->intvalue = deftoken->line; +      token->floatvalue = deftoken->line; +      token->type = TT_NUMBER; +      token->subtype = TT_DECIMAL | TT_INTEGER; +      *firsttoken = token; +      *lasttoken = token; +      break; +    } +    case BUILTIN_FILE: +    { +      strcpy(token->string, source->scriptstack->filename); +      token->type = TT_NAME; +      token->subtype = strlen(token->string); +      *firsttoken = token; +      *lasttoken = token; +      break; +    } +    case BUILTIN_DATE: +    { +      t = time(NULL); +      curtime = ctime(&t); +      strcpy(token->string, "\""); +      strncat(token->string, curtime+4, 7); +      strncat(token->string+7, curtime+20, 4); +      strcat(token->string, "\""); +      free(curtime); +      token->type = TT_NAME; +      token->subtype = strlen(token->string); +      *firsttoken = token; +      *lasttoken = token; +      break; +    } +    case BUILTIN_TIME: +    { +      t = time(NULL); +      curtime = ctime(&t); +      strcpy(token->string, "\""); +      strncat(token->string, curtime+11, 8); +      strcat(token->string, "\""); +      free(curtime); +      token->type = TT_NAME; +      token->subtype = strlen(token->string); +      *firsttoken = token; +      *lasttoken = token; +      break; +    } +    case BUILTIN_STDC: +    default: +    { +      *firsttoken = NULL; +      *lasttoken = NULL; +      break; +    } +  } +  return qtrue; +} + +/* +=============== +Parse_ExpandDefine +=============== +*/ +static int Parse_ExpandDefine(source_t *source, token_t *deftoken, define_t *define, +                    token_t **firsttoken, token_t **lasttoken) +{ +  token_t *parms[MAX_DEFINEPARMS], *dt, *pt, *t; +  token_t *t1, *t2, *first, *last, *nextpt, token; +  int parmnum, i; + +  //if it is a builtin define +  if (define->builtin) +  { +    return Parse_ExpandBuiltinDefine(source, deftoken, define, firsttoken, lasttoken); +  } +  //if the define has parameters +  if (define->numparms) +  { +    if (!Parse_ReadDefineParms(source, define, parms, MAX_DEFINEPARMS)) return qfalse; +  } +  //empty list at first +  first = NULL; +  last = NULL; +  //create a list with tokens of the expanded define +  for (dt = define->tokens; dt; dt = dt->next) +  { +    parmnum = -1; +    //if the token is a name, it could be a define parameter +    if (dt->type == TT_NAME) +    { +      parmnum = Parse_FindDefineParm(define, dt->string); +    } +    //if it is a define parameter +    if (parmnum >= 0) +    { +      for (pt = parms[parmnum]; pt; pt = pt->next) +      { +        t = Parse_CopyToken(pt); +        //add the token to the list +        t->next = NULL; +        if (last) last->next = t; +        else first = t; +        last = t; +      } +    } +    else +    { +      //if stringizing operator +      if (dt->string[0] == '#' && dt->string[1] == '\0') +      { +        //the stringizing operator must be followed by a define parameter +        if (dt->next) parmnum = Parse_FindDefineParm(define, dt->next->string); +        else parmnum = -1; +        // +        if (parmnum >= 0) +        { +          //step over the stringizing operator +          dt = dt->next; +          //stringize the define parameter tokens +          if (!Parse_StringizeTokens(parms[parmnum], &token)) +          { +            Parse_SourceError(source, "can't stringize tokens"); +            return qfalse; +          } +          t = Parse_CopyToken(&token); +        } +        else +        { +          Parse_SourceWarning(source, "stringizing operator without define parameter"); +          continue; +        } +      } +      else +      { +        t = Parse_CopyToken(dt); +      } +      //add the token to the list +      t->next = NULL; +      if (last) last->next = t; +      else first = t; +      last = t; +    } +  } +  //check for the merging operator +  for (t = first; t; ) +  { +    if (t->next) +    { +      //if the merging operator +      if (t->next->string[0] == '#' && t->next->string[1] == '#') +      { +        t1 = t; +        t2 = t->next->next; +        if (t2) +        { +          if (!Parse_MergeTokens(t1, t2)) +          { +            Parse_SourceError(source, "can't merge %s with %s", t1->string, t2->string); +            return qfalse; +          } +          Parse_FreeToken(t1->next); +          t1->next = t2->next; +          if (t2 == last) last = t1; +          Parse_FreeToken(t2); +          continue; +        } +      } +    } +    t = t->next; +  } +  //store the first and last token of the list +  *firsttoken = first; +  *lasttoken = last; +  //free all the parameter tokens +  for (i = 0; i < define->numparms; i++) +  { +    for (pt = parms[i]; pt; pt = nextpt) +    { +      nextpt = pt->next; +      Parse_FreeToken(pt); +    } +  } +  // +  return qtrue; +} + +/* +=============== +Parse_ExpandDefineIntoSource +=============== +*/ +static int Parse_ExpandDefineIntoSource(source_t *source, token_t *deftoken, define_t *define) +{ +  token_t *firsttoken, *lasttoken; + +  if (!Parse_ExpandDefine(source, deftoken, define, &firsttoken, &lasttoken)) return qfalse; + +  if (firsttoken && lasttoken) +  { +    lasttoken->next = source->tokens; +    source->tokens = firsttoken; +    return qtrue; +  } +  return qfalse; +} + +/* +=============== +Parse_ConvertPath +=============== +*/ +static void Parse_ConvertPath(char *path) +{ +  char *ptr; + +  //remove double path seperators +  for (ptr = path; *ptr;) +  { +    if ((*ptr == '\\' || *ptr == '/') && +        (*(ptr+1) == '\\' || *(ptr+1) == '/')) +    { +      strcpy(ptr, ptr+1); +    } +    else +    { +      ptr++; +    } +  } +  //set OS dependent path seperators +  for (ptr = path; *ptr;) +  { +    if (*ptr == '/' || *ptr == '\\') *ptr = PATH_SEP; +    ptr++; +  } +} + +/* +=============== +Parse_ReadLine + +reads a token from the current line, continues reading on the next +line only if a backslash '\' is encountered. +=============== +*/ +static int Parse_ReadLine(source_t *source, token_t *token) +{ +  int crossline; + +  crossline = 0; +  do +  { +    if (!Parse_ReadSourceToken(source, token)) return qfalse; + +    if (token->linescrossed > crossline) +    { +      Parse_UnreadSourceToken(source, token); +      return qfalse; +    } +    crossline = 1; +  } while(!strcmp(token->string, "\\")); +  return qtrue; +} + +/* +=============== +Parse_OperatorPriority +=============== +*/ +typedef struct operator_s +{ +  int operator; +  int priority; +  int parentheses; +  struct operator_s *prev, *next; +} operator_t; + +typedef struct value_s +{ +  signed long int intvalue; +  double floatvalue; +  int parentheses; +  struct value_s *prev, *next; +} value_t; + +static int Parse_OperatorPriority(int op) +{ +  switch(op) +  { +    case P_MUL: return 15; +    case P_DIV: return 15; +    case P_MOD: return 15; +    case P_ADD: return 14; +    case P_SUB: return 14; + +    case P_LOGIC_AND: return 7; +    case P_LOGIC_OR: return 6; +    case P_LOGIC_GEQ: return 12; +    case P_LOGIC_LEQ: return 12; +    case P_LOGIC_EQ: return 11; +    case P_LOGIC_UNEQ: return 11; + +    case P_LOGIC_NOT: return 16; +    case P_LOGIC_GREATER: return 12; +    case P_LOGIC_LESS: return 12; + +    case P_RSHIFT: return 13; +    case P_LSHIFT: return 13; + +    case P_BIN_AND: return 10; +    case P_BIN_OR: return 8; +    case P_BIN_XOR: return 9; +    case P_BIN_NOT: return 16; + +    case P_COLON: return 5; +    case P_QUESTIONMARK: return 5; +  } +  return qfalse; +} + +#define MAX_VALUES    64 +#define MAX_OPERATORS 64 +#define AllocValue(val)                 \ +  if (numvalues >= MAX_VALUES) {            \ +    Parse_SourceError(source, "out of value space\n");    \ +    error = 1;                    \ +    break;                      \ +  }                         \ +  else                        \ +    val = &value_heap[numvalues++]; +#define FreeValue(val) +// +#define AllocOperator(op)               \ +  if (numoperators >= MAX_OPERATORS) {        \ +    Parse_SourceError(source, "out of operator space\n"); \ +    error = 1;                    \ +    break;                      \ +  }                         \ +  else                        \ +    op = &operator_heap[numoperators++]; +#define FreeOperator(op) + +/* +=============== +Parse_EvaluateTokens +=============== +*/ +static int Parse_EvaluateTokens(source_t *source, token_t *tokens, signed long int *intvalue, +                                  double *floatvalue, int integer) +{ +  operator_t *o, *firstoperator, *lastoperator; +  value_t *v, *firstvalue, *lastvalue, *v1, *v2; +  token_t *t; +  int brace = 0; +  int parentheses = 0; +  int error = 0; +  int lastwasvalue = 0; +  int negativevalue = 0; +  int questmarkintvalue = 0; +  double questmarkfloatvalue = 0; +  int gotquestmarkvalue = qfalse; +  int lastoperatortype = 0; +  // +  operator_t operator_heap[MAX_OPERATORS]; +  int numoperators = 0; +  value_t value_heap[MAX_VALUES]; +  int numvalues = 0; + +  firstoperator = lastoperator = NULL; +  firstvalue = lastvalue = NULL; +  if (intvalue) *intvalue = 0; +  if (floatvalue) *floatvalue = 0; +  for (t = tokens; t; t = t->next) +  { +    switch(t->type) +    { +      case TT_NAME: +      { +        if (lastwasvalue || negativevalue) +        { +          Parse_SourceError(source, "syntax error in #if/#elif"); +          error = 1; +          break; +        } +        if (strcmp(t->string, "defined")) +        { +          Parse_SourceError(source, "undefined name %s in #if/#elif", t->string); +          error = 1; +          break; +        } +        t = t->next; +        if (!strcmp(t->string, "(")) +        { +          brace = qtrue; +          t = t->next; +        } +        if (!t || t->type != TT_NAME) +        { +          Parse_SourceError(source, "defined without name in #if/#elif"); +          error = 1; +          break; +        } +        //v = (value_t *) Z_Malloc(sizeof(value_t)); +        AllocValue(v); +        if (Parse_FindHashedDefine(source->definehash, t->string)) +        { +          v->intvalue = 1; +          v->floatvalue = 1; +        } +        else +        { +          v->intvalue = 0; +          v->floatvalue = 0; +        } +        v->parentheses = parentheses; +        v->next = NULL; +        v->prev = lastvalue; +        if (lastvalue) lastvalue->next = v; +        else firstvalue = v; +        lastvalue = v; +        if (brace) +        { +          t = t->next; +          if (!t || strcmp(t->string, ")")) +          { +            Parse_SourceError(source, "defined without ) in #if/#elif"); +            error = 1; +            break; +          } +        } +        brace = qfalse; +        // defined() creates a value +        lastwasvalue = 1; +        break; +      } +      case TT_NUMBER: +      { +        if (lastwasvalue) +        { +          Parse_SourceError(source, "syntax error in #if/#elif"); +          error = 1; +          break; +        } +        //v = (value_t *) Z_Malloc(sizeof(value_t)); +        AllocValue(v); +        if (negativevalue) +        { +          v->intvalue = - (signed int) t->intvalue; +          v->floatvalue = - t->floatvalue; +        } +        else +        { +          v->intvalue = t->intvalue; +          v->floatvalue = t->floatvalue; +        } +        v->parentheses = parentheses; +        v->next = NULL; +        v->prev = lastvalue; +        if (lastvalue) lastvalue->next = v; +        else firstvalue = v; +        lastvalue = v; +        //last token was a value +        lastwasvalue = 1; +        // +        negativevalue = 0; +        break; +      } +      case TT_PUNCTUATION: +      { +        if (negativevalue) +        { +          Parse_SourceError(source, "misplaced minus sign in #if/#elif"); +          error = 1; +          break; +        } +        if (t->subtype == P_PARENTHESESOPEN) +        { +          parentheses++; +          break; +        } +        else if (t->subtype == P_PARENTHESESCLOSE) +        { +          parentheses--; +          if (parentheses < 0) +          { +            Parse_SourceError(source, "too many ) in #if/#elsif"); +            error = 1; +          } +          break; +        } +        //check for invalid operators on floating point values +        if (!integer) +        { +          if (t->subtype == P_BIN_NOT || t->subtype == P_MOD || +            t->subtype == P_RSHIFT || t->subtype == P_LSHIFT || +            t->subtype == P_BIN_AND || t->subtype == P_BIN_OR || +            t->subtype == P_BIN_XOR) +          { +            Parse_SourceError(source, "illigal operator %s on floating point operands\n", t->string); +            error = 1; +            break; +          } +        } +        switch(t->subtype) +        { +          case P_LOGIC_NOT: +          case P_BIN_NOT: +          { +            if (lastwasvalue) +            { +              Parse_SourceError(source, "! or ~ after value in #if/#elif"); +              error = 1; +              break; +            } +            break; +          } +          case P_INC: +          case P_DEC: +          { +            Parse_SourceError(source, "++ or -- used in #if/#elif"); +            break; +          } +          case P_SUB: +          { +            if (!lastwasvalue) +            { +              negativevalue = 1; +              break; +            } +          } + +          case P_MUL: +          case P_DIV: +          case P_MOD: +          case P_ADD: + +          case P_LOGIC_AND: +          case P_LOGIC_OR: +          case P_LOGIC_GEQ: +          case P_LOGIC_LEQ: +          case P_LOGIC_EQ: +          case P_LOGIC_UNEQ: + +          case P_LOGIC_GREATER: +          case P_LOGIC_LESS: + +          case P_RSHIFT: +          case P_LSHIFT: + +          case P_BIN_AND: +          case P_BIN_OR: +          case P_BIN_XOR: + +          case P_COLON: +          case P_QUESTIONMARK: +          { +            if (!lastwasvalue) +            { +              Parse_SourceError(source, "operator %s after operator in #if/#elif", t->string); +              error = 1; +              break; +            } +            break; +          } +          default: +          { +            Parse_SourceError(source, "invalid operator %s in #if/#elif", t->string); +            error = 1; +            break; +          } +        } +        if (!error && !negativevalue) +        { +          //o = (operator_t *) Z_Malloc(sizeof(operator_t)); +          AllocOperator(o); +          o->operator = t->subtype; +          o->priority = Parse_OperatorPriority(t->subtype); +          o->parentheses = parentheses; +          o->next = NULL; +          o->prev = lastoperator; +          if (lastoperator) lastoperator->next = o; +          else firstoperator = o; +          lastoperator = o; +          lastwasvalue = 0; +        } +        break; +      } +      default: +      { +        Parse_SourceError(source, "unknown %s in #if/#elif", t->string); +        error = 1; +        break; +      } +    } +    if (error) break; +  } +  if (!error) +  { +    if (!lastwasvalue) +    { +      Parse_SourceError(source, "trailing operator in #if/#elif"); +      error = 1; +    } +    else if (parentheses) +    { +      Parse_SourceError(source, "too many ( in #if/#elif"); +      error = 1; +    } +  } +  // +  gotquestmarkvalue = qfalse; +  questmarkintvalue = 0; +  questmarkfloatvalue = 0; +  //while there are operators +  while(!error && firstoperator) +  { +    v = firstvalue; +    for (o = firstoperator; o->next; o = o->next) +    { +      //if the current operator is nested deeper in parentheses +      //than the next operator +      if (o->parentheses > o->next->parentheses) break; +      //if the current and next operator are nested equally deep in parentheses +      if (o->parentheses == o->next->parentheses) +      { +        //if the priority of the current operator is equal or higher +        //than the priority of the next operator +        if (o->priority >= o->next->priority) break; +      } +      //if the arity of the operator isn't equal to 1 +      if (o->operator != P_LOGIC_NOT +          && o->operator != P_BIN_NOT) v = v->next; +      //if there's no value or no next value +      if (!v) +      { +        Parse_SourceError(source, "mising values in #if/#elif"); +        error = 1; +        break; +      } +    } +    if (error) break; +    v1 = v; +    v2 = v->next; +    switch(o->operator) +    { +      case P_LOGIC_NOT:   v1->intvalue = !v1->intvalue; +                  v1->floatvalue = !v1->floatvalue; break; +      case P_BIN_NOT:     v1->intvalue = ~v1->intvalue; +                  break; +      case P_MUL:       v1->intvalue *= v2->intvalue; +                  v1->floatvalue *= v2->floatvalue; break; +      case P_DIV:       if (!v2->intvalue || !v2->floatvalue) +                  { +                    Parse_SourceError(source, "divide by zero in #if/#elif\n"); +                    error = 1; +                    break; +                  } +                  v1->intvalue /= v2->intvalue; +                  v1->floatvalue /= v2->floatvalue; break; +      case P_MOD:       if (!v2->intvalue) +                  { +                    Parse_SourceError(source, "divide by zero in #if/#elif\n"); +                    error = 1; +                    break; +                  } +                  v1->intvalue %= v2->intvalue; break; +      case P_ADD:       v1->intvalue += v2->intvalue; +                  v1->floatvalue += v2->floatvalue; break; +      case P_SUB:       v1->intvalue -= v2->intvalue; +                  v1->floatvalue -= v2->floatvalue; break; +      case P_LOGIC_AND:   v1->intvalue = v1->intvalue && v2->intvalue; +                  v1->floatvalue = v1->floatvalue && v2->floatvalue; break; +      case P_LOGIC_OR:    v1->intvalue = v1->intvalue || v2->intvalue; +                  v1->floatvalue = v1->floatvalue || v2->floatvalue; break; +      case P_LOGIC_GEQ:   v1->intvalue = v1->intvalue >= v2->intvalue; +                  v1->floatvalue = v1->floatvalue >= v2->floatvalue; break; +      case P_LOGIC_LEQ:   v1->intvalue = v1->intvalue <= v2->intvalue; +                  v1->floatvalue = v1->floatvalue <= v2->floatvalue; break; +      case P_LOGIC_EQ:    v1->intvalue = v1->intvalue == v2->intvalue; +                  v1->floatvalue = v1->floatvalue == v2->floatvalue; break; +      case P_LOGIC_UNEQ:    v1->intvalue = v1->intvalue != v2->intvalue; +                  v1->floatvalue = v1->floatvalue != v2->floatvalue; break; +      case P_LOGIC_GREATER: v1->intvalue = v1->intvalue > v2->intvalue; +                  v1->floatvalue = v1->floatvalue > v2->floatvalue; break; +      case P_LOGIC_LESS:    v1->intvalue = v1->intvalue < v2->intvalue; +                  v1->floatvalue = v1->floatvalue < v2->floatvalue; break; +      case P_RSHIFT:      v1->intvalue >>= v2->intvalue; +                  break; +      case P_LSHIFT:      v1->intvalue <<= v2->intvalue; +                  break; +      case P_BIN_AND:     v1->intvalue &= v2->intvalue; +                  break; +      case P_BIN_OR:      v1->intvalue |= v2->intvalue; +                  break; +      case P_BIN_XOR:     v1->intvalue ^= v2->intvalue; +                  break; +      case P_COLON: +      { +        if (!gotquestmarkvalue) +        { +          Parse_SourceError(source, ": without ? in #if/#elif"); +          error = 1; +          break; +        } +        if (integer) +        { +          if (!questmarkintvalue) v1->intvalue = v2->intvalue; +        } +        else +        { +          if (!questmarkfloatvalue) v1->floatvalue = v2->floatvalue; +        } +        gotquestmarkvalue = qfalse; +        break; +      } +      case P_QUESTIONMARK: +      { +        if (gotquestmarkvalue) +        { +          Parse_SourceError(source, "? after ? in #if/#elif"); +          error = 1; +          break; +        } +        questmarkintvalue = v1->intvalue; +        questmarkfloatvalue = v1->floatvalue; +        gotquestmarkvalue = qtrue; +        break; +      } +    } +    if (error) break; +    lastoperatortype = o->operator; +    //if not an operator with arity 1 +    if (o->operator != P_LOGIC_NOT +        && o->operator != P_BIN_NOT) +    { +      //remove the second value if not question mark operator +      if (o->operator != P_QUESTIONMARK) v = v->next; +      // +      if (v->prev) v->prev->next = v->next; +      else firstvalue = v->next; +      if (v->next) v->next->prev = v->prev; +      else lastvalue = v->prev; +      //Z_Free(v); +      FreeValue(v); +    } +    //remove the operator +    if (o->prev) o->prev->next = o->next; +    else firstoperator = o->next; +    if (o->next) o->next->prev = o->prev; +    else lastoperator = o->prev; +    //Z_Free(o); +    FreeOperator(o); +  } +  if (firstvalue) +  { +    if (intvalue) *intvalue = firstvalue->intvalue; +    if (floatvalue) *floatvalue = firstvalue->floatvalue; +  } +  for (o = firstoperator; o; o = lastoperator) +  { +    lastoperator = o->next; +    //Z_Free(o); +    FreeOperator(o); +  } +  for (v = firstvalue; v; v = lastvalue) +  { +    lastvalue = v->next; +    //Z_Free(v); +    FreeValue(v); +  } +  if (!error) return qtrue; +  if (intvalue) *intvalue = 0; +  if (floatvalue) *floatvalue = 0; +  return qfalse; +} + +/* +=============== +Parse_Evaluate +=============== +*/ +static int Parse_Evaluate(source_t *source, signed long int *intvalue, +                        double *floatvalue, int integer) +{ +  token_t token, *firsttoken, *lasttoken; +  token_t *t, *nexttoken; +  define_t *define; +  int defined = qfalse; + +  if (intvalue) *intvalue = 0; +  if (floatvalue) *floatvalue = 0; +  // +  if (!Parse_ReadLine(source, &token)) +  { +    Parse_SourceError(source, "no value after #if/#elif"); +    return qfalse; +  } +  firsttoken = NULL; +  lasttoken = NULL; +  do +  { +    //if the token is a name +    if (token.type == TT_NAME) +    { +      if (defined) +      { +        defined = qfalse; +        t = Parse_CopyToken(&token); +        t->next = NULL; +        if (lasttoken) lasttoken->next = t; +        else firsttoken = t; +        lasttoken = t; +      } +      else if (!strcmp(token.string, "defined")) +      { +        defined = qtrue; +        t = Parse_CopyToken(&token); +        t->next = NULL; +        if (lasttoken) lasttoken->next = t; +        else firsttoken = t; +        lasttoken = t; +      } +      else +      { +        //then it must be a define +        define = Parse_FindHashedDefine(source->definehash, token.string); +        if (!define) +        { +          Parse_SourceError(source, "can't evaluate %s, not defined", token.string); +          return qfalse; +        } +        if (!Parse_ExpandDefineIntoSource(source, &token, define)) return qfalse; +      } +    } +    //if the token is a number or a punctuation +    else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) +    { +      t = Parse_CopyToken(&token); +      t->next = NULL; +      if (lasttoken) lasttoken->next = t; +      else firsttoken = t; +      lasttoken = t; +    } +    else //can't evaluate the token +    { +      Parse_SourceError(source, "can't evaluate %s", token.string); +      return qfalse; +    } +  } while(Parse_ReadLine(source, &token)); +  // +  if (!Parse_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return qfalse; +  // +  for (t = firsttoken; t; t = nexttoken) +  { +    nexttoken = t->next; +    Parse_FreeToken(t); +  } +  // +  return qtrue; +} + +/* +=============== +Parse_DollarEvaluate +=============== +*/ +static int Parse_DollarEvaluate(source_t *source, signed long int *intvalue, +                        double *floatvalue, int integer) +{ +  int indent, defined = qfalse; +  token_t token, *firsttoken, *lasttoken; +  token_t *t, *nexttoken; +  define_t *define; + +  if (intvalue) *intvalue = 0; +  if (floatvalue) *floatvalue = 0; +  // +  if (!Parse_ReadSourceToken(source, &token)) +  { +    Parse_SourceError(source, "no leading ( after $evalint/$evalfloat"); +    return qfalse; +  } +  if (!Parse_ReadSourceToken(source, &token)) +  { +    Parse_SourceError(source, "nothing to evaluate"); +    return qfalse; +  } +  indent = 1; +  firsttoken = NULL; +  lasttoken = NULL; +  do +  { +    //if the token is a name +    if (token.type == TT_NAME) +    { +      if (defined) +      { +        defined = qfalse; +        t = Parse_CopyToken(&token); +        t->next = NULL; +        if (lasttoken) lasttoken->next = t; +        else firsttoken = t; +        lasttoken = t; +      } +      else if (!strcmp(token.string, "defined")) +      { +        defined = qtrue; +        t = Parse_CopyToken(&token); +        t->next = NULL; +        if (lasttoken) lasttoken->next = t; +        else firsttoken = t; +        lasttoken = t; +      } +      else +      { +        //then it must be a define +        define = Parse_FindHashedDefine(source->definehash, token.string); +        if (!define) +        { +          Parse_SourceError(source, "can't evaluate %s, not defined", token.string); +          return qfalse; +        } +        if (!Parse_ExpandDefineIntoSource(source, &token, define)) return qfalse; +      } +    } +    //if the token is a number or a punctuation +    else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) +    { +      if (*token.string == '(') indent++; +      else if (*token.string == ')') indent--; +      if (indent <= 0) break; +      t = Parse_CopyToken(&token); +      t->next = NULL; +      if (lasttoken) lasttoken->next = t; +      else firsttoken = t; +      lasttoken = t; +    } +    else //can't evaluate the token +    { +      Parse_SourceError(source, "can't evaluate %s", token.string); +      return qfalse; +    } +  } while(Parse_ReadSourceToken(source, &token)); +  // +  if (!Parse_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return qfalse; +  // +  for (t = firsttoken; t; t = nexttoken) +  { +    nexttoken = t->next; +    Parse_FreeToken(t); +  } +  // +  return qtrue; +} + +/* +=============== +Parse_Directive_include +=============== +*/ +static int Parse_Directive_include(source_t *source) +{ +  script_t *script; +  token_t token; +  char path[MAX_QPATH]; + +  if (source->skip > 0) return qtrue; +  // +  if (!Parse_ReadSourceToken(source, &token)) +  { +    Parse_SourceError(source, "#include without file name"); +    return qfalse; +  } +  if (token.linescrossed > 0) +  { +    Parse_SourceError(source, "#include without file name"); +    return qfalse; +  } +  if (token.type == TT_STRING) +  { +    Parse_StripDoubleQuotes(token.string); +    Parse_ConvertPath(token.string); +    script = Parse_LoadScriptFile(token.string); +    if (!script) +    { +      strcpy(path, source->includepath); +      strcat(path, token.string); +      script = Parse_LoadScriptFile(path); +    } +  } +  else if (token.type == TT_PUNCTUATION && *token.string == '<') +  { +    strcpy(path, source->includepath); +    while(Parse_ReadSourceToken(source, &token)) +    { +      if (token.linescrossed > 0) +      { +        Parse_UnreadSourceToken(source, &token); +        break; +      } +      if (token.type == TT_PUNCTUATION && *token.string == '>') break; +      strncat(path, token.string, MAX_QPATH); +    } +    if (*token.string != '>') +    { +      Parse_SourceWarning(source, "#include missing trailing >"); +    } +    if (!strlen(path)) +    { +      Parse_SourceError(source, "#include without file name between < >"); +      return qfalse; +    } +    Parse_ConvertPath(path); +    script = Parse_LoadScriptFile(path); +  } +  else +  { +    Parse_SourceError(source, "#include without file name"); +    return qfalse; +  } +  if (!script) +  { +    Parse_SourceError(source, "file %s not found", path); +    return qfalse; +  } +  Parse_PushScript(source, script); +  return qtrue; +} + +/* +=============== +Parse_WhiteSpaceBeforeToken +=============== +*/ +static int Parse_WhiteSpaceBeforeToken(token_t *token) +{ +  return token->endwhitespace_p - token->whitespace_p > 0; +} + +/* +=============== +Parse_ClearTokenWhiteSpace +=============== +*/ +static void Parse_ClearTokenWhiteSpace(token_t *token) +{ +  token->whitespace_p = NULL; +  token->endwhitespace_p = NULL; +  token->linescrossed = 0; +} + +/* +=============== +Parse_Directive_undef +=============== +*/ +static int Parse_Directive_undef(source_t *source) +{ +  token_t token; +  define_t *define, *lastdefine; +  int hash; + +  if (source->skip > 0) return qtrue; +  // +  if (!Parse_ReadLine(source, &token)) +  { +    Parse_SourceError(source, "undef without name"); +    return qfalse; +  } +  if (token.type != TT_NAME) +  { +    Parse_UnreadSourceToken(source, &token); +    Parse_SourceError(source, "expected name, found %s", token.string); +    return qfalse; +  } + +  hash = Parse_NameHash(token.string); +  for (lastdefine = NULL, define = source->definehash[hash]; define; define = define->hashnext) +  { +    if (!strcmp(define->name, token.string)) +    { +      if (define->flags & DEFINE_FIXED) +      { +        Parse_SourceWarning(source, "can't undef %s", token.string); +      } +      else +      { +        if (lastdefine) lastdefine->hashnext = define->hashnext; +        else source->definehash[hash] = define->hashnext; +        Parse_FreeDefine(define); +      } +      break; +    } +    lastdefine = define; +  } +  return qtrue; +} + +/* +=============== +Parse_Directive_elif +=============== +*/ +static int Parse_Directive_elif(source_t *source) +{ +  signed long int value; +  int type, skip; + +  Parse_PopIndent(source, &type, &skip); +  if (!type || type == INDENT_ELSE) +  { +    Parse_SourceError(source, "misplaced #elif"); +    return qfalse; +  } +  if (!Parse_Evaluate(source, &value, NULL, qtrue)) return qfalse; +  skip = (value == 0); +  Parse_PushIndent(source, INDENT_ELIF, skip); +  return qtrue; +} + +/* +=============== +Parse_Directive_if +=============== +*/ +static int Parse_Directive_if(source_t *source) +{ +  signed long int value; +  int skip; + +  if (!Parse_Evaluate(source, &value, NULL, qtrue)) return qfalse; +  skip = (value == 0); +  Parse_PushIndent(source, INDENT_IF, skip); +  return qtrue; +} + +/* +=============== +Parse_Directive_line +=============== +*/ +static int Parse_Directive_line(source_t *source) +{ +  Parse_SourceError(source, "#line directive not supported"); +  return qfalse; +} + +/* +=============== +Parse_Directive_error +=============== +*/ +static int Parse_Directive_error(source_t *source) +{ +  token_t token; + +  strcpy(token.string, ""); +  Parse_ReadSourceToken(source, &token); +  Parse_SourceError(source, "#error directive: %s", token.string); +  return qfalse; +} + +/* +=============== +Parse_Directive_pragma +=============== +*/ +static int Parse_Directive_pragma(source_t *source) +{ +  token_t token; + +  Parse_SourceWarning(source, "#pragma directive not supported"); +  while(Parse_ReadLine(source, &token)) ; +  return qtrue; +} + +/* +=============== +Parse_UnreadSignToken +=============== +*/ +static void Parse_UnreadSignToken(source_t *source) +{ +  token_t token; + +  token.line = source->scriptstack->line; +  token.whitespace_p = source->scriptstack->script_p; +  token.endwhitespace_p = source->scriptstack->script_p; +  token.linescrossed = 0; +  strcpy(token.string, "-"); +  token.type = TT_PUNCTUATION; +  token.subtype = P_SUB; +  Parse_UnreadSourceToken(source, &token); +} + +/* +=============== +Parse_Directive_eval +=============== +*/ +static int Parse_Directive_eval(source_t *source) +{ +  signed long int value; +  token_t token; + +  if (!Parse_Evaluate(source, &value, NULL, qtrue)) return qfalse; +  // +  token.line = source->scriptstack->line; +  token.whitespace_p = source->scriptstack->script_p; +  token.endwhitespace_p = source->scriptstack->script_p; +  token.linescrossed = 0; +  sprintf(token.string, "%d", abs(value)); +  token.type = TT_NUMBER; +  token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL; +  Parse_UnreadSourceToken(source, &token); +  if (value < 0) Parse_UnreadSignToken(source); +  return qtrue; +} + +/* +=============== +Parse_Directive_evalfloat +=============== +*/ +static int Parse_Directive_evalfloat(source_t *source) +{ +  double value; +  token_t token; + +  if (!Parse_Evaluate(source, NULL, &value, qfalse)) return qfalse; +  token.line = source->scriptstack->line; +  token.whitespace_p = source->scriptstack->script_p; +  token.endwhitespace_p = source->scriptstack->script_p; +  token.linescrossed = 0; +  sprintf(token.string, "%1.2f", fabs(value)); +  token.type = TT_NUMBER; +  token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL; +  Parse_UnreadSourceToken(source, &token); +  if (value < 0) Parse_UnreadSignToken(source); +  return qtrue; +} + +/* +=============== +Parse_DollarDirective_evalint +=============== +*/ +static int Parse_DollarDirective_evalint(source_t *source) +{ +  signed long int value; +  token_t token; + +  if (!Parse_DollarEvaluate(source, &value, NULL, qtrue)) return qfalse; +  // +  token.line = source->scriptstack->line; +  token.whitespace_p = source->scriptstack->script_p; +  token.endwhitespace_p = source->scriptstack->script_p; +  token.linescrossed = 0; +  sprintf(token.string, "%d", abs(value)); +  token.type = TT_NUMBER; +  token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL; +  token.intvalue = value; +  token.floatvalue = value; +  Parse_UnreadSourceToken(source, &token); +  if (value < 0) Parse_UnreadSignToken(source); +  return qtrue; +} + +/* +=============== +Parse_DollarDirective_evalfloat +=============== +*/ +static int Parse_DollarDirective_evalfloat(source_t *source) +{ +  double value; +  token_t token; + +  if (!Parse_DollarEvaluate(source, NULL, &value, qfalse)) return qfalse; +  token.line = source->scriptstack->line; +  token.whitespace_p = source->scriptstack->script_p; +  token.endwhitespace_p = source->scriptstack->script_p; +  token.linescrossed = 0; +  sprintf(token.string, "%1.2f", fabs(value)); +  token.type = TT_NUMBER; +  token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL; +  token.intvalue = (unsigned long) value; +  token.floatvalue = value; +  Parse_UnreadSourceToken(source, &token); +  if (value < 0) Parse_UnreadSignToken(source); +  return qtrue; +} + +/* +=============== +Parse_ReadDollarDirective +=============== +*/ +directive_t dollardirectives[20] = +{ +  {"evalint", Parse_DollarDirective_evalint}, +  {"evalfloat", Parse_DollarDirective_evalfloat}, +  {NULL, NULL} +}; + +static int Parse_ReadDollarDirective(source_t *source) +{ +  token_t token; +  int i; + +  //read the directive name +  if (!Parse_ReadSourceToken(source, &token)) +  { +    Parse_SourceError(source, "found $ without name"); +    return qfalse; +  } +  //directive name must be on the same line +  if (token.linescrossed > 0) +  { +    Parse_UnreadSourceToken(source, &token); +    Parse_SourceError(source, "found $ at end of line"); +    return qfalse; +  } +  //if if is a name +  if (token.type == TT_NAME) +  { +    //find the precompiler directive +    for (i = 0; dollardirectives[i].name; i++) +    { +      if (!strcmp(dollardirectives[i].name, token.string)) +      { +        return dollardirectives[i].func(source); +      } +    } +  } +  Parse_UnreadSourceToken(source, &token); +  Parse_SourceError(source, "unknown precompiler directive %s", token.string); +  return qfalse; +} + +/* +=============== +Parse_Directive_if_def +=============== +*/ +static int Parse_Directive_if_def(source_t *source, int type) +{ +  token_t token; +  define_t *d; +  int skip; + +  if (!Parse_ReadLine(source, &token)) +  { +    Parse_SourceError(source, "#ifdef without name"); +    return qfalse; +  } +  if (token.type != TT_NAME) +  { +    Parse_UnreadSourceToken(source, &token); +    Parse_SourceError(source, "expected name after #ifdef, found %s", token.string); +    return qfalse; +  } +  d = Parse_FindHashedDefine(source->definehash, token.string); +  skip = (type == INDENT_IFDEF) == (d == NULL); +  Parse_PushIndent(source, type, skip); +  return qtrue; +} + +/* +=============== +Parse_Directive_ifdef +=============== +*/ +static int Parse_Directive_ifdef(source_t *source) +{ +  return Parse_Directive_if_def(source, INDENT_IFDEF); +} + +/* +=============== +Parse_Directive_ifndef +=============== +*/ +static int Parse_Directive_ifndef(source_t *source) +{ +  return Parse_Directive_if_def(source, INDENT_IFNDEF); +} + +/* +=============== +Parse_Directive_else +=============== +*/ +static int Parse_Directive_else(source_t *source) +{ +  int type, skip; + +  Parse_PopIndent(source, &type, &skip); +  if (!type) +  { +    Parse_SourceError(source, "misplaced #else"); +    return qfalse; +  } +  if (type == INDENT_ELSE) +  { +    Parse_SourceError(source, "#else after #else"); +    return qfalse; +  } +  Parse_PushIndent(source, INDENT_ELSE, !skip); +  return qtrue; +} + +/* +=============== +Parse_Directive_endif +=============== +*/ +static int Parse_Directive_endif(source_t *source) +{ +  int type, skip; + +  Parse_PopIndent(source, &type, &skip); +  if (!type) +  { +    Parse_SourceError(source, "misplaced #endif"); +    return qfalse; +  } +  return qtrue; +} + +/* +=============== +Parse_CheckTokenString +=============== +*/ +static int Parse_CheckTokenString(source_t *source, char *string) +{ +  token_t tok; + +  if (!Parse_ReadToken(source, &tok)) return qfalse; +  //if the token is available +  if (!strcmp(tok.string, string)) return qtrue; +  // +  Parse_UnreadSourceToken(source, &tok); +  return qfalse; +} + +/* +=============== +Parse_Directive_define +=============== +*/ +static int Parse_Directive_define(source_t *source) +{ +  token_t token, *t, *last; +  define_t *define; + +  if (source->skip > 0) return qtrue; +  // +  if (!Parse_ReadLine(source, &token)) +  { +    Parse_SourceError(source, "#define without name"); +    return qfalse; +  } +  if (token.type != TT_NAME) +  { +    Parse_UnreadSourceToken(source, &token); +    Parse_SourceError(source, "expected name after #define, found %s", token.string); +    return qfalse; +  } +  //check if the define already exists +  define = Parse_FindHashedDefine(source->definehash, token.string); +  if (define) +  { +    if (define->flags & DEFINE_FIXED) +    { +      Parse_SourceError(source, "can't redefine %s", token.string); +      return qfalse; +    } +    Parse_SourceWarning(source, "redefinition of %s", token.string); +    //unread the define name before executing the #undef directive +    Parse_UnreadSourceToken(source, &token); +    if (!Parse_Directive_undef(source)) return qfalse; +    //if the define was not removed (define->flags & DEFINE_FIXED) +    define = Parse_FindHashedDefine(source->definehash, token.string); +  } +  //allocate define +  define = (define_t *) Z_Malloc(sizeof(define_t) + strlen(token.string) + 1); +  Com_Memset(define, 0, sizeof(define_t)); +  define->name = (char *) define + sizeof(define_t); +  strcpy(define->name, token.string); +  //add the define to the source +  Parse_AddDefineToHash(define, source->definehash); +  //if nothing is defined, just return +  if (!Parse_ReadLine(source, &token)) return qtrue; +  //if it is a define with parameters +  if (!Parse_WhiteSpaceBeforeToken(&token) && !strcmp(token.string, "(")) +  { +    //read the define parameters +    last = NULL; +    if (!Parse_CheckTokenString(source, ")")) +    { +      while(1) +      { +        if (!Parse_ReadLine(source, &token)) +        { +          Parse_SourceError(source, "expected define parameter"); +          return qfalse; +        } +        //if it isn't a name +        if (token.type != TT_NAME) +        { +          Parse_SourceError(source, "invalid define parameter"); +          return qfalse; +        } +        // +        if (Parse_FindDefineParm(define, token.string) >= 0) +        { +          Parse_SourceError(source, "two the same define parameters"); +          return qfalse; +        } +        //add the define parm +        t = Parse_CopyToken(&token); +        Parse_ClearTokenWhiteSpace(t); +        t->next = NULL; +        if (last) last->next = t; +        else define->parms = t; +        last = t; +        define->numparms++; +        //read next token +        if (!Parse_ReadLine(source, &token)) +        { +          Parse_SourceError(source, "define parameters not terminated"); +          return qfalse; +        } +        // +        if (!strcmp(token.string, ")")) break; +        //then it must be a comma +        if (strcmp(token.string, ",")) +        { +          Parse_SourceError(source, "define not terminated"); +          return qfalse; +        } +      } +    } +    if (!Parse_ReadLine(source, &token)) return qtrue; +  } +  //read the defined stuff +  last = NULL; +  do +  { +    t = Parse_CopyToken(&token); +    if (t->type == TT_NAME && !strcmp(t->string, define->name)) +    { +      Parse_SourceError(source, "recursive define (removed recursion)"); +      continue; +    } +    Parse_ClearTokenWhiteSpace(t); +    t->next = NULL; +    if (last) last->next = t; +    else define->tokens = t; +    last = t; +  } while(Parse_ReadLine(source, &token)); +  // +  if (last) +  { +    //check for merge operators at the beginning or end +    if (!strcmp(define->tokens->string, "##") || +        !strcmp(last->string, "##")) +    { +      Parse_SourceError(source, "define with misplaced ##"); +      return qfalse; +    } +  } +  return qtrue; +} + +/* +=============== +Parse_ReadDirective +=============== +*/ +directive_t directives[20] = +{ +  {"if", Parse_Directive_if}, +  {"ifdef", Parse_Directive_ifdef}, +  {"ifndef", Parse_Directive_ifndef}, +  {"elif", Parse_Directive_elif}, +  {"else", Parse_Directive_else}, +  {"endif", Parse_Directive_endif}, +  {"include", Parse_Directive_include}, +  {"define", Parse_Directive_define}, +  {"undef", Parse_Directive_undef}, +  {"line", Parse_Directive_line}, +  {"error", Parse_Directive_error}, +  {"pragma", Parse_Directive_pragma}, +  {"eval", Parse_Directive_eval}, +  {"evalfloat", Parse_Directive_evalfloat}, +  {NULL, NULL} +}; + +static int Parse_ReadDirective(source_t *source) +{ +  token_t token; +  int i; + +  //read the directive name +  if (!Parse_ReadSourceToken(source, &token)) +  { +    Parse_SourceError(source, "found # without name"); +    return qfalse; +  } +  //directive name must be on the same line +  if (token.linescrossed > 0) +  { +    Parse_UnreadSourceToken(source, &token); +    Parse_SourceError(source, "found # at end of line"); +    return qfalse; +  } +  //if if is a name +  if (token.type == TT_NAME) +  { +    //find the precompiler directive +    for (i = 0; directives[i].name; i++) +    { +      if (!strcmp(directives[i].name, token.string)) +      { +        return directives[i].func(source); +      } +    } +  } +  Parse_SourceError(source, "unknown precompiler directive %s", token.string); +  return qfalse; +} + +/* +=============== +Parse_UnreadToken +=============== +*/ +static void Parse_UnreadToken(source_t *source, token_t *token) +{ +  Parse_UnreadSourceToken(source, token); +} + +/* +=============== +Parse_ReadToken +=============== +*/ +static int Parse_ReadToken(source_t *source, token_t *token) +{ +  define_t *define; + +  while(1) +  { +    if (!Parse_ReadSourceToken(source, token)) return qfalse; +    //check for precompiler directives +    if (token->type == TT_PUNCTUATION && *token->string == '#') +    { +      { +        //read the precompiler directive +        if (!Parse_ReadDirective(source)) return qfalse; +        continue; +      } +    } +    if (token->type == TT_PUNCTUATION && *token->string == '$') +    { +      { +        //read the precompiler directive +        if (!Parse_ReadDollarDirective(source)) return qfalse; +        continue; +      } +    } +    // recursively concatenate strings that are behind each other still resolving defines +    if (token->type == TT_STRING) +    { +      token_t newtoken; +      if (Parse_ReadToken(source, &newtoken)) +      { +        if (newtoken.type == TT_STRING) +        { +          token->string[strlen(token->string)-1] = '\0'; +          if (strlen(token->string) + strlen(newtoken.string+1) + 1 >= MAX_TOKEN_CHARS) +          { +            Parse_SourceError(source, "string longer than MAX_TOKEN_CHARS %d\n", MAX_TOKEN_CHARS); +            return qfalse; +          } +          strcat(token->string, newtoken.string+1); +        } +        else +        { +          Parse_UnreadToken(source, &newtoken); +        } +      } +    } +    //if skipping source because of conditional compilation +    if (source->skip) continue; +    //if the token is a name +    if (token->type == TT_NAME) +    { +      //check if the name is a define macro +      define = Parse_FindHashedDefine(source->definehash, token->string); +      //if it is a define macro +      if (define) +      { +        //expand the defined macro +        if (!Parse_ExpandDefineIntoSource(source, token, define)) return qfalse; +        continue; +      } +    } +    //copy token for unreading +    Com_Memcpy(&source->token, token, sizeof(token_t)); +    //found a token +    return qtrue; +  } +} + +/* +=============== +Parse_DefineFromString +=============== +*/ +static define_t *Parse_DefineFromString(char *string) +{ +  script_t *script; +  source_t src; +  token_t *t; +  int res, i; +  define_t *def; + +  script = Parse_LoadScriptMemory(string, strlen(string), "*extern"); +  //create a new source +  Com_Memset(&src, 0, sizeof(source_t)); +  strncpy(src.filename, "*extern", MAX_QPATH); +  src.scriptstack = script; +  src.definehash = Z_Malloc(DEFINEHASHSIZE * sizeof(define_t *)); +  Com_Memset( src.definehash, 0, DEFINEHASHSIZE * sizeof(define_t *)); +  //create a define from the source +  res = Parse_Directive_define(&src); +  //free any tokens if left +  for (t = src.tokens; t; t = src.tokens) +  { +    src.tokens = src.tokens->next; +    Parse_FreeToken(t); +  } +  def = NULL; +  for (i = 0; i < DEFINEHASHSIZE; i++) +  { +    if (src.definehash[i]) +    { +      def = src.definehash[i]; +      break; +    } +  } +  // +  Z_Free(src.definehash); +  // +  Parse_FreeScript(script); +  //if the define was created succesfully +  if (res > 0) return def; +  //free the define is created +  if (src.defines) Parse_FreeDefine(def); +  // +  return NULL; +} + +/* +=============== +Parse_AddGlobalDefine + +add a globals define that will be added to all opened sources +=============== +*/ +int Parse_AddGlobalDefine(char *string) +{ +  define_t *define; + +  define = Parse_DefineFromString(string); +  if (!define) return qfalse; +  define->next = globaldefines; +  globaldefines = define; +  return qtrue; +} + +/* +=============== +Parse_CopyDefine +=============== +*/ +static define_t *Parse_CopyDefine(source_t *source, define_t *define) +{ +  define_t *newdefine; +  token_t *token, *newtoken, *lasttoken; + +  newdefine = (define_t *) Z_Malloc(sizeof(define_t) + strlen(define->name) + 1); +  //copy the define name +  newdefine->name = (char *) newdefine + sizeof(define_t); +  strcpy(newdefine->name, define->name); +  newdefine->flags = define->flags; +  newdefine->builtin = define->builtin; +  newdefine->numparms = define->numparms; +  //the define is not linked +  newdefine->next = NULL; +  newdefine->hashnext = NULL; +  //copy the define tokens +  newdefine->tokens = NULL; +  for (lasttoken = NULL, token = define->tokens; token; token = token->next) +  { +    newtoken = Parse_CopyToken(token); +    newtoken->next = NULL; +    if (lasttoken) lasttoken->next = newtoken; +    else newdefine->tokens = newtoken; +    lasttoken = newtoken; +  } +  //copy the define parameters +  newdefine->parms = NULL; +  for (lasttoken = NULL, token = define->parms; token; token = token->next) +  { +    newtoken = Parse_CopyToken(token); +    newtoken->next = NULL; +    if (lasttoken) lasttoken->next = newtoken; +    else newdefine->parms = newtoken; +    lasttoken = newtoken; +  } +  return newdefine; +} + +/* +=============== +Parse_AddGlobalDefinesToSource +=============== +*/ +static void Parse_AddGlobalDefinesToSource(source_t *source) +{ +  define_t *define, *newdefine; + +  for (define = globaldefines; define; define = define->next) +  { +    newdefine = Parse_CopyDefine(source, define); +    Parse_AddDefineToHash(newdefine, source->definehash); +  } +} + +/* +=============== +Parse_LoadSourceFile +=============== +*/ +static source_t *Parse_LoadSourceFile(const char *filename) +{ +  source_t *source; +  script_t *script; + +  script = Parse_LoadScriptFile(filename); +  if (!script) return NULL; + +  script->next = NULL; + +  source = (source_t *) Z_Malloc(sizeof(source_t)); +  Com_Memset(source, 0, sizeof(source_t)); + +  strncpy(source->filename, filename, MAX_QPATH); +  source->scriptstack = script; +  source->tokens = NULL; +  source->defines = NULL; +  source->indentstack = NULL; +  source->skip = 0; + +  source->definehash = Z_Malloc(DEFINEHASHSIZE * sizeof(define_t *)); +  Com_Memset( source->definehash, 0, DEFINEHASHSIZE * sizeof(define_t *)); +  Parse_AddGlobalDefinesToSource(source); +  return source; +} + +/* +=============== +Parse_FreeSource +=============== +*/ +static void Parse_FreeSource(source_t *source) +{ +  script_t *script; +  token_t *token; +  define_t *define; +  indent_t *indent; +  int i; + +  //Parse_PrintDefineHashTable(source->definehash); +  //free all the scripts +  while(source->scriptstack) +  { +    script = source->scriptstack; +    source->scriptstack = source->scriptstack->next; +    Parse_FreeScript(script); +  } +  //free all the tokens +  while(source->tokens) +  { +    token = source->tokens; +    source->tokens = source->tokens->next; +    Parse_FreeToken(token); +  } +  for (i = 0; i < DEFINEHASHSIZE; i++) +  { +    while(source->definehash[i]) +    { +      define = source->definehash[i]; +      source->definehash[i] = source->definehash[i]->hashnext; +      Parse_FreeDefine(define); +    } +  } +  //free all indents +  while(source->indentstack) +  { +    indent = source->indentstack; +    source->indentstack = source->indentstack->next; +    Z_Free(indent); +  } +  // +  if (source->definehash) Z_Free(source->definehash); +  //free the source itself +  Z_Free(source); +} + +#define MAX_SOURCEFILES   64 + +source_t *sourceFiles[MAX_SOURCEFILES]; + +/* +=============== +Parse_LoadSourceHandle +=============== +*/ +int Parse_LoadSourceHandle(const char *filename) +{ +  source_t *source; +  int i; + +  for (i = 1; i < MAX_SOURCEFILES; i++) +  { +    if (!sourceFiles[i]) +      break; +  } +  if (i >= MAX_SOURCEFILES) +    return 0; +  Parse_SetBaseFolder(""); +  source = Parse_LoadSourceFile(filename); +  if (!source) +    return 0; +  sourceFiles[i] = source; +  return i; +} + +/* +=============== +Parse_FreeSourceHandle +=============== +*/ +int Parse_FreeSourceHandle(int handle) +{ +  if (handle < 1 || handle >= MAX_SOURCEFILES) +    return qfalse; +  if (!sourceFiles[handle]) +    return qfalse; + +  Parse_FreeSource(sourceFiles[handle]); +  sourceFiles[handle] = NULL; +  return qtrue; +} + +/* +=============== +Parse_ReadTokenHandle +=============== +*/ +int Parse_ReadTokenHandle(int handle, pc_token_t *pc_token) +{ +  token_t token; +  int ret; + +  if (handle < 1 || handle >= MAX_SOURCEFILES) +    return 0; +  if (!sourceFiles[handle]) +    return 0; + +  ret = Parse_ReadToken(sourceFiles[handle], &token); +  strcpy(pc_token->string, token.string); +  pc_token->type = token.type; +  pc_token->subtype = token.subtype; +  pc_token->intvalue = token.intvalue; +  pc_token->floatvalue = token.floatvalue; +  if (pc_token->type == TT_STRING) +    Parse_StripDoubleQuotes(pc_token->string); +  return ret; +} + +/* +=============== +Parse_SourceFileAndLine +=============== +*/ +int Parse_SourceFileAndLine(int handle, char *filename, int *line) +{ +  if (handle < 1 || handle >= MAX_SOURCEFILES) +    return qfalse; +  if (!sourceFiles[handle]) +    return qfalse; + +  strcpy(filename, sourceFiles[handle]->filename); +  if (sourceFiles[handle]->scriptstack) +    *line = sourceFiles[handle]->scriptstack->line; +  else +    *line = 0; +  return qtrue; +} diff --git a/src/qcommon/q_math.c b/src/qcommon/q_math.c new file mode 100644 index 0000000..196d2f5 --- /dev/null +++ b/src/qcommon/q_math.c @@ -0,0 +1,1562 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ +// +// q_math.c -- stateless support routines that are included in each code module + +// Some of the vector functions are static inline in q_shared.h. q3asm +// doesn't understand static functions though, so we only want them in +// one file. That's what this is about. +#ifdef Q3_VM +#define __Q3_VM_MATH +#endif + +#include "q_shared.h" + +vec3_t	vec3_origin = {0,0,0}; +vec3_t	axisDefault[3] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; + + +vec4_t		colorBlack	= {0, 0, 0, 1}; +vec4_t		colorRed	= {1, 0, 0, 1}; +vec4_t		colorGreen	= {0, 1, 0, 1}; +vec4_t		colorBlue	= {0, 0, 1, 1}; +vec4_t		colorYellow	= {1, 1, 0, 1}; +vec4_t		colorMagenta= {1, 0, 1, 1}; +vec4_t		colorCyan	= {0, 1, 1, 1}; +vec4_t		colorWhite	= {1, 1, 1, 1}; +vec4_t		colorLtGrey	= {0.75, 0.75, 0.75, 1}; +vec4_t		colorMdGrey	= {0.5, 0.5, 0.5, 1}; +vec4_t		colorDkGrey	= {0.25, 0.25, 0.25, 1}; + +vec4_t	g_color_table[8] = +	{ +	{0.0, 0.0, 0.0, 1.0}, +	{1.0, 0.0, 0.0, 1.0}, +	{0.0, 1.0, 0.0, 1.0}, +	{1.0, 1.0, 0.0, 1.0}, +	{0.0, 0.0, 1.0, 1.0}, +	{0.0, 1.0, 1.0, 1.0}, +	{1.0, 0.0, 1.0, 1.0}, +	{1.0, 1.0, 1.0, 1.0}, +	}; + + +vec3_t	bytedirs[NUMVERTEXNORMALS] = +{ +{-0.525731f, 0.000000f, 0.850651f}, {-0.442863f, 0.238856f, 0.864188f},  +{-0.295242f, 0.000000f, 0.955423f}, {-0.309017f, 0.500000f, 0.809017f},  +{-0.162460f, 0.262866f, 0.951056f}, {0.000000f, 0.000000f, 1.000000f},  +{0.000000f, 0.850651f, 0.525731f}, {-0.147621f, 0.716567f, 0.681718f},  +{0.147621f, 0.716567f, 0.681718f}, {0.000000f, 0.525731f, 0.850651f},  +{0.309017f, 0.500000f, 0.809017f}, {0.525731f, 0.000000f, 0.850651f},  +{0.295242f, 0.000000f, 0.955423f}, {0.442863f, 0.238856f, 0.864188f},  +{0.162460f, 0.262866f, 0.951056f}, {-0.681718f, 0.147621f, 0.716567f},  +{-0.809017f, 0.309017f, 0.500000f},{-0.587785f, 0.425325f, 0.688191f},  +{-0.850651f, 0.525731f, 0.000000f},{-0.864188f, 0.442863f, 0.238856f},  +{-0.716567f, 0.681718f, 0.147621f},{-0.688191f, 0.587785f, 0.425325f},  +{-0.500000f, 0.809017f, 0.309017f}, {-0.238856f, 0.864188f, 0.442863f},  +{-0.425325f, 0.688191f, 0.587785f}, {-0.716567f, 0.681718f, -0.147621f},  +{-0.500000f, 0.809017f, -0.309017f}, {-0.525731f, 0.850651f, 0.000000f},  +{0.000000f, 0.850651f, -0.525731f}, {-0.238856f, 0.864188f, -0.442863f},  +{0.000000f, 0.955423f, -0.295242f}, {-0.262866f, 0.951056f, -0.162460f},  +{0.000000f, 1.000000f, 0.000000f}, {0.000000f, 0.955423f, 0.295242f},  +{-0.262866f, 0.951056f, 0.162460f}, {0.238856f, 0.864188f, 0.442863f},  +{0.262866f, 0.951056f, 0.162460f}, {0.500000f, 0.809017f, 0.309017f},  +{0.238856f, 0.864188f, -0.442863f},{0.262866f, 0.951056f, -0.162460f},  +{0.500000f, 0.809017f, -0.309017f},{0.850651f, 0.525731f, 0.000000f},  +{0.716567f, 0.681718f, 0.147621f}, {0.716567f, 0.681718f, -0.147621f},  +{0.525731f, 0.850651f, 0.000000f}, {0.425325f, 0.688191f, 0.587785f},  +{0.864188f, 0.442863f, 0.238856f}, {0.688191f, 0.587785f, 0.425325f},  +{0.809017f, 0.309017f, 0.500000f}, {0.681718f, 0.147621f, 0.716567f},  +{0.587785f, 0.425325f, 0.688191f}, {0.955423f, 0.295242f, 0.000000f},  +{1.000000f, 0.000000f, 0.000000f}, {0.951056f, 0.162460f, 0.262866f},  +{0.850651f, -0.525731f, 0.000000f},{0.955423f, -0.295242f, 0.000000f},  +{0.864188f, -0.442863f, 0.238856f}, {0.951056f, -0.162460f, 0.262866f},  +{0.809017f, -0.309017f, 0.500000f}, {0.681718f, -0.147621f, 0.716567f},  +{0.850651f, 0.000000f, 0.525731f}, {0.864188f, 0.442863f, -0.238856f},  +{0.809017f, 0.309017f, -0.500000f}, {0.951056f, 0.162460f, -0.262866f},  +{0.525731f, 0.000000f, -0.850651f}, {0.681718f, 0.147621f, -0.716567f},  +{0.681718f, -0.147621f, -0.716567f},{0.850651f, 0.000000f, -0.525731f},  +{0.809017f, -0.309017f, -0.500000f}, {0.864188f, -0.442863f, -0.238856f},  +{0.951056f, -0.162460f, -0.262866f}, {0.147621f, 0.716567f, -0.681718f},  +{0.309017f, 0.500000f, -0.809017f}, {0.425325f, 0.688191f, -0.587785f},  +{0.442863f, 0.238856f, -0.864188f}, {0.587785f, 0.425325f, -0.688191f},  +{0.688191f, 0.587785f, -0.425325f}, {-0.147621f, 0.716567f, -0.681718f},  +{-0.309017f, 0.500000f, -0.809017f}, {0.000000f, 0.525731f, -0.850651f},  +{-0.525731f, 0.000000f, -0.850651f}, {-0.442863f, 0.238856f, -0.864188f},  +{-0.295242f, 0.000000f, -0.955423f}, {-0.162460f, 0.262866f, -0.951056f},  +{0.000000f, 0.000000f, -1.000000f}, {0.295242f, 0.000000f, -0.955423f},  +{0.162460f, 0.262866f, -0.951056f}, {-0.442863f, -0.238856f, -0.864188f},  +{-0.309017f, -0.500000f, -0.809017f}, {-0.162460f, -0.262866f, -0.951056f},  +{0.000000f, -0.850651f, -0.525731f}, {-0.147621f, -0.716567f, -0.681718f},  +{0.147621f, -0.716567f, -0.681718f}, {0.000000f, -0.525731f, -0.850651f},  +{0.309017f, -0.500000f, -0.809017f}, {0.442863f, -0.238856f, -0.864188f},  +{0.162460f, -0.262866f, -0.951056f}, {0.238856f, -0.864188f, -0.442863f},  +{0.500000f, -0.809017f, -0.309017f}, {0.425325f, -0.688191f, -0.587785f},  +{0.716567f, -0.681718f, -0.147621f}, {0.688191f, -0.587785f, -0.425325f},  +{0.587785f, -0.425325f, -0.688191f}, {0.000000f, -0.955423f, -0.295242f},  +{0.000000f, -1.000000f, 0.000000f}, {0.262866f, -0.951056f, -0.162460f},  +{0.000000f, -0.850651f, 0.525731f}, {0.000000f, -0.955423f, 0.295242f},  +{0.238856f, -0.864188f, 0.442863f}, {0.262866f, -0.951056f, 0.162460f},  +{0.500000f, -0.809017f, 0.309017f}, {0.716567f, -0.681718f, 0.147621f},  +{0.525731f, -0.850651f, 0.000000f}, {-0.238856f, -0.864188f, -0.442863f},  +{-0.500000f, -0.809017f, -0.309017f}, {-0.262866f, -0.951056f, -0.162460f},  +{-0.850651f, -0.525731f, 0.000000f}, {-0.716567f, -0.681718f, -0.147621f},  +{-0.716567f, -0.681718f, 0.147621f}, {-0.525731f, -0.850651f, 0.000000f},  +{-0.500000f, -0.809017f, 0.309017f}, {-0.238856f, -0.864188f, 0.442863f},  +{-0.262866f, -0.951056f, 0.162460f}, {-0.864188f, -0.442863f, 0.238856f},  +{-0.809017f, -0.309017f, 0.500000f}, {-0.688191f, -0.587785f, 0.425325f},  +{-0.681718f, -0.147621f, 0.716567f}, {-0.442863f, -0.238856f, 0.864188f},  +{-0.587785f, -0.425325f, 0.688191f}, {-0.309017f, -0.500000f, 0.809017f},  +{-0.147621f, -0.716567f, 0.681718f}, {-0.425325f, -0.688191f, 0.587785f},  +{-0.162460f, -0.262866f, 0.951056f}, {0.442863f, -0.238856f, 0.864188f},  +{0.162460f, -0.262866f, 0.951056f}, {0.309017f, -0.500000f, 0.809017f},  +{0.147621f, -0.716567f, 0.681718f}, {0.000000f, -0.525731f, 0.850651f},  +{0.425325f, -0.688191f, 0.587785f}, {0.587785f, -0.425325f, 0.688191f},  +{0.688191f, -0.587785f, 0.425325f}, {-0.955423f, 0.295242f, 0.000000f},  +{-0.951056f, 0.162460f, 0.262866f}, {-1.000000f, 0.000000f, 0.000000f},  +{-0.850651f, 0.000000f, 0.525731f}, {-0.955423f, -0.295242f, 0.000000f},  +{-0.951056f, -0.162460f, 0.262866f}, {-0.864188f, 0.442863f, -0.238856f},  +{-0.951056f, 0.162460f, -0.262866f}, {-0.809017f, 0.309017f, -0.500000f},  +{-0.864188f, -0.442863f, -0.238856f}, {-0.951056f, -0.162460f, -0.262866f},  +{-0.809017f, -0.309017f, -0.500000f}, {-0.681718f, 0.147621f, -0.716567f},  +{-0.681718f, -0.147621f, -0.716567f}, {-0.850651f, 0.000000f, -0.525731f},  +{-0.688191f, 0.587785f, -0.425325f}, {-0.587785f, 0.425325f, -0.688191f},  +{-0.425325f, 0.688191f, -0.587785f}, {-0.425325f, -0.688191f, -0.587785f},  +{-0.587785f, -0.425325f, -0.688191f}, {-0.688191f, -0.587785f, -0.425325f} +}; + +//============================================================== + +int		Q_rand( int *seed ) { +	*seed = (69069 * *seed + 1); +	return *seed; +} + +float	Q_random( int *seed ) { +	return ( Q_rand( seed ) & 0xffff ) / (float)0x10000; +} + +float	Q_crandom( int *seed ) { +	return 2.0 * ( Q_random( seed ) - 0.5 ); +} + +//======================================================= + +signed char ClampChar( int i ) { +	if ( i < -128 ) { +		return -128; +	} +	if ( i > 127 ) { +		return 127; +	} +	return i; +} + +signed short ClampShort( int i ) { +	if ( i < -32768 ) { +		return -32768; +	} +	if ( i > 0x7fff ) { +		return 0x7fff; +	} +	return i; +} + + +// this isn't a real cheap function to call! +int DirToByte( vec3_t dir ) { +	int		i, best; +	float	d, bestd; + +	if ( !dir ) { +		return 0; +	} + +	bestd = 0; +	best = 0; +	for (i=0 ; i<NUMVERTEXNORMALS ; i++) +	{ +		d = DotProduct (dir, bytedirs[i]); +		if (d > bestd) +		{ +			bestd = d; +			best = i; +		} +	} + +	return best; +} + +void ByteToDir( int b, vec3_t dir ) { +	if ( b < 0 || b >= NUMVERTEXNORMALS ) { +		VectorCopy( vec3_origin, dir ); +		return; +	} +	VectorCopy (bytedirs[b], dir); +} + + +unsigned ColorBytes3 (float r, float g, float b) { +	unsigned	i; + +	( (byte *)&i )[0] = r * 255; +	( (byte *)&i )[1] = g * 255; +	( (byte *)&i )[2] = b * 255; + +	return i; +} + +unsigned ColorBytes4 (float r, float g, float b, float a) { +	unsigned	i; + +	( (byte *)&i )[0] = r * 255; +	( (byte *)&i )[1] = g * 255; +	( (byte *)&i )[2] = b * 255; +	( (byte *)&i )[3] = a * 255; + +	return i; +} + +float NormalizeColor( const vec3_t in, vec3_t out ) { +	float	max; +	 +	max = in[0]; +	if ( in[1] > max ) { +		max = in[1]; +	} +	if ( in[2] > max ) { +		max = in[2]; +	} + +	if ( !max ) { +		VectorClear( out ); +	} else { +		out[0] = in[0] / max; +		out[1] = in[1] / max; +		out[2] = in[2] / max; +	} +	return max; +} + + +/* +===================== +PlaneFromPoints + +Returns false if the triangle is degenrate. +The normal will point out of the clock for clockwise ordered points +===================== +*/ +qboolean PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c ) { +	vec3_t	d1, d2; + +	VectorSubtract( b, a, d1 ); +	VectorSubtract( c, a, d2 ); +	CrossProduct( d2, d1, plane ); +	if ( VectorNormalize( plane ) == 0 ) { +		return qfalse; +	} + +	plane[3] = DotProduct( a, plane ); +	return qtrue; +} + +/* +=============== +RotatePointAroundVector + +This is not implemented very well... +=============== +*/ +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, +							 float degrees ) { +	float sin_a; +	float cos_a; +	float cos_ia; +	float i_i_ia; +	float j_j_ia; +	float k_k_ia; +	float i_j_ia; +	float i_k_ia; +	float j_k_ia; +	float a_sin; +	float b_sin; +	float c_sin; +	float rot[3][3]; + +	cos_ia = DEG2RAD(degrees); +	sin_a = sin(cos_ia); +	cos_a = cos(cos_ia); +	cos_ia = 1.0F - cos_a; + +	i_i_ia = dir[0] * dir[0] * cos_ia; +	j_j_ia = dir[1] * dir[1] * cos_ia; +	k_k_ia = dir[2] * dir[2] * cos_ia; +	i_j_ia = dir[0] * dir[1] * cos_ia; +	i_k_ia = dir[0] * dir[2] * cos_ia; +	j_k_ia = dir[1] * dir[2] * cos_ia; + +	a_sin = dir[0] * sin_a; +	b_sin = dir[1] * sin_a; +	c_sin = dir[2] * sin_a; + +	rot[0][0] = i_i_ia + cos_a; +	rot[0][1] = i_j_ia - c_sin; +	rot[0][2] = i_k_ia + b_sin; +	rot[1][0] = i_j_ia + c_sin; +	rot[1][1] = j_j_ia + cos_a; +	rot[1][2] = j_k_ia - a_sin; +	rot[2][0] = i_k_ia - b_sin; +	rot[2][1] = j_k_ia + a_sin; +	rot[2][2] = k_k_ia + cos_a; + +	dst[0] = point[0] * rot[0][0] + point[1] * rot[0][1] + point[2] * rot[0][2]; +	dst[1] = point[0] * rot[1][0] + point[1] * rot[1][1] + point[2] * rot[1][2]; +	dst[2] = point[0] * rot[2][0] + point[1] * rot[2][1] + point[2] * rot[2][2]; +} + +/* +=============== +RotateAroundDirection +=============== +*/ +void RotateAroundDirection( vec3_t axis[3], vec_t angle ) { +	vec_t scale; + +	angle = DEG2RAD( angle ); + +	// create an arbitrary axis[1] +	PerpendicularVector( axis[ 1 ], axis[ 0 ] ); + +	// cross to get axis[2] +	CrossProduct( axis[ 0 ], axis[ 1 ], axis[ 2 ] ); + +	// rotate +	scale = cos( angle ); +	VectorScale( axis[ 1 ], scale, axis[ 1 ] ); + +	scale = sin( angle ); +	VectorMA( axis[ 1 ], scale, axis[ 2 ], axis[ 1 ] ); + +	// recalculate axis[2] +	CrossProduct( axis[ 0 ], axis[ 1 ], axis[ 2 ] ); +} + + + +void vectoangles( const vec3_t value1, vec3_t angles ) { +	float	forward; +	float	yaw, pitch; +	 +	if ( value1[1] == 0 && value1[0] == 0 ) { +		yaw = 0; +		if ( value1[2] > 0 ) { +			pitch = 90; +		} +		else { +			pitch = 270; +		} +	} +	else { +		if ( value1[0] ) { +			yaw = ( atan2 ( value1[1], value1[0] ) * 180 / M_PI ); +		} +		else if ( value1[1] > 0 ) { +			yaw = 90; +		} +		else { +			yaw = 270; +		} +		if ( yaw < 0 ) { +			yaw += 360; +		} + +		forward = sqrt ( value1[0]*value1[0] + value1[1]*value1[1] ); +		pitch = ( atan2(value1[2], forward) * 180 / M_PI ); +		if ( pitch < 0 ) { +			pitch += 360; +		} +	} + +	angles[PITCH] = -pitch; +	angles[YAW] = yaw; +	angles[ROLL] = 0; +} + + +/* +================= +AxisToAngles + +Takes an axis (forward + right + up) +and returns angles -- including a roll +================= +*/ +void AxisToAngles( vec3_t axis[3], vec3_t angles ) { +	float length1; +	float yaw, pitch, roll = 0.0f; + +	if ( axis[0][1] == 0 && axis[0][0] == 0 ) { +		yaw = 0; +		if ( axis[0][2] > 0 ) { +			pitch = 90; +		} +		else { +			pitch = 270; +		} +	} +	else { +		if ( axis[0][0] ) { +			yaw = ( atan2 ( axis[0][1], axis[0][0] ) * 180 / M_PI ); +		} +		else if ( axis[0][1] > 0 ) { +			yaw = 90; +		} +		else { +			yaw = 270; +		} +		if ( yaw < 0 ) { +			yaw += 360; +		} + +		length1 = sqrt ( axis[0][0]*axis[0][0] + axis[0][1]*axis[0][1] ); +		pitch = ( atan2(axis[0][2], length1) * 180 / M_PI ); +		if ( pitch < 0 ) { +			pitch += 360; +		} + +		roll = ( atan2( axis[1][2], axis[2][2] ) * 180 / M_PI ); +		if ( roll < 0 ) { +			roll += 360; +		} +	} + +	angles[PITCH] = -pitch; +	angles[YAW] = yaw; +	angles[ROLL] = roll; +} + +/* +================= +AnglesToAxis +================= +*/ +void AnglesToAxis( const vec3_t angles, vec3_t axis[3] ) { +	vec3_t	right; + +	// angle vectors returns "right" instead of "y axis" +	AngleVectors( angles, axis[0], right, axis[2] ); +	VectorSubtract( vec3_origin, right, axis[1] ); +} + +void AxisClear( vec3_t axis[3] ) { +	axis[0][0] = 1; +	axis[0][1] = 0; +	axis[0][2] = 0; +	axis[1][0] = 0; +	axis[1][1] = 1; +	axis[1][2] = 0; +	axis[2][0] = 0; +	axis[2][1] = 0; +	axis[2][2] = 1; +} + +void AxisCopy( vec3_t in[3], vec3_t out[3] ) { +	VectorCopy( in[0], out[0] ); +	VectorCopy( in[1], out[1] ); +	VectorCopy( in[2], out[2] ); +} + +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ) +{ +	float d; +	vec3_t n; +	float inv_denom; + +	inv_denom = 1.0f / DotProduct( normal, normal ); +#ifndef Q3_VM +	assert( Q_fabs(inv_denom) != 0.0f ); // bk010122 - zero vectors get here +#endif +	inv_denom = 1.0f / inv_denom; + +	d = DotProduct( normal, p ) * inv_denom; + +	n[0] = normal[0] * inv_denom; +	n[1] = normal[1] * inv_denom; +	n[2] = normal[2] * inv_denom; + +	dst[0] = p[0] - d * n[0]; +	dst[1] = p[1] - d * n[1]; +	dst[2] = p[2] - d * n[2]; +} + +/* +================ +MakeNormalVectors + +Given a normalized forward vector, create two +other perpendicular vectors +================ +*/ +void MakeNormalVectors( const vec3_t forward, vec3_t right, vec3_t up) { +	float		d; + +	// this rotate and negate guarantees a vector +	// not colinear with the original +	right[1] = -forward[0]; +	right[2] = forward[1]; +	right[0] = forward[2]; + +	d = DotProduct (right, forward); +	VectorMA (right, -d, forward, right); +	VectorNormalize (right); +	CrossProduct (right, forward, up); +} + + +void VectorRotate( vec3_t in, vec3_t matrix[3], vec3_t out ) +{ +	out[0] = DotProduct( in, matrix[0] ); +	out[1] = DotProduct( in, matrix[1] ); +	out[2] = DotProduct( in, matrix[2] ); +} + +//============================================================================ + +#if !idppc +/* +** float q_rsqrt( float number ) +*/ +float Q_rsqrt( float number ) +{ +	union { +		float f; +		int i; +	} t; +	float x2, y; +	const float threehalfs = 1.5F; + +	x2 = number * 0.5F; +	t.f  = number; +	t.i  = 0x5f3759df - ( t.i >> 1 );               // what the fuck? +	y  = t.f; +	y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration +//	y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed + +	//assert( !isnan(y) ); // bk010122 - FPE? +	return y; +} + +float Q_fabs( float f ) { +	int tmp = * ( int * ) &f; +	tmp &= 0x7FFFFFFF; +	return * ( float * ) &tmp; +} +#endif + +//============================================================ + +/* +=============== +LerpAngle + +=============== +*/ +float LerpAngle (float from, float to, float frac) { +	float	a; + +	if ( to - from > 180 ) { +		to -= 360; +	} +	if ( to - from < -180 ) { +		to += 360; +	} +	a = from + frac * (to - from); + +	return a; +} + + +/* +================= +AngleSubtract + +Always returns a value from -180 to 180 +================= +*/ +float	AngleSubtract( float a1, float a2 ) { +	float	a; + +	a = a1 - a2; +	while ( a > 180 ) { +		a -= 360; +	} +	while ( a < -180 ) { +		a += 360; +	} +	return a; +} + + +void AnglesSubtract( vec3_t v1, vec3_t v2, vec3_t v3 ) { +	v3[0] = AngleSubtract( v1[0], v2[0] ); +	v3[1] = AngleSubtract( v1[1], v2[1] ); +	v3[2] = AngleSubtract( v1[2], v2[2] ); +} + + +float	AngleMod(float a) { +	a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); +	return a; +} + + +/* +================= +AngleNormalize360 + +returns angle normalized to the range [0 <= angle < 360] +================= +*/ +float AngleNormalize360 ( float angle ) { +	return (360.0 / 65536) * ((int)(angle * (65536 / 360.0)) & 65535); +} + + +/* +================= +AngleNormalize180 + +returns angle normalized to the range [-180 < angle <= 180] +================= +*/ +float AngleNormalize180 ( float angle ) { +	angle = AngleNormalize360( angle ); +	if ( angle > 180.0 ) { +		angle -= 360.0; +	} +	return angle; +} + + +/* +================= +AngleDelta + +returns the normalized delta from angle1 to angle2 +================= +*/ +float AngleDelta ( float angle1, float angle2 ) { +	return AngleNormalize180( angle1 - angle2 ); +} + + +//============================================================ + + +/* +================= +SetPlaneSignbits +================= +*/ +void SetPlaneSignbits (cplane_t *out) { +	int	bits, j; + +	// for fast box on planeside test +	bits = 0; +	for (j=0 ; j<3 ; j++) { +		if (out->normal[j] < 0) { +			bits |= 1<<j; +		} +	} +	out->signbits = bits; +} + + +/* +================== +BoxOnPlaneSide + +Returns 1, 2, or 1 + 2 + +// this is the slow, general version +int BoxOnPlaneSide2 (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ +	int		i; +	float	dist1, dist2; +	int		sides; +	vec3_t	corners[2]; + +	for (i=0 ; i<3 ; i++) +	{ +		if (p->normal[i] < 0) +		{ +			corners[0][i] = emins[i]; +			corners[1][i] = emaxs[i]; +		} +		else +		{ +			corners[1][i] = emins[i]; +			corners[0][i] = emaxs[i]; +		} +	} +	dist1 = DotProduct (p->normal, corners[0]) - p->dist; +	dist2 = DotProduct (p->normal, corners[1]) - p->dist; +	sides = 0; +	if (dist1 >= 0) +		sides = 1; +	if (dist2 < 0) +		sides |= 2; + +	return sides; +} + +================== +*/ + +#if !id386 + +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ +	float	dist1, dist2; +	int		sides; + +// fast axial cases +	if (p->type < 3) +	{ +		if (p->dist <= emins[p->type]) +			return 1; +		if (p->dist >= emaxs[p->type]) +			return 2; +		return 3; +	} + +// general case +	switch (p->signbits) +	{ +	case 0: +		dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; +		dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; +		break; +	case 1: +		dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; +		dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; +		break; +	case 2: +		dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; +		dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; +		break; +	case 3: +		dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; +		dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; +		break; +	case 4: +		dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; +		dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; +		break; +	case 5: +		dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; +		dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; +		break; +	case 6: +		dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; +		dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; +		break; +	case 7: +		dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; +		dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; +		break; +	default: +		dist1 = dist2 = 0;		// shut up compiler +		break; +	} + +	sides = 0; +	if (dist1 >= p->dist) +		sides = 1; +	if (dist2 < p->dist) +		sides |= 2; + +	return sides; +} +#elif __GNUC__ +// use matha.s +#else +#pragma warning( disable: 4035 ) + +__declspec( naked ) int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ +	static int bops_initialized; +	static int Ljmptab[8]; + +	__asm { + +		push ebx +			 +		cmp bops_initialized, 1 +		je  initialized +		mov bops_initialized, 1 +		 +		mov Ljmptab[0*4], offset Lcase0 +		mov Ljmptab[1*4], offset Lcase1 +		mov Ljmptab[2*4], offset Lcase2 +		mov Ljmptab[3*4], offset Lcase3 +		mov Ljmptab[4*4], offset Lcase4 +		mov Ljmptab[5*4], offset Lcase5 +		mov Ljmptab[6*4], offset Lcase6 +		mov Ljmptab[7*4], offset Lcase7 +			 +initialized: + +		mov edx,dword ptr[4+12+esp] +		mov ecx,dword ptr[4+4+esp] +		xor eax,eax +		mov ebx,dword ptr[4+8+esp] +		mov al,byte ptr[17+edx] +		cmp al,8 +		jge Lerror +		fld dword ptr[0+edx] +		fld st(0) +		jmp dword ptr[Ljmptab+eax*4] +Lcase0: +		fmul dword ptr[ebx] +		fld dword ptr[0+4+edx] +		fxch st(2) +		fmul dword ptr[ecx] +		fxch st(2) +		fld st(0) +		fmul dword ptr[4+ebx] +		fld dword ptr[0+8+edx] +		fxch st(2) +		fmul dword ptr[4+ecx] +		fxch st(2) +		fld st(0) +		fmul dword ptr[8+ebx] +		fxch st(5) +		faddp st(3),st(0) +		fmul dword ptr[8+ecx] +		fxch st(1) +		faddp st(3),st(0) +		fxch st(3) +		faddp st(2),st(0) +		jmp LSetSides +Lcase1: +		fmul dword ptr[ecx] +		fld dword ptr[0+4+edx] +		fxch st(2) +		fmul dword ptr[ebx] +		fxch st(2) +		fld st(0) +		fmul dword ptr[4+ebx] +		fld dword ptr[0+8+edx] +		fxch st(2) +		fmul dword ptr[4+ecx] +		fxch st(2) +		fld st(0) +		fmul dword ptr[8+ebx] +		fxch st(5) +		faddp st(3),st(0) +		fmul dword ptr[8+ecx] +		fxch st(1) +		faddp st(3),st(0) +		fxch st(3) +		faddp st(2),st(0) +		jmp LSetSides +Lcase2: +		fmul dword ptr[ebx] +		fld dword ptr[0+4+edx] +		fxch st(2) +		fmul dword ptr[ecx] +		fxch st(2) +		fld st(0) +		fmul dword ptr[4+ecx] +		fld dword ptr[0+8+edx] +		fxch st(2) +		fmul dword ptr[4+ebx] +		fxch st(2) +		fld st(0) +		fmul dword ptr[8+ebx] +		fxch st(5) +		faddp st(3),st(0) +		fmul dword ptr[8+ecx] +		fxch st(1) +		faddp st(3),st(0) +		fxch st(3) +		faddp st(2),st(0) +		jmp LSetSides +Lcase3: +		fmul dword ptr[ecx] +		fld dword ptr[0+4+edx] +		fxch st(2) +		fmul dword ptr[ebx] +		fxch st(2) +		fld st(0) +		fmul dword ptr[4+ecx] +		fld dword ptr[0+8+edx] +		fxch st(2) +		fmul dword ptr[4+ebx] +		fxch st(2) +		fld st(0) +		fmul dword ptr[8+ebx] +		fxch st(5) +		faddp st(3),st(0) +		fmul dword ptr[8+ecx] +		fxch st(1) +		faddp st(3),st(0) +		fxch st(3) +		faddp st(2),st(0) +		jmp LSetSides +Lcase4: +		fmul dword ptr[ebx] +		fld dword ptr[0+4+edx] +		fxch st(2) +		fmul dword ptr[ecx] +		fxch st(2) +		fld st(0) +		fmul dword ptr[4+ebx] +		fld dword ptr[0+8+edx] +		fxch st(2) +		fmul dword ptr[4+ecx] +		fxch st(2) +		fld st(0) +		fmul dword ptr[8+ecx] +		fxch st(5) +		faddp st(3),st(0) +		fmul dword ptr[8+ebx] +		fxch st(1) +		faddp st(3),st(0) +		fxch st(3) +		faddp st(2),st(0) +		jmp LSetSides +Lcase5: +		fmul dword ptr[ecx] +		fld dword ptr[0+4+edx] +		fxch st(2) +		fmul dword ptr[ebx] +		fxch st(2) +		fld st(0) +		fmul dword ptr[4+ebx] +		fld dword ptr[0+8+edx] +		fxch st(2) +		fmul dword ptr[4+ecx] +		fxch st(2) +		fld st(0) +		fmul dword ptr[8+ecx] +		fxch st(5) +		faddp st(3),st(0) +		fmul dword ptr[8+ebx] +		fxch st(1) +		faddp st(3),st(0) +		fxch st(3) +		faddp st(2),st(0) +		jmp LSetSides +Lcase6: +		fmul dword ptr[ebx] +		fld dword ptr[0+4+edx] +		fxch st(2) +		fmul dword ptr[ecx] +		fxch st(2) +		fld st(0) +		fmul dword ptr[4+ecx] +		fld dword ptr[0+8+edx] +		fxch st(2) +		fmul dword ptr[4+ebx] +		fxch st(2) +		fld st(0) +		fmul dword ptr[8+ecx] +		fxch st(5) +		faddp st(3),st(0) +		fmul dword ptr[8+ebx] +		fxch st(1) +		faddp st(3),st(0) +		fxch st(3) +		faddp st(2),st(0) +		jmp LSetSides +Lcase7: +		fmul dword ptr[ecx] +		fld dword ptr[0+4+edx] +		fxch st(2) +		fmul dword ptr[ebx] +		fxch st(2) +		fld st(0) +		fmul dword ptr[4+ecx] +		fld dword ptr[0+8+edx] +		fxch st(2) +		fmul dword ptr[4+ebx] +		fxch st(2) +		fld st(0) +		fmul dword ptr[8+ecx] +		fxch st(5) +		faddp st(3),st(0) +		fmul dword ptr[8+ebx] +		fxch st(1) +		faddp st(3),st(0) +		fxch st(3) +		faddp st(2),st(0) +LSetSides: +		faddp st(2),st(0) +		fcomp dword ptr[12+edx] +		xor ecx,ecx +		fnstsw ax +		fcomp dword ptr[12+edx] +		and ah,1 +		xor ah,1 +		add cl,ah +		fnstsw ax +		and ah,1 +		add ah,ah +		add cl,ah +		pop ebx +		mov eax,ecx +		ret +Lerror: +		int 3 +	} +} +#pragma warning( default: 4035 ) + +#endif + +/* +================= +RadiusFromBounds +================= +*/ +float RadiusFromBounds( const vec3_t mins, const vec3_t maxs ) { +	int		i; +	vec3_t	corner; +	float	a, b; + +	for (i=0 ; i<3 ; i++) { +		a = fabs( mins[i] ); +		b = fabs( maxs[i] ); +		corner[i] = a > b ? a : b; +	} + +	return VectorLength (corner); +} + + +void ClearBounds( vec3_t mins, vec3_t maxs ) { +	mins[0] = mins[1] = mins[2] = 99999; +	maxs[0] = maxs[1] = maxs[2] = -99999; +} + +void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ) { +	if ( v[0] < mins[0] ) { +		mins[0] = v[0]; +	} +	if ( v[0] > maxs[0]) { +		maxs[0] = v[0]; +	} + +	if ( v[1] < mins[1] ) { +		mins[1] = v[1]; +	} +	if ( v[1] > maxs[1]) { +		maxs[1] = v[1]; +	} + +	if ( v[2] < mins[2] ) { +		mins[2] = v[2]; +	} +	if ( v[2] > maxs[2]) { +		maxs[2] = v[2]; +	} +} + + +vec_t VectorNormalize( vec3_t v ) { +	// NOTE: TTimo - Apple G4 altivec source uses double? +	float	length, ilength; + +	length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; +	length = sqrt (length); + +	if ( length ) { +		ilength = 1/length; +		v[0] *= ilength; +		v[1] *= ilength; +		v[2] *= ilength; +	} +		 +	return length; +} + +vec_t VectorNormalize2( const vec3_t v, vec3_t out) { +	float	length, ilength; + +	length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; +	length = sqrt (length); + +	if (length) +	{ +#ifndef Q3_VM // bk0101022 - FPE related +//	  assert( ((Q_fabs(v[0])!=0.0f) || (Q_fabs(v[1])!=0.0f) || (Q_fabs(v[2])!=0.0f)) ); +#endif +		ilength = 1/length; +		out[0] = v[0]*ilength; +		out[1] = v[1]*ilength; +		out[2] = v[2]*ilength; +	} else { +#ifndef Q3_VM // bk0101022 - FPE related +//	  assert( ((Q_fabs(v[0])==0.0f) && (Q_fabs(v[1])==0.0f) && (Q_fabs(v[2])==0.0f)) ); +#endif +		VectorClear( out ); +	} +		 +	return length; + +} + +void _VectorMA( const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc) { +	vecc[0] = veca[0] + scale*vecb[0]; +	vecc[1] = veca[1] + scale*vecb[1]; +	vecc[2] = veca[2] + scale*vecb[2]; +} + + +vec_t _DotProduct( const vec3_t v1, const vec3_t v2 ) { +	return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract( const vec3_t veca, const vec3_t vecb, vec3_t out ) { +	out[0] = veca[0]-vecb[0]; +	out[1] = veca[1]-vecb[1]; +	out[2] = veca[2]-vecb[2]; +} + +void _VectorAdd( const vec3_t veca, const vec3_t vecb, vec3_t out ) { +	out[0] = veca[0]+vecb[0]; +	out[1] = veca[1]+vecb[1]; +	out[2] = veca[2]+vecb[2]; +} + +void _VectorCopy( const vec3_t in, vec3_t out ) { +	out[0] = in[0]; +	out[1] = in[1]; +	out[2] = in[2]; +} + +void _VectorScale( const vec3_t in, vec_t scale, vec3_t out ) { +	out[0] = in[0]*scale; +	out[1] = in[1]*scale; +	out[2] = in[2]*scale; +} + +void Vector4Scale( const vec4_t in, vec_t scale, vec4_t out ) { +	out[0] = in[0]*scale; +	out[1] = in[1]*scale; +	out[2] = in[2]*scale; +	out[3] = in[3]*scale; +} + + +int Q_log2( int val ) { +	int answer; + +	answer = 0; +	while ( ( val>>=1 ) != 0 ) { +		answer++; +	} +	return answer; +} + + + +/* +================= +PlaneTypeForNormal +================= +*/ +/* +int	PlaneTypeForNormal (vec3_t normal) { +	if ( normal[0] == 1.0 ) +		return PLANE_X; +	if ( normal[1] == 1.0 ) +		return PLANE_Y; +	if ( normal[2] == 1.0 ) +		return PLANE_Z; +	 +	return PLANE_NON_AXIAL; +} +*/ + + +/* +================ +MatrixMultiply +================ +*/ +void MatrixMultiply(float in1[3][3], float in2[3][3], float out[3][3]) { +	out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + +				in1[0][2] * in2[2][0]; +	out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + +				in1[0][2] * in2[2][1]; +	out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + +				in1[0][2] * in2[2][2]; +	out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + +				in1[1][2] * in2[2][0]; +	out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + +				in1[1][2] * in2[2][1]; +	out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + +				in1[1][2] * in2[2][2]; +	out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + +				in1[2][2] * in2[2][0]; +	out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + +				in1[2][2] * in2[2][1]; +	out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + +				in1[2][2] * in2[2][2]; +} + +/* +================ +VectorMatrixMultiply +================ +*/ +void VectorMatrixMultiply( const vec3_t p, vec3_t m[ 3 ], vec3_t out ) +{ +	out[ 0 ] = m[ 0 ][ 0 ] * p[ 0 ] + m[ 1 ][ 0 ] * p[ 1 ] + m[ 2 ][ 0 ] * p[ 2 ]; +	out[ 1 ] = m[ 0 ][ 1 ] * p[ 0 ] + m[ 1 ][ 1 ] * p[ 1 ] + m[ 2 ][ 1 ] * p[ 2 ]; +	out[ 2 ] = m[ 0 ][ 2 ] * p[ 0 ] + m[ 1 ][ 2 ] * p[ 1 ] + m[ 2 ][ 2 ] * p[ 2 ]; +} + + +void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) { +	float		angle; +	static float		sr, sp, sy, cr, cp, cy; +	// static to help MS compiler fp bugs + +	angle = angles[YAW] * (M_PI*2 / 360); +	sy = sin(angle); +	cy = cos(angle); +	angle = angles[PITCH] * (M_PI*2 / 360); +	sp = sin(angle); +	cp = cos(angle); +	angle = angles[ROLL] * (M_PI*2 / 360); +	sr = sin(angle); +	cr = cos(angle); + +	if (forward) +	{ +		forward[0] = cp*cy; +		forward[1] = cp*sy; +		forward[2] = -sp; +	} +	if (right) +	{ +		right[0] = (-1*sr*sp*cy+-1*cr*-sy); +		right[1] = (-1*sr*sp*sy+-1*cr*cy); +		right[2] = -1*sr*cp; +	} +	if (up) +	{ +		up[0] = (cr*sp*cy+-sr*-sy); +		up[1] = (cr*sp*sy+-sr*cy); +		up[2] = cr*cp; +	} +} + +/* +** assumes "src" is normalized +*/ +void PerpendicularVector( vec3_t dst, const vec3_t src ) +{ +	int	pos; +	int i; +	float minelem = 1.0F; +	vec3_t tempvec; + +	/* +	** find the smallest magnitude axially aligned vector +	*/ +	for ( pos = 0, i = 0; i < 3; i++ ) +	{ +		if ( fabs( src[i] ) < minelem ) +		{ +			pos = i; +			minelem = fabs( src[i] ); +		} +	} +	tempvec[0] = tempvec[1] = tempvec[2] = 0.0F; +	tempvec[pos] = 1.0F; + +	/* +	** project the point onto the plane defined by src +	*/ +	ProjectPointOnPlane( dst, tempvec, src ); + +	/* +	** normalize the result +	*/ +	VectorNormalize( dst ); +} + +/* +================= +pointToLineDistance + +Distance from a point to some line +================= +*/ +float pointToLineDistance( const vec3_t p0, const vec3_t p1, const vec3_t p2 ) +{ +	vec3_t	v, w, y; +	float	 c1, c2; + +	VectorSubtract( p2, p1, v ); +	VectorSubtract( p1, p0, w ); + +	CrossProduct( w, v, y ); +	c1 = VectorLength( y ); +	c2 = VectorLength( v ); + +	if( c2 == 0.0f ) +		return 0.0f; +	else +		return c1 / c2; +} + +/* +================= +GetPerpendicularViewVector + +Used to find an "up" vector for drawing a sprite so that it always faces the view as best as possible +================= +*/ +void GetPerpendicularViewVector( const vec3_t point, const vec3_t p1, const vec3_t p2, vec3_t up ) +{ +	vec3_t	v1, v2; + +	VectorSubtract( point, p1, v1 ); +	VectorNormalize( v1 ); + +	VectorSubtract( point, p2, v2 ); +	VectorNormalize( v2 ); + +	CrossProduct( v1, v2, up ); +	VectorNormalize( up ); +} + +/* +================ +ProjectPointOntoVector +================ +*/ +void ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj ) +{ +	vec3_t pVec, vec; + +	VectorSubtract( point, vStart, pVec ); +	VectorSubtract( vEnd, vStart, vec ); +	VectorNormalize( vec ); +	// project onto the directional vector for this segment +	VectorMA( vStart, DotProduct( pVec, vec ), vec, vProj ); +} + +/* +================ +VectorMaxComponent + +Return the biggest component of some vector +================ +*/ +float VectorMaxComponent( vec3_t v ) +{ +	float biggest = v[ 0 ]; + +	if( v[ 1 ] > biggest ) +		biggest = v[ 1 ]; + +	if( v[ 2 ] > biggest ) +		biggest = v[ 2 ]; + +	return biggest; +} + +/* +================ +VectorMinComponent + +Return the smallest component of some vector +================ +*/ +float VectorMinComponent( vec3_t v ) +{ +	float smallest = v[ 0 ]; + +	if( v[ 1 ] < smallest ) +		smallest = v[ 1 ]; + +	if( v[ 2 ] < smallest ) +		smallest = v[ 2 ]; + +	return smallest; +} + + +#define LINE_DISTANCE_EPSILON 1e-05f + +/* +================ +DistanceBetweenLineSegmentsSquared + +Return the smallest distance between two line segments, squared +================ +*/ +vec_t DistanceBetweenLineSegmentsSquared( +    const vec3_t sP0, const vec3_t sP1, +    const vec3_t tP0, const vec3_t tP1, +    float *s, float *t ) +{ +  vec3_t  sMag, tMag, diff; +  float   a, b, c, d, e; +  float   D; +  float   sN, sD; +  float   tN, tD; +  vec3_t  separation; + +  VectorSubtract( sP1, sP0, sMag ); +  VectorSubtract( tP1, tP0, tMag ); +  VectorSubtract( sP0, tP0, diff ); +  a = DotProduct( sMag, sMag ); +  b = DotProduct( sMag, tMag ); +  c = DotProduct( tMag, tMag ); +  d = DotProduct( sMag, diff ); +  e = DotProduct( tMag, diff ); +  sD = tD = D = a * c - b * b; + +  if( D < LINE_DISTANCE_EPSILON ) +  { +    // the lines are almost parallel +    sN = 0.0;   // force using point P0 on segment S1 +    sD = 1.0;   // to prevent possible division by 0.0 later +    tN = e; +    tD = c; +  } +  else +  { +    // get the closest points on the infinite lines +    sN = ( b * e - c * d ); +    tN = ( a * e - b * d ); + +    if( sN < 0.0 ) +    { +      // sN < 0 => the s=0 edge is visible +      sN = 0.0; +      tN = e; +      tD = c; +    } +    else if( sN > sD ) +    { +      // sN > sD => the s=1 edge is visible +      sN = sD; +      tN = e + b; +      tD = c; +    } +  } + +  if( tN < 0.0 ) +  { +    // tN < 0 => the t=0 edge is visible +    tN = 0.0; + +    // recompute sN for this edge +    if( -d < 0.0 ) +      sN = 0.0; +    else if( -d > a ) +      sN = sD; +    else +    { +      sN = -d; +      sD = a; +    } +  } +  else if( tN > tD ) +  { +    // tN > tD => the t=1 edge is visible +    tN = tD; + +    // recompute sN for this edge +    if( ( -d + b ) < 0.0 ) +      sN = 0; +    else if( ( -d + b ) > a ) +      sN = sD; +    else +    { +      sN = ( -d + b ); +      sD = a; +    } +  } + +  // finally do the division to get *s and *t +  *s = ( fabs( sN ) < LINE_DISTANCE_EPSILON ? 0.0 : sN / sD ); +  *t = ( fabs( tN ) < LINE_DISTANCE_EPSILON ? 0.0 : tN / tD ); + +  // get the difference of the two closest points +  VectorScale( sMag, *s, sMag ); +  VectorScale( tMag, *t, tMag ); +  VectorAdd( diff, sMag, separation ); +  VectorSubtract( separation, tMag, separation ); + +  return VectorLengthSquared( separation ); +} + +/* +================ +DistanceBetweenLineSegments + +Return the smallest distance between two line segments +================ +*/ +vec_t DistanceBetweenLineSegments( +    const vec3_t sP0, const vec3_t sP1, +    const vec3_t tP0, const vec3_t tP1, +    float *s, float *t ) +{ +  return (vec_t)sqrt( DistanceBetweenLineSegmentsSquared( +        sP0, sP1, tP0, tP1, s, t ) ); +} + +/* +================= +Q_isnan + +Don't pass doubles to this +================ +*/ +int Q_isnan( float x ) +{ +	union +	{ +		float f; +		unsigned int i; +	} t; + +	t.f = x; +	t.i &= 0x7FFFFFFF; +	t.i = 0x7F800000 - t.i; + +	return (int)( (unsigned int)t.i >> 31 ); +} diff --git a/src/qcommon/q_platform.h b/src/qcommon/q_platform.h new file mode 100644 index 0000000..ad9a938 --- /dev/null +++ b/src/qcommon/q_platform.h @@ -0,0 +1,335 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ +// +#ifndef __Q_PLATFORM_H +#define __Q_PLATFORM_H + +// this is for determining if we have an asm version of a C function +#ifdef Q3_VM + +#define id386 0 +#define idppc 0 +#define idppc_altivec 0 + +#else + +#if (defined _M_IX86 || defined __i386__) && !defined(C_ONLY) +#define id386 1 +#else +#define id386 0 +#endif + +#if (defined(powerc) || defined(powerpc) || defined(ppc) || \ +	defined(__ppc) || defined(__ppc__)) && !defined(C_ONLY) +#define idppc 1 +#if defined(__VEC__) +#define idppc_altivec 1 +#ifdef MACOS_X  // Apple's GCC does this differently than the FSF. +#define VECCONST_UINT8(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) \ +	(vector unsigned char) (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) +#else +#define VECCONST_UINT8(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) \ +	(vector unsigned char) {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p} +#endif +#else +#define idppc_altivec 0 +#endif +#else +#define idppc 0 +#define idppc_altivec 0 +#endif + +#endif + +#ifndef __ASM_I386__ // don't include the C bits if included from qasm.h + +// for windows fastcall option +#define QDECL + +//================================================================= WIN32 === + +#ifdef _WIN32 + +#undef QDECL +#define QDECL __cdecl + +#if defined( _MSC_VER ) +#define OS_STRING "win_msvc" +#elif defined __MINGW32__ +#define OS_STRING "win_mingw" +#endif + +#define ID_INLINE __inline +#define PATH_SEP '\\' + +#if defined( _M_IX86 ) || defined( __i386__ ) +#define ARCH_STRING "x86" +#elif defined _M_ALPHA +#define ARCH_STRING "AXP" +#endif + +#define Q3_LITTLE_ENDIAN + +#define DLL_EXT ".dll" + +#endif + +//============================================================== MAC OS X === + +#if defined(MACOS_X) || defined(__APPLE_CC__) + +// make sure this is defined, just for sanity's sake... +#ifndef MACOS_X +#define MACOS_X +#endif + +#define OS_STRING "macosx" +#define ID_INLINE inline +#define PATH_SEP '/' + +#ifdef __ppc__ +#define ARCH_STRING "ppc" +#define Q3_BIG_ENDIAN +#elif defined __i386__ +#define ARCH_STRING "x86" +#define Q3_LITTLE_ENDIAN +#endif + +#define DLL_EXT ".dylib" + +#endif + +//================================================================= LINUX === + +#ifdef __linux__ + +#define OS_STRING "linux" +#define ID_INLINE inline +#define PATH_SEP '/' + +#if defined __i386__ +#define ARCH_STRING "x86" +#elif defined __x86_64__ +#define ARCH_STRING "x86_64" +#elif defined __powerpc64__ +#define ARCH_STRING "ppc64" +#elif defined __powerpc__ +#define ARCH_STRING "ppc" +#elif defined __s390__ +#define ARCH_STRING "s390" +#elif defined __s390x__ +#define ARCH_STRING "s390x" +#elif defined __ia64__ +#define ARCH_STRING "ia64" +#elif defined __alpha__ +#define ARCH_STRING "alpha" +#elif defined __sparc__ +#define ARCH_STRING "sparc" +#elif defined __arm__ +#define ARCH_STRING "arm" +#elif defined __cris__ +#define ARCH_STRING "cris" +#elif defined __hppa__ +#define ARCH_STRING "hppa" +#elif defined __mips__ +#define ARCH_STRING "mips" +#elif defined __sh__ +#define ARCH_STRING "sh" +#endif + +#if __FLOAT_WORD_ORDER == __BIG_ENDIAN +#define Q3_BIG_ENDIAN +#else +#define Q3_LITTLE_ENDIAN +#endif + +#define DLL_EXT ".so" + +#endif + +//=============================================================== FreeBSD === + +#ifdef __FreeBSD__ // rb010123 + +#include <machine/endian.h> + +#define OS_STRING "freebsd" +#define ID_INLINE inline +#define PATH_SEP '/' + +#ifdef __i386__ +#define ARCH_STRING "x86" +#elif defined __axp__ +#define ARCH_STRING "alpha" +#endif + +#if BYTE_ORDER == BIG_ENDIAN +#define Q3_BIG_ENDIAN +#else +#define Q3_LITTLE_ENDIAN +#endif + +#define DLL_EXT ".so" + +#endif + +//================================================================ NetBSD === + +// This is very much like the FreeBSD one and can probably be merged +#ifdef __NetBSD__ + +#include <machine/endian.h> + +#define OS_STRING "netbsd" +#define ID_INLINE inline +#define PATH_SEP '/' + +#ifdef __i386__ +#define ARCH_STRING "x86" +// Netbsd has alot of platforms +#endif + +#if BYTE_ORDER == BIG_ENDIAN +#define Q3_BIG_ENDIAN +#else +#define Q3_LITTLE_ENDIAN +#endif + +#define DLL_EXT ".so" + +#endif + +//================================================================= SUNOS === + +#ifdef __sun + +#include <stdint.h> +#include <sys/byteorder.h> + +#define OS_STRING "solaris" +#define ID_INLINE inline +#define PATH_SEP '/' + +#ifdef __i386__ +#define ARCH_STRING "x86" +#elif defined __sparc +#define ARCH_STRING "sparc" +#endif + +#if defined( _BIG_ENDIAN ) +#define Q3_BIG_ENDIAN +#elif defined( _LITTLE_ENDIAN ) +#define Q3_LITTLE_ENDIAN +#endif + +#define DLL_EXT ".so" + +#endif + +//================================================================== Q3VM === + +#ifdef Q3_VM + +#define OS_STRING "q3vm" +#define ID_INLINE +#define PATH_SEP '/' + +#define ARCH_STRING "bytecode" + +#define DLL_EXT ".qvm" + +#endif + +//=========================================================================== + +//catch missing defines in above blocks +#if !defined( OS_STRING ) +#error "Operating system not supported" +#endif + +#if !defined( ARCH_STRING ) +#error "Architecture not supported" +#endif + +#ifndef ID_INLINE +#error "ID_INLINE not defined" +#endif + +#ifndef PATH_SEP +#error "PATH_SEP not defined" +#endif + +#ifndef DLL_EXT +#error "DLL_EXT not defined" +#endif + + +//endianness +short ShortSwap (short l); +int LongSwap (int l); +float FloatSwap (const float *f); + +#if defined( Q3_BIG_ENDIAN ) && defined( Q3_LITTLE_ENDIAN ) +#error "Endianness defined as both big and little" +#elif defined( Q3_BIG_ENDIAN ) + +#define LittleShort(x) ShortSwap(x) +#define LittleLong(x) LongSwap(x) +#define LittleFloat(x) FloatSwap(&x) +#define BigShort +#define BigLong +#define BigFloat + +#elif defined( Q3_LITTLE_ENDIAN ) + +#define LittleShort +#define LittleLong +#define LittleFloat +#define BigShort(x) ShortSwap(x) +#define BigLong(x) LongSwap(x) +#define BigFloat(x) FloatSwap(&x) + +#elif defined( Q3_VM ) + +#define LittleShort +#define LittleLong +#define LittleFloat +#define BigShort +#define BigLong +#define BigFloat + +#else +#error "Endianness not defined" +#endif + + +//platform string +#ifdef NDEBUG +#define PLATFORM_STRING OS_STRING "-" ARCH_STRING +#else +#define PLATFORM_STRING OS_STRING "-" ARCH_STRING "-debug" +#endif + +#endif + +#endif diff --git a/src/qcommon/q_shared.c b/src/qcommon/q_shared.c new file mode 100644 index 0000000..436a0ca --- /dev/null +++ b/src/qcommon/q_shared.c @@ -0,0 +1,1368 @@ +/*
 +===========================================================================
 +Copyright (C) 1999-2005 Id Software, Inc.
 +Copyright (C) 2000-2006 Tim Angus
 +
 +This file is part of Tremulous.
 +
 +Tremulous is free software; you can redistribute it
 +and/or modify it under the terms of the GNU General Public License as
 +published by the Free Software Foundation; either version 2 of the License,
 +or (at your option) any later version.
 +
 +Tremulous is distributed in the hope that it will be
 +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +GNU General Public License for more details.
 +
 +You should have received a copy of the GNU General Public License
 +along with Tremulous; if not, write to the Free Software
 +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 +===========================================================================
 +*/
 +//
 +// q_shared.c -- stateless support routines that are included in each code dll
 +#include "q_shared.h"
 +
 +float Com_Clamp( float min, float max, float value ) {
 +	if ( value < min ) {
 +		return min;
 +	}
 +	if ( value > max ) {
 +		return max;
 +	}
 +	return value;
 +}
 +
 +
 +/*
 +============
 +COM_SkipPath
 +============
 +*/
 +char *COM_SkipPath (char *pathname)
 +{
 +	char	*last;
 +	
 +	last = pathname;
 +	while (*pathname)
 +	{
 +		if (*pathname=='/')
 +			last = pathname+1;
 +		pathname++;
 +	}
 +	return last;
 +}
 +
 +/*
 +============
 +COM_StripExtension
 +============
 +*/
 +void COM_StripExtension( const char *in, char *out, int destsize ) {
 +	int             length;
 +
 +	Q_strncpyz(out, in, destsize);
 +
 +	length = strlen(out)-1;
 +	while (length > 0 && out[length] != '.')
 +	{
 +		length--;
 +		if (out[length] == '/')
 +			return;		// no extension
 +	}
 +	if (length)
 +		out[length] = 0;
 +}
 +
 +
 +/*
 +==================
 +COM_DefaultExtension
 +==================
 +*/
 +void COM_DefaultExtension (char *path, int maxSize, const char *extension ) {
 +	char	oldPath[MAX_QPATH];
 +	char    *src;
 +
 +//
 +// if path doesn't have a .EXT, append extension
 +// (extension should include the .)
 +//
 +	src = path + strlen(path) - 1;
 +
 +	while (*src != '/' && src != path) {
 +		if ( *src == '.' ) {
 +			return;                 // it has an extension
 +		}
 +		src--;
 +	}
 +
 +	Q_strncpyz( oldPath, path, sizeof( oldPath ) );
 +	Com_sprintf( path, maxSize, "%s%s", oldPath, extension );
 +}
 +
 +/*
 +============================================================================
 +
 +					BYTE ORDER FUNCTIONS
 +
 +============================================================================
 +*/
 +/*
 +// can't just use function pointers, or dll linkage can
 +// mess up when qcommon is included in multiple places
 +static short	(*_BigShort) (short l);
 +static short	(*_LittleShort) (short l);
 +static int		(*_BigLong) (int l);
 +static int		(*_LittleLong) (int l);
 +static qint64	(*_BigLong64) (qint64 l);
 +static qint64	(*_LittleLong64) (qint64 l);
 +static float	(*_BigFloat) (const float *l);
 +static float	(*_LittleFloat) (const float *l);
 +
 +short	BigShort(short l){return _BigShort(l);}
 +short	LittleShort(short l) {return _LittleShort(l);}
 +int		BigLong (int l) {return _BigLong(l);}
 +int		LittleLong (int l) {return _LittleLong(l);}
 +qint64 	BigLong64 (qint64 l) {return _BigLong64(l);}
 +qint64 	LittleLong64 (qint64 l) {return _LittleLong64(l);}
 +float	BigFloat (const float *l) {return _BigFloat(l);}
 +float	LittleFloat (const float *l) {return _LittleFloat(l);}
 +*/
 +
 +short   ShortSwap (short l)
 +{
 +	byte    b1,b2;
 +
 +	b1 = l&255;
 +	b2 = (l>>8)&255;
 +
 +	return (b1<<8) + b2;
 +}
 +
 +short	ShortNoSwap (short l)
 +{
 +	return l;
 +}
 +
 +int    LongSwap (int l)
 +{
 +	byte    b1,b2,b3,b4;
 +
 +	b1 = l&255;
 +	b2 = (l>>8)&255;
 +	b3 = (l>>16)&255;
 +	b4 = (l>>24)&255;
 +
 +	return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4;
 +}
 +
 +int	LongNoSwap (int l)
 +{
 +	return l;
 +}
 +
 +qint64 Long64Swap (qint64 ll)
 +{
 +	qint64	result;
 +
 +	result.b0 = ll.b7;
 +	result.b1 = ll.b6;
 +	result.b2 = ll.b5;
 +	result.b3 = ll.b4;
 +	result.b4 = ll.b3;
 +	result.b5 = ll.b2;
 +	result.b6 = ll.b1;
 +	result.b7 = ll.b0;
 +
 +	return result;
 +}
 +
 +qint64 Long64NoSwap (qint64 ll)
 +{
 +	return ll;
 +}
 +
 +typedef union {
 +    float	f;
 +    unsigned int i;
 +} _FloatByteUnion;
 +
 +float FloatSwap (const float *f) {
 +	_FloatByteUnion out;
 +
 +	out.f = *f;
 +	out.i = LongSwap(out.i);
 +
 +	return out.f;
 +}
 +
 +float FloatNoSwap (const float *f)
 +{
 +	return *f;
 +}
 +
 +/*
 +================
 +Swap_Init
 +================
 +*/
 +/*
 +void Swap_Init (void)
 +{
 +	byte	swaptest[2] = {1,0};
 +
 +// set the byte swapping variables in a portable manner	
 +	if ( *(short *)swaptest == 1)
 +	{
 +		_BigShort = ShortSwap;
 +		_LittleShort = ShortNoSwap;
 +		_BigLong = LongSwap;
 +		_LittleLong = LongNoSwap;
 +		_BigLong64 = Long64Swap;
 +		_LittleLong64 = Long64NoSwap;
 +		_BigFloat = FloatSwap;
 +		_LittleFloat = FloatNoSwap;
 +	}
 +	else
 +	{
 +		_BigShort = ShortNoSwap;
 +		_LittleShort = ShortSwap;
 +		_BigLong = LongNoSwap;
 +		_LittleLong = LongSwap;
 +		_BigLong64 = Long64NoSwap;
 +		_LittleLong64 = Long64Swap;
 +		_BigFloat = FloatNoSwap;
 +		_LittleFloat = FloatSwap;
 +	}
 +
 +}
 +*/
 +
 +/*
 +============================================================================
 +
 +PARSING
 +
 +============================================================================
 +*/
 +
 +static	char	com_token[MAX_TOKEN_CHARS];
 +static	char	com_parsename[MAX_TOKEN_CHARS];
 +static	int		com_lines;
 +
 +void COM_BeginParseSession( const char *name )
 +{
 +	com_lines = 0;
 +	Com_sprintf(com_parsename, sizeof(com_parsename), "%s", name);
 +}
 +
 +int COM_GetCurrentParseLine( void )
 +{
 +	return com_lines;
 +}
 +
 +char *COM_Parse( char **data_p )
 +{
 +	return COM_ParseExt( data_p, qtrue );
 +}
 +
 +void COM_ParseError( char *format, ... )
 +{
 +	va_list argptr;
 +	static char string[4096];
 +
 +	va_start (argptr, format);
 +	vsprintf (string, format, argptr);
 +	va_end (argptr);
 +
 +	Com_Printf("ERROR: %s, line %d: %s\n", com_parsename, com_lines, string);
 +}
 +
 +void COM_ParseWarning( char *format, ... )
 +{
 +	va_list argptr;
 +	static char string[4096];
 +
 +	va_start (argptr, format);
 +	vsprintf (string, format, argptr);
 +	va_end (argptr);
 +
 +	Com_Printf("WARNING: %s, line %d: %s\n", com_parsename, com_lines, string);
 +}
 +
 +/*
 +==============
 +COM_Parse
 +
 +Parse a token out of a string
 +Will never return NULL, just empty strings
 +
 +If "allowLineBreaks" is qtrue then an empty
 +string will be returned if the next token is
 +a newline.
 +==============
 +*/
 +static char *SkipWhitespace( char *data, qboolean *hasNewLines ) {
 +	int c;
 +
 +	while( (c = *data) <= ' ') {
 +		if( !c ) {
 +			return NULL;
 +		}
 +		if( c == '\n' ) {
 +			com_lines++;
 +			*hasNewLines = qtrue;
 +		}
 +		data++;
 +	}
 +
 +	return data;
 +}
 +
 +int COM_Compress( char *data_p ) {
 +	char *in, *out;
 +	int c;
 +	qboolean newline = qfalse, whitespace = qfalse;
 +
 +	in = out = data_p;
 +	if (in) {
 +		while ((c = *in) != 0) {
 +			// skip double slash comments
 +			if ( c == '/' && in[1] == '/' ) {
 +				while (*in && *in != '\n') {
 +					in++;
 +				}
 +			// skip /* */ comments
 +			} else if ( c == '/' && in[1] == '*' ) {
 +				while ( *in && ( *in != '*' || in[1] != '/' ) ) 
 +					in++;
 +				if ( *in ) 
 +					in += 2;
 +                        // record when we hit a newline
 +                        } else if ( c == '\n' || c == '\r' ) {
 +                            newline = qtrue;
 +                            in++;
 +                        // record when we hit whitespace
 +                        } else if ( c == ' ' || c == '\t') {
 +                            whitespace = qtrue;
 +                            in++;
 +                        // an actual token
 +			} else {
 +                            // if we have a pending newline, emit it (and it counts as whitespace)
 +                            if (newline) {
 +                                *out++ = '\n';
 +                                newline = qfalse;
 +                                whitespace = qfalse;
 +                            } if (whitespace) {
 +                                *out++ = ' ';
 +                                whitespace = qfalse;
 +                            }
 +                            
 +                            // copy quoted strings unmolested
 +                            if (c == '"') {
 +                                    *out++ = c;
 +                                    in++;
 +                                    while (1) {
 +                                        c = *in;
 +                                        if (c && c != '"') {
 +                                            *out++ = c;
 +                                            in++;
 +                                        } else {
 +                                            break;
 +                                        }
 +                                    }
 +                                    if (c == '"') {
 +                                        *out++ = c;
 +                                        in++;
 +                                    }
 +                            } else {
 +                                *out = c;
 +                                out++;
 +                                in++;
 +                            }
 +			}
 +		}
 +	}
 +	*out = 0;
 +	return out - data_p;
 +}
 +
 +char *COM_ParseExt( char **data_p, qboolean allowLineBreaks )
 +{
 +	int c = 0, len;
 +	qboolean hasNewLines = qfalse;
 +	char *data;
 +
 +	data = *data_p;
 +	len = 0;
 +	com_token[0] = 0;
 +
 +	// make sure incoming data is valid
 +	if ( !data )
 +	{
 +		*data_p = NULL;
 +		return com_token;
 +	}
 +
 +	while ( 1 )
 +	{
 +		// skip whitespace
 +		data = SkipWhitespace( data, &hasNewLines );
 +		if ( !data )
 +		{
 +			*data_p = NULL;
 +			return com_token;
 +		}
 +		if ( hasNewLines && !allowLineBreaks )
 +		{
 +			*data_p = data;
 +			return com_token;
 +		}
 +
 +		c = *data;
 +
 +		// skip double slash comments
 +		if ( c == '/' && data[1] == '/' )
 +		{
 +			data += 2;
 +			while (*data && *data != '\n') {
 +				data++;
 +			}
 +		}
 +		// skip /* */ comments
 +		else if ( c=='/' && data[1] == '*' ) 
 +		{
 +			data += 2;
 +			while ( *data && ( *data != '*' || data[1] != '/' ) ) 
 +			{
 +				data++;
 +			}
 +			if ( *data ) 
 +			{
 +				data += 2;
 +			}
 +		}
 +		else
 +		{
 +			break;
 +		}
 +	}
 +
 +	// handle quoted strings
 +	if (c == '\"')
 +	{
 +		data++;
 +		while (1)
 +		{
 +			c = *data++;
 +			if (c=='\"' || !c)
 +			{
 +				com_token[len] = 0;
 +				*data_p = ( char * ) data;
 +				return com_token;
 +			}
 +			if (len < MAX_TOKEN_CHARS - 1)
 +			{
 +				com_token[len] = c;
 +				len++;
 +			}
 +		}
 +	}
 +
 +	// parse a regular word
 +	do
 +	{
 +		if (len < MAX_TOKEN_CHARS - 1)
 +		{
 +			com_token[len] = c;
 +			len++;
 +		}
 +		data++;
 +		c = *data;
 +		if ( c == '\n' )
 +			com_lines++;
 +	} while (c>32);
 +
 +	com_token[len] = 0;
 +
 +	*data_p = ( char * ) data;
 +	return com_token;
 +}
 +
 +
 +#if 0
 +// no longer used
 +/*
 +===============
 +COM_ParseInfos
 +===============
 +*/
 +int COM_ParseInfos( char *buf, int max, char infos[][MAX_INFO_STRING] ) {
 +	char	*token;
 +	int		count;
 +	char	key[MAX_TOKEN_CHARS];
 +
 +	count = 0;
 +
 +	while ( 1 ) {
 +		token = COM_Parse( &buf );
 +		if ( !token[0] ) {
 +			break;
 +		}
 +		if ( strcmp( token, "{" ) ) {
 +			Com_Printf( "Missing { in info file\n" );
 +			break;
 +		}
 +
 +		if ( count == max ) {
 +			Com_Printf( "Max infos exceeded\n" );
 +			break;
 +		}
 +
 +		infos[count][0] = 0;
 +		while ( 1 ) {
 +			token = COM_ParseExt( &buf, qtrue );
 +			if ( !token[0] ) {
 +				Com_Printf( "Unexpected end of info file\n" );
 +				break;
 +			}
 +			if ( !strcmp( token, "}" ) ) {
 +				break;
 +			}
 +			Q_strncpyz( key, token, sizeof( key ) );
 +
 +			token = COM_ParseExt( &buf, qfalse );
 +			if ( !token[0] ) {
 +				strcpy( token, "<NULL>" );
 +			}
 +			Info_SetValueForKey( infos[count], key, token );
 +		}
 +		count++;
 +	}
 +
 +	return count;
 +}
 +#endif
 +
 +
 +/*
 +==================
 +COM_MatchToken
 +==================
 +*/
 +void COM_MatchToken( char **buf_p, char *match ) {
 +	char	*token;
 +
 +	token = COM_Parse( buf_p );
 +	if ( strcmp( token, match ) ) {
 +		Com_Error( ERR_DROP, "MatchToken: %s != %s", token, match );
 +	}
 +}
 +
 +
 +/*
 +=================
 +SkipBracedSection
 +
 +The next token should be an open brace.
 +Skips until a matching close brace is found.
 +Internal brace depths are properly skipped.
 +=================
 +*/
 +void SkipBracedSection (char **program) {
 +	char			*token;
 +	int				depth;
 +
 +	depth = 0;
 +	do {
 +		token = COM_ParseExt( program, qtrue );
 +		if( token[1] == 0 ) {
 +			if( token[0] == '{' ) {
 +				depth++;
 +			}
 +			else if( token[0] == '}' ) {
 +				depth--;
 +			}
 +		}
 +	} while( depth && *program );
 +}
 +
 +/*
 +=================
 +SkipRestOfLine
 +=================
 +*/
 +void SkipRestOfLine ( char **data ) {
 +	char	*p;
 +	int		c;
 +
 +	p = *data;
 +	while ( (c = *p++) != 0 ) {
 +		if ( c == '\n' ) {
 +			com_lines++;
 +			break;
 +		}
 +	}
 +
 +	*data = p;
 +}
 +
 +
 +void Parse1DMatrix (char **buf_p, int x, float *m) {
 +	char	*token;
 +	int		i;
 +
 +	COM_MatchToken( buf_p, "(" );
 +
 +	for (i = 0 ; i < x ; i++) {
 +		token = COM_Parse(buf_p);
 +		m[i] = atof(token);
 +	}
 +
 +	COM_MatchToken( buf_p, ")" );
 +}
 +
 +void Parse2DMatrix (char **buf_p, int y, int x, float *m) {
 +	int		i;
 +
 +	COM_MatchToken( buf_p, "(" );
 +
 +	for (i = 0 ; i < y ; i++) {
 +		Parse1DMatrix (buf_p, x, m + i * x);
 +	}
 +
 +	COM_MatchToken( buf_p, ")" );
 +}
 +
 +void Parse3DMatrix (char **buf_p, int z, int y, int x, float *m) {
 +	int		i;
 +
 +	COM_MatchToken( buf_p, "(" );
 +
 +	for (i = 0 ; i < z ; i++) {
 +		Parse2DMatrix (buf_p, y, x, m + i * x*y);
 +	}
 +
 +	COM_MatchToken( buf_p, ")" );
 +}
 +
 +
 +/*
 +============================================================================
 +
 +					LIBRARY REPLACEMENT FUNCTIONS
 +
 +============================================================================
 +*/
 +
 +int Q_isprint( int c )
 +{
 +	if ( c >= 0x20 && c <= 0x7E )
 +		return ( 1 );
 +	return ( 0 );
 +}
 +
 +int Q_islower( int c )
 +{
 +	if (c >= 'a' && c <= 'z')
 +		return ( 1 );
 +	return ( 0 );
 +}
 +
 +int Q_isupper( int c )
 +{
 +	if (c >= 'A' && c <= 'Z')
 +		return ( 1 );
 +	return ( 0 );
 +}
 +
 +int Q_isalpha( int c )
 +{
 +	if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
 +		return ( 1 );
 +	return ( 0 );
 +}
 +
 +int Q_isdigit( int c )
 +{
 +	if ((c >= '0' && c <= '9'))
 +		return ( 1 );
 +	return ( 0 );
 +}
 +
 +char* Q_strrchr( const char* string, int c )
 +{
 +	char cc = c;
 +	char *s;
 +	char *sp=(char *)0;
 +
 +	s = (char*)string;
 +
 +	while (*s)
 +	{
 +		if (*s == cc)
 +			sp = s;
 +		s++;
 +	}
 +	if (cc == 0)
 +		sp = s;
 +
 +	return sp;
 +}
 +
 +/*
 +=============
 +Q_strncpyz
 + 
 +Safe strncpy that ensures a trailing zero
 +=============
 +*/
 +void Q_strncpyz( char *dest, const char *src, int destsize ) {
 +  // bk001129 - also NULL dest
 +  if ( !dest ) {
 +    Com_Error( ERR_FATAL, "Q_strncpyz: NULL dest" );
 +  }
 +	if ( !src ) {
 +		Com_Error( ERR_FATAL, "Q_strncpyz: NULL src" );
 +	}
 +	if ( destsize < 1 ) {
 +		Com_Error(ERR_FATAL,"Q_strncpyz: destsize < 1" ); 
 +	}
 +
 +	strncpy( dest, src, destsize-1 );
 +  dest[destsize-1] = 0;
 +}
 +                 
 +int Q_stricmpn (const char *s1, const char *s2, int n) {
 +	int		c1, c2;
 +
 +	// bk001129 - moved in 1.17 fix not in id codebase
 +        if ( s1 == NULL ) {
 +           if ( s2 == NULL )
 +             return 0;
 +           else
 +             return -1;
 +        }
 +        else if ( s2==NULL )
 +          return 1;
 +
 +
 +	
 +	do {
 +		c1 = *s1++;
 +		c2 = *s2++;
 +
 +		if (!n--) {
 +			return 0;		// strings are equal until end point
 +		}
 +		
 +		if (c1 != c2) {
 +			if (c1 >= 'a' && c1 <= 'z') {
 +				c1 -= ('a' - 'A');
 +			}
 +			if (c2 >= 'a' && c2 <= 'z') {
 +				c2 -= ('a' - 'A');
 +			}
 +			if (c1 != c2) {
 +				return c1 < c2 ? -1 : 1;
 +			}
 +		}
 +	} while (c1);
 +	
 +	return 0;		// strings are equal
 +}
 +
 +int Q_strncmp (const char *s1, const char *s2, int n) {
 +	int		c1, c2;
 +	
 +	do {
 +		c1 = *s1++;
 +		c2 = *s2++;
 +
 +		if (!n--) {
 +			return 0;		// strings are equal until end point
 +		}
 +		
 +		if (c1 != c2) {
 +			return c1 < c2 ? -1 : 1;
 +		}
 +	} while (c1);
 +	
 +	return 0;		// strings are equal
 +}
 +
 +int Q_stricmp (const char *s1, const char *s2) {
 +	return (s1 && s2) ? Q_stricmpn (s1, s2, 99999) : -1;
 +}
 +
 +
 +char *Q_strlwr( char *s1 ) {
 +    char	*s;
 +
 +    s = s1;
 +	while ( *s ) {
 +		*s = tolower(*s);
 +		s++;
 +	}
 +    return s1;
 +}
 +
 +char *Q_strupr( char *s1 ) {
 +    char	*s;
 +
 +    s = s1;
 +	while ( *s ) {
 +		*s = toupper(*s);
 +		s++;
 +	}
 +    return s1;
 +}
 +
 +
 +// never goes past bounds or leaves without a terminating 0
 +void Q_strcat( char *dest, int size, const char *src ) {
 +	int		l1;
 +
 +	l1 = strlen( dest );
 +	if ( l1 >= size ) {
 +		Com_Error( ERR_FATAL, "Q_strcat: already overflowed" );
 +	}
 +	Q_strncpyz( dest + l1, src, size - l1 );
 +}
 +
 +
 +int Q_PrintStrlen( const char *string ) {
 +	int			len;
 +	const char	*p;
 +
 +	if( !string ) {
 +		return 0;
 +	}
 +
 +	len = 0;
 +	p = string;
 +	while( *p ) {
 +		if( Q_IsColorString( p ) ) {
 +			p += 2;
 +			continue;
 +		}
 +		p++;
 +		len++;
 +	}
 +
 +	return len;
 +}
 +
 +
 +char *Q_CleanStr( char *string ) {
 +	char*	d;
 +	char*	s;
 +	int		c;
 +
 +	s = string;
 +	d = string;
 +	while ((c = *s) != 0 ) {
 +		if ( Q_IsColorString( s ) ) {
 +			s++;
 +		}		
 +		else if ( c >= 0x20 && c <= 0x7E ) {
 +			*d++ = c;
 +		}
 +		s++;
 +	}
 +	*d = '\0';
 +
 +	return string;
 +}
 +
 +
 +void QDECL Com_sprintf( char *dest, int size, const char *fmt, ...) {
 +	int		len;
 +	va_list		argptr;
 +	char	bigbuffer[32000];	// big, but small enough to fit in PPC stack
 +
 +	va_start (argptr,fmt);
 +	len = vsprintf (bigbuffer,fmt,argptr);
 +	va_end (argptr);
 +	if ( len >= sizeof( bigbuffer ) ) {
 +		Com_Error( ERR_FATAL, "Com_sprintf: overflowed bigbuffer" );
 +	}
 +	if (len >= size) {
 +		Com_Printf ("Com_sprintf: overflow of %i in %i\n", len, size);
 +#ifdef	_DEBUG
 +		__asm {
 +			int 3;
 +		}
 +#endif
 +	}
 +	Q_strncpyz (dest, bigbuffer, size );
 +}
 +
 +
 +/*
 +============
 +va
 +
 +does a varargs printf into a temp buffer, so I don't need to have
 +varargs versions of all text functions.
 +FIXME: make this buffer size safe someday
 +============
 +*/
 +char	* QDECL va( char *format, ... ) {
 +	va_list		argptr;
 +	static char		string[2][32000];	// in case va is called by nested functions
 +	static int		index = 0;
 +	char	*buf;
 +
 +	buf = string[index & 1];
 +	index++;
 +
 +	va_start (argptr, format);
 +	vsprintf (buf, format,argptr);
 +	va_end (argptr);
 +
 +	return buf;
 +}
 +
 +/*
 +============
 +Com_TruncateLongString
 +
 +Assumes buffer is atleast TRUNCATE_LENGTH big
 +============
 +*/
 +void Com_TruncateLongString( char *buffer, const char *s )
 +{
 +	int length = strlen( s );
 +
 +	if( length <= TRUNCATE_LENGTH )
 +		Q_strncpyz( buffer, s, TRUNCATE_LENGTH );
 +	else
 +	{
 +		Q_strncpyz( buffer, s, ( TRUNCATE_LENGTH / 2 ) - 3 );
 +		Q_strcat( buffer, TRUNCATE_LENGTH, " ... " );
 +		Q_strcat( buffer, TRUNCATE_LENGTH, s + length - ( TRUNCATE_LENGTH / 2 ) + 3 );
 +	}
 +}
 +
 +/*
 +=====================================================================
 +
 +  INFO STRINGS
 +
 +=====================================================================
 +*/
 +
 +/*
 +===============
 +Info_ValueForKey
 +
 +Searches the string for the given
 +key and returns the associated value, or an empty string.
 +FIXME: overflow check?
 +===============
 +*/
 +char *Info_ValueForKey( const char *s, const char *key ) {
 +	char	pkey[BIG_INFO_KEY];
 +	static	char value[2][BIG_INFO_VALUE];	// use two buffers so compares
 +											// work without stomping on each other
 +	static	int	valueindex = 0;
 +	char	*o;
 +	
 +	if ( !s || !key ) {
 +		return "";
 +	}
 +
 +	if ( strlen( s ) >= BIG_INFO_STRING ) {
 +		Com_Error( ERR_DROP, "Info_ValueForKey: oversize infostring" );
 +	}
 +
 +	valueindex ^= 1;
 +	if (*s == '\\')
 +		s++;
 +	while (1)
 +	{
 +		o = pkey;
 +		while (*s != '\\')
 +		{
 +			if (!*s)
 +				return "";
 +			*o++ = *s++;
 +		}
 +		*o = 0;
 +		s++;
 +
 +		o = value[valueindex];
 +
 +		while (*s != '\\' && *s)
 +		{
 +			*o++ = *s++;
 +		}
 +		*o = 0;
 +
 +		if (!Q_stricmp (key, pkey) )
 +			return value[valueindex];
 +
 +		if (!*s)
 +			break;
 +		s++;
 +	}
 +
 +	return "";
 +}
 +
 +
 +/*
 +===================
 +Info_NextPair
 +
 +Used to itterate through all the key/value pairs in an info string
 +===================
 +*/
 +void Info_NextPair( const char **head, char *key, char *value ) {
 +	char	*o;
 +	const char	*s;
 +
 +	s = *head;
 +
 +	if ( *s == '\\' ) {
 +		s++;
 +	}
 +	key[0] = 0;
 +	value[0] = 0;
 +
 +	o = key;
 +	while ( *s != '\\' ) {
 +		if ( !*s ) {
 +			*o = 0;
 +			*head = s;
 +			return;
 +		}
 +		*o++ = *s++;
 +	}
 +	*o = 0;
 +	s++;
 +
 +	o = value;
 +	while ( *s != '\\' && *s ) {
 +		*o++ = *s++;
 +	}
 +	*o = 0;
 +
 +	*head = s;
 +}
 +
 +
 +/*
 +===================
 +Info_RemoveKey
 +===================
 +*/
 +void Info_RemoveKey( char *s, const char *key ) {
 +	char	*start;
 +	char	pkey[MAX_INFO_KEY];
 +	char	value[MAX_INFO_VALUE];
 +	char	*o;
 +
 +	if ( strlen( s ) >= MAX_INFO_STRING ) {
 +		Com_Error( ERR_DROP, "Info_RemoveKey: oversize infostring" );
 +	}
 +
 +	if (strchr (key, '\\')) {
 +		return;
 +	}
 +
 +	while (1)
 +	{
 +		start = s;
 +		if (*s == '\\')
 +			s++;
 +		o = pkey;
 +		while (*s != '\\')
 +		{
 +			if (!*s)
 +				return;
 +			*o++ = *s++;
 +		}
 +		*o = 0;
 +		s++;
 +
 +		o = value;
 +		while (*s != '\\' && *s)
 +		{
 +			if (!*s)
 +				return;
 +			*o++ = *s++;
 +		}
 +		*o = 0;
 +
 +		if (!strcmp (key, pkey) )
 +		{
 +			strcpy (start, s);	// remove this part
 +			return;
 +		}
 +
 +		if (!*s)
 +			return;
 +	}
 +
 +}
 +
 +/*
 +===================
 +Info_RemoveKey_Big
 +===================
 +*/
 +void Info_RemoveKey_Big( char *s, const char *key ) {
 +	char	*start;
 +	char	pkey[BIG_INFO_KEY];
 +	char	value[BIG_INFO_VALUE];
 +	char	*o;
 +
 +	if ( strlen( s ) >= BIG_INFO_STRING ) {
 +		Com_Error( ERR_DROP, "Info_RemoveKey_Big: oversize infostring" );
 +	}
 +
 +	if (strchr (key, '\\')) {
 +		return;
 +	}
 +
 +	while (1)
 +	{
 +		start = s;
 +		if (*s == '\\')
 +			s++;
 +		o = pkey;
 +		while (*s != '\\')
 +		{
 +			if (!*s)
 +				return;
 +			*o++ = *s++;
 +		}
 +		*o = 0;
 +		s++;
 +
 +		o = value;
 +		while (*s != '\\' && *s)
 +		{
 +			if (!*s)
 +				return;
 +			*o++ = *s++;
 +		}
 +		*o = 0;
 +
 +		if (!strcmp (key, pkey) )
 +		{
 +			strcpy (start, s);	// remove this part
 +			return;
 +		}
 +
 +		if (!*s)
 +			return;
 +	}
 +
 +}
 +
 +
 +
 +
 +/*
 +==================
 +Info_Validate
 +
 +Some characters are illegal in info strings because they
 +can mess up the server's parsing
 +==================
 +*/
 +qboolean Info_Validate( const char *s ) {
 +	if ( strchr( s, '\"' ) ) {
 +		return qfalse;
 +	}
 +	if ( strchr( s, ';' ) ) {
 +		return qfalse;
 +	}
 +	return qtrue;
 +}
 +
 +/*
 +==================
 +Info_SetValueForKey
 +
 +Changes or adds a key/value pair
 +==================
 +*/
 +void Info_SetValueForKey( char *s, const char *key, const char *value ) {
 +	char	newi[MAX_INFO_STRING];
 +	const char* blacklist = "\\;\"";
 +
 +	if ( strlen( s ) >= MAX_INFO_STRING ) {
 +		Com_Error( ERR_DROP, "Info_SetValueForKey: oversize infostring" );
 +	}
 +
 +	for(; *blacklist; ++blacklist)
 +	{
 +		if (strchr (key, *blacklist) || strchr (value, *blacklist))
 +		{
 +			Com_Printf (S_COLOR_YELLOW "Can't use keys or values with a '%c': %s = %s\n", *blacklist, key, value);
 +			return;
 +		}
 +	}
 +	
 +	Info_RemoveKey (s, key);
 +	if (!value || !strlen(value))
 +		return;
 +
 +	Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value);
 +
 +	if (strlen(newi) + strlen(s) >= MAX_INFO_STRING)
 +	{
 +		Com_Printf ("Info string length exceeded\n");
 +		return;
 +	}
 +
 +	strcat (newi, s);
 +	strcpy (s, newi);
 +}
 +
 +/*
 +==================
 +Info_SetValueForKey_Big
 +
 +Changes or adds a key/value pair
 +==================
 +*/
 +void Info_SetValueForKey_Big( char *s, const char *key, const char *value ) {
 +	char	newi[BIG_INFO_STRING];
 +	const char* blacklist = "\\;\"";
 +
 +	if ( strlen( s ) >= BIG_INFO_STRING ) {
 +		Com_Error( ERR_DROP, "Info_SetValueForKey: oversize infostring" );
 +	}
 +
 +	for(; *blacklist; ++blacklist)
 +	{
 +		if (strchr (key, *blacklist) || strchr (value, *blacklist))
 +		{
 +			Com_Printf (S_COLOR_YELLOW "Can't use keys or values with a '%c': %s = %s\n", *blacklist, key, value);
 +			return;
 +		}
 +	}
 +
 +	Info_RemoveKey_Big (s, key);
 +	if (!value || !strlen(value))
 +		return;
 +
 +	Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value);
 +
 +	if (strlen(newi) + strlen(s) >= BIG_INFO_STRING)
 +	{
 +		Com_Printf ("BIG Info string length exceeded\n");
 +		return;
 +	}
 +
 +	strcat (s, newi);
 +}
 +
 +
 +
 +
 +//====================================================================
 +
 +/*
 +==================
 +Com_CharIsOneOfCharset
 +==================
 +*/
 +static qboolean Com_CharIsOneOfCharset( char c, char *set )
 +{
 +	int i;
 +
 +	for( i = 0; i < strlen( set ); i++ )
 +	{
 +		if( set[ i ] == c )
 +			return qtrue;
 +	}
 +
 +	return qfalse;
 +}
 +
 +/*
 +==================
 +Com_SkipCharset
 +==================
 +*/
 +char *Com_SkipCharset( char *s, char *sep )
 +{
 +	char	*p = s;
 +
 +	while( p )
 +	{
 +		if( Com_CharIsOneOfCharset( *p, sep ) )
 +			p++;
 +		else
 +			break;
 +	}
 +
 +	return p;
 +}
 +
 +/*
 +==================
 +Com_SkipTokens
 +==================
 +*/
 +char *Com_SkipTokens( char *s, int numTokens, char *sep )
 +{
 +	int		sepCount = 0;
 +	char	*p = s;
 +
 +	while( sepCount < numTokens )
 +	{
 +		if( Com_CharIsOneOfCharset( *p++, sep ) )
 +		{
 +			sepCount++;
 +			while( Com_CharIsOneOfCharset( *p, sep ) )
 +				p++;
 +		}
 +		else if( *p == '\0' )
 +			break;
 +	}
 +
 +	if( sepCount == numTokens )
 +		return p;
 +	else
 +		return s;
 +}
 +
 +//ROTAX
 +#define isvisiblechar(c) (isgraph(c) && c != '"')
 +
 +void fixLineSpaces( char *result, char *source )
 +{
 +  char *reader = source;
 +  char *writer = result;
 +  
 +  while( *reader )
 +  {
 +    //Copy all normal characters
 +    while ( *reader && isvisiblechar(*reader)) 
 +    {
 +    	*writer = *reader;
 +    	reader++;
 +    	writer++;
 +    }
 +    if (*reader && !isvisiblechar(*reader))
 +    {
 +      *writer = ' ';
 +      writer++;
 +      reader++;
 +    }
 +    while ( *reader && !isvisiblechar(*reader)) 
 +    {
 +    	reader++;
 +    }
 +  }
 +  *writer = 0; //End of the line
 +}
 diff --git a/src/qcommon/q_shared.h b/src/qcommon/q_shared.h new file mode 100644 index 0000000..47aead8 --- /dev/null +++ b/src/qcommon/q_shared.h @@ -0,0 +1,1330 @@ +/*
 +===========================================================================
 +Copyright (C) 1999-2005 Id Software, Inc.
 +Copyright (C) 2000-2006 Tim Angus
 +
 +This file is part of Tremulous.
 +
 +Tremulous is free software; you can redistribute it
 +and/or modify it under the terms of the GNU General Public License as
 +published by the Free Software Foundation; either version 2 of the License,
 +or (at your option) any later version.
 +
 +Tremulous is distributed in the hope that it will be
 +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +GNU General Public License for more details.
 +
 +You should have received a copy of the GNU General Public License
 +along with Tremulous; if not, write to the Free Software
 +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 +===========================================================================
 +*/
 +//
 +#ifndef __Q_SHARED_H
 +#define __Q_SHARED_H
 +
 +// q_shared.h -- included first by ALL program modules.
 +// A user mod should never modify this file
 +
 +#define VERSION_NUMBER        "1.1.0"
 +#define Q3_VERSION            "tremulous " VERSION_NUMBER
 +#ifndef SVN_VERSION
 +#define SVN_VERSION           Q3_VERSION
 +#endif
 +#define CLIENT_WINDOW_TITLE   "Tremulous " VERSION_NUMBER
 +#define CLIENT_WINDOW_ICON    "Tremulous"
 +#define CONSOLE_WINDOW_TITLE  "Tremulous " VERSION_NUMBER " console"
 +#define CONSOLE_WINDOW_ICON   "Tremulous console"
 +
 +#define MAX_TEAMNAME 32
 +
 +#ifdef _MSC_VER
 +
 +#pragma warning(disable : 4018)     // signed/unsigned mismatch
 +#pragma warning(disable : 4032)
 +#pragma warning(disable : 4051)
 +#pragma warning(disable : 4057)		// slightly different base types
 +#pragma warning(disable : 4100)		// unreferenced formal parameter
 +#pragma warning(disable : 4115)
 +#pragma warning(disable : 4125)		// decimal digit terminates octal escape sequence
 +#pragma warning(disable : 4127)		// conditional expression is constant
 +#pragma warning(disable : 4136)
 +#pragma warning(disable : 4152)		// nonstandard extension, function/data pointer conversion in expression
 +//#pragma warning(disable : 4201)
 +//#pragma warning(disable : 4214)
 +#pragma warning(disable : 4244)
 +#pragma warning(disable : 4142)		// benign redefinition
 +//#pragma warning(disable : 4305)		// truncation from const double to float
 +//#pragma warning(disable : 4310)		// cast truncates constant value
 +//#pragma warning(disable:  4505) 	// unreferenced local function has been removed
 +#pragma warning(disable : 4514)
 +#pragma warning(disable : 4702)		// unreachable code
 +#pragma warning(disable : 4711)		// selected for automatic inline expansion
 +#pragma warning(disable : 4220)		// varargs matches remaining parameters
 +//#pragma intrinsic( memset, memcpy )
 +#endif
 +
 +//Ignore __attribute__ on non-gcc platforms
 +#ifndef __GNUC__
 +#ifndef __attribute__
 +#define __attribute__(x)
 +#endif
 +#endif
 +
 +/**********************************************************************
 +  VM Considerations
 +
 +  The VM can not use the standard system headers because we aren't really
 +  using the compiler they were meant for.  We use bg_lib.h which contains
 +  prototypes for the functions we define for our own use in bg_lib.c.
 +
 +  When writing mods, please add needed headers HERE, do not start including
 +  stuff like <stdio.h> in the various .c files that make up each of the VMs
 +  since you will be including system headers files can will have issues.
 +
 +  Remember, if you use a C library function that is not defined in bg_lib.c,
 +  you will have to add your own version for support in the VM.
 +
 + **********************************************************************/
 +
 +#ifdef Q3_VM
 +
 +#include "../game/bg_lib.h"
 +
 +#else
 +
 +#include <assert.h>
 +#include <math.h>
 +#include <stdio.h>
 +#include <stdarg.h>
 +#include <string.h>
 +#include <stdlib.h>
 +#include <time.h>
 +#include <ctype.h>
 +#include <limits.h>
 +
 +#endif
 +
 +#include "q_platform.h"
 +
 +//=============================================================
 +
 +#ifdef Q3_VM
 +   typedef int intptr_t;
 +#else
 +  #ifndef _MSC_VER
 +    #include <stdint.h>
 +  #else
 +    #include <io.h>
 +    typedef __int64 int64_t;
 +    typedef __int32 int32_t;
 +    typedef __int16 int16_t;
 +    typedef __int8 int8_t;
 +    typedef unsigned __int64 uint64_t;
 +    typedef unsigned __int32 uint32_t;
 +    typedef unsigned __int16 uint16_t;
 +    typedef unsigned __int8 uint8_t;
 +  #endif
 +#endif
 +
 +typedef unsigned char 		byte;
 +
 +typedef enum {qfalse, qtrue}	qboolean;
 +
 +typedef int		qhandle_t;
 +typedef int		sfxHandle_t;
 +typedef int		fileHandle_t;
 +typedef int		clipHandle_t;
 +
 +#define PAD(x,y) (((x)+(y)-1) & ~((y)-1))
 +
 +#ifdef __GNUC__
 +#define ALIGN(x) __attribute__((aligned(x)))
 +#else
 +#define ALIGN(x)
 +#endif
 +
 +#ifndef NULL
 +#define NULL ((void *)0)
 +#endif
 +
 +#define	MAX_QINT			0x7fffffff
 +#define	MIN_QINT			(-MAX_QINT-1)
 +
 +
 +// angle indexes
 +#define	PITCH				0		// up / down
 +#define	YAW					1		// left / right
 +#define	ROLL				2		// fall over
 +
 +// the game guarantees that no string from the network will ever
 +// exceed MAX_STRING_CHARS
 +#define	MAX_STRING_CHARS	1024	// max length of a string passed to Cmd_TokenizeString
 +#define	MAX_STRING_TOKENS	1024	// max tokens resulting from Cmd_TokenizeString
 +#define	MAX_TOKEN_CHARS		1024	// max length of an individual token
 +
 +#define	MAX_INFO_STRING		1024
 +#define	MAX_INFO_KEY		  1024
 +#define	MAX_INFO_VALUE		1024
 +
 +#define	BIG_INFO_STRING		8192  // used for system info key only
 +#define	BIG_INFO_KEY		  8192
 +#define	BIG_INFO_VALUE		8192
 +
 +
 +#define	MAX_QPATH			64		// max length of a quake game pathname
 +#ifdef PATH_MAX
 +#define MAX_OSPATH			PATH_MAX
 +#else
 +#define	MAX_OSPATH			256		// max length of a filesystem pathname
 +#endif
 +
 +#define	MAX_NAME_LENGTH			32		// max length of a client name
 +#define	MAX_HOSTNAME_LENGTH	80		// max length of a host name
 +
 +#define	MAX_SAY_TEXT	150
 +
 +// paramters for command buffer stuffing
 +typedef enum {
 +	EXEC_NOW,			// don't return until completed, a VM should NEVER use this,
 +						// because some commands might cause the VM to be unloaded...
 +	EXEC_INSERT,		// insert at current position, but don't run yet
 +	EXEC_APPEND			// add to end of the command buffer (normal case)
 +} cbufExec_t;
 +
 +
 +//
 +// these aren't needed by any of the VMs.  put in another header?
 +//
 +#define	MAX_MAP_AREA_BYTES		32		// bit vector of area visibility
 +
 +
 +// print levels from renderer (FIXME: set up for game / cgame?)
 +typedef enum {
 +	PRINT_ALL,
 +	PRINT_DEVELOPER,		// only print when "developer 1"
 +	PRINT_WARNING,
 +	PRINT_ERROR
 +} printParm_t;
 +
 +
 +#ifdef ERR_FATAL
 +#undef ERR_FATAL			// this is be defined in malloc.h
 +#endif
 +
 +// parameters to the main Error routine
 +typedef enum {
 +	ERR_FATAL,					// exit the entire game with a popup window
 +	ERR_DROP,					// print to console and disconnect from game
 +	ERR_SERVERDISCONNECT,		// don't kill server
 +	ERR_DISCONNECT,				// client disconnected from the server
 +	ERR_NEED_CD					// pop up the need-cd dialog
 +} errorParm_t;
 +
 +
 +// font rendering values used by ui and cgame
 +
 +#define PROP_GAP_WIDTH			3
 +#define PROP_SPACE_WIDTH		8
 +#define PROP_HEIGHT				27
 +#define PROP_SMALL_SIZE_SCALE	0.75
 +
 +#define BLINK_DIVISOR			200
 +#define PULSE_DIVISOR			75
 +
 +#define UI_LEFT			0x00000000	// default
 +#define UI_CENTER		0x00000001
 +#define UI_RIGHT		0x00000002
 +#define UI_FORMATMASK	0x00000007
 +#define UI_SMALLFONT	0x00000010
 +#define UI_BIGFONT		0x00000020	// default
 +#define UI_GIANTFONT	0x00000040
 +#define UI_DROPSHADOW	0x00000800
 +#define UI_BLINK		0x00001000
 +#define UI_INVERSE		0x00002000
 +#define UI_PULSE		0x00004000
 +
 +#if defined(_DEBUG) && !defined(BSPC)
 +	#define HUNK_DEBUG
 +#endif
 +
 +typedef enum {
 +	h_high,
 +	h_low,
 +	h_dontcare
 +} ha_pref;
 +
 +#ifdef HUNK_DEBUG
 +#define Hunk_Alloc( size, preference )				Hunk_AllocDebug(size, preference, #size, __FILE__, __LINE__)
 +void *Hunk_AllocDebug( int size, ha_pref preference, char *label, char *file, int line );
 +#else
 +void *Hunk_Alloc( int size, ha_pref preference );
 +#endif
 +
 +#if defined(__GNUC__) && !defined(__MINGW32__) && !defined(MACOS_X)
 +// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=371
 +// custom Snd_Memset implementation for glibc memset bug workaround
 +void Snd_Memset (void* dest, const int val, const size_t count);
 +#else
 +#define Snd_Memset Com_Memset
 +#endif
 +
 +#define Com_Memset memset
 +#define Com_Memcpy memcpy
 +
 +#define CIN_system	1
 +#define CIN_loop	2
 +#define	CIN_hold	4
 +#define CIN_silent	8
 +#define CIN_shader	16
 +
 +/*
 +==============================================================
 +
 +MATHLIB
 +
 +==============================================================
 +*/
 +
 +
 +typedef float vec_t;
 +typedef vec_t vec2_t[2];
 +typedef vec_t vec3_t[3];
 +typedef vec_t vec4_t[4];
 +typedef vec_t vec5_t[5];
 +
 +typedef	int	fixed4_t;
 +typedef	int	fixed8_t;
 +typedef	int	fixed16_t;
 +
 +#ifndef M_PI
 +#define M_PI		3.14159265358979323846f	// matches value in gcc v2 math.h
 +#endif
 +
 +#ifndef M_SQRT2
 +#define M_SQRT2 1.414213562f
 +#endif
 +
 +#ifndef M_ROOT3
 +#define M_ROOT3 1.732050808f
 +#endif
 +
 +#define NUMVERTEXNORMALS	162
 +extern	vec3_t	bytedirs[NUMVERTEXNORMALS];
 +
 +// all drawing is done to a 640*480 virtual screen size
 +// and will be automatically scaled to the real resolution
 +#define	SCREEN_WIDTH		640
 +#define	SCREEN_HEIGHT		480
 +
 +#define TINYCHAR_WIDTH		(SMALLCHAR_WIDTH)
 +#define TINYCHAR_HEIGHT		(SMALLCHAR_HEIGHT/2)
 +
 +#define SMALLCHAR_WIDTH		8
 +#define SMALLCHAR_HEIGHT	16
 +
 +#define BIGCHAR_WIDTH		16
 +#define BIGCHAR_HEIGHT		16
 +
 +#define	GIANTCHAR_WIDTH		32
 +#define	GIANTCHAR_HEIGHT	48
 +
 +extern	vec4_t		colorBlack;
 +extern	vec4_t		colorRed;
 +extern	vec4_t		colorGreen;
 +extern	vec4_t		colorBlue;
 +extern	vec4_t		colorYellow;
 +extern	vec4_t		colorMagenta;
 +extern	vec4_t		colorCyan;
 +extern	vec4_t		colorWhite;
 +extern	vec4_t		colorLtGrey;
 +extern	vec4_t		colorMdGrey;
 +extern	vec4_t		colorDkGrey;
 +
 +#define Q_COLOR_ESCAPE	'^'
 +#define Q_IsColorString(p)	( p && *(p) == Q_COLOR_ESCAPE && *((p)+1) && *((p)+1) != Q_COLOR_ESCAPE )
 +
 +#define COLOR_BLACK		'0'
 +#define COLOR_RED		'1'
 +#define COLOR_GREEN		'2'
 +#define COLOR_YELLOW	'3'
 +#define COLOR_BLUE		'4'
 +#define COLOR_CYAN		'5'
 +#define COLOR_MAGENTA	'6'
 +#define COLOR_WHITE		'7'
 +#define ColorIndex(c)	( ( (c) - '0' ) & 7 )
 +
 +#define S_COLOR_BLACK	"^0"
 +#define S_COLOR_RED		"^1"
 +#define S_COLOR_GREEN	"^2"
 +#define S_COLOR_YELLOW	"^3"
 +#define S_COLOR_BLUE	"^4"
 +#define S_COLOR_CYAN	"^5"
 +#define S_COLOR_MAGENTA	"^6"
 +#define S_COLOR_WHITE	"^7"
 +
 +extern vec4_t	g_color_table[8];
 +
 +#define	MAKERGB( v, r, g, b ) v[0]=r;v[1]=g;v[2]=b
 +#define	MAKERGBA( v, r, g, b, a ) v[0]=r;v[1]=g;v[2]=b;v[3]=a
 +
 +#define DEG2RAD( a ) ( ( (a) * M_PI ) / 180.0F )
 +#define RAD2DEG( a ) ( ( (a) * 180.0f ) / M_PI )
 +
 +struct cplane_s;
 +
 +extern	vec3_t	vec3_origin;
 +extern	vec3_t	axisDefault[3];
 +
 +#define	nanmask (255<<23)
 +
 +#define	IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask)
 +
 +#if idppc
 +
 +static ID_INLINE float Q_rsqrt( float number ) {
 +		float x = 0.5f * number;
 +                float y;
 +#ifdef __GNUC__            
 +                asm("frsqrte %0,%1" : "=f" (y) : "f" (number));
 +#else
 +		y = __frsqrte( number );
 +#endif
 +		return y * (1.5f - (x * y * y));
 +	}
 +
 +#ifdef __GNUC__            
 +static ID_INLINE float Q_fabs(float x) {
 +    float abs_x;
 +    
 +    asm("fabs %0,%1" : "=f" (abs_x) : "f" (x));
 +    return abs_x;
 +}
 +#else
 +#define Q_fabs __fabsf
 +#endif
 +
 +#else
 +float Q_fabs( float f );
 +float Q_rsqrt( float f );		// reciprocal square root
 +#endif
 +
 +#define SQRTFAST( x ) ( (x) * Q_rsqrt( x ) )
 +
 +signed char ClampChar( int i );
 +signed short ClampShort( int i );
 +
 +// this isn't a real cheap function to call!
 +int DirToByte( vec3_t dir );
 +void ByteToDir( int b, vec3_t dir );
 +
 +#if	1
 +
 +#define DotProduct(x,y)			((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2])
 +#define VectorSubtract(a,b,c)	((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2])
 +#define VectorAdd(a,b,c)		((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2])
 +#define VectorCopy(a,b)			((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2])
 +#define	VectorScale(v, s, o)	((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s))
 +#define	VectorMA(v, s, b, o)	((o)[0]=(v)[0]+(b)[0]*(s),(o)[1]=(v)[1]+(b)[1]*(s),(o)[2]=(v)[2]+(b)[2]*(s))
 +#define VectorLerp( f, s, e, r ) ((r)[0]=(s)[0]+(f)*((e)[0]-(s)[0]),\
 +  (r)[1]=(s)[1]+(f)*((e)[1]-(s)[1]),\
 +  (r)[2]=(s)[2]+(f)*((e)[2]-(s)[2])) 
 +
 +#else
 +
 +#define DotProduct(x,y)			_DotProduct(x,y)
 +#define VectorSubtract(a,b,c)	_VectorSubtract(a,b,c)
 +#define VectorAdd(a,b,c)		_VectorAdd(a,b,c)
 +#define VectorCopy(a,b)			_VectorCopy(a,b)
 +#define	VectorScale(v, s, o)	_VectorScale(v,s,o)
 +#define	VectorMA(v, s, b, o)	_VectorMA(v,s,b,o)
 +
 +#endif
 +
 +#ifdef Q3_VM
 +#ifdef VectorCopy
 +#undef VectorCopy
 +// this is a little hack to get more efficient copies in our interpreter
 +typedef struct {
 +	float	v[3];
 +} vec3struct_t;
 +#define VectorCopy(a,b)	(*(vec3struct_t *)b=*(vec3struct_t *)a)
 +#endif
 +#endif
 +
 +#define Vector2Set(v, x, y) ((v)[0]=(x), (v)[1]=(y))
 +#define VectorClear(a)			((a)[0]=(a)[1]=(a)[2]=0)
 +#define VectorNegate(a,b)		((b)[0]=-(a)[0],(b)[1]=-(a)[1],(b)[2]=-(a)[2])
 +#define VectorSet(v, x, y, z)	((v)[0]=(x), (v)[1]=(y), (v)[2]=(z))
 +#define Vector4Copy(a,b)		((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3])
 +#define Vector4Add(a,b,c)    ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2],(c)[3]=(a)[3]+(b)[3])
 +
 +#define	SnapVector(v) {v[0]=((int)(v[0]));v[1]=((int)(v[1]));v[2]=((int)(v[2]));}
 +// just in case you do't want to use the macros
 +vec_t _DotProduct( const vec3_t v1, const vec3_t v2 );
 +void _VectorSubtract( const vec3_t veca, const vec3_t vecb, vec3_t out );
 +void _VectorAdd( const vec3_t veca, const vec3_t vecb, vec3_t out );
 +void _VectorCopy( const vec3_t in, vec3_t out );
 +void _VectorScale( const vec3_t in, float scale, vec3_t out );
 +void _VectorMA( const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc );
 +
 +unsigned ColorBytes3 (float r, float g, float b);
 +unsigned ColorBytes4 (float r, float g, float b, float a);
 +
 +float NormalizeColor( const vec3_t in, vec3_t out );
 +
 +float RadiusFromBounds( const vec3_t mins, const vec3_t maxs );
 +void ClearBounds( vec3_t mins, vec3_t maxs );
 +void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs );
 +
 +#if !defined( Q3_VM ) || ( defined( Q3_VM ) && defined( __Q3_VM_MATH ) )
 +static ID_INLINE int VectorCompare( const vec3_t v1, const vec3_t v2 ) {
 +	if (v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2]) {
 +		return 0;
 +	}			
 +	return 1;
 +}
 +
 +static ID_INLINE int VectorCompareEpsilon(
 +		const vec3_t v1, const vec3_t v2, float epsilon )
 +{
 +	vec3_t d;
 +
 +	VectorSubtract( v1, v2, d );
 +	d[ 0 ] = fabs( d[ 0 ] );
 +	d[ 1 ] = fabs( d[ 1 ] );
 +	d[ 2 ] = fabs( d[ 2 ] );
 +
 +	if( d[ 0 ] > epsilon || d[ 1 ] > epsilon || d[ 2 ] > epsilon )
 +		return 0;
 +
 +	return 1;
 +}
 +
 +static ID_INLINE vec_t VectorLength( const vec3_t v ) {
 +	return (vec_t)sqrt (v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
 +}
 +
 +static ID_INLINE vec_t VectorLengthSquared( const vec3_t v ) {
 +	return (v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
 +}
 +
 +static ID_INLINE vec_t Distance( const vec3_t p1, const vec3_t p2 ) {
 +	vec3_t	v;
 +
 +	VectorSubtract (p2, p1, v);
 +	return VectorLength( v );
 +}
 +
 +static ID_INLINE vec_t DistanceSquared( const vec3_t p1, const vec3_t p2 ) {
 +	vec3_t	v;
 +
 +	VectorSubtract (p2, p1, v);
 +	return v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
 +}
 +
 +// fast vector normalize routine that does not check to make sure
 +// that length != 0, nor does it return length, uses rsqrt approximation
 +static ID_INLINE void VectorNormalizeFast( vec3_t v )
 +{
 +	float ilength;
 +
 +	ilength = Q_rsqrt( DotProduct( v, v ) );
 +
 +	v[0] *= ilength;
 +	v[1] *= ilength;
 +	v[2] *= ilength;
 +}
 +
 +static ID_INLINE void VectorInverse( vec3_t v ){
 +	v[0] = -v[0];
 +	v[1] = -v[1];
 +	v[2] = -v[2];
 +}
 +
 +static ID_INLINE void CrossProduct( const vec3_t v1, const vec3_t v2, vec3_t cross ) {
 +	cross[0] = v1[1]*v2[2] - v1[2]*v2[1];
 +	cross[1] = v1[2]*v2[0] - v1[0]*v2[2];
 +	cross[2] = v1[0]*v2[1] - v1[1]*v2[0];
 +}
 +
 +#else
 +int VectorCompare( const vec3_t v1, const vec3_t v2 );
 +
 +vec_t VectorLength( const vec3_t v );
 +
 +vec_t VectorLengthSquared( const vec3_t v );
 +
 +vec_t Distance( const vec3_t p1, const vec3_t p2 );
 +
 +vec_t DistanceSquared( const vec3_t p1, const vec3_t p2 );
 + 
 +void VectorNormalizeFast( vec3_t v );
 +
 +void VectorInverse( vec3_t v );
 +
 +void CrossProduct( const vec3_t v1, const vec3_t v2, vec3_t cross );
 +
 +#endif
 +
 +vec_t VectorNormalize (vec3_t v);		// returns vector length
 +vec_t VectorNormalize2( const vec3_t v, vec3_t out );
 +void Vector4Scale( const vec4_t in, vec_t scale, vec4_t out );
 +void VectorRotate( vec3_t in, vec3_t matrix[3], vec3_t out );
 +int Q_log2(int val);
 +
 +float Q_acos(float c);
 +
 +int		Q_rand( int *seed );
 +float	Q_random( int *seed );
 +float	Q_crandom( int *seed );
 +
 +#define random()	((rand () & 0x7fff) / ((float)0x7fff))
 +#define crandom()	(2.0 * (random() - 0.5))
 +
 +void vectoangles( const vec3_t value1, vec3_t angles);
 +void AnglesToAxis( const vec3_t angles, vec3_t axis[3] );
 +void AxisToAngles( vec3_t axis[3], vec3_t angles );
 +
 +void AxisClear( vec3_t axis[3] );
 +void AxisCopy( vec3_t in[3], vec3_t out[3] );
 +
 +void SetPlaneSignbits( struct cplane_s *out );
 +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *plane);
 +
 +float	AngleMod(float a);
 +float	LerpAngle (float from, float to, float frac);
 +float	AngleSubtract( float a1, float a2 );
 +void	AnglesSubtract( vec3_t v1, vec3_t v2, vec3_t v3 );
 +
 +float AngleNormalize360 ( float angle );
 +float AngleNormalize180 ( float angle );
 +float AngleDelta ( float angle1, float angle2 );
 +
 +qboolean PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c );
 +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal );
 +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees );
 +void RotateAroundDirection( vec3_t axis[3], vec_t angle );
 +void MakeNormalVectors( const vec3_t forward, vec3_t right, vec3_t up );
 +// perpendicular vector could be replaced by this
 +
 +//int	PlaneTypeForNormal (vec3_t normal);
 +
 +void MatrixMultiply(float in1[3][3], float in2[3][3], float out[3][3]);
 +void VectorMatrixMultiply( const vec3_t p, vec3_t m[ 3 ], vec3_t out );
 +void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up);
 +void PerpendicularVector( vec3_t dst, const vec3_t src );
 +int Q_isnan( float x );
 +
 +void GetPerpendicularViewVector( const vec3_t point, const vec3_t p1,
 +		const vec3_t p2, vec3_t up );
 +void ProjectPointOntoVector( vec3_t point, vec3_t vStart,
 +		vec3_t vEnd, vec3_t vProj );
 +float VectorDistance( vec3_t v1, vec3_t v2 );
 +
 +float pointToLineDistance( const vec3_t point, const vec3_t p1, const vec3_t p2 );
 +float VectorMinComponent( vec3_t v );
 +float VectorMaxComponent( vec3_t v );
 +
 +vec_t DistanceBetweenLineSegmentsSquared(
 +    const vec3_t sP0, const vec3_t sP1,
 +    const vec3_t tP0, const vec3_t tP1,
 +    float *s, float *t );
 +vec_t DistanceBetweenLineSegments(
 +    const vec3_t sP0, const vec3_t sP1,
 +    const vec3_t tP0, const vec3_t tP1,
 +    float *s, float *t );
 +
 +#ifndef MAX
 +#define MAX(x,y) ((x)>(y)?(x):(y))
 +#endif
 +
 +#ifndef MIN
 +#define MIN(x,y) ((x)<(y)?(x):(y))
 +#endif
 +
 +//=============================================
 +
 +float Com_Clamp( float min, float max, float value );
 +
 +char	*COM_SkipPath( char *pathname );
 +void	COM_StripExtension(const char *in, char *out, int destsize);
 +void	COM_DefaultExtension( char *path, int maxSize, const char *extension );
 +
 +void	COM_BeginParseSession( const char *name );
 +int		COM_GetCurrentParseLine( void );
 +char	*COM_Parse( char **data_p );
 +char	*COM_ParseExt( char **data_p, qboolean allowLineBreak );
 +int		COM_Compress( char *data_p );
 +void	COM_ParseError( char *format, ... ) __attribute__ ((format (printf, 1, 2)));
 +void	COM_ParseWarning( char *format, ... ) __attribute__ ((format (printf, 1, 2)));
 +//int		COM_ParseInfos( char *buf, int max, char infos[][MAX_INFO_STRING] );
 +
 +#define MAX_TOKENLENGTH		1024
 +
 +#ifndef TT_STRING
 +//token types
 +#define TT_STRING					1			// string
 +#define TT_LITERAL					2			// literal
 +#define TT_NUMBER					3			// number
 +#define TT_NAME						4			// name
 +#define TT_PUNCTUATION				5			// punctuation
 +#endif
 +
 +typedef struct pc_token_s
 +{
 +	int type;
 +	int subtype;
 +	int intvalue;
 +	float floatvalue;
 +	char string[MAX_TOKENLENGTH];
 +} pc_token_t;
 +
 +// data is an in/out parm, returns a parsed out token
 +
 +void	COM_MatchToken( char**buf_p, char *match );
 +
 +void SkipBracedSection (char **program);
 +void SkipRestOfLine ( char **data );
 +
 +void Parse1DMatrix (char **buf_p, int x, float *m);
 +void Parse2DMatrix (char **buf_p, int y, int x, float *m);
 +void Parse3DMatrix (char **buf_p, int z, int y, int x, float *m);
 +
 +void	QDECL Com_sprintf (char *dest, int size, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
 +
 +char *Com_SkipTokens( char *s, int numTokens, char *sep );
 +char *Com_SkipCharset( char *s, char *sep );
 +
 +void Com_RandomBytes( byte *string, int len );
 +
 +// mode parm for FS_FOpenFile
 +typedef enum {
 +	FS_READ,
 +	FS_WRITE,
 +	FS_APPEND,
 +	FS_APPEND_SYNC
 +} fsMode_t;
 +
 +typedef enum {
 +	FS_SEEK_CUR,
 +	FS_SEEK_END,
 +	FS_SEEK_SET
 +} fsOrigin_t;
 +
 +//=============================================
 +
 +int Q_isprint( int c );
 +int Q_islower( int c );
 +int Q_isupper( int c );
 +int Q_isalpha( int c );
 +int Q_isdigit( int c );
 +
 +// portable case insensitive compare
 +int		Q_stricmp (const char *s1, const char *s2);
 +int		Q_strncmp (const char *s1, const char *s2, int n);
 +int		Q_stricmpn (const char *s1, const char *s2, int n);
 +char	*Q_strlwr( char *s1 );
 +char	*Q_strupr( char *s1 );
 +char	*Q_strrchr( const char* string, int c );
 +
 +// buffer size safe library replacements
 +void	Q_strncpyz( char *dest, const char *src, int destsize );
 +void	Q_strcat( char *dest, int size, const char *src );
 +
 +// strlen that discounts Quake color sequences
 +int Q_PrintStrlen( const char *string );
 +// removes color sequences from string
 +char *Q_CleanStr( char *string );
 +
 +//=============================================
 +
 +// 64-bit integers for global rankings interface
 +// implemented as a struct for qvm compatibility
 +typedef struct
 +{
 +	byte	b0;
 +	byte	b1;
 +	byte	b2;
 +	byte	b3;
 +	byte	b4;
 +	byte	b5;
 +	byte	b6;
 +	byte	b7;
 +} qint64;
 +
 +//=============================================
 +/*
 +short	BigShort(short l);
 +short	LittleShort(short l);
 +int		BigLong (int l);
 +int		LittleLong (int l);
 +qint64  BigLong64 (qint64 l);
 +qint64  LittleLong64 (qint64 l);
 +float	BigFloat (const float *l);
 +float	LittleFloat (const float *l);
 +
 +void	Swap_Init (void);
 +*/
 +char	* QDECL va(char *format, ...) __attribute__ ((format (printf, 1, 2)));
 +
 +#define TRUNCATE_LENGTH	64
 +void Com_TruncateLongString( char *buffer, const char *s );
 +
 +//=============================================
 +
 +//
 +// key / value info strings
 +//
 +char *Info_ValueForKey( const char *s, const char *key );
 +void Info_RemoveKey( char *s, const char *key );
 +void Info_RemoveKey_big( char *s, const char *key );
 +void Info_SetValueForKey( char *s, const char *key, const char *value );
 +void Info_SetValueForKey_Big( char *s, const char *key, const char *value );
 +qboolean Info_Validate( const char *s );
 +void Info_NextPair( const char **s, char *key, char *value );
 +
 +// this is only here so the functions in q_shared.c and bg_*.c can link
 +void	QDECL Com_Error( int level, const char *error, ... ) __attribute__ ((format (printf, 2, 3)));
 +void	QDECL Com_Printf( const char *msg, ... ) __attribute__ ((format (printf, 1, 2)));
 +
 +
 +/*
 +==========================================================
 +
 +CVARS (console variables)
 +
 +Many variables can be used for cheating purposes, so when
 +cheats is zero, force all unspecified variables to their
 +default values.
 +==========================================================
 +*/
 +
 +#define	CVAR_ARCHIVE		1	// set to cause it to be saved to vars.rc
 +								// used for system variables, not for player
 +								// specific configurations
 +#define	CVAR_USERINFO		2	// sent to server on connect or change
 +#define	CVAR_SERVERINFO		4	// sent in response to front end requests
 +#define	CVAR_SYSTEMINFO		8	// these cvars will be duplicated on all clients
 +#define	CVAR_INIT			16	// don't allow change from console at all,
 +								// but can be set from the command line
 +#define	CVAR_LATCH			32	// will only change when C code next does
 +								// a Cvar_Get(), so it can't be changed
 +								// without proper initialization.  modified
 +								// will be set, even though the value hasn't
 +								// changed yet
 +#define	CVAR_ROM			64	// display only, cannot be set by user at all
 +#define	CVAR_USER_CREATED	128	// created by a set command
 +#define	CVAR_TEMP			256	// can be set even when cheats are disabled, but is not archived
 +#define CVAR_CHEAT			512	// can not be changed if cheats are disabled
 +#define CVAR_NORESTART		1024	// do not clear when a cvar_restart is issued
 +
 +#define CVAR_SERVER_CREATED	2048	// cvar was created by a server the client connected to.
 +#define CVAR_NONEXISTENT	0xFFFFFFFF	// Cvar doesn't exist.
 +
 +// nothing outside the Cvar_*() functions should modify these fields!
 +typedef struct cvar_s {
 +	char		*name;
 +	char		*string;
 +	char		*resetString;		// cvar_restart will reset to this value
 +	char		*latchedString;		// for CVAR_LATCH vars
 +	int			flags;
 +	qboolean	modified;			// set each time the cvar is changed
 +	int			modificationCount;	// incremented each time the cvar is changed
 +	float		value;				// atof( string )
 +	int			integer;			// atoi( string )
 +	struct cvar_s *next;
 +	struct cvar_s *hashNext;
 +} cvar_t;
 +
 +#define	MAX_CVAR_VALUE_STRING	256
 +
 +typedef int	cvarHandle_t;
 +
 +// the modules that run in the virtual machine can't access the cvar_t directly,
 +// so they must ask for structured updates
 +typedef struct {
 +	cvarHandle_t	handle;
 +	int			modificationCount;
 +	float		value;
 +	int			integer;
 +	char		string[MAX_CVAR_VALUE_STRING];
 +} vmCvar_t;
 +
 +/*
 +==============================================================
 +
 +COLLISION DETECTION
 +
 +==============================================================
 +*/
 +
 +#include "surfaceflags.h"			// shared with the q3map utility
 +
 +// plane types are used to speed some tests
 +// 0-2 are axial planes
 +#define	PLANE_X			0
 +#define	PLANE_Y			1
 +#define	PLANE_Z			2
 +#define	PLANE_NON_AXIAL	3
 +
 +
 +/*
 +=================
 +PlaneTypeForNormal
 +=================
 +*/
 +
 +#define PlaneTypeForNormal(x) (x[0] == 1.0 ? PLANE_X : (x[1] == 1.0 ? PLANE_Y : (x[2] == 1.0 ? PLANE_Z : PLANE_NON_AXIAL) ) )
 +
 +// plane_t structure
 +// !!! if this is changed, it must be changed in asm code too !!!
 +typedef struct cplane_s {
 +	vec3_t	normal;
 +	float	dist;
 +	byte	type;			// for fast side tests: 0,1,2 = axial, 3 = nonaxial
 +	byte	signbits;		// signx + (signy<<1) + (signz<<2), used as lookup during collision
 +	byte	pad[2];
 +} cplane_t;
 +
 +typedef enum {
 +	TT_NONE,
 +
 +	TT_AABB,
 +	TT_CAPSULE,
 +	TT_BISPHERE,
 +
 +	TT_NUM_TRACE_TYPES
 +} traceType_t;
 +
 +// a trace is returned when a box is swept through the world
 +typedef struct {
 +	qboolean	allsolid;	// if true, plane is not valid
 +	qboolean	startsolid;	// if true, the initial point was in a solid area
 +	float		fraction;	// time completed, 1.0 = didn't hit anything
 +	vec3_t		endpos;		// final position
 +	cplane_t	plane;		// surface normal at impact, transformed to world space
 +	int			surfaceFlags;	// surface hit
 +	int			contents;	// contents on other side of surface hit
 +	int			entityNum;	// entity the contacted sirface is a part of
 +	float		lateralFraction; // fraction of collision tangetially to the trace direction
 +} trace_t;
 +
 +// trace->entityNum can also be 0 to (MAX_GENTITIES-1)
 +// or ENTITYNUM_NONE, ENTITYNUM_WORLD
 +
 +
 +// markfragments are returned by CM_MarkFragments()
 +typedef struct {
 +	int		firstPoint;
 +	int		numPoints;
 +} markFragment_t;
 +
 +
 +
 +typedef struct {
 +	vec3_t		origin;
 +	vec3_t		axis[3];
 +} orientation_t;
 +
 +//=====================================================================
 +
 +
 +// in order from highest priority to lowest
 +// if none of the catchers are active, bound key strings will be executed
 +#define KEYCATCH_CONSOLE		0x0001
 +#define	KEYCATCH_UI					0x0002
 +#define	KEYCATCH_MESSAGE		0x0004
 +#define	KEYCATCH_CGAME			0x0008
 +
 +
 +// sound channels
 +// channel 0 never willingly overrides
 +// other channels will allways override a playing sound on that channel
 +typedef enum {
 +	CHAN_AUTO,
 +	CHAN_LOCAL,		// menu sounds, etc
 +	CHAN_WEAPON,
 +	CHAN_VOICE,
 +	CHAN_ITEM,
 +	CHAN_BODY,
 +	CHAN_LOCAL_SOUND,	// chat messages, etc
 +	CHAN_ANNOUNCER		// announcer voices, etc
 +} soundChannel_t;
 +
 +
 +/*
 +========================================================================
 +
 +  ELEMENTS COMMUNICATED ACROSS THE NET
 +
 +========================================================================
 +*/
 +
 +#define	ANGLE2SHORT(x)	((int)((x)*65536/360) & 65535)
 +#define	SHORT2ANGLE(x)	((x)*(360.0/65536))
 +
 +#define	SNAPFLAG_RATE_DELAYED	1
 +#define	SNAPFLAG_NOT_ACTIVE		2	// snapshot used during connection and for zombies
 +#define SNAPFLAG_SERVERCOUNT	4	// toggled every map_restart so transitions can be detected
 +
 +//
 +// per-level limits
 +//
 +#define	MAX_CLIENTS			64		// absolute limit
 +#define MAX_LOCATIONS		64
 +
 +#define	GENTITYNUM_BITS		10		// don't need to send any more
 +#define	MAX_GENTITIES		(1<<GENTITYNUM_BITS)
 +
 +// entitynums are communicated with GENTITY_BITS, so any reserved
 +// values that are going to be communcated over the net need to
 +// also be in this range
 +#define	ENTITYNUM_NONE		(MAX_GENTITIES-1)
 +#define	ENTITYNUM_WORLD		(MAX_GENTITIES-2)
 +#define	ENTITYNUM_MAX_NORMAL	(MAX_GENTITIES-2)
 +
 +
 +#define	MAX_MODELS									256		// these are sent over the net as 8 bits
 +#define	MAX_SOUNDS									256		// so they cannot be blindly increased
 +#define	MAX_GAME_SHADERS						64
 +#define	MAX_GAME_PARTICLE_SYSTEMS		64
 +
 +
 +#define	MAX_CONFIGSTRINGS	1024
 +
 +// these are the only configstrings that the system reserves, all the
 +// other ones are strictly for servergame to clientgame communication
 +#define	CS_SERVERINFO		0		// an info string with all the serverinfo cvars
 +#define	CS_SYSTEMINFO		1		// an info string for server system to client system configuration (timescale, etc)
 +
 +#define	RESERVED_CONFIGSTRINGS	2	// game can't modify below this, only the system can
 +
 +#define	MAX_GAMESTATE_CHARS	16000
 +typedef struct {
 +	int			stringOffsets[MAX_CONFIGSTRINGS];
 +	char		stringData[MAX_GAMESTATE_CHARS];
 +	int			dataCount;
 +} gameState_t;
 +
 +//=========================================================
 +
 +// bit field limits
 +#define	MAX_STATS				16
 +#define	MAX_PERSISTANT			16
 +#define	MAX_POWERUPS			16
 +#define	MAX_WEAPONS				16		
 +
 +#define	MAX_PS_EVENTS			2
 +
 +#define PS_PMOVEFRAMECOUNTBITS	6
 +
 +// playerState_t is the information needed by both the client and server
 +// to predict player motion and actions
 +// nothing outside of pmove should modify these, or some degree of prediction error
 +// will occur
 +
 +// you can't add anything to this without modifying the code in msg.c
 +
 +// playerState_t is a full superset of entityState_t as it is used by players,
 +// so if a playerState_t is transmitted, the entityState_t can be fully derived
 +// from it.
 +typedef struct playerState_s {
 +	int			commandTime;	// cmd->serverTime of last executed command
 +	int			pm_type;
 +	int			bobCycle;		// for view bobbing and footstep generation
 +	int			pm_flags;		// ducked, jump_held, etc
 +	int			pm_time;
 +
 +	vec3_t		origin;
 +	vec3_t		velocity;
 +	int			weaponTime;
 +	int			gravity;
 +	int			speed;
 +	int			delta_angles[3];	// add to command angles to get view direction
 +									// changed by spawns, rotating objects, and teleporters
 +
 +	int			groundEntityNum;// ENTITYNUM_NONE = in air
 +
 +	int			legsTimer;		// don't change low priority animations until this runs out
 +	int			legsAnim;		// mask off ANIM_TOGGLEBIT
 +
 +	int			torsoTimer;		// don't change low priority animations until this runs out
 +	int			torsoAnim;		// mask off ANIM_TOGGLEBIT
 +
 +	int			movementDir;	// a number 0 to 7 that represents the reletive angle
 +								// of movement to the view angle (axial and diagonals)
 +								// when at rest, the value will remain unchanged
 +								// used to twist the legs during strafing
 +
 +	vec3_t		grapplePoint;	// location of grapple to pull towards if PMF_GRAPPLE_PULL
 +
 +	int			eFlags;			// copied to entityState_t->eFlags
 +
 +	int			eventSequence;	// pmove generated events
 +	int			events[MAX_PS_EVENTS];
 +	int			eventParms[MAX_PS_EVENTS];
 +
 +	int			externalEvent;	// events set on player from another source
 +	int			externalEventParm;
 +	int			externalEventTime;
 +
 +	int			clientNum;		// ranges from 0 to MAX_CLIENTS-1
 +	int			weapon;			// copied to entityState_t->weapon
 +	int			weaponstate;
 +
 +	vec3_t		viewangles;		// for fixed views
 +	int			viewheight;
 +
 +	// damage feedback
 +	int			damageEvent;	// when it changes, latch the other parms
 +	int			damageYaw;
 +	int			damagePitch;
 +	int			damageCount;
 +
 +	int			stats[MAX_STATS];
 +	int			persistant[MAX_PERSISTANT];	// stats that aren't cleared on death
 +	int			powerups[MAX_POWERUPS];	// level.time that the powerup runs out
 +	int			ammo[MAX_WEAPONS];
 +
 +	int			generic1;
 +	int			loopSound;
 +	int			otherEntityNum;
 +
 +	// not communicated over the net at all
 +	int			ping;			// server to game info for scoreboard
 +	int			pmove_framecount;	// FIXME: don't transmit over the network
 +	int			jumppad_frame;
 +	int			entityEventSequence;
 +} playerState_t;
 +
 +
 +//====================================================================
 +
 +
 +//
 +// usercmd_t->button bits, many of which are generated by the client system,
 +// so they aren't game/cgame only definitions
 +//
 +#define	BUTTON_ATTACK		1
 +#define	BUTTON_TALK			2			// displays talk balloon and disables actions
 +#define	BUTTON_USE_HOLDABLE	4
 +#define	BUTTON_GESTURE		8
 +#define	BUTTON_WALKING		16			// walking can't just be infered from MOVE_RUN
 +										// because a key pressed late in the frame will
 +										// only generate a small move value for that frame
 +										// walking will use different animations and
 +										// won't generate footsteps
 +#define BUTTON_ATTACK2	32
 +#define	BUTTON_NEGATIVE		64
 +
 +#define BUTTON_GETFLAG		128
 +#define BUTTON_GUARDBASE	256
 +#define BUTTON_PATROL		512
 +#define BUTTON_FOLLOWME		1024
 +
 +#define	BUTTON_ANY			2048			// any key whatsoever
 +
 +#define	MOVE_RUN			120			// if forwardmove or rightmove are >= MOVE_RUN,
 +										// then BUTTON_WALKING should be set
 +
 +// usercmd_t is sent to the server each client frame
 +typedef struct usercmd_s {
 +	int				serverTime;
 +	int				angles[3];
 +	int 			buttons;
 +	byte			weapon;           // weapon 
 +	signed char	forwardmove, rightmove, upmove;
 +} usercmd_t;
 +
 +//===================================================================
 +
 +// if entityState->solid == SOLID_BMODEL, modelindex is an inline model number
 +#define	SOLID_BMODEL	0xffffff
 +
 +typedef enum {
 +	TR_STATIONARY,
 +	TR_INTERPOLATE,				// non-parametric, but interpolate between snapshots
 +	TR_LINEAR,
 +	TR_LINEAR_STOP,
 +	TR_SINE,					// value = base + sin( time / duration ) * delta
 +	TR_GRAVITY,
 +	TR_BUOYANCY //TA: what the hell is this doing in here anyway?
 +} trType_t;
 +
 +typedef struct {
 +	trType_t	trType;
 +	int		trTime;
 +	int		trDuration;			// if non 0, trTime + trDuration = stop time
 +	vec3_t	trBase;
 +	vec3_t	trDelta;			// velocity, etc
 +} trajectory_t;
 +
 +// entityState_t is the information conveyed from the server
 +// in an update message about entities that the client will
 +// need to render in some way
 +// Different eTypes may use the information in different ways
 +// The messages are delta compressed, so it doesn't really matter if
 +// the structure size is fairly large
 +
 +typedef struct entityState_s {
 +	int		number;			// entity index
 +	int		eType;			// entityType_t
 +	int		eFlags;
 +
 +	trajectory_t	pos;	// for calculating position
 +	trajectory_t	apos;	// for calculating angles
 +
 +	int		time;
 +	int		time2;
 +
 +	vec3_t	origin;
 +	vec3_t	origin2;
 +
 +	vec3_t	angles;
 +	vec3_t	angles2;
 +
 +	int		otherEntityNum;	// shotgun sources, etc
 +	int		otherEntityNum2;
 +
 +	int		groundEntityNum;	// -1 = in air
 +
 +	int		constantLight;	// r + (g<<8) + (b<<16) + (intensity<<24)
 +	int		loopSound;		// constantly loop this sound
 +
 +	int		modelindex;
 +	int		modelindex2;
 +	int		clientNum;		// 0 to (MAX_CLIENTS - 1), for players and corpses
 +	int		frame;
 +
 +	int		solid;			// for client side prediction, trap_linkentity sets this properly
 +
 +	int		event;			// impulse events -- muzzle flashes, footsteps, etc
 +	int		eventParm;
 +
 +	// for players
 +	int		powerups;		// bit flags
 +	int		weapon;			// determines weapon and flash model, etc
 +	int		legsAnim;		// mask off ANIM_TOGGLEBIT
 +	int		torsoAnim;		// mask off ANIM_TOGGLEBIT
 +
 +	int		generic1;
 +} entityState_t;
 +
 +typedef enum {
 +	CA_UNINITIALIZED,
 +	CA_DISCONNECTED, 	// not talking to a server
 +	CA_AUTHORIZING,		// not used any more, was checking cd key 
 +	CA_CONNECTING,		// sending request packets to the server
 +	CA_CHALLENGING,		// sending challenge packets to the server
 +	CA_CONNECTED,		// netchan_t established, getting gamestate
 +	CA_LOADING,			// only during cgame initialization, never during main loop
 +	CA_PRIMED,			// got gamestate, waiting for first frame
 +	CA_ACTIVE,			// game views should be displayed
 +	CA_CINEMATIC		// playing a cinematic or a static pic, not connected to a server
 +} connstate_t;
 +
 +// font support 
 +
 +#define GLYPH_START 0
 +#define GLYPH_END 255
 +#define GLYPH_CHARSTART 32
 +#define GLYPH_CHAREND 127
 +#define GLYPHS_PER_FONT GLYPH_END - GLYPH_START + 1
 +typedef struct {
 +  int height;       // number of scan lines
 +  int top;          // top of glyph in buffer
 +  int bottom;       // bottom of glyph in buffer
 +  int pitch;        // width for copying
 +  int xSkip;        // x adjustment
 +  int imageWidth;   // width of actual image
 +  int imageHeight;  // height of actual image
 +  float s;          // x offset in image where glyph starts
 +  float t;          // y offset in image where glyph starts
 +  float s2;
 +  float t2;
 +  qhandle_t glyph;  // handle to the shader with the glyph
 +  char shaderName[32];
 +} glyphInfo_t;
 +
 +typedef struct {
 +  glyphInfo_t glyphs [GLYPHS_PER_FONT];
 +  float glyphScale;
 +  char name[MAX_QPATH];
 +} fontInfo_t;
 +
 +#define Square(x) ((x)*(x))
 +
 +// real time
 +//=============================================
 +
 +
 +typedef struct qtime_s {
 +	int tm_sec;     /* seconds after the minute - [0,59] */
 +	int tm_min;     /* minutes after the hour - [0,59] */
 +	int tm_hour;    /* hours since midnight - [0,23] */
 +	int tm_mday;    /* day of the month - [1,31] */
 +	int tm_mon;     /* months since January - [0,11] */
 +	int tm_year;    /* years since 1900 */
 +	int tm_wday;    /* days since Sunday - [0,6] */
 +	int tm_yday;    /* days since January 1 - [0,365] */
 +	int tm_isdst;   /* daylight savings time flag */
 +} qtime_t;
 +
 +
 +// server browser sources
 +// TTimo: AS_MPLAYER is no longer used
 +#define AS_GLOBAL			2
 +#define AS_MPLAYER		1
 +#define AS_LOCAL			0
 +#define AS_FAVORITES	3
 +
 +
 +// cinematic states
 +typedef enum {
 +	FMV_IDLE,
 +	FMV_PLAY,		// play
 +	FMV_EOF,		// all other conditions, i.e. stop/EOF/abort
 +	FMV_ID_BLT,
 +	FMV_ID_IDLE,
 +	FMV_LOOPED,
 +	FMV_ID_WAIT
 +} e_status;
 +
 +typedef enum _flag_status {
 +	FLAG_ATBASE = 0,
 +	FLAG_TAKEN,			// CTF
 +	FLAG_TAKEN_RED,		// One Flag CTF
 +	FLAG_TAKEN_BLUE,	// One Flag CTF
 +	FLAG_DROPPED
 +} flagStatus_t;
 +
 +typedef enum {
 +	DS_NONE,
 +
 +	DS_PLAYBACK,
 +	DS_RECORDING,
 +
 +	DS_NUM_DEMO_STATES
 +} demoState_t;
 +
 +
 +#define	MAX_GLOBAL_SERVERS				4096
 +#define	MAX_OTHER_SERVERS					128
 +#define MAX_PINGREQUESTS					32
 +#define MAX_SERVERSTATUSREQUESTS	16
 +
 +#define SAY_ALL		0
 +#define SAY_TEAM	1
 +#define SAY_TELL	2
 +#define SAY_ACTION      3
 +#define SAY_ACTION_T    4
 +#define SAY_ADMINS    5
 +
 +#endif	// __Q_SHARED_H
 +
 +
 +
 +
 +
 diff --git a/src/qcommon/qcommon.h b/src/qcommon/qcommon.h new file mode 100644 index 0000000..9dcb0ba --- /dev/null +++ b/src/qcommon/qcommon.h @@ -0,0 +1,1106 @@ +/*
 +===========================================================================
 +Copyright (C) 1999-2005 Id Software, Inc.
 +Copyright (C) 2000-2006 Tim Angus
 +
 +This file is part of Tremulous.
 +
 +Tremulous is free software; you can redistribute it
 +and/or modify it under the terms of the GNU General Public License as
 +published by the Free Software Foundation; either version 2 of the License,
 +or (at your option) any later version.
 +
 +Tremulous is distributed in the hope that it will be
 +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +GNU General Public License for more details.
 +
 +You should have received a copy of the GNU General Public License
 +along with Tremulous; if not, write to the Free Software
 +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 +===========================================================================
 +*/
 +// qcommon.h -- definitions common between client and server, but not game.or ref modules
 +#ifndef _QCOMMON_H_
 +#define _QCOMMON_H_
 +
 +#include "../qcommon/cm_public.h"
 +
 +//Ignore __attribute__ on non-gcc platforms
 +#ifndef __GNUC__
 +#ifndef __attribute__
 +#define __attribute__(x)
 +#endif
 +#endif
 +
 +//============================================================================
 +
 +//
 +// msg.c
 +//
 +typedef struct {
 +	qboolean	allowoverflow;	// if false, do a Com_Error
 +	qboolean	overflowed;		// set to true if the buffer size failed (with allowoverflow set)
 +	qboolean	oob;			// set to true if the buffer size failed (with allowoverflow set)
 +	byte	*data;
 +	int		maxsize;
 +	int		cursize;
 +	int		readcount;
 +	int		bit;				// for bitwise reads and writes
 +} msg_t;
 +
 +void MSG_Init (msg_t *buf, byte *data, int length);
 +void MSG_InitOOB( msg_t *buf, byte *data, int length );
 +void MSG_Clear (msg_t *buf);
 +void MSG_WriteData (msg_t *buf, const void *data, int length);
 +void MSG_Bitstream( msg_t *buf );
 +
 +// TTimo
 +// copy a msg_t in case we need to store it as is for a bit
 +// (as I needed this to keep an msg_t from a static var for later use)
 +// sets data buffer as MSG_Init does prior to do the copy
 +void MSG_Copy(msg_t *buf, byte *data, int length, msg_t *src);
 +
 +struct usercmd_s;
 +struct entityState_s;
 +struct playerState_s;
 +
 +void MSG_WriteBits( msg_t *msg, int value, int bits );
 +
 +void MSG_WriteChar (msg_t *sb, int c);
 +void MSG_WriteByte (msg_t *sb, int c);
 +void MSG_WriteShort (msg_t *sb, int c);
 +void MSG_WriteLong (msg_t *sb, int c);
 +void MSG_WriteFloat (msg_t *sb, float f);
 +void MSG_WriteString (msg_t *sb, const char *s);
 +void MSG_WriteBigString (msg_t *sb, const char *s);
 +void MSG_WriteAngle16 (msg_t *sb, float f);
 +
 +void	MSG_BeginReading (msg_t *sb);
 +void	MSG_BeginReadingOOB(msg_t *sb);
 +
 +int		MSG_ReadBits( msg_t *msg, int bits );
 +
 +int		MSG_ReadChar (msg_t *sb);
 +int		MSG_ReadByte (msg_t *sb);
 +int		MSG_ReadShort (msg_t *sb);
 +int		MSG_ReadLong (msg_t *sb);
 +float	MSG_ReadFloat (msg_t *sb);
 +char	*MSG_ReadString (msg_t *sb);
 +char	*MSG_ReadBigString (msg_t *sb);
 +char	*MSG_ReadStringLine (msg_t *sb);
 +float	MSG_ReadAngle16 (msg_t *sb);
 +void	MSG_ReadData (msg_t *sb, void *buffer, int size);
 +
 +
 +void MSG_WriteDeltaUsercmd( msg_t *msg, struct usercmd_s *from, struct usercmd_s *to );
 +void MSG_ReadDeltaUsercmd( msg_t *msg, struct usercmd_s *from, struct usercmd_s *to );
 +
 +void MSG_WriteDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *to );
 +void MSG_ReadDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *to );
 +
 +void MSG_WriteDeltaEntity( msg_t *msg, struct entityState_s *from, struct entityState_s *to
 +						   , qboolean force );
 +void MSG_ReadDeltaEntity( msg_t *msg, entityState_t *from, entityState_t *to, 
 +						 int number );
 +
 +void MSG_WriteDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to );
 +void MSG_ReadDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to );
 +
 +
 +void MSG_ReportChangeVectors_f( void );
 +
 +//============================================================================
 +
 +/*
 +==============================================================
 +
 +NET
 +
 +==============================================================
 +*/
 +
 +#define	PACKET_BACKUP	32	// number of old messages that must be kept on client and
 +							// server for delta comrpession and ping estimation
 +#define	PACKET_MASK		(PACKET_BACKUP-1)
 +
 +#define	MAX_PACKET_USERCMDS		32		// max number of usercmd_t in a packet
 +
 +#define	PORT_ANY			-1
 +
 +#define	MAX_RELIABLE_COMMANDS	128			// max string commands buffered for restransmit
 +
 +typedef enum {
 +	NA_BAD,					// an address lookup failed
 +	NA_LOOPBACK,
 +	NA_BROADCAST,
 +	NA_IP,
 +	NA_IPX,
 +	NA_BROADCAST_IPX
 +} netadrtype_t;
 +
 +typedef enum {
 +	NS_CLIENT,
 +	NS_SERVER
 +} netsrc_t;
 +
 +typedef struct {
 +	netadrtype_t	type;
 +
 +	byte	ip[4];
 +	byte	ipx[10];
 +
 +	unsigned short	port;
 +} netadr_t;
 +
 +void		NET_Init( void );
 +void		NET_Shutdown( void );
 +void		NET_Restart( void );
 +void		NET_Config( qboolean enableNetworking );
 +void		NET_FlushPacketQueue(void);
 +void		NET_SendPacket (netsrc_t sock, int length, const void *data, netadr_t to);
 +void		QDECL NET_OutOfBandPrint( netsrc_t net_socket, netadr_t adr, const char *format, ...) __attribute__ ((format (printf, 3, 4)));
 +void		QDECL NET_OutOfBandData( netsrc_t sock, netadr_t adr, byte *format, int len );
 +
 +qboolean	NET_CompareAdr (netadr_t a, netadr_t b);
 +qboolean	NET_CompareBaseAdr (netadr_t a, netadr_t b);
 +qboolean	NET_IsLocalAddress (netadr_t adr);
 +const char	*NET_AdrToString (netadr_t a);
 +qboolean	NET_StringToAdr ( const char *s, netadr_t *a);
 +qboolean	NET_GetLoopPacket (netsrc_t sock, netadr_t *net_from, msg_t *net_message);
 +void		NET_Sleep(int msec);
 +
 +
 +#define	MAX_MSGLEN				16384		// max length of a message, which may
 +											// be fragmented into multiple packets
 +
 +#define MAX_DOWNLOAD_WINDOW			8		// max of eight download frames
 +#define MAX_DOWNLOAD_BLKSIZE		2048	// 2048 byte block chunks
 + 
 +
 +/*
 +Netchan handles packet fragmentation and out of order / duplicate suppression
 +*/
 +
 +typedef struct {
 +	netsrc_t	sock;
 +
 +	int			dropped;			// between last packet and previous
 +
 +	netadr_t	remoteAddress;
 +	int			qport;				// qport value to write when transmitting
 +
 +	// sequencing variables
 +	int			incomingSequence;
 +	int			outgoingSequence;
 +
 +	// incoming fragment assembly buffer
 +	int			fragmentSequence;
 +	int			fragmentLength;	
 +	byte		fragmentBuffer[MAX_MSGLEN];
 +
 +	// outgoing fragment buffer
 +	// we need to space out the sending of large fragmented messages
 +	qboolean	unsentFragments;
 +	int			unsentFragmentStart;
 +	int			unsentLength;
 +	byte		unsentBuffer[MAX_MSGLEN];
 +} netchan_t;
 +
 +void Netchan_Init( int qport );
 +void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport );
 +
 +void Netchan_Transmit( netchan_t *chan, int length, const byte *data );
 +void Netchan_TransmitNextFragment( netchan_t *chan );
 +
 +qboolean Netchan_Process( netchan_t *chan, msg_t *msg );
 +
 +
 +/*
 +==============================================================
 +
 +PROTOCOL
 +
 +==============================================================
 +*/
 +
 +#define	PROTOCOL_VERSION	69
 +
 +// maintain a list of compatible protocols for demo playing
 +// NOTE: that stuff only works with two digits protocols
 +extern int demo_protocols[];
 +
 +// override on command line, config files etc.
 +#ifndef MASTER_SERVER_NAME
 +#define MASTER_SERVER_NAME	"master.tremulous.net"
 +#endif
 +
 +#define	PORT_MASTER			30710
 +#define	PORT_SERVER			30720
 +#define	NUM_SERVER_PORTS	4		// broadcast scan this many ports after
 +									// PORT_SERVER so a single machine can
 +									// run multiple servers
 +
 +
 +// the svc_strings[] array in cl_parse.c should mirror this
 +//
 +// server to client
 +//
 +enum svc_ops_e {
 +	svc_bad,
 +	svc_nop,
 +	svc_gamestate,
 +	svc_configstring,			// [short] [string] only in gamestate messages
 +	svc_baseline,				// only in gamestate messages
 +	svc_serverCommand,			// [string] to be executed by client game module
 +	svc_download,				// [short] size [size bytes]
 +	svc_snapshot,
 +	svc_EOF
 +};
 +
 +
 +//
 +// client to server
 +//
 +enum clc_ops_e {
 +	clc_bad,
 +	clc_nop, 		
 +	clc_move,				// [[usercmd_t]
 +	clc_moveNoDelta,		// [[usercmd_t]
 +	clc_clientCommand,		// [string] message
 +	clc_EOF
 +};
 +
 +/*
 +==============================================================
 +
 +VIRTUAL MACHINE
 +
 +==============================================================
 +*/
 +
 +typedef struct vm_s vm_t;
 +
 +typedef enum {
 +	VMI_NATIVE,
 +	VMI_BYTECODE,
 +	VMI_COMPILED
 +} vmInterpret_t;
 +
 +typedef enum {
 +	TRAP_MEMSET = 100,
 +	TRAP_MEMCPY,
 +	TRAP_STRNCPY,
 +	TRAP_SIN,
 +	TRAP_COS,
 +	TRAP_ATAN2,
 +	TRAP_SQRT,
 +	TRAP_MATRIXMULTIPLY,
 +	TRAP_ANGLEVECTORS,
 +	TRAP_PERPENDICULARVECTOR,
 +	TRAP_FLOOR,
 +	TRAP_CEIL,
 +
 +	TRAP_TESTPRINTINT,
 +	TRAP_TESTPRINTFLOAT
 +} sharedTraps_t;
 +
 +void	VM_Init( void );
 +vm_t	*VM_Create( const char *module, intptr_t (*systemCalls)(intptr_t *), 
 +				   vmInterpret_t interpret );
 +// module should be bare: "cgame", not "cgame.dll" or "vm/cgame.qvm"
 +
 +void	VM_Free( vm_t *vm );
 +void	VM_Clear(void);
 +vm_t	*VM_Restart( vm_t *vm );
 +
 +intptr_t		QDECL VM_Call( vm_t *vm, int callNum, ... );
 +
 +void	VM_Debug( int level );
 +
 +void	*VM_ArgPtr( intptr_t intValue );
 +void	*VM_ExplicitArgPtr( vm_t *vm, intptr_t intValue );
 +
 +#define	VMA(x) VM_ArgPtr(args[x])
 +static ID_INLINE float _vmf(intptr_t x)
 +{
 +	union {
 +		intptr_t l;
 +		float f;
 +	} t;
 +	t.l = x;
 +	return t.f;
 +}
 +#define	VMF(x)	_vmf(args[x])
 +
 +
 +/*
 +==============================================================
 +
 +CMD
 +
 +Command text buffering and command execution
 +
 +==============================================================
 +*/
 +
 +/*
 +
 +Any number of commands can be added in a frame, from several different sources.
 +Most commands come from either keybindings or console line input, but entire text
 +files can be execed.
 +
 +*/
 +
 +void Cbuf_Init (void);
 +// allocates an initial text buffer that will grow as needed
 +
 +void Cbuf_AddText( const char *text );
 +// Adds command text at the end of the buffer, does NOT add a final \n
 +
 +void Cbuf_ExecuteText( int exec_when, const char *text );
 +// this can be used in place of either Cbuf_AddText or Cbuf_InsertText
 +
 +void Cbuf_Execute (void);
 +// Pulls off \n terminated lines of text from the command buffer and sends
 +// them through Cmd_ExecuteString.  Stops when the buffer is empty.
 +// Normally called once per frame, but may be explicitly invoked.
 +// Do not call inside a command function, or current args will be destroyed.
 +
 +//===========================================================================
 +
 +/*
 +
 +Command execution takes a null terminated string, breaks it into tokens,
 +then searches for a command or variable that matches the first token.
 +
 +*/
 +
 +typedef void (*xcommand_t) (void);
 +
 +void	Cmd_Init (void);
 +
 +void	Cmd_AddCommand( const char *cmd_name, xcommand_t function );
 +// called by the init functions of other parts of the program to
 +// register commands and functions to call for them.
 +// The cmd_name is referenced later, so it should not be in temp memory
 +// if function is NULL, the command will be forwarded to the server
 +// as a clc_clientCommand instead of executed locally
 +
 +void	Cmd_RemoveCommand( const char *cmd_name );
 +
 +void	Cmd_CommandCompletion( void(*callback)(const char *s) );
 +// callback with each valid string
 +
 +int		Cmd_Argc (void);
 +char	*Cmd_Argv (int arg);
 +void	Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength );
 +char	*Cmd_Args (void);
 +char	*Cmd_ArgsFrom( int arg );
 +void	Cmd_ArgsBuffer( char *buffer, int bufferLength );
 +void	Cmd_LiteralArgsBuffer( char *buffer, int bufferLength );
 +char	*Cmd_Cmd (void);
 +// The functions that execute commands get their parameters with these
 +// functions. Cmd_Argv () will return an empty string, not a NULL
 +// if arg > argc, so string operations are allways safe.
 +
 +void	Cmd_TokenizeString( const char *text );
 +void	Cmd_TokenizeStringIgnoreQuotes( const char *text_in );
 +// Takes a null terminated string.  Does not need to be /n terminated.
 +// breaks the string up into arg tokens.
 +
 +void	Cmd_ExecuteString( const char *text );
 +// Parses a single line of text into arguments and tries to execute it
 +// as if it was typed at the console
 +
 +void Cmd_SaveCmdContext( void );
 +void Cmd_RestoreCmdContext( void );
 +
 +/*
 +==============================================================
 +
 +CVAR
 +
 +==============================================================
 +*/
 +
 +/*
 +
 +cvar_t variables are used to hold scalar or string variables that can be changed
 +or displayed at the console or prog code as well as accessed directly
 +in C code.
 +
 +The user can access cvars from the console in three ways:
 +r_draworder			prints the current value
 +r_draworder 0		sets the current value to 0
 +set r_draworder 0	as above, but creates the cvar if not present
 +
 +Cvars are restricted from having the same names as commands to keep this
 +interface from being ambiguous.
 +
 +The are also occasionally used to communicated information between different
 +modules of the program.
 +
 +*/
 +
 +cvar_t *Cvar_Get( const char *var_name, const char *value, int flags );
 +// creates the variable if it doesn't exist, or returns the existing one
 +// if it exists, the value will not be changed, but flags will be ORed in
 +// that allows variables to be unarchived without needing bitflags
 +// if value is "", the value will not override a previously set value.
 +
 +void	Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags );
 +// basically a slightly modified Cvar_Get for the interpreted modules
 +
 +void	Cvar_Update( vmCvar_t *vmCvar );
 +// updates an interpreted modules' version of a cvar
 +
 +void 	Cvar_Set( const char *var_name, const char *value );
 +// will create the variable with no flags if it doesn't exist
 +
 +void Cvar_SetLatched( const char *var_name, const char *value);
 +// don't set the cvar immediately
 +
 +void	Cvar_SetValue( const char *var_name, float value );
 +// expands value to a string and calls Cvar_Set
 +
 +float	Cvar_VariableValue( const char *var_name );
 +int		Cvar_VariableIntegerValue( const char *var_name );
 +// returns 0 if not defined or non numeric
 +
 +char	*Cvar_VariableString( const char *var_name );
 +void	Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize );
 +// returns an empty string if not defined
 +
 +int	Cvar_Flags(const char *var_name);
 +// returns CVAR_NONEXISTENT if cvar doesn't exist or the flags of that particular CVAR.
 +
 +void	Cvar_CommandCompletion( void(*callback)(const char *s) );
 +// callback with each valid string
 +
 +void 	Cvar_Reset( const char *var_name );
 +void 	Cvar_ForceReset(const char *var_name);
 +
 +void	Cvar_SetCheatState( void );
 +// reset all testing vars to a safe value
 +
 +qboolean Cvar_Command( void );
 +// called by Cmd_ExecuteString when Cmd_Argv(0) doesn't match a known
 +// command.  Returns true if the command was a variable reference that
 +// was handled. (print or change)
 +
 +void 	Cvar_WriteVariables( fileHandle_t f );
 +// writes lines containing "set variable value" for all variables
 +// with the archive flag set to true.
 +
 +void	Cvar_Init( void );
 +
 +char	*Cvar_InfoString( int bit );
 +char	*Cvar_InfoString_Big( int bit );
 +// returns an info string containing all the cvars that have the given bit set
 +// in their flags ( CVAR_USERINFO, CVAR_SERVERINFO, CVAR_SYSTEMINFO, etc )
 +void	Cvar_InfoStringBuffer( int bit, char *buff, int buffsize );
 +
 +void	Cvar_Restart_f( void );
 +
 +extern	int			cvar_modifiedFlags;
 +// whenever a cvar is modifed, its flags will be OR'd into this, so
 +// a single check can determine if any CVAR_USERINFO, CVAR_SERVERINFO,
 +// etc, variables have been modified since the last check.  The bit
 +// can then be cleared to allow another change detection.
 +
 +/*
 +==============================================================
 +
 +FILESYSTEM
 +
 +No stdio calls should be used by any part of the game, because
 +we need to deal with all sorts of directory and seperator char
 +issues.
 +==============================================================
 +*/
 +
 +// referenced flags
 +// these are in loop specific order so don't change the order
 +#define FS_GENERAL_REF	0x01
 +#define FS_UI_REF		0x02
 +#define FS_CGAME_REF	0x04
 +#define FS_QAGAME_REF	0x08
 +// number of id paks that will never be autodownloaded from baseq3
 +#define NUM_ID_PAKS		9
 +
 +#define	MAX_FILE_HANDLES	64
 +
 +#define BASEGAME "base"
 +
 +qboolean FS_Initialized( void );
 +
 +void	FS_InitFilesystem ( void );
 +void	FS_Shutdown( qboolean closemfp );
 +
 +qboolean	FS_ConditionalRestart( int checksumFeed );
 +void	FS_Restart( int checksumFeed );
 +// shutdown and restart the filesystem so changes to fs_gamedir can take effect
 +
 +char	**FS_ListFiles( const char *directory, const char *extension, int *numfiles );
 +// directory should not have either a leading or trailing /
 +// if extension is "/", only subdirectories will be returned
 +// the returned files will not include any directories or /
 +
 +void	FS_FreeFileList( char **list );
 +
 +qboolean FS_FileExists( const char *file );
 +
 +char   *FS_BuildOSPath( const char *base, const char *game, const char *qpath );
 +
 +int		FS_LoadStack( void );
 +
 +int		FS_GetFileList(  const char *path, const char *extension, char *listbuf, int bufsize );
 +int		FS_GetModList(  char *listbuf, int bufsize );
 +
 +fileHandle_t	FS_FOpenFileWrite( const char *qpath );
 +// will properly create any needed paths and deal with seperater character issues
 +
 +int		FS_filelength( fileHandle_t f );
 +fileHandle_t FS_SV_FOpenFileWrite( const char *filename );
 +int		FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp );
 +void	FS_SV_Rename( const char *from, const char *to );
 +int		FS_FOpenFileRead( const char *qpath, fileHandle_t *file, qboolean uniqueFILE );
 +// if uniqueFILE is true, then a new FILE will be fopened even if the file
 +// is found in an already open pak file.  If uniqueFILE is false, you must call
 +// FS_FCloseFile instead of fclose, otherwise the pak FILE would be improperly closed
 +// It is generally safe to always set uniqueFILE to true, because the majority of
 +// file IO goes through FS_ReadFile, which Does The Right Thing already.
 +
 +int		FS_FileIsInPAK(const char *filename, int *pChecksum );
 +// returns 1 if a file is in the PAK file, otherwise -1
 +
 +int		FS_Write( const void *buffer, int len, fileHandle_t f );
 +
 +int		FS_Read2( void *buffer, int len, fileHandle_t f );
 +int		FS_Read( void *buffer, int len, fileHandle_t f );
 +// properly handles partial reads and reads from other dlls
 +
 +void	FS_FCloseFile( fileHandle_t f );
 +// note: you can't just fclose from another DLL, due to MS libc issues
 +
 +int		FS_ReadFile( const char *qpath, void **buffer );
 +// returns the length of the file
 +// a null buffer will just return the file length without loading
 +// as a quick check for existance. -1 length == not present
 +// A 0 byte will always be appended at the end, so string ops are safe.
 +// the buffer should be considered read-only, because it may be cached
 +// for other uses.
 +
 +void	FS_ForceFlush( fileHandle_t f );
 +// forces flush on files we're writing to.
 +
 +void	FS_FreeFile( void *buffer );
 +// frees the memory returned by FS_ReadFile
 +
 +void	FS_WriteFile( const char *qpath, const void *buffer, int size );
 +// writes a complete file, creating any subdirectories needed
 +
 +int		FS_filelength( fileHandle_t f );
 +// doesn't work for files that are opened from a pack file
 +
 +int		FS_FTell( fileHandle_t f );
 +// where are we?
 +
 +void	FS_Flush( fileHandle_t f );
 +
 +void 	QDECL FS_Printf( fileHandle_t f, const char *fmt, ... ) __attribute__ ((format (printf, 2, 3)));
 +// like fprintf
 +
 +int		FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode );
 +// opens a file for reading, writing, or appending depending on the value of mode
 +
 +int		FS_Seek( fileHandle_t f, long offset, int origin );
 +// seek on a file (doesn't work for zip files!!!!!!!!)
 +
 +qboolean FS_FilenameCompare( const char *s1, const char *s2 );
 +
 +const char *FS_GamePureChecksum( void );
 +// Returns the checksum of the pk3 from which the server loaded the qagame.qvm
 +
 +const char *FS_LoadedPakNames( void );
 +const char *FS_LoadedPakChecksums( void );
 +const char *FS_LoadedPakPureChecksums( void );
 +// Returns a space separated string containing the checksums of all loaded pk3 files.
 +// Servers with sv_pure set will get this string and pass it to clients.
 +
 +const char *FS_ReferencedPakNames( void );
 +const char *FS_ReferencedPakChecksums( void );
 +const char *FS_ReferencedPakPureChecksums( void );
 +// Returns a space separated string containing the checksums of all loaded 
 +// AND referenced pk3 files. Servers with sv_pure set will get this string 
 +// back from clients for pure validation 
 +
 +void FS_ClearPakReferences( int flags );
 +// clears referenced booleans on loaded pk3s
 +
 +void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames );
 +void FS_PureServerSetLoadedPaks( const char *pakSums, const char *pakNames );
 +// If the string is empty, all data sources will be allowed.
 +// If not empty, only pk3 files that match one of the space
 +// separated checksums will be checked for files, with the
 +// sole exception of .cfg files.
 +
 +qboolean FS_CheckDirTraversal(const char *checkdir);
 +qboolean FS_idPak( char *pak, char *base );
 +qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring );
 +
 +void FS_Rename( const char *from, const char *to );
 +
 +void FS_Remove( const char *osPath );
 +void FS_HomeRemove( const char *homePath );
 +
 +void	FS_FilenameCompletion( const char *dir, const char *ext,
 +		qboolean stripExt, void(*callback)(const char *s) );
 +/*
 +==============================================================
 +
 +Edit fields and command line history/completion
 +
 +==============================================================
 +*/
 +
 +#define	MAX_EDIT_LINE	256
 +typedef struct {
 +	int		cursor;
 +	int		scroll;
 +	int		widthInChars;
 +	char	buffer[MAX_EDIT_LINE];
 +} field_t;
 +
 +void Field_Clear( field_t *edit );
 +void Field_AutoComplete( field_t *edit );
 +
 +/*
 +==============================================================
 +
 +MISC
 +
 +==============================================================
 +*/
 +
 +// TTimo
 +// vsnprintf is ISO/IEC 9899:1999
 +// abstracting this to make it portable
 +#ifdef WIN32
 +#define Q_vsnprintf _vsnprintf
 +#else
 +// TODO: do we need Mac define?
 +#define Q_vsnprintf vsnprintf
 +#endif
 +
 +// returnbed by Sys_GetProcessorId
 +#define CPUID_GENERIC			0			// any unrecognized processor
 +
 +#define CPUID_AXP				0x10
 +
 +#define CPUID_INTEL_UNSUPPORTED	0x20			// Intel 386/486
 +#define CPUID_INTEL_PENTIUM		0x21			// Intel Pentium or PPro
 +#define CPUID_INTEL_MMX			0x22			// Intel Pentium/MMX or P2/MMX
 +#define CPUID_INTEL_KATMAI		0x23			// Intel Katmai
 +
 +#define CPUID_AMD_3DNOW			0x30			// AMD K6 3DNOW!
 +
 +// TTimo
 +// centralized and cleaned, that's the max string you can send to a Com_Printf / Com_DPrintf (above gets truncated)
 +#define	MAXPRINTMSG	4096
 +
 +char		*CopyString( const char *in );
 +void		Info_Print( const char *s );
 +
 +void		Com_BeginRedirect (char *buffer, int buffersize, void (*flush)(char *));
 +void		Com_EndRedirect( void );
 +void 		QDECL Com_Printf( const char *fmt, ... ) __attribute__ ((format (printf, 1, 2)));
 +void 		QDECL Com_DPrintf( const char *fmt, ... ) __attribute__ ((format (printf, 1, 2)));
 +void 		QDECL Com_Error( int code, const char *fmt, ... ) __attribute__ ((format (printf, 2, 3)));
 +void 		Com_Quit_f( void );
 +int			Com_EventLoop( void );
 +int			Com_Milliseconds( void );	// will be journaled properly
 +unsigned	Com_BlockChecksum( const void *buffer, int length );
 +char		*Com_MD5File(const char *filename, int length, const char *prefix, int prefix_len);
 +int			Com_HashKey(char *string, int maxlen);
 +int			Com_Filter(char *filter, char *name, int casesensitive);
 +int			Com_FilterPath(char *filter, char *name, int casesensitive);
 +int			Com_RealTime(qtime_t *qtime);
 +qboolean	Com_SafeMode( void );
 +
 +void		Com_StartupVariable( const char *match );
 +// checks for and removes command line "+set var arg" constructs
 +// if match is NULL, all set commands will be executed, otherwise
 +// only a set with the exact name.  Only used during startup.
 +
 +
 +extern	cvar_t	*com_developer;
 +extern	cvar_t	*com_dedicated;
 +extern	cvar_t	*com_speeds;
 +extern	cvar_t	*com_timescale;
 +extern	cvar_t	*com_sv_running;
 +extern	cvar_t	*com_cl_running;
 +extern	cvar_t	*com_viewlog;			// 0 = hidden, 1 = visible, 2 = minimized
 +extern	cvar_t	*com_version;
 +extern	cvar_t	*com_blood;
 +extern	cvar_t	*com_buildScript;		// for building release pak files
 +extern	cvar_t	*com_journal;
 +extern	cvar_t	*com_cameraMode;
 +extern	cvar_t	*com_altivec;
 +
 +// both client and server must agree to pause
 +extern	cvar_t	*cl_paused;
 +extern	cvar_t	*sv_paused;
 +
 +extern	cvar_t	*cl_packetdelay;
 +extern	cvar_t	*sv_packetdelay;
 +
 +// com_speeds times
 +extern	int		time_game;
 +extern	int		time_frontend;
 +extern	int		time_backend;		// renderer backend time
 +
 +extern	int		com_frameTime;
 +extern	int		com_frameMsec;
 +
 +extern	qboolean	com_errorEntered;
 +
 +extern	fileHandle_t	com_journalFile;
 +extern	fileHandle_t	com_journalDataFile;
 +
 +typedef enum {
 +	TAG_FREE,
 +	TAG_GENERAL,
 +	TAG_BOTLIB,
 +	TAG_RENDERER,
 +	TAG_SMALL,
 +	TAG_STATIC
 +} memtag_t;
 +
 +/*
 +
 +--- low memory ----
 +server vm
 +server clipmap
 +---mark---
 +renderer initialization (shaders, etc)
 +UI vm
 +cgame vm
 +renderer map
 +renderer models
 +
 +---free---
 +
 +temp file loading
 +--- high memory ---
 +
 +*/
 +
 +#if defined(_DEBUG) && !defined(BSPC)
 +	#define ZONE_DEBUG
 +#endif
 +
 +#ifdef ZONE_DEBUG
 +#define Z_TagMalloc(size, tag)			Z_TagMallocDebug(size, tag, #size, __FILE__, __LINE__)
 +#define Z_Malloc(size)					Z_MallocDebug(size, #size, __FILE__, __LINE__)
 +#define S_Malloc(size)					S_MallocDebug(size, #size, __FILE__, __LINE__)
 +void *Z_TagMallocDebug( int size, int tag, char *label, char *file, int line );	// NOT 0 filled memory
 +void *Z_MallocDebug( int size, char *label, char *file, int line );			// returns 0 filled memory
 +void *S_MallocDebug( int size, char *label, char *file, int line );			// returns 0 filled memory
 +#else
 +void *Z_TagMalloc( int size, int tag );	// NOT 0 filled memory
 +void *Z_Malloc( int size );			// returns 0 filled memory
 +void *S_Malloc( int size );			// NOT 0 filled memory only for small allocations
 +#endif
 +void Z_Free( void *ptr );
 +void Z_FreeTags( int tag );
 +int Z_AvailableMemory( void );
 +void Z_LogHeap( void );
 +
 +void Hunk_Clear( void );
 +void Hunk_ClearToMark( void );
 +void Hunk_SetMark( void );
 +qboolean Hunk_CheckMark( void );
 +void Hunk_ClearTempMemory( void );
 +void *Hunk_AllocateTempMemory( int size );
 +void Hunk_FreeTempMemory( void *buf );
 +int	Hunk_MemoryRemaining( void );
 +void Hunk_Log( void);
 +void Hunk_Trash( void );
 +
 +void Com_TouchMemory( void );
 +
 +// commandLine should not include the executable name (argv[0])
 +void Com_Init( char *commandLine );
 +void Com_Frame( void );
 +void Com_Shutdown( void );
 +
 +
 +/*
 +==============================================================
 +
 +CLIENT / SERVER SYSTEMS
 +
 +==============================================================
 +*/
 +
 +//
 +// client interface
 +//
 +void CL_InitKeyCommands( void );
 +// the keyboard binding interface must be setup before execing
 +// config files, but the rest of client startup will happen later
 +
 +void CL_Init( void );
 +void CL_Disconnect( qboolean showMainMenu );
 +void CL_Shutdown( void );
 +void CL_Frame( int msec );
 +qboolean CL_GameCommand( void );
 +void CL_KeyEvent (int key, qboolean down, unsigned time);
 +
 +void CL_CharEvent( int key );
 +// char events are for field typing, not game control
 +
 +void CL_MouseEvent( int dx, int dy, int time );
 +
 +void CL_JoystickEvent( int axis, int value, int time );
 +
 +void CL_PacketEvent( netadr_t from, msg_t *msg );
 +
 +void CL_ConsolePrint( char *text );
 +
 +void CL_MapLoading( void );
 +// do a screen update before starting to load a map
 +// when the server is going to load a new map, the entire hunk
 +// will be cleared, so the client must shutdown cgame, ui, and
 +// the renderer
 +
 +void	CL_ForwardCommandToServer( const char *string );
 +// adds the current command line as a clc_clientCommand to the client message.
 +// things like godmode, noclip, etc, are commands directed to the server,
 +// so when they are typed in at the console, they will need to be forwarded.
 +
 +void CL_CDDialog( void );
 +// bring up the "need a cd to play" dialog
 +
 +void CL_ShutdownAll( void );
 +// shutdown all the client stuff
 +
 +void CL_FlushMemory( void );
 +// dump all memory on an error
 +
 +void CL_StartHunkUsers( void );
 +// start all the client stuff using the hunk
 +
 +void Key_WriteBindings( fileHandle_t f );
 +// for writing the config files
 +
 +void S_ClearSoundBuffer( void );
 +// call before filesystem access
 +
 +void SCR_DebugGraph (float value, int color);	// FIXME: move logging to common?
 +
 +//
 +// server interface
 +//
 +void SV_Init( void );
 +void SV_Shutdown( char *finalmsg );
 +void SV_Frame( int msec );
 +void SV_PacketEvent( netadr_t from, msg_t *msg );
 +qboolean SV_GameCommand( void );
 +
 +
 +//
 +// UI interface
 +//
 +qboolean UI_GameCommand( void );
 +
 +/*
 +==============================================================
 +
 +NON-PORTABLE SYSTEM SERVICES
 +
 +==============================================================
 +*/
 +
 +typedef enum {
 +	AXIS_SIDE,
 +	AXIS_FORWARD,
 +	AXIS_UP,
 +	AXIS_ROLL,
 +	AXIS_YAW,
 +	AXIS_PITCH,
 +	MAX_JOYSTICK_AXIS
 +} joystickAxis_t;
 +
 +typedef enum {
 +  // bk001129 - make sure SE_NONE is zero
 +	SE_NONE = 0,	// evTime is still valid
 +	SE_KEY,		// evValue is a key code, evValue2 is the down flag
 +	SE_CHAR,	// evValue is an ascii char
 +	SE_MOUSE,	// evValue and evValue2 are reletive signed x / y moves
 +	SE_JOYSTICK_AXIS,	// evValue is an axis number and evValue2 is the current state (-127 to 127)
 +	SE_CONSOLE,	// evPtr is a char*
 +	SE_PACKET	// evPtr is a netadr_t followed by data bytes to evPtrLength
 +} sysEventType_t;
 +
 +typedef struct {
 +	int				evTime;
 +	sysEventType_t	evType;
 +	int				evValue, evValue2;
 +	int				evPtrLength;	// bytes of data pointed to by evPtr, for journaling
 +	void			*evPtr;			// this must be manually freed if not NULL
 +} sysEvent_t;
 +
 +sysEvent_t	Sys_GetEvent( void );
 +
 +void	Sys_Init (void);
 +
 +// general development dll loading for virtual machine testing
 +// fqpath param added 7/20/02 by T.Ray - Sys_LoadDll is only called in vm.c at this time
 +void	* QDECL Sys_LoadDll( const char *name, char *fqpath , intptr_t (QDECL **entryPoint)(int, ...),
 +				  intptr_t (QDECL *systemcalls)(intptr_t, ...) );
 +void	Sys_UnloadDll( void *dllHandle );
 +
 +void	Sys_UnloadGame( void );
 +void	*Sys_GetGameAPI( void *parms );
 +
 +void	Sys_UnloadCGame( void );
 +void	*Sys_GetCGameAPI( void );
 +
 +void	Sys_UnloadUI( void );
 +void	*Sys_GetUIAPI( void );
 +
 +//bot libraries
 +void	Sys_UnloadBotLib( void );
 +void	*Sys_GetBotLibAPI( void *parms );
 +
 +char	*Sys_GetCurrentUser( void );
 +
 +void	QDECL Sys_Error( const char *error, ...) __attribute__ ((format (printf, 1, 2)));
 +void	Sys_Quit (void);
 +char	*Sys_GetClipboardData( void );	// note that this isn't journaled...
 +
 +void	Sys_Print( const char *msg );
 +
 +// Sys_Milliseconds should only be used for profiling purposes,
 +// any game related timing information should come from event timestamps
 +int		Sys_Milliseconds (void);
 +
 +void	Sys_SnapVector( float *v );
 +
 +qboolean Sys_RandomBytes( byte *string, int len );
 +
 +// the system console is shown when a dedicated server is running
 +void	Sys_DisplaySystemConsole( qboolean show );
 +
 +int		Sys_GetProcessorId( void );
 +
 +void	Sys_BeginStreamedFile( fileHandle_t f, int readahead );
 +void	Sys_EndStreamedFile( fileHandle_t f );
 +int		Sys_StreamedRead( void *buffer, int size, int count, fileHandle_t f );
 +void	Sys_StreamSeek( fileHandle_t f, int offset, int origin );
 +
 +void	Sys_ShowConsole( int level, qboolean quitOnClose );
 +void	Sys_SetErrorText( const char *text );
 +
 +void	Sys_SendPacket( int length, const void *data, netadr_t to );
 +
 +qboolean	Sys_StringToAdr( const char *s, netadr_t *a );
 +//Does NOT parse port numbers, only base addresses.
 +
 +qboolean	Sys_IsLANAddress (netadr_t adr);
 +void		Sys_ShowIP(void);
 +
 +qboolean	Sys_CheckCD( void );
 +
 +void	Sys_Mkdir( const char *path );
 +char	*Sys_Cwd( void );
 +void	Sys_SetDefaultCDPath(const char *path);
 +char	*Sys_DefaultCDPath(void);
 +void	Sys_SetDefaultInstallPath(const char *path);
 +char	*Sys_DefaultInstallPath(void);
 +void  Sys_SetDefaultHomePath(const char *path);
 +char	*Sys_DefaultHomePath(void);
 +
 +char **Sys_ListFiles( const char *directory, const char *extension, char *filter, int *numfiles, qboolean wantsubs );
 +void	Sys_FreeFileList( char **list );
 +
 +void	Sys_BeginProfiling( void );
 +void	Sys_EndProfiling( void );
 +
 +qboolean Sys_LowPhysicalMemory( void );
 +unsigned int Sys_ProcessorCount( void );
 +
 +int Sys_MonkeyShouldBeSpanked( void );
 +
 +qboolean Sys_DetectAltivec( void );
 +
 +/* This is based on the Adaptive Huffman algorithm described in Sayood's Data
 + * Compression book.  The ranks are not actually stored, but implicitly defined
 + * by the location of a node within a doubly-linked list */
 +
 +#define NYT HMAX					/* NYT = Not Yet Transmitted */
 +#define INTERNAL_NODE (HMAX+1)
 +
 +typedef struct nodetype {
 +	struct	nodetype *left, *right, *parent; /* tree structure */ 
 +	struct	nodetype *next, *prev; /* doubly-linked list */
 +	struct	nodetype **head; /* highest ranked node in block */
 +	int		weight;
 +	int		symbol;
 +} node_t;
 +
 +#define HMAX 256 /* Maximum symbol */
 +
 +typedef struct {
 +	int			blocNode;
 +	int			blocPtrs;
 +
 +	node_t*		tree;
 +	node_t*		lhead;
 +	node_t*		ltail;
 +	node_t*		loc[HMAX+1];
 +	node_t**	freelist;
 +
 +	node_t		nodeList[768];
 +	node_t*		nodePtrs[768];
 +} huff_t;
 +
 +typedef struct {
 +	huff_t		compressor;
 +	huff_t		decompressor;
 +} huffman_t;
 +
 +void	Huff_Compress(msg_t *buf, int offset);
 +void	Huff_Decompress(msg_t *buf, int offset);
 +void	Huff_Init(huffman_t *huff);
 +void	Huff_addRef(huff_t* huff, byte ch);
 +int		Huff_Receive (node_t *node, int *ch, byte *fin);
 +void	Huff_transmit (huff_t *huff, int ch, byte *fout);
 +void	Huff_offsetReceive (node_t *node, int *ch, byte *fin, int *offset);
 +void	Huff_offsetTransmit (huff_t *huff, int ch, byte *fout, int *offset);
 +void	Huff_putBit( int bit, byte *fout, int *offset);
 +int		Huff_getBit( byte *fout, int *offset);
 +
 +int		Parse_AddGlobalDefine(char *string);
 +int		Parse_LoadSourceHandle(const char *filename);
 +int		Parse_FreeSourceHandle(int handle);
 +int		Parse_ReadTokenHandle(int handle, pc_token_t *pc_token);
 +int		Parse_SourceFileAndLine(int handle, char *filename, int *line);
 +
 +extern huffman_t clientHuffTables;
 +
 +#define	SV_ENCODE_START		4
 +#define SV_DECODE_START		12
 +#define	CL_ENCODE_START		12
 +#define CL_DECODE_START		4
 +
 +// flags for sv_allowDownload and cl_allowDownload
 +#define DLF_ENABLE 1
 +#define DLF_NO_REDIRECT 2
 +#define DLF_NO_UDP 4
 +#define DLF_NO_DISCONNECT 8
 +
 +#endif // _QCOMMON_H_
 diff --git a/src/qcommon/qfiles.h b/src/qcommon/qfiles.h new file mode 100644 index 0000000..7e901b7 --- /dev/null +++ b/src/qcommon/qfiles.h @@ -0,0 +1,626 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ +#ifndef __QFILES_H__ +#define __QFILES_H__ + +// +// qfiles.h: quake file formats +// This file must be identical in the quake and utils directories +// + +//Ignore __attribute__ on non-gcc platforms +#ifndef __GNUC__ +#ifndef __attribute__ +#define __attribute__(x) +#endif +#endif + +// surface geometry should not exceed these limits +#define	SHADER_MAX_VERTEXES	1000 +#define	SHADER_MAX_INDEXES	(6*SHADER_MAX_VERTEXES) + + +// the maximum size of game relative pathnames +#define	MAX_QPATH		64 + +/* +======================================================================== + +QVM files + +======================================================================== +*/ + +#define	VM_MAGIC			0x12721444 +#define	VM_MAGIC_VER2	0x12721445 +typedef struct { +	int		vmMagic; + +	int		instructionCount; + +	int		codeOffset; +	int		codeLength; + +	int		dataOffset; +	int		dataLength; +	int		litLength;			// ( dataLength - litLength ) should be byteswapped on load +	int		bssLength;			// zero filled memory appended to datalength + +	//!!! below here is VM_MAGIC_VER2 !!! +	int		jtrgLength;			// number of jump table targets +} vmHeader_t; + + +/* +======================================================================== + +PCX files are used for 8 bit images + +======================================================================== +*/ + +typedef struct { +    char	manufacturer; +    char	version; +    char	encoding; +    char	bits_per_pixel; +    unsigned short	xmin,ymin,xmax,ymax; +    unsigned short	hres,vres; +    unsigned char	palette[48]; +    char	reserved; +    char	color_planes; +    unsigned short	bytes_per_line; +    unsigned short	palette_type; +    char	filler[58]; +    unsigned char	data;			// unbounded +} pcx_t; + + +/* +======================================================================== + +TGA files are used for 24/32 bit images + +======================================================================== +*/ + +typedef struct _TargaHeader { +	unsigned char 	id_length, colormap_type, image_type; +	unsigned short	colormap_index, colormap_length; +	unsigned char	colormap_size; +	unsigned short	x_origin, y_origin, width, height; +	unsigned char	pixel_size, attributes; +} TargaHeader; + + + +/* +======================================================================== + +.MD3 triangle model file format + +======================================================================== +*/ + +#define MD3_IDENT			(('3'<<24)+('P'<<16)+('D'<<8)+'I') +#define MD3_VERSION			15 + +// limits +#define MD3_MAX_LODS		3 +#define	MD3_MAX_TRIANGLES	8192	// per surface +#define MD3_MAX_VERTS		4096	// per surface +#define MD3_MAX_SHADERS		256		// per surface +#define MD3_MAX_FRAMES		1024	// per model +#define	MD3_MAX_SURFACES	32		// per model +#define MD3_MAX_TAGS		16		// per frame + +// vertex scales +#define	MD3_XYZ_SCALE		(1.0/64) + +typedef struct md3Frame_s { +	vec3_t		bounds[2]; +	vec3_t		localOrigin; +	float		radius; +	char		name[16]; +} md3Frame_t; + +typedef struct md3Tag_s { +	char		name[MAX_QPATH];	// tag name +	vec3_t		origin; +	vec3_t		axis[3]; +} md3Tag_t; + +/* +** md3Surface_t +** +** CHUNK			SIZE +** header			sizeof( md3Surface_t ) +** shaders			sizeof( md3Shader_t ) * numShaders +** triangles[0]		sizeof( md3Triangle_t ) * numTriangles +** st				sizeof( md3St_t ) * numVerts +** XyzNormals		sizeof( md3XyzNormal_t ) * numVerts * numFrames +*/ +typedef struct { +	int		ident;				//  + +	char	name[MAX_QPATH];	// polyset name + +	int		flags; +	int		numFrames;			// all surfaces in a model should have the same + +	int		numShaders;			// all surfaces in a model should have the same +	int		numVerts; + +	int		numTriangles; +	int		ofsTriangles; + +	int		ofsShaders;			// offset from start of md3Surface_t +	int		ofsSt;				// texture coords are common for all frames +	int		ofsXyzNormals;		// numVerts * numFrames + +	int		ofsEnd;				// next surface follows +} md3Surface_t; + +typedef struct { +	char			name[MAX_QPATH]; +	int				shaderIndex;	// for in-game use +} md3Shader_t; + +typedef struct { +	int			indexes[3]; +} md3Triangle_t; + +typedef struct { +	float		st[2]; +} md3St_t; + +typedef struct { +	short		xyz[3]; +	short		normal; +} md3XyzNormal_t; + +typedef struct { +	int			ident; +	int			version; + +	char		name[MAX_QPATH];	// model name + +	int			flags; + +	int			numFrames; +	int			numTags;			 +	int			numSurfaces; + +	int			numSkins; + +	int			ofsFrames;			// offset for first frame +	int			ofsTags;			// numFrames * numTags +	int			ofsSurfaces;		// first surface, others follow + +	int			ofsEnd;				// end of file +} md3Header_t; + +/* +============================================================================== + +MD4 file format + +============================================================================== +*/ + +#define MD4_IDENT			(('4'<<24)+('P'<<16)+('D'<<8)+'I') +#define MD4_VERSION			1 +#define	MD4_MAX_BONES		128 + +typedef struct { +	int			boneIndex;		// these are indexes into the boneReferences, +	float		   boneWeight;		// not the global per-frame bone list +	vec3_t		offset; +} md4Weight_t; + +typedef struct { +	vec3_t		normal; +	vec2_t		texCoords; +	int			numWeights; +	md4Weight_t	weights[1];		// variable sized +} md4Vertex_t; + +typedef struct { +	int			indexes[3]; +} md4Triangle_t; + +typedef struct { +	int			ident; + +	char		name[MAX_QPATH];	// polyset name +	char		shader[MAX_QPATH]; +	int			shaderIndex;		// for in-game use + +	int			ofsHeader;			// this will be a negative number + +	int			numVerts; +	int			ofsVerts; + +	int			numTriangles; +	int			ofsTriangles; + +	// Bone references are a set of ints representing all the bones +	// present in any vertex weights for this surface.  This is +	// needed because a model may have surfaces that need to be +	// drawn at different sort times, and we don't want to have +	// to re-interpolate all the bones for each surface. +	int			numBoneReferences; +	int			ofsBoneReferences; + +	int			ofsEnd;				// next surface follows +} md4Surface_t; + +typedef struct { +	float		matrix[3][4]; +} md4Bone_t; + +typedef struct { +	vec3_t		bounds[2];			// bounds of all surfaces of all LOD's for this frame +	vec3_t		localOrigin;		// midpoint of bounds, used for sphere cull +	float		radius;				// dist from localOrigin to corner +	md4Bone_t	bones[1];			// [numBones] +} md4Frame_t; + +typedef struct { +	int			numSurfaces; +	int			ofsSurfaces;		// first surface, others follow +	int			ofsEnd;				// next lod follows +} md4LOD_t; + +typedef struct { +	int			ident; +	int			version; + +	char		name[MAX_QPATH];	// model name + +	// frames and bones are shared by all levels of detail +	int			numFrames; +	int			numBones; +	int			ofsBoneNames;		// char	name[ MAX_QPATH ] +	int			ofsFrames;			// md4Frame_t[numFrames] + +	// each level of detail has completely separate sets of surfaces +	int			numLODs; +	int			ofsLODs; + +	int			ofsEnd;				// end of file +} md4Header_t; + +/* + * Here are the definitions for Ravensoft's model format of md4. Raven stores their + * playermodels in .mdr files, in some games, which are pretty much like the md4 + * format implemented by ID soft. It seems like ID's original md4 stuff is not used at all. + * MDR is being used in EliteForce, JediKnight2 and Soldiers of Fortune2 (I think). + * So this comes in handy for anyone who wants to make it possible to load player + * models from these games. + * This format has bone tags, which is similar to the thing you have in md3 I suppose. + * Raven has released their version of md3view under GPL enabling me to add support + * to this codebase. Thanks to Steven Howes aka Skinner for helping with example + * source code. + * + * - Thilo Schulz (arny@ats.s.bawue.de) + */ + +// If you want to enable support for Raven's .mdr / md4 format, uncomment the next +// line. +//#define RAVENMD4 + +#ifdef RAVENMD4 + +#define MDR_IDENT	(('5'<<24)+('M'<<16)+('D'<<8)+'R') +#define MDR_VERSION	2 +#define	MDR_MAX_BONES	128 + +typedef struct { +	int			boneIndex;	// these are indexes into the boneReferences, +	float		   boneWeight;		// not the global per-frame bone list +	vec3_t		offset; +} mdrWeight_t; + +typedef struct { +	vec3_t		normal; +	vec2_t		texCoords; +	int			numWeights; +	mdrWeight_t	weights[1];		// variable sized +} mdrVertex_t; + +typedef struct { +	int			indexes[3]; +} mdrTriangle_t; + +typedef struct { +	int			ident; + +	char		name[MAX_QPATH];	// polyset name +	char		shader[MAX_QPATH]; +	int			shaderIndex;	// for in-game use + +	int			ofsHeader;	// this will be a negative number + +	int			numVerts; +	int			ofsVerts; + +	int			numTriangles; +	int			ofsTriangles; + +	// Bone references are a set of ints representing all the bones +	// present in any vertex weights for this surface.  This is +	// needed because a model may have surfaces that need to be +	// drawn at different sort times, and we don't want to have +	// to re-interpolate all the bones for each surface. +	int			numBoneReferences; +	int			ofsBoneReferences; + +	int			ofsEnd;		// next surface follows +} mdrSurface_t; + +typedef struct { +	float		matrix[3][4]; +} mdrBone_t; + +typedef struct { +	vec3_t		bounds[2];		// bounds of all surfaces of all LOD's for this frame +	vec3_t		localOrigin;		// midpoint of bounds, used for sphere cull +	float		radius;			// dist from localOrigin to corner +	char		name[16]; +	mdrBone_t	bones[1];		// [numBones] +} mdrFrame_t; + +typedef struct { +        unsigned char Comp[24]; // MC_COMP_BYTES is in MatComp.h, but don't want to couple +} mdrCompBone_t; + +typedef struct { +        vec3_t          bounds[2];		// bounds of all surfaces of all LOD's for this frame +        vec3_t          localOrigin;		// midpoint of bounds, used for sphere cull +        float           radius;			// dist from localOrigin to corner +        mdrCompBone_t   bones[1];		// [numBones] +} mdrCompFrame_t; + +typedef struct { +	int			numSurfaces; +	int			ofsSurfaces;		// first surface, others follow +	int			ofsEnd;				// next lod follows +} mdrLOD_t; + +typedef struct { +        int                     boneIndex; +        char            name[32]; +} mdrTag_t; + +typedef struct { +	int			ident; +	int			version; + +	char		name[MAX_QPATH];	// model name + +	// frames and bones are shared by all levels of detail +	int			numFrames; +	int			numBones; +	int			ofsFrames;			// mdrFrame_t[numFrames] + +	// each level of detail has completely separate sets of surfaces +	int			numLODs; +	int			ofsLODs; + +        int                     numTags; +        int                     ofsTags; + +	int			ofsEnd;				// end of file +} mdrHeader_t; + +#endif + +/* +============================================================================== + +  .BSP file format + +============================================================================== +*/ + + +#define BSP_IDENT	(('P'<<24)+('S'<<16)+('B'<<8)+'I') +		// little-endian "IBSP" + +#define BSP_VERSION			46 + + +// there shouldn't be any problem with increasing these values at the +// expense of more memory allocation in the utilities +#define	MAX_MAP_MODELS		0x400 +#define	MAX_MAP_BRUSHES		0x8000 +#define	MAX_MAP_ENTITIES	0x800 +#define	MAX_MAP_ENTSTRING	0x40000 +#define	MAX_MAP_SHADERS		0x400 + +#define	MAX_MAP_AREAS		0x100	// MAX_MAP_AREA_BYTES in q_shared must match! +#define	MAX_MAP_FOGS		0x100 +#define	MAX_MAP_PLANES		0x20000 +#define	MAX_MAP_NODES		0x20000 +#define	MAX_MAP_BRUSHSIDES	0x20000 +#define	MAX_MAP_LEAFS		0x20000 +#define	MAX_MAP_LEAFFACES	0x20000 +#define	MAX_MAP_LEAFBRUSHES 0x40000 +#define	MAX_MAP_PORTALS		0x20000 +#define	MAX_MAP_LIGHTING	0x800000 +#define	MAX_MAP_LIGHTGRID	0x800000 +#define	MAX_MAP_VISIBILITY	0x200000 + +#define	MAX_MAP_DRAW_SURFS	0x20000 +#define	MAX_MAP_DRAW_VERTS	0x80000 +#define	MAX_MAP_DRAW_INDEXES	0x80000 + + +// key / value pair sizes in the entities lump +#define	MAX_KEY				32 +#define	MAX_VALUE			1024 + +// the editor uses these predefined yaw angles to orient entities up or down +#define	ANGLE_UP			-1 +#define	ANGLE_DOWN			-2 + +#define	LIGHTMAP_WIDTH		128 +#define	LIGHTMAP_HEIGHT		128 + +#define MAX_WORLD_COORD		( 128*1024 ) +#define MIN_WORLD_COORD		( -128*1024 ) +#define WORLD_SIZE			( MAX_WORLD_COORD - MIN_WORLD_COORD ) + +//============================================================================= + + +typedef struct { +	int		fileofs, filelen; +} lump_t; + +#define	LUMP_ENTITIES		0 +#define	LUMP_SHADERS		1 +#define	LUMP_PLANES			2 +#define	LUMP_NODES			3 +#define	LUMP_LEAFS			4 +#define	LUMP_LEAFSURFACES	5 +#define	LUMP_LEAFBRUSHES	6 +#define	LUMP_MODELS			7 +#define	LUMP_BRUSHES		8 +#define	LUMP_BRUSHSIDES		9 +#define	LUMP_DRAWVERTS		10 +#define	LUMP_DRAWINDEXES	11 +#define	LUMP_FOGS			12 +#define	LUMP_SURFACES		13 +#define	LUMP_LIGHTMAPS		14 +#define	LUMP_LIGHTGRID		15 +#define	LUMP_VISIBILITY		16 +#define	HEADER_LUMPS		17 + +typedef struct { +	int			ident; +	int			version; + +	lump_t		lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct { +	float		mins[3], maxs[3]; +	int			firstSurface, numSurfaces; +	int			firstBrush, numBrushes; +} dmodel_t; + +typedef struct { +	char		shader[MAX_QPATH]; +	int			surfaceFlags; +	int			contentFlags; +} dshader_t; + +// planes x^1 is allways the opposite of plane x + +typedef struct { +	float		normal[3]; +	float		dist; +} dplane_t; + +typedef struct { +	int			planeNum; +	int			children[2];	// negative numbers are -(leafs+1), not nodes +	int			mins[3];		// for frustom culling +	int			maxs[3]; +} dnode_t; + +typedef struct { +	int			cluster;			// -1 = opaque cluster (do I still store these?) +	int			area; + +	int			mins[3];			// for frustum culling +	int			maxs[3]; + +	int			firstLeafSurface; +	int			numLeafSurfaces; + +	int			firstLeafBrush; +	int			numLeafBrushes; +} dleaf_t; + +typedef struct { +	int			planeNum;			// positive plane side faces out of the leaf +	int			shaderNum; +} dbrushside_t; + +typedef struct { +	int			firstSide; +	int			numSides; +	int			shaderNum;		// the shader that determines the contents flags +} dbrush_t; + +typedef struct { +	char		shader[MAX_QPATH]; +	int			brushNum; +	int			visibleSide;	// the brush side that ray tests need to clip against (-1 == none) +} dfog_t; + +typedef struct { +	vec3_t		xyz; +	float		st[2]; +	float		lightmap[2]; +	vec3_t		normal; +	byte		color[4]; +} drawVert_t; + +#define drawVert_t_cleared(x) drawVert_t (x) = {{0, 0, 0}, {0, 0}, {0, 0}, {0, 0, 0}, {0, 0, 0, 0}} + +typedef enum { +	MST_BAD, +	MST_PLANAR, +	MST_PATCH, +	MST_TRIANGLE_SOUP, +	MST_FLARE +} mapSurfaceType_t; + +typedef struct { +	int			shaderNum; +	int			fogNum; +	int			surfaceType; + +	int			firstVert; +	int			numVerts; + +	int			firstIndex; +	int			numIndexes; + +	int			lightmapNum; +	int			lightmapX, lightmapY; +	int			lightmapWidth, lightmapHeight; + +	vec3_t		lightmapOrigin; +	vec3_t		lightmapVecs[3];	// for patches, [0] and [1] are lodbounds + +	int			patchWidth; +	int			patchHeight; +} dsurface_t; + + +#endif diff --git a/src/qcommon/surfaceflags.h b/src/qcommon/surfaceflags.h new file mode 100644 index 0000000..31ece5c --- /dev/null +++ b/src/qcommon/surfaceflags.h @@ -0,0 +1,91 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ +// +// This file must be identical in the quake and utils directories + +// contents flags are seperate bits +// a given brush can contribute multiple content bits + +// these definitions also need to be in q_shared.h! + +#define	CONTENTS_SOLID			1		// an eye is never valid in a solid +#define	CONTENTS_LAVA			8 +#define	CONTENTS_SLIME			16 +#define	CONTENTS_WATER			32 +#define	CONTENTS_FOG			64 + +#define CONTENTS_NOTTEAM1		0x0080 +#define CONTENTS_NOTTEAM2		0x0100 +#define CONTENTS_NOBOTCLIP		0x0200 + +#define	CONTENTS_AREAPORTAL		0x8000 + +#define	CONTENTS_PLAYERCLIP		0x10000 +#define	CONTENTS_MONSTERCLIP	0x20000 +//bot specific contents types +#define	CONTENTS_TELEPORTER		0x40000 +#define	CONTENTS_JUMPPAD		0x80000 +#define CONTENTS_CLUSTERPORTAL	0x100000 +#define CONTENTS_DONOTENTER		0x200000 +#define CONTENTS_BOTCLIP		0x400000 +#define CONTENTS_MOVER			0x800000 + +#define	CONTENTS_ORIGIN			0x1000000	// removed before bsping an entity + +#define	CONTENTS_BODY			0x2000000	// should never be on a brush, only in game +#define	CONTENTS_CORPSE			0x4000000 +#define	CONTENTS_DETAIL			0x8000000	// brushes not used for the bsp +#define	CONTENTS_STRUCTURAL		0x10000000	// brushes used for the bsp +#define	CONTENTS_TRANSLUCENT	0x20000000	// don't consume surface fragments inside +#define	CONTENTS_TRIGGER		0x40000000 +#define	CONTENTS_NODROP			0x80000000	// don't leave bodies or items (death fog, lava) + +//TA: custominfoparms below +#define	CONTENTS_NOALIENBUILD			0x1000	//disallow alien building +#define	CONTENTS_NOHUMANBUILD			0x2000	//disallow alien building +#define	CONTENTS_NOBUILD					0x4000	//disallow alien building + +#define	SURF_NODAMAGE			0x1		// never give falling damage +#define	SURF_SLICK				0x2		// effects game physics +#define	SURF_SKY				0x4		// lighting from environment map +#define	SURF_LADDER				0x8 +#define	SURF_NOIMPACT			0x10	// don't make missile explosions +#define	SURF_NOMARKS			0x20	// don't leave missile marks +#define	SURF_FLESH				0x40	// make flesh sounds and effects +#define	SURF_NODRAW				0x80	// don't generate a drawsurface at all +#define	SURF_HINT				0x100	// make a primary bsp splitter +#define	SURF_SKIP				0x200	// completely ignore, allowing non-closed brushes +#define	SURF_NOLIGHTMAP			0x400	// surface doesn't need a lightmap +#define	SURF_POINTLIGHT			0x800	// generate lighting info at vertexes +#define	SURF_METALSTEPS			0x1000	// clanking footsteps +#define	SURF_NOSTEPS			0x2000	// no footstep sounds +#define	SURF_NONSOLID			0x4000	// don't collide against curves with this set +#define	SURF_LIGHTFILTER		0x8000	// act as a light filter during q3map -light +#define	SURF_ALPHASHADOW		0x10000	// do per-pixel light shadow casting in q3map +#define	SURF_NODLIGHT			0x20000	// don't dlight even if solid (solid lava, skies) +#define SURF_DUST				0x40000 // leave a dust trail when walking on this surface + +//TA: custominfoparms below +#define	SURF_NOALIENBUILD	0x80000  //disallow alien building +#define	SURF_NOHUMANBUILD	0x100000 //disallow alien building +#define	SURF_NOBUILD			0x200000 //disallow alien building diff --git a/src/qcommon/unzip.c b/src/qcommon/unzip.c new file mode 100644 index 0000000..ac95609 --- /dev/null +++ b/src/qcommon/unzip.c @@ -0,0 +1,4297 @@ +/***************************************************************************** + * name:		unzip.c + * + * desc:		IO on .zip files using portions of zlib  + * + * $Archive: /MissionPack/code/qcommon/unzip.c $ + * + *****************************************************************************/ + +#include "../client/client.h" +#include "unzip.h" + +/* unzip.h -- IO for uncompress .zip files using zlib  +   Version 0.15 beta, Mar 19th, 1998, + +   Copyright (C) 1998 Gilles Vollant + +   This unzip package allow extract file from .ZIP file, compatible with PKZip 2.04g +     WinZip, InfoZip tools and compatible. +   Encryption and multi volume ZipFile (span) are not supported. +   Old compressions used by old PKZip 1.x are not supported + +   THIS IS AN ALPHA VERSION. AT THIS STAGE OF DEVELOPPEMENT, SOMES API OR STRUCTURE +   CAN CHANGE IN FUTURE VERSION !! +   I WAIT FEEDBACK at mail info@winimage.com +   Visit also http://www.winimage.com/zLibDll/unzip.htm for evolution + +   Condition of use and distribution are the same than zlib : + +  This software is provided 'as-is', without any express or implied +  warranty.  In no event will the authors be held liable for any damages +  arising from the use of this software. + +  Permission is granted to anyone to use this software for any purpose, +  including commercial applications, and to alter it and redistribute it +  freely, subject to the following restrictions: + +  1. The origin of this software must not be misrepresented; you must not +     claim that you wrote the original software. If you use this software +     in a product, an acknowledgment in the product documentation would be +     appreciated but is not required. +  2. Altered source versions must be plainly marked as such, and must not be +     misrepresented as being the original software. +  3. This notice may not be removed or altered from any source distribution. + + +*/ +/* for more info about .ZIP format, see  +      ftp://ftp.cdrom.com/pub/infozip/doc/appnote-970311-iz.zip +   PkWare has also a specification at : +      ftp://ftp.pkware.com/probdesc.zip */ + +/* zlib.h -- interface of the 'zlib' general purpose compression library +  version 1.1.3, July 9th, 1998 + +  Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler + +  This software is provided 'as-is', without any express or implied +  warranty.  In no event will the authors be held liable for any damages +  arising from the use of this software. + +  Permission is granted to anyone to use this software for any purpose, +  including commercial applications, and to alter it and redistribute it +  freely, subject to the following restrictions: + +  1. The origin of this software must not be misrepresented; you must not +     claim that you wrote the original software. If you use this software +     in a product, an acknowledgment in the product documentation would be +     appreciated but is not required. +  2. Altered source versions must be plainly marked as such, and must not be +     misrepresented as being the original software. +  3. This notice may not be removed or altered from any source distribution. + +  Jean-loup Gailly        Mark Adler +  jloup@gzip.org          madler@alumni.caltech.edu + + +  The data format used by the zlib library is described by RFCs (Request for +  Comments) 1950 to 1952 in the files ftp://ds.internic.net/rfc/rfc1950.txt +  (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format). +*/ + +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-1998 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h  + */ + + +#ifndef _ZCONF_H +#define _ZCONF_H + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +#  ifdef MAXSEG_64K +#    define MAX_MEM_LEVEL 8 +#  else +#    define MAX_MEM_LEVEL 9 +#  endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +#  define MAX_WBITS   15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): +            (1 << (windowBits+2)) +  (1 << (memLevel+9)) + that is: 128K for windowBits=15  +  128K for memLevel = 8  (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with +     make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + +   The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects. +*/ + +                        /* Type declarations */ + +#ifndef OF /* function prototypes */ +#define OF(args)  args +#endif + +typedef unsigned char  Byte;  /* 8 bits */ +typedef unsigned int   uInt;  /* 16 bits or more */ +typedef unsigned long  uLong; /* 32 bits or more */ +typedef Byte    *voidp; + +#ifndef SEEK_SET +#  define SEEK_SET        0       /* Seek from beginning of file.  */ +#  define SEEK_CUR        1       /* Seek from current position.  */ +#  define SEEK_END        2       /* Set file pointer to EOF plus "offset" */ +#endif + +#endif /* _ZCONF_H */ + +#define ZLIB_VERSION "1.1.3" + +/*  +     The 'zlib' compression library provides in-memory compression and +  decompression functions, including integrity checks of the uncompressed +  data.  This version of the library supports only one compression method +  (deflation) but other algorithms will be added later and will have the same +  stream interface. + +     Compression can be done in a single step if the buffers are large +  enough (for example if an input file is mmap'ed), or can be done by +  repeated calls of the compression function.  In the latter case, the +  application must provide more input and/or consume the output +  (providing more output space) before each call. + +     The library also supports reading and writing files in gzip (.gz) format +  with an interface similar to that of stdio. + +     The library does not install any signal handler. The decoder checks +  the consistency of the compressed data, so the library should never +  crash even in case of corrupted input. +*/ + +/* +   The application must update next_in and avail_in when avail_in has +   dropped to zero. It must update next_out and avail_out when avail_out +   has dropped to zero. The application must initialize zalloc, zfree and +   opaque before calling the init function. All other fields are set by the +   compression library and must not be updated by the application. + +   The opaque value provided by the application will be passed as the first +   parameter for calls of zalloc and zfree. This can be useful for custom +   memory management. The compression library attaches no meaning to the +   opaque value. + +   zalloc must return Z_NULL if there is not enough memory for the object. +   If zlib is used in a multi-threaded application, zalloc and zfree must be +   thread safe. + +   On 16-bit systems, the functions zalloc and zfree must be able to allocate +   exactly 65536 bytes, but will not be required to allocate more than this +   if the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, +   pointers returned by zalloc for objects of exactly 65536 bytes *must* +   have their offset normalized to zero. The default allocation function +   provided by this library ensures this (see zutil.c). To reduce memory +   requirements and avoid any allocation of 64K objects, at the expense of +   compression ratio, compile the library with -DMAX_WBITS=14 (see zconf.h). + +   The fields total_in and total_out can be used for statistics or +   progress reports. After compression, total_in holds the total size of +   the uncompressed data and may be saved for use in the decompressor +   (particularly if the decompressor wants to decompress everything in +   a single step). +*/ + +                        /* constants */ + +#define Z_NO_FLUSH      0 +#define Z_PARTIAL_FLUSH 1 /* will be removed, use Z_SYNC_FLUSH instead */ +#define Z_SYNC_FLUSH    2 +#define Z_FULL_FLUSH    3 +#define Z_FINISH        4 +/* Allowed flush values; see deflate() below for details */ + +#define Z_OK            0 +#define Z_STREAM_END    1 +#define Z_NEED_DICT     2 +#define Z_ERRNO        (-1) +#define Z_STREAM_ERROR (-2) +#define Z_DATA_ERROR   (-3) +#define Z_MEM_ERROR    (-4) +#define Z_BUF_ERROR    (-5) +#define Z_VERSION_ERROR (-6) +/* Return codes for the compression/decompression functions. Negative + * values are errors, positive values are used for special but normal events. + */ + +#define Z_NO_COMPRESSION         0 +#define Z_BEST_SPEED             1 +#define Z_BEST_COMPRESSION       9 +#define Z_DEFAULT_COMPRESSION  (-1) +/* compression levels */ + +#define Z_FILTERED            1 +#define Z_HUFFMAN_ONLY        2 +#define Z_DEFAULT_STRATEGY    0 +/* compression strategy; see deflateInit2() below for details */ + +#define Z_BINARY   0 +#define Z_ASCII    1 +#define Z_UNKNOWN  2 +/* Possible values of the data_type field */ + +#define Z_DEFLATED   8 +/* The deflate compression method (the only one supported in this version) */ + +#define Z_NULL  (void *)0  /* for initializing zalloc, zfree, opaque */ + +#define zlib_version zlibVersion() +/* for compatibility with versions < 1.0.2 */ + +                        /* basic functions */ + +// static const char * zlibVersion OF((void)); +/* The application can compare zlibVersion and ZLIB_VERSION for consistency. +   If the first character differs, the library code actually used is +   not compatible with the zlib.h header file used by the application. +   This check is automatically made by deflateInit and inflateInit. + */ + +/*  +int deflateInit OF((z_streamp strm, int level)); + +     Initializes the internal stream state for compression. The fields +   zalloc, zfree and opaque must be initialized before by the caller. +   If zalloc and zfree are set to Z_NULL, deflateInit updates them to +   use default allocation functions. + +     The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: +   1 gives best speed, 9 gives best compression, 0 gives no compression at +   all (the input data is simply copied a block at a time). +   Z_DEFAULT_COMPRESSION requests a default compromise between speed and +   compression (currently equivalent to level 6). + +     deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not +   enough memory, Z_STREAM_ERROR if level is not a valid compression level, +   Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible +   with the version assumed by the caller (ZLIB_VERSION). +   msg is set to null if there is no error message.  deflateInit does not +   perform any compression: this will be done by deflate(). +*/ + + +// static int deflate OF((z_streamp strm, int flush)); +/* +    deflate compresses as much data as possible, and stops when the input +  buffer becomes empty or the output buffer becomes full. It may introduce some +  output latency (reading input without producing any output) except when +  forced to flush. + +    The detailed semantics are as follows. deflate performs one or both of the +  following actions: + +  - Compress more input starting at next_in and update next_in and avail_in +    accordingly. If not all input can be processed (because there is not +    enough room in the output buffer), next_in and avail_in are updated and +    processing will resume at this point for the next call of deflate(). + +  - Provide more output starting at next_out and update next_out and avail_out +    accordingly. This action is forced if the parameter flush is non zero. +    Forcing flush frequently degrades the compression ratio, so this parameter +    should be set only when necessary (in interactive applications). +    Some output may be provided even if flush is not set. + +  Before the call of deflate(), the application should ensure that at least +  one of the actions is possible, by providing more input and/or consuming +  more output, and updating avail_in or avail_out accordingly; avail_out +  should never be zero before the call. The application can consume the +  compressed output when it wants, for example when the output buffer is full +  (avail_out == 0), or after each call of deflate(). If deflate returns Z_OK +  and with zero avail_out, it must be called again after making room in the +  output buffer because there might be more output pending. + +    If the parameter flush is set to Z_SYNC_FLUSH, all pending output is +  flushed to the output buffer and the output is aligned on a byte boundary, so +  that the decompressor can get all input data available so far. (In particular +  avail_in is zero after the call if enough output space has been provided +  before the call.)  Flushing may degrade compression for some compression +  algorithms and so it should be used only when necessary. + +    If flush is set to Z_FULL_FLUSH, all output is flushed as with +  Z_SYNC_FLUSH, and the compression state is reset so that decompression can +  restart from this point if previous compressed data has been damaged or if +  random access is desired. Using Z_FULL_FLUSH too often can seriously degrade +  the compression. + +    If deflate returns with avail_out == 0, this function must be called again +  with the same value of the flush parameter and more output space (updated +  avail_out), until the flush is complete (deflate returns with non-zero +  avail_out). + +    If the parameter flush is set to Z_FINISH, pending input is processed, +  pending output is flushed and deflate returns with Z_STREAM_END if there +  was enough output space; if deflate returns with Z_OK, this function must be +  called again with Z_FINISH and more output space (updated avail_out) but no +  more input data, until it returns with Z_STREAM_END or an error. After +  deflate has returned Z_STREAM_END, the only possible operations on the +  stream are deflateReset or deflateEnd. +   +    Z_FINISH can be used immediately after deflateInit if all the compression +  is to be done in a single step. In this case, avail_out must be at least +  0.1% larger than avail_in plus 12 bytes.  If deflate does not return +  Z_STREAM_END, then it must be called again as described above. + +    deflate() sets strm->adler to the adler32 checksum of all input read +  so (that is, total_in bytes). + +    deflate() may update data_type if it can make a good guess about +  the input data type (Z_ASCII or Z_BINARY). In doubt, the data is considered +  binary. This field is only for information purposes and does not affect +  the compression algorithm in any manner. + +    deflate() returns Z_OK if some progress has been made (more input +  processed or more output produced), Z_STREAM_END if all input has been +  consumed and all output has been produced (only when flush is set to +  Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example +  if next_in or next_out was NULL), Z_BUF_ERROR if no progress is possible +  (for example avail_in or avail_out was zero). +*/ + + +// static int deflateEnd OF((z_streamp strm)); +/* +     All dynamically allocated data structures for this stream are freed. +   This function discards any unprocessed input and does not flush any +   pending output. + +     deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the +   stream state was inconsistent, Z_DATA_ERROR if the stream was freed +   prematurely (some input or output was discarded). In the error case, +   msg may be set but then points to a static string (which must not be +   deallocated). +*/ + + +/*  +int inflateInit OF((z_streamp strm)); + +     Initializes the internal stream state for decompression. The fields +   next_in, avail_in, zalloc, zfree and opaque must be initialized before by +   the caller. If next_in is not Z_NULL and avail_in is large enough (the exact +   value depends on the compression method), inflateInit determines the +   compression method from the zlib header and allocates all data structures +   accordingly; otherwise the allocation will be deferred to the first call of +   inflate.  If zalloc and zfree are set to Z_NULL, inflateInit updates them to +   use default allocation functions. + +     inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough +   memory, Z_VERSION_ERROR if the zlib library version is incompatible with the +   version assumed by the caller.  msg is set to null if there is no error +   message. inflateInit does not perform any decompression apart from reading +   the zlib header if present: this will be done by inflate().  (So next_in and +   avail_in may be modified, but next_out and avail_out are unchanged.) +*/ + + +static int inflate OF((z_streamp strm, int flush)); +/* +    inflate decompresses as much data as possible, and stops when the input +  buffer becomes empty or the output buffer becomes full. It may some +  introduce some output latency (reading input without producing any output) +  except when forced to flush. + +  The detailed semantics are as follows. inflate performs one or both of the +  following actions: + +  - Decompress more input starting at next_in and update next_in and avail_in +    accordingly. If not all input can be processed (because there is not +    enough room in the output buffer), next_in is updated and processing +    will resume at this point for the next call of inflate(). + +  - Provide more output starting at next_out and update next_out and avail_out +    accordingly.  inflate() provides as much output as possible, until there +    is no more input data or no more space in the output buffer (see below +    about the flush parameter). + +  Before the call of inflate(), the application should ensure that at least +  one of the actions is possible, by providing more input and/or consuming +  more output, and updating the next_* and avail_* values accordingly. +  The application can consume the uncompressed output when it wants, for +  example when the output buffer is full (avail_out == 0), or after each +  call of inflate(). If inflate returns Z_OK and with zero avail_out, it +  must be called again after making room in the output buffer because there +  might be more output pending. + +    If the parameter flush is set to Z_SYNC_FLUSH, inflate flushes as much +  output as possible to the output buffer. The flushing behavior of inflate is +  not specified for values of the flush parameter other than Z_SYNC_FLUSH +  and Z_FINISH, but the current implementation actually flushes as much output +  as possible anyway. + +    inflate() should normally be called until it returns Z_STREAM_END or an +  error. However if all decompression is to be performed in a single step +  (a single call of inflate), the parameter flush should be set to +  Z_FINISH. In this case all pending input is processed and all pending +  output is flushed; avail_out must be large enough to hold all the +  uncompressed data. (The size of the uncompressed data may have been saved +  by the compressor for this purpose.) The next operation on this stream must +  be inflateEnd to deallocate the decompression state. The use of Z_FINISH +  is never required, but can be used to inform inflate that a faster routine +  may be used for the single inflate() call. + +     If a preset dictionary is needed at this point (see inflateSetDictionary +  below), inflate sets strm-adler to the adler32 checksum of the +  dictionary chosen by the compressor and returns Z_NEED_DICT; otherwise  +  it sets strm->adler to the adler32 checksum of all output produced +  so (that is, total_out bytes) and returns Z_OK, Z_STREAM_END or +  an error code as described below. At the end of the stream, inflate() +  checks that its computed adler32 checksum is equal to that saved by the +  compressor and returns Z_STREAM_END only if the checksum is correct. + +    inflate() returns Z_OK if some progress has been made (more input processed +  or more output produced), Z_STREAM_END if the end of the compressed data has +  been reached and all uncompressed output has been produced, Z_NEED_DICT if a +  preset dictionary is needed at this point, Z_DATA_ERROR if the input data was +  corrupted (input stream not conforming to the zlib format or incorrect +  adler32 checksum), Z_STREAM_ERROR if the stream structure was inconsistent +  (for example if next_in or next_out was NULL), Z_MEM_ERROR if there was not +  enough memory, Z_BUF_ERROR if no progress is possible or if there was not +  enough room in the output buffer when Z_FINISH is used. In the Z_DATA_ERROR +  case, the application may then call inflateSync to look for a good +  compression block. +*/ + + +static int inflateEnd OF((z_streamp strm)); +/* +     All dynamically allocated data structures for this stream are freed. +   This function discards any unprocessed input and does not flush any +   pending output. + +     inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state +   was inconsistent. In the error case, msg may be set but then points to a +   static string (which must not be deallocated). +*/ + +                        /* Advanced functions */ + +/* +    The following functions are needed only in some special applications. +*/ + +/*    +int deflateInit2 OF((z_streamp strm, +                                     int  level, +                                     int  method, +                                     int  windowBits, +                                     int  memLevel, +                                     int  strategy)); + +     This is another version of deflateInit with more compression options. The +   fields next_in, zalloc, zfree and opaque must be initialized before by +   the caller. + +     The method parameter is the compression method. It must be Z_DEFLATED in +   this version of the library. + +     The windowBits parameter is the base two logarithm of the window size +   (the size of the history buffer).  It should be in the range 8..15 for this +   version of the library. Larger values of this parameter result in better +   compression at the expense of memory usage. The default value is 15 if +   deflateInit is used instead. + +     The memLevel parameter specifies how much memory should be allocated +   for the internal compression state. memLevel=1 uses minimum memory but +   is slow and reduces compression ratio; memLevel=9 uses maximum memory +   for optimal speed. The default value is 8. See zconf.h for total memory +   usage as a function of windowBits and memLevel. + +     The strategy parameter is used to tune the compression algorithm. Use the +   value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a +   filter (or predictor), or Z_HUFFMAN_ONLY to force Huffman encoding only (no +   string match).  Filtered data consists mostly of small values with a +   somewhat random distribution. In this case, the compression algorithm is +   tuned to compress them better. The effect of Z_FILTERED is to force more +   Huffman coding and less string matching; it is somewhat intermediate +   between Z_DEFAULT and Z_HUFFMAN_ONLY. The strategy parameter only affects +   the compression ratio but not the correctness of the compressed output even +   if it is not set appropriately. + +      deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough +   memory, Z_STREAM_ERROR if a parameter is invalid (such as an invalid +   method). msg is set to null if there is no error message.  deflateInit2 does +   not perform any compression: this will be done by deflate(). +*/ +                             +/* +static int deflateSetDictionary OF((z_streamp strm, +                                             const Byte *dictionary, +                                             uInt  dictLength)); +*/ +/* +     Initializes the compression dictionary from the given byte sequence +   without producing any compressed output. This function must be called +   immediately after deflateInit, deflateInit2 or deflateReset, before any +   call of deflate. The compressor and decompressor must use exactly the same +   dictionary (see inflateSetDictionary). + +     The dictionary should consist of strings (byte sequences) that are likely +   to be encountered later in the data to be compressed, with the most commonly +   used strings preferably put towards the end of the dictionary. Using a +   dictionary is most useful when the data to be compressed is short and can be +   predicted with good accuracy; the data can then be compressed better than +   with the default empty dictionary. + +     Depending on the size of the compression data structures selected by +   deflateInit or deflateInit2, a part of the dictionary may in effect be +   discarded, for example if the dictionary is larger than the window size in +   deflate or deflate2. Thus the strings most likely to be useful should be +   put at the end of the dictionary, not at the front. + +     Upon return of this function, strm->adler is set to the Adler32 value +   of the dictionary; the decompressor may later use this value to determine +   which dictionary has been used by the compressor. (The Adler32 value +   applies to the whole dictionary even if only a subset of the dictionary is +   actually used by the compressor.) + +     deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a +   parameter is invalid (such as NULL dictionary) or the stream state is +   inconsistent (for example if deflate has already been called for this stream +   or if the compression method is bsort). deflateSetDictionary does not +   perform any compression: this will be done by deflate(). +*/ + +/* +static int deflateCopy OF((z_streamp dest, +                                    z_streamp source)); +*/ +/* +     Sets the destination stream as a complete copy of the source stream. + +     This function can be useful when several compression strategies will be +   tried, for example when there are several ways of pre-processing the input +   data with a filter. The streams that will be discarded should then be freed +   by calling deflateEnd.  Note that deflateCopy duplicates the internal +   compression state which can be quite large, so this strategy is slow and +   can consume lots of memory. + +     deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not +   enough memory, Z_STREAM_ERROR if the source stream state was inconsistent +   (such as zalloc being NULL). msg is left unchanged in both source and +   destination. +*/ + +// static int deflateReset OF((z_streamp strm)); +/* +     This function is equivalent to deflateEnd followed by deflateInit, +   but does not free and reallocate all the internal compression state. +   The stream will keep the same compression level and any other attributes +   that may have been set by deflateInit2. + +      deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source +   stream state was inconsistent (such as zalloc or state being NULL). +*/ + +/* +static int deflateParams OF((z_streamp strm, +				      int level, +				      int strategy)); +*/ +/* +     Dynamically update the compression level and compression strategy.  The +   interpretation of level and strategy is as in deflateInit2.  This can be +   used to switch between compression and straight copy of the input data, or +   to switch to a different kind of input data requiring a different +   strategy. If the compression level is changed, the input available so far +   is compressed with the old level (and may be flushed); the new level will +   take effect only at the next call of deflate(). + +     Before the call of deflateParams, the stream state must be set as for +   a call of deflate(), since the currently available input may have to +   be compressed and flushed. In particular, strm->avail_out must be non-zero. + +     deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source +   stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR +   if strm->avail_out was zero. +*/ + +/*    +int inflateInit2 OF((z_streamp strm, +                                     int  windowBits)); + +     This is another version of inflateInit with an extra parameter. The +   fields next_in, avail_in, zalloc, zfree and opaque must be initialized +   before by the caller. + +     The windowBits parameter is the base two logarithm of the maximum window +   size (the size of the history buffer).  It should be in the range 8..15 for +   this version of the library. The default value is 15 if inflateInit is used +   instead. If a compressed stream with a larger window size is given as +   input, inflate() will return with the error code Z_DATA_ERROR instead of +   trying to allocate a larger window. + +      inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough +   memory, Z_STREAM_ERROR if a parameter is invalid (such as a negative +   memLevel). msg is set to null if there is no error message.  inflateInit2 +   does not perform any decompression apart from reading the zlib header if +   present: this will be done by inflate(). (So next_in and avail_in may be +   modified, but next_out and avail_out are unchanged.) +*/ + +/* +static int inflateSetDictionary OF((z_streamp strm, +                                             const Byte *dictionary, +                                             uInt  dictLength)); +*/ +/* +     Initializes the decompression dictionary from the given uncompressed byte +   sequence. This function must be called immediately after a call of inflate +   if this call returned Z_NEED_DICT. The dictionary chosen by the compressor +   can be determined from the Adler32 value returned by this call of +   inflate. The compressor and decompressor must use exactly the same +   dictionary (see deflateSetDictionary). + +     inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a +   parameter is invalid (such as NULL dictionary) or the stream state is +   inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the +   expected one (incorrect Adler32 value). inflateSetDictionary does not +   perform any decompression: this will be done by subsequent calls of +   inflate(). +*/ + +// static int inflateSync OF((z_streamp strm)); +/*  +    Skips invalid compressed data until a full flush point (see above the +  description of deflate with Z_FULL_FLUSH) can be found, or until all +  available input is skipped. No output is provided. + +    inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR +  if no more input was provided, Z_DATA_ERROR if no flush point has been found, +  or Z_STREAM_ERROR if the stream structure was inconsistent. In the success +  case, the application may save the current current value of total_in which +  indicates where valid compressed data was found. In the error case, the +  application may repeatedly call inflateSync, providing more input each time, +  until success or end of the input data. +*/ + +static int inflateReset OF((z_streamp strm)); +/* +     This function is equivalent to inflateEnd followed by inflateInit, +   but does not free and reallocate all the internal decompression state. +   The stream will keep attributes that may have been set by inflateInit2. + +      inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source +   stream state was inconsistent (such as zalloc or state being NULL). +*/ + + +                        /* utility functions */ + +/* +     The following utility functions are implemented on top of the +   basic stream-oriented functions. To simplify the interface, some +   default options are assumed (compression level and memory usage, +   standard memory allocation functions). The source code of these +   utility functions can easily be modified if you need special options. +*/ + +/* +static int compress OF((Byte *dest,   uLong *destLen, +                                 const Byte *source, uLong sourceLen)); +*/ +/* +     Compresses the source buffer into the destination buffer.  sourceLen is +   the byte length of the source buffer. Upon entry, destLen is the total +   size of the destination buffer, which must be at least 0.1% larger than +   sourceLen plus 12 bytes. Upon exit, destLen is the actual size of the +   compressed buffer. +     This function can be used to compress a whole file at once if the +   input file is mmap'ed. +     compress returns Z_OK if success, Z_MEM_ERROR if there was not +   enough memory, Z_BUF_ERROR if there was not enough room in the output +   buffer. +*/ + +/* +static int compress2 OF((Byte *dest,   uLong *destLen, +                                  const Byte *source, uLong sourceLen, +                                  int level)); +*/ +/* +     Compresses the source buffer into the destination buffer. The level +   parameter has the same meaning as in deflateInit.  sourceLen is the byte +   length of the source buffer. Upon entry, destLen is the total size of the +   destination buffer, which must be at least 0.1% larger than sourceLen plus +   12 bytes. Upon exit, destLen is the actual size of the compressed buffer. + +     compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough +   memory, Z_BUF_ERROR if there was not enough room in the output buffer, +   Z_STREAM_ERROR if the level parameter is invalid. +*/ + +/* +static int uncompress OF((Byte *dest,   uLong *destLen, +                                   const Byte *source, uLong sourceLen)); +*/                                    +/* +     Decompresses the source buffer into the destination buffer.  sourceLen is +   the byte length of the source buffer. Upon entry, destLen is the total +   size of the destination buffer, which must be large enough to hold the +   entire uncompressed data. (The size of the uncompressed data must have +   been saved previously by the compressor and transmitted to the decompressor +   by some mechanism outside the scope of this compression library.) +   Upon exit, destLen is the actual size of the compressed buffer. +     This function can be used to decompress a whole file at once if the +   input file is mmap'ed. + +     uncompress returns Z_OK if success, Z_MEM_ERROR if there was not +   enough memory, Z_BUF_ERROR if there was not enough room in the output +   buffer, or Z_DATA_ERROR if the input data was corrupted. +*/ + + +typedef voidp gzFile; + +gzFile gzopen  OF((const char *path, const char *mode)); +/* +     Opens a gzip (.gz) file for reading or writing. The mode parameter +   is as in fopen ("rb" or "wb") but can also include a compression level +   ("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for +   Huffman only compression as in "wb1h". (See the description +   of deflateInit2 for more information about the strategy parameter.) + +     gzopen can be used to read a file which is not in gzip format; in this +   case gzread will directly read from the file without decompression. + +     gzopen returns NULL if the file could not be opened or if there was +   insufficient memory to allocate the (de)compression state; errno +   can be checked to distinguish the two cases (if errno is zero, the +   zlib error is Z_MEM_ERROR).  */ + +gzFile gzdopen  OF((int fd, const char *mode)); +/* +     gzdopen() associates a gzFile with the file descriptor fd.  File +   descriptors are obtained from calls like open, dup, creat, pipe or +   fileno (in the file has been previously opened with fopen). +   The mode parameter is as in gzopen. +     The next call of gzclose on the returned gzFile will also close the +   file descriptor fd, just like fclose(fdopen(fd), mode) closes the file +   descriptor fd. If you want to keep fd open, use gzdopen(dup(fd), mode). +     gzdopen returns NULL if there was insufficient memory to allocate +   the (de)compression state. +*/ + +int gzsetparams OF((gzFile file, int level, int strategy)); +/* +     Dynamically update the compression level or strategy. See the description +   of deflateInit2 for the meaning of these parameters. +     gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not +   opened for writing. +*/ + +int    gzread  OF((gzFile file, voidp buf, unsigned len)); +/* +     Reads the given number of uncompressed bytes from the compressed file. +   If the input file was not in gzip format, gzread copies the given number +   of bytes into the buffer. +     gzread returns the number of uncompressed bytes actually read (0 for +   end of file, -1 for error). */ + +int    gzwrite OF((gzFile file,  +				   const voidp buf, unsigned len)); +/* +     Writes the given number of uncompressed bytes into the compressed file. +   gzwrite returns the number of uncompressed bytes actually written +   (0 in case of error). +*/ + +int    QDECL gzprintf OF((gzFile file, const char *format, ...)); +/* +     Converts, formats, and writes the args to the compressed file under +   control of the format string, as in fprintf. gzprintf returns the number of +   uncompressed bytes actually written (0 in case of error). +*/ + +int gzputs OF((gzFile file, const char *s)); +/* +      Writes the given null-terminated string to the compressed file, excluding +   the terminating null character. +      gzputs returns the number of characters written, or -1 in case of error. +*/ + +char * gzgets OF((gzFile file, char *buf, int len)); +/* +      Reads bytes from the compressed file until len-1 characters are read, or +   a newline character is read and transferred to buf, or an end-of-file +   condition is encountered.  The string is then terminated with a null +   character. +      gzgets returns buf, or Z_NULL in case of error. +*/ + +int    gzputc OF((gzFile file, int c)); +/* +      Writes c, converted to an unsigned char, into the compressed file. +   gzputc returns the value that was written, or -1 in case of error. +*/ + +int    gzgetc OF((gzFile file)); +/* +      Reads one byte from the compressed file. gzgetc returns this byte +   or -1 in case of end of file or error. +*/ + +int    gzflush OF((gzFile file, int flush)); +/* +     Flushes all pending output into the compressed file. The parameter +   flush is as in the deflate() function. The return value is the zlib +   error number (see function gzerror below). gzflush returns Z_OK if +   the flush parameter is Z_FINISH and all output could be flushed. +     gzflush should be called only when strictly necessary because it can +   degrade compression. +*/ + +long gzseek OF((gzFile file, +				      long offset, int whence)); +/*  +      Sets the starting position for the next gzread or gzwrite on the +   given compressed file. The offset represents a number of bytes in the +   uncompressed data stream. The whence parameter is defined as in lseek(2); +   the value SEEK_END is not supported. +     If the file is opened for reading, this function is emulated but can be +   extremely slow. If the file is opened for writing, only forward seeks are +   supported; gzseek then compresses a sequence of zeroes up to the new +   starting position. + +      gzseek returns the resulting offset location as measured in bytes from +   the beginning of the uncompressed stream, or -1 in case of error, in +   particular if the file is opened for writing and the new starting position +   would be before the current position. +*/ + +int    gzrewind OF((gzFile file)); +/* +     Rewinds the given file. This function is supported only for reading. + +   gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) +*/ + +long    gztell OF((gzFile file)); +/* +     Returns the starting position for the next gzread or gzwrite on the +   given compressed file. This position represents a number of bytes in the +   uncompressed data stream. + +   gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) +*/ + +int gzeof OF((gzFile file)); +/* +     Returns 1 when EOF has previously been detected reading the given +   input stream, otherwise zero. +*/ + +int    gzclose OF((gzFile file)); +/* +     Flushes all pending output if necessary, closes the compressed file +   and deallocates all the (de)compression state. The return value is the zlib +   error number (see function gzerror below). +*/ + +// static const char * gzerror OF((gzFile file, int *errnum)); +/* +     Returns the error message for the last error which occurred on the +   given compressed file. errnum is set to zlib error number. If an +   error occurred in the file system and not in the compression library, +   errnum is set to Z_ERRNO and the application may consult errno +   to get the exact error code. +*/ + +                        /* checksum functions */ + +/* +     These functions are not related to compression but are exported +   anyway because they might be useful in applications using the +   compression library. +*/ + +static uLong adler32 OF((uLong adler, const Byte *buf, uInt len)); + +/* +     Update a running Adler-32 checksum with the bytes buf[0..len-1] and +   return the updated checksum. If buf is NULL, this function returns +   the required initial value for the checksum. +   An Adler-32 checksum is almost as reliable as a CRC32 but can be computed +   much faster. Usage example: + +     uLong adler = adler32(0L, Z_NULL, 0); + +     while (read_buffer(buffer, length) != EOF) { +       adler = adler32(adler, buffer, length); +     } +     if (adler != original_adler) error(); +*/ + +                        /* various hacks, don't look :) */ + +/* deflateInit and inflateInit are macros to allow checking the zlib version + * and the compiler's view of z_stream: + */ +/* +static int deflateInit_ OF((z_streamp strm, int level, +                                     const char *version, int stream_size)); +static int inflateInit_ OF((z_streamp strm, +                                     const char *version, int stream_size)); +static int deflateInit2_ OF((z_streamp strm, int  level, int  method, +                                      int windowBits, int memLevel, +                                      int strategy, const char *version, +                                      int stream_size)); +*/ +static int inflateInit2_ OF((z_streamp strm, int  windowBits, +                                      const char *version, int stream_size)); + +#define deflateInit(strm, level) \ +        deflateInit_((strm), (level),       ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit(strm) \ +        inflateInit_((strm),                ZLIB_VERSION, sizeof(z_stream)) +#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ +        deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\ +                      (strategy),           ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit2(strm, windowBits) \ +        inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) + + +// static const char   * zError           OF((int err)); +// static int            inflateSyncPoint OF((z_streamp z)); +// static const uLong * get_crc_table    OF((void)); + +typedef unsigned char  uch; +typedef unsigned short ush; +typedef unsigned long  ulg; + +// static const char *z_errmsg[10]; /* indexed by 2-zlib_error */ +/* (size given to avoid silly warnings with Visual C++) */ + +#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] + +#define ERR_RETURN(strm,err) \ +  return (strm->msg = (char*)ERR_MSG(err), (err)) +/* To be used only when the state is known to be valid */ + +        /* common constants */ + +#ifndef DEF_WBITS +#  define DEF_WBITS MAX_WBITS +#endif +/* default windowBits for decompression. MAX_WBITS is for compression only */ + +#if MAX_MEM_LEVEL >= 8 +#  define DEF_MEM_LEVEL 8 +#else +#  define DEF_MEM_LEVEL  MAX_MEM_LEVEL +#endif +/* default memLevel */ + +#define STORED_BLOCK 0 +#define STATIC_TREES 1 +#define DYN_TREES    2 +/* The three kinds of block type */ + +#define MIN_MATCH  3 +#define MAX_MATCH  258 +/* The minimum and maximum match lengths */ + +#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */ + +        /* target dependencies */ + +        /* Common defaults */ + +#ifndef OS_CODE +#  define OS_CODE  0x03  /* assume Unix */ +#endif + +#ifndef F_OPEN +#  define F_OPEN(name, mode) fopen((name), (mode)) +#endif + +         /* functions */ + +#ifdef HAVE_STRERROR +   extern char *strerror OF((int)); +#  define zstrerror(errnum) strerror(errnum) +#else +#  define zstrerror(errnum) "" +#endif + +#define zmemcpy Com_Memcpy +#define zmemcmp memcmp +#define zmemzero(dest, len) Com_Memset(dest, 0, len) + +/* Diagnostic functions */ +#ifdef _ZIP_DEBUG_ +   int z_verbose = 0; +#  define Assert(cond,msg) assert(cond); +   //{if(!(cond)) Sys_Error(msg);} +#  define Trace(x) {if (z_verbose>=0) Sys_Error x ;} +#  define Tracev(x) {if (z_verbose>0) Sys_Error x ;} +#  define Tracevv(x) {if (z_verbose>1) Sys_Error x ;} +#  define Tracec(c,x) {if (z_verbose>0 && (c)) Sys_Error x ;} +#  define Tracecv(c,x) {if (z_verbose>1 && (c)) Sys_Error x ;} +#else +#  define Assert(cond,msg) +#  define Trace(x) +#  define Tracev(x) +#  define Tracevv(x) +#  define Tracec(c,x) +#  define Tracecv(c,x) +#endif + + +typedef uLong (*check_func) OF((uLong check, const Byte *buf, uInt len)); +static voidp zcalloc OF((voidp opaque, unsigned items, unsigned size)); +static void   zcfree  OF((voidp opaque, voidp ptr)); + +#define ZALLOC(strm, items, size) \ +           (*((strm)->zalloc))((strm)->opaque, (items), (size)) +#define ZFREE(strm, addr)  (*((strm)->zfree))((strm)->opaque, (voidp)(addr)) +#define TRY_FREE(s, p) {if (p) ZFREE(s, p);} + + +#if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) && \ +                      !defined(CASESENSITIVITYDEFAULT_NO) +#define CASESENSITIVITYDEFAULT_NO +#endif + + +#ifndef UNZ_BUFSIZE +#define UNZ_BUFSIZE (65536) +#endif + +#ifndef UNZ_MAXFILENAMEINZIP +#define UNZ_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +# define ALLOC(size) (Z_Malloc(size)) +#endif +#ifndef TRYFREE +# define TRYFREE(p) {if (p) Z_Free(p);} +#endif + +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) + + + +/* =========================================================================== +     Read a byte from a gz_stream; update next_in and avail_in. Return EOF +   for end of file. +   IN assertion: the stream s has been sucessfully opened for reading. +*/ + +/* +static int unzlocal_getByte(FILE *fin,int *pi) +{ +    unsigned char c; +	int err = fread(&c, 1, 1, fin); +    if (err==1) +    { +        *pi = (int)c; +        return UNZ_OK; +    } +    else +    { +        if (ferror(fin))  +            return UNZ_ERRNO; +        else +            return UNZ_EOF; +    } +} +*/ + +/* =========================================================================== +   Reads a long in LSB order from the given gz_stream. Sets  +*/ +static int unzlocal_getShort (FILE* fin, uLong *pX) +{ +	short	v; + +	fread( &v, sizeof(v), 1, fin ); + +	*pX = LittleShort( v); +	return UNZ_OK; + +/* +    uLong x ; +    int i; +    int err; + +    err = unzlocal_getByte(fin,&i); +    x = (uLong)i; +     +    if (err==UNZ_OK) +        err = unzlocal_getByte(fin,&i); +    x += ((uLong)i)<<8; +    +    if (err==UNZ_OK) +        *pX = x; +    else +        *pX = 0; +    return err; +*/ +} + +static int unzlocal_getLong (FILE *fin, uLong *pX) +{ +	int		v; + +	fread( &v, sizeof(v), 1, fin ); + +	*pX = LittleLong( v); +	return UNZ_OK; + +/* +    uLong x ; +    int i; +    int err; + +    err = unzlocal_getByte(fin,&i); +    x = (uLong)i; +     +    if (err==UNZ_OK) +        err = unzlocal_getByte(fin,&i); +    x += ((uLong)i)<<8; + +    if (err==UNZ_OK) +        err = unzlocal_getByte(fin,&i); +    x += ((uLong)i)<<16; + +    if (err==UNZ_OK) +        err = unzlocal_getByte(fin,&i); +    x += ((uLong)i)<<24; +    +    if (err==UNZ_OK) +        *pX = x; +    else +        *pX = 0; +    return err; +*/ +} + + +/* My own strcmpi / strcasecmp */ +static int strcmpcasenosensitive_internal (const char* fileName1,const char* fileName2) +{ +	for (;;) +	{ +		char c1=*(fileName1++); +		char c2=*(fileName2++); +		if ((c1>='a') && (c1<='z')) +			c1 -= 0x20; +		if ((c2>='a') && (c2<='z')) +			c2 -= 0x20; +		if (c1=='\0') +			return ((c2=='\0') ? 0 : -1); +		if (c2=='\0') +			return 1; +		if (c1<c2) +			return -1; +		if (c1>c2) +			return 1; +	} +} + + +#ifdef  CASESENSITIVITYDEFAULT_NO +#define CASESENSITIVITYDEFAULTVALUE 2 +#else +#define CASESENSITIVITYDEFAULTVALUE 1 +#endif + +#ifndef STRCMPCASENOSENTIVEFUNCTION +#define STRCMPCASENOSENTIVEFUNCTION strcmpcasenosensitive_internal +#endif + +/*  +   Compare two filename (fileName1,fileName2). +   If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) +   If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi +                                                                or strcasecmp) +   If iCaseSenisivity = 0, case sensitivity is defaut of your operating system +        (like 1 on Unix, 2 on Windows) + +*/ +extern  int unzStringFileNameCompare (const char* fileName1,const char* fileName2,int iCaseSensitivity) +{ +	if (iCaseSensitivity==0) +		iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE; + +	if (iCaseSensitivity==1) +		return strcmp(fileName1,fileName2); + +	return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2); +}  + +#define BUFREADCOMMENT (0x400) + +/* +  Locate the Central directory of a zipfile (at the end, just before +    the global comment) +*/ +extern uLong unzlocal_SearchCentralDir(FILE *fin) +{ +	unsigned char* buf; +	uLong uSizeFile; +	uLong uBackRead; +	uLong uMaxBack=0xffff; /* maximum size of global comment */ +	uLong uPosFound=0; +	 +	if (fseek(fin,0,SEEK_END) != 0) +		return 0; + + +	uSizeFile = ftell( fin ); +	 +	if (uMaxBack>uSizeFile) +		uMaxBack = uSizeFile; + +	buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); +	if (buf==NULL) +		return 0; + +	uBackRead = 4; +	while (uBackRead<uMaxBack) +	{ +		uLong uReadSize,uReadPos ; +		int i; +		if (uBackRead+BUFREADCOMMENT>uMaxBack)  +			uBackRead = uMaxBack; +		else +			uBackRead+=BUFREADCOMMENT; +		uReadPos = uSizeFile-uBackRead ; +		 +		uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ?  +                     (BUFREADCOMMENT+4) : (uSizeFile-uReadPos); +		if (fseek(fin,uReadPos,SEEK_SET)!=0) +			break; + +		if (fread(buf,(uInt)uReadSize,1,fin)!=1) +			break; + +                for (i=(int)uReadSize-3; (i--)>0;) +			if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) &&  +				((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) +			{ +				uPosFound = uReadPos+i; +				break; +			} + +		if (uPosFound!=0) +			break; +	} +	TRYFREE(buf); +	return uPosFound; +} + +extern unzFile unzReOpen (const char* path, unzFile file) +{ +	unz_s *s; +	FILE * fin; + +    fin=fopen(path,"rb"); +	if (fin==NULL) +		return NULL; + +	s=(unz_s*)ALLOC(sizeof(unz_s)); +	Com_Memcpy(s, (unz_s*)file, sizeof(unz_s)); + +	s->file = fin; +	return (unzFile)s;	 +} + +/* +  Open a Zip file. path contain the full pathname (by example, +     on a Windows NT computer "c:\\test\\zlib109.zip" or on an Unix computer +	 "zlib/zlib109.zip". +	 If the zipfile cannot be opened (file don't exist or in not valid), the +	   return value is NULL. +     Else, the return value is a unzFile Handle, usable with other function +	   of this unzip package. +*/ +extern unzFile unzOpen (const char* path) +{ +	unz_s us; +	unz_s *s; +	uLong central_pos,uL; +	FILE * fin ; + +	uLong number_disk;          /* number of the current dist, used for  +								   spaning ZIP, unsupported, always 0*/ +	uLong number_disk_with_CD;  /* number the the disk with central dir, used +								   for spaning ZIP, unsupported, always 0*/ +	uLong number_entry_CD;      /* total number of entries in +	                               the central dir  +	                               (same than number_entry on nospan) */ + +	int err=UNZ_OK; + +    fin=fopen(path,"rb"); +	if (fin==NULL) +		return NULL; + +	central_pos = unzlocal_SearchCentralDir(fin); +	if (central_pos==0) +		err=UNZ_ERRNO; + +	if (fseek(fin,central_pos,SEEK_SET)!=0) +		err=UNZ_ERRNO; + +	/* the signature, already checked */ +	if (unzlocal_getLong(fin,&uL)!=UNZ_OK) +		err=UNZ_ERRNO; + +	/* number of this disk */ +	if (unzlocal_getShort(fin,&number_disk)!=UNZ_OK) +		err=UNZ_ERRNO; + +	/* number of the disk with the start of the central directory */ +	if (unzlocal_getShort(fin,&number_disk_with_CD)!=UNZ_OK) +		err=UNZ_ERRNO; + +	/* total number of entries in the central dir on this disk */ +	if (unzlocal_getShort(fin,&us.gi.number_entry)!=UNZ_OK) +		err=UNZ_ERRNO; + +	/* total number of entries in the central dir */ +	if (unzlocal_getShort(fin,&number_entry_CD)!=UNZ_OK) +		err=UNZ_ERRNO; + +	if ((number_entry_CD!=us.gi.number_entry) || +		(number_disk_with_CD!=0) || +		(number_disk!=0)) +		err=UNZ_BADZIPFILE; + +	/* size of the central directory */ +	if (unzlocal_getLong(fin,&us.size_central_dir)!=UNZ_OK) +		err=UNZ_ERRNO; + +	/* offset of start of central directory with respect to the  +	      starting disk number */ +	if (unzlocal_getLong(fin,&us.offset_central_dir)!=UNZ_OK) +		err=UNZ_ERRNO; + +	/* zipfile comment length */ +	if (unzlocal_getShort(fin,&us.gi.size_comment)!=UNZ_OK) +		err=UNZ_ERRNO; + +	if ((central_pos<us.offset_central_dir+us.size_central_dir) &&  +		(err==UNZ_OK)) +		err=UNZ_BADZIPFILE; + +	if (err!=UNZ_OK) +	{ +		fclose(fin); +		return NULL; +	} + +	us.file=fin; +	us.byte_before_the_zipfile = central_pos - +		                    (us.offset_central_dir+us.size_central_dir); +	us.central_pos = central_pos; +    us.pfile_in_zip_read = NULL; +	 + +	s=(unz_s*)ALLOC(sizeof(unz_s)); +	*s=us; +//	unzGoToFirstFile((unzFile)s);	 +	return (unzFile)s;	 +} + + +/* +  Close a ZipFile opened with unzipOpen. +  If there is files inside the .Zip opened with unzipOpenCurrentFile (see later), +    these files MUST be closed with unzipCloseCurrentFile before call unzipClose. +  return UNZ_OK if there is no problem. */ +extern int unzClose (unzFile file) +{ +	unz_s* s; +	if (file==NULL) +		return UNZ_PARAMERROR; +	s=(unz_s*)file; + +    if (s->pfile_in_zip_read!=NULL) +        unzCloseCurrentFile(file); + +	fclose(s->file); +	TRYFREE(s); +	return UNZ_OK; +} + + +/* +  Write info about the ZipFile in the *pglobal_info structure. +  No preparation of the structure is needed +  return UNZ_OK if there is no problem. */ +extern int unzGetGlobalInfo (unzFile file,unz_global_info *pglobal_info) +{ +	unz_s* s; +	if (file==NULL) +		return UNZ_PARAMERROR; +	s=(unz_s*)file; +	*pglobal_info=s->gi; +	return UNZ_OK; +} + + +/* +   Translate date/time from Dos format to tm_unz (readable more easilty) +*/ +static void unzlocal_DosDateToTmuDate (uLong ulDosDate, tm_unz* ptm) +{ +    uLong uDate; +    uDate = (uLong)(ulDosDate>>16); +    ptm->tm_mday = (uInt)(uDate&0x1f) ; +    ptm->tm_mon =  (uInt)((((uDate)&0x1E0)/0x20)-1) ; +    ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980) ; + +    ptm->tm_hour = (uInt) ((ulDosDate &0xF800)/0x800); +    ptm->tm_min =  (uInt) ((ulDosDate&0x7E0)/0x20) ; +    ptm->tm_sec =  (uInt) (2*(ulDosDate&0x1f)) ; +} + +/* +  Get Info about the current file in the zipfile, with internal only info +*/ +static int unzlocal_GetCurrentFileInfoInternal (unzFile file, +                                                  unz_file_info *pfile_info, +                                                  unz_file_info_internal  +                                                  *pfile_info_internal, +                                                  char *szFileName, +												  uLong fileNameBufferSize, +                                                  void *extraField, +												  uLong extraFieldBufferSize, +                                                  char *szComment, +												  uLong commentBufferSize) +{ +	unz_s* s; +	unz_file_info file_info; +	unz_file_info_internal file_info_internal; +	int err=UNZ_OK; +	uLong uMagic; +	long lSeek=0; + +	if (file==NULL) +		return UNZ_PARAMERROR; +	s=(unz_s*)file; +	if (fseek(s->file,s->pos_in_central_dir+s->byte_before_the_zipfile,SEEK_SET)!=0) +		err=UNZ_ERRNO; + + +	/* we check the magic */ +	if (err==UNZ_OK) { +		if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK) +			err=UNZ_ERRNO; +		else if (uMagic!=0x02014b50) +			err=UNZ_BADZIPFILE; +	} +	if (unzlocal_getShort(s->file,&file_info.version) != UNZ_OK) +		err=UNZ_ERRNO; + +	if (unzlocal_getShort(s->file,&file_info.version_needed) != UNZ_OK) +		err=UNZ_ERRNO; + +	if (unzlocal_getShort(s->file,&file_info.flag) != UNZ_OK) +		err=UNZ_ERRNO; + +	if (unzlocal_getShort(s->file,&file_info.compression_method) != UNZ_OK) +		err=UNZ_ERRNO; + +	if (unzlocal_getLong(s->file,&file_info.dosDate) != UNZ_OK) +		err=UNZ_ERRNO; + +    unzlocal_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date); + +	if (unzlocal_getLong(s->file,&file_info.crc) != UNZ_OK) +		err=UNZ_ERRNO; + +	if (unzlocal_getLong(s->file,&file_info.compressed_size) != UNZ_OK) +		err=UNZ_ERRNO; + +	if (unzlocal_getLong(s->file,&file_info.uncompressed_size) != UNZ_OK) +		err=UNZ_ERRNO; + +	if (unzlocal_getShort(s->file,&file_info.size_filename) != UNZ_OK) +		err=UNZ_ERRNO; + +	if (unzlocal_getShort(s->file,&file_info.size_file_extra) != UNZ_OK) +		err=UNZ_ERRNO; + +	if (unzlocal_getShort(s->file,&file_info.size_file_comment) != UNZ_OK) +		err=UNZ_ERRNO; + +	if (unzlocal_getShort(s->file,&file_info.disk_num_start) != UNZ_OK) +		err=UNZ_ERRNO; + +	if (unzlocal_getShort(s->file,&file_info.internal_fa) != UNZ_OK) +		err=UNZ_ERRNO; + +	if (unzlocal_getLong(s->file,&file_info.external_fa) != UNZ_OK) +		err=UNZ_ERRNO; + +	if (unzlocal_getLong(s->file,&file_info_internal.offset_curfile) != UNZ_OK) +		err=UNZ_ERRNO; + +	lSeek+=file_info.size_filename; +	if ((err==UNZ_OK) && (szFileName!=NULL)) +	{ +		uLong uSizeRead ; +		if (file_info.size_filename<fileNameBufferSize) +		{ +			*(szFileName+file_info.size_filename)='\0'; +			uSizeRead = file_info.size_filename; +		} +		else +			uSizeRead = fileNameBufferSize; + +		if ((file_info.size_filename>0) && (fileNameBufferSize>0)) +			if (fread(szFileName,(uInt)uSizeRead,1,s->file)!=1) +				err=UNZ_ERRNO; +		lSeek -= uSizeRead; +	} + +	 +	if ((err==UNZ_OK) && (extraField!=NULL)) +	{ +		uLong uSizeRead ; +		if (file_info.size_file_extra<extraFieldBufferSize) +			uSizeRead = file_info.size_file_extra; +		else +			uSizeRead = extraFieldBufferSize; + +		if (lSeek!=0) { +			if (fseek(s->file,lSeek,SEEK_CUR)==0) +				lSeek=0; +			else +				err=UNZ_ERRNO; +		} +		if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0)) { +			if (fread(extraField,(uInt)uSizeRead,1,s->file)!=1) +				err=UNZ_ERRNO; +		} +		lSeek += file_info.size_file_extra - uSizeRead; +	} +	else +		lSeek+=file_info.size_file_extra;  + +	 +	if ((err==UNZ_OK) && (szComment!=NULL)) +	{ +		uLong uSizeRead ; +		if (file_info.size_file_comment<commentBufferSize) +		{ +			*(szComment+file_info.size_file_comment)='\0'; +			uSizeRead = file_info.size_file_comment; +		} +		else +			uSizeRead = commentBufferSize; + +		if (lSeek!=0) { +			if (fseek(s->file,lSeek,SEEK_CUR)==0) +				lSeek=0; +			else +				err=UNZ_ERRNO; +		} +		if ((file_info.size_file_comment>0) && (commentBufferSize>0)) { +			if (fread(szComment,(uInt)uSizeRead,1,s->file)!=1) +				err=UNZ_ERRNO; +		} +		lSeek+=file_info.size_file_comment - uSizeRead; +	} +	else +		lSeek+=file_info.size_file_comment; + +	if ((err==UNZ_OK) && (pfile_info!=NULL)) +		*pfile_info=file_info; + +	if ((err==UNZ_OK) && (pfile_info_internal!=NULL)) +		*pfile_info_internal=file_info_internal; + +	return err; +} + + + +/* +  Write info about the ZipFile in the *pglobal_info structure. +  No preparation of the structure is needed +  return UNZ_OK if there is no problem. +*/ +extern int unzGetCurrentFileInfo (	unzFile file, unz_file_info *pfile_info, +									char *szFileName, uLong fileNameBufferSize, +									void *extraField, uLong extraFieldBufferSize, +									char *szComment, uLong commentBufferSize) +{ +	return unzlocal_GetCurrentFileInfoInternal(file,pfile_info,NULL, +												szFileName,fileNameBufferSize, +												extraField,extraFieldBufferSize, +												szComment,commentBufferSize); +} + +/* +  Set the current file of the zipfile to the first file. +  return UNZ_OK if there is no problem +*/ +extern int unzGoToFirstFile (unzFile file) +{ +	int err=UNZ_OK; +	unz_s* s; +	if (file==NULL) +		return UNZ_PARAMERROR; +	s=(unz_s*)file; +	s->pos_in_central_dir=s->offset_central_dir; +	s->num_file=0; +	err=unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, +											 &s->cur_file_info_internal, +											 NULL,0,NULL,0,NULL,0); +	s->current_file_ok = (err == UNZ_OK); +	return err; +} + + +/* +  Set the current file of the zipfile to the next file. +  return UNZ_OK if there is no problem +  return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ +extern int unzGoToNextFile (unzFile file) +{ +	unz_s* s;	 +	int err; + +	if (file==NULL) +		return UNZ_PARAMERROR; +	s=(unz_s*)file; +	if (!s->current_file_ok) +		return UNZ_END_OF_LIST_OF_FILE; +	if (s->num_file+1==s->gi.number_entry) +		return UNZ_END_OF_LIST_OF_FILE; + +	s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename + +			s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment ; +	s->num_file++; +	err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, +											   &s->cur_file_info_internal, +											   NULL,0,NULL,0,NULL,0); +	s->current_file_ok = (err == UNZ_OK); +	return err; +} + +/* +  Get the position of the info of the current file in the zip. +  return UNZ_OK if there is no problem +*/ +extern int unzGetCurrentFileInfoPosition (unzFile file, unsigned long *pos ) +{ +	unz_s* s;	 + +	if (file==NULL) +		return UNZ_PARAMERROR; +	s=(unz_s*)file; + +	*pos = s->pos_in_central_dir; +	return UNZ_OK; +} + +/* +  Set the position of the info of the current file in the zip. +  return UNZ_OK if there is no problem +*/ +extern int unzSetCurrentFileInfoPosition (unzFile file, unsigned long pos ) +{ +	unz_s* s;	 +	int err; + +	if (file==NULL) +		return UNZ_PARAMERROR; +	s=(unz_s*)file; + +	s->pos_in_central_dir = pos; +	err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, +											   &s->cur_file_info_internal, +											   NULL,0,NULL,0,NULL,0); +	s->current_file_ok = (err == UNZ_OK); +	return UNZ_OK; +} + +/* +  Try locate the file szFileName in the zipfile. +  For the iCaseSensitivity signification, see unzipStringFileNameCompare + +  return value : +  UNZ_OK if the file is found. It becomes the current file. +  UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ +extern int unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity) +{ +	unz_s* s;	 +	int err; + +	 +	uLong num_fileSaved; +	uLong pos_in_central_dirSaved; + + +	if (file==NULL) +		return UNZ_PARAMERROR; + +    if (strlen(szFileName)>=UNZ_MAXFILENAMEINZIP) +        return UNZ_PARAMERROR; + +	s=(unz_s*)file; +	if (!s->current_file_ok) +		return UNZ_END_OF_LIST_OF_FILE; + +	num_fileSaved = s->num_file; +	pos_in_central_dirSaved = s->pos_in_central_dir; + +	err = unzGoToFirstFile(file); + +	while (err == UNZ_OK) +	{ +		char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1]; +		unzGetCurrentFileInfo(file,NULL, +								szCurrentFileName,sizeof(szCurrentFileName)-1, +								NULL,0,NULL,0); +		if (unzStringFileNameCompare(szCurrentFileName, +										szFileName,iCaseSensitivity)==0) +			return UNZ_OK; +		err = unzGoToNextFile(file); +	} + +	s->num_file = num_fileSaved ; +	s->pos_in_central_dir = pos_in_central_dirSaved ; +	return err; +} + + +/* +  Read the static header of the current zipfile +  Check the coherency of the static header and info in the end of central +        directory about this file +  store in *piSizeVar the size of extra info in static header +        (filename and size of extra field data) +*/ +static int unzlocal_CheckCurrentFileCoherencyHeader (unz_s* s, uInt* piSizeVar, +													uLong *poffset_local_extrafield, +													uInt *psize_local_extrafield) +{ +	uLong uMagic,uData,uFlags; +	uLong size_filename; +	uLong size_extra_field; +	int err=UNZ_OK; + +	*piSizeVar = 0; +	*poffset_local_extrafield = 0; +	*psize_local_extrafield = 0; + +	if (fseek(s->file,s->cur_file_info_internal.offset_curfile + +								s->byte_before_the_zipfile,SEEK_SET)!=0) +		return UNZ_ERRNO; + + +	if (err==UNZ_OK) { +		if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK) +			err=UNZ_ERRNO; +		else if (uMagic!=0x04034b50) +			err=UNZ_BADZIPFILE; +	} +	if (unzlocal_getShort(s->file,&uData) != UNZ_OK) +		err=UNZ_ERRNO; +/* +	else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion)) +		err=UNZ_BADZIPFILE; +*/ +	if (unzlocal_getShort(s->file,&uFlags) != UNZ_OK) +		err=UNZ_ERRNO; + +	if (unzlocal_getShort(s->file,&uData) != UNZ_OK) +		err=UNZ_ERRNO; +	else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compression_method)) +		err=UNZ_BADZIPFILE; + +    if ((err==UNZ_OK) && (s->cur_file_info.compression_method!=0) && +                         (s->cur_file_info.compression_method!=Z_DEFLATED)) +        err=UNZ_BADZIPFILE; + +	if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* date/time */ +		err=UNZ_ERRNO; + +	if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* crc */ +		err=UNZ_ERRNO; +	else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) && +		                      ((uFlags & 8)==0)) +		err=UNZ_BADZIPFILE; + +	if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* size compr */ +		err=UNZ_ERRNO; +	else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compressed_size) && +							  ((uFlags & 8)==0)) +		err=UNZ_BADZIPFILE; + +	if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* size uncompr */ +		err=UNZ_ERRNO; +	else if ((err==UNZ_OK) && (uData!=s->cur_file_info.uncompressed_size) &&  +							  ((uFlags & 8)==0)) +		err=UNZ_BADZIPFILE; + + +	if (unzlocal_getShort(s->file,&size_filename) != UNZ_OK) +		err=UNZ_ERRNO; +	else if ((err==UNZ_OK) && (size_filename!=s->cur_file_info.size_filename)) +		err=UNZ_BADZIPFILE; + +	*piSizeVar += (uInt)size_filename; + +	if (unzlocal_getShort(s->file,&size_extra_field) != UNZ_OK) +		err=UNZ_ERRNO; +	*poffset_local_extrafield= s->cur_file_info_internal.offset_curfile + +									SIZEZIPLOCALHEADER + size_filename; +	*psize_local_extrafield = (uInt)size_extra_field; + +	*piSizeVar += (uInt)size_extra_field; + +	return err; +} +												 +/* +  Open for reading data the current file in the zipfile. +  If there is no error and the file is opened, the return value is UNZ_OK. +*/ +extern int unzOpenCurrentFile (unzFile file) +{ +	int err=UNZ_OK; +	int Store; +	uInt iSizeVar; +	unz_s* s; +	file_in_zip_read_info_s* pfile_in_zip_read_info; +	uLong offset_local_extrafield;  /* offset of the static extra field */ +	uInt  size_local_extrafield;    /* size of the static extra field */ + +	if (file==NULL) +		return UNZ_PARAMERROR; +	s=(unz_s*)file; +	if (!s->current_file_ok) +		return UNZ_PARAMERROR; + +    if (s->pfile_in_zip_read != NULL) +        unzCloseCurrentFile(file); + +	if (unzlocal_CheckCurrentFileCoherencyHeader(s,&iSizeVar, +				&offset_local_extrafield,&size_local_extrafield)!=UNZ_OK) +		return UNZ_BADZIPFILE; + +	pfile_in_zip_read_info = (file_in_zip_read_info_s*) +									    ALLOC(sizeof(file_in_zip_read_info_s)); +	if (pfile_in_zip_read_info==NULL) +		return UNZ_INTERNALERROR; + +	pfile_in_zip_read_info->read_buffer=(char*)ALLOC(UNZ_BUFSIZE); +	pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield; +	pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield; +	pfile_in_zip_read_info->pos_local_extrafield=0; + +	if (pfile_in_zip_read_info->read_buffer==NULL) +	{ +		TRYFREE(pfile_in_zip_read_info); +		return UNZ_INTERNALERROR; +	} + +	pfile_in_zip_read_info->stream_initialised=0; +	 +	if ((s->cur_file_info.compression_method!=0) && +        (s->cur_file_info.compression_method!=Z_DEFLATED)) +		err=UNZ_BADZIPFILE; +	Store = s->cur_file_info.compression_method==0; + +	pfile_in_zip_read_info->crc32_wait=s->cur_file_info.crc; +	pfile_in_zip_read_info->crc32=0; +	pfile_in_zip_read_info->compression_method = +            s->cur_file_info.compression_method; +	pfile_in_zip_read_info->file=s->file; +	pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile; + +    pfile_in_zip_read_info->stream.total_out = 0; + +	if (!Store) +	{ +	  pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; +	  pfile_in_zip_read_info->stream.zfree = (free_func)0; +	  pfile_in_zip_read_info->stream.opaque = (voidp)0;  +       +	  err=inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS); +	  if (err == Z_OK) +	    pfile_in_zip_read_info->stream_initialised=1; +        /* windowBits is passed < 0 to tell that there is no zlib header. +         * Note that in this case inflate *requires* an extra "dummy" byte +         * after the compressed stream in order to complete decompression and +         * return Z_STREAM_END.  +         * In unzip, i don't wait absolutely Z_STREAM_END because I known the  +         * size of both compressed and uncompressed data +         */ +	} +	pfile_in_zip_read_info->rest_read_compressed =  +            s->cur_file_info.compressed_size ; +	pfile_in_zip_read_info->rest_read_uncompressed =  +            s->cur_file_info.uncompressed_size ; + +	 +	pfile_in_zip_read_info->pos_in_zipfile =  +            s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER +  +			  iSizeVar; +	 +	pfile_in_zip_read_info->stream.avail_in = (uInt)0; + + +	s->pfile_in_zip_read = pfile_in_zip_read_info; +    return UNZ_OK; +} + + +/* +  Read bytes from the current file. +  buf contain buffer where data must be copied +  len the size of buf. + +  return the number of byte copied if somes bytes are copied +  return 0 if the end of file was reached +  return <0 with error code if there is an error +    (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ +extern int unzReadCurrentFile  (unzFile file, void *buf, unsigned len) +{ +	int err=UNZ_OK; +	uInt iRead = 0; +	unz_s* s; +	file_in_zip_read_info_s* pfile_in_zip_read_info; +	if (file==NULL) +		return UNZ_PARAMERROR; +	s=(unz_s*)file; +    pfile_in_zip_read_info=s->pfile_in_zip_read; + +	if (pfile_in_zip_read_info==NULL) +		return UNZ_PARAMERROR; + + +	if ((pfile_in_zip_read_info->read_buffer == NULL)) +		return UNZ_END_OF_LIST_OF_FILE; +	if (len==0) +		return 0; + +	pfile_in_zip_read_info->stream.next_out = (Byte*)buf; + +	pfile_in_zip_read_info->stream.avail_out = (uInt)len; +	 +	if (len>pfile_in_zip_read_info->rest_read_uncompressed) +		pfile_in_zip_read_info->stream.avail_out =  +		  (uInt)pfile_in_zip_read_info->rest_read_uncompressed; + +	while (pfile_in_zip_read_info->stream.avail_out>0) +	{ +		if ((pfile_in_zip_read_info->stream.avail_in==0) && +            (pfile_in_zip_read_info->rest_read_compressed>0)) +		{ +			uInt uReadThis = UNZ_BUFSIZE; +			if (pfile_in_zip_read_info->rest_read_compressed<uReadThis) +				uReadThis = (uInt)pfile_in_zip_read_info->rest_read_compressed; +			if (uReadThis == 0) +				return UNZ_EOF; +			if (s->cur_file_info.compressed_size == pfile_in_zip_read_info->rest_read_compressed) +				if (fseek(pfile_in_zip_read_info->file, +						  pfile_in_zip_read_info->pos_in_zipfile +  +							 pfile_in_zip_read_info->byte_before_the_zipfile,SEEK_SET)!=0) +					return UNZ_ERRNO; +			if (fread(pfile_in_zip_read_info->read_buffer,uReadThis,1, +                         pfile_in_zip_read_info->file)!=1) +				return UNZ_ERRNO; +			pfile_in_zip_read_info->pos_in_zipfile += uReadThis; + +			pfile_in_zip_read_info->rest_read_compressed-=uReadThis; +			 +			pfile_in_zip_read_info->stream.next_in =  +                (Byte*)pfile_in_zip_read_info->read_buffer; +			pfile_in_zip_read_info->stream.avail_in = (uInt)uReadThis; +		} + +		if (pfile_in_zip_read_info->compression_method==0) +		{ +			uInt uDoCopy,i ; +			if (pfile_in_zip_read_info->stream.avail_out <  +                            pfile_in_zip_read_info->stream.avail_in) +				uDoCopy = pfile_in_zip_read_info->stream.avail_out ; +			else +				uDoCopy = pfile_in_zip_read_info->stream.avail_in ; +				 +			for (i=0;i<uDoCopy;i++) +				*(pfile_in_zip_read_info->stream.next_out+i) = +                        *(pfile_in_zip_read_info->stream.next_in+i); +					 +//			pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, +//								pfile_in_zip_read_info->stream.next_out, +//								uDoCopy); +			pfile_in_zip_read_info->rest_read_uncompressed-=uDoCopy; +			pfile_in_zip_read_info->stream.avail_in -= uDoCopy; +			pfile_in_zip_read_info->stream.avail_out -= uDoCopy; +			pfile_in_zip_read_info->stream.next_out += uDoCopy; +			pfile_in_zip_read_info->stream.next_in += uDoCopy; +            pfile_in_zip_read_info->stream.total_out += uDoCopy; +			iRead += uDoCopy; +		} +		else +		{ +			uLong uTotalOutBefore,uTotalOutAfter; +			const Byte *bufBefore; +			uLong uOutThis; +			int flush=Z_SYNC_FLUSH; + +			uTotalOutBefore = pfile_in_zip_read_info->stream.total_out; +			bufBefore = pfile_in_zip_read_info->stream.next_out; + +			/* +			if ((pfile_in_zip_read_info->rest_read_uncompressed == +			         pfile_in_zip_read_info->stream.avail_out) && +				(pfile_in_zip_read_info->rest_read_compressed == 0)) +				flush = Z_FINISH; +			*/ +			err=inflate(&pfile_in_zip_read_info->stream,flush); + +			uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; +			uOutThis = uTotalOutAfter-uTotalOutBefore; +			 +//			pfile_in_zip_read_info->crc32 =  +//                crc32(pfile_in_zip_read_info->crc32,bufBefore, +//                        (uInt)(uOutThis)); + +			pfile_in_zip_read_info->rest_read_uncompressed -= +                uOutThis; + +			iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); +             +			if (err==Z_STREAM_END) +				return (iRead==0) ? UNZ_EOF : iRead; +			if (err!=Z_OK)  +				break; +		} +	} + +	if (err==Z_OK) +		return iRead; +	return err; +} + + +/* +  Give the current position in uncompressed data +*/ +extern long unztell (unzFile file) +{ +	unz_s* s; +	file_in_zip_read_info_s* pfile_in_zip_read_info; +	if (file==NULL) +		return UNZ_PARAMERROR; +	s=(unz_s*)file; +    pfile_in_zip_read_info=s->pfile_in_zip_read; + +	if (pfile_in_zip_read_info==NULL) +		return UNZ_PARAMERROR; + +	return (long)pfile_in_zip_read_info->stream.total_out; +} + + +/* +  return 1 if the end of file was reached, 0 elsewhere  +*/ +extern int unzeof (unzFile file) +{ +	unz_s* s; +	file_in_zip_read_info_s* pfile_in_zip_read_info; +	if (file==NULL) +		return UNZ_PARAMERROR; +	s=(unz_s*)file; +    pfile_in_zip_read_info=s->pfile_in_zip_read; + +	if (pfile_in_zip_read_info==NULL) +		return UNZ_PARAMERROR; +	 +	if (pfile_in_zip_read_info->rest_read_uncompressed == 0) +		return 1; +	else +		return 0; +} + + + +/* +  Read extra field from the current file (opened by unzOpenCurrentFile) +  This is the static-header version of the extra field (sometimes, there is +    more info in the static-header version than in the central-header) + +  if buf==NULL, it return the size of the static extra field that can be read + +  if buf!=NULL, len is the size of the buffer, the extra header is copied in +	buf. +  the return value is the number of bytes copied in buf, or (if <0)  +	the error code +*/ +extern int unzGetLocalExtrafield (unzFile file,void *buf,unsigned len) +{ +	unz_s* s; +	file_in_zip_read_info_s* pfile_in_zip_read_info; +	uInt read_now; +	uLong size_to_read; + +	if (file==NULL) +		return UNZ_PARAMERROR; +	s=(unz_s*)file; +    pfile_in_zip_read_info=s->pfile_in_zip_read; + +	if (pfile_in_zip_read_info==NULL) +		return UNZ_PARAMERROR; + +	size_to_read = (pfile_in_zip_read_info->size_local_extrafield -  +				pfile_in_zip_read_info->pos_local_extrafield); + +	if (buf==NULL) +		return (int)size_to_read; +	 +	if (len>size_to_read) +		read_now = (uInt)size_to_read; +	else +		read_now = (uInt)len ; + +	if (read_now==0) +		return 0; +	 +	if (fseek(pfile_in_zip_read_info->file, +              pfile_in_zip_read_info->offset_local_extrafield +  +			  pfile_in_zip_read_info->pos_local_extrafield,SEEK_SET)!=0) +		return UNZ_ERRNO; + +	if (fread(buf,(uInt)size_to_read,1,pfile_in_zip_read_info->file)!=1) +		return UNZ_ERRNO; + +	return (int)read_now; +} + +/* +  Close the file in zip opened with unzipOpenCurrentFile +  Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ +extern int unzCloseCurrentFile (unzFile file) +{ +	int err=UNZ_OK; + +	unz_s* s; +	file_in_zip_read_info_s* pfile_in_zip_read_info; +	if (file==NULL) +		return UNZ_PARAMERROR; +	s=(unz_s*)file; +    pfile_in_zip_read_info=s->pfile_in_zip_read; + +	if (pfile_in_zip_read_info==NULL) +		return UNZ_PARAMERROR; + +/* +	if (pfile_in_zip_read_info->rest_read_uncompressed == 0) +	{ +		if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) +			err=UNZ_CRCERROR; +	} +*/ + +	TRYFREE(pfile_in_zip_read_info->read_buffer); +	pfile_in_zip_read_info->read_buffer = NULL; +	if (pfile_in_zip_read_info->stream_initialised) +		inflateEnd(&pfile_in_zip_read_info->stream); + +	pfile_in_zip_read_info->stream_initialised = 0; +	TRYFREE(pfile_in_zip_read_info); + +    s->pfile_in_zip_read=NULL; + +	return err; +} + + +/* +  Get the global comment string of the ZipFile, in the szComment buffer. +  uSizeBuf is the size of the szComment buffer. +  return the number of byte copied or an error code <0 +*/ +extern int unzGetGlobalComment (unzFile file, char *szComment, uLong uSizeBuf) +{ +	unz_s* s; +	uLong uReadThis ; +	if (file==NULL) +		return UNZ_PARAMERROR; +	s=(unz_s*)file; + +	uReadThis = uSizeBuf; +	if (uReadThis>s->gi.size_comment) +		uReadThis = s->gi.size_comment; + +	if (fseek(s->file,s->central_pos+22,SEEK_SET)!=0) +		return UNZ_ERRNO; + +	if (uReadThis>0) +    { +      *szComment='\0'; +	  if (fread(szComment,(uInt)uReadThis,1,s->file)!=1) +		return UNZ_ERRNO; +    } + +	if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment)) +		*(szComment+s->gi.size_comment)='\0'; +	return (int)uReadThis; +} + +/* infblock.h -- header to use infblock.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h  + */ + +/* WARNING: this file should *not* be used by applications. It is +   part of the implementation of the compression library and is +   subject to change. Applications should only use zlib.h. + */ + +struct inflate_blocks_state; +typedef struct inflate_blocks_state inflate_blocks_statef; + +static inflate_blocks_statef * inflate_blocks_new OF(( +    z_streamp z, +    check_func c,               /* check function */ +    uInt w));                   /* window size */ + +static int inflate_blocks OF(( +    inflate_blocks_statef *, +    z_streamp , +    int));                      /* initial return code */ + +static void inflate_blocks_reset OF(( +    inflate_blocks_statef *, +    z_streamp , +    uLong *));                  /* check value on output */ + +static int inflate_blocks_free OF(( +    inflate_blocks_statef *, +    z_streamp)); + +#if 0 +static void inflate_set_dictionary OF(( +    inflate_blocks_statef *s, +    const Byte *d,  /* dictionary */ +    uInt  n));       /* dictionary length */ + +static int inflate_blocks_sync_point OF(( +    inflate_blocks_statef *s)); +#endif + +/* simplify the use of the inflate_huft type with some defines */ +#define exop word.what.Exop +#define bits word.what.Bits + +/* Table for deflate from PKZIP's appnote.txt. */ +static const uInt border[] = { /* Order of the bit length code lengths */ +        16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* inftrees.h -- header to use inftrees.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h  + */ + +/* WARNING: this file should *not* be used by applications. It is +   part of the implementation of the compression library and is +   subject to change. Applications should only use zlib.h. + */ + +/* Huffman code lookup table entry--this entry is four bytes for machines +   that have 16-bit pointers (e.g. PC's in the small or medium model). */ + +typedef struct inflate_huft_s inflate_huft; + +struct inflate_huft_s { +  union { +    struct { +      Byte Exop;        /* number of extra bits or operation */ +      Byte Bits;        /* number of bits in this code or subcode */ +    } what; +    uInt pad;           /* pad structure to a power of 2 (4 bytes for */ +  } word;               /*  16-bit, 8 bytes for 32-bit int's) */ +  uInt base;            /* literal, length base, distance base, +                           or table offset */ +}; + +/* Maximum size of dynamic tree.  The maximum found in a long but non- +   exhaustive search was 1004 huft structures (850 for length/literals +   and 154 for distances, the latter actually the result of an +   exhaustive search).  The actual maximum is not known, but the +   value below is more than safe. */ +#define MANY 1440 + +static  int inflate_trees_bits OF(( +    uInt *,                    /* 19 code lengths */ +    uInt *,                    /* bits tree desired/actual depth */ +    inflate_huft * *,       /* bits tree result */ +    inflate_huft *,             /* space for trees */ +    z_streamp));                /* for messages */ + +static  int inflate_trees_dynamic OF(( +    uInt,                       /* number of literal/length codes */ +    uInt,                       /* number of distance codes */ +    uInt *,                    /* that many (total) code lengths */ +    uInt *,                    /* literal desired/actual bit depth */ +    uInt *,                    /* distance desired/actual bit depth */ +    inflate_huft * *,       /* literal/length tree result */ +    inflate_huft * *,       /* distance tree result */ +    inflate_huft *,             /* space for trees */ +    z_streamp));                /* for messages */ + +static  int inflate_trees_fixed OF(( +    uInt *,                    /* literal desired/actual bit depth */ +    uInt *,                    /* distance desired/actual bit depth */ +    inflate_huft * *,       /* literal/length tree result */ +    inflate_huft * *,       /* distance tree result */ +    z_streamp));                /* for memory allocation */ + + +/* infcodes.h -- header to use infcodes.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h  + */ + +/* WARNING: this file should *not* be used by applications. It is +   part of the implementation of the compression library and is +   subject to change. Applications should only use zlib.h. + */ + +struct inflate_codes_state; +typedef struct inflate_codes_state inflate_codes_statef; + +static inflate_codes_statef *inflate_codes_new OF(( +    uInt, uInt, +    inflate_huft *, inflate_huft *, +    z_streamp )); + +static  int inflate_codes OF(( +    inflate_blocks_statef *, +    z_streamp , +    int)); + +static  void inflate_codes_free OF(( +    inflate_codes_statef *, +    z_streamp )); + +/* infutil.h -- types and macros common to blocks and codes + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h  + */ + +/* WARNING: this file should *not* be used by applications. It is +   part of the implementation of the compression library and is +   subject to change. Applications should only use zlib.h. + */ + +#ifndef _INFUTIL_H +#define _INFUTIL_H + +typedef enum { +      TYPE,     /* get type bits (3, including end bit) */ +      LENS,     /* get lengths for stored */ +      STORED,   /* processing stored block */ +      TABLE,    /* get table lengths */ +      BTREE,    /* get bit lengths tree for a dynamic block */ +      DTREE,    /* get length, distance trees for a dynamic block */ +      CODES,    /* processing fixed or dynamic block */ +      DRY,      /* output remaining window bytes */ +      DONE,     /* finished last block, done */ +      BAD}      /* got a data error--stuck here */ +inflate_block_mode; + +/* inflate blocks semi-private state */ +struct inflate_blocks_state { + +  /* mode */ +  inflate_block_mode  mode;     /* current inflate_block mode */ + +  /* mode dependent information */ +  union { +    uInt left;          /* if STORED, bytes left to copy */ +    struct { +      uInt table;               /* table lengths (14 bits) */ +      uInt index;               /* index into blens (or border) */ +      uInt *blens;             /* bit lengths of codes */ +      uInt bb;                  /* bit length tree depth */ +      inflate_huft *tb;         /* bit length decoding tree */ +    } trees;            /* if DTREE, decoding info for trees */ +    struct { +      inflate_codes_statef  +         *codes; +    } decode;           /* if CODES, current state */ +  } sub;                /* submode */ +  uInt last;            /* true if this block is the last block */ + +  /* mode independent information */ +  uInt bitk;            /* bits in bit buffer */ +  uLong bitb;           /* bit buffer */ +  inflate_huft *hufts;  /* single malloc for tree space */ +  Byte *window;        /* sliding window */ +  Byte *end;           /* one byte after sliding window */ +  Byte *read;          /* window read pointer */ +  Byte *write;         /* window write pointer */ +  check_func checkfn;   /* check function */ +  uLong check;          /* check on output */ + +}; + + +/* defines for inflate input/output */ +/*   update pointers and return */ +#define UPDBITS {s->bitb=b;s->bitk=k;} +#define UPDIN {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} +#define UPDOUT {s->write=q;} +#define UPDATE {UPDBITS UPDIN UPDOUT} +#define LEAVE {UPDATE return inflate_flush(s,z,r);} +/*   get bytes and bits */ +#define LOADIN {p=z->next_in;n=z->avail_in;b=s->bitb;k=s->bitk;} +#define NEEDBYTE {if(n)r=Z_OK;else LEAVE} +#define NEXTBYTE (n--,*p++) +#define NEEDBITS(j) {while(k<(j)){NEEDBYTE;b|=((uLong)NEXTBYTE)<<k;k+=8;}} +#define DUMPBITS(j) {b>>=(j);k-=(j);} +/*   output bytes */ +#define WAVAIL (uInt)(q<s->read?s->read-q-1:s->end-q) +#define LOADOUT {q=s->write;m=(uInt)WAVAIL;} +#define WRAP {if(q==s->end&&s->read!=s->window){q=s->window;m=(uInt)WAVAIL;}} +#define FLUSH {UPDOUT r=inflate_flush(s,z,r); LOADOUT} +#define NEEDOUT {if(m==0){WRAP if(m==0){FLUSH WRAP if(m==0) LEAVE}}r=Z_OK;} +#define OUTBYTE(a) {*q++=(Byte)(a);m--;} +/*   load static pointers */ +#define LOAD {LOADIN LOADOUT} + +/* masks for lower bits (size given to avoid silly warnings with Visual C++) */ +static  uInt inflate_mask[17]; + +/* copy as much as possible from the sliding window to the output area */ +static  int inflate_flush OF(( +    inflate_blocks_statef *, +    z_streamp , +    int)); + +#endif + +								 +/* +   Notes beyond the 1.93a appnote.txt: + +   1. Distance pointers never point before the beginning of the output +      stream. +   2. Distance pointers can point back across blocks, up to 32k away. +   3. There is an implied maximum of 7 bits for the bit length table and +      15 bits for the actual data. +   4. If only one code exists, then it is encoded using one bit.  (Zero +      would be more efficient, but perhaps a little confusing.)  If two +      codes exist, they are coded using one bit each (0 and 1). +   5. There is no way of sending zero distance codes--a dummy must be +      sent if there are none.  (History: a pre 2.0 version of PKZIP would +      store blocks with no distance codes, but this was discovered to be +      too harsh a criterion.)  Valid only for 1.93a.  2.04c does allow +      zero distance codes, which is sent as one code of zero bits in +      length. +   6. There are up to 286 literal/length codes.  Code 256 represents the +      end-of-block.  Note however that the static length tree defines +      288 codes just to fill out the Huffman codes.  Codes 286 and 287 +      cannot be used though, since there is no length base or extra bits +      defined for them.  Similarily, there are up to 30 distance codes. +      However, static trees define 32 codes (all 5 bits) to fill out the +      Huffman codes, but the last two had better not show up in the data. +   7. Unzip can check dynamic Huffman blocks for complete code sets. +      The exception is that a single code would not be complete (see #4). +   8. The five bits following the block type is really the number of +      literal codes sent minus 257. +   9. Length codes 8,16,16 are interpreted as 13 length codes of 8 bits +      (1+6+6).  Therefore, to output three times the length, you output +      three codes (1+1+1), whereas to output four times the same length, +      you only need two codes (1+3).  Hmm. +  10. In the tree reconstruction algorithm, Code = Code + Increment +      only if BitLength(i) is not zero.  (Pretty obvious.) +  11. Correction: 4 Bits: # of Bit Length codes - 4     (4 - 19) +  12. Note: length code 284 can represent 227-258, but length code 285 +      really is 258.  The last length deserves its own, short code +      since it gets used a lot in very redundant files.  The length +      258 is special since 258 - 3 (the min match length) is 255. +  13. The literal/length and distance code bit lengths are read as a +      single stream of lengths.  It is possible (and advantageous) for +      a repeat code (16, 17, or 18) to go across the boundary between +      the two sets of lengths. + */ + + +void inflate_blocks_reset(inflate_blocks_statef *s, z_streamp z, uLong *c) +{ +  if (c != Z_NULL) +    *c = s->check; +  if (s->mode == BTREE || s->mode == DTREE) +    ZFREE(z, s->sub.trees.blens); +  if (s->mode == CODES) +    inflate_codes_free(s->sub.decode.codes, z); +  s->mode = TYPE; +  s->bitk = 0; +  s->bitb = 0; +  s->read = s->write = s->window; +  if (s->checkfn != Z_NULL) +    z->adler = s->check = (*s->checkfn)(0L, (const Byte *)Z_NULL, 0); +  Tracev(("inflate:   blocks reset\n")); +} + + +inflate_blocks_statef *inflate_blocks_new(z_streamp z, check_func c, uInt w) +{ +  inflate_blocks_statef *s; + +  if ((s = (inflate_blocks_statef *)ZALLOC +       (z,1,sizeof(struct inflate_blocks_state))) == Z_NULL) +    return s; +  if ((s->hufts = +       (inflate_huft *)ZALLOC(z, sizeof(inflate_huft), MANY)) == Z_NULL) +  { +    ZFREE(z, s); +    return Z_NULL; +  } +  if ((s->window = (Byte *)ZALLOC(z, 1, w)) == Z_NULL) +  { +    ZFREE(z, s->hufts); +    ZFREE(z, s); +    return Z_NULL; +  } +  s->end = s->window + w; +  s->checkfn = c; +  s->mode = TYPE; +  Tracev(("inflate:   blocks allocated\n")); +  inflate_blocks_reset(s, z, Z_NULL); +  return s; +} + + +int inflate_blocks(inflate_blocks_statef *s, z_streamp z, int r) +{ +  uInt t;               /* temporary storage */ +  uLong b;              /* bit buffer */ +  uInt k;               /* bits in bit buffer */ +  Byte *p;             /* input data pointer */ +  uInt n;               /* bytes available there */ +  Byte *q;             /* output window write pointer */ +  uInt m;               /* bytes to end of window or read pointer */ + +  /* copy input/output information to locals (UPDATE macro restores) */ +  LOAD + +  /* process input based on current state */ +  while (1) switch (s->mode) +  { +    case TYPE: +      NEEDBITS(3) +      t = (uInt)b & 7; +      s->last = t & 1; +      switch (t >> 1) +      { +        case 0:                         /* stored */ +          Tracev(("inflate:     stored block%s\n", +                 s->last ? " (last)" : "")); +          DUMPBITS(3) +          t = k & 7;                    /* go to byte boundary */ +          DUMPBITS(t) +          s->mode = LENS;               /* get length of stored block */ +          break; +        case 1:                         /* fixed */ +          Tracev(("inflate:     fixed codes block%s\n", +                 s->last ? " (last)" : "")); +          { +            uInt bl, bd; +            inflate_huft *tl, *td; +            inflate_trees_fixed(&bl, &bd, &tl, &td, z); +            s->sub.decode.codes = inflate_codes_new(bl, bd, tl, td, z); +            if (s->sub.decode.codes == Z_NULL) +            { +              r = Z_MEM_ERROR; +              LEAVE +            } +          } +          DUMPBITS(3) +          s->mode = CODES; +          break; +        case 2:                         /* dynamic */ +          Tracev(("inflate:     dynamic codes block%s\n", +                 s->last ? " (last)" : "")); +          DUMPBITS(3) +          s->mode = TABLE; +          break; +        case 3:                         /* illegal */ +          DUMPBITS(3) +          s->mode = BAD; +          z->msg = (char*)"invalid block type"; +          r = Z_DATA_ERROR; +          LEAVE +      } +      break; +    case LENS: +      NEEDBITS(32) +      if ((((~b) >> 16) & 0xffff) != (b & 0xffff)) +      { +        s->mode = BAD; +        z->msg = (char*)"invalid stored block lengths"; +        r = Z_DATA_ERROR; +        LEAVE +      } +      s->sub.left = (uInt)b & 0xffff; +      b = k = 0;                      /* dump bits */ +      Tracev(("inflate:       stored length %u\n", s->sub.left)); +      s->mode = s->sub.left ? STORED : (s->last ? DRY : TYPE); +      break; +    case STORED: +      if (n == 0) +        LEAVE +      NEEDOUT +      t = s->sub.left; +      if (t > n) t = n; +      if (t > m) t = m; +      zmemcpy(q, p, t); +      p += t;  n -= t; +      q += t;  m -= t; +      if ((s->sub.left -= t) != 0) +        break; +      Tracev(("inflate:       stored end, %lu total out\n", +              z->total_out + (q >= s->read ? q - s->read : +              (s->end - s->read) + (q - s->window)))); +      s->mode = s->last ? DRY : TYPE; +      break; +    case TABLE: +      NEEDBITS(14) +      s->sub.trees.table = t = (uInt)b & 0x3fff; +#ifndef PKZIP_BUG_WORKAROUND +      if ((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29) +      { +        s->mode = BAD; +        z->msg = (char*)"too many length or distance symbols"; +        r = Z_DATA_ERROR; +        LEAVE +      } +#endif +      t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f); +      if ((s->sub.trees.blens = (uInt*)ZALLOC(z, t, sizeof(uInt))) == Z_NULL) +      { +        r = Z_MEM_ERROR; +        LEAVE +      } +      DUMPBITS(14) +      s->sub.trees.index = 0; +      Tracev(("inflate:       table sizes ok\n")); +      s->mode = BTREE; +    case BTREE: +      while (s->sub.trees.index < 4 + (s->sub.trees.table >> 10)) +      { +        NEEDBITS(3) +        s->sub.trees.blens[border[s->sub.trees.index++]] = (uInt)b & 7; +        DUMPBITS(3) +      } +      while (s->sub.trees.index < 19) +        s->sub.trees.blens[border[s->sub.trees.index++]] = 0; +      s->sub.trees.bb = 7; +      t = inflate_trees_bits(s->sub.trees.blens, &s->sub.trees.bb, +                             &s->sub.trees.tb, s->hufts, z); +      if (t != Z_OK) +      { +        ZFREE(z, s->sub.trees.blens); +        r = t; +        if (r == Z_DATA_ERROR) +          s->mode = BAD; +        LEAVE +      } +      s->sub.trees.index = 0; +      Tracev(("inflate:       bits tree ok\n")); +      s->mode = DTREE; +    case DTREE: +      while (t = s->sub.trees.table, +             s->sub.trees.index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f)) +      { +        inflate_huft *h; +        uInt i, j, c; + +        t = s->sub.trees.bb; +        NEEDBITS(t) +        h = s->sub.trees.tb + ((uInt)b & inflate_mask[t]); +        t = h->bits; +        c = h->base; +        if (c < 16) +        { +          DUMPBITS(t) +          s->sub.trees.blens[s->sub.trees.index++] = c; +        } +        else /* c == 16..18 */ +        { +          i = c == 18 ? 7 : c - 14; +          j = c == 18 ? 11 : 3; +          NEEDBITS(t + i) +          DUMPBITS(t) +          j += (uInt)b & inflate_mask[i]; +          DUMPBITS(i) +          i = s->sub.trees.index; +          t = s->sub.trees.table; +          if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || +              (c == 16 && i < 1)) +          { +            ZFREE(z, s->sub.trees.blens); +            s->mode = BAD; +            z->msg = (char*)"invalid bit length repeat"; +            r = Z_DATA_ERROR; +            LEAVE +          } +          c = c == 16 ? s->sub.trees.blens[i - 1] : 0; +          do { +            s->sub.trees.blens[i++] = c; +          } while (--j); +          s->sub.trees.index = i; +        } +      } +      s->sub.trees.tb = Z_NULL; +      { +        uInt bl, bd; +        inflate_huft *tl, *td; +        inflate_codes_statef *c; + +        tl = NULL; +        td = NULL; +        bl = 9;         /* must be <= 9 for lookahead assumptions */ +        bd = 6;         /* must be <= 9 for lookahead assumptions */ +        t = s->sub.trees.table; +        t = inflate_trees_dynamic(257 + (t & 0x1f), 1 + ((t >> 5) & 0x1f), +                                  s->sub.trees.blens, &bl, &bd, &tl, &td, +                                  s->hufts, z); +        ZFREE(z, s->sub.trees.blens); +        if (t != Z_OK) +        { +          if (t == (uInt)Z_DATA_ERROR) +            s->mode = BAD; +          r = t; +          LEAVE +        } +        Tracev(("inflate:       trees ok\n")); +        if ((c = inflate_codes_new(bl, bd, tl, td, z)) == Z_NULL) +        { +          r = Z_MEM_ERROR; +          LEAVE +        } +        s->sub.decode.codes = c; +      } +      s->mode = CODES; +    case CODES: +      UPDATE +      if ((r = inflate_codes(s, z, r)) != Z_STREAM_END) +        return inflate_flush(s, z, r); +      r = Z_OK; +      inflate_codes_free(s->sub.decode.codes, z); +      LOAD +      Tracev(("inflate:       codes end, %lu total out\n", +              z->total_out + (q >= s->read ? q - s->read : +              (s->end - s->read) + (q - s->window)))); +      if (!s->last) +      { +        s->mode = TYPE; +        break; +      } +      s->mode = DRY; +    case DRY: +      FLUSH +      if (s->read != s->write) +        LEAVE +      s->mode = DONE; +    case DONE: +      r = Z_STREAM_END; +      LEAVE +    case BAD: +      r = Z_DATA_ERROR; +      LEAVE +    default: +      r = Z_STREAM_ERROR; +      LEAVE +  } +} + + +int inflate_blocks_free(inflate_blocks_statef *s, z_streamp z) +{ +  inflate_blocks_reset(s, z, Z_NULL); +  ZFREE(z, s->window); +  ZFREE(z, s->hufts); +  ZFREE(z, s); +  Tracev(("inflate:   blocks freed\n")); +  return Z_OK; +} + +#if 0 +void inflate_set_dictionary(inflate_blocks_statef *s, const Byte *d, uInt n) +{ +  zmemcpy(s->window, d, n); +  s->read = s->write = s->window + n; +} + +/* Returns true if inflate is currently at the end of a block generated + * by Z_SYNC_FLUSH or Z_FULL_FLUSH.  + * IN assertion: s != Z_NULL + */ +int inflate_blocks_sync_point(inflate_blocks_statef *s) +{ +  return s->mode == LENS; +} +#endif + + +/* And'ing with mask[n] masks the lower n bits */ +static uInt inflate_mask[17] = { +    0x0000, +    0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, +    0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff +}; + + +/* copy as much as possible from the sliding window to the output area */ +int inflate_flush(inflate_blocks_statef *s, z_streamp z, int r) +{ +  uInt n; +  Byte *p; +  Byte *q; + +  /* static copies of source and destination pointers */ +  p = z->next_out; +  q = s->read; + +  /* compute number of bytes to copy as as end of window */ +  n = (uInt)((q <= s->write ? s->write : s->end) - q); +  if (n > z->avail_out) n = z->avail_out; +  if (n && r == Z_BUF_ERROR) r = Z_OK; + +  /* update counters */ +  z->avail_out -= n; +  z->total_out += n; + +  /* update check information */ +  if (s->checkfn != Z_NULL) +    z->adler = s->check = (*s->checkfn)(s->check, q, n); + +  /* copy as as end of window */ +  zmemcpy(p, q, n); +  p += n; +  q += n; + +  /* see if more to copy at beginning of window */ +  if (q == s->end) +  { +    /* wrap pointers */ +    q = s->window; +    if (s->write == s->end) +      s->write = s->window; + +    /* compute bytes to copy */ +    n = (uInt)(s->write - q); +    if (n > z->avail_out) n = z->avail_out; +    if (n && r == Z_BUF_ERROR) r = Z_OK; + +    /* update counters */ +    z->avail_out -= n; +    z->total_out += n; + +    /* update check information */ +    if (s->checkfn != Z_NULL) +      z->adler = s->check = (*s->checkfn)(s->check, q, n); + +    /* copy */ +    zmemcpy(p, q, n); +    p += n; +    q += n; +  } + +  /* update pointers */ +  z->next_out = p; +  s->read = q; + +  /* done */ +  return r; +} + +/* inftrees.c -- generate Huffman trees for efficient decoding + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h  + */ + +/* +  If you use the zlib library in a product, an acknowledgment is welcome +  in the documentation of your product. If for some reason you cannot +  include such an acknowledgment, I would appreciate that you keep this +  copyright string in the executable of your product. + */ + +/* simplify the use of the inflate_huft type with some defines */ +#define exop word.what.Exop +#define bits word.what.Bits + + +static int huft_build OF(( +    uInt *,				/* code lengths in bits */ +    uInt,               /* number of codes */ +    uInt,               /* number of "simple" codes */ +    const uInt *,		/* list of base values for non-simple codes */ +    const uInt *,		/* list of extra bits for non-simple codes */ +    inflate_huft **,	/* result: starting table */ +    uInt *,				/* maximum lookup bits (returns actual) */ +    inflate_huft *,     /* space for trees */ +    uInt *,             /* hufts used in space */ +    uInt * ));			/* space for values */ + +/* Tables for deflate from PKZIP's appnote.txt. */ +static const uInt cplens[31] = { /* Copy lengths for literal codes 257..285 */ +        3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, +        35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; +        /* see note #13 above about 258 */ +static const uInt cplext[31] = { /* Extra bits for literal codes 257..285 */ +        0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, +        3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112}; /* 112==invalid */ +static const uInt cpdist[30] = { /* Copy offsets for distance codes 0..29 */ +        1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, +        257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, +        8193, 12289, 16385, 24577}; +static const uInt cpdext[30] = { /* Extra bits for distance codes */ +        0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, +        7, 7, 8, 8, 9, 9, 10, 10, 11, 11, +        12, 12, 13, 13}; + +/* +   Huffman code decoding is performed using a multi-level table lookup. +   The fastest way to decode is to simply build a lookup table whose +   size is determined by the longest code.  However, the time it takes +   to build this table can also be a factor if the data being decoded +   is not very long.  The most common codes are necessarily the +   shortest codes, so those codes dominate the decoding time, and hence +   the speed.  The idea is you can have a shorter table that decodes the +   shorter, more probable codes, and then point to subsidiary tables for +   the longer codes.  The time it costs to decode the longer codes is +   then traded against the time it takes to make longer tables. + +   This results of this trade are in the variables lbits and dbits +   below.  lbits is the number of bits the first level table for literal/ +   length codes can decode in one step, and dbits is the same thing for +   the distance codes.  Subsequent tables are also less than or equal to +   those sizes.  These values may be adjusted either when all of the +   codes are shorter than that, in which case the longest code length in +   bits is used, or when the shortest code is *longer* than the requested +   table size, in which case the length of the shortest code in bits is +   used. + +   There are two different values for the two tables, since they code a +   different number of possibilities each.  The literal/length table +   codes 286 possible values, or in a flat code, a little over eight +   bits.  The distance table codes 30 possible values, or a little less +   than five bits, flat.  The optimum values for speed end up being +   about one bit more than those, so lbits is 8+1 and dbits is 5+1. +   The optimum values may differ though from machine to machine, and +   possibly even between compilers.  Your mileage may vary. + */ + + +/* If BMAX needs to be larger than 16, then h and x[] should be uLong. */ +#define BMAX 15         /* maximum bit length of any code */ + +static int huft_build(uInt *b, uInt n, uInt s, const uInt *d, const uInt *e, inflate_huft ** t, uInt *m, inflate_huft *hp, uInt *hn, uInt *v) +//uInt *b;               /* code lengths in bits (all assumed <= BMAX) */ +//uInt n;                 /* number of codes (assumed <= 288) */ +//uInt s;                 /* number of simple-valued codes (0..s-1) */ +//const uInt *d;         /* list of base values for non-simple codes */ +//const uInt *e;         /* list of extra bits for non-simple codes */ +//inflate_huft ** t;		/* result: starting table */ +//uInt *m;               /* maximum lookup bits, returns actual */ +//inflate_huft *hp;       /* space for trees */ +//uInt *hn;               /* hufts used in space */ +//uInt *v;               /* working area: values in order of bit length */ +/* Given a list of code lengths and a maximum table size, make a set of +   tables to decode that set of codes.  Return Z_OK on success, Z_BUF_ERROR +   if the given code set is incomplete (the tables are still built in this +   case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of +   lengths), or Z_MEM_ERROR if not enough memory. */ +{ + +  uInt a;                       /* counter for codes of length k */ +  uInt c[BMAX+1];               /* bit length count table */ +  uInt f;                       /* i repeats in table every f entries */ +  int g;                        /* maximum code length */ +  int h;                        /* table level */ +  register uInt i;              /* counter, current code */ +  register uInt j;              /* counter */ +  register int k;               /* number of bits in current code */ +  int l;                        /* bits per table (returned in m) */ +  uInt mask;                    /* (1 << w) - 1, to avoid cc -O bug on HP */ +  register uInt *p;            /* pointer into c[], b[], or v[] */ +  inflate_huft *q;              /* points to current table */ +  struct inflate_huft_s r = {{{0, 0}}};      /* table entry for structure assignment */ +  inflate_huft *u[BMAX];        /* table stack */ +  register int w;               /* bits before this table == (l * h) */ +  uInt x[BMAX+1];               /* bit offsets, then code stack */ +  uInt *xp;                    /* pointer into x */ +  int y;                        /* number of dummy codes added */ +  uInt z;                       /* number of entries in current table */ + + +  /* Generate counts for each bit length */ +  p = c; +#define C0 *p++ = 0; +#define C2 C0 C0 C0 C0 +#define C4 C2 C2 C2 C2 +  C4                            /* clear c[]--assume BMAX+1 is 16 */ +  p = b;  i = n; +  do { +    c[*p++]++;                  /* assume all entries <= BMAX */ +  } while (--i); +  if (c[0] == n)                /* null input--all zero length codes */ +  { +    *t = (inflate_huft *)Z_NULL; +    *m = 0; +    return Z_OK; +  } + + +  /* Find minimum and maximum length, bound *m by those */ +  l = *m; +  for (j = 1; j <= BMAX; j++) +    if (c[j]) +      break; +  k = j;                        /* minimum code length */ +  if ((uInt)l < j) +    l = j; +  for (i = BMAX; i; i--) +    if (c[i]) +      break; +  g = i;                        /* maximum code length */ +  if ((uInt)l > i) +    l = i; +  *m = l; + + +  /* Adjust last length count to fill out codes, if needed */ +  for (y = 1 << j; j < i; j++, y <<= 1) +    if ((y -= c[j]) < 0) +      return Z_DATA_ERROR; +  if ((y -= c[i]) < 0) +    return Z_DATA_ERROR; +  c[i] += y; + + +  /* Generate starting offsets into the value table for each length */ +  x[1] = j = 0; +  p = c + 1;  xp = x + 2; +  while (--i) {                 /* note that i == g from above */ +    *xp++ = (j += *p++); +  } + + +  /* Make a table of values in order of bit lengths */ +  p = b;  i = 0; +  do { +    if ((j = *p++) != 0) +      v[x[j]++] = i; +  } while (++i < n); +  n = x[g];                     /* set n to length of v */ + + +  /* Generate the Huffman codes and for each, make the table entries */ +  x[0] = i = 0;                 /* first Huffman code is zero */ +  p = v;                        /* grab values in bit order */ +  h = -1;                       /* no tables yet--level -1 */ +  w = -l;                       /* bits decoded == (l * h) */ +  u[0] = (inflate_huft *)Z_NULL;        /* just to keep compilers happy */ +  q = (inflate_huft *)Z_NULL;   /* ditto */ +  z = 0;                        /* ditto */ + +  /* go through the bit lengths (k already is bits in shortest code) */ +  for (; k <= g; k++) +  { +    a = c[k]; +    while (a--) +    { +      /* here i is the Huffman code of length k bits for value *p */ +      /* make tables up to required level */ +      while (k > w + l) +      { +        h++; +        w += l;                 /* previous table always l bits */ + +        /* compute minimum size table less than or equal to l bits */ +        z = g - w; +        z = z > (uInt)l ? l : z;        /* table size upper limit */ +        if ((f = 1 << (j = k - w)) > a + 1)     /* try a k-w bit table */ +        {                       /* too few codes for k-w bit table */ +          f -= a + 1;           /* deduct codes from patterns left */ +          xp = c + k; +          if (j < z) +            while (++j < z)     /* try smaller tables up to z bits */ +            { +              if ((f <<= 1) <= *++xp) +                break;          /* enough codes to use up j bits */ +              f -= *xp;         /* else deduct codes from patterns */ +            } +        } +        z = 1 << j;             /* table entries for j-bit table */ + +        /* allocate new table */ +        if (*hn + z > MANY)     /* (note: doesn't matter for fixed) */ +          return Z_MEM_ERROR;   /* not enough memory */ +        u[h] = q = hp + *hn; +        *hn += z; + +        /* connect to last table, if there is one */ +        if (h) +        { +          x[h] = i;             /* save pattern for backing up */ +          r.bits = (Byte)l;     /* bits to dump before this table */ +          r.exop = (Byte)j;     /* bits in this table */ +          j = i >> (w - l); +          r.base = (uInt)(q - u[h-1] - j);   /* offset to this table */ +          u[h-1][j] = r;        /* connect to last table */ +        } +        else +          *t = q;               /* first table is returned result */ +      } + +      /* set up table entry in r */ +      r.bits = (Byte)(k - w); +      if (p >= v + n) +        r.exop = 128 + 64;      /* out of values--invalid code */ +      else if (*p < s) +      { +        r.exop = (Byte)(*p < 256 ? 0 : 32 + 64);     /* 256 is end-of-block */ +        r.base = *p++;          /* simple code is just the value */ +      } +      else +      { +        r.exop = (Byte)(e[*p - s] + 16 + 64);/* non-simple--look up in lists */ +        r.base = d[*p++ - s]; +      } + +      /* fill code-like entries with r */ +      f = 1 << (k - w); +      for (j = i >> w; j < z; j += f) +        q[j] = r; + +      /* backwards increment the k-bit code i */ +      for (j = 1 << (k - 1); i & j; j >>= 1) +        i ^= j; +      i ^= j; + +      /* backup over finished tables */ +      mask = (1 << w) - 1;      /* needed on HP, cc -O bug */ +      while ((i & mask) != x[h]) +      { +        h--;                    /* don't need to update q */ +        w -= l; +        mask = (1 << w) - 1; +      } +    } +  } + + +  /* Return Z_BUF_ERROR if we were given an incomplete table */ +  return y != 0 && g != 1 ? Z_BUF_ERROR : Z_OK; +} + + +int inflate_trees_bits(uInt *c, uInt *bb, inflate_huft * *tb, inflate_huft *hp, z_streamp z) +//uInt *c;               /* 19 code lengths */ +//uInt *bb;              /* bits tree desired/actual depth */ +//inflate_huft * *tb; /* bits tree result */ +//inflate_huft *hp;       /* space for trees */ +//z_streamp z;            /* for messages */ +{ +  int r; +  uInt hn = 0;          /* hufts used in space */ +  uInt *v;             /* work area for huft_build */ + +  if ((v = (uInt*)ZALLOC(z, 19, sizeof(uInt))) == Z_NULL) +    return Z_MEM_ERROR; +  r = huft_build(c, 19, 19, (uInt*)Z_NULL, (uInt*)Z_NULL, +                 tb, bb, hp, &hn, v); +  if (r == Z_DATA_ERROR) +    z->msg = (char*)"oversubscribed dynamic bit lengths tree"; +  else if (r == Z_BUF_ERROR || *bb == 0) +  { +    z->msg = (char*)"incomplete dynamic bit lengths tree"; +    r = Z_DATA_ERROR; +  } +  ZFREE(z, v); +  return r; +} + + +int inflate_trees_dynamic(uInt nl, uInt nd, uInt *c, uInt *bl, uInt *bd, inflate_huft * *tl, inflate_huft * *td, inflate_huft *hp, z_streamp z) +//uInt nl;                /* number of literal/length codes */ +//uInt nd;                /* number of distance codes */ +//uInt *c;               /* that many (total) code lengths */ +//uInt *bl;              /* literal desired/actual bit depth */ +//uInt *bd;              /* distance desired/actual bit depth */ +//inflate_huft * *tl; /* literal/length tree result */ +//inflate_huft * *td; /* distance tree result */ +//inflate_huft *hp;       /* space for trees */ +//z_streamp z;            /* for messages */ +{ +  int r; +  uInt hn = 0;          /* hufts used in space */ +  uInt *v;             /* work area for huft_build */ + +  /* allocate work area */ +  if ((v = (uInt*)ZALLOC(z, 288, sizeof(uInt))) == Z_NULL) +    return Z_MEM_ERROR; + +  /* build literal/length tree */ +  r = huft_build(c, nl, 257, cplens, cplext, tl, bl, hp, &hn, v); +  if (r != Z_OK || *bl == 0) +  { +    if (r == Z_DATA_ERROR) +      z->msg = (char*)"oversubscribed literal/length tree"; +    else if (r != Z_MEM_ERROR) +    { +      z->msg = (char*)"incomplete literal/length tree"; +      r = Z_DATA_ERROR; +    } +    ZFREE(z, v); +    return r; +  } + +  /* build distance tree */ +  r = huft_build(c + nl, nd, 0, cpdist, cpdext, td, bd, hp, &hn, v); +  if (r != Z_OK || (*bd == 0 && nl > 257)) +  { +    if (r == Z_DATA_ERROR) +      z->msg = (char*)"oversubscribed distance tree"; +    else if (r == Z_BUF_ERROR) { +#ifdef PKZIP_BUG_WORKAROUND +      r = Z_OK; +    } +#else +      z->msg = (char*)"incomplete distance tree"; +      r = Z_DATA_ERROR; +    } +    else if (r != Z_MEM_ERROR) +    { +      z->msg = (char*)"empty distance tree with lengths"; +      r = Z_DATA_ERROR; +    } +    ZFREE(z, v); +    return r; +#endif +  } + +  /* done */ +  ZFREE(z, v); +  return Z_OK; +} + +/* inffixed.h -- table for decoding fixed codes + * Generated automatically by the maketree.c program + */ + +/* WARNING: this file should *not* be used by applications. It is +   part of the implementation of the compression library and is +   subject to change. Applications should only use zlib.h. + */ + +static uInt fixed_bl = 9; +static uInt fixed_bd = 5; +static inflate_huft fixed_tl[] = { +    {{{96,7}},256}, {{{0,8}},80}, {{{0,8}},16}, {{{84,8}},115}, +    {{{82,7}},31}, {{{0,8}},112}, {{{0,8}},48}, {{{0,9}},192}, +    {{{80,7}},10}, {{{0,8}},96}, {{{0,8}},32}, {{{0,9}},160}, +    {{{0,8}},0}, {{{0,8}},128}, {{{0,8}},64}, {{{0,9}},224}, +    {{{80,7}},6}, {{{0,8}},88}, {{{0,8}},24}, {{{0,9}},144}, +    {{{83,7}},59}, {{{0,8}},120}, {{{0,8}},56}, {{{0,9}},208}, +    {{{81,7}},17}, {{{0,8}},104}, {{{0,8}},40}, {{{0,9}},176}, +    {{{0,8}},8}, {{{0,8}},136}, {{{0,8}},72}, {{{0,9}},240}, +    {{{80,7}},4}, {{{0,8}},84}, {{{0,8}},20}, {{{85,8}},227}, +    {{{83,7}},43}, {{{0,8}},116}, {{{0,8}},52}, {{{0,9}},200}, +    {{{81,7}},13}, {{{0,8}},100}, {{{0,8}},36}, {{{0,9}},168}, +    {{{0,8}},4}, {{{0,8}},132}, {{{0,8}},68}, {{{0,9}},232}, +    {{{80,7}},8}, {{{0,8}},92}, {{{0,8}},28}, {{{0,9}},152}, +    {{{84,7}},83}, {{{0,8}},124}, {{{0,8}},60}, {{{0,9}},216}, +    {{{82,7}},23}, {{{0,8}},108}, {{{0,8}},44}, {{{0,9}},184}, +    {{{0,8}},12}, {{{0,8}},140}, {{{0,8}},76}, {{{0,9}},248}, +    {{{80,7}},3}, {{{0,8}},82}, {{{0,8}},18}, {{{85,8}},163}, +    {{{83,7}},35}, {{{0,8}},114}, {{{0,8}},50}, {{{0,9}},196}, +    {{{81,7}},11}, {{{0,8}},98}, {{{0,8}},34}, {{{0,9}},164}, +    {{{0,8}},2}, {{{0,8}},130}, {{{0,8}},66}, {{{0,9}},228}, +    {{{80,7}},7}, {{{0,8}},90}, {{{0,8}},26}, {{{0,9}},148}, +    {{{84,7}},67}, {{{0,8}},122}, {{{0,8}},58}, {{{0,9}},212}, +    {{{82,7}},19}, {{{0,8}},106}, {{{0,8}},42}, {{{0,9}},180}, +    {{{0,8}},10}, {{{0,8}},138}, {{{0,8}},74}, {{{0,9}},244}, +    {{{80,7}},5}, {{{0,8}},86}, {{{0,8}},22}, {{{192,8}},0}, +    {{{83,7}},51}, {{{0,8}},118}, {{{0,8}},54}, {{{0,9}},204}, +    {{{81,7}},15}, {{{0,8}},102}, {{{0,8}},38}, {{{0,9}},172}, +    {{{0,8}},6}, {{{0,8}},134}, {{{0,8}},70}, {{{0,9}},236}, +    {{{80,7}},9}, {{{0,8}},94}, {{{0,8}},30}, {{{0,9}},156}, +    {{{84,7}},99}, {{{0,8}},126}, {{{0,8}},62}, {{{0,9}},220}, +    {{{82,7}},27}, {{{0,8}},110}, {{{0,8}},46}, {{{0,9}},188}, +    {{{0,8}},14}, {{{0,8}},142}, {{{0,8}},78}, {{{0,9}},252}, +    {{{96,7}},256}, {{{0,8}},81}, {{{0,8}},17}, {{{85,8}},131}, +    {{{82,7}},31}, {{{0,8}},113}, {{{0,8}},49}, {{{0,9}},194}, +    {{{80,7}},10}, {{{0,8}},97}, {{{0,8}},33}, {{{0,9}},162}, +    {{{0,8}},1}, {{{0,8}},129}, {{{0,8}},65}, {{{0,9}},226}, +    {{{80,7}},6}, {{{0,8}},89}, {{{0,8}},25}, {{{0,9}},146}, +    {{{83,7}},59}, {{{0,8}},121}, {{{0,8}},57}, {{{0,9}},210}, +    {{{81,7}},17}, {{{0,8}},105}, {{{0,8}},41}, {{{0,9}},178}, +    {{{0,8}},9}, {{{0,8}},137}, {{{0,8}},73}, {{{0,9}},242}, +    {{{80,7}},4}, {{{0,8}},85}, {{{0,8}},21}, {{{80,8}},258}, +    {{{83,7}},43}, {{{0,8}},117}, {{{0,8}},53}, {{{0,9}},202}, +    {{{81,7}},13}, {{{0,8}},101}, {{{0,8}},37}, {{{0,9}},170}, +    {{{0,8}},5}, {{{0,8}},133}, {{{0,8}},69}, {{{0,9}},234}, +    {{{80,7}},8}, {{{0,8}},93}, {{{0,8}},29}, {{{0,9}},154}, +    {{{84,7}},83}, {{{0,8}},125}, {{{0,8}},61}, {{{0,9}},218}, +    {{{82,7}},23}, {{{0,8}},109}, {{{0,8}},45}, {{{0,9}},186}, +    {{{0,8}},13}, {{{0,8}},141}, {{{0,8}},77}, {{{0,9}},250}, +    {{{80,7}},3}, {{{0,8}},83}, {{{0,8}},19}, {{{85,8}},195}, +    {{{83,7}},35}, {{{0,8}},115}, {{{0,8}},51}, {{{0,9}},198}, +    {{{81,7}},11}, {{{0,8}},99}, {{{0,8}},35}, {{{0,9}},166}, +    {{{0,8}},3}, {{{0,8}},131}, {{{0,8}},67}, {{{0,9}},230}, +    {{{80,7}},7}, {{{0,8}},91}, {{{0,8}},27}, {{{0,9}},150}, +    {{{84,7}},67}, {{{0,8}},123}, {{{0,8}},59}, {{{0,9}},214}, +    {{{82,7}},19}, {{{0,8}},107}, {{{0,8}},43}, {{{0,9}},182}, +    {{{0,8}},11}, {{{0,8}},139}, {{{0,8}},75}, {{{0,9}},246}, +    {{{80,7}},5}, {{{0,8}},87}, {{{0,8}},23}, {{{192,8}},0}, +    {{{83,7}},51}, {{{0,8}},119}, {{{0,8}},55}, {{{0,9}},206}, +    {{{81,7}},15}, {{{0,8}},103}, {{{0,8}},39}, {{{0,9}},174}, +    {{{0,8}},7}, {{{0,8}},135}, {{{0,8}},71}, {{{0,9}},238}, +    {{{80,7}},9}, {{{0,8}},95}, {{{0,8}},31}, {{{0,9}},158}, +    {{{84,7}},99}, {{{0,8}},127}, {{{0,8}},63}, {{{0,9}},222}, +    {{{82,7}},27}, {{{0,8}},111}, {{{0,8}},47}, {{{0,9}},190}, +    {{{0,8}},15}, {{{0,8}},143}, {{{0,8}},79}, {{{0,9}},254}, +    {{{96,7}},256}, {{{0,8}},80}, {{{0,8}},16}, {{{84,8}},115}, +    {{{82,7}},31}, {{{0,8}},112}, {{{0,8}},48}, {{{0,9}},193}, +    {{{80,7}},10}, {{{0,8}},96}, {{{0,8}},32}, {{{0,9}},161}, +    {{{0,8}},0}, {{{0,8}},128}, {{{0,8}},64}, {{{0,9}},225}, +    {{{80,7}},6}, {{{0,8}},88}, {{{0,8}},24}, {{{0,9}},145}, +    {{{83,7}},59}, {{{0,8}},120}, {{{0,8}},56}, {{{0,9}},209}, +    {{{81,7}},17}, {{{0,8}},104}, {{{0,8}},40}, {{{0,9}},177}, +    {{{0,8}},8}, {{{0,8}},136}, {{{0,8}},72}, {{{0,9}},241}, +    {{{80,7}},4}, {{{0,8}},84}, {{{0,8}},20}, {{{85,8}},227}, +    {{{83,7}},43}, {{{0,8}},116}, {{{0,8}},52}, {{{0,9}},201}, +    {{{81,7}},13}, {{{0,8}},100}, {{{0,8}},36}, {{{0,9}},169}, +    {{{0,8}},4}, {{{0,8}},132}, {{{0,8}},68}, {{{0,9}},233}, +    {{{80,7}},8}, {{{0,8}},92}, {{{0,8}},28}, {{{0,9}},153}, +    {{{84,7}},83}, {{{0,8}},124}, {{{0,8}},60}, {{{0,9}},217}, +    {{{82,7}},23}, {{{0,8}},108}, {{{0,8}},44}, {{{0,9}},185}, +    {{{0,8}},12}, {{{0,8}},140}, {{{0,8}},76}, {{{0,9}},249}, +    {{{80,7}},3}, {{{0,8}},82}, {{{0,8}},18}, {{{85,8}},163}, +    {{{83,7}},35}, {{{0,8}},114}, {{{0,8}},50}, {{{0,9}},197}, +    {{{81,7}},11}, {{{0,8}},98}, {{{0,8}},34}, {{{0,9}},165}, +    {{{0,8}},2}, {{{0,8}},130}, {{{0,8}},66}, {{{0,9}},229}, +    {{{80,7}},7}, {{{0,8}},90}, {{{0,8}},26}, {{{0,9}},149}, +    {{{84,7}},67}, {{{0,8}},122}, {{{0,8}},58}, {{{0,9}},213}, +    {{{82,7}},19}, {{{0,8}},106}, {{{0,8}},42}, {{{0,9}},181}, +    {{{0,8}},10}, {{{0,8}},138}, {{{0,8}},74}, {{{0,9}},245}, +    {{{80,7}},5}, {{{0,8}},86}, {{{0,8}},22}, {{{192,8}},0}, +    {{{83,7}},51}, {{{0,8}},118}, {{{0,8}},54}, {{{0,9}},205}, +    {{{81,7}},15}, {{{0,8}},102}, {{{0,8}},38}, {{{0,9}},173}, +    {{{0,8}},6}, {{{0,8}},134}, {{{0,8}},70}, {{{0,9}},237}, +    {{{80,7}},9}, {{{0,8}},94}, {{{0,8}},30}, {{{0,9}},157}, +    {{{84,7}},99}, {{{0,8}},126}, {{{0,8}},62}, {{{0,9}},221}, +    {{{82,7}},27}, {{{0,8}},110}, {{{0,8}},46}, {{{0,9}},189}, +    {{{0,8}},14}, {{{0,8}},142}, {{{0,8}},78}, {{{0,9}},253}, +    {{{96,7}},256}, {{{0,8}},81}, {{{0,8}},17}, {{{85,8}},131}, +    {{{82,7}},31}, {{{0,8}},113}, {{{0,8}},49}, {{{0,9}},195}, +    {{{80,7}},10}, {{{0,8}},97}, {{{0,8}},33}, {{{0,9}},163}, +    {{{0,8}},1}, {{{0,8}},129}, {{{0,8}},65}, {{{0,9}},227}, +    {{{80,7}},6}, {{{0,8}},89}, {{{0,8}},25}, {{{0,9}},147}, +    {{{83,7}},59}, {{{0,8}},121}, {{{0,8}},57}, {{{0,9}},211}, +    {{{81,7}},17}, {{{0,8}},105}, {{{0,8}},41}, {{{0,9}},179}, +    {{{0,8}},9}, {{{0,8}},137}, {{{0,8}},73}, {{{0,9}},243}, +    {{{80,7}},4}, {{{0,8}},85}, {{{0,8}},21}, {{{80,8}},258}, +    {{{83,7}},43}, {{{0,8}},117}, {{{0,8}},53}, {{{0,9}},203}, +    {{{81,7}},13}, {{{0,8}},101}, {{{0,8}},37}, {{{0,9}},171}, +    {{{0,8}},5}, {{{0,8}},133}, {{{0,8}},69}, {{{0,9}},235}, +    {{{80,7}},8}, {{{0,8}},93}, {{{0,8}},29}, {{{0,9}},155}, +    {{{84,7}},83}, {{{0,8}},125}, {{{0,8}},61}, {{{0,9}},219}, +    {{{82,7}},23}, {{{0,8}},109}, {{{0,8}},45}, {{{0,9}},187}, +    {{{0,8}},13}, {{{0,8}},141}, {{{0,8}},77}, {{{0,9}},251}, +    {{{80,7}},3}, {{{0,8}},83}, {{{0,8}},19}, {{{85,8}},195}, +    {{{83,7}},35}, {{{0,8}},115}, {{{0,8}},51}, {{{0,9}},199}, +    {{{81,7}},11}, {{{0,8}},99}, {{{0,8}},35}, {{{0,9}},167}, +    {{{0,8}},3}, {{{0,8}},131}, {{{0,8}},67}, {{{0,9}},231}, +    {{{80,7}},7}, {{{0,8}},91}, {{{0,8}},27}, {{{0,9}},151}, +    {{{84,7}},67}, {{{0,8}},123}, {{{0,8}},59}, {{{0,9}},215}, +    {{{82,7}},19}, {{{0,8}},107}, {{{0,8}},43}, {{{0,9}},183}, +    {{{0,8}},11}, {{{0,8}},139}, {{{0,8}},75}, {{{0,9}},247}, +    {{{80,7}},5}, {{{0,8}},87}, {{{0,8}},23}, {{{192,8}},0}, +    {{{83,7}},51}, {{{0,8}},119}, {{{0,8}},55}, {{{0,9}},207}, +    {{{81,7}},15}, {{{0,8}},103}, {{{0,8}},39}, {{{0,9}},175}, +    {{{0,8}},7}, {{{0,8}},135}, {{{0,8}},71}, {{{0,9}},239}, +    {{{80,7}},9}, {{{0,8}},95}, {{{0,8}},31}, {{{0,9}},159}, +    {{{84,7}},99}, {{{0,8}},127}, {{{0,8}},63}, {{{0,9}},223}, +    {{{82,7}},27}, {{{0,8}},111}, {{{0,8}},47}, {{{0,9}},191}, +    {{{0,8}},15}, {{{0,8}},143}, {{{0,8}},79}, {{{0,9}},255} +  }; +static inflate_huft fixed_td[] = { +    {{{80,5}},1}, {{{87,5}},257}, {{{83,5}},17}, {{{91,5}},4097}, +    {{{81,5}},5}, {{{89,5}},1025}, {{{85,5}},65}, {{{93,5}},16385}, +    {{{80,5}},3}, {{{88,5}},513}, {{{84,5}},33}, {{{92,5}},8193}, +    {{{82,5}},9}, {{{90,5}},2049}, {{{86,5}},129}, {{{192,5}},24577}, +    {{{80,5}},2}, {{{87,5}},385}, {{{83,5}},25}, {{{91,5}},6145}, +    {{{81,5}},7}, {{{89,5}},1537}, {{{85,5}},97}, {{{93,5}},24577}, +    {{{80,5}},4}, {{{88,5}},769}, {{{84,5}},49}, {{{92,5}},12289}, +    {{{82,5}},13}, {{{90,5}},3073}, {{{86,5}},193}, {{{192,5}},24577} +  }; + +int inflate_trees_fixed(uInt *bl, uInt *bd, inflate_huft * *tl, inflate_huft * *td, z_streamp z) +//uInt *bl;               /* literal desired/actual bit depth */ +//uInt *bd;               /* distance desired/actual bit depth */ +//inflate_huft * *tl;  /* literal/length tree result */ +//inflate_huft * *td;  /* distance tree result */ +//z_streamp z;             /* for memory allocation */ +{ +  *bl = fixed_bl; +  *bd = fixed_bd; +  *tl = fixed_tl; +  *td = fixed_td; +  return Z_OK; +} + +/* simplify the use of the inflate_huft type with some defines */ +#define exop word.what.Exop +#define bits word.what.Bits + +/* macros for bit input with no checking and for returning unused bytes */ +#define GRABBITS(j) {while(k<(j)){b|=((uLong)NEXTBYTE)<<k;k+=8;}} +#define UNGRAB {c=z->avail_in-n;c=(k>>3)<c?k>>3:c;n+=c;p-=c;k-=c<<3;} + +/* Called with number of bytes left to write in window at least 258 +   (the maximum string length) and number of input bytes available +   at least ten.  The ten bytes are six bytes for the longest length/ +   distance pair plus four bytes for overloading the bit buffer. */ + +static int inflate_fast(uInt bl, uInt bd, inflate_huft *tl, inflate_huft *td, inflate_blocks_statef *s, z_streamp z) +{ +  inflate_huft *t;      /* temporary pointer */ +  uInt e;               /* extra bits or operation */ +  uLong b;              /* bit buffer */ +  uInt k;               /* bits in bit buffer */ +  Byte *p;             /* input data pointer */ +  uInt n;               /* bytes available there */ +  Byte *q;             /* output window write pointer */ +  uInt m;               /* bytes to end of window or read pointer */ +  uInt ml;              /* mask for literal/length tree */ +  uInt md;              /* mask for distance tree */ +  uInt c;               /* bytes to copy */ +  uInt d;               /* distance back to copy from */ +  Byte *r;             /* copy source pointer */ + +  /* load input, output, bit values */ +  LOAD + +  /* initialize masks */ +  ml = inflate_mask[bl]; +  md = inflate_mask[bd]; + +  /* do until not enough input or output space for fast loop */ +  do {                          /* assume called with m >= 258 && n >= 10 */ +    /* get literal/length code */ +    GRABBITS(20)                /* max bits for literal/length code */ +    if ((e = (t = tl + ((uInt)b & ml))->exop) == 0) +    { +      DUMPBITS(t->bits) +      Tracevv((t->base >= 0x20 && t->base < 0x7f ? +                "inflate:         * literal '%c'\n" : +                "inflate:         * literal 0x%02x\n", t->base)); +      *q++ = (Byte)t->base; +      m--; +      continue; +    } +    do { +      DUMPBITS(t->bits) +      if (e & 16) +      { +        /* get extra bits for length */ +        e &= 15; +        c = t->base + ((uInt)b & inflate_mask[e]); +        DUMPBITS(e) +        Tracevv(("inflate:         * length %u\n", c)); + +        /* decode distance base of block to copy */ +        GRABBITS(15);           /* max bits for distance code */ +        e = (t = td + ((uInt)b & md))->exop; +        do { +          DUMPBITS(t->bits) +          if (e & 16) +          { +            /* get extra bits to add to distance base */ +            e &= 15; +            GRABBITS(e)         /* get extra bits (up to 13) */ +            d = t->base + ((uInt)b & inflate_mask[e]); +            DUMPBITS(e) +            Tracevv(("inflate:         * distance %u\n", d)); + +            /* do the copy */ +            m -= c; +            if ((uInt)(q - s->window) >= d)     /* offset before dest */ +            {                                   /*  just copy */ +              r = q - d; +              *q++ = *r++;  c--;        /* minimum count is three, */ +              *q++ = *r++;  c--;        /*  so unroll loop a little */ +            } +            else                        /* else offset after destination */ +            { +              e = d - (uInt)(q - s->window); /* bytes from offset to end */ +              r = s->end - e;           /* pointer to offset */ +              if (c > e)                /* if source crosses, */ +              { +                c -= e;                 /* copy to end of window */ +                do { +                  *q++ = *r++; +                } while (--e); +                r = s->window;          /* copy rest from start of window */ +              } +            } +            do {                        /* copy all or what's left */ +              *q++ = *r++; +            } while (--c); +            break; +          } +          else if ((e & 64) == 0) +          { +            t += t->base; +            e = (t += ((uInt)b & inflate_mask[e]))->exop; +          } +          else +          { +            z->msg = (char*)"invalid distance code"; +            UNGRAB +            UPDATE +            return Z_DATA_ERROR; +          } +        } while (1); +        break; +      } +      if ((e & 64) == 0) +      { +        t += t->base; +        if ((e = (t += ((uInt)b & inflate_mask[e]))->exop) == 0) +        { +          DUMPBITS(t->bits) +          Tracevv((t->base >= 0x20 && t->base < 0x7f ? +                    "inflate:         * literal '%c'\n" : +                    "inflate:         * literal 0x%02x\n", t->base)); +          *q++ = (Byte)t->base; +          m--; +          break; +        } +      } +      else if (e & 32) +      { +        Tracevv(("inflate:         * end of block\n")); +        UNGRAB +        UPDATE +        return Z_STREAM_END; +      } +      else +      { +        z->msg = (char*)"invalid literal/length code"; +        UNGRAB +        UPDATE +        return Z_DATA_ERROR; +      } +    } while (1); +  } while (m >= 258 && n >= 10); + +  /* not enough input or output--restore pointers and return */ +  UNGRAB +  UPDATE +  return Z_OK; +} + +/* infcodes.c -- process literals and length/distance pairs + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h  + */ + +/* simplify the use of the inflate_huft type with some defines */ +#define exop word.what.Exop +#define bits word.what.Bits + +typedef enum {        /* waiting for "i:"=input, "o:"=output, "x:"=nothing */ +      START,    /* x: set up for LEN */ +      LEN,      /* i: get length/literal/eob next */ +      LENEXT,   /* i: getting length extra (have base) */ +      DIST,     /* i: get distance next */ +      DISTEXT,  /* i: getting distance extra */ +      COPY,     /* o: copying bytes in window, waiting for space */ +      LIT,      /* o: got literal, waiting for output space */ +      WASH,     /* o: got eob, possibly still output waiting */ +      END,      /* x: got eob and all data flushed */ +      BADCODE}  /* x: got error */ +inflate_codes_mode; + +/* inflate codes private state */ +struct inflate_codes_state { + +  /* mode */ +  inflate_codes_mode mode;      /* current inflate_codes mode */ + +  /* mode dependent information */ +  uInt len; +  union { +    struct { +      inflate_huft *tree;       /* pointer into tree */ +      uInt need;                /* bits needed */ +    } code;             /* if LEN or DIST, where in tree */ +    uInt lit;           /* if LIT, literal */ +    struct { +      uInt get;                 /* bits to get for extra */ +      uInt dist;                /* distance back to copy from */ +    } copy;             /* if EXT or COPY, where and how much */ +  } sub;                /* submode */ + +  /* mode independent information */ +  Byte lbits;           /* ltree bits decoded per branch */ +  Byte dbits;           /* dtree bits decoder per branch */ +  inflate_huft *ltree;          /* literal/length/eob tree */ +  inflate_huft *dtree;          /* distance tree */ + +}; + + +inflate_codes_statef *inflate_codes_new(uInt bl, uInt bd, inflate_huft *tl, inflate_huft *td, z_streamp z) +{ +  inflate_codes_statef *c; + +  if ((c = (inflate_codes_statef *) +       ZALLOC(z,1,sizeof(struct inflate_codes_state))) != Z_NULL) +  { +    c->mode = START; +    c->lbits = (Byte)bl; +    c->dbits = (Byte)bd; +    c->ltree = tl; +    c->dtree = td; +    Tracev(("inflate:       codes new\n")); +  } +  return c; +} + + +int inflate_codes(inflate_blocks_statef *s, z_streamp z, int r) +{ +  uInt j;               /* temporary storage */ +  inflate_huft *t;      /* temporary pointer */ +  uInt e;               /* extra bits or operation */ +  uLong b;              /* bit buffer */ +  uInt k;               /* bits in bit buffer */ +  Byte *p;             /* input data pointer */ +  uInt n;               /* bytes available there */ +  Byte *q;             /* output window write pointer */ +  uInt m;               /* bytes to end of window or read pointer */ +  Byte *f;             /* pointer to copy strings from */ +  inflate_codes_statef *c = s->sub.decode.codes;  /* codes state */ + +  /* copy input/output information to locals (UPDATE macro restores) */ +  LOAD + +  /* process input and output based on current state */ +  while (1) switch (c->mode) +  {             /* waiting for "i:"=input, "o:"=output, "x:"=nothing */ +    case START:         /* x: set up for LEN */ +#ifndef SLOW +      if (m >= 258 && n >= 10) +      { +        UPDATE +        r = inflate_fast(c->lbits, c->dbits, c->ltree, c->dtree, s, z); +        LOAD +        if (r != Z_OK) +        { +          c->mode = r == Z_STREAM_END ? WASH : BADCODE; +          break; +        } +      } +#endif /* !SLOW */ +      c->sub.code.need = c->lbits; +      c->sub.code.tree = c->ltree; +      c->mode = LEN; +    case LEN:           /* i: get length/literal/eob next */ +      j = c->sub.code.need; +      NEEDBITS(j) +      t = c->sub.code.tree + ((uInt)b & inflate_mask[j]); +      DUMPBITS(t->bits) +      e = (uInt)(t->exop); +      if (e == 0)               /* literal */ +      { +        c->sub.lit = t->base; +        Tracevv((t->base >= 0x20 && t->base < 0x7f ? +                 "inflate:         literal '%c'\n" : +                 "inflate:         literal 0x%02x\n", t->base)); +        c->mode = LIT; +        break; +      } +      if (e & 16)               /* length */ +      { +        c->sub.copy.get = e & 15; +        c->len = t->base; +        c->mode = LENEXT; +        break; +      } +      if ((e & 64) == 0)        /* next table */ +      { +        c->sub.code.need = e; +        c->sub.code.tree = t + t->base; +        break; +      } +      if (e & 32)               /* end of block */ +      { +        Tracevv(("inflate:         end of block\n")); +        c->mode = WASH; +        break; +      } +      c->mode = BADCODE;        /* invalid code */ +      z->msg = (char*)"invalid literal/length code"; +      r = Z_DATA_ERROR; +      LEAVE +    case LENEXT:        /* i: getting length extra (have base) */ +      j = c->sub.copy.get; +      NEEDBITS(j) +      c->len += (uInt)b & inflate_mask[j]; +      DUMPBITS(j) +      c->sub.code.need = c->dbits; +      c->sub.code.tree = c->dtree; +      Tracevv(("inflate:         length %u\n", c->len)); +      c->mode = DIST; +    case DIST:          /* i: get distance next */ +      j = c->sub.code.need; +      NEEDBITS(j) +      t = c->sub.code.tree + ((uInt)b & inflate_mask[j]); +      DUMPBITS(t->bits) +      e = (uInt)(t->exop); +      if (e & 16)               /* distance */ +      { +        c->sub.copy.get = e & 15; +        c->sub.copy.dist = t->base; +        c->mode = DISTEXT; +        break; +      } +      if ((e & 64) == 0)        /* next table */ +      { +        c->sub.code.need = e; +        c->sub.code.tree = t + t->base; +        break; +      } +      c->mode = BADCODE;        /* invalid code */ +      z->msg = (char*)"invalid distance code"; +      r = Z_DATA_ERROR; +      LEAVE +    case DISTEXT:       /* i: getting distance extra */ +      j = c->sub.copy.get; +      NEEDBITS(j) +      c->sub.copy.dist += (uInt)b & inflate_mask[j]; +      DUMPBITS(j) +      Tracevv(("inflate:         distance %u\n", c->sub.copy.dist)); +      c->mode = COPY; +    case COPY:          /* o: copying bytes in window, waiting for space */ +#ifndef __TURBOC__ /* Turbo C bug for following expression */ +      f = (uInt)(q - s->window) < c->sub.copy.dist ? +          s->end - (c->sub.copy.dist - (q - s->window)) : +          q - c->sub.copy.dist; +#else +      f = q - c->sub.copy.dist; +      if ((uInt)(q - s->window) < c->sub.copy.dist) +        f = s->end - (c->sub.copy.dist - (uInt)(q - s->window)); +#endif +      while (c->len) +      { +        NEEDOUT +        OUTBYTE(*f++) +        if (f == s->end) +          f = s->window; +        c->len--; +      } +      c->mode = START; +      break; +    case LIT:           /* o: got literal, waiting for output space */ +      NEEDOUT +      OUTBYTE(c->sub.lit) +      c->mode = START; +      break; +    case WASH:          /* o: got eob, possibly more output */ +      if (k > 7)        /* return unused byte, if any */ +      { +        Assert(k < 16, "inflate_codes grabbed too many bytes") +        k -= 8; +        n++; +        p--;            /* can always return one */ +      } +      FLUSH +      if (s->read != s->write) +        LEAVE +      c->mode = END; +    case END: +      r = Z_STREAM_END; +      LEAVE +    case BADCODE:       /* x: got error */ +      r = Z_DATA_ERROR; +      LEAVE +    default: +      r = Z_STREAM_ERROR; +      LEAVE +  } +#ifdef NEED_DUMMY_RETURN +  return Z_STREAM_ERROR;  /* Some dumb compilers complain without this */ +#endif +} + + +void inflate_codes_free(inflate_codes_statef *c, z_streamp z) +{ +  ZFREE(z, c); +  Tracev(("inflate:       codes free\n")); +} + +/* adler32.c -- compute the Adler-32 checksum of a data stream + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h  + */ + +#define BASE 65521L /* largest prime smaller than 65536 */ +#define NMAX 5552 +/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ + +#undef DO1 +#undef DO2 +#undef DO4 +#undef DO8 + +#define DO1(buf,i)  {s1 += buf[i]; s2 += s1;} +#define DO2(buf,i)  DO1(buf,i); DO1(buf,i+1); +#define DO4(buf,i)  DO2(buf,i); DO2(buf,i+2); +#define DO8(buf,i)  DO4(buf,i); DO4(buf,i+4); +#define DO16(buf)   DO8(buf,0); DO8(buf,8); + +/* ========================================================================= */ +static uLong adler32(uLong adler, const Byte *buf, uInt len) +{ +    unsigned long s1 = adler & 0xffff; +    unsigned long s2 = (adler >> 16) & 0xffff; +    int k; + +    if (buf == Z_NULL) return 1L; + +    while (len > 0) { +        k = len < NMAX ? len : NMAX; +        len -= k; +        while (k >= 16) { +            DO16(buf); +	    buf += 16; +            k -= 16; +        } +        if (k != 0) do { +            s1 += *buf++; +	    s2 += s1; +        } while (--k); +        s1 %= BASE; +        s2 %= BASE; +    } +    return (s2 << 16) | s1; +} + + +/* infblock.h -- header to use infblock.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h  + */ + +/* WARNING: this file should *not* be used by applications. It is +   part of the implementation of the compression library and is +   subject to change. Applications should only use zlib.h. + */ + +static inflate_blocks_statef * inflate_blocks_new OF(( +    z_streamp z, +    check_func c,               /* check function */ +    uInt w));                   /* window size */ + +static int inflate_blocks OF(( +    inflate_blocks_statef *, +    z_streamp , +    int));                      /* initial return code */ + +static void inflate_blocks_reset OF(( +    inflate_blocks_statef *, +    z_streamp , +    uLong *));                  /* check value on output */ + +static int inflate_blocks_free OF(( +    inflate_blocks_statef *, +    z_streamp)); + +#if 0 +static void inflate_set_dictionary OF(( +    inflate_blocks_statef *s, +    const Byte *d,  /* dictionary */ +    uInt  n));       /* dictionary length */ + +static int inflate_blocks_sync_point OF(( +    inflate_blocks_statef *s)); +#endif + +typedef enum { +      imMETHOD,   /* waiting for method byte */ +      imFLAG,     /* waiting for flag byte */ +      imDICT4,    /* four dictionary check bytes to go */ +      imDICT3,    /* three dictionary check bytes to go */ +      imDICT2,    /* two dictionary check bytes to go */ +      imDICT1,    /* one dictionary check byte to go */ +      imDICT0,    /* waiting for inflateSetDictionary */ +      imBLOCKS,   /* decompressing blocks */ +      imCHECK4,   /* four check bytes to go */ +      imCHECK3,   /* three check bytes to go */ +      imCHECK2,   /* two check bytes to go */ +      imCHECK1,   /* one check byte to go */ +      imDONE,     /* finished check, done */ +      imBAD}      /* got an error--stay here */ +inflate_mode; + +/* inflate private state */ +struct internal_state { + +  /* mode */ +  inflate_mode  mode;   /* current inflate mode */ + +  /* mode dependent information */ +  union { +    uInt method;        /* if FLAGS, method byte */ +    struct { +      uLong was;                /* computed check value */ +      uLong need;               /* stream check value */ +    } check;            /* if CHECK, check values to compare */ +    uInt marker;        /* if BAD, inflateSync's marker bytes count */ +  } sub;        /* submode */ + +  /* mode independent information */ +  int  nowrap;          /* flag for no wrapper */ +  uInt wbits;           /* log2(window size)  (8..15, defaults to 15) */ +  inflate_blocks_statef  +    *blocks;            /* current inflate_blocks state */ + +}; + + +int inflateReset(z_streamp z) +{ +  if (z == Z_NULL || z->state == Z_NULL) +    return Z_STREAM_ERROR; +  z->total_in = z->total_out = 0; +  z->msg = Z_NULL; +  z->state->mode = z->state->nowrap ? imBLOCKS : imMETHOD; +  inflate_blocks_reset(z->state->blocks, z, Z_NULL); +  Tracev(("inflate: reset\n")); +  return Z_OK; +} + + +int inflateEnd(z_streamp z) +{ +  if (z == Z_NULL || z->state == Z_NULL || z->zfree == Z_NULL) +    return Z_STREAM_ERROR; +  if (z->state->blocks != Z_NULL) +    inflate_blocks_free(z->state->blocks, z); +  ZFREE(z, z->state); +  z->state = Z_NULL; +  Tracev(("inflate: end\n")); +  return Z_OK; +} + + + +int inflateInit2_(z_streamp z, int w, const char *version, int stream_size) +{ +  if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || +      stream_size != sizeof(z_stream)) +      return Z_VERSION_ERROR; + +  /* initialize state */ +  if (z == Z_NULL) +    return Z_STREAM_ERROR; +  z->msg = Z_NULL; +  if (z->zalloc == Z_NULL) +  { +    z->zalloc = (void *(*)(void *, unsigned, unsigned))zcalloc; +    z->opaque = (voidp)0; +  } +  if (z->zfree == Z_NULL) z->zfree = (void (*)(void *, void *))zcfree; +  if ((z->state = (struct internal_state *) +       ZALLOC(z,1,sizeof(struct internal_state))) == Z_NULL) +    return Z_MEM_ERROR; +  z->state->blocks = Z_NULL; + +  /* handle undocumented nowrap option (no zlib header or check) */ +  z->state->nowrap = 0; +  if (w < 0) +  { +    w = - w; +    z->state->nowrap = 1; +  } + +  /* set window size */ +  if (w < 8 || w > 15) +  { +    inflateEnd(z); +    return Z_STREAM_ERROR; +  } +  z->state->wbits = (uInt)w; + +  /* create inflate_blocks state */ +  if ((z->state->blocks = +      inflate_blocks_new(z, z->state->nowrap ? Z_NULL : adler32, (uInt)1 << w)) +      == Z_NULL) +  { +    inflateEnd(z); +    return Z_MEM_ERROR; +  } +  Tracev(("inflate: allocated\n")); + +  /* reset state */ +  inflateReset(z); +  return Z_OK; +} + +#if 0 +int inflateInit_(z_streamp z, const char *version, int stream_size) +{ +  return inflateInit2_(z, DEF_WBITS, version, stream_size); +} +#endif + +#define iNEEDBYTE {if(z->avail_in==0)return r;r=f;} +#define iNEXTBYTE (z->avail_in--,z->total_in++,*z->next_in++) + +int inflate(z_streamp z, int f) +{ +  int r; +  uInt b; + +  if (z == Z_NULL || z->state == Z_NULL || z->next_in == Z_NULL) +    return Z_STREAM_ERROR; +  f = f == Z_FINISH ? Z_BUF_ERROR : Z_OK; +  r = Z_BUF_ERROR; +  while (1) switch (z->state->mode) +  { +    case imMETHOD: +      iNEEDBYTE +      if (((z->state->sub.method = iNEXTBYTE) & 0xf) != Z_DEFLATED) +      { +        z->state->mode = imBAD; +        z->msg = (char*)"unknown compression method"; +        z->state->sub.marker = 5;       /* can't try inflateSync */ +        break; +      } +      if ((z->state->sub.method >> 4) + 8 > z->state->wbits) +      { +        z->state->mode = imBAD; +        z->msg = (char*)"invalid window size"; +        z->state->sub.marker = 5;       /* can't try inflateSync */ +        break; +      } +      z->state->mode = imFLAG; +    case imFLAG: +      iNEEDBYTE +      b = iNEXTBYTE; +      if (((z->state->sub.method << 8) + b) % 31) +      { +        z->state->mode = imBAD; +        z->msg = (char*)"incorrect header check"; +        z->state->sub.marker = 5;       /* can't try inflateSync */ +        break; +      } +      Tracev(("inflate: zlib header ok\n")); +      if (!(b & PRESET_DICT)) +      { +        z->state->mode = imBLOCKS; +        break; +      } +      z->state->mode = imDICT4; +    case imDICT4: +      iNEEDBYTE +      z->state->sub.check.need = (uLong)iNEXTBYTE << 24; +      z->state->mode = imDICT3; +    case imDICT3: +      iNEEDBYTE +      z->state->sub.check.need += (uLong)iNEXTBYTE << 16; +      z->state->mode = imDICT2; +    case imDICT2: +      iNEEDBYTE +      z->state->sub.check.need += (uLong)iNEXTBYTE << 8; +      z->state->mode = imDICT1; +    case imDICT1: +      iNEEDBYTE +      z->state->sub.check.need += (uLong)iNEXTBYTE; +      z->adler = z->state->sub.check.need; +      z->state->mode = imDICT0; +      return Z_NEED_DICT; +    case imDICT0: +      z->state->mode = imBAD; +      z->msg = (char*)"need dictionary"; +      z->state->sub.marker = 0;       /* can try inflateSync */ +      return Z_STREAM_ERROR; +    case imBLOCKS: +      r = inflate_blocks(z->state->blocks, z, r); +      if (r == Z_DATA_ERROR) +      { +        z->state->mode = imBAD; +        z->state->sub.marker = 0;       /* can try inflateSync */ +        break; +      } +      if (r == Z_OK) +        r = f; +      if (r != Z_STREAM_END) +        return r; +      r = f; +      inflate_blocks_reset(z->state->blocks, z, &z->state->sub.check.was); +      if (z->state->nowrap) +      { +        z->state->mode = imDONE; +        break; +      } +      z->state->mode = imCHECK4; +    case imCHECK4: +      iNEEDBYTE +      z->state->sub.check.need = (uLong)iNEXTBYTE << 24; +      z->state->mode = imCHECK3; +    case imCHECK3: +      iNEEDBYTE +      z->state->sub.check.need += (uLong)iNEXTBYTE << 16; +      z->state->mode = imCHECK2; +    case imCHECK2: +      iNEEDBYTE +      z->state->sub.check.need += (uLong)iNEXTBYTE << 8; +      z->state->mode = imCHECK1; +    case imCHECK1: +      iNEEDBYTE +      z->state->sub.check.need += (uLong)iNEXTBYTE; + +      if (z->state->sub.check.was != z->state->sub.check.need) +      { +        z->state->mode = imBAD; +        z->msg = (char*)"incorrect data check"; +        z->state->sub.marker = 5;       /* can't try inflateSync */ +        break; +      } +      Tracev(("inflate: zlib check ok\n")); +      z->state->mode = imDONE; +    case imDONE: +      return Z_STREAM_END; +    case imBAD: +      return Z_DATA_ERROR; +    default: +      return Z_STREAM_ERROR; +  } +#ifdef NEED_DUMMY_RETURN +  return Z_STREAM_ERROR;  /* Some dumb compilers complain without this */ +#endif +} + +// defined but not used +#if 0 +int inflateSetDictionary(z_streamp z, const Byte *dictionary, uInt dictLength) +{ +  uInt length = dictLength; + +  if (z == Z_NULL || z->state == Z_NULL || z->state->mode != imDICT0) +    return Z_STREAM_ERROR; + +  if (adler32(1L, dictionary, dictLength) != z->adler) return Z_DATA_ERROR; +  z->adler = 1L; + +  if (length >= ((uInt)1<<z->state->wbits)) +  { +    length = (1<<z->state->wbits)-1; +    dictionary += dictLength - length; +  } +  inflate_set_dictionary(z->state->blocks, dictionary, length); +  z->state->mode = imBLOCKS; +  return Z_OK; +} + +int inflateSync(z_streamp z) +{ +  uInt n;       /* number of bytes to look at */ +  Byte *p;     /* pointer to bytes */ +  uInt m;       /* number of marker bytes found in a row */ +  uLong r, w;   /* temporaries to save total_in and total_out */ + +  /* set up */ +  if (z == Z_NULL || z->state == Z_NULL) +    return Z_STREAM_ERROR; +  if (z->state->mode != imBAD) +  { +    z->state->mode = imBAD; +    z->state->sub.marker = 0; +  } +  if ((n = z->avail_in) == 0) +    return Z_BUF_ERROR; +  p = z->next_in; +  m = z->state->sub.marker; + +  /* search */ +  while (n && m < 4) +  { +    static const Byte mark[4] = {0, 0, 0xff, 0xff}; +    if (*p == mark[m]) +      m++; +    else if (*p) +      m = 0; +    else +      m = 4 - m; +    p++, n--; +  } + +  /* restore */ +  z->total_in += p - z->next_in; +  z->next_in = p; +  z->avail_in = n; +  z->state->sub.marker = m; + +  /* return no joy or set up to restart on a new block */ +  if (m != 4) +    return Z_DATA_ERROR; +  r = z->total_in;  w = z->total_out; +  inflateReset(z); +  z->total_in = r;  z->total_out = w; +  z->state->mode = imBLOCKS; +  return Z_OK; +} + +/* Returns true if inflate is currently at the end of a block generated + * by Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP + * implementation to provide an additional safety check. PPP uses Z_SYNC_FLUSH + * but removes the length bytes of the resulting empty stored block. When + * decompressing, PPP checks that at the end of input packet, inflate is + * waiting for these length bytes. + */ +int inflateSyncPoint(z_streamp z) +{ +  if (z == Z_NULL || z->state == Z_NULL || z->state->blocks == Z_NULL) +    return Z_STREAM_ERROR; +  return inflate_blocks_sync_point(z->state->blocks); +} +#endif + +voidp zcalloc (voidp opaque, unsigned items, unsigned size) +{ +    if (opaque) items += size - size; /* make compiler happy */ +    return (voidp)Z_Malloc(items*size); +} + +void  zcfree (voidp opaque, voidp ptr) +{ +    Z_Free(ptr); +    if (opaque) return; /* make compiler happy */ +} + + diff --git a/src/qcommon/unzip.h b/src/qcommon/unzip.h new file mode 100644 index 0000000..18eb31e --- /dev/null +++ b/src/qcommon/unzip.h @@ -0,0 +1,337 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ + +#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted +    from (void*) without cast */ +typedef struct TagunzFile__ { int unused; } unzFile__;  +typedef unzFile__ *unzFile; +#else +typedef void* unzFile; +#endif + +/* tm_unz contain date/time info */ +typedef struct tm_unz_s  +{ +	unsigned int tm_sec;            /* seconds after the minute - [0,59] */ +	unsigned int tm_min;            /* minutes after the hour - [0,59] */ +	unsigned int tm_hour;           /* hours since midnight - [0,23] */ +	unsigned int tm_mday;           /* day of the month - [1,31] */ +	unsigned int tm_mon;            /* months since January - [0,11] */ +	unsigned int tm_year;           /* years - [1980..2044] */ +} tm_unz; + +/* unz_global_info structure contain global data about the ZIPfile +   These data comes from the end of central dir */ +typedef struct unz_global_info_s +{ +	unsigned long number_entry;         /* total number of entries in the central dir on this disk */ +	unsigned long size_comment;         /* size of the global comment of the zipfile */ +} unz_global_info; + + +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_info_s +{ +    unsigned long version;              /* version made by                 2 unsigned chars */ +    unsigned long version_needed;       /* version needed to extract       2 unsigned chars */ +    unsigned long flag;                 /* general purpose bit flag        2 unsigned chars */ +    unsigned long compression_method;   /* compression method              2 unsigned chars */ +    unsigned long dosDate;              /* last mod file date in Dos fmt   4 unsigned chars */ +    unsigned long crc;                  /* crc-32                          4 unsigned chars */ +    unsigned long compressed_size;      /* compressed size                 4 unsigned chars */  +    unsigned long uncompressed_size;    /* uncompressed size               4 unsigned chars */  +    unsigned long size_filename;        /* filename length                 2 unsigned chars */ +    unsigned long size_file_extra;      /* extra field length              2 unsigned chars */ +    unsigned long size_file_comment;    /* file comment length             2 unsigned chars */ + +    unsigned long disk_num_start;       /* disk number start               2 unsigned chars */ +    unsigned long internal_fa;          /* internal file attributes        2 unsigned chars */ +    unsigned long external_fa;          /* external file attributes        4 unsigned chars */ + +    tm_unz tmu_date; +} unz_file_info; + +/* unz_file_info_interntal contain internal info about a file in zipfile*/ +typedef struct unz_file_info_internal_s +{ +    unsigned long offset_curfile;/* relative offset of static header 4 unsigned chars */ +} unz_file_info_internal; + +typedef void* (*alloc_func) (void* opaque, unsigned int items, unsigned int size); +typedef void   (*free_func) (void* opaque, void* address); + +struct internal_state; + +typedef struct z_stream_s { +    unsigned char    *next_in;  /* next input unsigned char */ +    unsigned int     avail_in;  /* number of unsigned chars available at next_in */ +    unsigned long    total_in;  /* total nb of input unsigned chars read so */ + +    unsigned char    *next_out; /* next output unsigned char should be put there */ +    unsigned int     avail_out; /* remaining free space at next_out */ +    unsigned long    total_out; /* total nb of unsigned chars output so */ + +    char     *msg;      /* last error message, NULL if no error */ +    struct internal_state *state; /* not visible by applications */ + +    alloc_func zalloc;  /* used to allocate the internal state */ +    free_func  zfree;   /* used to free the internal state */ +    unsigned char*     opaque;  /* private data object passed to zalloc and zfree */ + +    int     data_type;  /* best guess about the data type: ascii or binary */ +    unsigned long   adler;      /* adler32 value of the uncompressed data */ +    unsigned long   reserved;   /* reserved for future use */ +} z_stream; + +typedef z_stream *z_streamp; + + +/* file_in_zip_read_info_s contain internal information about a file in zipfile, +    when reading and decompress it */ +typedef struct +{ +	char  *read_buffer;         /* internal buffer for compressed data */ +	z_stream stream;            /* zLib stream structure for inflate */ + +	unsigned long pos_in_zipfile;       /* position in unsigned char on the zipfile, for fseek*/ +	unsigned long stream_initialised;   /* flag set if stream structure is initialised*/ + +	unsigned long offset_local_extrafield;/* offset of the static extra field */ +	unsigned int  size_local_extrafield;/* size of the static extra field */ +	unsigned long pos_local_extrafield;   /* position in the static extra field in read*/ + +	unsigned long crc32;                /* crc32 of all data uncompressed */ +	unsigned long crc32_wait;           /* crc32 we must obtain after decompress all */ +	unsigned long rest_read_compressed; /* number of unsigned char to be decompressed */ +	unsigned long rest_read_uncompressed;/*number of unsigned char to be obtained after decomp*/ +	FILE* file;                 /* io structore of the zipfile */ +	unsigned long compression_method;   /* compression method (0==store) */ +	unsigned long byte_before_the_zipfile;/* unsigned char before the zipfile, (>0 for sfx)*/ +} file_in_zip_read_info_s; + + +/* unz_s contain internal information about the zipfile +*/ +typedef struct +{ +	FILE* file;                 /* io structore of the zipfile */ +	unz_global_info gi;       /* public global information */ +	unsigned long byte_before_the_zipfile;/* unsigned char before the zipfile, (>0 for sfx)*/ +	unsigned long num_file;             /* number of the current file in the zipfile*/ +	unsigned long pos_in_central_dir;   /* pos of the current file in the central dir*/ +	unsigned long current_file_ok;      /* flag about the usability of the current file*/ +	unsigned long central_pos;          /* position of the beginning of the central dir*/ + +	unsigned long size_central_dir;     /* size of the central directory  */ +	unsigned long offset_central_dir;   /* offset of start of central directory with +								   respect to the starting disk number */ + +	unz_file_info cur_file_info; /* public info about the current file in zip*/ +	unz_file_info_internal cur_file_info_internal; /* private info about it*/ +    file_in_zip_read_info_s* pfile_in_zip_read; /* structure about the current +	                                    file if we are decompressing it */ +	unsigned char*	tmpFile; +	int	tmpPos,tmpSize; +} unz_s; + +#define UNZ_OK                                  (0) +#define UNZ_END_OF_LIST_OF_FILE (-100) +#define UNZ_ERRNO               (Z_ERRNO) +#define UNZ_EOF                 (0) +#define UNZ_PARAMERROR                  (-102) +#define UNZ_BADZIPFILE                  (-103) +#define UNZ_INTERNALERROR               (-104) +#define UNZ_CRCERROR                    (-105) + +#define UNZ_CASESENSITIVE		1 +#define UNZ_NOTCASESENSITIVE	2 +#define UNZ_OSDEFAULTCASE		0 + +extern int unzStringFileNameCompare (const char* fileName1, const char* fileName2, int iCaseSensitivity); + +/* +   Compare two filename (fileName1,fileName2). +   If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) +   If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi +								or strcasecmp) +   If iCaseSenisivity = 0, case sensitivity is defaut of your operating system +	(like 1 on Unix, 2 on Windows) +*/ + +extern unzFile unzOpen (const char *path); +extern unzFile unzReOpen (const char* path, unzFile file); + +/* +  Open a Zip file. path contain the full pathname (by example, +     on a Windows NT computer "c:\\zlib\\zlib111.zip" or on an Unix computer +	 "zlib/zlib111.zip". +	 If the zipfile cannot be opened (file don't exist or in not valid), the +	   return value is NULL. +     Else, the return value is a unzFile Handle, usable with other function +	   of this unzip package. +*/ + +extern int unzClose (unzFile file); + +/* +  Close a ZipFile opened with unzipOpen. +  If there is files inside the .Zip opened with unzOpenCurrentFile (see later), +    these files MUST be closed with unzipCloseCurrentFile before call unzipClose. +  return UNZ_OK if there is no problem. */ + +extern int unzGetGlobalInfo (unzFile file, unz_global_info *pglobal_info); + +/* +  Write info about the ZipFile in the *pglobal_info structure. +  No preparation of the structure is needed +  return UNZ_OK if there is no problem. */ + + +extern int unzGetGlobalComment (unzFile file, char *szComment, unsigned long uSizeBuf); + +/* +  Get the global comment string of the ZipFile, in the szComment buffer. +  uSizeBuf is the size of the szComment buffer. +  return the number of unsigned char copied or an error code <0 +*/ + + +/***************************************************************************/ +/* Unzip package allow you browse the directory of the zipfile */ + +extern int unzGoToFirstFile (unzFile file); + +/* +  Set the current file of the zipfile to the first file. +  return UNZ_OK if there is no problem +*/ + +extern int unzGoToNextFile (unzFile file); + +/* +  Set the current file of the zipfile to the next file. +  return UNZ_OK if there is no problem +  return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ + +extern int unzGetCurrentFileInfoPosition (unzFile file, unsigned long *pos ); + +/* +  Get the position of the info of the current file in the zip. +  return UNZ_OK if there is no problem +*/ + +extern int unzSetCurrentFileInfoPosition (unzFile file, unsigned long pos ); + +/* +  Set the position of the info of the current file in the zip. +  return UNZ_OK if there is no problem +*/ + +extern int unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity); + +/* +  Try locate the file szFileName in the zipfile. +  For the iCaseSensitivity signification, see unzStringFileNameCompare + +  return value : +  UNZ_OK if the file is found. It becomes the current file. +  UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ + + +extern int unzGetCurrentFileInfo (unzFile file, unz_file_info *pfile_info, char *szFileName, unsigned long fileNameBufferSize, void *extraField, unsigned long extraFieldBufferSize, char *szComment, unsigned long commentBufferSize); + +/* +  Get Info about the current file +  if pfile_info!=NULL, the *pfile_info structure will contain somes info about +	    the current file +  if szFileName!=NULL, the filemane string will be copied in szFileName +			(fileNameBufferSize is the size of the buffer) +  if extraField!=NULL, the extra field information will be copied in extraField +			(extraFieldBufferSize is the size of the buffer). +			This is the Central-header version of the extra field +  if szComment!=NULL, the comment string of the file will be copied in szComment +			(commentBufferSize is the size of the buffer) +*/ + +/***************************************************************************/ +/* for reading the content of the current zipfile, you can open it, read data +   from it, and close it (you can close it before reading all the file) +   */ + +extern int unzOpenCurrentFile (unzFile file); + +/* +  Open for reading data the current file in the zipfile. +  If there is no error, the return value is UNZ_OK. +*/ + +extern int unzCloseCurrentFile (unzFile file); + +/* +  Close the file in zip opened with unzOpenCurrentFile +  Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ + +												 +extern int unzReadCurrentFile (unzFile file, void* buf, unsigned len); + +/* +  Read unsigned chars from the current file (opened by unzOpenCurrentFile) +  buf contain buffer where data must be copied +  len the size of buf. + +  return the number of unsigned char copied if somes unsigned chars are copied +  return 0 if the end of file was reached +  return <0 with error code if there is an error +    (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ + +extern long unztell(unzFile file); + +/* +  Give the current position in uncompressed data +*/ + +extern int unzeof (unzFile file); + +/* +  return 1 if the end of file was reached, 0 elsewhere  +*/ + +extern int unzGetLocalExtrafield (unzFile file, void* buf, unsigned len); + +/* +  Read extra field from the current file (opened by unzOpenCurrentFile) +  This is the local-header version of the extra field (sometimes, there is +    more info in the local-header version than in the central-header) + +  if buf==NULL, it return the size of the local extra field + +  if buf!=NULL, len is the size of the buffer, the extra header is copied in +	buf. +  the return value is the number of unsigned chars copied in buf, or (if <0)  +	the error code +*/ diff --git a/src/qcommon/vm.c b/src/qcommon/vm.c new file mode 100644 index 0000000..0017b41 --- /dev/null +++ b/src/qcommon/vm.c @@ -0,0 +1,895 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ +// vm.c -- virtual machine + +/* + + +intermix code and data +symbol table + +a dll has one imported function: VM_SystemCall +and one exported function: Perform + + +*/ + +#include "vm_local.h" + + +vm_t	*currentVM = NULL; // bk001212 +vm_t	*lastVM    = NULL; // bk001212 +int		vm_debugLevel; + +#define	MAX_VM		3 +vm_t	vmTable[MAX_VM]; + + +void VM_VmInfo_f( void ); +void VM_VmProfile_f( void ); + + + +#if 0 // 64bit! +// converts a VM pointer to a C pointer and +// checks to make sure that the range is acceptable +void	*VM_VM2C( vmptr_t p, int length ) { +	return (void *)p; +} +#endif + +void VM_Debug( int level ) { +	vm_debugLevel = level; +} + +/* +============== +VM_Init +============== +*/ +void VM_Init( void ) { +	Cvar_Get( "vm_cgame", "2", CVAR_ARCHIVE );	// !@# SHIP WITH SET TO 2 +	Cvar_Get( "vm_game", "2", CVAR_ARCHIVE );	// !@# SHIP WITH SET TO 2 +	Cvar_Get( "vm_ui", "2", CVAR_ARCHIVE );		// !@# SHIP WITH SET TO 2 + +	Cmd_AddCommand ("vmprofile", VM_VmProfile_f ); +	Cmd_AddCommand ("vminfo", VM_VmInfo_f ); + +	Com_Memset( vmTable, 0, sizeof( vmTable ) ); +} + + +/* +=============== +VM_ValueToSymbol + +Assumes a program counter value +=============== +*/ +const char *VM_ValueToSymbol( vm_t *vm, int value ) { +	vmSymbol_t	*sym; +	static char		text[MAX_TOKEN_CHARS]; + +	sym = vm->symbols; +	if ( !sym ) { +		return "NO SYMBOLS"; +	} + +	// find the symbol +	while ( sym->next && sym->next->symValue <= value ) { +		sym = sym->next; +	} + +	if ( value == sym->symValue ) { +		return sym->symName; +	} + +	Com_sprintf( text, sizeof( text ), "%s+%i", sym->symName, value - sym->symValue ); + +	return text; +} + +/* +=============== +VM_ValueToFunctionSymbol + +For profiling, find the symbol behind this value +=============== +*/ +vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ) { +	vmSymbol_t	*sym; +	static vmSymbol_t	nullSym; + +	sym = vm->symbols; +	if ( !sym ) { +		return &nullSym; +	} + +	while ( sym->next && sym->next->symValue <= value ) { +		sym = sym->next; +	} + +	return sym; +} + + +/* +=============== +VM_SymbolToValue +=============== +*/ +int VM_SymbolToValue( vm_t *vm, const char *symbol ) { +	vmSymbol_t	*sym; + +	for ( sym = vm->symbols ; sym ; sym = sym->next ) { +		if ( !strcmp( symbol, sym->symName ) ) { +			return sym->symValue; +		} +	} +	return 0; +} + + +/* +===================== +VM_SymbolForCompiledPointer +===================== +*/ +#if 0 // 64bit! +const char *VM_SymbolForCompiledPointer( vm_t *vm, void *code ) { +	int			i; + +	if ( code < (void *)vm->codeBase ) { +		return "Before code block"; +	} +	if ( code >= (void *)(vm->codeBase + vm->codeLength) ) { +		return "After code block"; +	} + +	// find which original instruction it is after +	for ( i = 0 ; i < vm->codeLength ; i++ ) { +		if ( (void *)vm->instructionPointers[i] > code ) { +			break; +		} +	} +	i--; + +	// now look up the bytecode instruction pointer +	return VM_ValueToSymbol( vm, i ); +} +#endif + + + +/* +=============== +ParseHex +=============== +*/ +int	ParseHex( const char *text ) { +	int		value; +	int		c; + +	value = 0; +	while ( ( c = *text++ ) != 0 ) { +		if ( c >= '0' && c <= '9' ) { +			value = value * 16 + c - '0'; +			continue; +		} +		if ( c >= 'a' && c <= 'f' ) { +			value = value * 16 + 10 + c - 'a'; +			continue; +		} +		if ( c >= 'A' && c <= 'F' ) { +			value = value * 16 + 10 + c - 'A'; +			continue; +		} +	} + +	return value; +} + +/* +=============== +VM_LoadSymbols +=============== +*/ +void VM_LoadSymbols( vm_t *vm ) { +	int		len; +	char	*mapfile, *text_p, *token; +	char	name[MAX_QPATH]; +	char	symbols[MAX_QPATH]; +	vmSymbol_t	**prev, *sym; +	int		count; +	int		value; +	int		chars; +	int		segment; +	int		numInstructions; + +	// don't load symbols if not developer +	if ( !com_developer->integer ) { +		return; +	} + +	COM_StripExtension(vm->name, name, sizeof(name)); +	Com_sprintf( symbols, sizeof( symbols ), "vm/%s.map", name ); +	len = FS_ReadFile( symbols, (void **)&mapfile ); +	if ( !mapfile ) { +		Com_Printf( "Couldn't load symbol file: %s\n", symbols ); +		return; +	} + +	numInstructions = vm->instructionPointersLength >> 2; + +	// parse the symbols +	text_p = mapfile; +	prev = &vm->symbols; +	count = 0; + +	while ( 1 ) { +		token = COM_Parse( &text_p ); +		if ( !token[0] ) { +			break; +		} +		segment = ParseHex( token ); +		if ( segment ) { +			COM_Parse( &text_p ); +			COM_Parse( &text_p ); +			continue;		// only load code segment values +		} + +		token = COM_Parse( &text_p ); +		if ( !token[0] ) { +			Com_Printf( "WARNING: incomplete line at end of file\n" ); +			break; +		} +		value = ParseHex( token ); + +		token = COM_Parse( &text_p ); +		if ( !token[0] ) { +			Com_Printf( "WARNING: incomplete line at end of file\n" ); +			break; +		} +		chars = strlen( token ); +		sym = Hunk_Alloc( sizeof( *sym ) + chars, h_high ); +		*prev = sym; +		prev = &sym->next; +		sym->next = NULL; + +		// convert value from an instruction number to a code offset +		if ( value >= 0 && value < numInstructions ) { +			value = vm->instructionPointers[value]; +		} + +		sym->symValue = value; +		Q_strncpyz( sym->symName, token, chars + 1 ); + +		count++; +	} + +	vm->numSymbols = count; +	Com_Printf( "%i symbols parsed from %s\n", count, symbols ); +	FS_FreeFile( mapfile ); +} + +/* +============ +VM_DllSyscall + +Dlls will call this directly + + rcg010206 The horror; the horror. + +  The syscall mechanism relies on stack manipulation to get it's args. +   This is likely due to C's inability to pass "..." parameters to +   a function in one clean chunk. On PowerPC Linux, these parameters +   are not necessarily passed on the stack, so while (&arg[0] == arg) +   is true, (&arg[1] == 2nd function parameter) is not necessarily +   accurate, as arg's value might have been stored to the stack or +   other piece of scratch memory to give it a valid address, but the +   next parameter might still be sitting in a register. + +  Quake's syscall system also assumes that the stack grows downward, +   and that any needed types can be squeezed, safely, into a signed int. + +  This hack below copies all needed values for an argument to a +   array in memory, so that Quake can get the correct values. This can +   also be used on systems where the stack grows upwards, as the +   presumably standard and safe stdargs.h macros are used. + +  As for having enough space in a signed int for your datatypes, well, +   it might be better to wait for DOOM 3 before you start porting.  :) + +  The original code, while probably still inherently dangerous, seems +   to work well enough for the platforms it already works on. Rather +   than add the performance hit for those platforms, the original code +   is still in use there. + +  For speed, we just grab 15 arguments, and don't worry about exactly +   how many the syscall actually needs; the extra is thrown away. +  +============ +*/ +intptr_t QDECL VM_DllSyscall( intptr_t arg, ... ) { +#if !id386 +  // rcg010206 - see commentary above +  intptr_t args[16]; +  int i; +  va_list ap; +   +  args[0] = arg; +   +  va_start(ap, arg); +  for (i = 1; i < sizeof (args) / sizeof (args[i]); i++) +    args[i] = va_arg(ap, intptr_t); +  va_end(ap); +   +  return currentVM->systemCall( args ); +#else // original id code +	return currentVM->systemCall( &arg ); +#endif +} + +/* +================= +VM_LoadQVM + +Load a .qvm file +================= +*/ +vmHeader_t *VM_LoadQVM( vm_t *vm, qboolean alloc ) { +	int					length; +	int					dataLength; +	int					i; +	char				filename[MAX_QPATH]; +	vmHeader_t	*header; + +	// load the image +	Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", vm->name ); +	Com_Printf( "Loading vm file %s...\n", filename ); +	length = FS_ReadFile( filename, (void **)&header ); +	if ( !header ) { +		Com_Printf( "Failed.\n" ); +		VM_Free( vm ); +		return NULL; +	} + +	if( LittleLong( header->vmMagic ) == VM_MAGIC_VER2 ) { +		Com_Printf( "...which has vmMagic VM_MAGIC_VER2\n" ); + +		// byte swap the header +		for ( i = 0 ; i < sizeof( vmHeader_t ) / 4 ; i++ ) { +			((int *)header)[i] = LittleLong( ((int *)header)[i] ); +		} + +		// validate +		if ( header->jtrgLength < 0 +			|| header->bssLength < 0 +			|| header->dataLength < 0 +			|| header->litLength < 0 +			|| header->codeLength <= 0 ) { +			VM_Free( vm ); +			Com_Error( ERR_FATAL, "%s has bad header", filename ); +		} +	} else if( LittleLong( header->vmMagic ) == VM_MAGIC ) { +		// byte swap the header +		// sizeof( vmHeader_t ) - sizeof( int ) is the 1.32b vm header size +		for ( i = 0 ; i < ( sizeof( vmHeader_t ) - sizeof( int ) ) / 4 ; i++ ) { +			((int *)header)[i] = LittleLong( ((int *)header)[i] ); +		} + +		// validate +		if ( header->bssLength < 0 +			|| header->dataLength < 0 +			|| header->litLength < 0 +			|| header->codeLength <= 0 ) { +			VM_Free( vm ); +			Com_Error( ERR_FATAL, "%s has bad header", filename ); +		} +	} else { +		VM_Free( vm ); +		Com_Error( ERR_FATAL, "%s does not have a recognisable " +				"magic number in its header", filename ); +	} + +	// round up to next power of 2 so all data operations can +	// be mask protected +	dataLength = header->dataLength + header->litLength + header->bssLength; +	for ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) { +	} +	dataLength = 1 << i; + +	if( alloc ) { +		// allocate zero filled space for initialized and uninitialized data +		vm->dataBase = Hunk_Alloc( dataLength, h_high ); +		vm->dataMask = dataLength - 1; +	} else { +		// clear the data +		Com_Memset( vm->dataBase, 0, dataLength ); +	} + +	// copy the intialized data +	Com_Memcpy( vm->dataBase, (byte *)header + header->dataOffset, header->dataLength + header->litLength ); + +	// byte swap the longs +	for ( i = 0 ; i < header->dataLength ; i += 4 ) { +		*(int *)(vm->dataBase + i) = LittleLong( *(int *)(vm->dataBase + i ) ); +	} + +	if( header->vmMagic == VM_MAGIC_VER2 ) { +		vm->numJumpTableTargets = header->jtrgLength >> 2; +		Com_Printf( "Loading %d jump table targets\n", vm->numJumpTableTargets ); + +		if( alloc ) { +			vm->jumpTableTargets = Hunk_Alloc( header->jtrgLength, h_high ); +		} else { +			Com_Memset( vm->jumpTableTargets, 0, header->jtrgLength ); +		} + +		Com_Memcpy( vm->jumpTableTargets, (byte *)header + header->dataOffset + +				header->dataLength + header->litLength, header->jtrgLength ); + +		// byte swap the longs +		for ( i = 0 ; i < header->jtrgLength ; i += 4 ) { +			*(int *)(vm->jumpTableTargets + i) = LittleLong( *(int *)(vm->jumpTableTargets + i ) ); +		} +	} + +	return header; +} + +/* +================= +VM_Restart + +Reload the data, but leave everything else in place +This allows a server to do a map_restart without changing memory allocation +================= +*/ +vm_t *VM_Restart( vm_t *vm ) { +	vmHeader_t	*header; + +	// DLL's can't be restarted in place +	if ( vm->dllHandle ) { +		char	name[MAX_QPATH]; +		intptr_t	(*systemCall)( intptr_t *parms ); +		 +		systemCall = vm->systemCall;	 +		Q_strncpyz( name, vm->name, sizeof( name ) ); + +		VM_Free( vm ); + +		vm = VM_Create( name, systemCall, VMI_NATIVE ); +		return vm; +	} + +	// load the image +	Com_Printf( "VM_Restart()\n" ); + +	if( !( header = VM_LoadQVM( vm, qfalse ) ) ) { +		Com_Error( ERR_DROP, "VM_Restart failed.\n" ); +		return NULL; +	} + +	// free the original file +	FS_FreeFile( header ); + +	return vm; +} + +/* +================ +VM_Create + +If image ends in .qvm it will be interpreted, otherwise +it will attempt to load as a system dll +================ +*/ + +#define	STACK_SIZE	0x20000 + +vm_t *VM_Create( const char *module, intptr_t (*systemCalls)(intptr_t *),  +				vmInterpret_t interpret ) { +	vm_t		*vm; +	vmHeader_t	*header; +	int			i, remaining; + +	if ( !module || !module[0] || !systemCalls ) { +		Com_Error( ERR_FATAL, "VM_Create: bad parms" ); +	} + +	remaining = Hunk_MemoryRemaining(); + +	// see if we already have the VM +	for ( i = 0 ; i < MAX_VM ; i++ ) { +		if (!Q_stricmp(vmTable[i].name, module)) { +			vm = &vmTable[i]; +			return vm; +		} +	} + +	// find a free vm +	for ( i = 0 ; i < MAX_VM ; i++ ) { +		if ( !vmTable[i].name[0] ) { +			break; +		} +	} + +	if ( i == MAX_VM ) { +		Com_Error( ERR_FATAL, "VM_Create: no free vm_t" ); +	} + +	vm = &vmTable[i]; + +	Q_strncpyz( vm->name, module, sizeof( vm->name ) ); +	vm->systemCall = systemCalls; + +	// never allow dll loading with a demo +	if ( interpret == VMI_NATIVE ) { +		if ( Cvar_VariableValue( "fs_restrict" ) ) { +			interpret = VMI_COMPILED; +		} +	} + +	if ( interpret == VMI_NATIVE ) { +		// try to load as a system dll +		Com_Printf( "Loading dll file %s.\n", vm->name ); +		vm->dllHandle = Sys_LoadDll( module, vm->fqpath , &vm->entryPoint, VM_DllSyscall ); +		if ( vm->dllHandle ) { +			return vm; +		} + +		Com_Printf( "Failed to load dll, looking for qvm.\n" ); +		interpret = VMI_COMPILED; +	} + +	// load the image +	if( !( header = VM_LoadQVM( vm, qtrue ) ) ) { +		return NULL; +	} + +	// allocate space for the jump targets, which will be filled in by the compile/prep functions +	vm->instructionPointersLength = header->instructionCount * 4; +	vm->instructionPointers = Hunk_Alloc( vm->instructionPointersLength, h_high ); + +	// copy or compile the instructions +	vm->codeLength = header->codeLength; + +	vm->compiled = qfalse; + +#ifdef NO_VM_COMPILED +	if(interpret >= VMI_COMPILED) { +		Com_Printf("Architecture doesn't have a bytecode compiler, using interpreter\n"); +		interpret = VMI_BYTECODE; +	} +#else +	if ( interpret >= VMI_COMPILED ) { +		vm->compiled = qtrue; +		VM_Compile( vm, header ); +	} +#endif +	// VM_Compile may have reset vm->compiled if compilation failed +	if (!vm->compiled) +	{ +		VM_PrepareInterpreter( vm, header ); +	} + +	// free the original file +	FS_FreeFile( header ); + +	// load the map file +	VM_LoadSymbols( vm ); + +	// the stack is implicitly at the end of the image +	vm->programStack = vm->dataMask + 1; +	vm->stackBottom = vm->programStack - STACK_SIZE; + +	Com_Printf("%s loaded in %d bytes on the hunk\n", module, remaining - Hunk_MemoryRemaining()); + +	return vm; +} + +/* +============== +VM_Free +============== +*/ +void VM_Free( vm_t *vm ) { + +	if(vm->destroy) +		vm->destroy(vm); + +	if ( vm->dllHandle ) { +		Sys_UnloadDll( vm->dllHandle ); +		Com_Memset( vm, 0, sizeof( *vm ) ); +	} +#if 0	// now automatically freed by hunk +	if ( vm->codeBase ) { +		Z_Free( vm->codeBase ); +	} +	if ( vm->dataBase ) { +		Z_Free( vm->dataBase ); +	} +	if ( vm->instructionPointers ) { +		Z_Free( vm->instructionPointers ); +	} +#endif +	Com_Memset( vm, 0, sizeof( *vm ) ); + +	currentVM = NULL; +	lastVM = NULL; +} + +void VM_Clear(void) { +	int i; +	for (i=0;i<MAX_VM; i++) { +		if ( vmTable[i].dllHandle ) { +			Sys_UnloadDll( vmTable[i].dllHandle ); +		} +		Com_Memset( &vmTable[i], 0, sizeof( vm_t ) ); +	} +	currentVM = NULL; +	lastVM = NULL; +} + +void *VM_ArgPtr( intptr_t intValue ) { +	if ( !intValue ) { +		return NULL; +	} +	// bk001220 - currentVM is missing on reconnect +	if ( currentVM==NULL ) +	  return NULL; + +	if ( currentVM->entryPoint ) { +		return (void *)(currentVM->dataBase + intValue); +	} +	else { +		return (void *)(currentVM->dataBase + (intValue & currentVM->dataMask)); +	} +} + +void *VM_ExplicitArgPtr( vm_t *vm, intptr_t intValue ) { +	if ( !intValue ) { +		return NULL; +	} + +	// bk010124 - currentVM is missing on reconnect here as well? +	if ( currentVM==NULL ) +	  return NULL; + +	// +	if ( vm->entryPoint ) { +		return (void *)(vm->dataBase + intValue); +	} +	else { +		return (void *)(vm->dataBase + (intValue & vm->dataMask)); +	} +} + + +/* +============== +VM_Call + + +Upon a system call, the stack will look like: + +sp+32	parm1 +sp+28	parm0 +sp+24	return value +sp+20	return address +sp+16	local1 +sp+14	local0 +sp+12	arg1 +sp+8	arg0 +sp+4	return stack +sp		return address + +An interpreted function will immediately execute +an OP_ENTER instruction, which will subtract space for +locals from sp +============== +*/ +#define	MAX_STACK	256 +#define	STACK_MASK	(MAX_STACK-1) + +intptr_t	QDECL VM_Call( vm_t *vm, int callnum, ... ) { +	vm_t	*oldVM; +	intptr_t r; +	int i; + +	if ( !vm ) { +		Com_Error( ERR_FATAL, "VM_Call with NULL vm" ); +	} + +	oldVM = currentVM; +	currentVM = vm; +	lastVM = vm; + +	if ( vm_debugLevel ) { +	  Com_Printf( "VM_Call( %d )\n", callnum ); +	} + +	// if we have a dll loaded, call it directly +	if ( vm->entryPoint ) { +		//rcg010207 -  see dissertation at top of VM_DllSyscall() in this file. +		int args[10]; +		va_list ap; +		va_start(ap, callnum); +		for (i = 0; i < sizeof (args) / sizeof (args[i]); i++) { +			args[i] = va_arg(ap, int); +		} +		va_end(ap); + +		r = vm->entryPoint( callnum,  args[0],  args[1],  args[2], args[3], +                            args[4],  args[5],  args[6], args[7], +                            args[8],  args[9]); +	} else { +#if id386 // i386 calling convention doesn't need conversion +#ifndef NO_VM_COMPILED +		if ( vm->compiled ) +			r = VM_CallCompiled( vm, (int*)&callnum ); +		else +#endif +			r = VM_CallInterpreted( vm, (int*)&callnum ); +#else +		struct { +			int callnum; +			int args[10]; +		} a; +		va_list ap; + +		a.callnum = callnum; +		va_start(ap, callnum); +		for (i = 0; i < sizeof (a.args) / sizeof (a.args[0]); i++) { +			a.args[i] = va_arg(ap, int); +		} +		va_end(ap); +#ifndef NO_VM_COMPILED +		if ( vm->compiled ) +			r = VM_CallCompiled( vm, &a.callnum ); +		else +#endif +			r = VM_CallInterpreted( vm, &a.callnum ); +#endif +	} + +	if ( oldVM != NULL ) // bk001220 - assert(currentVM!=NULL) for oldVM==NULL +	  currentVM = oldVM; +	return r; +} + +//================================================================= + +static int QDECL VM_ProfileSort( const void *a, const void *b ) { +	vmSymbol_t	*sa, *sb; + +	sa = *(vmSymbol_t **)a; +	sb = *(vmSymbol_t **)b; + +	if ( sa->profileCount < sb->profileCount ) { +		return -1; +	} +	if ( sa->profileCount > sb->profileCount ) { +		return 1; +	} +	return 0; +} + +/* +============== +VM_VmProfile_f + +============== +*/ +void VM_VmProfile_f( void ) { +	vm_t		*vm; +	vmSymbol_t	**sorted, *sym; +	int			i; +	double		total; + +	if ( !lastVM ) { +		return; +	} + +	vm = lastVM; + +	if ( !vm->numSymbols ) { +		return; +	} + +	sorted = Z_Malloc( vm->numSymbols * sizeof( *sorted ) ); +	sorted[0] = vm->symbols; +	total = sorted[0]->profileCount; +	for ( i = 1 ; i < vm->numSymbols ; i++ ) { +		sorted[i] = sorted[i-1]->next; +		total += sorted[i]->profileCount; +	} + +	qsort( sorted, vm->numSymbols, sizeof( *sorted ), VM_ProfileSort ); + +	for ( i = 0 ; i < vm->numSymbols ; i++ ) { +		int		perc; + +		sym = sorted[i]; + +		perc = 100 * (float) sym->profileCount / total; +		Com_Printf( "%2i%% %9i %s\n", perc, sym->profileCount, sym->symName ); +		sym->profileCount = 0; +	} + +	Com_Printf("    %9.0f total\n", total ); + +	Z_Free( sorted ); +} + +/* +============== +VM_VmInfo_f + +============== +*/ +void VM_VmInfo_f( void ) { +	vm_t	*vm; +	int		i; + +	Com_Printf( "Registered virtual machines:\n" ); +	for ( i = 0 ; i < MAX_VM ; i++ ) { +		vm = &vmTable[i]; +		if ( !vm->name[0] ) { +			break; +		} +		Com_Printf( "%s : ", vm->name ); +		if ( vm->dllHandle ) { +			Com_Printf( "native\n" ); +			continue; +		} +		if ( vm->compiled ) { +			Com_Printf( "compiled on load\n" ); +		} else { +			Com_Printf( "interpreted\n" ); +		} +		Com_Printf( "    code length : %7i\n", vm->codeLength ); +		Com_Printf( "    table length: %7i\n", vm->instructionPointersLength ); +		Com_Printf( "    data length : %7i\n", vm->dataMask + 1 ); +	} +} + +/* +=============== +VM_LogSyscalls + +Insert calls to this while debugging the vm compiler +=============== +*/ +void VM_LogSyscalls( int *args ) { +	static	int		callnum; +	static	FILE	*f; + +	if ( !f ) { +		f = fopen("syscalls.log", "w" ); +	} +	callnum++; +	fprintf(f, "%i: %p (%i) = %i %i %i %i\n", callnum, (void*)(args - (int *)currentVM->dataBase), +		args[0], args[1], args[2], args[3], args[4] ); +} diff --git a/src/qcommon/vm_interpreted.c b/src/qcommon/vm_interpreted.c new file mode 100644 index 0000000..35096e5 --- /dev/null +++ b/src/qcommon/vm_interpreted.c @@ -0,0 +1,922 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ +#include "vm_local.h" + +//#define	DEBUG_VM +#ifdef DEBUG_VM // bk001204 +static char	*opnames[256] = { +	"OP_UNDEF",  + +	"OP_IGNORE",  + +	"OP_BREAK", + +	"OP_ENTER", +	"OP_LEAVE", +	"OP_CALL", +	"OP_PUSH", +	"OP_POP", + +	"OP_CONST", + +	"OP_LOCAL", + +	"OP_JUMP", + +	//------------------- + +	"OP_EQ", +	"OP_NE", + +	"OP_LTI", +	"OP_LEI", +	"OP_GTI", +	"OP_GEI", + +	"OP_LTU", +	"OP_LEU", +	"OP_GTU", +	"OP_GEU", + +	"OP_EQF", +	"OP_NEF", + +	"OP_LTF", +	"OP_LEF", +	"OP_GTF", +	"OP_GEF", + +	//------------------- + +	"OP_LOAD1", +	"OP_LOAD2", +	"OP_LOAD4", +	"OP_STORE1", +	"OP_STORE2", +	"OP_STORE4", +	"OP_ARG", + +	"OP_BLOCK_COPY", + +	//------------------- + +	"OP_SEX8", +	"OP_SEX16", + +	"OP_NEGI", +	"OP_ADD", +	"OP_SUB", +	"OP_DIVI", +	"OP_DIVU", +	"OP_MODI", +	"OP_MODU", +	"OP_MULI", +	"OP_MULU", + +	"OP_BAND", +	"OP_BOR", +	"OP_BXOR", +	"OP_BCOM", + +	"OP_LSH", +	"OP_RSHI", +	"OP_RSHU", + +	"OP_NEGF", +	"OP_ADDF", +	"OP_SUBF", +	"OP_DIVF", +	"OP_MULF", + +	"OP_CVIF", +	"OP_CVFI" +}; +#endif + +#if idppc + +//FIXME: these, um... look the same to me +#if defined(__GNUC__) +static ID_INLINE unsigned int loadWord(void *addr) { +	unsigned int word; + +	asm("lwbrx %0,0,%1" : "=r" (word) : "r" (addr)); +	return word; +} +#else +static ID_INLINE unsigned int __lwbrx(register void *addr, +		register int offset) { +	register unsigned int word; + +	asm("lwbrx %0,%2,%1" : "=r" (word) : "r" (addr), "b" (offset)); +	return word; +} +#define loadWord(addr) __lwbrx(addr,0) +#endif + +#else +    static ID_INLINE int loadWord(void *addr) { +	int word; +	memcpy(&word, addr, 4); +	return LittleLong(word); +    } +#endif + +char *VM_Indent( vm_t *vm ) { +	static char	*string = "                                        "; +	if ( vm->callLevel > 20 ) { +		return string; +	} +	return string + 2 * ( 20 - vm->callLevel ); +} + +void VM_StackTrace( vm_t *vm, int programCounter, int programStack ) { +	int		count; + +	count = 0; +	do { +		Com_Printf( "%s\n", VM_ValueToSymbol( vm, programCounter ) ); +		programStack =  *(int *)&vm->dataBase[programStack+4]; +		programCounter = *(int *)&vm->dataBase[programStack]; +	} while ( programCounter != -1 && ++count < 32 ); + +} + + +/* +==================== +VM_PrepareInterpreter +==================== +*/ +void VM_PrepareInterpreter( vm_t *vm, vmHeader_t *header ) { +	int		op; +	int		pc; +	byte	*code; +	int		instruction; +	int		*codeBase; + +	vm->codeBase = Hunk_Alloc( vm->codeLength*4, h_high );			// we're now int aligned +//	memcpy( vm->codeBase, (byte *)header + header->codeOffset, vm->codeLength ); + +	// we don't need to translate the instructions, but we still need +	// to find each instructions starting point for jumps +	pc = 0; +	instruction = 0; +	code = (byte *)header + header->codeOffset; +	codeBase = (int *)vm->codeBase; + +	while ( instruction < header->instructionCount ) { +		vm->instructionPointers[ instruction ] = pc; +		instruction++; + +		op = code[ pc ]; +		codeBase[pc] = op; +		if ( pc > header->codeLength ) { +			Com_Error( ERR_FATAL, "VM_PrepareInterpreter: pc > header->codeLength" ); +		} + +		pc++; + +		// these are the only opcodes that aren't a single byte +		switch ( op ) { +		case OP_ENTER: +		case OP_CONST: +		case OP_LOCAL: +		case OP_LEAVE: +		case OP_EQ: +		case OP_NE: +		case OP_LTI: +		case OP_LEI: +		case OP_GTI: +		case OP_GEI: +		case OP_LTU: +		case OP_LEU: +		case OP_GTU: +		case OP_GEU: +		case OP_EQF: +		case OP_NEF: +		case OP_LTF: +		case OP_LEF: +		case OP_GTF: +		case OP_GEF: +		case OP_BLOCK_COPY: +			codeBase[pc+0] = loadWord(&code[pc]); +			pc += 4; +			break; +		case OP_ARG: +			codeBase[pc+0] = code[pc]; +			pc += 1; +			break; +		default: +			break; +		} + +	} +	pc = 0; +	instruction = 0; +	code = (byte *)header + header->codeOffset; +	codeBase = (int *)vm->codeBase; + +	while ( instruction < header->instructionCount ) { +		op = code[ pc ]; +		instruction++; +		pc++; +		switch ( op ) { +		case OP_ENTER: +		case OP_CONST: +		case OP_LOCAL: +		case OP_LEAVE: +		case OP_EQ: +		case OP_NE: +		case OP_LTI: +		case OP_LEI: +		case OP_GTI: +		case OP_GEI: +		case OP_LTU: +		case OP_LEU: +		case OP_GTU: +		case OP_GEU: +		case OP_EQF: +		case OP_NEF: +		case OP_LTF: +		case OP_LEF: +		case OP_GTF: +		case OP_GEF: +		case OP_BLOCK_COPY: +			switch(op) { +				case OP_EQ: +				case OP_NE: +				case OP_LTI: +				case OP_LEI: +				case OP_GTI: +				case OP_GEI: +				case OP_LTU: +				case OP_LEU: +				case OP_GTU: +				case OP_GEU: +				case OP_EQF: +				case OP_NEF: +				case OP_LTF: +				case OP_LEF: +				case OP_GTF: +				case OP_GEF: +				codeBase[pc] = vm->instructionPointers[codeBase[pc]]; +				break; +			default: +				break; +			} +			pc += 4; +			break; +		case OP_ARG: +			pc += 1; +			break; +		default: +			break; +		} + +	} +} + +/* +============== +VM_Call + + +Upon a system call, the stack will look like: + +sp+32	parm1 +sp+28	parm0 +sp+24	return stack +sp+20	return address +sp+16	local1 +sp+14	local0 +sp+12	arg1 +sp+8	arg0 +sp+4	return stack +sp		return address + +An interpreted function will immediately execute +an OP_ENTER instruction, which will subtract space for +locals from sp +============== +*/ +#define	MAX_STACK	256 +#define	STACK_MASK	(MAX_STACK-1) + +#define	DEBUGSTR va("%s%i", VM_Indent(vm), opStack-stack ) + +int	VM_CallInterpreted( vm_t *vm, int *args ) { +	int		stack[MAX_STACK]; +	int		*opStack; +	int		programCounter; +	int		programStack; +	int		stackOnEntry; +	byte	*image; +	int		*codeImage; +	int		v1; +	int		dataMask; +#ifdef DEBUG_VM +	vmSymbol_t	*profileSymbol; +#endif + +	// interpret the code +	vm->currentlyInterpreting = qtrue; + +	// we might be called recursively, so this might not be the very top +	programStack = stackOnEntry = vm->programStack; + +#ifdef DEBUG_VM +	profileSymbol = VM_ValueToFunctionSymbol( vm, 0 ); +	// uncomment this for debugging breakpoints +	vm->breakFunction = 0; +#endif +	// set up the stack frame  + +	image = vm->dataBase; +	codeImage = (int *)vm->codeBase; +	dataMask = vm->dataMask; +	 +	// leave a free spot at start of stack so +	// that as long as opStack is valid, opStack-1 will +	// not corrupt anything +	opStack = stack; +	programCounter = 0; + +	programStack -= 48; + +	*(int *)&image[ programStack + 44] = args[9]; +	*(int *)&image[ programStack + 40] = args[8]; +	*(int *)&image[ programStack + 36] = args[7]; +	*(int *)&image[ programStack + 32] = args[6]; +	*(int *)&image[ programStack + 28] = args[5]; +	*(int *)&image[ programStack + 24] = args[4]; +	*(int *)&image[ programStack + 20] = args[3]; +	*(int *)&image[ programStack + 16] = args[2]; +	*(int *)&image[ programStack + 12] = args[1]; +	*(int *)&image[ programStack + 8 ] = args[0]; +	*(int *)&image[ programStack + 4 ] = 0;	// return stack +	*(int *)&image[ programStack ] = -1;	// will terminate the loop on return + +	vm->callLevel = 0; +	 +	VM_Debug(0); + +//	vm_debugLevel=2; +	// main interpreter loop, will exit when a LEAVE instruction +	// grabs the -1 program counter + +#define r2 codeImage[programCounter] + +	while ( 1 ) { +		int		opcode,	r0, r1; +//		unsigned int	r2; + +nextInstruction: +		r0 = ((int *)opStack)[0]; +		r1 = ((int *)opStack)[-1]; +nextInstruction2: +#ifdef DEBUG_VM +		if ( (unsigned)programCounter >= vm->codeLength ) { +			Com_Error( ERR_DROP, "VM pc out of range" ); +		} + +		if ( opStack < stack ) { +			Com_Error( ERR_DROP, "VM opStack underflow" ); +		} +		if ( opStack >= stack+MAX_STACK ) { +			Com_Error( ERR_DROP, "VM opStack overflow" ); +		} + +		if ( programStack <= vm->stackBottom ) { +			Com_Error( ERR_DROP, "VM stack overflow" ); +		} + +		if ( programStack & 3 ) { +			Com_Error( ERR_DROP, "VM program stack misaligned" ); +		} + +		if ( vm_debugLevel > 1 ) { +			Com_Printf( "%s %s\n", DEBUGSTR, opnames[opcode] ); +		} +		profileSymbol->profileCount++; +#endif +		opcode = codeImage[ programCounter++ ]; + +		switch ( opcode ) { +#ifdef DEBUG_VM +		default: +			Com_Error( ERR_DROP, "Bad VM instruction" );  // this should be scanned on load! +#endif +		case OP_BREAK: +			vm->breakCount++; +			goto nextInstruction2; +		case OP_CONST: +			opStack++; +			r1 = r0; +			r0 = *opStack = r2; +			 +			programCounter += 4; +			goto nextInstruction2; +		case OP_LOCAL: +			opStack++; +			r1 = r0; +			r0 = *opStack = r2+programStack; + +			programCounter += 4; +			goto nextInstruction2; + +		case OP_LOAD4: +#ifdef DEBUG_VM +			if ( *opStack & 3 ) { +				Com_Error( ERR_DROP, "OP_LOAD4 misaligned" ); +			} +#endif +			r0 = *opStack = *(int *)&image[ r0&dataMask ]; +			goto nextInstruction2; +		case OP_LOAD2: +			r0 = *opStack = *(unsigned short *)&image[ r0&dataMask ]; +			goto nextInstruction2; +		case OP_LOAD1: +			r0 = *opStack = image[ r0&dataMask ]; +			goto nextInstruction2; + +		case OP_STORE4: +			*(int *)&image[ r1&(dataMask & ~3) ] = r0; +			opStack -= 2; +			goto nextInstruction; +		case OP_STORE2: +			*(short *)&image[ r1&(dataMask & ~1) ] = r0; +			opStack -= 2; +			goto nextInstruction; +		case OP_STORE1: +			image[ r1&dataMask ] = r0; +			opStack -= 2; +			goto nextInstruction; + +		case OP_ARG: +			// single byte offset from programStack +			*(int *)&image[ codeImage[programCounter] + programStack ] = r0; +			opStack--; +			programCounter += 1; +			goto nextInstruction; + +		case OP_BLOCK_COPY: +			{ +				int		*src, *dest; +				int		i, count, srci, desti; + +				count = r2; +				// MrE: copy range check +				srci = r0 & dataMask; +				desti = r1 & dataMask; +				count = ((srci + count) & dataMask) - srci; +				count = ((desti + count) & dataMask) - desti; + +				src = (int *)&image[ r0&dataMask ]; +				dest = (int *)&image[ r1&dataMask ]; +				if ( ( (intptr_t)src | (intptr_t)dest | count ) & 3 ) { +					// happens in westernq3 +					Com_Printf( S_COLOR_YELLOW "Warning: OP_BLOCK_COPY not dword aligned\n"); +				} +				count >>= 2; +				for ( i = count-1 ; i>= 0 ; i-- ) { +					dest[i] = src[i]; +				} +				programCounter += 4; +				opStack -= 2; +			} +			goto nextInstruction; + +		case OP_CALL: +			// save current program counter +			*(int *)&image[ programStack ] = programCounter; +			 +			// jump to the location on the stack +			programCounter = r0; +			opStack--; +			if ( programCounter < 0 ) { +				// system call +				int		r; +				int		temp; +#ifdef DEBUG_VM +				int		stomped; + +				if ( vm_debugLevel ) { +					Com_Printf( "%s---> systemcall(%i)\n", DEBUGSTR, -1 - programCounter ); +				} +#endif +				// save the stack to allow recursive VM entry +				temp = vm->callLevel; +				vm->programStack = programStack - 4; +#ifdef DEBUG_VM +				stomped = *(int *)&image[ programStack + 4 ]; +#endif +				*(int *)&image[ programStack + 4 ] = -1 - programCounter; + +//VM_LogSyscalls( (int *)&image[ programStack + 4 ] ); +				{ +					intptr_t* argptr = (intptr_t *)&image[ programStack + 4 ]; +				#if __WORDSIZE == 64 +				// the vm has ints on the stack, we expect +				// longs so we have to convert it +					intptr_t argarr[16]; +					int i; +					for (i = 0; i < 16; ++i) { +						argarr[i] = *(int*)&image[ programStack + 4 + 4*i ]; +					} +					argptr = argarr; +				#endif +					r = vm->systemCall( argptr ); +				} + +#ifdef DEBUG_VM +				// this is just our stack frame pointer, only needed +				// for debugging +				*(int *)&image[ programStack + 4 ] = stomped; +#endif + +				// save return value +				opStack++; +				*opStack = r; +				programCounter = *(int *)&image[ programStack ]; +				vm->callLevel = temp; +#ifdef DEBUG_VM +				if ( vm_debugLevel ) { +					Com_Printf( "%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter ) ); +				} +#endif +			} else if ( (unsigned)programCounter >= vm->codeLength ) { +				Com_Error( ERR_DROP, "VM program counter out of range in OP_CALL" ); +			} else { +				programCounter = vm->instructionPointers[ programCounter ]; +			} +			goto nextInstruction; + +		// push and pop are only needed for discarded or bad function return values +		case OP_PUSH: +			opStack++; +			goto nextInstruction; +		case OP_POP: +			opStack--; +			goto nextInstruction; + +		case OP_ENTER: +#ifdef DEBUG_VM +			profileSymbol = VM_ValueToFunctionSymbol( vm, programCounter ); +#endif +			// get size of stack frame +			v1 = r2; + +			programCounter += 4; +			programStack -= v1; +#ifdef DEBUG_VM +			// save old stack frame for debugging traces +			*(int *)&image[programStack+4] = programStack + v1; +			if ( vm_debugLevel ) { +				Com_Printf( "%s---> %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter - 5 ) ); +				if ( vm->breakFunction && programCounter - 5 == vm->breakFunction ) { +					// this is to allow setting breakpoints here in the debugger +					vm->breakCount++; +//					vm_debugLevel = 2; +//					VM_StackTrace( vm, programCounter, programStack ); +				} +				vm->callLevel++; +			} +#endif +			goto nextInstruction; +		case OP_LEAVE: +			// remove our stack frame +			v1 = r2; + +			programStack += v1; + +			// grab the saved program counter +			programCounter = *(int *)&image[ programStack ]; +#ifdef DEBUG_VM +			profileSymbol = VM_ValueToFunctionSymbol( vm, programCounter ); +			if ( vm_debugLevel ) { +				vm->callLevel--; +				Com_Printf( "%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter ) ); +			} +#endif +			// check for leaving the VM +			if ( programCounter == -1 ) { +				goto done; +			} else if ( (unsigned)programCounter >= vm->codeLength ) { +				Com_Error( ERR_DROP, "VM program counter out of range in OP_LEAVE" ); +			} +			goto nextInstruction; + +		/* +		=================================================================== +		BRANCHES +		=================================================================== +		*/ + +		case OP_JUMP: +			programCounter = r0; +			programCounter = vm->instructionPointers[ programCounter ]; +			opStack--; +			goto nextInstruction; + +		case OP_EQ: +			opStack -= 2; +			if ( r1 == r0 ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				goto nextInstruction; +			} else { +				programCounter += 4; +				goto nextInstruction; +			} + +		case OP_NE: +			opStack -= 2; +			if ( r1 != r0 ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				goto nextInstruction; +			} else { +				programCounter += 4; +				goto nextInstruction; +			} + +		case OP_LTI: +			opStack -= 2; +			if ( r1 < r0 ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				goto nextInstruction; +			} else { +				programCounter += 4; +				goto nextInstruction; +			} + +		case OP_LEI: +			opStack -= 2; +			if ( r1 <= r0 ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				goto nextInstruction; +			} else { +				programCounter += 4; +				goto nextInstruction; +			} + +		case OP_GTI: +			opStack -= 2; +			if ( r1 > r0 ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				goto nextInstruction; +			} else { +				programCounter += 4; +				goto nextInstruction; +			} + +		case OP_GEI: +			opStack -= 2; +			if ( r1 >= r0 ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				goto nextInstruction; +			} else { +				programCounter += 4; +				goto nextInstruction; +			} + +		case OP_LTU: +			opStack -= 2; +			if ( ((unsigned)r1) < ((unsigned)r0) ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				goto nextInstruction; +			} else { +				programCounter += 4; +				goto nextInstruction; +			} + +		case OP_LEU: +			opStack -= 2; +			if ( ((unsigned)r1) <= ((unsigned)r0) ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				goto nextInstruction; +			} else { +				programCounter += 4; +				goto nextInstruction; +			} + +		case OP_GTU: +			opStack -= 2; +			if ( ((unsigned)r1) > ((unsigned)r0) ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				goto nextInstruction; +			} else { +				programCounter += 4; +				goto nextInstruction; +			} + +		case OP_GEU: +			opStack -= 2; +			if ( ((unsigned)r1) >= ((unsigned)r0) ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				goto nextInstruction; +			} else { +				programCounter += 4; +				goto nextInstruction; +			} + +		case OP_EQF: +			if ( ((float *)opStack)[-1] == *(float *)opStack ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				opStack -= 2; +				goto nextInstruction; +			} else { +				programCounter += 4; +				opStack -= 2; +				goto nextInstruction; +			} + +		case OP_NEF: +			if ( ((float *)opStack)[-1] != *(float *)opStack ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				opStack -= 2; +				goto nextInstruction; +			} else { +				programCounter += 4; +				opStack -= 2; +				goto nextInstruction; +			} + +		case OP_LTF: +			if ( ((float *)opStack)[-1] < *(float *)opStack ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				opStack -= 2; +				goto nextInstruction; +			} else { +				programCounter += 4; +				opStack -= 2; +				goto nextInstruction; +			} + +		case OP_LEF: +			if ( ((float *)opStack)[-1] <= *(float *)opStack ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				opStack -= 2; +				goto nextInstruction; +			} else { +				programCounter += 4; +				opStack -= 2; +				goto nextInstruction; +			} + +		case OP_GTF: +			if ( ((float *)opStack)[-1] > *(float *)opStack ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				opStack -= 2; +				goto nextInstruction; +			} else { +				programCounter += 4; +				opStack -= 2; +				goto nextInstruction; +			} + +		case OP_GEF: +			if ( ((float *)opStack)[-1] >= *(float *)opStack ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				opStack -= 2; +				goto nextInstruction; +			} else { +				programCounter += 4; +				opStack -= 2; +				goto nextInstruction; +			} + + +		//=================================================================== + +		case OP_NEGI: +			*opStack = -r0; +			goto nextInstruction; +		case OP_ADD: +			opStack[-1] = r1 + r0; +			opStack--; +			goto nextInstruction; +		case OP_SUB: +			opStack[-1] = r1 - r0; +			opStack--; +			goto nextInstruction; +		case OP_DIVI: +			opStack[-1] = r1 / r0; +			opStack--; +			goto nextInstruction; +		case OP_DIVU: +			opStack[-1] = ((unsigned)r1) / ((unsigned)r0); +			opStack--; +			goto nextInstruction; +		case OP_MODI: +			opStack[-1] = r1 % r0; +			opStack--; +			goto nextInstruction; +		case OP_MODU: +			opStack[-1] = ((unsigned)r1) % (unsigned)r0; +			opStack--; +			goto nextInstruction; +		case OP_MULI: +			opStack[-1] = r1 * r0; +			opStack--; +			goto nextInstruction; +		case OP_MULU: +			opStack[-1] = ((unsigned)r1) * ((unsigned)r0); +			opStack--; +			goto nextInstruction; + +		case OP_BAND: +			opStack[-1] = ((unsigned)r1) & ((unsigned)r0); +			opStack--; +			goto nextInstruction; +		case OP_BOR: +			opStack[-1] = ((unsigned)r1) | ((unsigned)r0); +			opStack--; +			goto nextInstruction; +		case OP_BXOR: +			opStack[-1] = ((unsigned)r1) ^ ((unsigned)r0); +			opStack--; +			goto nextInstruction; +		case OP_BCOM: +			*opStack = ~ ((unsigned)r0); +			goto nextInstruction; + +		case OP_LSH: +			opStack[-1] = r1 << r0; +			opStack--; +			goto nextInstruction; +		case OP_RSHI: +			opStack[-1] = r1 >> r0; +			opStack--; +			goto nextInstruction; +		case OP_RSHU: +			opStack[-1] = ((unsigned)r1) >> r0; +			opStack--; +			goto nextInstruction; + +		case OP_NEGF: +			*(float *)opStack =  -*(float *)opStack; +			goto nextInstruction; +		case OP_ADDF: +			*(float *)(opStack-1) = *(float *)(opStack-1) + *(float *)opStack; +			opStack--; +			goto nextInstruction; +		case OP_SUBF: +			*(float *)(opStack-1) = *(float *)(opStack-1) - *(float *)opStack; +			opStack--; +			goto nextInstruction; +		case OP_DIVF: +			*(float *)(opStack-1) = *(float *)(opStack-1) / *(float *)opStack; +			opStack--; +			goto nextInstruction; +		case OP_MULF: +			*(float *)(opStack-1) = *(float *)(opStack-1) * *(float *)opStack; +			opStack--; +			goto nextInstruction; + +		case OP_CVIF: +			*(float *)opStack =  (float)*opStack; +			goto nextInstruction; +		case OP_CVFI: +			*opStack = (int) *(float *)opStack; +			goto nextInstruction; +		case OP_SEX8: +			*opStack = (signed char)*opStack; +			goto nextInstruction; +		case OP_SEX16: +			*opStack = (short)*opStack; +			goto nextInstruction; +		} +	} + +done: +	vm->currentlyInterpreting = qfalse; + +	if ( opStack != &stack[1] ) { +		Com_Error( ERR_DROP, "Interpreter error: opStack = %ld", (long int) (opStack - stack) ); +	} + +	vm->programStack = stackOnEntry; + +	// return the result +	return *opStack; +} diff --git a/src/qcommon/vm_local.h b/src/qcommon/vm_local.h new file mode 100644 index 0000000..27b5226 --- /dev/null +++ b/src/qcommon/vm_local.h @@ -0,0 +1,185 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ +#include "q_shared.h" +#include "qcommon.h" + +typedef enum { +	OP_UNDEF,  + +	OP_IGNORE,  + +	OP_BREAK, + +	OP_ENTER, +	OP_LEAVE, +	OP_CALL, +	OP_PUSH, +	OP_POP, + +	OP_CONST, +	OP_LOCAL, + +	OP_JUMP, + +	//------------------- + +	OP_EQ, +	OP_NE, + +	OP_LTI, +	OP_LEI, +	OP_GTI, +	OP_GEI, + +	OP_LTU, +	OP_LEU, +	OP_GTU, +	OP_GEU, + +	OP_EQF, +	OP_NEF, + +	OP_LTF, +	OP_LEF, +	OP_GTF, +	OP_GEF, + +	//------------------- + +	OP_LOAD1, +	OP_LOAD2, +	OP_LOAD4, +	OP_STORE1, +	OP_STORE2, +	OP_STORE4,				// *(stack[top-1]) = stack[top] +	OP_ARG, + +	OP_BLOCK_COPY, + +	//------------------- + +	OP_SEX8, +	OP_SEX16, + +	OP_NEGI, +	OP_ADD, +	OP_SUB, +	OP_DIVI, +	OP_DIVU, +	OP_MODI, +	OP_MODU, +	OP_MULI, +	OP_MULU, + +	OP_BAND, +	OP_BOR, +	OP_BXOR, +	OP_BCOM, + +	OP_LSH, +	OP_RSHI, +	OP_RSHU, + +	OP_NEGF, +	OP_ADDF, +	OP_SUBF, +	OP_DIVF, +	OP_MULF, + +	OP_CVIF, +	OP_CVFI +} opcode_t; + + + +typedef int	vmptr_t; + +typedef struct vmSymbol_s { +	struct vmSymbol_s	*next; +	int		symValue; +	int		profileCount; +	char	symName[1];		// variable sized +} vmSymbol_t; + +#define	VM_OFFSET_PROGRAM_STACK		0 +#define	VM_OFFSET_SYSTEM_CALL		4 + +struct vm_s { +    // DO NOT MOVE OR CHANGE THESE WITHOUT CHANGING THE VM_OFFSET_* DEFINES +    // USED BY THE ASM CODE +    int			programStack;		// the vm may be recursively entered +    intptr_t			(*systemCall)( intptr_t *parms ); + +	//------------------------------------ +    +    char		name[MAX_QPATH]; + +	// for dynamic linked modules +	void		*dllHandle; +	intptr_t			(QDECL *entryPoint)( int callNum, ... ); +	void (*destroy)(vm_t* self); + +	// for interpreted modules +	qboolean	currentlyInterpreting; + +	qboolean	compiled; +	byte		*codeBase; +	int			codeLength; + +	int			*instructionPointers; +	int			instructionPointersLength; + +	byte		*dataBase; +	int			dataMask; + +	int			stackBottom;		// if programStack < stackBottom, error + +	int			numSymbols; +	struct vmSymbol_s	*symbols; + +	int			callLevel;			// for debug indenting +	int			breakFunction;		// increment breakCount on function entry to this +	int			breakCount; + +// fqpath member added 7/20/02 by T.Ray +	char		fqpath[MAX_QPATH+1] ; + +	byte		*jumpTableTargets; +	int			numJumpTableTargets; +}; + + +extern	vm_t	*currentVM; +extern	int		vm_debugLevel; + +void VM_Compile( vm_t *vm, vmHeader_t *header ); +int	VM_CallCompiled( vm_t *vm, int *args ); + +void VM_PrepareInterpreter( vm_t *vm, vmHeader_t *header ); +int	VM_CallInterpreted( vm_t *vm, int *args ); + +vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ); +int VM_SymbolToValue( vm_t *vm, const char *symbol ); +const char *VM_ValueToSymbol( vm_t *vm, int value ); +void VM_LogSyscalls( int *args ); + diff --git a/src/qcommon/vm_none.c b/src/qcommon/vm_none.c new file mode 100644 index 0000000..c7e5ba0 --- /dev/null +++ b/src/qcommon/vm_none.c @@ -0,0 +1,10 @@ +#include "vm_local.h" + +int VM_CallCompiled( vm_t *vm, int *args ) { +	exit(99); +	return 0; +} + +void VM_Compile( vm_t *vm, vmHeader_t *header ) { +	exit(99); +} diff --git a/src/qcommon/vm_ppc.c b/src/qcommon/vm_ppc.c new file mode 100644 index 0000000..460fe42 --- /dev/null +++ b/src/qcommon/vm_ppc.c @@ -0,0 +1,2065 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ +// vm_ppc.c +// ppc dynamic compiler + +#include "vm_local.h" +#include <sys/mman.h> + +#define DEBUG_VM 0 + +#if DEBUG_VM +static char	*opnames[256] = { +	"OP_UNDEF",  + +	"OP_IGNORE",  + +	"OP_BREAK", + +	"OP_ENTER", +	"OP_LEAVE", +	"OP_CALL", +	"OP_PUSH", +	"OP_POP", + +	"OP_CONST", + +	"OP_LOCAL", + +	"OP_JUMP", + +	//------------------- + +	"OP_EQ", +	"OP_NE", + +	"OP_LTI", +	"OP_LEI", +	"OP_GTI", +	"OP_GEI", + +	"OP_LTU", +	"OP_LEU", +	"OP_GTU", +	"OP_GEU", + +	"OP_EQF", +	"OP_NEF", + +	"OP_LTF", +	"OP_LEF", +	"OP_GTF", +	"OP_GEF", + +	//------------------- + +	"OP_LOAD1", +	"OP_LOAD2", +	"OP_LOAD4", +	"OP_STORE1", +	"OP_STORE2", +	"OP_STORE4", +	"OP_ARG", + +	"OP_BLOCK_COPY", + +	//------------------- + +	"OP_SEX8", +	"OP_SEX16", + +	"OP_NEGI", +	"OP_ADD", +	"OP_SUB", +	"OP_DIVI", +	"OP_DIVU", +	"OP_MODI", +	"OP_MODU", +	"OP_MULI", +	"OP_MULU", + +	"OP_BAND", +	"OP_BOR", +	"OP_BXOR", +	"OP_BCOM", + +	"OP_LSH", +	"OP_RSHI", +	"OP_RSHU", + +	"OP_NEGF", +	"OP_ADDF", +	"OP_SUBF", +	"OP_DIVF", +	"OP_MULF", + +	"OP_CVIF", +	"OP_CVFI" +}; +#endif + +typedef enum { +    R_REAL_STACK = 1, +	// registers 3-11 are the parameter passing registers +	 +	// state +	R_STACK = 3,			// local +	R_OPSTACK,				// global + +	// constants	 +	R_MEMBASE,			// global +	R_MEMMASK, +	R_ASMCALL,			// global +    R_INSTRUCTIONS,		// global +    R_NUM_INSTRUCTIONS,	// global +    R_CVM,				// currentVM +     +	// temps +	R_TOP = 11, +	R_SECOND = 12, +	R_EA = 2				// effective address calculation +	  +} regNums_t; + +#define	RG_REAL_STACK		r1 +#define	RG_STACK			r3 +#define	RG_OPSTACK			r4 +#define	RG_MEMBASE			r5 +#define	RG_MEMMASK			r6 +#define	RG_ASMCALL			r7 +#define	RG_INSTRUCTIONS		r8 +#define	RG_NUM_INSTRUCTIONS	r9 +#define	RG_CVM				r10 +#define	RG_TOP				r12 +#define	RG_SECOND			r13 +#define	RG_EA 				r14 + +// The deepest value I saw in the Quake3 games was 9. +#define OP_STACK_MAX_DEPTH	16 + +// These are all volatile and thus must be saved upon entry to the VM code. +// NOTE: These are General Purpose Registers (GPR) numbers like the  +//       R_ definitions in the regNums_t enum above (31 is the max) +static int opStackIntRegisters[OP_STACK_MAX_DEPTH] = +{ +	16, 17, 18, 19, +	20, 21, 22, 23, +	24, 25, 26, 27, +	28, 29, 30, 31 +}; + +static unsigned int *opStackLoadInstructionAddr[OP_STACK_MAX_DEPTH]; + +// We use different registers for the floating point +// operand stack (these are volatile in the PPC ABI) +// NOTE: these are Floating Point Register (FPR) numbers, not  +//       General Purpose Register (GPR) numbers +static int opStackFloatRegisters[OP_STACK_MAX_DEPTH] = +{ +	0, 1, 2, 3, +	4, 5, 6, 7, +	8, 9, 10, 11, +	12, 13, 14, 15 +}; + +static int opStackRegType[OP_STACK_MAX_DEPTH] = +{ +	0, 0, 0, 0, +	0, 0, 0, 0, +	0, 0, 0, 0, +	0, 0, 0, 0 +}; + +// this doesn't have the low order bits set for instructions i'm not using... +typedef enum { +	PPC_TDI		= 0x08000000, +	PPC_TWI		= 0x0c000000, +	PPC_MULLI	= 0x1c000000, +	PPC_SUBFIC	= 0x20000000, +	PPC_CMPI	= 0x28000000, +	PPC_CMPLI	= 0x2c000000, +	PPC_ADDIC	= 0x30000000, +	PPC_ADDIC_	= 0x34000000, +	PPC_ADDI	= 0x38000000, +	PPC_ADDIS	= 0x3c000000, +	PPC_BC		= 0x40000000, +	PPC_SC		= 0x44000000, +	PPC_B		= 0x48000000, + +	PPC_MCRF	= 0x4c000000, +	PPC_BCLR	= 0x4c000020, +	PPC_RFID	= 0x4c000000, +	PPC_CRNOR	= 0x4c000000, +	PPC_RFI		= 0x4c000000, +	PPC_CRANDC	= 0x4c000000, +	PPC_ISYNC	= 0x4c000000, +	PPC_CRXOR	= 0x4c000000, +	PPC_CRNAND	= 0x4c000000, +	PPC_CREQV	= 0x4c000000, +	PPC_CRORC	= 0x4c000000, +	PPC_CROR	= 0x4c000000, +//------------ +	PPC_BCCTR	= 0x4c000420, +	PPC_RLWIMI	= 0x50000000, +	PPC_RLWINM	= 0x54000000, +	PPC_RLWNM	= 0x5c000000, +	PPC_ORI		= 0x60000000, +	PPC_ORIS	= 0x64000000, +	PPC_XORI	= 0x68000000, +	PPC_XORIS	= 0x6c000000, +	PPC_ANDI_	= 0x70000000, +	PPC_ANDIS_	= 0x74000000, +	PPC_RLDICL	= 0x78000000, +	PPC_RLDICR	= 0x78000000, +	PPC_RLDIC	= 0x78000000, +	PPC_RLDIMI	= 0x78000000, +	PPC_RLDCL	= 0x78000000, +	PPC_RLDCR	= 0x78000000, +	PPC_CMP		= 0x7c000000, +	PPC_TW		= 0x7c000000, +	PPC_SUBFC	= 0x7c000010, +	PPC_MULHDU	= 0x7c000000, +	PPC_ADDC	= 0x7c000014, +	PPC_MULHWU	= 0x7c000000, +	PPC_MFCR	= 0x7c000000, +	PPC_LWAR	= 0x7c000000, +	PPC_LDX		= 0x7c000000, +	PPC_LWZX	= 0x7c00002e, +	PPC_SLW		= 0x7c000030, +	PPC_CNTLZW	= 0x7c000000, +	PPC_SLD		= 0x7c000000, +	PPC_AND		= 0x7c000038, +	PPC_CMPL	= 0x7c000040, +	PPC_SUBF	= 0x7c000050, +	PPC_LDUX	= 0x7c000000, +//------------ +	PPC_DCBST	= 0x7c000000, +	PPC_LWZUX	= 0x7c00006c, +	PPC_CNTLZD	= 0x7c000000, +	PPC_ANDC	= 0x7c000000, +	PPC_TD		= 0x7c000000, +	PPC_MULHD	= 0x7c000000, +	PPC_MULHW	= 0x7c000000, +	PPC_MTSRD	= 0x7c000000, +	PPC_MFMSR	= 0x7c000000, +	PPC_LDARX	= 0x7c000000, +	PPC_DCBF	= 0x7c000000, +	PPC_LBZX	= 0x7c0000ae, +	PPC_NEG		= 0x7c000000, +	PPC_MTSRDIN	= 0x7c000000, +	PPC_LBZUX	= 0x7c000000, +	PPC_NOR		= 0x7c0000f8, +	PPC_SUBFE	= 0x7c000000, +	PPC_ADDE	= 0x7c000000, +	PPC_MTCRF	= 0x7c000000, +	PPC_MTMSR	= 0x7c000000, +	PPC_STDX	= 0x7c000000, +	PPC_STWCX_	= 0x7c000000, +	PPC_STWX	= 0x7c00012e, +	PPC_MTMSRD	= 0x7c000000, +	PPC_STDUX	= 0x7c000000, +	PPC_STWUX	= 0x7c00016e, +	PPC_SUBFZE	= 0x7c000000, +	PPC_ADDZE	= 0x7c000000, +	PPC_MTSR	= 0x7c000000, +	PPC_STDCX_	= 0x7c000000, +	PPC_STBX	= 0x7c0001ae, +	PPC_SUBFME	= 0x7c000000, +	PPC_MULLD	= 0x7c000000, +//------------ +	PPC_ADDME	= 0x7c000000, +	PPC_MULLW	= 0x7c0001d6, +	PPC_MTSRIN	= 0x7c000000, +	PPC_DCBTST	= 0x7c000000, +	PPC_STBUX	= 0x7c000000, +	PPC_ADD		= 0x7c000214, +	PPC_DCBT	= 0x7c000000, +	PPC_LHZX	= 0x7c00022e, +	PPC_EQV		= 0x7c000000, +	PPC_TLBIE	= 0x7c000000, +	PPC_ECIWX	= 0x7c000000, +	PPC_LHZUX	= 0x7c000000, +	PPC_XOR		= 0x7c000278, +	PPC_MFSPR	= 0x7c0002a6, +	PPC_LWAX	= 0x7c000000, +	PPC_LHAX	= 0x7c000000, +	PPC_TLBIA	= 0x7c000000, +	PPC_MFTB	= 0x7c000000, +	PPC_LWAUX	= 0x7c000000, +	PPC_LHAUX	= 0x7c000000, +	PPC_STHX	= 0x7c00032e, +	PPC_ORC		= 0x7c000338, +	PPC_SRADI	= 0x7c000000, +	PPC_SLBIE	= 0x7c000000, +	PPC_ECOWX	= 0x7c000000, +	PPC_STHUX	= 0x7c000000, +	PPC_OR		= 0x7c000378, +	PPC_DIVDU	= 0x7c000000, +	PPC_DIVWU	= 0x7c000396, +	PPC_MTSPR	= 0x7c0003a6, +	PPC_DCBI	= 0x7c000000, +	PPC_NAND	= 0x7c000000, +	PPC_DIVD	= 0x7c000000, +//------------ +	PPC_DIVW	= 0x7c0003d6, +	PPC_SLBIA	= 0x7c000000, +	PPC_MCRXR	= 0x7c000000, +	PPC_LSWX	= 0x7c000000, +	PPC_LWBRX	= 0x7c000000, +	PPC_LFSX	= 0x7c00042e, +	PPC_SRW		= 0x7c000430, +	PPC_SRD		= 0x7c000000, +	PPC_TLBSYNC	= 0x7c000000, +	PPC_LFSUX	= 0x7c000000, +	PPC_MFSR	= 0x7c000000, +	PPC_LSWI	= 0x7c000000, +	PPC_SYNC	= 0x7c000000, +	PPC_LFDX	= 0x7c000000, +	PPC_LFDUX	= 0x7c000000, +	PPC_MFSRIN	= 0x7c000000, +	PPC_STSWX	= 0x7c000000, +	PPC_STWBRX	= 0x7c000000, +	PPC_STFSX	= 0x7c00052e, +	PPC_STFSUX	= 0x7c000000, +	PPC_STSWI	= 0x7c000000, +	PPC_STFDX	= 0x7c000000, +	PPC_DCBA	= 0x7c000000, +	PPC_STFDUX	= 0x7c000000, +	PPC_LHBRX	= 0x7c000000, +	PPC_SRAW	= 0x7c000630, +	PPC_SRAD	= 0x7c000000, +	PPC_SRAWI	= 0x7c000000, +	PPC_EIEIO	= 0x7c000000, +	PPC_STHBRX	= 0x7c000000, +	PPC_EXTSH	= 0x7c000734, +	PPC_EXTSB	= 0x7c000774, +	PPC_ICBI	= 0x7c000000, +//------------ +	PPC_STFIWX	= 0x7c0007ae, +	PPC_EXTSW	= 0x7c000000, +	PPC_DCBZ	= 0x7c000000, +	PPC_LWZ		= 0x80000000, +	PPC_LWZU	= 0x84000000, +	PPC_LBZ		= 0x88000000, +	PPC_LBZU	= 0x8c000000, +	PPC_STW		= 0x90000000, +	PPC_STWU	= 0x94000000, +	PPC_STB		= 0x98000000, +	PPC_STBU	= 0x9c000000, +	PPC_LHZ		= 0xa0000000, +	PPC_LHZU	= 0xa4000000, +	PPC_LHA		= 0xa8000000, +	PPC_LHAU	= 0xac000000, +	PPC_STH		= 0xb0000000, +	PPC_STHU	= 0xb4000000, +	PPC_LMW		= 0xb8000000, +	PPC_STMW	= 0xbc000000, +	PPC_LFS		= 0xc0000000, +	PPC_LFSU	= 0xc4000000, +	PPC_LFD		= 0xc8000000, +	PPC_LFDU	= 0xcc000000, +	PPC_STFS	= 0xd0000000, +	PPC_STFSU	= 0xd4000000, +	PPC_STFD	= 0xd8000000, +	PPC_STFDU	= 0xdc000000, +	PPC_LD		= 0xe8000000, +	PPC_LDU		= 0xe8000001, +	PPC_LWA		= 0xe8000002, +	PPC_FDIVS	= 0xec000024, +	PPC_FSUBS	= 0xec000028, +	PPC_FADDS	= 0xec00002a, +//------------ +	PPC_FSQRTS	= 0xec000000, +	PPC_FRES	= 0xec000000, +	PPC_FMULS	= 0xec000032, +	PPC_FMSUBS	= 0xec000000, +	PPC_FMADDS	= 0xec000000, +	PPC_FNMSUBS	= 0xec000000, +	PPC_FNMADDS	= 0xec000000, +	PPC_STD		= 0xf8000000, +	PPC_STDU	= 0xf8000001, +	PPC_FCMPU	= 0xfc000000, +	PPC_FRSP	= 0xfc000018, +	PPC_FCTIW	= 0xfc000000, +	PPC_FCTIWZ	= 0xfc00001e, +	PPC_FDIV	= 0xfc000000, +	PPC_FSUB	= 0xfc000028, +	PPC_FADD	= 0xfc000000, +	PPC_FSQRT	= 0xfc000000, +	PPC_FSEL	= 0xfc000000, +	PPC_FMUL	= 0xfc000000, +	PPC_FRSQRTE	= 0xfc000000, +	PPC_FMSUB	= 0xfc000000, +	PPC_FMADD	= 0xfc000000, +	PPC_FNMSUB	= 0xfc000000, +	PPC_FNMADD	= 0xfc000000, +	PPC_FCMPO	= 0xfc000000, +	PPC_MTFSB1	= 0xfc000000, +	PPC_FNEG	= 0xfc000050, +	PPC_MCRFS	= 0xfc000000, +	PPC_MTFSB0	= 0xfc000000, +	PPC_FMR		= 0xfc000000, +	PPC_MTFSFI	= 0xfc000000, +	PPC_FNABS	= 0xfc000000, +	PPC_FABS	= 0xfc000000, +//------------ +	PPC_MFFS	= 0xfc000000, +	PPC_MTFSF	= 0xfc000000, +	PPC_FCTID	= 0xfc000000, +	PPC_FCTIDZ	= 0xfc000000, +	PPC_FCFID	= 0xfc000000 +	 +} ppcOpcodes_t; + + +// the newly generated code +static	unsigned	*buf; +static	int		compiledOfs;	// in dwords +static int pass; + +// fromt the original bytecode +static	byte	*code; +static	int		pc; + +void AsmCall( void ); + +double	itofConvert[2]; + +static int	Constant4( void ) { +	int		v; + +	v = code[pc] | (code[pc+1]<<8) | (code[pc+2]<<16) | (code[pc+3]<<24); +	pc += 4; +	return v; +} + +static int	Constant1( void ) { +	int		v; + +	v = code[pc]; +	pc += 1; +	return v; +} + +static void Emit4( char *opname, int i ) { +	#if DEBUG_VM +	if(pass == 1) +		printf("\t\t\t%p %s\t%08lx\n",&buf[compiledOfs],opname,i&0x3ffffff); +	#endif +	buf[ compiledOfs ] = i; +	compiledOfs++; +} + +static void Inst( char *opname, int opcode, int destReg, int aReg, int bReg ) { +    unsigned		r; + +	#if DEBUG_VM +	if(pass == 1) +		printf("\t\t\t%p %s\tr%d,r%d,r%d\n",&buf[compiledOfs],opname,destReg,aReg,bReg); +	#endif +    r = opcode | ( destReg << 21 ) | ( aReg << 16 ) | ( bReg << 11 ) ; +    buf[ compiledOfs ] = r; +    compiledOfs++; +} + +static void Inst4( char *opname, int opcode, int destReg, int aReg, int bReg, int cReg ) { +    unsigned		r; + +	#if DEBUG_VM +	if(pass == 1) +		printf("\t\t\t%p %s\tr%d,r%d,r%d,r%d\n",&buf[compiledOfs],opname,destReg,aReg,bReg,cReg); +	#endif +    r = opcode | ( destReg << 21 ) | ( aReg << 16 ) | ( bReg << 11 ) | ( cReg << 6 ); +    buf[ compiledOfs ] = r; +    compiledOfs++; +} + +static void InstImm( char *opname, int opcode, int destReg, int aReg, int immediate ) { +	unsigned		r; +	 +	if ( immediate > 32767 || immediate < -32768 ) { +	    Com_Error( ERR_FATAL, "VM_Compile: immediate value %i out of range, opcode %x,%d,%d", immediate, opcode, destReg, aReg ); +	} +	#if DEBUG_VM +	if(pass == 1) +		printf("\t\t\t%p %s\tr%d,r%d,0x%x\n",&buf[compiledOfs],opname,destReg,aReg,immediate); +	#endif +	r = opcode | ( destReg << 21 ) | ( aReg << 16 ) | ( immediate & 0xffff ); +	buf[ compiledOfs ] = r; +	compiledOfs++; +} + +static void InstImmU( char *opname, int opcode, int destReg, int aReg, int immediate ) { +	unsigned		r; +	 +	if ( immediate > 0xffff || immediate < 0 ) { +		Com_Error( ERR_FATAL, "VM_Compile: immediate value %i out of range", immediate ); +	} +	#if DEBUG_VM +	if(pass == 1) +		printf("\t\t\t%p %s\tr%d,r%d,0x%x\n",&buf[compiledOfs],opname,destReg,aReg,immediate); +	#endif +    r = opcode | ( destReg << 21 ) | ( aReg << 16 ) | ( immediate & 0xffff ); +	buf[ compiledOfs ] = r; +	compiledOfs++; +} + +static int pop0, pop1, oc0, oc1; +static vm_t *tvm; +static int instruction; +static byte *jused; + +static void spillOpStack(int depth) +{ +	// Store out each register on the operand stack to it's correct location. +	int i; +	 +	for(i = 0; i < depth; i++) +	{ +		assert(opStackRegType[i]); +		assert(opStackRegType[i] == 1); +		switch(opStackRegType[i]) +		{ +			case 1:	// Integer register +				InstImm( "stw", PPC_STW, opStackIntRegisters[i], R_OPSTACK, i*4+4); +				break; +			case 2: // Float register +				InstImm( "stfs", PPC_STFS, opStackFloatRegisters[i], R_OPSTACK, i*4+4); +				break; +		} +		opStackRegType[i] = 0; +	} +} + +static void loadOpStack(int depth) +{ +	// Back off operand stack pointer and reload all operands. +//	InstImm( "addi", PPC_ADDI, R_OPSTACK, R_OPSTACK, -(depth)*4 ); + +	int i; +	 +	for(i = 0; i < depth; i++) +	{ +		assert(opStackRegType[i] == 0); +		// For now we're stuck reloading everything as an integer. +		opStackLoadInstructionAddr[i] = &buf[compiledOfs]; +		InstImm( "lwz", PPC_LWZ, opStackIntRegisters[i], R_OPSTACK, i*4+4); +		opStackRegType[i] = 1; +	}	 +} + +static void makeFloat(int depth) +{ +	//assert(opStackRegType[depth] == 1); +	if(opStackRegType[depth] == 1) +	{ +		unsigned instruction; +		unsigned destReg, aReg, bReg, imm; +		 +		if(opStackLoadInstructionAddr[depth]) +		{ +			// Repatch load instruction to use LFS instead of LWZ +			instruction = *opStackLoadInstructionAddr[depth]; +			// Figure out if it's LWZ or LWZX +			if((instruction & 0xfc000000) == PPC_LWZ) +			{ +				//printf("patching LWZ at %p to LFS at depth %ld\n",opStackLoadInstructionAddr[depth],depth); +				//printf("old instruction: %08lx\n",instruction); +				// Extract registers +				destReg = (instruction >> 21) & 31; +				aReg    = (instruction >> 16) & 31; +				imm     = instruction & 0xffff; +				 +				// Calculate correct FP register to use. +				// THIS ASSUMES REGISTER USAGE FOR THE STACK IS n, n+1, n+2, etc! +				//printf("old dest: %ld\n",destReg); +				destReg = (destReg - opStackIntRegisters[0]) + opStackFloatRegisters[0]; +				instruction = PPC_LFS | ( destReg << 21 ) | ( aReg << 16 ) | imm ;			 +				//printf("new dest: %ld\n",destReg); +				//printf("new instruction: %08lx\n",instruction); +			} +			else +			{ +				//printf("patching LWZX at %p to LFSX at depth %ld\n",opStackLoadInstructionAddr[depth],depth); +				//printf("old instruction: %08lx\n",instruction); +				// Extract registers +				destReg = (instruction >> 21) & 31; +				aReg    = (instruction >> 16) & 31; +				bReg    = (instruction >> 11) & 31; +				// Calculate correct FP register to use. +				// THIS ASSUMES REGISTER USAGE FOR THE STACK IS n, n+1, n+2, etc! +				//printf("old dest: %ld\n",destReg); +				destReg = (destReg - opStackIntRegisters[0]) + opStackFloatRegisters[0]; +				instruction = PPC_LFSX | ( destReg << 21 ) | ( aReg << 16 ) | ( bReg << 11 ) ;			 +				//printf("new dest: %ld\n",destReg); +				//printf("new instruction: %08lx\n",instruction); +			} +			*opStackLoadInstructionAddr[depth] = instruction; +			opStackLoadInstructionAddr[depth] = 0; +		} +		else +		{	 +			//printf("doing float constant load at %p for depth %ld\n",&buf[compiledOfs],depth); +			// It was likely loaded as a constant so we have to save/load it.  A more +			// interesting implementation might be to generate code to do a "PC relative" +			// load from the VM code region. +			InstImm( "stw", PPC_STW, opStackIntRegisters[depth], R_OPSTACK, depth*4+4); +			// For XXX make sure we force enough NOPs to get the load into +			// another dispatch group to avoid pipeline flush. +			Inst( "ori", PPC_ORI,  0, 0, 0 ); +			Inst( "ori", PPC_ORI,  0, 0, 0 ); +			Inst( "ori", PPC_ORI,  0, 0, 0 ); +			Inst( "ori", PPC_ORI,  0, 0, 0 ); +			InstImm( "lfs", PPC_LFS, opStackFloatRegisters[depth], R_OPSTACK, depth*4+4); +		} +		opStackRegType[depth] = 2; +	} +} + +// TJW: Unused +#if 0 +static void fltop() { +    if (rtopped == qfalse) { +	InstImm( PPC_LFS, R_TOP, R_OPSTACK, 0 );		// get value from opstack +    } +} +#endif + +#if 0 +static void fltopandsecond() { +	InstImm( PPC_LFS, R_TOP, R_OPSTACK, 0 );		// get value from opstack +	InstImm( PPC_LFS, R_SECOND, R_OPSTACK, -4 );	// get value from opstack +	InstImm( PPC_ADDI, R_OPSTACK, R_OPSTACK, -8 ); +    rtopped = qfalse; +	return; +} +#endif + +#define assertInteger(depth)	assert(opStackRegType[depth] == 1) + +/* +================= +VM_Compile +================= +*/ +void VM_Compile( vm_t *vm, vmHeader_t *header ) { +	int		op; +	int		maxLength; +	int		v; +	int		i; +        int		opStackDepth; +	 +	int		mainFunction; +	 +	// set up the into-to-float variables +   	((int *)itofConvert)[0] = 0x43300000; +   	((int *)itofConvert)[1] = 0x80000000; +   	((int *)itofConvert)[2] = 0x43300000; + +	// allocate a very large temp buffer, we will shrink it later +	maxLength = header->codeLength * 8; +	buf = Z_Malloc( maxLength ); +	jused = Z_Malloc(header->instructionCount + 2); +	Com_Memset(jused, 0, header->instructionCount+2); +	 +    // compile everything twice, so the second pass will have valid instruction +    // pointers for branches +    for ( pass = -1 ; pass < 2 ; pass++ ) { + +        // translate all instructions +        pc = 0; +	mainFunction = 0; +	opStackDepth = 0; +	 +	pop0 = 343545; +	pop1 = 2443545; +	oc0 = -2343535; +	oc1 = 24353454; +	tvm = vm; +        code = (byte *)header + header->codeOffset; +        compiledOfs = 0; +#ifndef __GNUC__ +		// metrowerks seems to require this header in front of functions +		Emit4( (int)(buf+2) ); +		Emit4( 0 ); +#endif + +	for ( instruction = 0 ; instruction < header->instructionCount ; instruction++ ) { +            if ( compiledOfs*4 > maxLength - 16 ) { +                Com_Error( ERR_DROP, "VM_Compile: maxLength exceeded" ); +            } + +            op = code[ pc ]; +            if ( !pass ) { +                vm->instructionPointers[ instruction ] = compiledOfs * 4; +            } +            pc++; +            switch ( op ) { +            case 0: +                break; +            case OP_BREAK: +		#if DEBUG_VM +		if(pass == 1) +			printf("%08lx BREAK\n",instruction); +		#endif +                InstImmU( "addi", PPC_ADDI, R_TOP, 0, 0 ); +                InstImm( "lwz", PPC_LWZ, R_TOP, R_TOP, 0 );			// *(int *)0 to crash to debugger +                break; +            case OP_ENTER: +		opStackDepth = 0; +		v = Constant4(); +		#if DEBUG_VM +		if(pass == 1) +			printf("%08x ENTER\t%04x\n",instruction,v); +		#endif +		opStackRegType[opStackDepth] = 0; +		mainFunction++; +		if(mainFunction == 1) +		{ +			// Main VM entry point is the first thing we compile, so save off operand stack +			// registers here.  This avoids issues with trying to trick the native compiler +			// into doing it, and properly matches the PowerPC ABI +			InstImm( "addi", PPC_ADDI, R_REAL_STACK, R_REAL_STACK, -OP_STACK_MAX_DEPTH*4 );	// sub R_STACK, R_STACK, imm +			for(i = 0; i < OP_STACK_MAX_DEPTH; i++) +				InstImm( "stw", PPC_STW, opStackIntRegisters[i], R_REAL_STACK, i*4); +		} +                InstImm( "addi", PPC_ADDI, R_STACK, R_STACK, -v );	// sub R_STACK, R_STACK, imm +                break; +            case OP_CONST: +                v = Constant4(); +		#if DEBUG_VM +		if(pass == 1) +			printf("%08x CONST\t%08x\n",instruction,v); +		#endif +		opStackLoadInstructionAddr[opStackDepth] = 0; +                if ( v < 32768 && v >= -32768 ) { +                    InstImmU( "addi", PPC_ADDI, opStackIntRegisters[opStackDepth], 0, v & 0xffff ); +                } else { +                    InstImmU( "addis", PPC_ADDIS, opStackIntRegisters[opStackDepth], 0, (v >> 16)&0xffff ); +                    if ( v & 0xffff ) { +                        InstImmU( "ori", PPC_ORI, opStackIntRegisters[opStackDepth], opStackIntRegisters[opStackDepth], v & 0xffff ); +                    } +                } +		opStackRegType[opStackDepth] = 1; +		opStackDepth += 1; +		if (code[pc] == OP_JUMP) { +		    jused[v] = 1; +		} +		break; +            case OP_LOCAL: +		oc1 = Constant4(); +		#if DEBUG_VM +		if(pass == 1) +			printf("%08x LOCAL\t%08x\n",instruction,oc1); +		#endif +		if (code[pc] == OP_LOAD4 || code[pc] == OP_LOAD2 || code[pc] == OP_LOAD1) { +		    oc1 &= vm->dataMask; +		} +                InstImm( "addi", PPC_ADDI, opStackIntRegisters[opStackDepth], R_STACK, oc1 ); +		opStackRegType[opStackDepth] = 1; +		opStackLoadInstructionAddr[opStackDepth] = 0; +		opStackDepth += 1; +                break; +            case OP_ARG: +		v = Constant1(); +		#if DEBUG_VM +		if(pass == 1) +			printf("%08x ARG \t%08x\n",instruction,v); +		#endif +                InstImm( "addi", PPC_ADDI, R_EA, R_STACK, v );	// location to put it +		if(opStackRegType[opStackDepth-1] == 1) +			Inst( "stwx", PPC_STWX, opStackIntRegisters[opStackDepth-1], R_EA, R_MEMBASE ); +		else +			Inst( "stfsx", PPC_STFSX, opStackFloatRegisters[opStackDepth-1], R_EA, R_MEMBASE ); +		opStackRegType[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackDepth -= 1; +		 +                break; +            case OP_CALL: +		#if DEBUG_VM +		if(pass == 1) +			printf("%08x CALL\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assert(opStackDepth > 0); +                Inst( "mflr", PPC_MFSPR, R_SECOND, 8, 0 );			// move from link register +                InstImm( "stwu", PPC_STWU, R_SECOND, R_REAL_STACK, -16 );	// save off the old return address + +		// Spill operand stack registers. +		spillOpStack(opStackDepth); +		 +		// We need to leave R_OPSTACK pointing to the top entry on the stack, which is the call address. +		// It will be consumed (and R4 decremented) by the AsmCall code. +		InstImm( "addi", PPC_ADDI, R_OPSTACK, R_OPSTACK, opStackDepth*4); + +                Inst( "mtctr", PPC_MTSPR, R_ASMCALL, 9, 0 );			// move to count register +                Inst( "bctrl", PPC_BCCTR | 1, 20, 0, 0 );			// jump and link to the count register + +		// R4 now points to the top of the operand stack, which has the return value in it.  We want to +		// back off the pointer to point to the base of our local operand stack and then reload the stack. +		 +		InstImm("addi", PPC_ADDI, R_OPSTACK, R_OPSTACK, -opStackDepth*4); +		 +		// Reload operand stack. +		loadOpStack(opStackDepth); + +                InstImm( "lwz", PPC_LWZ, R_SECOND, R_REAL_STACK, 0 );		// fetch the old return address +                InstImm( "addi", PPC_ADDI, R_REAL_STACK, R_REAL_STACK, 16 ); +                Inst( "mtlr", PPC_MTSPR, R_SECOND, 8, 0 );			// move to link register +                break; +            case OP_PUSH: +		#if DEBUG_VM +		if(pass == 1) +			printf("%08x PUSH\n",instruction); +		#endif +		opStackRegType[opStackDepth] = 1; 	// Garbage int value. +		opStackDepth += 1; +                break; +            case OP_POP: +		#if DEBUG_VM +		if(pass == 1) +			printf("%08x POP\n",instruction); +		#endif +		opStackDepth -= 1; +		opStackRegType[opStackDepth] = 0; 	// ?? +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +                break; +            case OP_LEAVE: +		#if DEBUG_VM +		if(pass == 1) +			printf("%08x LEAVE\n",instruction); +		#endif +		assert(opStackDepth == 1); +		assert(opStackRegType[0] != 0); +		// Save return value onto top of op stack.  We also have to increment R_OPSTACK +		switch(opStackRegType[0]) +		{ +			case 1:	// Integer register +				InstImm( "stw", PPC_STWU, opStackIntRegisters[0], R_OPSTACK, 4); +				break; +			case 2: // Float register +				InstImm( "stfs", PPC_STFSU, opStackFloatRegisters[0], R_OPSTACK, 4); +				break; +		} +                InstImm( "addi", PPC_ADDI, R_STACK, R_STACK, Constant4() );	// add R_STACK, R_STACK, imm +		if(mainFunction == 1) +		{ +			for(i = 0; i < OP_STACK_MAX_DEPTH; i++) +				InstImm( "lwz", PPC_LWZ,  opStackIntRegisters[i], R_REAL_STACK, i*4); +			InstImm( "addi", PPC_ADDI, R_REAL_STACK, R_REAL_STACK, OP_STACK_MAX_DEPTH*4 );	 +		} +		opStackDepth--; +		opStackRegType[opStackDepth] = 0; +		opStackLoadInstructionAddr[opStackDepth] = 0; +                Inst( "blr", PPC_BCLR, 20, 0, 0 );							// branch unconditionally to link register +                break; +            case OP_LOAD4: +		#if DEBUG_VM +		if(pass == 1) +			printf("%08x LOAD4\n",instruction); +		#endif +		// We should try to figure out whether to use LWZX or LFSX based +		// on some kind of code analysis after subsequent passes.  I think what +		// we could do is store the compiled load instruction address along with +		// the register type.  When we hit the first mismatched operator, we go back +		// and patch the load.  Since LCC's operand stack should be at 0 depth by the +		// time we hit a branch, this should work fairly well.  FIXME FIXME FIXME. +		assertInteger(opStackDepth-1); +		opStackLoadInstructionAddr[opStackDepth-1] = &buf[ compiledOfs ]; +                Inst( "lwzx", PPC_LWZX, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], R_MEMBASE );// load from memory base +		opStackRegType[opStackDepth-1] = 1; +                break; +            case OP_LOAD2: +		#if DEBUG_VM +		if(pass == 1) +			printf("%08x LOAD2\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +                Inst( "lhzx", PPC_LHZX, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], R_MEMBASE );// load from memory base +		opStackRegType[opStackDepth-1] = 1; +                break; +            case OP_LOAD1: +		#if DEBUG_VM +		if(pass == 1) +			printf("%08x LOAD1\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +                Inst( "lbzx", PPC_LBZX, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], R_MEMBASE );// load from memory base +		opStackRegType[opStackDepth-1] = 1; +                break; +            case OP_STORE4: +		#if DEBUG_VM +		if(pass == 1) +			printf("%08x STORE4\n",instruction); +		#endif +		assertInteger(opStackDepth-2); +		if(opStackRegType[opStackDepth-1] == 1) +			Inst( "stwx", PPC_STWX, opStackIntRegisters[opStackDepth-1],  +					opStackIntRegisters[opStackDepth-2], R_MEMBASE );		// store from memory base +		else +			Inst( "stfsx", PPC_STFSX, opStackFloatRegisters[opStackDepth-1],  +					opStackIntRegisters[opStackDepth-2], R_MEMBASE );		// store from memory base +		opStackRegType[opStackDepth-1] = 0; +		opStackRegType[opStackDepth-2] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-2] = 0; +		opStackDepth -= 2; +                break; +            case OP_STORE2: +		#if DEBUG_VM +		if(pass == 1) +			printf("%08x STORE2\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "sthx", PPC_STHX, opStackIntRegisters[opStackDepth-1], +				opStackIntRegisters[opStackDepth-2], R_MEMBASE );		// store from memory base +		opStackRegType[opStackDepth-1] = 0; +		opStackRegType[opStackDepth-2] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-2] = 0; +		opStackDepth -= 2; +                break; +            case OP_STORE1: +		#if DEBUG_VM +		if(pass == 1) +			printf("%08x STORE1\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "stbx", PPC_STBX, opStackIntRegisters[opStackDepth-1], +				opStackIntRegisters[opStackDepth-2], R_MEMBASE );		// store from memory base +		opStackRegType[opStackDepth-1] = 0; +		opStackRegType[opStackDepth-2] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-2] = 0; +		opStackDepth -= 2; +                break; + +            case OP_EQ: +		#if DEBUG_VM +		if(pass == 1) +			printf("%08x EQ\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "cmp", PPC_CMP, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackRegType[opStackDepth-2] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-2] = 0; +		opStackDepth -= 2; +                i = Constant4(); +				jused[i] = 1; +                InstImm( "bc", PPC_BC, 4, 2, 8 ); +                if ( pass==1 ) { +                    v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs];                     +                } else { +                    v = 0;              +                } +                Emit4("b", PPC_B | (v&0x3ffffff) ); +                break; +            case OP_NE: +		#if DEBUG_VM +		if(pass == 1) +			printf("%08x NE\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "cmp", PPC_CMP, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackRegType[opStackDepth-2] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-2] = 0; +		opStackDepth -= 2; +                i = Constant4(); +				jused[i] = 1; +                InstImm( "bc", PPC_BC, 12, 2, 8 ); +                if ( pass==1 ) { +                    v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs];                     +                } else { +                    v = 0; +                } +                Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) ); +//                InstImm( "bc", PPC_BC, 4, 2, v ); + +                break; +            case OP_LTI: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x LTI\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "cmp", PPC_CMP, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackRegType[opStackDepth-2] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-2] = 0; +		opStackDepth -= 2; +                i = Constant4(); +				jused[i] = 1; +                InstImm( "bc", PPC_BC, 4, 0, 8 ); +                if ( pass==1 ) { +                    v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; +                } else { +                    v = 0; +                } +                Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) ); +//                InstImm( "bc", PPC_BC, 12, 0, v ); +                break; +            case OP_LEI: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x LEI\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "cmp", PPC_CMP, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackRegType[opStackDepth-2] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-2] = 0; +		opStackDepth -= 2; +                i = Constant4(); +				jused[i] = 1; +                InstImm( "bc", PPC_BC, 12, 1, 8 ); +                if ( pass==1 ) { +                    v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; +                } else { +                    v = 0; +                } +                Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) ); +//                InstImm( "bc", PPC_BC, 4, 1, v ); +                break; +            case OP_GTI: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x GTI\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "cmp", PPC_CMP, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackRegType[opStackDepth-2] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-2] = 0; +		opStackDepth -= 2; +                i = Constant4(); +				jused[i] = 1; +                InstImm( "bc", PPC_BC, 4, 1, 8 ); +                if ( pass==1 ) { +                    v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; +                } else { +                    v = 0; +                } +                Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) ); +//                InstImm( "bc", PPC_BC, 12, 1, v ); +                break; +            case OP_GEI: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x GEI\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "cmp", PPC_CMP, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackRegType[opStackDepth-2] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-2] = 0; +		opStackDepth -= 2; +                i = Constant4(); +				jused[i] = 1; +                InstImm( "bc", PPC_BC, 12, 0, 8 ); +                if ( pass==1 ) { +                    v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; +                } else { +                    v = 0; +                } +                Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) ); +//                InstImm( "bc", PPC_BC, 4, 0, v ); +                break; +            case OP_LTU: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x LTU\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "cmpl", PPC_CMPL, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackRegType[opStackDepth-2] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-2] = 0; +		opStackDepth -= 2; +                i = Constant4(); +		jused[i] = 1; +                InstImm( "bc", PPC_BC, 4, 0, 8 ); +                if ( pass==1 ) { +                    v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; +                } else { +                    v = 0; +                } +                Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) ); +//                InstImm( "bc", PPC_BC, 12, 0, v ); +                break; +            case OP_LEU: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x LEU\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "cmpl", PPC_CMPL, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackRegType[opStackDepth-2] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-2] = 0; +		opStackDepth -= 2; +                i = Constant4(); +		jused[i] = 1; +                InstImm( "bc", PPC_BC, 12, 1, 8 ); +                if ( pass==1 ) { +                    v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; +                } else { +                    v = 0; +                } +                Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) ); +//                InstImm( "bc", PPC_BC, 4, 1, v ); +                break; +            case OP_GTU: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x GTU\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "cmpl", PPC_CMPL, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackRegType[opStackDepth-2] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-2] = 0; +		opStackDepth -= 2; +                i = Constant4(); +		jused[i] = 1; +                InstImm( "bc", PPC_BC, 4, 1, 8 ); +                if ( pass==1 ) { +                    v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; +                } else { +                    v = 0; +                } +                Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) ); +//                InstImm( "bc", PPC_BC, 12, 1, v ); +                break; +            case OP_GEU: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x GEU\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "cmpl", PPC_CMPL, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackRegType[opStackDepth-2] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-2] = 0; +		opStackDepth -= 2; +                i = Constant4(); +		jused[i] = 1; +                InstImm( "bc", PPC_BC, 12, 0, 8 ); +                if ( pass==1 ) { +                    v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; +                } else { +                    v = 0; +                } +                Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) ); +//                InstImm( "bc", PPC_BC, 4, 0, v ); +                break; +                 +            case OP_EQF: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x EQF\n",instruction); +		#endif +		makeFloat(opStackDepth-1); +		makeFloat(opStackDepth-2); +                Inst( "fcmpu", PPC_FCMPU, 0, opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackRegType[opStackDepth-2] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-2] = 0; +		opStackDepth -= 2; +                i = Constant4(); +		jused[i] = 1; +                InstImm( "bc", PPC_BC, 4, 2, 8 ); +                if ( pass==1 ) { +                    v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; +                } else { +                    v = 0; +                } +                Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) ); +//                InstImm( "bc", PPC_BC, 12, 2, v ); +                break;			 +            case OP_NEF: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x NEF\n",instruction); +		#endif +		makeFloat(opStackDepth-1); +		makeFloat(opStackDepth-2); +                Inst( "fcmpu", PPC_FCMPU, 0, opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackRegType[opStackDepth-2] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-2] = 0; +		opStackDepth -= 2; +                i = Constant4(); +		jused[i] = 1; +                InstImm( "bc", PPC_BC, 12, 2, 8 ); +                if ( pass==1 ) { +                    v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; +                } else { +                    v = 0; +                } +                Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) ); +//                InstImm( "bc", PPC_BC, 4, 2, v ); +                break;			 +            case OP_LTF: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x LTF\n",instruction); +		#endif +		makeFloat(opStackDepth-1); +		makeFloat(opStackDepth-2); +                Inst( "fcmpu", PPC_FCMPU, 0, opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackRegType[opStackDepth-2] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-2] = 0; +		opStackDepth -= 2; +                i = Constant4(); +		jused[i] = 1; +                InstImm( "bc", PPC_BC, 4, 0, 8 ); +                if ( pass==1 ) { +                    v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; +                } else { +                    v = 0; +                } +                Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) ); +//                InstImm( "bc", PPC_BC, 12, 0, v ); +                break;			 +            case OP_LEF: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x LEF\n",instruction); +		#endif +		makeFloat(opStackDepth-1); +		makeFloat(opStackDepth-2); +                Inst( "fcmpu", PPC_FCMPU, 0, opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackRegType[opStackDepth-2] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-2] = 0; +		opStackDepth -= 2; +                i = Constant4(); +		jused[i] = 1; +                InstImm( "bc", PPC_BC, 12, 1, 8 ); +                if ( pass==1 ) { +                    v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; +                } else { +                    v = 0; +                } +                Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) ); +//                InstImm( "bc", PPC_BC, 4, 1, v ); +                break;			 +            case OP_GTF: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x GTF\n",instruction); +		#endif +		makeFloat(opStackDepth-1); +		makeFloat(opStackDepth-2); +                Inst( "fcmpu", PPC_FCMPU, 0, opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackRegType[opStackDepth-2] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-2] = 0; +		opStackDepth -= 2; +                i = Constant4(); +		jused[i] = 1; +                InstImm( "bc", PPC_BC, 4, 1, 8 ); +                if ( pass==1 ) { +                    v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; +                } else { +                    v = 0; +                } +                Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) ); +//                InstImm( "bc", PPC_BC, 12, 1, v ); +                break;			 +            case OP_GEF: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x GEF\n",instruction); +		#endif +		makeFloat(opStackDepth-1); +		makeFloat(opStackDepth-2); +                Inst( "fcmpu", PPC_FCMPU, 0, opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackRegType[opStackDepth-2] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-2] = 0; +		opStackDepth -= 2; +                i = Constant4(); +		jused[i] = 1; +                InstImm( "bc", PPC_BC, 12, 0, 8 ); +                if ( pass==1 ) { +                    v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; +                } else { +                    v = 0; +                } +                Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) ); +//                InstImm( "bc", PPC_BC, 4, 0, v ); +                break; + +            case OP_NEGI: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x NEGI\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +                InstImm( "subfic", PPC_SUBFIC, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], 0 ); +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +                break; +            case OP_ADD: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x ADD\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "add", PPC_ADD, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-2] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackDepth -= 1; +                break; +            case OP_SUB: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x SUB\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "subf", PPC_SUBF, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-2] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackDepth -= 1; +                break; +            case OP_DIVI: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x DIVI\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "divw", PPC_DIVW, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackDepth -= 1; +                break; +            case OP_DIVU: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x DIVU\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "divwu", PPC_DIVWU, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackDepth -= 1; +                break; +            case OP_MODI: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x MODI\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "divw", PPC_DIVW, R_EA, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] ); +                Inst( "mullw", PPC_MULLW, R_EA, opStackIntRegisters[opStackDepth-1], R_EA ); +                Inst( "subf", PPC_SUBF, opStackIntRegisters[opStackDepth-2], R_EA, opStackIntRegisters[opStackDepth-2] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackDepth -= 1; +                break; +            case OP_MODU: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x MODU\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "divwu", PPC_DIVWU, R_EA, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] ); +                Inst( "mullw", PPC_MULLW, R_EA, opStackIntRegisters[opStackDepth-1], R_EA ); +                Inst( "subf", PPC_SUBF, opStackIntRegisters[opStackDepth-2], R_EA, opStackIntRegisters[opStackDepth-2] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackDepth -= 1; +                break; +            case OP_MULI: +            case OP_MULU: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x MULI\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "mullw", PPC_MULLW, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-2] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackDepth -= 1; +                break; +            case OP_BAND: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x BAND\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "and", PPC_AND, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackDepth -= 1; +                break; +            case OP_BOR: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x BOR\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "or", PPC_OR, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackDepth -= 1; +                break; +            case OP_BXOR: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x BXOR\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "xor", PPC_XOR, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackDepth -= 1; +                break; +            case OP_BCOM: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x BCOM\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +                Inst( "nor", PPC_NOR, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1] ); +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +                break; +            case OP_LSH: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x LSH\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "slw", PPC_SLW, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackDepth -= 1; +                break; +            case OP_RSHI: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x RSHI\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "sraw", PPC_SRAW, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackDepth -= 1; +                break; +            case OP_RSHU: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x RSHU\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                Inst( "srw", PPC_SRW, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackDepth -= 1; +                break; + +            case OP_NEGF: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x NEGF\n",instruction); +		#endif +		makeFloat(opStackDepth-1); +                Inst( "fneg", PPC_FNEG, opStackFloatRegisters[opStackDepth-1], 0, opStackFloatRegisters[opStackDepth-1] ); +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +                break; +            case OP_ADDF: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x ADDF\n",instruction); +		#endif +		makeFloat(opStackDepth-1); +		makeFloat(opStackDepth-2); +                Inst( "fadds", PPC_FADDS, opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackDepth -= 1; +                break; +            case OP_SUBF: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x SUBF\n",instruction); +		#endif +		makeFloat(opStackDepth-1); +		makeFloat(opStackDepth-2); +                Inst( "fsubs", PPC_FSUBS, opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackDepth -= 1; +                break; +            case OP_DIVF: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x DIVF\n",instruction); +		#endif +		makeFloat(opStackDepth-1); +		makeFloat(opStackDepth-2); +                Inst( "fdivs", PPC_FDIVS, opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackDepth -= 1; +                break; +            case OP_MULF: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x MULF\n",instruction); +		#endif +		makeFloat(opStackDepth-1); +		makeFloat(opStackDepth-2); +                Inst4( "fmuls", PPC_FMULS, opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-2], 0, opStackFloatRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackDepth -= 1; +                break; + +            case OP_CVIF: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x CVIF\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +		//makeInteger(opStackDepth-1); +                v = (int)&itofConvert; +                InstImmU( "addis", PPC_ADDIS, R_EA, 0, (v >> 16)&0xffff ); +                InstImmU( "ori", PPC_ORI, R_EA, R_EA, v & 0xffff ); +                InstImmU( "xoris", PPC_XORIS, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], 0x8000 ); +                InstImm( "stw", PPC_STW, opStackIntRegisters[opStackDepth-1], R_EA, 12 ); +                InstImm( "lfd", PPC_LFD, opStackFloatRegisters[opStackDepth-1], R_EA, 0 ); +		Inst( "ori", PPC_ORI, 0, 0, 0); +		Inst( "ori", PPC_ORI, 0, 0, 0); +		Inst( "ori", PPC_ORI, 0, 0, 0); +                InstImm( "lfd", PPC_LFD, 13, R_EA, 8 ); +                Inst( "fsub", PPC_FSUB, opStackFloatRegisters[opStackDepth-1], 13, opStackFloatRegisters[opStackDepth-1] ); +		opStackRegType[opStackDepth-1] = 2; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +    //            Inst( PPC_FRSP, R_TOP, 0, R_TOP ); +                break; +            case OP_CVFI: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x CVFI\n",instruction); +		#endif +		makeFloat(opStackDepth-1); + +		InstImm( "addi", PPC_ADDI, R_OPSTACK, R_OPSTACK, opStackDepth*4); + +                Inst( "fctiwz", PPC_FCTIWZ, opStackFloatRegisters[opStackDepth-1], 0, opStackFloatRegisters[opStackDepth-1] ); +                Inst( "stfiwx", PPC_STFIWX, opStackFloatRegisters[opStackDepth-1], 0, R_OPSTACK );		// save value to opstack (dummy area now) +		Inst( "ori", PPC_ORI, 0, 0, 0); +		Inst( "ori", PPC_ORI, 0, 0, 0); +		Inst( "ori", PPC_ORI, 0, 0, 0); +		Inst( "ori", PPC_ORI, 0, 0, 0); +                InstImm( "lwz", PPC_LWZ, opStackIntRegisters[opStackDepth-1], R_OPSTACK, 0 ); +		 +		InstImm( "addi", PPC_ADDI, R_OPSTACK, R_OPSTACK, -opStackDepth*4); +		 +		opStackRegType[opStackDepth-1] = 1; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +                break; +            case OP_SEX8: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x SEX8\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +                Inst( "extsb", PPC_EXTSB, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], 0 ); +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +                break; +            case OP_SEX16: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x SEX16\n",instruction); +		#endif +		assertInteger(opStackDepth-1); +                Inst( "extsh", PPC_EXTSH, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], 0 ); +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +                break; + +            case OP_BLOCK_COPY: +                v = Constant4() >> 2; +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x BLOCK_COPY\t%08lx\n",instruction,v<<2); +		#endif +		assert(opStackDepth >= 2); +		assertInteger(opStackDepth-1); +		assertInteger(opStackDepth-2); +                InstImmU( "addi", PPC_ADDI, R_EA, 0, v );				// count +				// FIXME: range check +              	Inst( "mtctr", PPC_MTSPR, R_EA, 9, 0 );					// move to count register + +                Inst( "add", PPC_ADD, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], R_MEMBASE ); +                InstImm( "addi", PPC_ADDI, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], -4 ); +                Inst( "add", PPC_ADD, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-2], R_MEMBASE ); +                InstImm( "addi", PPC_ADDI, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-2], -4 ); + +                InstImm( "lwzu", PPC_LWZU, R_EA, opStackIntRegisters[opStackDepth-1], 4 );		// source +                InstImm( "stwu", PPC_STWU, R_EA, opStackIntRegisters[opStackDepth-2], 4 );	// dest +                Inst( "b", PPC_BC | 0xfff8 , 16, 0, 0 );					// loop +		opStackRegType[opStackDepth-1] = 0; +		opStackRegType[opStackDepth-2] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-2] = 0; +		opStackDepth -= 2; +                break; + +            case OP_JUMP: +		#if DEBUG_VM +		if(pass == 1) +		printf("%08x JUMP\n",instruction); +		#endif +		assert(opStackDepth == 1); +		assertInteger(opStackDepth-1); + +                Inst( "rlwinm", PPC_RLWINM | ( 29 << 1 ), opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], 2 ); +		// FIXME: range check +		Inst( "lwzx", PPC_LWZX, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], R_INSTRUCTIONS ); +                Inst( "mtctr", PPC_MTSPR, opStackIntRegisters[opStackDepth-1], 9, 0 );		// move to count register +                Inst( "bctr", PPC_BCCTR, 20, 0, 0 );		// jump to the count register +		opStackRegType[opStackDepth-1] = 0; +		opStackLoadInstructionAddr[opStackDepth-1] = 0; +		opStackDepth -= 1; +                break; +            default: +                Com_Error( ERR_DROP, "VM_CompilePPC: bad opcode %i at instruction %i, offset %i", op, instruction, pc ); +            } +	    pop0 = pop1; +	    pop1 = op; +		assert(opStackDepth >= 0); +		assert(opStackDepth < OP_STACK_MAX_DEPTH); +		 +		//printf("%4d\t%s\n",opStackDepth,opnames[op]); +        } + +	Com_Printf( "VM file %s pass %d compiled to %i bytes of code\n", vm->name, (pass+1), compiledOfs*4 ); + +    	if ( pass == 0 ) { +	    // copy to an exact size buffer on the hunk +	    vm->codeLength = compiledOfs * 4; +	    vm->codeBase = Hunk_Alloc( vm->codeLength, h_low ); +	    Com_Memcpy( vm->codeBase, buf, vm->codeLength ); +	     +	    //printf("codeBase: %p\n",vm->codeBase); +	     +	    Z_Free( buf ); +	 +	    // offset all the instruction pointers for the new location +	    for ( i = 0 ; i < header->instructionCount ; i++ ) { +		vm->instructionPointers[i] += (int)vm->codeBase; +		//printf("%08x %08lx\n",i,vm->instructionPointers[i]); +	    } + +	    // go back over it in place now to fixup reletive jump targets +	    buf = (unsigned *)vm->codeBase; +	} else if ( pass == 1 ) { +           // clear the instruction cache for generated code +           msync(vm->codeBase, vm->codeLength, MS_INVALIDATE); +       } +    } +    if(0) +    { +	char buf[256]; +	printf("wait..\n"); +	gets(buf); +    } +    Z_Free( jused ); +} + +/* +============== +VM_CallCompiled + +This function is called directly by the generated code +============== +*/ +int	VM_CallCompiled( vm_t *vm, int *args ) { +	int		stack[1024]; +	int		programStack; +	int		stackOnEntry; +	byte	*image; + +	currentVM = vm; + +	//printf("VM_CallCompiled: %p   %08lx %08lx %08lx\n", +	//	vm, args[0],args[1],args[2]); +		 +	// interpret the code +	vm->currentlyInterpreting = qtrue; + +	// we might be called recursively, so this might not be the very top +	programStack = vm->programStack; +	stackOnEntry = programStack; +	image = vm->dataBase; +	 +	// set up the stack frame  +	programStack -= 48; + +	*(int *)&image[ programStack + 44] = args[9]; +	*(int *)&image[ programStack + 40] = args[8]; +	*(int *)&image[ programStack + 36] = args[7]; +	*(int *)&image[ programStack + 32] = args[6]; +	*(int *)&image[ programStack + 28] = args[5]; +	*(int *)&image[ programStack + 24] = args[4]; +	*(int *)&image[ programStack + 20] = args[3]; +	*(int *)&image[ programStack + 16] = args[2]; +	*(int *)&image[ programStack + 12] = args[1]; +	*(int *)&image[ programStack + 8 ] = args[0]; +	*(int *)&image[ programStack + 4 ] = 0;	// return stack +	*(int *)&image[ programStack ] = -1;	// will terminate the loop on return + +	// Cheesy... manually save registers used by VM call... +	// off we go into generated code... +	// the PPC calling standard says the parms will all go into R3 - R11, so  +	// no special asm code is needed here +#ifdef __GNUC__ +	((void(*)(int, int, int, int, int, int, int, int))(vm->codeBase))(  +		programStack, (int)&stack,  +		(int)image, vm->dataMask, (int)&AsmCall,  +		(int)vm->instructionPointers, vm->instructionPointersLength, +        (int)vm ); +#else +	((void(*)(int, int, int, int, int, int, int, int))(vm->codeBase))(  +		programStack, (int)&stack,  +		(int)image, vm->dataMask, *(int *)&AsmCall /* skip function pointer header */,  +		(int)vm->instructionPointers, vm->instructionPointersLength, +        (int)vm ); +#endif +	vm->programStack = stackOnEntry; + +    vm->currentlyInterpreting = qfalse; + +	return stack[1]; +} + + +/* +================== +AsmCall + +Put this at end of file because gcc messes up debug line numbers  +================== +*/ +#ifdef __GNUC__ + +void AsmCall( void ) { +asm ( +     // pop off the destination instruction +"    lwz		r12,0(r4)	\n"	// RG_TOP, 0(RG_OPSTACK) +"    addi	r4,r4,-4		\n"	// RG_OPSTACK, RG_OPSTACK, -4 \n" + +    // see if it is a system trap +"    cmpwi	r12,0			\n"	// RG_TOP, 0 \n" +"    bc		12,0, systemTrap	\n" + +    // calling another VM function, so lookup in instructionPointers +"    slwi	r12,r12,2		\n"	// RG_TOP,RG_TOP,2 +                        // FIXME: range check +"    lwzx	r12, r8, r12		\n"	// RG_TOP, RG_INSTRUCTIONS(RG_TOP)	 +"    mtctr	r12			\n"	// RG_TOP +); + +#if defined(MACOS_X) && defined(__OPTIMIZE__) +    // On Mac OS X, gcc doesn't push a frame when we are optimized, so trying to tear it down results in grave disorder. +//#warning Mac OS X optimization on, not popping GCC AsmCall frame +#else +    // Mac OS X Server and unoptimized compiles include a GCC AsmCall frame +    asm ( +"	lwz		r1,0(r1)	\n"	// pop off the GCC AsmCall frame +"	lmw		r30,-8(r1)	\n" +); +#endif + +asm ( +"	    bcctr	20,0		\n" // when it hits a leave, it will branch to the current link register + +    // calling a system trap +"systemTrap:				\n" +	// convert to positive system call number +"	subfic	r12,r12,-1		\n" + +    // save all our registers, including the current link register +"    mflr	r13			\n"	// RG_SECOND		// copy off our link register +"	addi	r1,r1,-92		\n"	// required 24 byets of linkage, 32 bytes of parameter, plus our saves +"    stw		r3,56(r1)	\n"	// RG_STACK, -36(REAL_STACK) +"    stw		r4,60(r1)	\n"	// RG_OPSTACK, 4(RG_REAL_STACK) +"    stw		r5,64(r1)	\n"	// RG_MEMBASE, 8(RG_REAL_STACK) +"    stw		r6,68(r1)	\n"	// RG_MEMMASK, 12(RG_REAL_STACK) +"    stw		r7,72(r1)	\n"	// RG_ASMCALL, 16(RG_REAL_STACK) +"    stw		r8,76(r1)	\n"	// RG_INSTRUCTIONS, 20(RG_REAL_STACK) +"    stw		r9,80(r1)	\n"	// RG_NUM_INSTRUCTIONS, 24(RG_REAL_STACK) +"    stw		r10,84(r1)	\n"	// RG_VM, 28(RG_REAL_STACK) +"    stw		r13,88(r1)	\n"	// RG_SECOND, 32(RG_REAL_STACK)	// link register + +    // save the vm stack position to allow recursive VM entry +"    addi	r13,r3,-4		\n"	// RG_TOP, RG_STACK, -4 +"    stw		r13,0(r10)	\n"	//RG_TOP, VM_OFFSET_PROGRAM_STACK(RG_VM) + +    // save the system call number as the 0th parameter +"    add		r3,r3,r5	\n"	// r3,  RG_STACK, RG_MEMBASE		// r3 is the first parameter to vm->systemCalls +"    stwu	r12,4(r3)		\n"	// RG_TOP, 4(r3) + +    // make the system call with the address of all the VM parms as a parameter +    // vm->systemCalls( &parms ) +"    lwz		r12,4(r10)	\n"	// RG_TOP, VM_OFFSET_SYSTEM_CALL(RG_VM) +"    mtctr	r12			\n"	// RG_TOP +"    bcctrl	20,0			\n" +"    mr		r12,r3			\n"	// RG_TOP, r3 + +    // pop our saved registers +"   	lwz		r3,56(r1)	\n"	// RG_STACK, 0(RG_REAL_STACK) +"   	lwz		r4,60(r1)	\n"	// RG_OPSTACK, 4(RG_REAL_STACK) +"   	lwz		r5,64(r1)	\n"	// RG_MEMBASE, 8(RG_REAL_STACK) +"   	lwz		r6,68(r1)	\n"	// RG_MEMMASK, 12(RG_REAL_STACK) +"   	lwz		r7,72(r1)	\n"	// RG_ASMCALL, 16(RG_REAL_STACK) +"   	lwz		r8,76(r1)	\n"	// RG_INSTRUCTIONS, 20(RG_REAL_STACK) +"   	lwz		r9,80(r1)	\n"	// RG_NUM_INSTRUCTIONS, 24(RG_REAL_STACK) +"   	lwz		r10,84(r1)	\n"	// RG_VM, 28(RG_REAL_STACK) +"   	lwz		r13,88(r1)	\n"	// RG_SECOND, 32(RG_REAL_STACK) +"    addi	r1,r1,92		\n"	// RG_REAL_STACK, RG_REAL_STACK, 36 + +    // restore the old link register +"    mtlr	r13			\n"	// RG_SECOND + +    // save off the return value +"    stwu	r12,4(r4)		\n"	// RG_TOP, 0(RG_OPSTACK) + +	// GCC adds its own prolog / epliog code + ); +} +#else + +// codewarrior version + +void asm AsmCall( void ) { + +    // pop off the destination instruction + +    lwz		r12,0(r4)	// RG_TOP, 0(RG_OPSTACK) + +    addi	r4,r4,-4	// RG_OPSTACK, RG_OPSTACK, -4 + + + +    // see if it is a system trap + +    cmpwi	r12,0		// RG_TOP, 0 + +    bc		12,0, systemTrap + + + +    // calling another VM function, so lookup in instructionPointers + +    slwi	r12,r12,2		// RG_TOP,RG_TOP,2 + +                        // FIXME: range check + +    lwzx	r12, r8, r12	// RG_TOP, RG_INSTRUCTIONS(RG_TOP)	 + +    mtctr	r12			// RG_TOP + + + +    bcctr	20,0		// when it hits a leave, it will branch to the current link register + + + +    // calling a system trap + +systemTrap: + +	// convert to positive system call number + +	subfic	r12,r12,-1 + + + +    // save all our registers, including the current link register + +    mflr	r13			// RG_SECOND		// copy off our link register + +	addi	r1,r1,-92	// required 24 byets of linkage, 32 bytes of parameter, plus our saves + +    stw		r3,56(r1)	// RG_STACK, -36(REAL_STACK) + +    stw		r4,60(r1)	// RG_OPSTACK, 4(RG_REAL_STACK) + +    stw		r5,64(r1)	// RG_MEMBASE, 8(RG_REAL_STACK) + +    stw		r6,68(r1)	// RG_MEMMASK, 12(RG_REAL_STACK) + +    stw		r7,72(r1)	// RG_ASMCALL, 16(RG_REAL_STACK) + +    stw		r8,76(r1)	// RG_INSTRUCTIONS, 20(RG_REAL_STACK) + +    stw		r9,80(r1)	// RG_NUM_INSTRUCTIONS, 24(RG_REAL_STACK) + +    stw		r10,84(r1)	// RG_VM, 28(RG_REAL_STACK) + +    stw		r13,88(r1)	// RG_SECOND, 32(RG_REAL_STACK)	// link register + + + +    // save the vm stack position to allow recursive VM entry + +    addi	r13,r3,-4	// RG_TOP, RG_STACK, -4 + +    stw		r13,0(r10)	//RG_TOP, VM_OFFSET_PROGRAM_STACK(RG_VM) + + + +    // save the system call number as the 0th parameter + +    add		r3,r3,r5	// r3,  RG_STACK, RG_MEMBASE		// r3 is the first parameter to vm->systemCalls + +    stwu	r12,4(r3)	// RG_TOP, 4(r3) + + + +    // make the system call with the address of all the VM parms as a parameter + +    // vm->systemCalls( &parms ) + +    lwz		r12,4(r10)	// RG_TOP, VM_OFFSET_SYSTEM_CALL(RG_VM) + +     + +    // perform macos cross fragment fixup crap + +    lwz		r9,0(r12) + +    stw		r2,52(r1)	// save old TOC + +	lwz		r2,4(r12) + +	     + +    mtctr	r9			// RG_TOP + +    bcctrl	20,0 + +     + +    lwz		r2,52(r1)	// restore TOC + +     + +    mr		r12,r3		// RG_TOP, r3 + + + +    // pop our saved registers + +   	lwz		r3,56(r1)	// RG_STACK, 0(RG_REAL_STACK) + +   	lwz		r4,60(r1)	// RG_OPSTACK, 4(RG_REAL_STACK) + +   	lwz		r5,64(r1)	// RG_MEMBASE, 8(RG_REAL_STACK) + +   	lwz		r6,68(r1)	// RG_MEMMASK, 12(RG_REAL_STACK) + +   	lwz		r7,72(r1)	// RG_ASMCALL, 16(RG_REAL_STACK) + +   	lwz		r8,76(r1)	// RG_INSTRUCTIONS, 20(RG_REAL_STACK) + +   	lwz		r9,80(r1)	// RG_NUM_INSTRUCTIONS, 24(RG_REAL_STACK) + +   	lwz		r10,84(r1)	// RG_VM, 28(RG_REAL_STACK) + +   	lwz		r13,88(r1)	// RG_SECOND, 32(RG_REAL_STACK) + +    addi	r1,r1,92	// RG_REAL_STACK, RG_REAL_STACK, 36 + + + +    // restore the old link register + +    mtlr	r13			// RG_SECOND + + + +    // save off the return value + +    stwu	r12,4(r4)	// RG_TOP, 0(RG_OPSTACK) + + + +	blr + +} + + + + +#endif diff --git a/src/qcommon/vm_x86.c b/src/qcommon/vm_x86.c new file mode 100644 index 0000000..c0a703b --- /dev/null +++ b/src/qcommon/vm_x86.c @@ -0,0 +1,1240 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ +// vm_x86.c -- load time compiler and execution environment for x86 + +#include "vm_local.h" +#ifdef _WIN32 +#include <windows.h> +#endif + +#ifdef __FreeBSD__ // rb0101023 +#include <sys/types.h> +#endif + +#ifndef _WIN32 +#include <sys/mman.h> // for PROT_ stuff +#endif + +/* need this on NX enabled systems (i386 with PAE kernel or + * noexec32=on x86_64) */ +#ifdef __linux__ +#define VM_X86_MMAP +#endif + +static void VM_Destroy_Compiled(vm_t* self); + +/* + +  eax	scratch +  ebx	scratch +  ecx	scratch (required for shifts) +  edx	scratch (required for divisions) +  esi	program stack +  edi	opstack + +*/ + +// TTimo: initialised the statics, this fixes a crash when entering a compiled VM  +static	byte	*buf = NULL; +static	byte	*jused = NULL; +static	int		compiledOfs = 0; +static	byte	*code = NULL; +static	int		pc = 0; + +static	int		*instructionPointers = NULL; + +#define FTOL_PTR + +#ifdef _MSC_VER + +#if defined( FTOL_PTR ) +int _ftol( float ); +static	int		ftolPtr = (int)_ftol; +#endif + +void AsmCall( void ); +static	int		asmCallPtr = (int)AsmCall; + +#else // _MSC_VER + +#if defined( FTOL_PTR ) +// bk001213 - BEWARE: does not work! UI menu etc. broken - stack! +// bk001119 - added: int gftol( float x ) { return (int)x; } + +int qftol( void );     // bk001213 - label, see unix/ftol.nasm +int qftol027F( void ); // bk001215 - fixed FPU control variants +int qftol037F( void ); +int qftol0E7F( void ); // bk010102 - fixed bogus bits (duh) +int qftol0F7F( void ); + + +static	int		ftolPtr = (int)qftol0F7F; +#endif // FTOL_PTR + +void doAsmCall( void ); +static	int		asmCallPtr = (int)doAsmCall; +#endif + + +static	int		callMask = 0; // bk001213 - init + +static	int	instruction, pass; +static	int	lastConst = 0; +static	int	oc0, oc1, pop0, pop1; + +typedef enum  +{ +	LAST_COMMAND_NONE	= 0, +	LAST_COMMAND_MOV_EDI_EAX, +	LAST_COMMAND_SUB_DI_4, +	LAST_COMMAND_SUB_DI_8, +} ELastCommand; + +static	ELastCommand	LastCommand; + +/* +================= +AsmCall +================= +*/ +#ifdef _MSC_VER +__declspec( naked ) void AsmCall( void ) { +int		programStack; +int		*opStack; +int		syscallNum; +vm_t*	savedVM; + +__asm { +	mov		eax, dword ptr [edi] +	sub		edi, 4 +	or		eax,eax +	jl		systemCall +	// calling another vm function +	shl		eax,2 +	add		eax, dword ptr [instructionPointers] +	call	dword ptr [eax] +	mov		eax, dword ptr [edi] +	and		eax, [callMask] +	ret +systemCall: + +	// convert negative num to system call number +	// and store right before the first arg +	neg		eax +	dec		eax + +	push    ebp +	mov     ebp, esp +	sub     esp, __LOCAL_SIZE + +	mov		dword ptr syscallNum, eax	// so C code can get at it +	mov		dword ptr programStack, esi	// so C code can get at it +	mov		dword ptr opStack, edi + +	push	ecx +	push	esi							// we may call recursively, so the +	push	edi							// statics aren't guaranteed to be around +} + +	savedVM = currentVM; + +	// save the stack to allow recursive VM entry +	currentVM->programStack = programStack - 4; +	*(int *)((byte *)currentVM->dataBase + programStack + 4) = syscallNum; +//VM_LogSyscalls(  (int *)((byte *)currentVM->dataBase + programStack + 4) ); +	*(opStack+1) = currentVM->systemCall( (int *)((byte *)currentVM->dataBase + programStack + 4) ); + +	currentVM = savedVM; + +_asm { +	pop		edi +	pop		esi +	pop		ecx +	add		edi, 4		// we added the return value + +	mov     esp, ebp +	pop     ebp + +	ret +} + +} + +#else //!_MSC_VER + +#if defined(__MINGW32__) || defined(MACOS_X) // _ is prepended to compiled symbols +#define CMANG(sym) "_"#sym +#else +#define CMANG(sym) #sym +#endif + +static	int		callProgramStack; +static	int		*callOpStack; +static	int		callSyscallNum; + +void callAsmCall(void) +{ +	vm_t	*savedVM; +	int		*callOpStack2; + +	savedVM = currentVM; +	callOpStack2 = callOpStack; + +	// save the stack to allow recursive VM entry +	currentVM->programStack = callProgramStack - 4; +	*(int *)((byte *)currentVM->dataBase + callProgramStack + 4) = callSyscallNum; +	//VM_LogSyscalls((int *)((byte *)currentVM->dataBase + callProgramStack + 4) ); +	*(callOpStack2+1) = currentVM->systemCall( (intptr_t *)((byte *)currentVM->dataBase + callProgramStack + 4) ); + + 	currentVM = savedVM; +} + +// Note the C space function AsmCall is never actually called, and is in fact +// arbitrarily named (though this is not true for the MSC version).  When a vm +// makes a system call, control jumps straight to the doAsmCall label. +void AsmCall( void ) { +	asm( CMANG(doAsmCall) ":				\n\t" \ +		"	movl (%%edi),%%eax			\n\t" \ +		"	subl $4,%%edi				\n\t" \ +		"	orl %%eax,%%eax				\n\t" \ +		"	jl systemCall				\n\t" \ +		"	shll $2,%%eax				\n\t" \ +		"	addl %3,%%eax				\n\t" \ +		"	call *(%%eax)				\n\t" \ +		"	movl (%%edi),%%eax			\n\t" \ +		"	andl " CMANG(callMask) ", %%eax		\n\t" \ +		"	jmp doret				\n\t" \ +		"systemCall:					\n\t" \ +		"	negl %%eax				\n\t" \ +		"	decl %%eax				\n\t" \ +		"	movl %%eax,%0				\n\t" \ +		"	movl %%esi,%1				\n\t" \ +		"	movl %%edi,%2				\n\t" \ +		"	pushl %%ecx				\n\t" \ +		"	pushl %%esi				\n\t" \ +		"	pushl %%edi				\n\t" \ +		"	call " CMANG(callAsmCall) "		\n\t" \ +		"	popl %%edi				\n\t" \ +		"	popl %%esi				\n\t" \ +		"	popl %%ecx				\n\t" \ +		"	addl $4,%%edi				\n\t" \ +		"doret:						\n\t" \ +		"	ret					\n\t" \ +		: "=rm" (callSyscallNum), "=rm" (callProgramStack), "=rm" (callOpStack) \ +		: "m" (instructionPointers) \ +		: "ax", "di", "si", "cx" \ +	); +} +#endif + +static int	Constant4( void ) { +	int		v; + +	v = code[pc] | (code[pc+1]<<8) | (code[pc+2]<<16) | (code[pc+3]<<24); +	pc += 4; +	return v; +} + +static int	Constant1( void ) { +	int		v; + +	v = code[pc]; +	pc += 1; +	return v; +} + +static void Emit1( int v )  +{ +	buf[ compiledOfs ] = v; +	compiledOfs++; + +	LastCommand = LAST_COMMAND_NONE; +} + +#if 0 +static void Emit2( int v ) { +	Emit1( v & 255 ); +	Emit1( ( v >> 8 ) & 255 ); +} +#endif + +static void Emit4( int v ) { +	Emit1( v & 255 ); +	Emit1( ( v >> 8 ) & 255 ); +	Emit1( ( v >> 16 ) & 255 ); +	Emit1( ( v >> 24 ) & 255 ); +} + +static int Hex( int c ) { +	if ( c >= 'a' && c <= 'f' ) { +		return 10 + c - 'a'; +	} +	if ( c >= 'A' && c <= 'F' ) { +		return 10 + c - 'A'; +	} +	if ( c >= '0' && c <= '9' ) { +		return c - '0'; +	} + +	Com_Error( ERR_DROP, "Hex: bad char '%c'", c ); + +	return 0; +} +static void EmitString( const char *string ) { +	int		c1, c2; +	int		v; + +	while ( 1 ) { +		c1 = string[0]; +		c2 = string[1]; + +		v = ( Hex( c1 ) << 4 ) | Hex( c2 ); +		Emit1( v ); + +		if ( !string[2] ) { +			break; +		} +		string += 3; +	} +} + + + +static void EmitCommand(ELastCommand command) +{ +	switch(command) +	{ +		case LAST_COMMAND_MOV_EDI_EAX: +			EmitString( "89 07" );		// mov dword ptr [edi], eax +			break; + +		case LAST_COMMAND_SUB_DI_4: +			EmitString( "83 EF 04" );	// sub edi, 4 +			break; + +		case LAST_COMMAND_SUB_DI_8: +			EmitString( "83 EF 08" );	// sub edi, 8 +			break; +		default: +			break; +	} +	LastCommand = command; +} + +static void EmitAddEDI4(vm_t *vm) { +	if (LastCommand == LAST_COMMAND_SUB_DI_4 && jused[instruction-1] == 0)  +	{		// sub di,4 +		compiledOfs -= 3; +		vm->instructionPointers[ instruction-1 ] = compiledOfs; +		return; +	} +	if (LastCommand == LAST_COMMAND_SUB_DI_8 && jused[instruction-1] == 0)  +	{		// sub di,8 +		compiledOfs -= 3; +		vm->instructionPointers[ instruction-1 ] = compiledOfs; +		EmitString( "83 EF 04" );	//	sub edi,4 +		return; +	} +	EmitString( "83 C7 04" );	//	add edi,4 +} + +static void EmitMovEAXEDI(vm_t *vm) { +	if (LastCommand == LAST_COMMAND_MOV_EDI_EAX)  +	{	// mov [edi], eax +		compiledOfs -= 2; +		vm->instructionPointers[ instruction-1 ] = compiledOfs; +		return; +	} +	if (pop1 == OP_DIVI || pop1 == OP_DIVU || pop1 == OP_MULI || pop1 == OP_MULU || +		pop1 == OP_STORE4 || pop1 == OP_STORE2 || pop1 == OP_STORE1 )  +	{	 +		return; +	} +	if (pop1 == OP_CONST && buf[compiledOfs-6] == 0xC7 && buf[compiledOfs-5] == 0x07 )  +	{	// mov edi, 0x123456 +		compiledOfs -= 6; +		vm->instructionPointers[ instruction-1 ] = compiledOfs; +		EmitString( "B8" );			// mov	eax, 0x12345678 +		Emit4( lastConst ); +		return; +	} +	EmitString( "8B 07" );		// mov eax, dword ptr [edi] +} + +qboolean EmitMovEBXEDI(vm_t *vm, int andit) { +	if (LastCommand == LAST_COMMAND_MOV_EDI_EAX)  +	{	// mov [edi], eax +		compiledOfs -= 2; +		vm->instructionPointers[ instruction-1 ] = compiledOfs; +		EmitString( "8B D8");		// mov bx, eax +		return qfalse; +	} +	if (pop1 == OP_DIVI || pop1 == OP_DIVU || pop1 == OP_MULI || pop1 == OP_MULU || +		pop1 == OP_STORE4 || pop1 == OP_STORE2 || pop1 == OP_STORE1 )  +	{	 +		EmitString( "8B D8");		// mov bx, eax +		return qfalse; +	} +	if (pop1 == OP_CONST && buf[compiledOfs-6] == 0xC7 && buf[compiledOfs-5] == 0x07 )  +	{		// mov edi, 0x123456 +		compiledOfs -= 6; +		vm->instructionPointers[ instruction-1 ] = compiledOfs; +		EmitString( "BB" );			// mov	ebx, 0x12345678 +		if (andit) { +			Emit4( lastConst & andit ); +		} else { +			Emit4( lastConst ); +		} +		return qtrue; +	} + +	EmitString( "8B 1F" );		// mov ebx, dword ptr [edi] +	return qfalse; +} + +/* +================= +VM_Compile +================= +*/ +void VM_Compile( vm_t *vm, vmHeader_t *header ) { +	int		op; +	int		maxLength; +	int		v; +	int		i; +	qboolean opt; + +	// allocate a very large temp buffer, we will shrink it later +	maxLength = header->codeLength * 8; +	buf = Z_Malloc( maxLength ); +	jused = Z_Malloc(header->instructionCount + 2 ); +	 +	Com_Memset(jused, 0, header->instructionCount+2); + +	// ensure that the optimisation pass knows about all the jump +	// table targets +	for( i = 0; i < vm->numJumpTableTargets; i++ ) { +		jused[ *(int *)(vm->jumpTableTargets + ( i * sizeof( int ) ) ) ] = 1; +	} + +	for(pass=0;pass<2;pass++) { +	oc0 = -23423; +	oc1 = -234354; +	pop0 = -43435; +	pop1 = -545455; + +	// translate all instructions +	pc = 0; +	instruction = 0; +	code = (byte *)header + header->codeOffset; +	compiledOfs = 0; + +	LastCommand = LAST_COMMAND_NONE; + +	while ( instruction < header->instructionCount ) { +		if ( compiledOfs > maxLength - 16 ) { +			Com_Error( ERR_FATAL, "VM_CompileX86: maxLength exceeded" ); +		} + +		vm->instructionPointers[ instruction ] = compiledOfs; +		instruction++; + +		if ( pc > header->codeLength ) { +			Com_Error( ERR_FATAL, "VM_CompileX86: pc > header->codeLength" ); +		} + +		op = code[ pc ]; +		pc++; +		switch ( op ) { +		case 0: +			break; +		case OP_BREAK: +			EmitString( "CC" );			// int 3 +			break; +		case OP_ENTER: +			EmitString( "81 EE" );		// sub	esi, 0x12345678 +			Emit4( Constant4() ); +			break; +		case OP_CONST: +			if (code[pc+4] == OP_LOAD4) { +				EmitAddEDI4(vm); +				EmitString( "BB" );		// mov	ebx, 0x12345678 +				Emit4( (Constant4()&vm->dataMask) + (int)vm->dataBase); +				EmitString( "8B 03" );		// mov	eax, dword ptr [ebx] +				EmitCommand(LAST_COMMAND_MOV_EDI_EAX);		// mov dword ptr [edi], eax +				pc++;						// OP_LOAD4 +				instruction += 1; +				break; +			} +			if (code[pc+4] == OP_LOAD2) { +				EmitAddEDI4(vm); +				EmitString( "BB" );		// mov	ebx, 0x12345678 +				Emit4( (Constant4()&vm->dataMask) + (int)vm->dataBase); +				EmitString( "0F B7 03" );	// movzx	eax, word ptr [ebx] +				EmitCommand(LAST_COMMAND_MOV_EDI_EAX);		// mov dword ptr [edi], eax +				pc++;						// OP_LOAD4 +				instruction += 1; +				break; +			} +			if (code[pc+4] == OP_LOAD1) { +				EmitAddEDI4(vm); +				EmitString( "BB" );		// mov	ebx, 0x12345678 +				Emit4( (Constant4()&vm->dataMask) + (int)vm->dataBase); +				EmitString( "0F B6 03" );	// movzx	eax, byte ptr [ebx] +				EmitCommand(LAST_COMMAND_MOV_EDI_EAX);		// mov dword ptr [edi], eax +				pc++;						// OP_LOAD4 +				instruction += 1; +				break; +			} +			if (code[pc+4] == OP_STORE4) { +				opt = EmitMovEBXEDI(vm, (vm->dataMask & ~3)); +				EmitString( "B8" );			// mov	eax, 0x12345678 +				Emit4( Constant4() ); +//				if (!opt) { +//					EmitString( "81 E3" );		// and ebx, 0x12345678 +//					Emit4( vm->dataMask & ~3 ); +//				} +				EmitString( "89 83" );		// mov dword ptr [ebx+0x12345678], eax +				Emit4( (int)vm->dataBase ); +				EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +				pc++;						// OP_STORE4 +				instruction += 1; +				break; +			} +			if (code[pc+4] == OP_STORE2) { +				opt = EmitMovEBXEDI(vm, (vm->dataMask & ~1)); +				EmitString( "B8" );			// mov	eax, 0x12345678 +				Emit4( Constant4() ); +//				if (!opt) { +//					EmitString( "81 E3" );		// and ebx, 0x12345678 +//					Emit4( vm->dataMask & ~1 ); +//				} +				EmitString( "66 89 83" );	// mov word ptr [ebx+0x12345678], eax +				Emit4( (int)vm->dataBase ); +				EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +				pc++;						// OP_STORE4 +				instruction += 1; +				break; +			} +			if (code[pc+4] == OP_STORE1) { +				opt = EmitMovEBXEDI(vm, vm->dataMask); +				EmitString( "B8" );			// mov	eax, 0x12345678 +				Emit4( Constant4() ); +//				if (!opt) { +//					EmitString( "81 E3" );	// and ebx, 0x12345678 +//					Emit4( vm->dataMask ); +//				} +				EmitString( "88 83" );		// mov byte ptr [ebx+0x12345678], eax +				Emit4( (int)vm->dataBase ); +				EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +				pc++;						// OP_STORE4 +				instruction += 1; +				break; +			} +			if (code[pc+4] == OP_ADD) { +				EmitString( "81 07" );		// add dword ptr [edi], 0x1234567 +				Emit4( Constant4() ); +				pc++;						// OP_ADD +				instruction += 1; +				break; +			} +			if (code[pc+4] == OP_SUB) { +				EmitString( "81 2F" );		// sub dword ptr [edi], 0x1234567 +				Emit4( Constant4() ); +				pc++;						// OP_ADD +				instruction += 1; +				break; +			} +			EmitAddEDI4(vm); +			EmitString( "C7 07" );		// mov	dword ptr [edi], 0x12345678 +			lastConst = Constant4(); +			Emit4( lastConst ); +			if (code[pc] == OP_JUMP) { +				jused[lastConst] = 1; +			} +			break; +		case OP_LOCAL: +			EmitAddEDI4(vm); +			EmitString( "8D 86" );		// lea eax, [0x12345678 + esi] +			oc0 = oc1; +			oc1 = Constant4(); +			Emit4( oc1 ); +			EmitCommand(LAST_COMMAND_MOV_EDI_EAX);		// mov dword ptr [edi], eax +			break; +		case OP_ARG: +			EmitMovEAXEDI(vm);			// mov	eax,dword ptr [edi] +			EmitString( "89 86" );		// mov	dword ptr [esi+database],eax +			// FIXME: range check +			Emit4( Constant1() + (int)vm->dataBase ); +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			break; +		case OP_CALL: +			EmitString( "C7 86" );		// mov dword ptr [esi+database],0x12345678 +			Emit4( (int)vm->dataBase ); +			Emit4( pc ); +			EmitString( "FF 15" );		// call asmCallPtr +			Emit4( (int)&asmCallPtr ); +			break; +		case OP_PUSH: +			EmitAddEDI4(vm); +			break; +		case OP_POP: +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			break; +		case OP_LEAVE: +			v = Constant4(); +			EmitString( "81 C6" );		// add	esi, 0x12345678 +			Emit4( v ); +			EmitString( "C3" );			// ret +			break; +		case OP_LOAD4: +			if (code[pc] == OP_CONST && code[pc+5] == OP_ADD && code[pc+6] == OP_STORE4) { +				if (oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { +					compiledOfs -= 11; +					vm->instructionPointers[ instruction-1 ] = compiledOfs; +				} +				pc++;						// OP_CONST +				v = Constant4(); +				EmitMovEBXEDI(vm, vm->dataMask); +				if (v == 1 && oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { +					EmitString( "FF 83");		// inc dword ptr [ebx + 0x12345678] +					Emit4( (int)vm->dataBase ); +				} else { +					EmitString( "8B 83" );		// mov	eax, dword ptr [ebx + 0x12345678] +					Emit4( (int)vm->dataBase ); +					EmitString( "05"  );		// add eax, const +					Emit4( v ); +					if (oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { +						EmitString( "89 83" );		// mov dword ptr [ebx+0x12345678], eax +						Emit4( (int)vm->dataBase ); +					} else { +						EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +						EmitString( "8B 1F" );		// mov	ebx, dword ptr [edi] +						EmitString( "89 83" );		// mov dword ptr [ebx+0x12345678], eax +						Emit4( (int)vm->dataBase ); +					} +				} +				EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +				pc++;						// OP_ADD +				pc++;						// OP_STORE +				instruction += 3; +				break; +			} + +			if (code[pc] == OP_CONST && code[pc+5] == OP_SUB && code[pc+6] == OP_STORE4) { +				if (oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { +					compiledOfs -= 11; +					vm->instructionPointers[ instruction-1 ] = compiledOfs; +				} +				EmitMovEBXEDI(vm, vm->dataMask); +				EmitString( "8B 83" );		// mov	eax, dword ptr [ebx + 0x12345678] +				Emit4( (int)vm->dataBase ); +				pc++;						// OP_CONST +				v = Constant4(); +				if (v == 1 && oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { +					EmitString( "FF 8B");		// dec dword ptr [ebx + 0x12345678] +					Emit4( (int)vm->dataBase ); +				} else { +					EmitString( "2D"  );		// sub eax, const +					Emit4( v ); +					if (oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { +						EmitString( "89 83" );		// mov dword ptr [ebx+0x12345678], eax +						Emit4( (int)vm->dataBase ); +					} else { +						EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +						EmitString( "8B 1F" );		// mov	ebx, dword ptr [edi] +						EmitString( "89 83" );		// mov dword ptr [ebx+0x12345678], eax +						Emit4( (int)vm->dataBase ); +					} +				} +				EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +				pc++;						// OP_SUB +				pc++;						// OP_STORE +				instruction += 3; +				break; +			} + +			if (buf[compiledOfs-2] == 0x89 && buf[compiledOfs-1] == 0x07) { +				compiledOfs -= 2; +				vm->instructionPointers[ instruction-1 ] = compiledOfs; +				EmitString( "8B 80");	// mov eax, dword ptr [eax + 0x1234567] +				Emit4( (int)vm->dataBase ); +				EmitCommand(LAST_COMMAND_MOV_EDI_EAX);		// mov dword ptr [edi], eax +				break; +			} +			EmitMovEBXEDI(vm, vm->dataMask); +			EmitString( "8B 83" );		// mov	eax, dword ptr [ebx + 0x12345678] +			Emit4( (int)vm->dataBase ); +			EmitCommand(LAST_COMMAND_MOV_EDI_EAX);		// mov dword ptr [edi], eax +			break; +		case OP_LOAD2: +			EmitMovEBXEDI(vm, vm->dataMask); +			EmitString( "0F B7 83" );	// movzx	eax, word ptr [ebx + 0x12345678] +			Emit4( (int)vm->dataBase ); +			EmitCommand(LAST_COMMAND_MOV_EDI_EAX);		// mov dword ptr [edi], eax +			break; +		case OP_LOAD1: +			EmitMovEBXEDI(vm, vm->dataMask); +			EmitString( "0F B6 83" );	// movzx eax, byte ptr [ebx + 0x12345678] +			Emit4( (int)vm->dataBase ); +			EmitCommand(LAST_COMMAND_MOV_EDI_EAX);		// mov dword ptr [edi], eax +			break; +		case OP_STORE4: +			EmitMovEAXEDI(vm);	 +			EmitString( "8B 5F FC" );	// mov	ebx, dword ptr [edi-4] +//			if (pop1 != OP_CALL) { +//				EmitString( "81 E3" );		// and ebx, 0x12345678 +//				Emit4( vm->dataMask & ~3 ); +//			} +			EmitString( "89 83" );		// mov dword ptr [ebx+0x12345678], eax +			Emit4( (int)vm->dataBase ); +			EmitCommand(LAST_COMMAND_SUB_DI_8);		// sub edi, 8 +			break; +		case OP_STORE2: +			EmitMovEAXEDI(vm);	 +			EmitString( "8B 5F FC" );	// mov	ebx, dword ptr [edi-4] +//			EmitString( "81 E3" );		// and ebx, 0x12345678 +//			Emit4( vm->dataMask & ~1 ); +			EmitString( "66 89 83" );	// mov word ptr [ebx+0x12345678], eax +			Emit4( (int)vm->dataBase ); +			EmitCommand(LAST_COMMAND_SUB_DI_8);		// sub edi, 8 +			break; +		case OP_STORE1: +			EmitMovEAXEDI(vm);	 +			EmitString( "8B 5F FC" );	// mov	ebx, dword ptr [edi-4] +//			EmitString( "81 E3" );		// and ebx, 0x12345678 +//			Emit4( vm->dataMask ); +			EmitString( "88 83" );		// mov byte ptr [ebx+0x12345678], eax +			Emit4( (int)vm->dataBase ); +			EmitCommand(LAST_COMMAND_SUB_DI_8);		// sub edi, 8 +			break; + +		case OP_EQ: +			EmitCommand(LAST_COMMAND_SUB_DI_8);		// sub edi, 8 +			EmitString( "8B 47 04" );	// mov	eax, dword ptr [edi+4] +			EmitString( "3B 47 08" );	// cmp	eax, dword ptr [edi+8] +			EmitString( "75 06" );		// jne +6 +			EmitString( "FF 25" );		// jmp	[0x12345678] +			v = Constant4(); +			jused[v] = 1; +			Emit4( (int)vm->instructionPointers + v*4 ); +			break; +		case OP_NE: +			EmitCommand(LAST_COMMAND_SUB_DI_8);		// sub edi, 8 +			EmitString( "8B 47 04" );	// mov	eax, dword ptr [edi+4] +			EmitString( "3B 47 08" );	// cmp	eax, dword ptr [edi+8] +			EmitString( "74 06" );		// je +6 +			EmitString( "FF 25" );		// jmp	[0x12345678] +			v = Constant4(); +			jused[v] = 1; +			Emit4( (int)vm->instructionPointers + v*4 ); +			break; +		case OP_LTI: +			EmitCommand(LAST_COMMAND_SUB_DI_8);		// sub edi, 8 +			EmitString( "8B 47 04" );	// mov	eax, dword ptr [edi+4] +			EmitString( "3B 47 08" );	// cmp	eax, dword ptr [edi+8] +			EmitString( "7D 06" );		// jnl +6 +			EmitString( "FF 25" );		// jmp	[0x12345678] +			v = Constant4(); +			jused[v] = 1; +			Emit4( (int)vm->instructionPointers + v*4 ); +			break; +		case OP_LEI: +			EmitCommand(LAST_COMMAND_SUB_DI_8);		// sub edi, 8 +			EmitString( "8B 47 04" );	// mov	eax, dword ptr [edi+4] +			EmitString( "3B 47 08" );	// cmp	eax, dword ptr [edi+8] +			EmitString( "7F 06" );		// jnle +6 +			EmitString( "FF 25" );		// jmp	[0x12345678] +			v = Constant4(); +			jused[v] = 1; +			Emit4( (int)vm->instructionPointers + v*4 ); +			break; +		case OP_GTI: +			EmitCommand(LAST_COMMAND_SUB_DI_8);		// sub edi, 8 +			EmitString( "8B 47 04" );	// mov	eax, dword ptr [edi+4] +			EmitString( "3B 47 08" );	// cmp	eax, dword ptr [edi+8] +			EmitString( "7E 06" );		// jng +6 +			EmitString( "FF 25" );		// jmp	[0x12345678] +			v = Constant4(); +			jused[v] = 1; +			Emit4( (int)vm->instructionPointers + v*4 ); +			break; +		case OP_GEI: +			EmitCommand(LAST_COMMAND_SUB_DI_8);		// sub edi, 8 +			EmitString( "8B 47 04" );	// mov	eax, dword ptr [edi+4] +			EmitString( "3B 47 08" );	// cmp	eax, dword ptr [edi+8] +			EmitString( "7C 06" );		// jnge +6 +			EmitString( "FF 25" );		// jmp	[0x12345678] +			v = Constant4(); +			jused[v] = 1; +			Emit4( (int)vm->instructionPointers + v*4 ); +			break; +		case OP_LTU: +			EmitCommand(LAST_COMMAND_SUB_DI_8);		// sub edi, 8 +			EmitString( "8B 47 04" );	// mov	eax, dword ptr [edi+4] +			EmitString( "3B 47 08" );	// cmp	eax, dword ptr [edi+8] +			EmitString( "73 06" );		// jnb +6 +			EmitString( "FF 25" );		// jmp	[0x12345678] +			v = Constant4(); +			jused[v] = 1; +			Emit4( (int)vm->instructionPointers + v*4 ); +			break; +		case OP_LEU: +			EmitCommand(LAST_COMMAND_SUB_DI_8);		// sub edi, 8 +			EmitString( "8B 47 04" );	// mov	eax, dword ptr [edi+4] +			EmitString( "3B 47 08" );	// cmp	eax, dword ptr [edi+8] +			EmitString( "77 06" );		// jnbe +6 +			EmitString( "FF 25" );		// jmp	[0x12345678] +			v = Constant4(); +			jused[v] = 1; +			Emit4( (int)vm->instructionPointers + v*4 ); +			break; +		case OP_GTU: +			EmitCommand(LAST_COMMAND_SUB_DI_8);		// sub edi, 8 +			EmitString( "8B 47 04" );	// mov	eax, dword ptr [edi+4] +			EmitString( "3B 47 08" );	// cmp	eax, dword ptr [edi+8] +			EmitString( "76 06" );		// jna +6 +			EmitString( "FF 25" );		// jmp	[0x12345678] +			v = Constant4(); +			jused[v] = 1; +			Emit4( (int)vm->instructionPointers + v*4 ); +			break; +		case OP_GEU: +			EmitCommand(LAST_COMMAND_SUB_DI_8);		// sub edi, 8 +			EmitString( "8B 47 04" );	// mov	eax, dword ptr [edi+4] +			EmitString( "3B 47 08" );	// cmp	eax, dword ptr [edi+8] +			EmitString( "72 06" );		// jnae +6 +			EmitString( "FF 25" );		// jmp	[0x12345678] +			v = Constant4(); +			jused[v] = 1; +			Emit4( (int)vm->instructionPointers + v*4 ); +			break; +		case OP_EQF: +			EmitCommand(LAST_COMMAND_SUB_DI_8);		// sub edi, 8 +			EmitString( "D9 47 04" );	// fld dword ptr [edi+4] +			EmitString( "D8 5F 08" );	// fcomp dword ptr [edi+8] +			EmitString( "DF E0" );		// fnstsw ax +			EmitString( "F6 C4 40" );	// test	ah,0x40 +			EmitString( "74 06" );		// je +6 +			EmitString( "FF 25" );		// jmp	[0x12345678] +			v = Constant4(); +			jused[v] = 1; +			Emit4( (int)vm->instructionPointers + v*4 ); +			break;			 +		case OP_NEF: +			EmitCommand(LAST_COMMAND_SUB_DI_8);		// sub edi, 8 +			EmitString( "D9 47 04" );	// fld dword ptr [edi+4] +			EmitString( "D8 5F 08" );	// fcomp dword ptr [edi+8] +			EmitString( "DF E0" );		// fnstsw ax +			EmitString( "F6 C4 40" );	// test	ah,0x40 +			EmitString( "75 06" );		// jne +6 +			EmitString( "FF 25" );		// jmp	[0x12345678] +			v = Constant4(); +			jused[v] = 1; +			Emit4( (int)vm->instructionPointers + v*4 ); +			break;			 +		case OP_LTF: +			EmitCommand(LAST_COMMAND_SUB_DI_8);		// sub edi, 8 +			EmitString( "D9 47 04" );	// fld dword ptr [edi+4] +			EmitString( "D8 5F 08" );	// fcomp dword ptr [edi+8] +			EmitString( "DF E0" );		// fnstsw ax +			EmitString( "F6 C4 01" );	// test	ah,0x01 +			EmitString( "74 06" );		// je +6 +			EmitString( "FF 25" );		// jmp	[0x12345678] +			v = Constant4(); +			jused[v] = 1; +			Emit4( (int)vm->instructionPointers + v*4 ); +			break;			 +		case OP_LEF: +			EmitCommand(LAST_COMMAND_SUB_DI_8);		// sub edi, 8 +			EmitString( "D9 47 04" );	// fld dword ptr [edi+4] +			EmitString( "D8 5F 08" );	// fcomp dword ptr [edi+8] +			EmitString( "DF E0" );		// fnstsw ax +			EmitString( "F6 C4 41" );	// test	ah,0x41 +			EmitString( "74 06" );		// je +6 +			EmitString( "FF 25" );		// jmp	[0x12345678] +			v = Constant4(); +			jused[v] = 1; +			Emit4( (int)vm->instructionPointers + v*4 ); +			break;			 +		case OP_GTF: +			EmitCommand(LAST_COMMAND_SUB_DI_8);		// sub edi, 8 +			EmitString( "D9 47 04" );	// fld dword ptr [edi+4] +			EmitString( "D8 5F 08" );	// fcomp dword ptr [edi+8] +			EmitString( "DF E0" );		// fnstsw ax +			EmitString( "F6 C4 41" );	// test	ah,0x41 +			EmitString( "75 06" );		// jne +6 +			EmitString( "FF 25" );		// jmp	[0x12345678] +			v = Constant4(); +			jused[v] = 1; +			Emit4( (int)vm->instructionPointers + v*4 ); +			break;			 +		case OP_GEF: +			EmitCommand(LAST_COMMAND_SUB_DI_8);		// sub edi, 8 +			EmitString( "D9 47 04" );	// fld dword ptr [edi+4] +			EmitString( "D8 5F 08" );	// fcomp dword ptr [edi+8] +			EmitString( "DF E0" );		// fnstsw ax +			EmitString( "F6 C4 01" );	// test	ah,0x01 +			EmitString( "75 06" );		// jne +6 +			EmitString( "FF 25" );		// jmp	[0x12345678] +			v = Constant4(); +			jused[v] = 1; +			Emit4( (int)vm->instructionPointers + v*4 ); +			break;			 +		case OP_NEGI: +			EmitString( "F7 1F" );		// neg dword ptr [edi] +			break; +		case OP_ADD: +			EmitMovEAXEDI(vm);			// mov eax, dword ptr [edi] +			EmitString( "01 47 FC" );	// add dword ptr [edi-4],eax +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			break; +		case OP_SUB: +			EmitMovEAXEDI(vm);			// mov eax, dword ptr [edi] +			EmitString( "29 47 FC" );	// sub dword ptr [edi-4],eax +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			break; +		case OP_DIVI: +			EmitString( "8B 47 FC" );	// mov eax,dword ptr [edi-4] +			EmitString( "99" );			// cdq +			EmitString( "F7 3F" );		// idiv dword ptr [edi] +			EmitString( "89 47 FC" );	// mov dword ptr [edi-4],eax +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			break; +		case OP_DIVU: +			EmitString( "8B 47 FC" );	// mov eax,dword ptr [edi-4] +			EmitString( "33 D2" );		// xor edx, edx +			EmitString( "F7 37" );		// div dword ptr [edi] +			EmitString( "89 47 FC" );	// mov dword ptr [edi-4],eax +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			break; +		case OP_MODI: +			EmitString( "8B 47 FC" );	// mov eax,dword ptr [edi-4] +			EmitString( "99" );			// cdq +			EmitString( "F7 3F" );		// idiv dword ptr [edi] +			EmitString( "89 57 FC" );	// mov dword ptr [edi-4],edx +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			break; +		case OP_MODU: +			EmitString( "8B 47 FC" );	// mov eax,dword ptr [edi-4] +			EmitString( "33 D2" );		// xor edx, edx +			EmitString( "F7 37" );		// div dword ptr [edi] +			EmitString( "89 57 FC" );	// mov dword ptr [edi-4],edx +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			break; +		case OP_MULI: +			EmitString( "8B 47 FC" );	// mov eax,dword ptr [edi-4] +			EmitString( "F7 2F" );		// imul dword ptr [edi] +			EmitString( "89 47 FC" );	// mov dword ptr [edi-4],eax +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			break; +		case OP_MULU: +			EmitString( "8B 47 FC" );	// mov eax,dword ptr [edi-4] +			EmitString( "F7 27" );		// mul dword ptr [edi] +			EmitString( "89 47 FC" );	// mov dword ptr [edi-4],eax +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			break; +		case OP_BAND: +			EmitMovEAXEDI(vm);			// mov eax, dword ptr [edi] +			EmitString( "21 47 FC" );	// and dword ptr [edi-4],eax +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			break; +		case OP_BOR: +			EmitMovEAXEDI(vm);			// mov eax, dword ptr [edi] +			EmitString( "09 47 FC" );	// or dword ptr [edi-4],eax +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			break; +		case OP_BXOR: +			EmitMovEAXEDI(vm);			// mov eax, dword ptr [edi] +			EmitString( "31 47 FC" );	// xor dword ptr [edi-4],eax +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			break; +		case OP_BCOM: +			EmitString( "F7 17" );		// not dword ptr [edi] +			break; +		case OP_LSH: +			EmitString( "8B 0F" );		// mov ecx, dword ptr [edi] +			EmitString( "D3 67 FC" );	// shl dword ptr [edi-4], cl +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			break; +		case OP_RSHI: +			EmitString( "8B 0F" );		// mov ecx, dword ptr [edi] +			EmitString( "D3 7F FC" );	// sar dword ptr [edi-4], cl +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			break; +		case OP_RSHU: +			EmitString( "8B 0F" );		// mov ecx, dword ptr [edi] +			EmitString( "D3 6F FC" );	// shr dword ptr [edi-4], cl +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			break; +		case OP_NEGF: +			EmitString( "D9 07" );		// fld dword ptr [edi] +			EmitString( "D9 E0" );		// fchs +			EmitString( "D9 1F" );		// fstp dword ptr [edi] +			break; +		case OP_ADDF: +			EmitString( "D9 47 FC" );	// fld dword ptr [edi-4] +			EmitString( "D8 07" );		// fadd dword ptr [edi] +			EmitString( "D9 5F FC" );	// fstp dword ptr [edi-4] +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			break; +		case OP_SUBF: +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			EmitString( "D9 07" );		// fld dword ptr [edi] +			EmitString( "D8 67 04" );	// fsub dword ptr [edi+4] +			EmitString( "D9 1F" );		// fstp dword ptr [edi] +			break; +		case OP_DIVF: +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			EmitString( "D9 07" );		// fld dword ptr [edi] +			EmitString( "D8 77 04" );	// fdiv dword ptr [edi+4] +			EmitString( "D9 1F" );		// fstp dword ptr [edi] +			break; +		case OP_MULF: +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			EmitString( "D9 07" );		// fld dword ptr [edi] +			EmitString( "D8 4f 04" );	// fmul dword ptr [edi+4] +			EmitString( "D9 1F" );		// fstp dword ptr [edi] +			break; +		case OP_CVIF: +			EmitString( "DB 07" );		// fild dword ptr [edi] +			EmitString( "D9 1F" );		// fstp dword ptr [edi] +			break; +		case OP_CVFI: +#ifndef FTOL_PTR // WHENHELLISFROZENOVER  // bk001213 - was used in 1.17 +			// not IEEE complient, but simple and fast +			EmitString( "D9 07" );		// fld dword ptr [edi] +			EmitString( "DB 1F" );		// fistp dword ptr [edi] +#else // FTOL_PTR +			// call the library conversion function +			EmitString( "D9 07" );		// fld dword ptr [edi] +			EmitString( "FF 15" );		// call ftolPtr +			Emit4( (int)&ftolPtr ); +			EmitCommand(LAST_COMMAND_MOV_EDI_EAX);		// mov dword ptr [edi], eax +#endif +			break; +		case OP_SEX8: +			EmitString( "0F BE 07" );	// movsx eax, byte ptr [edi] +			EmitCommand(LAST_COMMAND_MOV_EDI_EAX);		// mov dword ptr [edi], eax +			break; +		case OP_SEX16: +			EmitString( "0F BF 07" );	// movsx eax, word ptr [edi] +			EmitCommand(LAST_COMMAND_MOV_EDI_EAX);		// mov dword ptr [edi], eax +			break; + +		case OP_BLOCK_COPY: +			// FIXME: range check +			EmitString( "56" );			// push esi +			EmitString( "57" );			// push edi +			EmitString( "8B 37" );		// mov esi,[edi]  +			EmitString( "8B 7F FC" );	// mov edi,[edi-4]  +			EmitString( "B9" );			// mov ecx,0x12345678 +			Emit4( Constant4() >> 2 ); +			EmitString( "B8" );			// mov eax, datamask +			Emit4( vm->dataMask ); +			EmitString( "BB" );			// mov ebx, database +			Emit4( (int)vm->dataBase ); +			EmitString( "23 F0" );		// and esi, eax +			EmitString( "03 F3" );		// add esi, ebx +			EmitString( "23 F8" );		// and edi, eax +			EmitString( "03 FB" );		// add edi, ebx +			EmitString( "F3 A5" );		// rep movsd +			EmitString( "5F" );			// pop edi +			EmitString( "5E" );			// pop esi +			EmitCommand(LAST_COMMAND_SUB_DI_8);		// sub edi, 8 +			break; + +		case OP_JUMP: +			EmitCommand(LAST_COMMAND_SUB_DI_4);		// sub edi, 4 +			EmitString( "8B 47 04" );	// mov eax,dword ptr [edi+4] +			// FIXME: range check +			EmitString( "FF 24 85" );	// jmp dword ptr [instructionPointers + eax * 4] +			Emit4( (int)vm->instructionPointers ); +			break; +		default: +			Com_Error( ERR_DROP, "VM_CompileX86: bad opcode %i at offset %i", op, pc ); +		} +		pop0 = pop1; +		pop1 = op; +	} +	} + +	// copy to an exact size buffer on the hunk +	vm->codeLength = compiledOfs; +#ifdef VM_X86_MMAP +	vm->codeBase = mmap(NULL, compiledOfs, PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); +	if(vm->codeBase == (void*)-1) +		Com_Error(ERR_DROP, "VM_CompileX86: can't mmap memory"); +#elif _WIN32 +	// allocate memory with EXECUTE permissions under windows. +	vm->codeBase = VirtualAlloc(NULL, compiledOfs, MEM_COMMIT, PAGE_EXECUTE_READWRITE); +	if(!vm->codeBase) +		Com_Error(ERR_DROP, "VM_CompileX86: VirtualAlloc failed"); +#else +	vm->codeBase = malloc(compiledOfs); +#endif + +	Com_Memcpy( vm->codeBase, buf, compiledOfs ); + +#ifdef VM_X86_MMAP +	if(mprotect(vm->codeBase, compiledOfs, PROT_READ|PROT_EXEC)) +		Com_Error(ERR_DROP, "VM_CompileX86: mprotect failed"); +#elif _WIN32 +	{ +		DWORD oldProtect = 0; +		 +		// remove write permissions. +		if(!VirtualProtect(vm->codeBase, compiledOfs, PAGE_EXECUTE_READ, &oldProtect)) +			Com_Error(ERR_DROP, "VM_CompileX86: VirtualProtect failed"); +	} +#endif + +	Z_Free( buf ); +	Z_Free( jused ); +	Com_Printf( "VM file %s compiled to %i bytes of code\n", vm->name, compiledOfs ); + +	vm->destroy = VM_Destroy_Compiled; + +	// offset all the instruction pointers for the new location +	for ( i = 0 ; i < header->instructionCount ; i++ ) { +		vm->instructionPointers[i] += (int)vm->codeBase; +	} +} + +void VM_Destroy_Compiled(vm_t* self) +{ +#ifdef VM_X86_MMAP +	munmap(self->codeBase, self->codeLength); +#elif _WIN32 +	VirtualFree(self->codeBase, self->codeLength, MEM_RELEASE); +#else +	free(self->codeBase); +#endif +} + +/* +============== +VM_CallCompiled + +This function is called directly by the generated code +============== +*/ +int	VM_CallCompiled( vm_t *vm, int *args ) { +	int		stack[1024]; +	int		programCounter; +	int		programStack; +	int		stackOnEntry; +	byte	*image; +	void	*entryPoint; +	void	*opStack; +	int		*oldInstructionPointers; + +	oldInstructionPointers = instructionPointers; + +	currentVM = vm; +	instructionPointers = vm->instructionPointers; + +	// interpret the code +	vm->currentlyInterpreting = qtrue; + +	callMask = vm->dataMask; + +	// we might be called recursively, so this might not be the very top +	programStack = vm->programStack; +	stackOnEntry = programStack; + +	// set up the stack frame  +	image = vm->dataBase; + +	programCounter = 0; + +	programStack -= 48; + +	*(int *)&image[ programStack + 44] = args[9]; +	*(int *)&image[ programStack + 40] = args[8]; +	*(int *)&image[ programStack + 36] = args[7]; +	*(int *)&image[ programStack + 32] = args[6]; +	*(int *)&image[ programStack + 28] = args[5]; +	*(int *)&image[ programStack + 24] = args[4]; +	*(int *)&image[ programStack + 20] = args[3]; +	*(int *)&image[ programStack + 16] = args[2]; +	*(int *)&image[ programStack + 12] = args[1]; +	*(int *)&image[ programStack + 8 ] = args[0]; +	*(int *)&image[ programStack + 4 ] = 0;	// return stack +	*(int *)&image[ programStack ] = -1;	// will terminate the loop on return + +	// off we go into generated code... +	entryPoint = vm->codeBase; +	opStack = &stack; + +#ifdef _MSC_VER +	__asm  { +		pushad +		mov		esi, programStack; +		mov		edi, opStack +		call	entryPoint +		mov		programStack, esi +		mov		opStack, edi +		popad +	} +#else +	{ +		static int memProgramStack; +		static void *memOpStack; +		static void *memEntryPoint; + +		memProgramStack	= programStack; +		memOpStack      = opStack;      +		memEntryPoint   = entryPoint;   +		 +		__asm__("	pushal				\n" \ +				"	movl %0,%%esi		\n" \ +				"	movl %1,%%edi		\n" \ +				"	call *%2			\n" \ +				"	movl %%esi,%0		\n" \ +				"	movl %%edi,%1		\n" \ +				"	popal				\n" \ +				: "=m" (memProgramStack), "=m" (memOpStack) \ +				: "m" (memEntryPoint), "m" (memProgramStack), "m" (memOpStack) \ +				: "si", "di" \ +		); + +		programStack = memProgramStack; +		opStack      = memOpStack; +	} +#endif + +	if ( opStack != &stack[1] ) { +		Com_Error( ERR_DROP, "opStack corrupted in compiled code" ); +	} +	if ( programStack != stackOnEntry - 48 ) { +		Com_Error( ERR_DROP, "programStack corrupted in compiled code" ); +	} + +	vm->programStack = stackOnEntry; + +	// in case we were recursively called by another vm +	instructionPointers = oldInstructionPointers; + +	return *(int *)opStack; +} diff --git a/src/qcommon/vm_x86_64.c b/src/qcommon/vm_x86_64.c new file mode 100644 index 0000000..e8e827e --- /dev/null +++ b/src/qcommon/vm_x86_64.c @@ -0,0 +1,1023 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2005 Ludwig Nussel <ludwig.nussel@web.de> + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ +// vm_x86_64.c -- load time compiler and execution environment for x86-64 + +#include "vm_local.h" + +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> + +#ifdef DEBUG_VM +#define Dfprintf(fd, args...) fprintf(fd, ##args) +static FILE* qdasmout; +#else +#define Dfprintf(args...) +#endif + +static void VM_Destroy_Compiled(vm_t* self); + +/* +  +  |=====================| +  ^       dataMask      ^- programStack rdi +  | +  +- r8 + +  eax	scratch +  ebx	scratch +  ecx	scratch (required for shifts) +  edx	scratch (required for divisions) +  rsi	stack pointer (opStack) +  rdi	program frame pointer (programStack) +  r8    pointer data (vm->dataBase) +  r10   start of generated code +*/ + + +static long callAsmCall(long callProgramStack, long callSyscallNum) +{ +	vm_t *savedVM; +	long ret = 0x77; +	long args[11]; +//	int iargs[11]; +	int i; + +//	Dfprintf(stderr, "callAsmCall(%ld, %ld)\n", callProgramStack, callSyscallNum); +//	Com_Printf("-> callAsmCall %s, level %d, num %ld\n", currentVM->name, currentVM->callLevel, callSyscallNum); + +	savedVM = currentVM; + +	// save the stack to allow recursive VM entry +	currentVM->programStack = callProgramStack - 4; + +	args[0] = callSyscallNum; +//	iargs[0] = callSyscallNum; +	for(i = 0; i < 10; ++i) +	{ +//		iargs[i+1] = *(int *)((byte *)currentVM->dataBase + callProgramStack + 8 + 4*i); +		args[i+1] = *(int *)((byte *)currentVM->dataBase + callProgramStack + 8 + 4*i); +	} +	ret = currentVM->systemCall(args); + + 	currentVM = savedVM; +//	Com_Printf("<- callAsmCall %s, level %d, num %ld\n", currentVM->name, currentVM->callLevel, callSyscallNum); + +	return ret; +} + +#ifdef DEBUG_VM // bk001204 +static char	*opnames[256] = { +	"OP_UNDEF",  + +	"OP_IGNORE",  + +	"OP_BREAK", + +	"OP_ENTER", +	"OP_LEAVE", +	"OP_CALL", +	"OP_PUSH", +	"OP_POP", + +	"OP_CONST", + +	"OP_LOCAL", + +	"OP_JUMP", + +	//------------------- + +	"OP_EQ", +	"OP_NE", + +	"OP_LTI", +	"OP_LEI", +	"OP_GTI", +	"OP_GEI", + +	"OP_LTU", +	"OP_LEU", +	"OP_GTU", +	"OP_GEU", + +	"OP_EQF", +	"OP_NEF", + +	"OP_LTF", +	"OP_LEF", +	"OP_GTF", +	"OP_GEF", + +	//------------------- + +	"OP_LOAD1", +	"OP_LOAD2", +	"OP_LOAD4", +	"OP_STORE1", +	"OP_STORE2", +	"OP_STORE4", +	"OP_ARG", + +	"OP_BLOCK_COPY", + +	//------------------- + +	"OP_SEX8", +	"OP_SEX16", + +	"OP_NEGI", +	"OP_ADD", +	"OP_SUB", +	"OP_DIVI", +	"OP_DIVU", +	"OP_MODI", +	"OP_MODU", +	"OP_MULI", +	"OP_MULU", + +	"OP_BAND", +	"OP_BOR", +	"OP_BXOR", +	"OP_BCOM", + +	"OP_LSH", +	"OP_RSHI", +	"OP_RSHU", + +	"OP_NEGF", +	"OP_ADDF", +	"OP_SUBF", +	"OP_DIVF", +	"OP_MULF", + +	"OP_CVIF", +	"OP_CVFI" +}; +#endif // DEBUG_VM + +static unsigned char op_argsize[256] =  +{ +	[OP_ENTER]      = 4, +	[OP_LEAVE]      = 4, +	[OP_CONST]      = 4, +	[OP_LOCAL]      = 4, +	[OP_EQ]         = 4, +	[OP_NE]         = 4, +	[OP_LTI]        = 4, +	[OP_LEI]        = 4, +	[OP_GTI]        = 4, +	[OP_GEI]        = 4, +	[OP_LTU]        = 4, +	[OP_LEU]        = 4, +	[OP_GTU]        = 4, +	[OP_GEU]        = 4, +	[OP_EQF]        = 4, +	[OP_NEF]        = 4, +	[OP_LTF]        = 4, +	[OP_LEF]        = 4, +	[OP_GTF]        = 4, +	[OP_GEF]        = 4, +	[OP_ARG]        = 1, +	[OP_BLOCK_COPY] = 4, +}; + +#define emit(x...) \ +	do { fprintf(fh_s, ##x); fputc('\n', fh_s); } while(0) +  +// integer compare and jump +#define IJ(op) \ +	emit("subq $8, %%rsi"); \ +	emit("movl 4(%%rsi), %%eax"); \ +	emit("cmpl 8(%%rsi), %%eax"); \ +	emit(op " i_%08x", instruction+1); \ +	emit("jmp i_%08x", iarg); + +#ifdef USE_X87 +#define FJ(bits, op) \ +	emit("subq $8, %%rsi");\ +	emit("flds 4(%%rsi)");\ +	emit("fcomps 8(%%rsi)");\ +	emit("fnstsw %%ax");\ +	emit("testb $" #bits ", %%ah");\ +	emit(op " i_%08x", instruction+1);\ +	emit("jmp i_%08x", iarg); +#define XJ(x) +#else +#define FJ(x, y) +#define XJ(op) \ +	emit("subq $8, %%rsi");\ +	emit("movss 4(%%rsi), %%xmm0");\ +	emit("ucomiss 8(%%rsi), %%xmm0");\ +	emit("jp i_%08x", instruction+1);\ +	emit(op " i_%08x", instruction+1);\ +	emit("jmp i_%08x", iarg); +#endif + +#define SIMPLE(op) \ +	emit("subq $4, %%rsi"); \ +	emit("movl 4(%%rsi), %%eax"); \ +	emit(op " %%eax, 0(%%rsi)"); + +#ifdef USE_X87 +#define FSIMPLE(op) \ +	emit("subq $4, %%rsi"); \ +	emit("flds 0(%%rsi)"); \ +	emit(op " 4(%%rsi)"); \ +	emit("fstps 0(%%rsi)"); +#define XSIMPLE(op) +#else +#define FSIMPLE(op) +#define XSIMPLE(op) \ +	emit("subq $4, %%rsi"); \ +	emit("movss 0(%%rsi), %%xmm0"); \ +	emit(op " 4(%%rsi), %%xmm0"); \ +	emit("movss %%xmm0, 0(%%rsi)"); +#endif + +#define SHIFT(op) \ +	emit("subq $4, %%rsi"); \ +	emit("movl 4(%%rsi), %%ecx"); \ +	emit("movl 0(%%rsi), %%eax"); \ +	emit(op " %%cl, %%eax"); \ +	emit("movl %%eax, 0(%%rsi)"); + +#if 1 +#define RANGECHECK(reg) \ +	emit("andl $0x%x, %%" #reg, vm->dataMask); +#elif 0 +#define RANGECHECK(reg) \ +	emit("pushl %%" #reg); \ +	emit("andl $0x%x, %%" #reg, ~vm->dataMask); \ +	emit("jz rangecheck_ok_i_%08x", instruction); \ +	emit("int3"); \ +	emit("rangecheck_ok_i_%08x:", instruction); \ +	emit("popl %%" #reg); \ +	emit("andl $0x%x, %%" #reg, vm->dataMask); +#else +#define RANGECHECK(reg) +#endif + +#ifdef DEBUG_VM +#define NOTIMPL(x) \ +	do { Com_Error(ERR_DROP, "instruction not implemented: %s\n", opnames[x]); } while(0) +#else +#define NOTIMPL(x) \ +	do { Com_Printf(S_COLOR_RED "instruction not implemented: %x\n", x); vm->compiled = qfalse; return; } while(0) +#endif + +static void* getentrypoint(vm_t* vm) +{ +       return vm->codeBase+64; // skip ELF header +} + +char* mmapfile(const char* fn, size_t* size) +{ +	int fd = -1; +	char* mem = NULL; +	struct stat stb; + +	fd = open(fn, O_RDONLY); +	if(fd == -1) +		goto out; + +	if(fstat(fd, &stb) == -1) +		goto out; + +	*size = stb.st_size; + +	mem = mmap(NULL, stb.st_size, PROT_READ|PROT_EXEC, MAP_SHARED, fd, 0); +	if(mem == (void*)-1) +		mem = NULL; + +out: +	if(fd != -1) +		close(fd); + +	return mem; +} + +static int doas(char* in, char* out, unsigned char** compiledcode) +{ +	unsigned char* mem; +	size_t size = -1; +	pid_t pid; + +	Com_Printf("running assembler < %s > %s\n", in, out); +	pid = fork(); +	if(pid == -1) +	{ +		Com_Printf(S_COLOR_RED "can't fork\n"); +		return -1; +	} + +	if(!pid) +	{ +		char* const argv[] = { +			"as", +			"-o", +			out, +			in, +			NULL +		}; + +		execvp(argv[0], argv); +		_exit(-1); +	} +	else +	{ +		int status; +		if(waitpid(pid, &status, 0) == -1) +		{ +			Com_Printf(S_COLOR_RED "can't wait for as: %s\n", strerror(errno)); +			return -1; +		} + +		if(!WIFEXITED(status)) +		{ +			Com_Printf(S_COLOR_RED "as died\n"); +			return -1; +		} +		if(WEXITSTATUS(status)) +		{ +			Com_Printf(S_COLOR_RED "as failed with status %d\n", WEXITSTATUS(status)); +			return -1; +		} +	} + +	Com_Printf("done\n"); + +	mem = (unsigned char*)mmapfile(out, &size); +	if(!mem) +	{ +		Com_Printf(S_COLOR_RED "can't mmap object file %s: %s\n", out, strerror(errno)); +		return -1; +	} + +	*compiledcode = mem; + +	return size; +} + +static void block_copy_vm(unsigned dest, unsigned src, unsigned count) +{ +	unsigned dataMask = currentVM->dataMask; + +	if ((dest & dataMask) != dest +	|| (src & dataMask) != src +	|| ((dest+count) & dataMask) != dest + count +	|| ((src+count) & dataMask) != src + count) +	{ +		Com_Error(ERR_DROP, "OP_BLOCK_COPY out of range!\n"); +	} + +	memcpy(currentVM->dataBase+dest, currentVM->dataBase+src, count); +} + +/* +================= +VM_Compile +================= +*/ +void VM_Compile( vm_t *vm, vmHeader_t *header ) { +	unsigned char op; +	int pc; +	unsigned instruction; +	char* code; +	unsigned iarg = 0; +	unsigned char barg = 0; +	void* entryPoint; + +	char fn_s[2*MAX_QPATH]; // output file for assembler code +	char fn_o[2*MAX_QPATH]; // file written by as +#ifdef DEBUG_VM +	char fn_d[MAX_QPATH]; // disassembled +#endif +	FILE* fh_s; +	int fd_s, fd_o; +	byte* compiledcode; +	int   compiledsize; + +	Com_Printf("compiling %s\n", vm->name); + +#ifdef DEBUG_VM +	snprintf(fn_s, sizeof(fn_s), "%.63s.s", vm->name); +	snprintf(fn_o, sizeof(fn_o), "%.63s.o", vm->name); +	fd_s = open(fn_s, O_CREAT|O_WRONLY, 0644); +	fd_o = open(fn_o, O_CREAT|O_WRONLY, 0644); +#else +	snprintf(fn_s, sizeof(fn_s), "/tmp/%.63s.s_XXXXXX", vm->name); +	snprintf(fn_o, sizeof(fn_o), "/tmp/%.63s.o_XXXXXX", vm->name); +	fd_s = mkstemp(fn_s); +	fd_o = mkstemp(fn_o); +#endif +	if(fd_s == -1 || fd_o == -1) +	{ +		if(fd_s != -1) close(fd_s); +		if(fd_o != -1) close(fd_o); +		unlink(fn_s); +		unlink(fn_o); + +		Com_Printf(S_COLOR_RED "can't create temporary file %s for vm\n", fn_s); +		vm->compiled = qfalse; +		return; +	} + +#ifdef DEBUG_VM +	strcpy(fn_d,vm->name); +	strcat(fn_d, ".qdasm"); + +	qdasmout = fopen(fn_d, "w"); +#endif + +	fh_s = fdopen(fd_s, "wb"); +	if(!fh_s) +	{ +		Com_Printf(S_COLOR_RED "can't write %s\n", fn_s); +		vm->compiled = qfalse; +		return; +	} + +	// translate all instructions +	pc = 0; +	code = (char *)header + header->codeOffset; + +	emit("start:"); +	emit("or %%r8, %%r8"); // check whether to set up instruction pointers +	emit("jnz main"); +	emit("jmp setupinstructionpointers"); + +	emit("main:"); + +	for ( instruction = 0; instruction < header->instructionCount; ++instruction ) +	{ +		op = code[ pc ]; +		++pc; + +		vm->instructionPointers[instruction] = pc; + +#if 0 +		emit("nop"); +		emit("movq $%d, %%r15", instruction); +		emit("nop"); +#endif + +		if(op_argsize[op] == 4) +		{ +			iarg = *(int*)(code+pc); +			pc += 4; +			Dfprintf(qdasmout, "%s %8u\n", opnames[op], iarg); +		} +		else if(op_argsize[op] == 1) +		{ +			barg = code[pc++]; +			Dfprintf(qdasmout, "%s %8hhu\n", opnames[op], barg); +		} +		else +		{ +			Dfprintf(qdasmout, "%s\n", opnames[op]); +		} +		emit("i_%08x:", instruction); +		switch ( op ) +		{ +			case OP_UNDEF: +				NOTIMPL(op); +				break; +			case OP_IGNORE: +				emit("nop"); +				break; +			case OP_BREAK: +				emit("int3"); +				break; +			case OP_ENTER: +				emit("subl $%d, %%edi", iarg); +				RANGECHECK(edi); +				break; +			case OP_LEAVE: +				emit("addl $%d, %%edi", iarg);          // get rid of stack frame +				emit("ret"); +				break; +			case OP_CALL: +				emit("movl 0(%%rsi), %%eax");  // get instr from stack +				emit("subq $4, %%rsi"); +				emit("movl $%d, 0(%%r8, %%rdi, 1)", instruction+1);  // save next instruction +				emit("orl %%eax, %%eax"); +				emit("jl callSyscall%d", instruction); +				emit("movq $%lu, %%rbx", (unsigned long)vm->instructionPointers); +				emit("movl (%%rbx, %%rax, 4), %%eax"); // load new relative jump address +				emit("addq %%r10, %%rax"); +				emit("callq *%%rax"); +				emit("jmp i_%08x", instruction+1); +				emit("callSyscall%d:", instruction); +//				emit("fnsave 4(%%rsi)"); +				emit("push %%rsi"); +				emit("push %%rdi"); +				emit("push %%r8"); +				emit("push %%r9"); +				emit("push %%r10"); +				emit("movq %%rsp, %%rbx"); // we need to align the stack pointer +				emit("subq $8, %%rbx");    //   | +				emit("andq $127, %%rbx");  //   | +				emit("subq %%rbx, %%rsp"); // <-+ +				emit("push %%rbx"); +				emit("negl %%eax");        // convert to actual number +				emit("decl %%eax"); +				                           // first argument already in rdi +				emit("movq %%rax, %%rsi"); // second argument in rsi +				emit("movq $%lu, %%rax", (unsigned long)callAsmCall); +				emit("callq *%%rax"); +				emit("pop %%rbx"); +				emit("addq %%rbx, %%rsp"); +				emit("pop %%r10"); +				emit("pop %%r9"); +				emit("pop %%r8"); +				emit("pop %%rdi"); +				emit("pop %%rsi"); +//				emit("frstor 4(%%rsi)"); +				emit("addq $4, %%rsi"); +				emit("movl %%eax, (%%rsi)"); // store return value +				break; +			case OP_PUSH: +				emit("addq $4, %%rsi"); +				break; +			case OP_POP: +				emit("subq $4, %%rsi"); +				break; +			case OP_CONST: +				emit("addq $4, %%rsi"); +				emit("movl $%d, 0(%%rsi)", iarg); +				break; +			case OP_LOCAL: +				emit("movl %%edi, %%ebx"); +				emit("addl $%d,%%ebx", iarg); +				emit("addq $4, %%rsi"); +				emit("movl %%ebx, 0(%%rsi)"); +				break; +			case OP_JUMP: +				emit("movl 0(%%rsi), %%eax"); // get instr from stack +				emit("subq $4, %%rsi"); +				emit("movq $%lu, %%rbx", (unsigned long)vm->instructionPointers); +				emit("movl (%%rbx, %%rax, 4), %%eax"); // load new relative jump address +				emit("addq %%r10, %%rax"); +				emit("jmp *%%rax"); +				break; +			case OP_EQ: +				IJ("jne"); +				break; +			case OP_NE: +				IJ("je"); +				break; +			case OP_LTI: +				IJ("jnl"); +				break; +			case OP_LEI: +				IJ("jnle"); +				break; +			case OP_GTI: +				IJ("jng"); +				break; +			case OP_GEI: +				IJ("jnge"); +				break; +			case OP_LTU: +				IJ("jnb"); +				break; +			case OP_LEU: +				IJ("jnbe"); +				break; +			case OP_GTU: +				IJ("jna"); +				break; +			case OP_GEU: +				IJ("jnae"); +				break; +			case OP_EQF: +				FJ(0x40, "jz"); +				XJ("jnz"); +				break; +			case OP_NEF: +				FJ(0x40, "jnz"); +#ifndef USE_X87 +				emit("subq $8, %%rsi"); +				emit("movss 4(%%rsi), %%xmm0"); +				emit("ucomiss 8(%%rsi), %%xmm0"); +				emit("jp dojump_i_%08x", instruction); +				emit("jz i_%08x", instruction+1); +				emit("dojump_i_%08x:", instruction); +				emit("jmp i_%08x", iarg); +#endif +				break; +			case OP_LTF: +				FJ(0x01, "jz"); +				XJ("jnc"); +				break; +			case OP_LEF: +				FJ(0x41, "jz"); +				XJ("ja"); +				break; +			case OP_GTF: +				FJ(0x41, "jnz"); +				XJ("jbe"); +				break; +			case OP_GEF: +				FJ(0x01, "jnz"); +				XJ("jb"); +				break; +			case OP_LOAD1: +				emit("movl 0(%%rsi), %%eax"); // get value from stack +				RANGECHECK(eax); +				emit("movb 0(%%r8, %%rax, 1), %%al"); // deref into eax +				emit("andq $255, %%rax"); +				emit("movl %%eax, 0(%%rsi)"); // store on stack +				break; +			case OP_LOAD2: +				emit("movl 0(%%rsi), %%eax"); // get value from stack +				RANGECHECK(eax); +				emit("movw 0(%%r8, %%rax, 1), %%ax"); // deref into eax +				emit("movl %%eax, 0(%%rsi)"); // store on stack +				break; +			case OP_LOAD4: +				emit("movl 0(%%rsi), %%eax"); // get value from stack +				RANGECHECK(eax); // not a pointer!? +				emit("movl 0(%%r8, %%rax, 1), %%eax"); // deref into eax +				emit("movl %%eax, 0(%%rsi)"); // store on stack +				break; +			case OP_STORE1: +				emit("movl 0(%%rsi), %%eax"); // get value from stack +				emit("andq $255, %%rax"); +				emit("movl -4(%%rsi), %%ebx"); // get pointer from stack +				RANGECHECK(ebx); +				emit("movb %%al, 0(%%r8, %%rbx, 1)"); // store in memory +				emit("subq $8, %%rsi"); +				break; +			case OP_STORE2: +				emit("movl 0(%%rsi), %%eax"); // get value from stack +				emit("movl -4(%%rsi), %%ebx"); // get pointer from stack +				RANGECHECK(ebx); +				emit("movw %%ax, 0(%%r8, %%rbx, 1)"); // store in memory +				emit("subq $8, %%rsi"); +				break; +			case OP_STORE4: +				emit("movl -4(%%rsi), %%ebx"); // get pointer from stack +				RANGECHECK(ebx); +				emit("movl 0(%%rsi), %%ecx"); // get value from stack +				emit("movl %%ecx, 0(%%r8, %%rbx, 1)"); // store in memory +				emit("subq $8, %%rsi"); +				break; +			case OP_ARG: +				emit("subq $4, %%rsi"); +				emit("movl 4(%%rsi), %%eax"); // get value from stack +				emit("movl $0x%hhx, %%ebx", barg); +				emit("addl %%edi, %%ebx"); +				RANGECHECK(ebx); +				emit("movl %%eax, 0(%%r8,%%rbx, 1)"); // store in args space +				break; +			case OP_BLOCK_COPY: + +				emit("subq $8, %%rsi"); +				emit("push %%rsi"); +				emit("push %%rdi"); +				emit("push %%r8"); +				emit("push %%r9"); +				emit("push %%r10"); +				emit("movl 4(%%rsi), %%edi");  // 1st argument dest +				emit("movl 8(%%rsi), %%esi");  // 2nd argument src +				emit("movl $%d, %%edx", iarg); // 3rd argument count +				emit("movq $%lu, %%rax", (unsigned long)block_copy_vm); +				emit("callq *%%rax"); +				emit("pop %%r10"); +				emit("pop %%r9"); +				emit("pop %%r8"); +				emit("pop %%rdi"); +				emit("pop %%rsi"); + +				break; +			case OP_SEX8: +				emit("movw 0(%%rsi), %%ax"); +				emit("andq $255, %%rax"); +				emit("cbw"); +				emit("cwde"); +				emit("movl %%eax, 0(%%rsi)"); +				break; +			case OP_SEX16: +				emit("movw 0(%%rsi), %%ax"); +				emit("cwde"); +				emit("movl %%eax, 0(%%rsi)"); +				break; +			case OP_NEGI: +				emit("negl 0(%%rsi)"); +				break; +			case OP_ADD: +				SIMPLE("addl"); +				break; +			case OP_SUB: +				SIMPLE("subl"); +				break; +			case OP_DIVI: +				emit("subq $4, %%rsi"); +				emit("movl 0(%%rsi), %%eax"); +				emit("cdq"); +				emit("idivl 4(%%rsi)"); +				emit("movl %%eax, 0(%%rsi)"); +				break; +			case OP_DIVU: +				emit("subq $4, %%rsi"); +				emit("movl 0(%%rsi), %%eax"); +				emit("xorq %%rdx, %%rdx"); +				emit("divl 4(%%rsi)"); +				emit("movl %%eax, 0(%%rsi)"); +				break; +			case OP_MODI: +				emit("subq $4, %%rsi"); +				emit("movl 0(%%rsi), %%eax"); +				emit("xorl %%edx, %%edx"); +				emit("cdq"); +				emit("idivl 4(%%rsi)"); +				emit("movl %%edx, 0(%%rsi)"); +				break; +			case OP_MODU: +				emit("subq $4, %%rsi"); +				emit("movl 0(%%rsi), %%eax"); +				emit("xorl %%edx, %%edx"); +				emit("divl 4(%%rsi)"); +				emit("movl %%edx, 0(%%rsi)"); +				break; +			case OP_MULI: +				emit("subq $4, %%rsi"); +				emit("movl 0(%%rsi), %%eax"); +				emit("imull 4(%%rsi)"); +				emit("movl %%eax, 0(%%rsi)"); +				break; +			case OP_MULU: +				emit("subq $4, %%rsi"); +				emit("movl 0(%%rsi), %%eax"); +				emit("mull 4(%%rsi)"); +				emit("movl %%eax, 0(%%rsi)"); +				break; +			case OP_BAND: +				SIMPLE("andl"); +				break; +			case OP_BOR: +				SIMPLE("orl"); +				break; +			case OP_BXOR: +				SIMPLE("xorl"); +				break; +			case OP_BCOM: +				emit("notl 0(%%rsi)"); +				break; +			case OP_LSH: +				SHIFT("shl"); +				break; +			case OP_RSHI: +				SHIFT("sarl"); +				break; +			case OP_RSHU: +				SHIFT("shrl"); +				break; +			case OP_NEGF: +#ifdef USE_X87 +				emit("flds 0(%%rsi)"); +				emit("fchs"); +				emit("fstps 0(%%rsi)"); +#else +				emit("movl $0x80000000, %%eax"); +				emit("xorl %%eax, 0(%%rsi)"); +#endif +				break; +			case OP_ADDF: +				FSIMPLE("fadds"); +				XSIMPLE("addss"); +				break; +			case OP_SUBF: +				FSIMPLE("fsubs"); +				XSIMPLE("subss"); +				break; +			case OP_DIVF: +				FSIMPLE("fdivs"); +				XSIMPLE("divss"); +				break; +			case OP_MULF: +				FSIMPLE("fmuls"); +				XSIMPLE("mulss"); +				break; +			case OP_CVIF: +#ifdef USE_X87 +				emit("filds 0(%%rsi)"); +				emit("fstps 0(%%rsi)"); +#else +				emit("movl 0(%%rsi), %%eax"); +				emit("cvtsi2ss %%eax, %%xmm0"); +				emit("movss %%xmm0, 0(%%rsi)"); +#endif +				break; +			case OP_CVFI: +#ifdef USE_X87 +				emit("flds 0(%%rsi)"); +				emit("fnstcw 4(%%rsi)"); +				emit("movw $0x0F7F, 8(%%rsi)"); // round toward zero +				emit("fldcw 8(%%rsi)"); +				emit("fistpl 0(%%rsi)"); +				emit("fldcw 4(%%rsi)"); +#else +				emit("movss 0(%%rsi), %%xmm0"); +				emit("cvttss2si %%xmm0, %%eax"); +				emit("movl %%eax, 0(%%rsi)"); +#endif +				break; +			default: +				NOTIMPL(op); +				break; +		} +	} + + +	emit("setupinstructionpointers:"); +	emit("movq $%lu, %%rax", (unsigned long)vm->instructionPointers); +	for ( instruction = 0; instruction < header->instructionCount; ++instruction ) +	{ +		emit("movl $i_%08x-start, %d(%%rax)", instruction, instruction*4); +	} +	emit("ret"); + +	emit("debugger:"); +	if(1); +	{ +		int i = 6; +		while(i--) +		{ +			emit("nop"); +			emit("int3"); +		} +	} + +	fflush(fh_s); +	fclose(fh_s); + +	compiledsize = doas(fn_s, fn_o, &compiledcode); +	if(compiledsize == -1) +	{ +		vm->compiled = qfalse; +		goto out; +	} + +	vm->codeBase   = compiledcode; // remember to skip ELF header! +	vm->codeLength = compiledsize; + +	vm->destroy = VM_Destroy_Compiled; +	 +	entryPoint = getentrypoint(vm); + +//	__asm__ __volatile__ ("int3"); +	Com_Printf("computing jump table\n"); + +	// call code with r8 set to zero to set up instruction pointers +	__asm__ __volatile__ ( +		"	xorq %%r8,%%r8		\r\n" \ +		"	movq %0,%%r10		\r\n" \ +		"	callq *%%r10		\r\n" \ +		: +		: "m" (entryPoint) +		: "%r8", "%r10", "%rax" +	); + +#ifdef DEBUG_VM +	fflush(qdasmout); +	fclose(qdasmout); +#endif + +	Com_Printf( "VM file %s compiled to %i bytes of code (%p - %p)\n", vm->name, vm->codeLength, vm->codeBase, vm->codeBase+vm->codeLength ); + +out: +	close(fd_o); + +#ifndef DEBUG_VM +	if(!com_developer->integer) +	{ +		unlink(fn_o); +		unlink(fn_s); +	} +#endif +} + + +void VM_Destroy_Compiled(vm_t* self) +{ +	munmap(self->codeBase, self->codeLength); +} + +/* +============== +VM_CallCompiled + +This function is called directly by the generated code +============== +*/ + +#ifdef DEBUG_VM +static char* memData; +#endif + +int	VM_CallCompiled( vm_t *vm, int *args ) { +	int		programCounter; +	int		programStack; +	int		stackOnEntry; +	byte	*image; +	void	*entryPoint; +	void	*opStack; +	int stack[1024] = { 0xDEADBEEF }; + +	currentVM = vm; + +	++vm->callLevel; +//	Com_Printf("entering %s level %d, call %d, arg1 = 0x%x\n", vm->name, vm->callLevel, args[0], args[1]); + +	// interpret the code +	vm->currentlyInterpreting = qtrue; + +//	callMask = vm->dataMask; + +	// we might be called recursively, so this might not be the very top +	programStack = vm->programStack; +	stackOnEntry = programStack; + +	// set up the stack frame  +	image = vm->dataBase; +#ifdef DEBUG_VM +	memData = (char*)image; +#endif + +	programCounter = 0; + +	programStack -= 48; + +	*(int *)&image[ programStack + 44] = args[9]; +	*(int *)&image[ programStack + 40] = args[8]; +	*(int *)&image[ programStack + 36] = args[7]; +	*(int *)&image[ programStack + 32] = args[6]; +	*(int *)&image[ programStack + 28] = args[5]; +	*(int *)&image[ programStack + 24] = args[4]; +	*(int *)&image[ programStack + 20] = args[3]; +	*(int *)&image[ programStack + 16] = args[2]; +	*(int *)&image[ programStack + 12] = args[1]; +	*(int *)&image[ programStack + 8 ] = args[0]; +	*(int *)&image[ programStack + 4 ] = 0x77777777;	// return stack +	*(int *)&image[ programStack ] = -1;	// will terminate the loop on return + +	// off we go into generated code... +	entryPoint = getentrypoint(vm); +	opStack = &stack; + +	__asm__ __volatile__ ( +		"	movq %5,%%rsi		\r\n" \ +		"	movl %4,%%edi		\r\n" \ +		"	movq %2,%%r10		\r\n" \ +		"	movq %3,%%r8		\r\n" \ +		"       subq $24, %%rsp # fix alignment as call pushes one value \r\n" \ +		"	callq *%%r10		\r\n" \ +		"       addq $24, %%rsp          \r\n" \ +		"	movl %%edi, %0		\r\n" \ +		"	movq %%rsi, %1		\r\n" \ +		: "=m" (programStack), "=m" (opStack) +		: "m" (entryPoint), "m" (vm->dataBase), "m" (programStack), "m" (opStack) +		: "%rsi", "%rdi", "%rax", "%rbx", "%rcx", "%rdx", "%r8", "%r10", "%r15", "%xmm0" +	); + +	if ( opStack != &stack[1] ) { +		Com_Error( ERR_DROP, "opStack corrupted in compiled code (offset %ld)\n", (long int) ((void *) &stack[1] - opStack)); +	} +	if ( programStack != stackOnEntry - 48 ) { +		Com_Error( ERR_DROP, "programStack corrupted in compiled code\n" ); +	} + +//	Com_Printf("exiting %s level %d\n", vm->name, vm->callLevel); +	--vm->callLevel; +	vm->programStack = stackOnEntry; + +	return *(int *)opStack; +}  | 
