diff options
Diffstat (limited to 'src/qcommon')
45 files changed, 44677 insertions, 0 deletions
diff --git a/src/qcommon/cm_load.c b/src/qcommon/cm_load.c new file mode 100644 index 0000000..230b18b --- /dev/null +++ b/src/qcommon/cm_load.c @@ -0,0 +1,1024 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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 ) { +	union { +		int				*i; +		void			*v; +	} 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, &buf.v ); +#else +	length = LoadQuakeFile((quakefile_t *) name, &buf.v); +#endif + +	if ( !buf.i ) { +		Com_Error (ERR_DROP, "Couldn't load %s", name); +	} + +	last_checksum = LittleLong (Com_BlockChecksum (buf.i, length)); +	*checksum = last_checksum; + +	header = *(dheader_t *)buf.i; +	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.i; + +	// 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.v); + +	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..8b93b9f --- /dev/null +++ b/src/qcommon/cm_local.h @@ -0,0 +1,216 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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 ); +qboolean CM_BoundsIntersect( const vec3_t mins, const vec3_t maxs, const vec3_t mins2, const vec3_t maxs2 ); +qboolean CM_BoundsIntersectPoint( const vec3_t mins, const vec3_t maxs, const vec3_t point ); + +// 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..55e2762 --- /dev/null +++ b/src/qcommon/cm_patch.c @@ -0,0 +1,1773 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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, (void *)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 ( !CM_BoundsIntersect( tw->bounds[0], tw->bounds[1], +				pc->bounds[0], pc->bounds[1] ) ) { +		return; +	} + +	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..2470339 --- /dev/null +++ b/src/qcommon/cm_patch.h @@ -0,0 +1,104 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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..03fd47d --- /dev/null +++ b/src/qcommon/cm_polylib.c @@ -0,0 +1,738 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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..b1b2a48 --- /dev/null +++ b/src/qcommon/cm_polylib.h @@ -0,0 +1,69 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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..123fee3 --- /dev/null +++ b/src/qcommon/cm_public.h @@ -0,0 +1,84 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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..5e520e4 --- /dev/null +++ b/src/qcommon/cm_test.c @@ -0,0 +1,525 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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]; +	} + +	if(leaf->area == -1) +		return CONTENTS_SOLID; + +	contents = 0; +	for (k=0 ; k<leaf->numLeafBrushes ; k++) { +		brushnum = cm.leafbrushes[leaf->firstLeafBrush+k]; +		b = &cm.brushes[brushnum]; + +		if ( !CM_BoundsIntersectPoint( b->bounds[0], b->bounds[1], p ) ) { +			continue; +		} + +		// 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; +} + +/* +==================== +CM_BoundsIntersect +==================== +*/ +qboolean CM_BoundsIntersect( const vec3_t mins, const vec3_t maxs, const vec3_t mins2, const vec3_t maxs2 ) +{ +	if (maxs[0] < mins2[0] - SURFACE_CLIP_EPSILON || +		maxs[1] < mins2[1] - SURFACE_CLIP_EPSILON || +		maxs[2] < mins2[2] - SURFACE_CLIP_EPSILON || +		mins[0] > maxs2[0] + SURFACE_CLIP_EPSILON || +		mins[1] > maxs2[1] + SURFACE_CLIP_EPSILON || +		mins[2] > maxs2[2] + SURFACE_CLIP_EPSILON) +	{ +		return qfalse; +	} + +	return qtrue; +} + +/* +==================== +CM_BoundsIntersectPoint +==================== +*/ +qboolean CM_BoundsIntersectPoint( const vec3_t mins, const vec3_t maxs, const vec3_t point ) +{ +	if (maxs[0] < point[0] - SURFACE_CLIP_EPSILON || +		maxs[1] < point[1] - SURFACE_CLIP_EPSILON || +		maxs[2] < point[2] - SURFACE_CLIP_EPSILON || +		mins[0] > point[0] + SURFACE_CLIP_EPSILON || +		mins[1] > point[1] + SURFACE_CLIP_EPSILON || +		mins[2] > point[2] + SURFACE_CLIP_EPSILON) +	{ +		return qfalse; +	} + +	return qtrue; +} diff --git a/src/qcommon/cm_trace.c b/src/qcommon/cm_trace.c new file mode 100644 index 0000000..6c4751f --- /dev/null +++ b/src/qcommon/cm_trace.c @@ -0,0 +1,1793 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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]) { // 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]) { // 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) { +	floatint_t 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; + +		if ( !CM_BoundsIntersect( tw->bounds[0], tw->bounds[1], +					b->bounds[0], b->bounds[1] ) ) { +			continue; +		} + +		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 { +			// 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 // 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..8ea761e --- /dev/null +++ b/src/qcommon/cmd.c @@ -0,0 +1,878 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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 ) ); +		if ( cmd_wait < 0 ) +			cmd_wait = 1; // ignore the argument +	} 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) { +			Com_DPrintf(S_COLOR_YELLOW "EXEC_NOW %s\n", text); +			Cmd_ExecuteString (text); +		} else { +			Cbuf_Execute(); +			Com_DPrintf(S_COLOR_YELLOW "EXEC_NOW %s\n", cmd_text.data); +		} +		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 > 0 ) { +			// 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 ) { +	union { +		char	*c; +		void	*v; +	} 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, &f.v); +	if (!f.c) { +		Com_Printf ("couldn't exec %s\n",Cmd_Argv(1)); +		return; +	} +	Com_Printf ("execing %s\n",Cmd_Argv(1)); +	 +	Cbuf_InsertText (f.c); + +	FS_FreeFile (f.v); +} + + +/* +=============== +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) +{ +	Com_Printf ("%s\n", Cmd_Args()); +} + + +/* +============================================================================= + +					COMMAND EXECUTION + +============================================================================= +*/ + +typedef struct cmd_function_s +{ +	struct cmd_function_s	*next; +	char					*name; +	xcommand_t				function; +	completionFunc_t	complete; +} 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; +} + +/* +   Replace command separators with space to prevent interpretation +   This is a hack to protect buggy qvms +   https://bugzilla.icculus.org/show_bug.cgi?id=3593 +   https://bugzilla.icculus.org/show_bug.cgi?id=4769 +*/ + +void Cmd_Args_Sanitize(void) +{ +	int i; + +	for(i = 1; i < cmd.argc; i++) +	{ +		char *c = cmd.argv[i]; +		 +		if(strlen(c) > MAX_CVAR_VALUE_STRING - 1) +			c[MAX_CVAR_VALUE_STRING - 1] = '\0'; +		 +		while ((c = strpbrk(c, "\n\r;"))) { +			*c = ' '; +			++c; +		} +	} +} + +/* +============ +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_FindCommand +============ +*/ +cmd_function_t *Cmd_FindCommand( const char *cmd_name ) +{ +	cmd_function_t *cmd; +	for( cmd = cmd_functions; cmd; cmd = cmd->next ) +		if( !Q_stricmp( cmd_name, cmd->name ) ) +			return cmd; +	return NULL; +} + +/* +============ +Cmd_AddCommand +============ +*/ +void	Cmd_AddCommand( const char *cmd_name, xcommand_t function ) { +	cmd_function_t	*cmd; +	 +	// fail if the command already exists +	if( Cmd_FindCommand( 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->complete = NULL; +	cmd->next = cmd_functions; +	cmd_functions = cmd; +} + +/* +============ +Cmd_SetCommandCompletionFunc +============ +*/ +void Cmd_SetCommandCompletionFunc( const char *command, completionFunc_t complete ) { +	cmd_function_t	*cmd; + +	for( cmd = cmd_functions; cmd; cmd = cmd->next ) { +		if( !Q_stricmp( command, cmd->name ) ) { +			cmd->complete = complete; +		} +	} +} + +/* +============ +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_RemoveCommandSafe + +Only remove commands with no associated function +============ +*/ +void Cmd_RemoveCommandSafe( const char *cmd_name ) +{ +	cmd_function_t *cmd = Cmd_FindCommand( cmd_name ); + +	if( !cmd ) +		return; +	if( cmd->function ) +	{ +		Com_Error( ERR_DROP, "Restricted source tried to remove " +			"system command \"%s\"\n", cmd_name ); +		return; +	} + +	Cmd_RemoveCommand( cmd_name ); +} + +/* +============ +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_CompleteArgument +============ +*/ +void Cmd_CompleteArgument( const char *command, char *args, int argNum ) { +	cmd_function_t	*cmd; + +	for( cmd = cmd_functions; cmd; cmd = cmd->next ) { +		if( !Q_stricmp( command, cmd->name ) && cmd->complete ) { +			cmd->complete( args, argNum ); +		} +	} +} + + +/* +============ +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_CompleteCfgName +================== +*/ +void Cmd_CompleteCfgName( char *args, int argNum ) { +	if( argNum == 2 ) { +		Field_CompleteFilename( "", "cfg", qfalse, qtrue ); +	} +} + +/* +============ +Cmd_Init +============ +*/ +void Cmd_Init (void) { +	Cmd_AddCommand ("cmdlist",Cmd_List_f); +	Cmd_AddCommand ("exec",Cmd_Exec_f); +	Cmd_SetCommandCompletionFunc( "exec", Cmd_CompleteCfgName ); +	Cmd_AddCommand ("vstr",Cmd_Vstr_f); +	Cmd_SetCommandCompletionFunc( "vstr", Cvar_CompleteCvarName ); +	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..91f9dba --- /dev/null +++ b/src/qcommon/common.c @@ -0,0 +1,3326 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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[] = +{ PROTOCOL_VERSION, 0 }; + +#define MAX_NUM_ARGVS	50 + +#define MIN_DEDICATED_COMHUNKMEGS 1 +#define MIN_COMHUNKMEGS		256 +#define DEF_COMHUNKMEGS		256 +#define DEF_COMZONEMEGS		24 +#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 pipefile; +static fileHandle_t logfile; +fileHandle_t	com_journalFile;			// events are written here +fileHandle_t	com_journalDataFile;		// config files are written here + +cvar_t	*com_speeds; +cvar_t	*com_developer; +cvar_t	*com_dedicated; +cvar_t	*com_timescale; +cvar_t	*com_fixedtime; +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_pipefile; +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; +cvar_t	*com_ansiColor; +cvar_t	*com_unfocused; +cvar_t	*com_maxfpsUnfocused; +cvar_t	*com_minimized; +cvar_t	*com_maxfpsMinimized; +cvar_t	*com_abnormalExit; +cvar_t  *com_homepath; +cvar_t	*com_busyWait; + +// com_speeds times +int		time_game; +int		time_frontend;		// renderer frontend time +int		time_backend;		// renderer backend time + +int			com_frameTime; +int			com_frameNumber; + +qboolean	com_errorEntered = qfalse; +qboolean	com_fullyInitialized = qfalse; +qboolean	com_gameRestarting = qfalse; + +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; +	} + +#ifndef DEDICATED +	CL_ConsolePrint( msg ); +#endif + +	Q_StripIndentMarker( 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 appropriate thing. +============= +*/ +void QDECL Com_Error( int code, const char *fmt, ... ) { +	va_list		argptr; +	static int	lastErrorTime; +	static int	errorCount; +	static qboolean	calledSysError = qfalse; +	int			currentTime; + +	if(com_errorEntered) +	{ +		if(!calledSysError) +		{ +			calledSysError = qtrue; +			Sys_Error("recursive error after: %s", com_errorMessage); +		} +		 +		return; +	} + +	com_errorEntered = qtrue; + +	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; + +	va_start (argptr,fmt); +	Q_vsnprintf (com_errorMessage, sizeof(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) { +		VM_Forced_Unload_Start(); +		SV_Shutdown( "Server disconnected" ); +		CL_Disconnect( qtrue ); +		CL_FlushMemory( ); +		VM_Forced_Unload_Done(); +		// 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); +		VM_Forced_Unload_Start(); +		SV_Shutdown (va("Server crashed: %s",  com_errorMessage)); +		CL_Disconnect( qtrue ); +		CL_FlushMemory( ); +		VM_Forced_Unload_Done(); +		FS_PureServerSetLoadedPaks("", ""); +		com_errorEntered = qfalse; +		longjmp (abortframe, -1); +	} else if ( code == ERR_NEED_CD ) { +		VM_Forced_Unload_Start(); +		SV_Shutdown( "Server didn't have CD" ); +		if ( com_cl_running && com_cl_running->integer ) { +			CL_Disconnect( qtrue ); +			CL_FlushMemory( ); +			VM_Forced_Unload_Done(); +			CL_CDDialog(); +		} else { +			Com_Printf("Server didn't have CD\n" ); +			VM_Forced_Unload_Done(); +		} + +		FS_PureServerSetLoadedPaks("", ""); + +		com_errorEntered = qfalse; +		longjmp (abortframe, -1); +	} else { +		VM_Forced_Unload_Start(); +		CL_Shutdown (va("Client fatal crashed: %s", com_errorMessage)); +		SV_Shutdown (va("Server fatal crashed: %s", com_errorMessage)); +		VM_Forced_Unload_Done(); +	} + +	Com_Shutdown (); + +	calledSysError = qtrue; +	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 +	char *p = Cmd_Args( ); +	if ( !com_errorEntered ) { +		SV_Shutdown (p[0] ? p : "Server quit"); +		CL_Shutdown (p[0] ? p : "Client quit"); +		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; + +	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)) +		{ +			if(Cvar_Flags(s) == CVAR_NONEXISTENT) +				Cvar_Get(s, Cmd_Argv(2), CVAR_USER_CREATED); +			else +				Cvar_Set(s, Cmd_Argv(2)); +		} +	} +} + + +/* +================= +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 already added with Com_StartupVariable +		if ( !Q_stricmpn( com_consoleLines[i], "set", 3 ) ) { +			continue; +		} + +		added = qtrue; +		Cbuf_AddText( com_consoleLines[i] ); +		Cbuf_AddText( "\n" ); +	} + +	return added; +} + + +//============================================================================ + +void Info_Print( const char *s ) { +	char	key[BIG_INFO_KEY]; +	char	value[BIG_INFO_VALUE]; +	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_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; + +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", +				(void *)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; +	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; + +	// Please note: com_zoneMegs can only be set on the command line, and +	// not in q3config.cfg or Com_StartupVariable, as they haven't been +	// executed by this point. It's a chicken and egg problem. We need the +	// memory manager configured to handle those places where you would +	// configure the memory manager. + +	// 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; +	} + +	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; +	} + +	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 +=================================================================== +*/ + +#define	MAX_PUSHED_EVENTS	            1024 +static int com_pushedEventsHead = 0; +static int com_pushedEventsTail = 0; +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" ); +	} +} + +/* +======================================================================== + +EVENT LOOP + +======================================================================== +*/ + +#define MAX_QUEUED_EVENTS  256 +#define MASK_QUEUED_EVENTS ( MAX_QUEUED_EVENTS - 1 ) + +static sysEvent_t  eventQueue[ MAX_QUEUED_EVENTS ]; +static int         eventHead = 0; +static int         eventTail = 0; + +/* +================ +Com_QueueEvent + +A time of 0 will get the current time +Ptr should either be null, or point to a block of data that can +be freed by the game later. +================ +*/ +void Com_QueueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ) +{ +	sysEvent_t  *ev; + +	ev = &eventQueue[ eventHead & MASK_QUEUED_EVENTS ]; + +	if ( eventHead - eventTail >= MAX_QUEUED_EVENTS ) +	{ +		Com_Printf("Com_QueueEvent: overflow\n"); +		// we are discarding an event, but don't leak memory +		if ( ev->evPtr ) +		{ +			Z_Free( ev->evPtr ); +		} +		eventTail++; +	} + +	eventHead++; + +	if ( time == 0 ) +	{ +		time = Sys_Milliseconds(); +	} + +	ev->evTime = time; +	ev->evType = type; +	ev->evValue = value; +	ev->evValue2 = value2; +	ev->evPtrLength = ptrLength; +	ev->evPtr = ptr; +} + +/* +================ +Com_GetSystemEvent + +================ +*/ +sysEvent_t Com_GetSystemEvent( void ) +{ +	sysEvent_t  ev; +	char        *s; + +	// return if we have data +	if ( eventHead > eventTail ) +	{ +		eventTail++; +		return eventQueue[ ( eventTail - 1 ) & MASK_QUEUED_EVENTS ]; +	} + +	// check for console commands +	s = Sys_ConsoleInput(); +	if ( s ) +	{ +		char  *b; +		int   len; + +		len = strlen( s ) + 1; +		b = Z_Malloc( len ); +		strcpy( b, s ); +		Com_QueueEvent( 0, SE_CONSOLE, 0, 0, len, b ); +	} + +	// return if we have data +	if ( eventHead > eventTail ) +	{ +		eventTail++; +		return eventQueue[ ( eventTail - 1 ) & MASK_QUEUED_EVENTS ]; +	} + +	// create an empty event to return +	memset( &ev, 0, sizeof( ev ) ); +	ev.evTime = Sys_Milliseconds(); + +	return ev; +} + +/* +================= +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 = Com_GetSystemEvent(); + +		// 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 +================= +*/ +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; + +	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 ) { +		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) +		{ +			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; +			default: +				Com_Error( ERR_FATAL, "Com_EventLoop: bad event type %i", ev.evType ); +			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; +} + +/* +================== +Com_Setenv_f + +For controlling environment variables +================== +*/ +void Com_Setenv_f(void) +{ +	int argc = Cmd_Argc(); +	char *arg1 = Cmd_Argv(1); + +	if(argc > 2) +	{ +		char *arg2 = Cmd_ArgsFrom(2); +		 +		Sys_SetEnv(arg1, arg2); +	} +	else if(argc == 2) +	{ +		char *env = getenv(arg1); +		 +		if(env) +			Com_Printf("%s=%s\n", arg1, env); +		else +			Com_Printf("%s undefined\n", arg1); +        } +} + +/* +================== +Com_ExecuteCfg + +For controlling environment variables +================== +*/ + +void Com_ExecuteCfg(void) +{ +	Cbuf_ExecuteText(EXEC_NOW, "exec default.cfg\n"); +	Cbuf_Execute(); // Always execute after exec to prevent text buffer overflowing + +	if(!Com_SafeMode()) +	{ +		// skip the q3config.cfg and autoexec.cfg if "safe" is on the command line +		Cbuf_ExecuteText(EXEC_NOW, "exec " Q3CONFIG_CFG "\n"); +		Cbuf_Execute(); +		Cbuf_ExecuteText(EXEC_NOW, "exec autoexec.cfg\n"); +		Cbuf_Execute(); +	} +} + +/* +================== +Com_GameRestart + +Change to a new mod properly with cleaning up cvars before switching. +================== +*/ + +void Com_GameRestart(int checksumFeed, qboolean clientRestart) +{ +	// make sure no recursion can be triggered +	if(!com_gameRestarting && com_fullyInitialized) +	{ +		com_gameRestarting = qtrue; +		 +		if(clientRestart) +		{ +			CL_Disconnect(qfalse); +			CL_ShutdownAll(); +		} + +		// Kill server if we have one +		if(com_sv_running->integer) +			SV_Shutdown("Game directory changed"); + +		FS_Restart(checksumFeed); +	 +		// Clean out any user and VM created cvars +		Cvar_Restart(qtrue); +		Com_ExecuteCfg(); +		 +		// Restart sound subsystem so old handles are flushed +		CL_Snd_Restart(); + +		if(clientRestart) +			CL_StartHunkUsers(qfalse); +		 +		com_gameRestarting = qfalse; +	} +} + +/* +================== +Com_GameRestart_f + +Expose possibility to change current running mod to the user +================== +*/ + +void Com_GameRestart_f(void) +{ +	Cvar_Set("fs_game", Cmd_Argv(1)); + +	Com_GameRestart(0, qtrue); +} + +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_GetProcessorFeatures( ) & CF_ALTIVEC ); +			detected = qtrue; +		} + +		if (!altivec) { +			Cvar_Set( "com_altivec", "0" );  // we don't have it! Disable support! +		} +	} +} + +/* +================= +Com_InitRand +Seed the random number generator, if possible with an OS supplied random seed. +================= +*/ +static void Com_InitRand(void) +{ +	unsigned int seed; + +	if(Sys_RandomBytes((byte *) &seed, sizeof(seed))) +		srand(seed); +	else +		srand(time(NULL)); +} + +/* +================= +Com_Init +================= +*/ +void Com_Init( char *commandLine ) { +	char	*s; +	int	qport; + +	Com_Printf( "%s %s %s\n", Q3_VERSION, PLATFORM_STRING, __DATE__ ); + +	if ( setjmp (abortframe) ) { +		Sys_Error ("Error during initialization"); +	} + +	// Clear queues +	Com_Memset( &eventQueue[ 0 ], 0, MAX_QUEUED_EVENTS * sizeof( sysEvent_t ) ); + +	// initialize the weak pseudo-random number generator for use later. +	Com_InitRand(); + +	// 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 (); + +	// override anything from the config files with command line args +	Com_StartupVariable( NULL ); + +	Com_InitZoneMemory(); +	Cmd_Init (); + +	// get the developer cvar set as early as possible +	com_developer = Cvar_Get("developer", "0", CVAR_TEMP); + +	// done early so bind command exists +	CL_InitKeyCommands(); + +	com_homepath = Cvar_Get("com_homepath", "", CVAR_INIT); +	 +	// Com_StartupVariable( +	FS_InitFilesystem (); + +	Com_InitJournaling(); + +	// Add some commands here already so users can use them from config files +	Cmd_AddCommand ("setenv", Com_Setenv_f); +	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 ); +	Cmd_SetCommandCompletionFunc( "writeconfig", Cmd_CompleteCfgName ); +	Cmd_AddCommand("game_restart", Com_GameRestart_f); + +	Com_ExecuteCfg(); + +	// 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_INIT); +	Cvar_CheckRange( com_dedicated, 1, 2, qtrue ); +#else +	com_dedicated = Cvar_Get ("dedicated", "0", CVAR_LATCH); +	Cvar_CheckRange( com_dedicated, 0, 2, qtrue ); +#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_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_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 ); +	com_ansiColor = Cvar_Get( "com_ansiColor", "0", CVAR_ARCHIVE ); + +	com_unfocused = Cvar_Get( "com_unfocused", "0", CVAR_ROM ); +	com_maxfpsUnfocused = Cvar_Get( "com_maxfpsUnfocused", "0", CVAR_ARCHIVE ); +	com_minimized = Cvar_Get( "com_minimized", "0", CVAR_ROM ); +	com_maxfpsMinimized = Cvar_Get( "com_maxfpsMinimized", "0", CVAR_ARCHIVE ); +	com_abnormalExit = Cvar_Get( "com_abnormalExit", "0", CVAR_ROM ); +	com_busyWait = Cvar_Get("com_busyWait", "0", CVAR_ARCHIVE); + +	s = va("%s %s %s", Q3_VERSION, PLATFORM_STRING, __DATE__ ); +	com_version = Cvar_Get ("version", s, CVAR_ROM | CVAR_SERVERINFO ); +	Cvar_Get ("protocol", va("%i", PROTOCOL_VERSION), CVAR_SERVERINFO | CVAR_ROM); + +	Sys_Init(); + +	if( Sys_WritePIDFile( ) ) { +#ifndef DEDICATED +		const char *message = "The last time " CLIENT_WINDOW_TITLE " ran, " +			"it didn't exit properly. This may be due to inappropriate video " +			"settings. Would you like to start with \"safe\" video settings?"; + +		if( Sys_Dialog( DT_YES_NO, message, "Abnormal Exit" ) == DR_YES ) { +			Cvar_Set( "com_abnormalExit", "1" ); +		} +#endif +	} + +	// Pick a random port value +	Com_RandomBytes( (byte*)&qport, sizeof(int) ); +	Netchan_Init( qport & 0xffff ); + +	VM_Init(); +	SV_Init(); + +	com_dedicated->modified = qfalse; +#ifndef DEDICATED +	CL_Init(); +#endif + +	// 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( qfalse ); + +	// 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_pipefile = Cvar_Get( "com_pipefile", "", CVAR_ARCHIVE|CVAR_LATCH ); +	if( com_pipefile->string[0] ) +	{ +		pipefile = FS_FCreateOpenPipeFile( com_pipefile->string ); +	} + +	Com_Printf ("--- Common Initialization Complete ---\n"); +} + +/* +=============== +Com_ReadFromPipe + +Read whatever is in com_pipefile, if anything, and execute it +=============== +*/ +void Com_ReadFromPipe( void ) +{ +	char buffer[MAX_STRING_CHARS] = {""}; +	qboolean read; + +	if( !pipefile ) +		return; + +	read = FS_Read( buffer, sizeof( buffer ), pipefile ); +	if( read ) +		Cbuf_ExecuteText( EXEC_APPEND, buffer ); +} + + +//================================================================== + +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( Q3CONFIG_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; +	int		timeVal; +	static int	lastTime = 0, bias = 0; +  +	int		timeBeforeFirstEvents; +	int		timeBeforeServer; +	int		timeBeforeEvents; +	int		timeBeforeClient; +	int		timeAfter; +   + +	if ( setjmp (abortframe) ) { +		return;			// an ERR_DROP was thrown +	} + +	timeBeforeFirstEvents =0; +	timeBeforeServer =0; +	timeBeforeEvents =0; +	timeBeforeClient = 0; +	timeAfter = 0; + +	// write config file if anything changed +	Com_WriteConfiguration();  + +	// +	// main event loop +	// +	if ( com_speeds->integer ) { +		timeBeforeFirstEvents = Sys_Milliseconds (); +	} + +	// Figure out how much time we have +	if(!com_timedemo->integer) +	{ +		if(com_dedicated->integer) +			minMsec = SV_FrameMsec(); +		else +		{ +			if(com_minimized->integer && com_maxfpsMinimized->integer > 0) +				minMsec = 1000 / com_maxfpsMinimized->integer; +			else if(com_unfocused->integer && com_maxfpsUnfocused->integer > 0) +				minMsec = 1000 / com_maxfpsUnfocused->integer; +			else if(com_maxfps->integer > 0) +				minMsec = 1000 / com_maxfps->integer; +			else +				minMsec = 1; +			 +			timeVal = com_frameTime - lastTime; +			bias += timeVal - minMsec; +			 +			if(bias > minMsec) +				bias = minMsec; +			 +			// Adjust minMsec if previous frame took too long to render so +			// that framerate is stable at the requested value. +			minMsec -= bias; +		} +	} +	else +		minMsec = 1; + +	timeVal = 0; +	do +	{ +		// Busy sleep the last millisecond for better timeout precision +		if(com_busyWait->integer || timeVal < 2) +			NET_Sleep(0); +		else +			NET_Sleep(timeVal - 1); + +		msec = Sys_Milliseconds() - com_frameTime; +		 +		if(msec >= minMsec) +			timeVal = 0; +		else +			timeVal = minMsec - msec; + +	} while(timeVal > 0); +	 +	lastTime = com_frameTime; +	com_frameTime = Com_EventLoop(); +	 +	msec = com_frameTime - lastTime; + +	Cbuf_Execute (); + +	if (com_altivec->modified) +	{ +		Com_DetectAltivec(); +		com_altivec->modified = qfalse; +	} + +	// mess with msec if needed +	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 ) { +			SV_Shutdown( "dedicated set to 0" ); +			CL_FlushMemory(); +		} +	} + +#ifndef DEDICATED +	// +	// client system +	// +	// +	// 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 (); +	} +#else +	if ( com_speeds->integer ) { +		timeAfter = Sys_Milliseconds (); +		timeBeforeEvents = timeAfter; +		timeBeforeClient = timeAfter; +	} +#endif + + +	NET_FlushPacketQueue(); + +	// +	// 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; +	} + +	Com_ReadFromPipe( ); + +	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; +	} + +	if( pipefile ) { +		FS_FCloseFile( pipefile ); +		FS_HomeRemove( com_pipefile->string ); +	} + +} + +//------------------------------------------------------------------------ + + +/* +===================== +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_Complete +=============== +*/ +static qboolean Field_Complete( void ) +{ +	int completionOffset; + +	if( matchCount == 0 ) +		return qtrue; + +	completionOffset = strlen( completionField->buffer ) - strlen( completionString ); + +	Q_strncpyz( &completionField->buffer[ completionOffset ], shortestMatch, +		sizeof( completionField->buffer ) - completionOffset ); + +	completionField->cursor = strlen( completionField->buffer ); + +	if( matchCount == 1 ) +	{ +		Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " ); +		completionField->cursor++; +		return qtrue; +	} + +	Com_Printf( "]%s\n", completionField->buffer ); + +	return qfalse; +} + +#ifndef DEDICATED +/* +=============== +Field_CompleteKeyname +=============== +*/ +void Field_CompleteKeyname( void ) +{ +	matchCount = 0; +	shortestMatch[ 0 ] = 0; + +	Key_KeynameCompletion( FindMatches ); + +	if( !Field_Complete( ) ) +		Key_KeynameCompletion( PrintMatches ); +} +#endif + +/* +=============== +Field_CompleteFilename +=============== +*/ +void Field_CompleteFilename( const char *dir, +		const char *ext, qboolean stripExt, qboolean allowNonPureFilesOnDisk ) +{ +	matchCount = 0; +	shortestMatch[ 0 ] = 0; + +	FS_FilenameCompletion( dir, ext, stripExt, FindMatches, allowNonPureFilesOnDisk ); + +	if( !Field_Complete( ) ) +		FS_FilenameCompletion( dir, ext, stripExt, PrintMatches, allowNonPureFilesOnDisk ); +} + +/* +=============== +Field_CompleteCommand +=============== +*/ +void Field_CompleteCommand( char *cmd, +		qboolean doCommands, qboolean doCvars ) +{ +	int		completionArgument = 0; + +	// 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 ); + +#ifndef DEDICATED +	// Unconditionally add a '\' to the start of the buffer +	if( completionField->buffer[ 0 ] && +			completionField->buffer[ 0 ] != '\\' ) +	{ +		if( completionField->buffer[ 0 ] != '/' ) +		{ +			// Buffer is full, refuse to complete +			if( strlen( completionField->buffer ) + 1 >= +				sizeof( completionField->buffer ) ) +				return; + +			memmove( &completionField->buffer[ 1 ], +				&completionField->buffer[ 0 ], +				strlen( completionField->buffer ) + 1 ); +			completionField->cursor++; +		} + +		completionField->buffer[ 0 ] = '\\'; +	} +#endif + +	if( completionArgument > 1 ) +	{ +		const char *baseCmd = Cmd_Argv( 0 ); +		char *p; + +#ifndef DEDICATED +		// This should always be true +		if( baseCmd[ 0 ] == '\\' || baseCmd[ 0 ] == '/' ) +			baseCmd++; +#endif + +		if( ( p = Field_FindFirstSeparator( cmd ) ) ) +			Field_CompleteCommand( p + 1, qtrue, qtrue ); // Compound command +		else +			Cmd_CompleteArgument( baseCmd, cmd, completionArgument );  +	} +	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( !Field_Complete( ) ) +		{ +			// 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" ); +	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..8db30e6 --- /dev/null +++ b/src/qcommon/cvar.c @@ -0,0 +1,1321 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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 = NULL; +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) +	{ +		if(cvar->name) +			callback(cvar->name); +	} +} + +/* +============ +Cvar_Validate +============ +*/ +static const char *Cvar_Validate( cvar_t *var, +    const char *value, qboolean warn ) +{ +	static char s[ MAX_CVAR_VALUE_STRING ]; +	float valuef; +	qboolean changed = qfalse; + +	if( !var->validate ) +		return value; + +	if( !value ) +		return value; + +	if( Q_isanumber( value ) ) +	{ +		valuef = atof( value ); + +		if( var->integral ) +		{ +			if( !Q_isintegral( valuef ) ) +			{ +				if( warn ) +					Com_Printf( "WARNING: cvar '%s' must be integral", var->name ); + +				valuef = (int)valuef; +				changed = qtrue; +			} +		} +	} +	else +	{ +		if( warn ) +			Com_Printf( "WARNING: cvar '%s' must be numeric", var->name ); + +		valuef = atof( var->resetString ); +		changed = qtrue; +	} + +	if( valuef < var->min ) +	{ +		if( warn ) +		{ +			if( changed ) +				Com_Printf( " and is" ); +			else +				Com_Printf( "WARNING: cvar '%s'", var->name ); + +			if( Q_isintegral( var->min ) ) +				Com_Printf( " out of range (min %d)", (int)var->min ); +			else +				Com_Printf( " out of range (min %f)", var->min ); +		} + +		valuef = var->min; +		changed = qtrue; +	} +	else if( valuef > var->max ) +	{ +		if( warn ) +		{ +			if( changed ) +				Com_Printf( " and is" ); +			else +				Com_Printf( "WARNING: cvar '%s'", var->name ); + +			if( Q_isintegral( var->max ) ) +				Com_Printf( " out of range (max %d)", (int)var->max ); +			else +				Com_Printf( " out of range (max %f)", var->max ); +		} + +		valuef = var->max; +		changed = qtrue; +	} + +	if( changed ) +	{ +		if( Q_isintegral( valuef ) ) +		{ +			Com_sprintf( s, sizeof( s ), "%d", (int)valuef ); + +			if( warn ) +				Com_Printf( ", setting to %d\n", (int)valuef ); +		} +		else +		{ +			Com_sprintf( s, sizeof( s ), "%f", valuef ); + +			if( warn ) +				Com_Printf( ", setting to %f\n", valuef ); +		} + +		return s; +	} +	else +		return value; +} + + +/* +============ +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; +	int	index; + +	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) +	{ +		var_value = Cvar_Validate(var, var_value, qfalse); + +		// 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) +		{ +			var->flags &= ~CVAR_USER_CREATED; +			Z_Free( var->resetString ); +			var->resetString = CopyString( var_value ); + +			if(flags & CVAR_ROM) +			{ +				// this variable was set by the user, +				// so force it to value given by the engine. + +				if(var->latchedString) +					Z_Free(var->latchedString); +				 +				var->latchedString = CopyString(var_value); +			} +		} +		 +		// Make sure the game code cannot mark engine-added variables as gamecode vars +		if(var->flags & CVAR_VM_CREATED) +		{ +			if(!(flags & CVAR_VM_CREATED)) +				var->flags &= ~CVAR_VM_CREATED; +		} +		else +		{ +			if(flags & CVAR_VM_CREATED) +				flags &= ~CVAR_VM_CREATED; +		} +		 +		// Make sure servers cannot mark engine-added variables as SERVER_CREATED +		if(var->flags & CVAR_SERVER_CREATED) +		{ +			if(!(flags & CVAR_SERVER_CREATED)) +				var->flags &= ~CVAR_SERVER_CREATED; +		} +		else +		{ +			if(flags & CVAR_SERVER_CREATED) +				flags &= ~CVAR_SERVER_CREATED; +		} +		 +		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 ); +		} + +		// ZOID--needs to be set so that cvars the game sets as  +		// SERVERINFO get sent to clients +		cvar_modifiedFlags |= flags; + +		return var; +	} + +	// +	// allocate a new cvar +	// + +	// find a free cvar +	for(index = 0; index < MAX_CVARS; index++) +	{ +		if(!cvar_indexes[index].name) +			break; +	} + +	if(index >= MAX_CVARS) +	{ +		if(!com_errorEntered) +			Com_Error(ERR_FATAL, "Error: Too many cvars, cannot create a new one!"); + +		return NULL; +	} +	 +	var = &cvar_indexes[index]; +	 +	if(index >= cvar_numIndexes) +		cvar_numIndexes = index + 1; +		 +	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 ); +	var->validate = qfalse; + +	// link the variable in +	var->next = cvar_vars; +	if(cvar_vars) +		cvar_vars->prev = var; + +	var->prev = NULL; +	cvar_vars = var; + +	var->flags = flags; +	// note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo) +	cvar_modifiedFlags |= var->flags; + +	hash = generateHashValue(var_name); +	var->hashIndex = hash; + +	var->hashNext = hashTable[hash]; +	if(hashTable[hash]) +		hashTable[hash]->hashPrev = var; + +	var->hashPrev = NULL; +	hashTable[hash] = var; + +	return var; +} + +/* +============ +Cvar_Print + +Prints the value, default, and latched string of the given variable +============ +*/ +void Cvar_Print( cvar_t *v ) { +	Com_Printf ("\"%s\" is:\"%s" S_COLOR_WHITE "\"", +			v->name, v->string ); + +	if ( !( v->flags & CVAR_ROM ) ) { +		if ( !Q_stricmp( v->string, v->resetString ) ) { +			Com_Printf (", the default" ); +		} else { +			Com_Printf (" default:\"%s" S_COLOR_WHITE "\"", +					v->resetString ); +		} +	} + +	Com_Printf ("\n"); + +	if ( v->latchedString ) { +		Com_Printf( "latched: \"%s\"\n", v->latchedString ); +	} +} + +/* +============ +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; +	} + +	value = Cvar_Validate(var, value, qtrue); + +	if((var->flags & CVAR_LATCH) && var->latchedString) +	{ +		if(!strcmp(value, var->string)) +		{ +			Z_Free(var->latchedString); +			var->latchedString = NULL; +			return var; +		} + +		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_SetSafe +============ +*/ +void Cvar_SetSafe( const char *var_name, const char *value ) +{ +	int flags = Cvar_Flags( var_name ); + +	if( flags != CVAR_NONEXISTENT && flags & CVAR_PROTECTED ) +	{ +		if( value ) +			Com_Error( ERR_DROP, "Restricted source tried to set " +				"\"%s\" to \"%s\"\n", var_name, value ); +		else +			Com_Error( ERR_DROP, "Restricted source tried to " +				"modify \"%s\"\n", var_name ); +		return; +	} +	Cvar_Set( var_name, value ); +} + +/* +============ +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_SetValueSafe +============ +*/ +void Cvar_SetValueSafe( const char *var_name, float value ) +{ +	char val[32]; + +	if( Q_isintegral( value ) ) +		Com_sprintf( val, sizeof(val), "%i", (int)value ); +	else +		Com_sprintf( val, sizeof(val), "%f", value ); +	Cvar_SetSafe( 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; + +	// check variables +	v = Cvar_FindVar (Cmd_Argv(0)); +	if (!v) { +		return qfalse; +	} + +	// perform a variable print or set +	if ( Cmd_Argc() == 1 ) { +		Cvar_Print( v ); +		return qtrue; +	} + +	// set the value if forcing isn't required +	Cvar_Set2 (v->name, Cmd_Args(), qfalse); +	return qtrue; +} + + +/* +============ +Cvar_Print_f + +Prints the contents of a cvar  +(preferred over Cvar_Command where cvar names and commands conflict) +============ +*/ +void Cvar_Print_f(void) +{ +	char *name; +	cvar_t *cv; +	 +	if(Cmd_Argc() != 2) +	{ +		Com_Printf ("usage: print <variable>\n"); +		return; +	} + +	name = Cmd_Argv(1); + +	cv = Cvar_FindVar(name); +	 +	if(cv) +		Cvar_Print(cv); +	else +		Com_Printf ("Cvar %s does not exist.\n", name); +} + +/* +============ +Cvar_Toggle_f + +Toggles a cvar for easy single key binding, optionally through a list of +given values +============ +*/ +void Cvar_Toggle_f( void ) { +	int		i, c = Cmd_Argc(); +	char		*curval; + +	if(c < 2) { +		Com_Printf("usage: toggle <variable> [value1, value2, ...]\n"); +		return; +	} + +	if(c == 2) { +		Cvar_Set2(Cmd_Argv(1), va("%d",  +			!Cvar_VariableValue(Cmd_Argv(1))),  +			qfalse); +		return; +	} + +	if(c == 3) { +		Com_Printf("toggle: nothing to toggle to\n"); +		return; +	} + +	curval = Cvar_VariableString(Cmd_Argv(1)); + +	// don't bother checking the last arg for a match since the desired +	// behaviour is the same as no match (set to the first argument) +	for(i = 2; i + 1 < c; i++) { +		if(strcmp(curval, Cmd_Argv(i)) == 0) { +			Cvar_Set2(Cmd_Argv(1), Cmd_Argv(i + 1), qfalse); +			return; +		} +	} + +	// fallback +	Cvar_Set2(Cmd_Argv(1), Cmd_Argv(2), 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		c; +	char	*cmd; +	cvar_t	*v; + +	c = Cmd_Argc(); +	cmd = Cmd_Argv(0); + +	if ( c < 2 ) { +		Com_Printf ("usage: %s <variable> <value>\n", cmd); +		return; +	} +	if ( c == 2 ) { +		Cvar_Print_f(); +		return; +	} + +	v = Cvar_Set2 (Cmd_Argv(1), Cmd_ArgsFrom(2), qfalse); +	if( !v ) { +		return; +	} +	switch( cmd[3] ) { +		case 'a': +			if( !( v->flags & CVAR_ARCHIVE ) ) { +				v->flags |= CVAR_ARCHIVE; +				cvar_modifiedFlags |= CVAR_ARCHIVE; +			} +			break; +		case 'u': +			if( !( v->flags & CVAR_USERINFO ) ) { +				v->flags |= CVAR_USERINFO; +				cvar_modifiedFlags |= CVAR_USERINFO; +			} +			break; +		case 's': +			if( !( v->flags & CVAR_SERVERINFO ) ) { +				v->flags |= CVAR_SERVERINFO; +				cvar_modifiedFlags |= CVAR_SERVERINFO; +			} +			break; +	} +} + +/* +============ +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->name) +			continue; + +		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(!var->name || (match && !Com_Filter(match, var->name, qfalse))) +			continue; + +		if (var->flags & CVAR_SERVERINFO) { +			Com_Printf("S"); +		} else { +			Com_Printf(" "); +		} +		if (var->flags & CVAR_SYSTEMINFO) { +			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(" "); +		} +		if (var->flags & CVAR_USER_CREATED) { +			Com_Printf("?"); +		} 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_Unset + +Unsets a cvar +============ +*/ + +cvar_t *Cvar_Unset(cvar_t *cv) +{ +	cvar_t *next = cv->next; + +	if(cv->name) +		Z_Free(cv->name); +	if(cv->string) +		Z_Free(cv->string); +	if(cv->latchedString) +		Z_Free(cv->latchedString); +	if(cv->resetString) +		Z_Free(cv->resetString); + +	if(cv->prev) +		cv->prev->next = cv->next; +	else +		cvar_vars = cv->next; +	if(cv->next) +		cv->next->prev = cv->prev; + +	if(cv->hashPrev) +		cv->hashPrev->hashNext = cv->hashNext; +	else +		hashTable[cv->hashIndex] = cv->hashNext; +	if(cv->hashNext) +		cv->hashNext->hashPrev = cv->hashPrev; + +	Com_Memset(cv, '\0', sizeof(*cv)); +	 +	return next; +} + +/* +============ +Cvar_Unset_f + +Unsets a userdefined cvar +============ +*/ + +void Cvar_Unset_f(void) +{ +	cvar_t *cv; +	 +	if(Cmd_Argc() != 2) +	{ +		Com_Printf("Usage: %s <varname>\n", Cmd_Argv(0)); +		return; +	} +	 +	cv = Cvar_FindVar(Cmd_Argv(1)); + +	if(!cv) +		return; +	 +	if(cv->flags & CVAR_USER_CREATED) +		Cvar_Unset(cv); +	else +		Com_Printf("Error: %s: Variable %s is not user created.\n", Cmd_Argv(0), cv->name); +} + + + +/* +============ +Cvar_Restart + +Resets all cvars to their hardcoded values and removes userdefined variables +and variables added via the VMs if requested. +============ +*/ + +void Cvar_Restart(qboolean unsetVM) +{ +	cvar_t	*curvar; + +	curvar = cvar_vars; + +	while(curvar) +	{ +		if((curvar->flags & CVAR_USER_CREATED) || +			(unsetVM && (curvar->flags & CVAR_VM_CREATED))) +		{ +			// throw out any variables the user/vm created +			curvar = Cvar_Unset(curvar); +			continue; +		} +		 +		if(!(curvar->flags & (CVAR_ROM | CVAR_INIT | CVAR_NORESTART))) +		{ +			// Just reset the rest to their default values. +			Cvar_Set2(curvar->name, curvar->resetString, qfalse); +		} +		 +		curvar = curvar->next; +	} +} + + +/* +============ +Cvar_Restart_f + +Resets all cvars to their hardcoded values +============ +*/ +void Cvar_Restart_f(void) +{ +	Cvar_Restart(qfalse); +} + +/* +===================== +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->name && (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->name && (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_CheckRange +===================== +*/ +void Cvar_CheckRange( cvar_t *var, float min, float max, qboolean integral ) +{ +	var->validate = qtrue; +	var->min = min; +	var->max = max; +	var->integral = integral; + +	// Force an initial range check +	Cvar_Set( var->name, var->string ); +} + +/* +===================== +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; + +	// There is code in Cvar_Get to prevent CVAR_ROM cvars being changed by the +	// user. In other words CVAR_ARCHIVE and CVAR_ROM are mutually exclusive +	// flags. Unfortunately some historical game code (including single player +	// baseq3) sets both flags. We unset CVAR_ROM for such cvars. +	if ((flags & (CVAR_ARCHIVE | CVAR_ROM)) == (CVAR_ARCHIVE | CVAR_ROM)) { +		Com_DPrintf( S_COLOR_YELLOW "WARNING: Unsetting CVAR_ROM cvar '%s', " +			"since it is also CVAR_ARCHIVE\n", varName ); +		flags &= ~CVAR_ROM; +	} + +	cv = Cvar_Get(varName, defaultValue, flags | CVAR_VM_CREATED); + +	if (!vmCvar) +		return; + +	vmCvar->handle = cv - cvar_indexes; +	vmCvar->modificationCount = -1; +	Cvar_Update( vmCvar ); +} + + +/* +===================== +Cvar_Update + +updates an interpreted modules' version of a cvar +===================== +*/ +void	Cvar_Update( vmCvar_t *vmCvar ) { +	cvar_t	*cv = NULL; +	assert(vmCvar); + +	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; +	if ( strlen(cv->string)+1 > MAX_CVAR_VALUE_STRING )  +	  Com_Error( ERR_DROP, "Cvar_Update: src %s length %u exceeds MAX_CVAR_VALUE_STRING", +		     cv->string,  +		     (unsigned int) strlen(cv->string)); +	Q_strncpyz( vmCvar->string, cv->string,  MAX_CVAR_VALUE_STRING );  + +	vmCvar->value = cv->value; +	vmCvar->integer = cv->integer; +} + +/* +================== +Cvar_CompleteCvarName +================== +*/ +void Cvar_CompleteCvarName( char *args, int argNum ) +{ +	if( argNum == 2 ) +	{ +		// Skip "<cmd> " +		char *p = Com_SkipTokens( args, 1, " " ); + +		if( p > args ) +			Field_CompleteCommand( p, qfalse, qtrue ); +	} +} + +/* +============ +Cvar_Init + +Reads in all archived cvars +============ +*/ +void Cvar_Init (void) +{ +	Com_Memset(cvar_indexes, '\0', sizeof(cvar_indexes)); +	Com_Memset(hashTable, '\0', sizeof(hashTable)); + +	cvar_cheats = Cvar_Get("sv_cheats", "1", CVAR_ROM | CVAR_SYSTEMINFO ); + +	Cmd_AddCommand ("print", Cvar_Print_f); +	Cmd_AddCommand ("toggle", Cvar_Toggle_f); +	Cmd_SetCommandCompletionFunc( "toggle", Cvar_CompleteCvarName ); +	Cmd_AddCommand ("set", Cvar_Set_f); +	Cmd_SetCommandCompletionFunc( "set", Cvar_CompleteCvarName ); +	Cmd_AddCommand ("sets", Cvar_Set_f); +	Cmd_SetCommandCompletionFunc( "sets", Cvar_CompleteCvarName ); +	Cmd_AddCommand ("setu", Cvar_Set_f); +	Cmd_SetCommandCompletionFunc( "setu", Cvar_CompleteCvarName ); +	Cmd_AddCommand ("seta", Cvar_Set_f); +	Cmd_SetCommandCompletionFunc( "seta", Cvar_CompleteCvarName ); +	Cmd_AddCommand ("reset", Cvar_Reset_f); +	Cmd_SetCommandCompletionFunc( "reset", Cvar_CompleteCvarName ); +	Cmd_AddCommand ("unset", Cvar_Unset_f); +	Cmd_SetCommandCompletionFunc("unset", Cvar_CompleteCvarName); + +	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..b85a1af --- /dev/null +++ b/src/qcommon/files.c @@ -0,0 +1,3525 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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 "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. + +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 + +============================================================================= + +*/ + +// 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 +	unsigned long			len;		// uncompress file size +	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; + +#ifdef MACOS_X +// Also search the .app bundle for .pk3 files +static  cvar_t          *fs_apppath; +#endif + +static	cvar_t		*fs_basepath; +static	cvar_t		*fs_basegame; +static	cvar_t		*fs_gamedirvar; +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 = 0;		// total number of files in packs + +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 = 0; +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 + +/* C99 defines __func__ */ +#ifndef __func__ +#define __func__ "(unknown)" +#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 its 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 range" ); +	} +	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; +	qboolean lastCharWasSep = qfalse; + +	for ( s = path ; *s ; s++ ) { +		if ( *s == '/' || *s == '\\' ) { +			if ( !lastCharWasSep ) { +				*s = PATH_SEP; +				lastCharWasSep = qtrue; +			} else { +				memmove (s, s + 1, strlen (s)); +			} +		} else { +			lastCharWasSep = qfalse; +		} +	} +} + +/* +=================== +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 +============ +*/ +qboolean FS_CreatePath (char *OSPath) { +	char	*ofs; +	char	path[MAX_OSPATH]; +	 +	// 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; +	} + +	Q_strncpyz( path, OSPath, sizeof( path ) ); +	FS_ReplaceSeparators( path ); + +	// Skip creation of the root directory as it will always be there +	ofs = strchr( path, PATH_SEP ); +	ofs++; + +	for (; ofs != NULL && *ofs ; ofs++) { +		if (*ofs == PATH_SEP) { +			// create the directory +			*ofs = 0; +			if (!Sys_Mkdir (path)) { +				Com_Error( ERR_FATAL, "FS_CreatePath: failed to create path \"%s\"\n", +					path ); +			} +			*ofs = PATH_SEP; +		} +	} + +	return qfalse; +} + +/* +================= +FS_CheckFilenameIsNotExecutable + +ERR_FATAL if trying to maniuplate a file with the platform library extension +================= + */ +static void FS_CheckFilenameIsNotExecutable( const char *filename, +		const char *function ) +{ +	// Check if the filename ends with the library extension +	if( !Q_stricmp( COM_GetExtension( filename ), DLL_EXT ) ) +	{ +		Com_Error( ERR_FATAL, "%s: Not allowed to manipulate '%s' due " +			"to %s extension\n", function, filename, DLL_EXT ); +	} +} + +/* +=========== +FS_Remove + +=========== +*/ +void FS_Remove( const char *osPath ) { +	FS_CheckFilenameIsNotExecutable( osPath, __func__ ); + +	remove( osPath ); +} + +/* +=========== +FS_HomeRemove + +=========== +*/ +void FS_HomeRemove( const char *homePath ) { +	FS_CheckFilenameIsNotExecutable( homePath, __func__ ); + +	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 ); +	} + +	FS_CheckFilenameIsNotExecutable( ospath, __func__ ); + +	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 then base path +in that 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) +	{ +		// If fs_homepath == fs_basepath, don't bother +		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; +		} +	} + +	*fp = f; +	if (f) { +		return FS_filelength(f); +	} + +	return -1; +} + + +/* +=========== +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 ); +	} + +	FS_CheckFilenameIsNotExecutable( to_ospath, __func__ ); + +	rename(from_ospath, to_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 ); +	} + +	FS_CheckFilenameIsNotExecutable( to_ospath, __func__ ); + +	rename(from_ospath, to_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].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 ); +	} + +	FS_CheckFilenameIsNotExecutable( ospath, __func__ ); + +	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 ); +	} + +	FS_CheckFilenameIsNotExecutable( ospath, __func__ ); + +	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_FCreateOpenPipeFile + +=========== +*/ +fileHandle_t FS_FCreateOpenPipeFile( const char *filename ) { +	char	    		*ospath; +	FILE					*fifo; +	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_FCreateOpenPipeFile: %s\n", ospath ); +	} + +	FS_CheckFilenameIsNotExecutable( ospath, __func__ ); + +	fifo = Sys_Mkfifo( ospath ); +	if( fifo ) { +		fsh[f].handleFiles.file.o = fifo; +		fsh[f].handleSync = qfalse; +	} +	else +	{ +		Com_Printf( S_COLOR_YELLOW "WARNING: Could not create new com_pipefile at %s. " +			"com_pipefile will not be used.\n", ospath ); +		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_IsExt + +Return qtrue if ext matches file extension filename +=========== +*/ + +qboolean FS_IsExt(const char *filename, const char *ext, int namelen) +{ +	int extlen; + +	extlen = strlen(ext); + +	if(extlen > namelen) +		return qfalse; + +	filename += namelen - extlen; + +	return !Q_stricmp(filename, ext); +} + +/* +=========== +FS_IsDemoExt + +Return qtrue if filename has a demo extension +=========== +*/ + +qboolean FS_IsDemoExt(const char *filename, int namelen) +{ +	char *ext_test; +	int index, protocol; + +	ext_test = Q_strrchr(filename, '.'); +	if(ext_test && !Q_stricmpn(ext_test + 1, DEMOEXT, ARRAY_LEN(DEMOEXT) - 1)) +	{ +		protocol = atoi(ext_test + ARRAY_LEN(DEMOEXT)); + +		if(protocol == PROTOCOL_VERSION) +			return qtrue; + +		for(index = 0; demo_protocols[index]; index++) +		{ +			if(demo_protocols[index] == protocol) +			return qtrue; +		} +	} + +	return qfalse; +} + +/* +=========== +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; +	FILE			*temp; +	int				l; + +	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" ); +	} + +	// 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(!FS_IsExt(filename, ".shader", l) && +						   !FS_IsExt(filename, ".txt", l) && +						   !FS_IsExt(filename, ".cfg", l) && +						   !FS_IsExt(filename, ".config", l) && +						   !FS_IsExt(filename, ".bot", l) && +						   !FS_IsExt(filename, ".arena", l) && +						   !FS_IsExt(filename, ".menu", l) && +						   !strstr(filename, "levelshots")) +						{ +							pak->referenced |= FS_GENERAL_REF; +						} +					} + +					if (!(pak->referenced & FS_QAGAME_REF) && strstr(filename, "game.qvm")) { +						pak->referenced |= FS_QAGAME_REF; +					} +					if (!(pak->referenced & FS_CGAME_REF) && strstr(filename, "cgame.qvm")) { +						pak->referenced |= FS_CGAME_REF; +					} +					if (!(pak->referenced & FS_UI_REF) && strstr(filename, "ui.qvm")) { +						pak->referenced |= FS_UI_REF; +					} + +					if ( uniqueFILE ) { +						// open a new file on the pakfile +						fsh[*file].handleFiles.file.z = unzOpen (pak->pakFilename); +						if (fsh[*file].handleFiles.file.z == NULL) { +							Com_Error (ERR_FATAL, "Couldn't open %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; +					// set the file position in the zip file (also sets the current file info) +					unzSetOffset(fsh[*file].handleFiles.file.z, pakFile->pos); +					// 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 pakFile->len; +				} +				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_numServerPaks) +			{ +				if(!FS_IsExt(filename, ".cfg", l) &&		// for config files +				   !FS_IsExt(filename, ".menu", l) &&		// menu files +				   !FS_IsExt(filename, ".game", l) &&		// menu files +				   !FS_IsExt(filename, ".cfg", l) &&		// for journal files +				   !FS_IsDemoExt(filename, l))			// demos +				{ +					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; +			} + +			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 ); +			} + +			return FS_filelength (*file); +		}		 +	} +	 +#ifdef FS_MISSING +	if (missingFiles) { +		fprintf(missingFiles, "%s\n", filename); +	} +#endif +	*file = 0; +	return -1; +} + + +char *FS_FindDll( const char *filename ) { +	searchpath_t	*search; +	directory_t		*dir; + +	if ( !fs_searchpaths ) { +		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); +	} + +	for ( search = fs_searchpaths ; search ; search = search->next ) { +		if ( search->dir ) { +			FILE *f; +			char *netpath; + +			dir = search->dir; +			netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename ); +			f = fopen( netpath, "rb" ); +			if (f) { +				fclose( f ); +				return netpath; +			} +		} +	} + +	return NULL; +} + +/* +================= +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 = FS_Read( buffer, len, 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; +	 	FS_Seek( 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: +				unzSetOffset(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(const 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; + +	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 +		buildBuffer[i].pos = unzGetOffset(uf); +		buildBuffer[i].len = file_info.uncompressed_size; +		buildBuffer[i].next = pack->hashTable[hash]; +		pack->hashTable[hash] = &buildBuffer[i]; +		unzGoToNextFile(uf); +	} + +	pack->checksum = Com_BlockChecksum( &fs_headerLongs[ 1 ], sizeof(*fs_headerLongs) * ( fs_numHeaderLongs - 1 ) ); +	pack->pure_checksum = Com_BlockChecksum( fs_headerLongs, sizeof(*fs_headerLongs) * fs_numHeaderLongs ); +	pack->checksum = LittleLong( pack->checksum ); +	pack->pure_checksum = LittleLong( pack->pure_checksum ); + +	Z_Free(fs_headerLongs); + +	pack->buildBuffer = buildBuffer; +	return pack; +} + +/* +================= +FS_FreePak + +Frees a pak structure and releases all associated resources +================= +*/ + +static void FS_FreePak(pack_t *thepak) +{ +	unzClose(thepak->handle); +	Z_Free(thepak->buildBuffer); +	Z_Free(thepak); +} + +/* +================= +FS_GetZipChecksum + +Compares whether the given pak file matches a referenced checksum +================= +*/ +qboolean FS_CompareZipChecksum(const char *zipfile) +{ +	pack_t *thepak; +	int index, checksum; +	 +	thepak = FS_LoadZipFile(zipfile, ""); +	 +	if(!thepak) +		return qfalse; +	 +	checksum = thepak->checksum; +	FS_FreePak(thepak); +	 +	for(index = 0; index < fs_numServerReferencedPaks; index++) +	{ +		if(checksum == fs_serverReferencedPaks[index]) +			return qtrue; +	} +	 +	return qfalse; +} + +/* +================================================================================= + +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, qboolean allowNonPureFilesOnDisk ) { +	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_numServerPaks && !allowNonPureFilesOnDisk ) { +		        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, qfalse ); +} + +/* +================= +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 ) +{ +	int totalLength = 0; +	char** cat = NULL, **dst, **src; + +	totalLength += Sys_CountFileList(list0); +	totalLength += Sys_CountFileList(list1); + +	/* 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; +	} + +	// 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 ); + +	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; +	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 ); +	// 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 ); +	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 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, qfalse ); + +	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 +============ +*/ +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 ); +	} +} + +/* +============ +FS_Which_f +============ +*/ +void FS_Which_f( void ) { +	searchpath_t	*search; +	char			*netpath; +	pack_t			*pak; +	fileInPack_t	*pakFile; +	directory_t		*dir; +	long			hash; +	FILE			*temp; +	char			*filename; +	char			buf[ MAX_OSPATH ]; + +	hash = 0; +	filename = Cmd_Argv(1); + +	if ( !filename[0] ) { +		Com_Printf( "Usage: which <file>\n" ); +		return; +	} + +	// qpaths are not supposed to have a leading slash +	if ( filename[0] == '/' || filename[0] == '\\' ) { +		filename++; +	} + +	// 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! +					Com_Printf( "File \"%s\" found in \"%s\"\n", filename, pak->pakFilename ); +					return; +				} +				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); +			Com_sprintf( buf, sizeof( buf ), "%s/%s", dir->path, dir->gamedir ); +			FS_ReplaceSeparators( buf ); +			Com_Printf( "File \"%s\" found at \"%s\"\n", filename, buf ); +			return; +		} +	} +	Com_Printf( "File not found: \"%s\"\n", filename ); +	return; +} + + +//=========================================================================== + + +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 +================ +*/ +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; + +	// Unique +	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); +		 +		fs_packFiles += pak->numfiles; + +		search = Z_Malloc (sizeof(searchpath_t)); +		search->pack = pak; +		search->next = fs_searchpaths; +		fs_searchpaths = search; +	} + +	// done +	Sys_FreeFileList( pakfiles ); +} + +/* +================ +FS_CheckDirTraversal + +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; + +		// 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) +			FS_FreePak(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" ); +	Cmd_RemoveCommand( "which" ); + +#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_packFiles = 0; + +	fs_debug = Cvar_Get( "fs_debug", "0", 0 ); +	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", "gpp", CVAR_INIT|CVAR_SYSTEMINFO ); + +	// add search path elements in reverse priority order +	if (fs_basepath->string[0]) { +		FS_AddGameDirectory( fs_basepath->string, gameName ); +	} +	// fs_homepath is somewhat particular to *nix systems, only add if relevant +	 +	#ifdef MACOS_X +	fs_apppath = Cvar_Get ("fs_apppath", Sys_DefaultAppPath(), CVAR_INIT|CVAR_PROTECTED ); +	// Make MacOSX also include the base path included with the .app bundle +	if (fs_apppath->string[0]) +		FS_AddGameDirectory(fs_apppath->string, gameName); +	#endif +	 +	// NOTE: same filtering below for mods and basegame +	if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) { +		FS_CreatePath ( fs_homepath->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( fs_basegame->string, gameName ) ) { +		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( fs_gamedirvar->string, gameName ) ) { +		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 ); +	Cmd_AddCommand ("which", FS_Which_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 game.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++; +			} +		} +	} +	// 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 (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) { +				if (*info) { +					Q_strcat(info, sizeof( info ), " " ); +				} +				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 < ARRAY_LEN(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_basepath" ); +	Com_StartupVariable( "fs_homepath" ); +	Com_StartupVariable( "fs_game" ); + +	// 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" ); +	} + +	Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase)); +	Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame)); +} + + +/* +================ +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'; +			FS_Restart(checksumFeed); +			Com_Error( ERR_DROP, "Invalid game folder\n" ); +			return; +		} +		Com_Error( ERR_FATAL, "Couldn't load default.cfg" ); +	} + +	if ( Q_stricmp(fs_gamedirvar->string, lastValidGame) ) { +		// skip the autogen.cfg if "safe" is on the command line +		if ( !Com_SafeMode() ) { +			Cbuf_AddText ("exec " Q3CONFIG_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) +	{ +		Com_GameRestart(checksumFeed, qfalse); +		return qtrue; +	} + +	else if(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, "FS_FOpenFileByMode: 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) { +			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), qboolean allowNonPureFilesOnDisk ) { +	char	**filenames; +	int		nfiles; +	int		i; +	char	filename[ MAX_STRING_CHARS ]; + +	filenames = FS_ListFilteredFiles( dir, ext, NULL, &nfiles, allowNonPureFilesOnDisk ); + +	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 ); +} + +const char *FS_GetCurrentGameDir(void) +{ +	if(fs_gamedirvar->string[0]) +		return fs_gamedirvar->string; + +	return BASEGAME; +} diff --git a/src/qcommon/huffman.c b/src/qcommon/huffman.c new file mode 100644 index 0000000..8ef702a --- /dev/null +++ b/src/qcommon/huffman.c @@ -0,0 +1,448 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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_getBloc(void) +{ +	return bloc; +} + +void	Huff_setBloc(int _bloc) +{ +	bloc = _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/ioapi.c b/src/qcommon/ioapi.c new file mode 100644 index 0000000..aab3c00 --- /dev/null +++ b/src/qcommon/ioapi.c @@ -0,0 +1,182 @@ +/* ioapi.c -- IO base function header for compress/uncompress .zip +   files using zlib + zip or unzip API + +   Version 1.01e, February 12th, 2005 + +   Copyright (C) 1998-2005 Gilles Vollant +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef USE_LOCAL_HEADERS +#include "../zlib/zlib.h" +#else +#include <zlib.h> +#endif + +#include "ioapi.h" + + + +/* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */ + +#ifndef SEEK_CUR +#define SEEK_CUR    1 +#endif + +#ifndef SEEK_END +#define SEEK_END    2 +#endif + +#ifndef SEEK_SET +#define SEEK_SET    0 +#endif + +voidpf ZCALLBACK fopen_file_func OF(( +   voidpf opaque, +   const char* filename, +   int mode)); + +uLong ZCALLBACK fread_file_func OF(( +   voidpf opaque, +   voidpf stream, +   void* buf, +   uLong size)); + +uLong ZCALLBACK fwrite_file_func OF(( +   voidpf opaque, +   voidpf stream, +   const void* buf, +   uLong size)); + +long ZCALLBACK ftell_file_func OF(( +   voidpf opaque, +   voidpf stream)); + +long ZCALLBACK fseek_file_func OF(( +   voidpf opaque, +   voidpf stream, +   uLong offset, +   int origin)); + +int ZCALLBACK fclose_file_func OF(( +   voidpf opaque, +   voidpf stream)); + +int ZCALLBACK ferror_file_func OF(( +   voidpf opaque, +   voidpf stream)); + + +voidpf ZCALLBACK fopen_file_func (opaque, filename, mode) +   voidpf opaque; +   const char* filename; +   int mode; +{ +    FILE* file = NULL; +    const char* mode_fopen = NULL; +    if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) +        mode_fopen = "rb"; +    else +    if (mode & ZLIB_FILEFUNC_MODE_EXISTING) +        mode_fopen = "r+b"; +    else +    if (mode & ZLIB_FILEFUNC_MODE_CREATE) +        mode_fopen = "wb"; + +    if ((filename!=NULL) && (mode_fopen != NULL)) +        file = fopen(filename, mode_fopen); +    return file; +} + + +uLong ZCALLBACK fread_file_func (opaque, stream, buf, size) +   voidpf opaque; +   voidpf stream; +   void* buf; +   uLong size; +{ +    uLong ret; +    ret = (uLong)fread(buf, 1, (size_t)size, (FILE *)stream); +    return ret; +} + + +uLong ZCALLBACK fwrite_file_func (opaque, stream, buf, size) +   voidpf opaque; +   voidpf stream; +   const void* buf; +   uLong size; +{ +    uLong ret; +    ret = (uLong)fwrite(buf, 1, (size_t)size, (FILE *)stream); +    return ret; +} + +long ZCALLBACK ftell_file_func (opaque, stream) +   voidpf opaque; +   voidpf stream; +{ +    long ret; +    ret = ftell((FILE *)stream); +    return ret; +} + +long ZCALLBACK fseek_file_func (opaque, stream, offset, origin) +   voidpf opaque; +   voidpf stream; +   uLong offset; +   int origin; +{ +    int fseek_origin=0; +    long ret; +    switch (origin) +    { +    case ZLIB_FILEFUNC_SEEK_CUR : +        fseek_origin = SEEK_CUR; +        break; +    case ZLIB_FILEFUNC_SEEK_END : +        fseek_origin = SEEK_END; +        break; +    case ZLIB_FILEFUNC_SEEK_SET : +        fseek_origin = SEEK_SET; +        break; +    default: return -1; +    } +    ret = 0; +    fseek((FILE *)stream, offset, fseek_origin); +    return ret; +} + +int ZCALLBACK fclose_file_func (opaque, stream) +   voidpf opaque; +   voidpf stream; +{ +    int ret; +    ret = fclose((FILE *)stream); +    return ret; +} + +int ZCALLBACK ferror_file_func (opaque, stream) +   voidpf opaque; +   voidpf stream; +{ +    int ret; +    ret = ferror((FILE *)stream); +    return ret; +} + +void fill_fopen_filefunc (pzlib_filefunc_def) +  zlib_filefunc_def* pzlib_filefunc_def; +{ +    pzlib_filefunc_def->zopen_file = fopen_file_func; +    pzlib_filefunc_def->zread_file = fread_file_func; +    pzlib_filefunc_def->zwrite_file = fwrite_file_func; +    pzlib_filefunc_def->ztell_file = ftell_file_func; +    pzlib_filefunc_def->zseek_file = fseek_file_func; +    pzlib_filefunc_def->zclose_file = fclose_file_func; +    pzlib_filefunc_def->zerror_file = ferror_file_func; +    pzlib_filefunc_def->opaque = NULL; +} diff --git a/src/qcommon/ioapi.h b/src/qcommon/ioapi.h new file mode 100644 index 0000000..7d457ba --- /dev/null +++ b/src/qcommon/ioapi.h @@ -0,0 +1,75 @@ +/* ioapi.h -- IO base function header for compress/uncompress .zip +   files using zlib + zip or unzip API + +   Version 1.01e, February 12th, 2005 + +   Copyright (C) 1998-2005 Gilles Vollant +*/ + +#ifndef _ZLIBIOAPI_H +#define _ZLIBIOAPI_H + + +#define ZLIB_FILEFUNC_SEEK_CUR (1) +#define ZLIB_FILEFUNC_SEEK_END (2) +#define ZLIB_FILEFUNC_SEEK_SET (0) + +#define ZLIB_FILEFUNC_MODE_READ      (1) +#define ZLIB_FILEFUNC_MODE_WRITE     (2) +#define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3) + +#define ZLIB_FILEFUNC_MODE_EXISTING (4) +#define ZLIB_FILEFUNC_MODE_CREATE   (8) + + +#ifndef ZCALLBACK + +#if (defined(WIN32) || defined (WINDOWS) || defined (_WINDOWS)) && defined(CALLBACK) && defined (USEWINDOWS_CALLBACK) +#define ZCALLBACK CALLBACK +#else +#define ZCALLBACK +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef voidpf (ZCALLBACK *open_file_func) OF((voidpf opaque, const char* filename, int mode)); +typedef uLong  (ZCALLBACK *read_file_func) OF((voidpf opaque, voidpf stream, void* buf, uLong size)); +typedef uLong  (ZCALLBACK *write_file_func) OF((voidpf opaque, voidpf stream, const void* buf, uLong size)); +typedef long   (ZCALLBACK *tell_file_func) OF((voidpf opaque, voidpf stream)); +typedef long   (ZCALLBACK *seek_file_func) OF((voidpf opaque, voidpf stream, uLong offset, int origin)); +typedef int    (ZCALLBACK *close_file_func) OF((voidpf opaque, voidpf stream)); +typedef int    (ZCALLBACK *testerror_file_func) OF((voidpf opaque, voidpf stream)); + +typedef struct zlib_filefunc_def_s +{ +    open_file_func      zopen_file; +    read_file_func      zread_file; +    write_file_func     zwrite_file; +    tell_file_func      ztell_file; +    seek_file_func      zseek_file; +    close_file_func     zclose_file; +    testerror_file_func zerror_file; +    voidpf              opaque; +} zlib_filefunc_def; + + + +void fill_fopen_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def)); + +#define ZREAD(filefunc,filestream,buf,size) ((*((filefunc).zread_file))((filefunc).opaque,filestream,buf,size)) +#define ZWRITE(filefunc,filestream,buf,size) ((*((filefunc).zwrite_file))((filefunc).opaque,filestream,buf,size)) +#define ZTELL(filefunc,filestream) ((*((filefunc).ztell_file))((filefunc).opaque,filestream)) +#define ZSEEK(filefunc,filestream,pos,mode) ((*((filefunc).zseek_file))((filefunc).opaque,filestream,pos,mode)) +#define ZCLOSE(filefunc,filestream) ((*((filefunc).zclose_file))((filefunc).opaque,filestream)) +#define ZERROR(filefunc,filestream) ((*((filefunc).zerror_file))((filefunc).opaque,filestream)) + + +#ifdef __cplusplus +} +#endif + +#endif + 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..05a7aa9 --- /dev/null +++ b/src/qcommon/msg.c @@ -0,0 +1,1759 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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 ) { +	floatint_t dat; +	dat.f = f; +	MSG_WriteBits( sb, dat.i, 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 0x80+ 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 0x80+ 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_LookaheadByte( msg_t *msg ) { +	const int bloc = Huff_getBloc(); +	const int readcount = msg->readcount; +	const int bit = msg->bit; +	int c = MSG_ReadByte(msg); +	Huff_setBloc(bloc); +	msg->readcount = readcount; +	msg->bit = bit; +	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 ) { +	floatint_t dat; +	 +	dat.i = 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; +		} +		// 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; +		} +		// 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; +		} +		// 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); +	} +} + +// a string hasher which gives the same hash value even if the +// string is later modified via the legacy MSG read/write code +int MSG_HashKey(const char *string, int maxlen) { +	int hash, i; + +	hash = 0; +	for (i = 0; i < maxlen && string[i] != '\0'; i++) { +		if (string[i] & 0x80) +			hash += '.' * (119 + i); +		else +			hash += string[i] * (119 + i); +	} +	hash = (hash ^ (hash >> 10) ^ (hash >> 20)); +	return hash; +} + +/* +============================================================================= + +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 ) { +	floatint_t fi; +	if ( oldV == newV ) { +		MSG_WriteBits( msg, 0, 1 ); +		return; +	} +	fi.f = newV; +	MSG_WriteBits( msg, 1, 1 ); +	MSG_WriteBits( msg, fi.i, 32 ); +} + +float MSG_ReadDeltaFloat( msg_t *msg, float oldV ) { +	if ( MSG_ReadBits( msg, 1 ) ) { +		floatint_t fi; + +		fi.i = MSG_ReadBits( msg, 32 ); +		return fi.f; +	} +	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 ) { +	floatint_t fi; +	if ( oldV == newV ) { +		MSG_WriteBits( msg, 0, 1 ); +		return; +	} +	fi.f = newV; +	MSG_WriteBits( msg, 1, 1 ); +	MSG_WriteBits( msg, fi.i ^ key, 32 ); +} + +float MSG_ReadDeltaKeyFloat( msg_t *msg, int key, float oldV ) { +	if ( MSG_ReadBits( msg, 1 ) ) { +		floatint_t fi; + +		fi.i = MSG_ReadBits( msg, 32 ) ^ key; +		return fi.f; +	} +	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,(size_t)&((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(weaponAnim), 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(misc), MAX_MISC }, +{ NETF(modelindex), 8 }, +{ NETF(otherEntityNum2), GENTITYNUM_BITS }, +{ NETF(loopSound), 8 }, +{ NETF(generic1), 10 }, +{ 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 = ARRAY_LEN( entityStateFields ); + +	// 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 +================== +*/ +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 = ARRAY_LEN( entityStateFields ); +	lc = MSG_ReadByte(msg); + +	if ( lc > numFields || lc < 0 ) { +		Com_Error( ERR_DROP, "invalid entityState field count" ); +	} + +	// 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,(size_t)&((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(weaponAnim), 8 }, +{ PSF(movementDir), 4 }, +{ PSF(events[0]), 8 }, +{ PSF(legsAnim), 8 }, +{ PSF(events[1]), 8 }, +{ PSF(pm_flags), 24 }, +{ 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(ammo), 12 }, +{ PSF(clips), 4 }, +{ PSF(generic1), 10 }, +{ PSF(pm_type), 8 },					 +{ PSF(delta_angles[0]), 16 }, +{ PSF(delta_angles[2]), 16 }, +{ PSF(torsoTimer), 12 }, +{ PSF(tauntTimer), 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				miscbits; +	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 = ARRAY_LEN( playerStateFields ); + +	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; +		} +	} +	miscbits = 0; +	for (i=0 ; i<MAX_MISC ; i++) { +		if (to->misc[i] != from->misc[i]) { +			miscbits |= 1<<i; +		} +	} + +	if (!statsbits && !persistantbits && !miscbits) { +		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 ( miscbits ) { +		MSG_WriteBits( msg, 1, 1 );	// changed +		MSG_WriteBits( msg, miscbits, MAX_MISC ); +		for (i=0 ; i<MAX_MISC ; i++) +			if (miscbits & (1<<i) ) +				MSG_WriteLong( msg, to->misc[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 = ARRAY_LEN( playerStateFields ); +	lc = MSG_ReadByte(msg); + +	if ( lc > numFields || lc < 0 ) { +		Com_Error( ERR_DROP, "invalid playerState field count" ); +	} + +	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 misc data +		if ( MSG_ReadBits( msg, 1 ) ) { +			LOG("PS_MISC"); +			bits = MSG_ReadBits (msg, MAX_MISC); +			for (i=0 ; i<MAX_MISC ; i++) { +				if (bits & (1<<i) ) { +					to->misc[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..4aacfa7 --- /dev/null +++ b/src/qcommon/net_chan.c @@ -0,0 +1,739 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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; +} + + +//============================================================================== + + + +/* +============================================================================= + +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() + (int)((float)offset / com_timescale->value);	 +	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 +return 0 on address not found, 1 on address found with port, 2 on address found without port. +============= +*/ +int NET_StringToAdr( const char *s, netadr_t *a, netadrtype_t family ) +{ +	char	base[MAX_STRING_CHARS], *search; +	char	*port = NULL; + +	if (!strcmp (s, "localhost")) { +		Com_Memset (a, 0, sizeof(*a)); +		a->type = NA_LOOPBACK; +// as NA_LOOPBACK doesn't require ports report port was given. +		return 1; +	} + +	Q_strncpyz( base, s, sizeof( base ) ); +	 +	if(*base == '[' || Q_CountChar(base, ':') > 1) +	{ +		// This is an ipv6 address, handle it specially. +		search = strchr(base, ']'); +		if(search) +		{ +			*search = '\0'; +			search++; + +			if(*search == ':') +				port = search + 1; +		} +		 +		if(*base == '[') +			search = base + 1; +		else +			search = base; +	} +	else +	{ +		// look for a port number +		port = strchr( base, ':' ); +		 +		if ( port ) { +			*port = '\0'; +			port++; +		} +		 +		search = base; +	} + +	if(!Sys_StringToAdr(search, a, family)) +	{ +		a->type = NA_BAD; +		return 0; +	} + +	if(port) +	{ +		a->port = BigShort((short) atoi(port)); +		return 1; +	} +	else +	{ +		a->port = BigShort(PORT_SERVER); +		return 2; +	} +} diff --git a/src/qcommon/net_ip.c b/src/qcommon/net_ip.c new file mode 100644 index 0000000..e3a95e4 --- /dev/null +++ b/src/qcommon/net_ip.c @@ -0,0 +1,1727 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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 "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" + +#ifdef _WIN32 +#	include <winsock2.h> +#	include <ws2tcpip.h> +#	if WINVER < 0x501 +#		ifdef __MINGW32__ +			// wspiapi.h isn't available on MinGW, so if it's +			// present it's because the end user has added it +			// and we should look for it in our tree +#			include "wspiapi.h" +#		else +#			include <wspiapi.h> +#		endif +#	else +#		include <ws2spi.h> +#	endif + +typedef int socklen_t; +#	ifdef ADDRESS_FAMILY +#		define sa_family_t	ADDRESS_FAMILY +#	else +typedef unsigned short sa_family_t; +#	endif + +#	define EAGAIN					WSAEWOULDBLOCK +#	define EADDRNOTAVAIL	WSAEADDRNOTAVAIL +#	define EAFNOSUPPORT		WSAEAFNOSUPPORT +#	define ECONNRESET			WSAECONNRESET +typedef u_long	ioctlarg_t; +#	define socketError		WSAGetLastError( ) + +static WSADATA	winsockdata; +static qboolean	winsockInitialized = qfalse; + +#else + +#	if MAC_OS_X_VERSION_MIN_REQUIRED == 1020 +		// needed for socklen_t on OSX 10.2 +#		define _BSD_SOCKLEN_T_ +#	endif + +#	include <arpa/inet.h> +#	include <errno.h> +#	include <netdb.h> +#	include <netinet/in.h> +#	include <sys/socket.h> +#	include <net/if.h> +#	include <sys/ioctl.h> +#	include <sys/types.h> +#	include <sys/time.h> +#	include <unistd.h> +#	if !defined(__sun) && !defined(__sgi) +#		include <ifaddrs.h> +#	endif + +#	ifdef __sun +#		include <sys/filio.h> +#	endif + +typedef int SOCKET; +#	define INVALID_SOCKET		-1 +#	define SOCKET_ERROR			-1 +#	define closesocket			close +#	define ioctlsocket			ioctl +typedef int	ioctlarg_t; +#	define socketError			errno + +#endif + +static qboolean usingSocks = qfalse; +static int networkingEnabled = 0; + +static cvar_t	*net_enabled; + +static cvar_t	*net_socksEnabled; +static cvar_t	*net_socksServer; +static cvar_t	*net_socksPort; +static cvar_t	*net_socksUsername; +static cvar_t	*net_socksPassword; + +static cvar_t	*net_ip; +static cvar_t	*net_ip6; +static cvar_t	*net_port; +static cvar_t	*net_port6; +static cvar_t	*net_mcast6addr; +static cvar_t	*net_mcast6iface; + +static cvar_t	*net_dropsim; + +static struct sockaddr	socksRelayAddr; + +static SOCKET	ip_socket = INVALID_SOCKET; +static SOCKET	ip6_socket = INVALID_SOCKET; +static SOCKET	socks_socket = INVALID_SOCKET; +static SOCKET	multicast6_socket = INVALID_SOCKET; + +// Keep track of currently joined multicast group. +static struct ipv6_mreq curgroup; +// And the currently bound address. +static struct sockaddr_in6 boundto; + +#ifndef IF_NAMESIZE +  #define IF_NAMESIZE 16 +#endif + +// use an admin local address per default so that network admins can decide on how to handle quake3 traffic. +#define NET_MULTICAST_IP6 "ff04::696f:7175:616b:6533" + +#define	MAX_IPS		32 + +typedef struct +{ +	char ifname[IF_NAMESIZE]; +	 +	netadrtype_t type; +	sa_family_t family; +	struct sockaddr_storage addr; +	struct sockaddr_storage netmask; +} nip_localaddr_t; + +static nip_localaddr_t localIP[MAX_IPS]; +static int numIP; + + +//============================================================================= + + +/* +==================== +NET_ErrorString +==================== +*/ +char *NET_ErrorString( void ) { +#ifdef _WIN32 +	//FIXME: replace with FormatMessage? +	switch( socketError ) { +		case WSAEINTR: return "WSAEINTR"; +		case WSAEBADF: return "WSAEBADF"; +		case WSAEACCES: return "WSAEACCES"; +		case WSAEDISCON: return "WSAEDISCON"; +		case WSAEFAULT: return "WSAEFAULT"; +		case WSAEINVAL: return "WSAEINVAL"; +		case WSAEMFILE: return "WSAEMFILE"; +		case WSAEWOULDBLOCK: return "WSAEWOULDBLOCK"; +		case WSAEINPROGRESS: return "WSAEINPROGRESS"; +		case WSAEALREADY: return "WSAEALREADY"; +		case WSAENOTSOCK: return "WSAENOTSOCK"; +		case WSAEDESTADDRREQ: return "WSAEDESTADDRREQ"; +		case WSAEMSGSIZE: return "WSAEMSGSIZE"; +		case WSAEPROTOTYPE: return "WSAEPROTOTYPE"; +		case WSAENOPROTOOPT: return "WSAENOPROTOOPT"; +		case WSAEPROTONOSUPPORT: return "WSAEPROTONOSUPPORT"; +		case WSAESOCKTNOSUPPORT: return "WSAESOCKTNOSUPPORT"; +		case WSAEOPNOTSUPP: return "WSAEOPNOTSUPP"; +		case WSAEPFNOSUPPORT: return "WSAEPFNOSUPPORT"; +		case WSAEAFNOSUPPORT: return "WSAEAFNOSUPPORT"; +		case WSAEADDRINUSE: return "WSAEADDRINUSE"; +		case WSAEADDRNOTAVAIL: return "WSAEADDRNOTAVAIL"; +		case WSAENETDOWN: return "WSAENETDOWN"; +		case WSAENETUNREACH: return "WSAENETUNREACH"; +		case WSAENETRESET: return "WSAENETRESET"; +		case WSAECONNABORTED: return "WSWSAECONNABORTEDAEINTR"; +		case WSAECONNRESET: return "WSAECONNRESET"; +		case WSAENOBUFS: return "WSAENOBUFS"; +		case WSAEISCONN: return "WSAEISCONN"; +		case WSAENOTCONN: return "WSAENOTCONN"; +		case WSAESHUTDOWN: return "WSAESHUTDOWN"; +		case WSAETOOMANYREFS: return "WSAETOOMANYREFS"; +		case WSAETIMEDOUT: return "WSAETIMEDOUT"; +		case WSAECONNREFUSED: return "WSAECONNREFUSED"; +		case WSAELOOP: return "WSAELOOP"; +		case WSAENAMETOOLONG: return "WSAENAMETOOLONG"; +		case WSAEHOSTDOWN: return "WSAEHOSTDOWN"; +		case WSASYSNOTREADY: return "WSASYSNOTREADY"; +		case WSAVERNOTSUPPORTED: return "WSAVERNOTSUPPORTED"; +		case WSANOTINITIALISED: return "WSANOTINITIALISED"; +		case WSAHOST_NOT_FOUND: return "WSAHOST_NOT_FOUND"; +		case WSATRY_AGAIN: return "WSATRY_AGAIN"; +		case WSANO_RECOVERY: return "WSANO_RECOVERY"; +		case WSANO_DATA: return "WSANO_DATA"; +		default: return "NO ERROR"; +	} +#else +	return strerror(socketError); +#endif +} + +static void NetadrToSockadr( netadr_t *a, struct sockaddr *s ) { +	if( a->type == NA_BROADCAST ) { +		((struct sockaddr_in *)s)->sin_family = AF_INET; +		((struct sockaddr_in *)s)->sin_port = a->port; +		((struct sockaddr_in *)s)->sin_addr.s_addr = INADDR_BROADCAST; +	} +	else if( a->type == NA_IP ) { +		((struct sockaddr_in *)s)->sin_family = AF_INET; +		((struct sockaddr_in *)s)->sin_addr.s_addr = *(int *)&a->ip; +		((struct sockaddr_in *)s)->sin_port = a->port; +	} +	else if( a->type == NA_IP6 ) { +		((struct sockaddr_in6 *)s)->sin6_family = AF_INET6; +		((struct sockaddr_in6 *)s)->sin6_addr = * ((struct in6_addr *) &a->ip6); +		((struct sockaddr_in6 *)s)->sin6_port = a->port; +		((struct sockaddr_in6 *)s)->sin6_scope_id = a->scope_id; +	} +	else if(a->type == NA_MULTICAST6) +	{ +		((struct sockaddr_in6 *)s)->sin6_family = AF_INET6; +		((struct sockaddr_in6 *)s)->sin6_addr = curgroup.ipv6mr_multiaddr; +		((struct sockaddr_in6 *)s)->sin6_port = a->port; +	} +} + + +static void SockadrToNetadr( struct sockaddr *s, netadr_t *a ) { +	if (s->sa_family == AF_INET) { +		a->type = NA_IP; +		*(int *)&a->ip = ((struct sockaddr_in *)s)->sin_addr.s_addr; +		a->port = ((struct sockaddr_in *)s)->sin_port; +	} +	else if(s->sa_family == AF_INET6) +	{ +		a->type = NA_IP6; +		memcpy(a->ip6, &((struct sockaddr_in6 *)s)->sin6_addr, sizeof(a->ip6)); +		a->port = ((struct sockaddr_in6 *)s)->sin6_port; +		a->scope_id = ((struct sockaddr_in6 *)s)->sin6_scope_id; +	} +} + + +static struct addrinfo *SearchAddrInfo(struct addrinfo *hints, sa_family_t family) +{ +	while(hints) +	{ +		if(hints->ai_family == family) +			return hints; + +		hints = hints->ai_next; +	} +	 +	return NULL; +} + +/* +============= +Sys_StringToSockaddr +============= +*/ +static qboolean Sys_StringToSockaddr(const char *s, struct sockaddr *sadr, int sadr_len, sa_family_t family) +{ +	struct addrinfo hints; +	struct addrinfo *res = NULL; +	struct addrinfo *search = NULL; +	struct addrinfo *hintsp; +	int retval; +	 +	memset(sadr, '\0', sizeof(*sadr)); +	memset(&hints, '\0', sizeof(hints)); + +	hintsp = &hints; +	hintsp->ai_family = family; +	hintsp->ai_socktype = SOCK_DGRAM; +	 +	retval = getaddrinfo(s, NULL, hintsp, &res); + +	if(!retval) +	{ +		if(family == AF_UNSPEC) +		{ +			// Decide here and now which protocol family to use +			if(net_enabled->integer & NET_PRIOV6) +			{ +				if(net_enabled->integer & NET_ENABLEV6) +					search = SearchAddrInfo(res, AF_INET6); +				 +				if(!search && (net_enabled->integer & NET_ENABLEV4)) +					search = SearchAddrInfo(res, AF_INET); +			} +			else +			{ +				if(net_enabled->integer & NET_ENABLEV4) +					search = SearchAddrInfo(res, AF_INET); +				 +				if(!search && (net_enabled->integer & NET_ENABLEV6)) +					search = SearchAddrInfo(res, AF_INET6); +			} +		} +		else +			search = SearchAddrInfo(res, family); + +		if(search) +		{ +			if(search->ai_addrlen > sadr_len) +				search->ai_addrlen = sadr_len; +				 +			memcpy(sadr, search->ai_addr, search->ai_addrlen); +			freeaddrinfo(search); +			 +			return qtrue; +		} +		else +			Com_Printf("Sys_StringToSockaddr: Error resolving %s: No address of required type found.\n", s); +	} +	else +		Com_Printf("Sys_StringToSockaddr: Error resolving %s: %s\n", s, gai_strerror(retval)); +	 +	if(res) +		freeaddrinfo(res); +	 +	return qfalse; +} + +/* +============= +Sys_SockaddrToString +============= +*/ +static void Sys_SockaddrToString(char *dest, int destlen, struct sockaddr *input) +{ +	socklen_t inputlen; + +	if (input->sa_family == AF_INET6) +		inputlen = sizeof(struct sockaddr_in6); +	else +		inputlen = sizeof(struct sockaddr_in); + +	if(getnameinfo(input, inputlen, dest, destlen, NULL, 0, NI_NUMERICHOST) && destlen > 0) +		*dest = '\0'; +} + +/* +============= +Sys_StringToAdr +============= +*/ +qboolean Sys_StringToAdr( const char *s, netadr_t *a, netadrtype_t family ) { +	struct sockaddr_storage sadr; +	sa_family_t fam; +	 +	switch(family) +	{ +		case NA_IP: +			fam = AF_INET; +		break; +		case NA_IP6: +			fam = AF_INET6; +		break; +		default: +			fam = AF_UNSPEC; +		break; +	} +	if( !Sys_StringToSockaddr(s, (struct sockaddr *) &sadr, sizeof(sadr), fam ) ) { +		return qfalse; +	} +	 +	SockadrToNetadr( (struct sockaddr *) &sadr, a ); +	return qtrue; +} + +/* +=================== +NET_CompareBaseAdrMask + +Compare without port, and up to the bit number given in netmask. +=================== +*/ +qboolean NET_CompareBaseAdrMask(netadr_t a, netadr_t b, int netmask) +{ +	qboolean differed; +	byte cmpmask, *addra, *addrb; +	int curbyte; +	 +	if (a.type != b.type) +		return qfalse; + +	if (a.type == NA_LOOPBACK) +		return qtrue; + +	if(a.type == NA_IP) +	{ +		addra = (byte *) &a.ip; +		addrb = (byte *) &b.ip; +		 +		if(netmask < 0 || netmask > 32) +			netmask = 32; +	} +	else if(a.type == NA_IP6) +	{ +		addra = (byte *) &a.ip6; +		addrb = (byte *) &b.ip6; +		 +		if(netmask < 0 || netmask > 128) +			netmask = 128; +	} +	else +	{ +		Com_Printf ("NET_CompareBaseAdr: bad address type\n"); +		return qfalse; +	} + +	differed = qfalse; +	curbyte = 0; + +	while(netmask > 7) +	{ +		if(addra[curbyte] != addrb[curbyte]) +		{ +			differed = qtrue; +			break; +		} + +		curbyte++; +		netmask -= 8; +	} + +	if(differed) +		return qfalse; + +	if(netmask) +	{ +		cmpmask = (1 << netmask) - 1; +		cmpmask <<= 8 - netmask; + +		if((addra[curbyte] & cmpmask) == (addrb[curbyte] & cmpmask)) +			return qtrue; +	} +	else +		return qtrue; +	 +	return qfalse; +} + + +/* +=================== +NET_CompareBaseAdr + +Compares without the port +=================== +*/ +qboolean NET_CompareBaseAdr (netadr_t a, netadr_t b) +{ +	return NET_CompareBaseAdrMask(a, b, -1); +} + +const char	*NET_AdrToString (netadr_t a) +{ +	static	char	s[NET_ADDRSTRMAXLEN]; + +	if (a.type == NA_LOOPBACK) +		Com_sprintf (s, sizeof(s), "loopback"); +	else if (a.type == NA_IP || a.type == NA_IP6) +	{ +		struct sockaddr_storage sadr; +	 +		memset(&sadr, 0, sizeof(sadr)); +		NetadrToSockadr(&a, (struct sockaddr *) &sadr); +		Sys_SockaddrToString(s, sizeof(s), (struct sockaddr *) &sadr); +	} + +	return s; +} + +const char	*NET_AdrToStringwPort (netadr_t a) +{ +	static	char	s[NET_ADDRSTRMAXLEN]; + +	if (a.type == NA_LOOPBACK) +		Com_sprintf (s, sizeof(s), "loopback"); +	else if(a.type == NA_IP) +		Com_sprintf(s, sizeof(s), "%s:%hu", NET_AdrToString(a), ntohs(a.port)); +	else if(a.type == NA_IP6) +		Com_sprintf(s, sizeof(s), "[%s]:%hu", NET_AdrToString(a), ntohs(a.port)); + +	return s; +} + + +qboolean	NET_CompareAdr (netadr_t a, netadr_t b) +{ +	if(!NET_CompareBaseAdr(a, b)) +		return qfalse; +	 +	if (a.type == NA_IP || a.type == NA_IP6) +	{ +		if (a.port == b.port) +			return qtrue; +	} +	else +		return qtrue; +		 +	return qfalse; +} + + +qboolean	NET_IsLocalAddress( netadr_t adr ) { +	return adr.type == NA_LOOPBACK; +} + +//============================================================================= + +/* +================== +NET_GetPacket + +Receive one packet +================== +*/ +#ifdef _DEBUG +int	recvfromCount; +#endif + +qboolean NET_GetPacket(netadr_t *net_from, msg_t *net_message, fd_set *fdr) +{ +	int 	ret; +	struct sockaddr_storage from; +	socklen_t	fromlen; +	int		err; + +#ifdef _DEBUG +	recvfromCount++;		// performance check +#endif +	 +	if(ip_socket != INVALID_SOCKET && FD_ISSET(ip_socket, fdr)) +	{ +		fromlen = sizeof(from); +		ret = recvfrom( ip_socket, (void *)net_message->data, net_message->maxsize, 0, (struct sockaddr *) &from, &fromlen ); +		 +		if (ret == SOCKET_ERROR) +		{ +			err = socketError; + +			if( err != EAGAIN && err != ECONNRESET ) +				Com_Printf( "NET_GetPacket: %s\n", NET_ErrorString() ); +		} +		else +		{ + +			memset( ((struct sockaddr_in *)&from)->sin_zero, 0, 8 ); +		 +			if ( usingSocks && memcmp( &from, &socksRelayAddr, fromlen ) == 0 ) { +				if ( ret < 10 || net_message->data[0] != 0 || net_message->data[1] != 0 || net_message->data[2] != 0 || net_message->data[3] != 1 ) { +					return qfalse; +				} +				net_from->type = NA_IP; +				net_from->ip[0] = net_message->data[4]; +				net_from->ip[1] = net_message->data[5]; +				net_from->ip[2] = net_message->data[6]; +				net_from->ip[3] = net_message->data[7]; +				net_from->port = *(short *)&net_message->data[8]; +				net_message->readcount = 10; +			} +			else { +				SockadrToNetadr( (struct sockaddr *) &from, net_from ); +				net_message->readcount = 0; +			} +		 +			if( ret >= net_message->maxsize ) { +				Com_Printf( "Oversize packet from %s\n", NET_AdrToString (*net_from) ); +				return qfalse; +			} +			 +			net_message->cursize = ret; +			return qtrue; +		} +	} +	 +	if(ip6_socket != INVALID_SOCKET && FD_ISSET(ip6_socket, fdr)) +	{ +		fromlen = sizeof(from); +		ret = recvfrom(ip6_socket, (void *)net_message->data, net_message->maxsize, 0, (struct sockaddr *) &from, &fromlen); +		 +		if (ret == SOCKET_ERROR) +		{ +			err = socketError; + +			if( err != EAGAIN && err != ECONNRESET ) +				Com_Printf( "NET_GetPacket: %s\n", NET_ErrorString() ); +		} +		else +		{ +			SockadrToNetadr((struct sockaddr *) &from, net_from); +			net_message->readcount = 0; +		 +			if(ret >= net_message->maxsize) +			{ +				Com_Printf( "Oversize packet from %s\n", NET_AdrToString (*net_from) ); +				return qfalse; +			} +			 +			net_message->cursize = ret; +			return qtrue; +		} +	} + +	if(multicast6_socket != INVALID_SOCKET && multicast6_socket != ip6_socket && FD_ISSET(multicast6_socket, fdr)) +	{ +		fromlen = sizeof(from); +		ret = recvfrom(multicast6_socket, (void *)net_message->data, net_message->maxsize, 0, (struct sockaddr *) &from, &fromlen); +		 +		if (ret == SOCKET_ERROR) +		{ +			err = socketError; + +			if( err != EAGAIN && err != ECONNRESET ) +				Com_Printf( "NET_GetPacket: %s\n", NET_ErrorString() ); +		} +		else +		{ +			SockadrToNetadr((struct sockaddr *) &from, net_from); +			net_message->readcount = 0; +		 +			if(ret >= net_message->maxsize) +			{ +				Com_Printf( "Oversize packet from %s\n", NET_AdrToString (*net_from) ); +				return qfalse; +			} +			 +			net_message->cursize = ret; +			return qtrue; +		} +	} +	 +	 +	return qfalse; +} + +//============================================================================= + +static char socksBuf[4096]; + +/* +================== +Sys_SendPacket +================== +*/ +void Sys_SendPacket( int length, const void *data, netadr_t to ) { +	int				ret = SOCKET_ERROR; +	struct sockaddr_storage	addr; + +	if( to.type != NA_BROADCAST && to.type != NA_IP && to.type != NA_IP6 && to.type != NA_MULTICAST6) +	{ +		Com_Error( ERR_FATAL, "Sys_SendPacket: bad address type" ); +		return; +	} + +	if( (ip_socket == INVALID_SOCKET && to.type == NA_IP) || +		(ip6_socket == INVALID_SOCKET && to.type == NA_IP6) || +		(ip6_socket == INVALID_SOCKET && to.type == NA_MULTICAST6) ) +		return; + +	if(to.type == NA_MULTICAST6 && (net_enabled->integer & NET_DISABLEMCAST)) +		return; + +	memset(&addr, 0, sizeof(addr)); +	NetadrToSockadr( &to, (struct sockaddr *) &addr ); + +	if( usingSocks && to.type == NA_IP ) { +		socksBuf[0] = 0;	// reserved +		socksBuf[1] = 0; +		socksBuf[2] = 0;	// fragment (not fragmented) +		socksBuf[3] = 1;	// address type: IPV4 +		*(int *)&socksBuf[4] = ((struct sockaddr_in *)&addr)->sin_addr.s_addr; +		*(short *)&socksBuf[8] = ((struct sockaddr_in *)&addr)->sin_port; +		memcpy( &socksBuf[10], data, length ); +		ret = sendto( ip_socket, socksBuf, length+10, 0, &socksRelayAddr, sizeof(socksRelayAddr) ); +	} +	else { +		if(addr.ss_family == AF_INET) +			ret = sendto( ip_socket, data, length, 0, (struct sockaddr *) &addr, sizeof(struct sockaddr_in) ); +		else if(addr.ss_family == AF_INET6) +			ret = sendto( ip6_socket, data, length, 0, (struct sockaddr *) &addr, sizeof(struct sockaddr_in6) ); +	} +	if( ret == SOCKET_ERROR ) { +		int err = socketError; + +		// wouldblock is silent +		if( err == EAGAIN ) { +			return; +		} + +		// some PPP links do not allow broadcasts and return an error +		if( ( err == EADDRNOTAVAIL ) && ( ( to.type == NA_BROADCAST ) ) ) { +			return; +		} + +		Com_Printf( "NET_SendPacket: %s\n", NET_ErrorString() ); +	} +} + + +//============================================================================= + +/* +================== +Sys_IsLANAddress + +LAN clients will have their rate var ignored +================== +*/ +qboolean Sys_IsLANAddress( netadr_t adr ) { +	int		index, run, addrsize; +	qboolean differed; +	byte *compareadr, *comparemask, *compareip; + +	if( adr.type == NA_LOOPBACK ) { +		return qtrue; +	} + +	if( adr.type == NA_IP ) +	{ +		// RFC1918: +		// 10.0.0.0        -   10.255.255.255  (10/8 prefix) +		// 172.16.0.0      -   172.31.255.255  (172.16/12 prefix) +		// 192.168.0.0     -   192.168.255.255 (192.168/16 prefix) +		if(adr.ip[0] == 10) +			return qtrue; +		if(adr.ip[0] == 172 && (adr.ip[1]&0xf0) == 16) +			return qtrue; +		if(adr.ip[0] == 192 && adr.ip[1] == 168) +			return qtrue; + +		if(adr.ip[0] == 127) +			return qtrue; +	} +	else if(adr.type == NA_IP6) +	{ +		if(adr.ip6[0] == 0xfe && (adr.ip6[1] & 0xc0) == 0x80) +			return qtrue; +		if((adr.ip6[0] & 0xfe) == 0xfc) +			return qtrue; +	} +	 +	// Now compare against the networks this computer is member of. +	for(index = 0; index < numIP; index++) +	{ +		if(localIP[index].type == adr.type) +		{ +			if(adr.type == NA_IP) +			{ +				compareip = (byte *) &((struct sockaddr_in *) &localIP[index].addr)->sin_addr.s_addr; +				comparemask = (byte *) &((struct sockaddr_in *) &localIP[index].netmask)->sin_addr.s_addr; +				compareadr = adr.ip; +				 +				addrsize = sizeof(adr.ip); +			} +			else +			{ +				// TODO? should we check the scope_id here? + +				compareip = (byte *) &((struct sockaddr_in6 *) &localIP[index].addr)->sin6_addr; +				comparemask = (byte *) &((struct sockaddr_in6 *) &localIP[index].netmask)->sin6_addr; +				compareadr = adr.ip6; +				 +				addrsize = sizeof(adr.ip6); +			} + +			differed = qfalse; +			for(run = 0; run < addrsize; run++) +			{ +				if((compareip[run] & comparemask[run]) != (compareadr[run] & comparemask[run])) +				{ +					differed = qtrue; +					break; +				} +			} +			 +			if(!differed) +				return qtrue; + +		} +	} +	 +	return qfalse; +} + +/* +================== +Sys_ShowIP +================== +*/ +void Sys_ShowIP(void) { +	int i; +	char addrbuf[NET_ADDRSTRMAXLEN]; + +	for(i = 0; i < numIP; i++) +	{ +		Sys_SockaddrToString(addrbuf, sizeof(addrbuf), (struct sockaddr *) &localIP[i].addr); + +		if(localIP[i].type == NA_IP) +			Com_Printf( "IP: %s\n", addrbuf); +		else if(localIP[i].type == NA_IP6) +			Com_Printf( "IP6: %s\n", addrbuf); +	} +} + + +//============================================================================= + + +/* +==================== +NET_IPSocket +==================== +*/ +int NET_IPSocket( char *net_interface, int port, int *err ) { +	SOCKET				newsocket; +	struct sockaddr_in	address; +	ioctlarg_t			_true = 1; +	int					i = 1; + +	*err = 0; + +	if( net_interface ) { +		Com_Printf( "Opening IP socket: %s:%i\n", net_interface, port ); +	} +	else { +		Com_Printf( "Opening IP socket: 0.0.0.0:%i\n", port ); +	} + +	if( ( newsocket = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP ) ) == INVALID_SOCKET ) { +		*err = socketError; +		Com_Printf( "WARNING: NET_IPSocket: socket: %s\n", NET_ErrorString() ); +		return newsocket; +	} +	// make it non-blocking +	if( ioctlsocket( newsocket, FIONBIO, &_true ) == SOCKET_ERROR ) { +		Com_Printf( "WARNING: NET_IPSocket: ioctl FIONBIO: %s\n", NET_ErrorString() ); +		*err = socketError; +		closesocket(newsocket); +		return INVALID_SOCKET; +	} + +	// make it broadcast capable +	if( setsockopt( newsocket, SOL_SOCKET, SO_BROADCAST, (char *) &i, sizeof(i) ) == SOCKET_ERROR ) { +		Com_Printf( "WARNING: NET_IPSocket: setsockopt SO_BROADCAST: %s\n", NET_ErrorString() ); +	} + +	if( !net_interface || !net_interface[0]) { +		address.sin_family = AF_INET; +		address.sin_addr.s_addr = INADDR_ANY; +	} +	else +	{ +		if(!Sys_StringToSockaddr( net_interface, (struct sockaddr *)&address, sizeof(address), AF_INET)) +		{ +			closesocket(newsocket); +			return INVALID_SOCKET; +		} +	} + +	if( port == PORT_ANY ) { +		address.sin_port = 0; +	} +	else { +		address.sin_port = htons( (short)port ); +	} + +	if( bind( newsocket, (void *)&address, sizeof(address) ) == SOCKET_ERROR ) { +		Com_Printf( "WARNING: NET_IPSocket: bind: %s\n", NET_ErrorString() ); +		*err = socketError; +		closesocket( newsocket ); +		return INVALID_SOCKET; +	} + +	return newsocket; +} + +/* +==================== +NET_IP6Socket +==================== +*/ +int NET_IP6Socket( char *net_interface, int port, struct sockaddr_in6 *bindto, int *err ) { +	SOCKET				newsocket; +	struct sockaddr_in6	address; +	ioctlarg_t			_true = 1; + +	*err = 0; + +	if( net_interface ) +	{ +		// Print the name in brackets if there is a colon: +		if(Q_CountChar(net_interface, ':')) +			Com_Printf( "Opening IP6 socket: [%s]:%i\n", net_interface, port ); +		else +			Com_Printf( "Opening IP6 socket: %s:%i\n", net_interface, port ); +	} +	else +		Com_Printf( "Opening IP6 socket: [::]:%i\n", port ); + +	if( ( newsocket = socket( PF_INET6, SOCK_DGRAM, IPPROTO_UDP ) ) == INVALID_SOCKET ) { +		*err = socketError; +		Com_Printf( "WARNING: NET_IP6Socket: socket: %s\n", NET_ErrorString() ); +		return newsocket; +	} + +	// make it non-blocking +	if( ioctlsocket( newsocket, FIONBIO, &_true ) == SOCKET_ERROR ) { +		Com_Printf( "WARNING: NET_IP6Socket: ioctl FIONBIO: %s\n", NET_ErrorString() ); +		*err = socketError; +		closesocket(newsocket); +		return INVALID_SOCKET; +	} + +#ifdef IPV6_V6ONLY +	{ +		int i = 1; + +		// ipv4 addresses should not be allowed to connect via this socket. +		if(setsockopt(newsocket, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &i, sizeof(i)) == SOCKET_ERROR) +		{ +			// win32 systems don't seem to support this anyways. +			Com_DPrintf("WARNING: NET_IP6Socket: setsockopt IPV6_V6ONLY: %s\n", NET_ErrorString()); +		} +	} +#endif + +	if( !net_interface || !net_interface[0]) { +		address.sin6_family = AF_INET6; +		address.sin6_addr = in6addr_any; +	} +	else +	{ +		if(!Sys_StringToSockaddr( net_interface, (struct sockaddr *)&address, sizeof(address), AF_INET6)) +		{ +			closesocket(newsocket); +			return INVALID_SOCKET; +		} +	} + +	if( port == PORT_ANY ) { +		address.sin6_port = 0; +	} +	else { +		address.sin6_port = htons( (short)port ); +	} + +	if( bind( newsocket, (void *)&address, sizeof(address) ) == SOCKET_ERROR ) { +		Com_Printf( "WARNING: NET_IP6Socket: bind: %s\n", NET_ErrorString() ); +		*err = socketError; +		closesocket( newsocket ); +		return INVALID_SOCKET; +	} +	 +	if(bindto) +		*bindto = address; + +	return newsocket; +} + +/* +==================== +NET_SetMulticast +Set the current multicast group +==================== +*/ +void NET_SetMulticast6(void) +{ +	struct sockaddr_in6 addr; + +	if(!*net_mcast6addr->string || !Sys_StringToSockaddr(net_mcast6addr->string, (struct sockaddr *) &addr, sizeof(addr), AF_INET6)) +	{ +		Com_Printf("WARNING: NET_JoinMulticast6: Incorrect multicast address given, " +			   "please set cvar %s to a sane value.\n", net_mcast6addr->name); +		 +		Cvar_SetValue(net_enabled->name, net_enabled->integer | NET_DISABLEMCAST); +		 +		return; +	} +	 +	memcpy(&curgroup.ipv6mr_multiaddr, &addr.sin6_addr, sizeof(curgroup.ipv6mr_multiaddr)); + +	if(*net_mcast6iface->string) +	{ +#ifdef _WIN32 +		curgroup.ipv6mr_interface = net_mcast6iface->integer; +#else +		curgroup.ipv6mr_interface = if_nametoindex(net_mcast6iface->string); +#endif +	} +	else +		curgroup.ipv6mr_interface = 0; +} + +/* +==================== +NET_JoinMulticast +Join an ipv6 multicast group +==================== +*/ +void NET_JoinMulticast6(void) +{ +	int err; +	 +	if(ip6_socket == INVALID_SOCKET || multicast6_socket != INVALID_SOCKET || (net_enabled->integer & NET_DISABLEMCAST)) +		return; +	 +	if(IN6_IS_ADDR_MULTICAST(&boundto.sin6_addr) || IN6_IS_ADDR_UNSPECIFIED(&boundto.sin6_addr)) +	{ +		// The way the socket was bound does not prohibit receiving multi-cast packets. So we don't need to open a new one. +		multicast6_socket = ip6_socket; +	} +	else +	{ +		if((multicast6_socket = NET_IP6Socket(net_mcast6addr->string, ntohs(boundto.sin6_port), NULL, &err)) == INVALID_SOCKET) +		{ +			// If the OS does not support binding to multicast addresses, like WinXP, at least try with the normal file descriptor. +			multicast6_socket = ip6_socket; +		} +	} +	 +	if(curgroup.ipv6mr_interface) +	{ +		if (setsockopt(multicast6_socket, IPPROTO_IPV6, IPV6_MULTICAST_IF, +					(char *) &curgroup.ipv6mr_interface, sizeof(curgroup.ipv6mr_interface)) < 0) +		{ +			Com_Printf("NET_JoinMulticast6: Couldn't set scope on multicast socket: %s\n", NET_ErrorString()); + +			if(multicast6_socket != ip6_socket) +			{ +				closesocket(multicast6_socket); +				multicast6_socket = INVALID_SOCKET; +				return; +			} +		} +	} + +	if (setsockopt(multicast6_socket, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *) &curgroup, sizeof(curgroup))) +	{ +		Com_Printf("NET_JoinMulticast6: Couldn't join multicast group: %s\n", NET_ErrorString()); + +		if(multicast6_socket != ip6_socket) +		{ +			closesocket(multicast6_socket); +			multicast6_socket = INVALID_SOCKET; +			return; +		} +	} +} + +void NET_LeaveMulticast6() +{ +	if(multicast6_socket != INVALID_SOCKET) +	{ +		if(multicast6_socket != ip6_socket) +			closesocket(multicast6_socket); +		else +			setsockopt(multicast6_socket, IPPROTO_IPV6, IPV6_LEAVE_GROUP, (char *) &curgroup, sizeof(curgroup)); + +		multicast6_socket = INVALID_SOCKET; +	} +} + +/* +==================== +NET_OpenSocks +==================== +*/ +void NET_OpenSocks( int port ) { +	struct sockaddr_in	address; +	int					err; +	struct hostent		*h; +	int					len; +	qboolean			rfc1929; +	unsigned char		buf[64]; + +	usingSocks = qfalse; + +	Com_Printf( "Opening connection to SOCKS server.\n" ); + +	if ( ( socks_socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ) ) == INVALID_SOCKET ) { +		err = socketError; +		Com_Printf( "WARNING: NET_OpenSocks: socket: %s\n", NET_ErrorString() ); +		return; +	} + +	h = gethostbyname( net_socksServer->string ); +	if ( h == NULL ) { +		err = socketError; +		Com_Printf( "WARNING: NET_OpenSocks: gethostbyname: %s\n", NET_ErrorString() ); +		return; +	} +	if ( h->h_addrtype != AF_INET ) { +		Com_Printf( "WARNING: NET_OpenSocks: gethostbyname: address type was not AF_INET\n" ); +		return; +	} +	address.sin_family = AF_INET; +	address.sin_addr.s_addr = *(int *)h->h_addr_list[0]; +	address.sin_port = htons( (short)net_socksPort->integer ); + +	if ( connect( socks_socket, (struct sockaddr *)&address, sizeof( address ) ) == SOCKET_ERROR ) { +		err = socketError; +		Com_Printf( "NET_OpenSocks: connect: %s\n", NET_ErrorString() ); +		return; +	} + +	// send socks authentication handshake +	if ( *net_socksUsername->string || *net_socksPassword->string ) { +		rfc1929 = qtrue; +	} +	else { +		rfc1929 = qfalse; +	} + +	buf[0] = 5;		// SOCKS version +	// method count +	if ( rfc1929 ) { +		buf[1] = 2; +		len = 4; +	} +	else { +		buf[1] = 1; +		len = 3; +	} +	buf[2] = 0;		// method #1 - method id #00: no authentication +	if ( rfc1929 ) { +		buf[2] = 2;		// method #2 - method id #02: username/password +	} +	if ( send( socks_socket, (void *)buf, len, 0 ) == SOCKET_ERROR ) { +		err = socketError; +		Com_Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() ); +		return; +	} + +	// get the response +	len = recv( socks_socket, (void *)buf, 64, 0 ); +	if ( len == SOCKET_ERROR ) { +		err = socketError; +		Com_Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() ); +		return; +	} +	if ( len != 2 || buf[0] != 5 ) { +		Com_Printf( "NET_OpenSocks: bad response\n" ); +		return; +	} +	switch( buf[1] ) { +	case 0:	// no authentication +		break; +	case 2: // username/password authentication +		break; +	default: +		Com_Printf( "NET_OpenSocks: request denied\n" ); +		return; +	} + +	// do username/password authentication if needed +	if ( buf[1] == 2 ) { +		int		ulen; +		int		plen; + +		// build the request +		ulen = strlen( net_socksUsername->string ); +		plen = strlen( net_socksPassword->string ); + +		buf[0] = 1;		// username/password authentication version +		buf[1] = ulen; +		if ( ulen ) { +			memcpy( &buf[2], net_socksUsername->string, ulen ); +		} +		buf[2 + ulen] = plen; +		if ( plen ) { +			memcpy( &buf[3 + ulen], net_socksPassword->string, plen ); +		} + +		// send it +		if ( send( socks_socket, (void *)buf, 3 + ulen + plen, 0 ) == SOCKET_ERROR ) { +			err = socketError; +			Com_Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() ); +			return; +		} + +		// get the response +		len = recv( socks_socket, (void *)buf, 64, 0 ); +		if ( len == SOCKET_ERROR ) { +			err = socketError; +			Com_Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() ); +			return; +		} +		if ( len != 2 || buf[0] != 1 ) { +			Com_Printf( "NET_OpenSocks: bad response\n" ); +			return; +		} +		if ( buf[1] != 0 ) { +			Com_Printf( "NET_OpenSocks: authentication failed\n" ); +			return; +		} +	} + +	// send the UDP associate request +	buf[0] = 5;		// SOCKS version +	buf[1] = 3;		// command: UDP associate +	buf[2] = 0;		// reserved +	buf[3] = 1;		// address type: IPV4 +	*(int *)&buf[4] = INADDR_ANY; +	*(short *)&buf[8] = htons( (short)port );		// port +	if ( send( socks_socket, (void *)buf, 10, 0 ) == SOCKET_ERROR ) { +		err = socketError; +		Com_Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() ); +		return; +	} + +	// get the response +	len = recv( socks_socket, (void *)buf, 64, 0 ); +	if( len == SOCKET_ERROR ) { +		err = socketError; +		Com_Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() ); +		return; +	} +	if( len < 2 || buf[0] != 5 ) { +		Com_Printf( "NET_OpenSocks: bad response\n" ); +		return; +	} +	// check completion code +	if( buf[1] != 0 ) { +		Com_Printf( "NET_OpenSocks: request denied: %i\n", buf[1] ); +		return; +	} +	if( buf[3] != 1 ) { +		Com_Printf( "NET_OpenSocks: relay address is not IPV4: %i\n", buf[3] ); +		return; +	} +	((struct sockaddr_in *)&socksRelayAddr)->sin_family = AF_INET; +	((struct sockaddr_in *)&socksRelayAddr)->sin_addr.s_addr = *(int *)&buf[4]; +	((struct sockaddr_in *)&socksRelayAddr)->sin_port = *(short *)&buf[8]; +	memset( ((struct sockaddr_in *)&socksRelayAddr)->sin_zero, 0, 8 ); + +	usingSocks = qtrue; +} + + +/* +===================== +NET_AddLocalAddress +===================== +*/ +static void NET_AddLocalAddress(char *ifname, struct sockaddr *addr, struct sockaddr *netmask) +{ +	int addrlen; +	sa_family_t family; +	 +	// only add addresses that have all required info. +	if(!addr || !netmask || !ifname) +		return; +	 +	family = addr->sa_family; + +	if(numIP < MAX_IPS) +	{ +		if(family == AF_INET) +		{ +			addrlen = sizeof(struct sockaddr_in); +			localIP[numIP].type = NA_IP; +		} +		else if(family == AF_INET6) +		{ +			addrlen = sizeof(struct sockaddr_in6); +			localIP[numIP].type = NA_IP6; +		} +		else +			return; +		 +		Q_strncpyz(localIP[numIP].ifname, ifname, sizeof(localIP[numIP].ifname)); +	 +		localIP[numIP].family = family; + +		memcpy(&localIP[numIP].addr, addr, addrlen); +		memcpy(&localIP[numIP].netmask, netmask, addrlen); +		 +		numIP++; +	} +} + +#if defined(__linux__) || defined(MACOSX) || defined(__BSD__) +static void NET_GetLocalAddress(void) +{ +	struct ifaddrs *ifap, *search; + +	if(getifaddrs(&ifap)) +		Com_Printf("NET_GetLocalAddress: Unable to get list of network interfaces: %s\n", NET_ErrorString()); +	else +	{ +		for(search = ifap; search; search = search->ifa_next) +		{ +			// Only add interfaces that are up. +			if(ifap->ifa_flags & IFF_UP) +				NET_AddLocalAddress(search->ifa_name, search->ifa_addr, search->ifa_netmask); +		} +	 +		freeifaddrs(ifap); +		 +		Sys_ShowIP(); +	} +} +#else +static void NET_GetLocalAddress( void ) { +	char				hostname[256]; +	struct addrinfo	hint; +	struct addrinfo	*res = NULL; + +	if(gethostname( hostname, 256 ) == SOCKET_ERROR) +		return; + +	Com_Printf( "Hostname: %s\n", hostname ); +	 +	memset(&hint, 0, sizeof(hint)); +	 +	hint.ai_family = AF_UNSPEC; +	hint.ai_socktype = SOCK_DGRAM; +	 +	if(!getaddrinfo(hostname, NULL, &hint, &res)) +	{ +		struct sockaddr_in mask4; +		struct sockaddr_in6 mask6; +		struct addrinfo *search; +	 +		/* On operating systems where it's more difficult to find out the configured interfaces, we'll just assume a +		 * netmask with all bits set. */ +	 +		memset(&mask4, 0, sizeof(mask4)); +		memset(&mask6, 0, sizeof(mask6)); +		mask4.sin_family = AF_INET; +		memset(&mask4.sin_addr.s_addr, 0xFF, sizeof(mask4.sin_addr.s_addr)); +		mask6.sin6_family = AF_INET6; +		memset(&mask6.sin6_addr, 0xFF, sizeof(mask6.sin6_addr)); + +		// add all IPs from returned list. +		for(search = res; search; search = search->ai_next) +		{ +			if(search->ai_family == AF_INET) +				NET_AddLocalAddress("", search->ai_addr, (struct sockaddr *) &mask4); +			else if(search->ai_family == AF_INET6) +				NET_AddLocalAddress("", search->ai_addr, (struct sockaddr *) &mask6); +		} +	 +		Sys_ShowIP(); +	} +	 +	if(res) +		freeaddrinfo(res); +} +#endif + +/* +==================== +NET_OpenIP +==================== +*/ +void NET_OpenIP( void ) { +	int		i; +	int		err; +	int		port; +	int		port6; + +	port = net_port->integer; +	port6 = net_port6->integer; + +	NET_GetLocalAddress(); + +	// automatically scan for a valid port, so multiple +	// dedicated servers can be started without requiring +	// a different net_port for each one + +	if(net_enabled->integer & NET_ENABLEV6) +	{ +		for( i = 0 ; i < 10 ; i++ ) +		{ +			ip6_socket = NET_IP6Socket(net_ip6->string, port6 + i, &boundto, &err); +			if (ip6_socket != INVALID_SOCKET) +			{ +				Cvar_SetValue( "net_port6", port6 + i ); +				break; +			} +			else +			{ +				if(err == EAFNOSUPPORT) +					break; +			} +		} +		if(ip6_socket == INVALID_SOCKET) +			Com_Printf( "WARNING: Couldn't bind to a v6 ip address.\n"); +	} + +	if(net_enabled->integer & NET_ENABLEV4) +	{ +		for( i = 0 ; i < 10 ; i++ ) { +			ip_socket = NET_IPSocket( net_ip->string, port + i, &err ); +			if (ip_socket != INVALID_SOCKET) { +				Cvar_SetValue( "net_port", port + i ); + +				if (net_socksEnabled->integer) +					NET_OpenSocks( port + i ); + +				break; +			} +			else +			{ +				if(err == EAFNOSUPPORT) +					break; +			} +		} +		 +		if(ip_socket == INVALID_SOCKET) +			Com_Printf( "WARNING: Couldn't bind to a v4 ip address.\n"); +	} +} + + +//=================================================================== + + +/* +==================== +NET_GetCvars +==================== +*/ +static qboolean NET_GetCvars( void ) { +	int modified; + +#ifdef DEDICATED +	// I want server owners to explicitly turn on ipv6 support. +	net_enabled = Cvar_Get( "net_enabled", "1", CVAR_LATCH | CVAR_ARCHIVE ); +#else +	/* End users have it enabled so they can connect to ipv6-only hosts, but ipv4 will be +	 * used if available due to ping */ +	net_enabled = Cvar_Get( "net_enabled", "3", CVAR_LATCH | CVAR_ARCHIVE ); +#endif +	modified = net_enabled->modified; +	net_enabled->modified = qfalse; + +	net_ip = Cvar_Get( "net_ip", "0.0.0.0", CVAR_LATCH ); +	modified += net_ip->modified; +	net_ip->modified = qfalse; +	 +	net_ip6 = Cvar_Get( "net_ip6", "::", CVAR_LATCH ); +	modified += net_ip6->modified; +	net_ip6->modified = qfalse; +	 +	net_port = Cvar_Get( "net_port", va( "%i", PORT_SERVER ), CVAR_LATCH ); +	modified += net_port->modified; +	net_port->modified = qfalse; +	 +	net_port6 = Cvar_Get( "net_port6", va( "%i", PORT_SERVER ), CVAR_LATCH ); +	modified += net_port6->modified; +	net_port6->modified = qfalse; + +	// Some cvars for configuring multicast options which facilitates scanning for servers on local subnets. +	net_mcast6addr = Cvar_Get( "net_mcast6addr", NET_MULTICAST_IP6, CVAR_LATCH | CVAR_ARCHIVE ); +	modified += net_mcast6addr->modified; +	net_mcast6addr->modified = qfalse; + +#ifdef _WIN32 +	net_mcast6iface = Cvar_Get( "net_mcast6iface", "0", CVAR_LATCH | CVAR_ARCHIVE ); +#else +	net_mcast6iface = Cvar_Get( "net_mcast6iface", "", CVAR_LATCH | CVAR_ARCHIVE ); +#endif +	modified += net_mcast6iface->modified; +	net_mcast6iface->modified = qfalse; + +	net_socksEnabled = Cvar_Get( "net_socksEnabled", "0", CVAR_LATCH | CVAR_ARCHIVE ); +	modified += net_socksEnabled->modified; +	net_socksEnabled->modified = qfalse; + +	net_socksServer = Cvar_Get( "net_socksServer", "", CVAR_LATCH | CVAR_ARCHIVE ); +	modified += net_socksServer->modified; +	net_socksServer->modified = qfalse; + +	net_socksPort = Cvar_Get( "net_socksPort", "1080", CVAR_LATCH | CVAR_ARCHIVE ); +	modified += net_socksPort->modified; +	net_socksPort->modified = qfalse; + +	net_socksUsername = Cvar_Get( "net_socksUsername", "", CVAR_LATCH | CVAR_ARCHIVE ); +	modified += net_socksUsername->modified; +	net_socksUsername->modified = qfalse; + +	net_socksPassword = Cvar_Get( "net_socksPassword", "", CVAR_LATCH | CVAR_ARCHIVE ); +	modified += net_socksPassword->modified; +	net_socksPassword->modified = qfalse; + +	net_dropsim = Cvar_Get("net_dropsim", "", CVAR_TEMP); + +	return modified ? qtrue : qfalse; +} + + +/* +==================== +NET_Config +==================== +*/ +void NET_Config( qboolean enableNetworking ) { +	qboolean	modified; +	qboolean	stop; +	qboolean	start; + +	// get any latched changes to cvars +	modified = NET_GetCvars(); + +	if( !net_enabled->integer ) { +		enableNetworking = 0; +	} + +	// if enable state is the same and no cvars were modified, we have nothing to do +	if( enableNetworking == networkingEnabled && !modified ) { +		return; +	} + +	if( enableNetworking == networkingEnabled ) { +		if( enableNetworking ) { +			stop = qtrue; +			start = qtrue; +		} +		else { +			stop = qfalse; +			start = qfalse; +		} +	} +	else { +		if( enableNetworking ) { +			stop = qfalse; +			start = qtrue; +		} +		else { +			stop = qtrue; +			start = qfalse; +		} +		networkingEnabled = enableNetworking; +	} + +	if( stop ) { +		if ( ip_socket != INVALID_SOCKET ) { +			closesocket( ip_socket ); +			ip_socket = INVALID_SOCKET; +		} + +		if(multicast6_socket) +		{ +			if(multicast6_socket != ip6_socket) +				closesocket(multicast6_socket); +				 +			multicast6_socket = INVALID_SOCKET; +		} + +		if ( ip6_socket != INVALID_SOCKET ) { +			closesocket( ip6_socket ); +			ip6_socket = INVALID_SOCKET; +		} + +		if ( socks_socket != INVALID_SOCKET ) { +			closesocket( socks_socket ); +			socks_socket = INVALID_SOCKET; +		} +		 +	} + +	if( start ) +	{ +		if (net_enabled->integer) +		{ +			NET_OpenIP(); +			NET_SetMulticast6(); +		} +	} +} + + +/* +==================== +NET_Init +==================== +*/ +void NET_Init( void ) { +#ifdef _WIN32 +	int		r; + +	r = WSAStartup( MAKEWORD( 1, 1 ), &winsockdata ); +	if( r ) { +		Com_Printf( "WARNING: Winsock initialization failed, returned %d\n", r ); +		return; +	} + +	winsockInitialized = qtrue; +	Com_Printf( "Winsock Initialized\n" ); +#endif + +	NET_Config( qtrue ); +	 +	Cmd_AddCommand ("net_restart", NET_Restart_f); +} + + +/* +==================== +NET_Shutdown +==================== +*/ +void NET_Shutdown( void ) { +	if ( !networkingEnabled ) { +		return; +	} + +	NET_Config( qfalse ); + +#ifdef _WIN32 +	WSACleanup(); +	winsockInitialized = qfalse; +#endif +} + +/* +==================== +NET_Event + +Called from NET_Sleep which uses select() to determine which sockets have seen action. +==================== +*/ + +void NET_Event(fd_set *fdr) +{ +	byte bufData[MAX_MSGLEN + 1]; +	netadr_t from; +	msg_t netmsg; +	 +	while(1) +	{ +		MSG_Init(&netmsg, bufData, sizeof(bufData)); + +		if(NET_GetPacket(&from, &netmsg, fdr)) +		{ +			if(net_dropsim->value > 0.0f && net_dropsim->value <= 100.0f) +			{ +				// com_dropsim->value percent of incoming packets get dropped. +				if(rand() < (int) (((double) RAND_MAX) / 100.0 * (double) net_dropsim->value)) +					continue;          // drop this packet +                        } + +			if(com_sv_running->integer) +				Com_RunAndTimeServerPacket(&from, &netmsg); +			else +				CL_PacketEvent(from, &netmsg); +		} +		else +			break; +	} +} + +/* +==================== +NET_Sleep + +Sleeps msec or until something happens on the network +==================== +*/ +void NET_Sleep(int msec) +{ +	struct timeval timeout; +	fd_set fdr; +	int highestfd = -1, retval; + +	FD_ZERO(&fdr); + +	if(ip_socket != INVALID_SOCKET) +	{ +		FD_SET(ip_socket, &fdr); + +		highestfd = ip_socket; +	} +	if(ip6_socket != INVALID_SOCKET) +	{ +		FD_SET(ip6_socket, &fdr); +		 +		if(ip6_socket > highestfd) +			highestfd = ip6_socket; +	} + +	timeout.tv_sec = msec/1000; +	timeout.tv_usec = (msec%1000)*1000; +	 +#ifdef _WIN32 +	if(highestfd < 0) +	{ +		// windows ain't happy when select is called without valid FDs +		SleepEx(msec, 0); +		return; +	} +#endif + +	retval = select(highestfd + 1, &fdr, NULL, NULL, &timeout); +	 +	if(retval < 0) +		Com_Printf("Warning: select() syscall failed: %s\n", NET_ErrorString()); +	else if(retval > 0) +		NET_Event(&fdr); +} + +/* +==================== +NET_Restart_f +==================== +*/ +void NET_Restart_f( void ) { +	NET_Config( networkingEnabled ); +} diff --git a/src/qcommon/parse.c b/src/qcommon/parse.c new file mode 100644 index 0000000..b1f6efd --- /dev/null +++ b/src/qcommon/parse.c @@ -0,0 +1,3727 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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); +static qboolean Parse_AddDefineToSourceFromString( source_t *source, +                                                   char *string ); + +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} +}; + +/* +=============== +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') +         && !(token->subtype & TT_LONG)) +    { +      script->script_p++; +      token->subtype |= TT_LONG; +    } +    //check for an UNSIGNED number +    else if ( (c == 'u' || c == 'U') +        && !(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_ReadScriptToken +=============== +*/ +static int Parse_ReadScriptToken(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 == '\"') +  { +    memmove( string, string + 1, strlen( 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; +  int length; +  void *buffer; +  script_t *script; + +  length = FS_FOpenFileRead( filename, &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); +  // + +  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_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, lines; + +  lines = 0; +  //if there's no token already available +  while(!source->tokens) +  { +    //if there's a token to read from the script +    if( Parse_ReadScriptToken( source->scriptstack, token ) ) +    { +      token->linescrossed += lines; +      return qtrue; +    } + +    // if lines were crossed before the end of the script, count them +    lines += source->scriptstack->line - source->scriptstack->lastline; + +    //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; +  time_t t; + +  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) == '/')) +    { +      memmove(ptr, ptr+1, strlen(ptr)); +    } +    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 - 1); +    } +    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_ReadEnumeration + +It is assumed that the 'enum' token has already been consumed +This is fairly basic: it doesn't catch some fairly obvious errors like nested +enums, and enumerated names conflict with #define parameters +=============== +*/ +static qboolean Parse_ReadEnumeration( source_t *source ) +{ +  token_t newtoken; +  int value; + +  if( !Parse_ReadToken( source, &newtoken ) ) +    return qfalse; + +  if( newtoken.type != TT_PUNCTUATION || newtoken.subtype != P_BRACEOPEN ) +  { +    Parse_SourceError( source, "Found %s when expecting {\n", +                       newtoken.string ); +    return qfalse; +  } + +  for( value = 0;; value++ ) +  { +    token_t name; + +    // read the name +    if( !Parse_ReadToken( source, &name ) ) +      break; + +    // it's ok for the enum to end immediately +    if( name.type == TT_PUNCTUATION && name.subtype == P_BRACECLOSE ) +    { +      if( !Parse_ReadToken( source, &name ) ) +        break; + +      // ignore trailing semicolon +      if( name.type != TT_PUNCTUATION || name.subtype != P_SEMICOLON ) +        Parse_UnreadToken( source, &name ); + +      return qtrue; +    } + +    // ... but not for it to do anything else +    if( name.type != TT_NAME ) +    { +      Parse_SourceError( source, "Found %s when expecting identifier\n", +                         name.string ); +      return qfalse; +    } + +    if( !Parse_ReadToken( source, &newtoken ) ) +      break; + +    if( newtoken.type != TT_PUNCTUATION ) +    { +      Parse_SourceError( source, "Found %s when expecting , or = or }\n", +                         newtoken.string ); +      return qfalse; +    } + +    if( newtoken.subtype == P_ASSIGN ) +    { +      int neg = 1; + +      if( !Parse_ReadToken( source, &newtoken ) ) +        break; + +      // Parse_ReadToken doesn't seem to read negative numbers, so we do it +      // ourselves +      if( newtoken.type == TT_PUNCTUATION && newtoken.subtype == P_SUB ) +      { +        neg = -1; + +        // the next token should be the number +        if( !Parse_ReadToken( source, &newtoken ) ) +          break; +      } + +      if( newtoken.type != TT_NUMBER || !( newtoken.subtype & TT_INTEGER ) ) +      { +        Parse_SourceError( source, "Found %s when expecting integer\n", +                           newtoken.string ); +        return qfalse; +      } + +      // this is somewhat silly, but cheap to check +      if( neg == -1 && ( newtoken.subtype & TT_UNSIGNED ) ) +      { +        Parse_SourceWarning( source, "Value in enumeration is negative and " +                                     "unsigned\n" ); +      } + +      // set the new define value +      value = newtoken.intvalue * neg; + +      if( !Parse_ReadToken( source, &newtoken ) ) +        break; +    } + +    if( newtoken.type != TT_PUNCTUATION || ( newtoken.subtype != P_COMMA && +        newtoken.subtype != P_BRACECLOSE ) ) +    { +      Parse_SourceError( source, "Found %s when expecting , or }\n", +                         newtoken.string ); +      return qfalse; +    } + +    if( !Parse_AddDefineToSourceFromString( source, va( "%s %d\n", name.string, +                                                        value ) ) ) +    { +      Parse_SourceWarning( source, "Couldn't add define to source: %s = %d\n", +                           name.string, value ); +      return qfalse; +    } + +    if( newtoken.subtype == P_BRACECLOSE ) +    { +      if( !Parse_ReadToken( source, &name ) ) +        break; + +      // ignore trailing semicolon +      if( name.type != TT_PUNCTUATION || name.subtype != P_SEMICOLON ) +        Parse_UnreadToken( source, &name ); + +      return qtrue; +    } +  } + +  // got here if a ReadToken returned false +  return qfalse; +} + +/* +=============== +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; +      } +    } +    if( token->type == TT_NAME && !Q_stricmp( token->string, "enum" ) ) +    { +      if( !Parse_ReadEnumeration( 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_AddDefineToSourceFromString +=============== +*/ +static qboolean Parse_AddDefineToSourceFromString( source_t *source, +                                                   char *string ) +{ +  Parse_PushScript( source, Parse_LoadScriptMemory( string, strlen( string ), +                                                    "*extern" ) ); +  return Parse_Directive_define( source ); +} + +/* +=============== +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; +  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/puff.c b/src/qcommon/puff.c new file mode 100644 index 0000000..721854d --- /dev/null +++ b/src/qcommon/puff.c @@ -0,0 +1,758 @@ +/* + *  This is a modified version of Mark Adlers work, + *  see below for the original copyright. + *  2006 - Joerg Dietrich <dietrich_joerg@gmx.de> + */ + +/* + * puff.c + * Copyright (C) 2002-2004 Mark Adler + * For conditions of distribution and use, see copyright notice in puff.h + * version 1.8, 9 Jan 2004 + * + * puff.c is a simple inflate written to be an unambiguous way to specify the + * deflate format.  It is not written for speed but rather simplicity.  As a + * side benefit, this code might actually be useful when small code is more + * important than speed, such as bootstrap applications.  For typical deflate + * data, zlib's inflate() is about four times as fast as puff().  zlib's + * inflate compiles to around 20K on my machine, whereas puff.c compiles to + * around 4K on my machine (a PowerPC using GNU cc).  If the faster decode() + * function here is used, then puff() is only twice as slow as zlib's + * inflate(). + * + * All dynamically allocated memory comes from the stack.  The stack required + * is less than 2K bytes.  This code is compatible with 16-bit int's and + * assumes that long's are at least 32 bits.  puff.c uses the short data type, + * assumed to be 16 bits, for arrays in order to to conserve memory.  The code + * works whether integers are stored big endian or little endian. + * + * In the comments below are "Format notes" that describe the inflate process + * and document some of the less obvious aspects of the format.  This source + * code is meant to supplement RFC 1951, which formally describes the deflate + * format: + * + *    http://www.zlib.org/rfc-deflate.html + */ + +/* + * Change history: + * + * 1.0  10 Feb 2002     - First version + * 1.1  17 Feb 2002     - Clarifications of some comments and notes + *                      - Update puff() dest and source pointers on negative + *                        errors to facilitate debugging deflators + *                      - Remove longest from struct huffman -- not needed + *                      - Simplify offs[] index in construct() + *                      - Add input size and checking, using longjmp() to + *                        maintain easy readability + *                      - Use short data type for large arrays + *                      - Use pointers instead of long to specify source and + *                        destination sizes to avoid arbitrary 4 GB limits + * 1.2  17 Mar 2002     - Add faster version of decode(), doubles speed (!), + *                        but leave simple version for readabilty + *                      - Make sure invalid distances detected if pointers + *                        are 16 bits + *                      - Fix fixed codes table error + *                      - Provide a scanning mode for determining size of + *                        uncompressed data + * 1.3  20 Mar 2002     - Go back to lengths for puff() parameters [Jean-loup] + *                      - Add a puff.h file for the interface + *                      - Add braces in puff() for else do [Jean-loup] + *                      - Use indexes instead of pointers for readability + * 1.4  31 Mar 2002     - Simplify construct() code set check + *                      - Fix some comments + *                      - Add FIXLCODES #define + * 1.5   6 Apr 2002     - Minor comment fixes + * 1.6   7 Aug 2002     - Minor format changes + * 1.7   3 Mar 2003     - Added test code for distribution + *                      - Added zlib-like license + * 1.8   9 Jan 2004     - Added some comments on no distance codes case + */ + +#include <setjmp.h>             /* for setjmp(), longjmp(), and jmp_buf */ +#include "puff.h"		/* prototype for puff() */ + +#define local static            /* for local function definitions */ + +/* + * Maximums for allocations and loops.  It is not useful to change these -- + * they are fixed by the deflate format. + */ +#define MAXBITS 15              /* maximum bits in a code */ +#define MAXLCODES 286           /* maximum number of literal/length codes */ +#define MAXDCODES 30            /* maximum number of distance codes */ +#define MAXCODES (MAXLCODES+MAXDCODES)  /* maximum codes lengths to read */ +#define FIXLCODES 288           /* number of fixed literal/length codes */ + +/* input and output state */ +struct state { +    /* output state */ +    uint8_t *out;         /* output buffer */ +    uint32_t outlen;       /* available space at out */ +    uint32_t outcnt;       /* bytes written to out so far */ + +    /* input state */ +    uint8_t *in;          /* input buffer */ +    uint32_t inlen;        /* available input at in */ +    uint32_t incnt;        /* bytes read so far */ +    int32_t bitbuf;                 /* bit buffer */ +    int32_t bitcnt;                 /* number of bits in bit buffer */ + +    /* input limit error return state for bits() and decode() */ +    jmp_buf env; +}; + +/* + * Return need bits from the input stream.  This always leaves less than + * eight bits in the buffer.  bits() works properly for need == 0. + * + * Format notes: + * + * - Bits are stored in bytes from the least significant bit to the most + *   significant bit.  Therefore bits are dropped from the bottom of the bit + *   buffer, using shift right, and new bytes are appended to the top of the + *   bit buffer, using shift left. + */ +local int32_t bits(struct state *s, int32_t need) +{ +    int32_t val;           /* bit accumulator (can use up to 20 bits) */ + +    /* load at least need bits into val */ +    val = s->bitbuf; +    while (s->bitcnt < need) { +        if (s->incnt == s->inlen) longjmp(s->env, 1);   /* out of input */ +        val |= (int32_t)(s->in[s->incnt++]) << s->bitcnt;  /* load eight bits */ +        s->bitcnt += 8; +    } + +    /* drop need bits and update buffer, always zero to seven bits left */ +    s->bitbuf = (int32_t)(val >> need); +    s->bitcnt -= need; + +    /* return need bits, zeroing the bits above that */ +    return (int32_t)(val & ((1L << need) - 1)); +} + +/* + * Process a stored block. + * + * Format notes: + * + * - After the two-bit stored block type (00), the stored block length and + *   stored bytes are byte-aligned for fast copying.  Therefore any leftover + *   bits in the byte that has the last bit of the type, as many as seven, are + *   discarded.  The value of the discarded bits are not defined and should not + *   be checked against any expectation. + * + * - The second inverted copy of the stored block length does not have to be + *   checked, but it's probably a good idea to do so anyway. + * + * - A stored block can have zero length.  This is sometimes used to byte-align + *   subsets of the compressed data for random access or partial recovery. + */ +local int32_t stored(struct state *s) +{ +    uint32_t len;       /* length of stored block */ + +    /* discard leftover bits from current byte (assumes s->bitcnt < 8) */ +    s->bitbuf = 0; +    s->bitcnt = 0; + +    /* get length and check against its one's complement */ +    if (s->incnt + 4 > s->inlen) return 2;      /* not enough input */ +    len = s->in[s->incnt++]; +    len |= s->in[s->incnt++] << 8; +    if (s->in[s->incnt++] != (~len & 0xff) || +        s->in[s->incnt++] != ((~len >> 8) & 0xff)) +        return -2;                              /* didn't match complement! */ + +    /* copy len bytes from in to out */ +    if (s->incnt + len > s->inlen) return 2;    /* not enough input */ +    if (s->out != NULL) { +        if (s->outcnt + len > s->outlen) +            return 1;                           /* not enough output space */ +        while (len--) +            s->out[s->outcnt++] = s->in[s->incnt++]; +    } +    else {                                      /* just scanning */ +        s->outcnt += len; +        s->incnt += len; +    } + +    /* done with a valid stored block */ +    return 0; +} + +/* + * Huffman code decoding tables.  count[1..MAXBITS] is the number of symbols of + * each length, which for a canonical code are stepped through in order. + * symbol[] are the symbol values in canonical order, where the number of + * entries is the sum of the counts in count[].  The decoding process can be + * seen in the function decode() below. + */ +struct huffman { +    int16_t *count;       /* number of symbols of each length */ +    int16_t *symbol;      /* canonically ordered symbols */ +}; + +/* + * Decode a code from the stream s using huffman table h.  Return the symbol or + * a negative value if there is an error.  If all of the lengths are zero, i.e. + * an empty code, or if the code is incomplete and an invalid code is received, + * then -9 is returned after reading MAXBITS bits. + * + * Format notes: + * + * - The codes as stored in the compressed data are bit-reversed relative to + *   a simple integer ordering of codes of the same lengths.  Hence below the + *   bits are pulled from the compressed data one at a time and used to + *   build the code value reversed from what is in the stream in order to + *   permit simple integer comparisons for decoding.  A table-based decoding + *   scheme (as used in zlib) does not need to do this reversal. + * + * - The first code for the shortest length is all zeros.  Subsequent codes of + *   the same length are simply integer increments of the previous code.  When + *   moving up a length, a zero bit is appended to the code.  For a complete + *   code, the last code of the longest length will be all ones. + * + * - Incomplete codes are handled by this decoder, since they are permitted + *   in the deflate format.  See the format notes for fixed() and dynamic(). + */ +local int32_t decode(struct state *s, struct huffman *h) +{ +    int32_t len;            /* current number of bits in code */ +    int32_t code;           /* len bits being decoded */ +    int32_t first;          /* first code of length len */ +    int32_t count;          /* number of codes of length len */ +    int32_t index;          /* index of first code of length len in symbol table */ +    int32_t bitbuf;         /* bits from stream */ +    int32_t left;           /* bits left in next or left to process */ +    int16_t *next;        /* next number of codes */ + +    bitbuf = s->bitbuf; +    left = s->bitcnt; +    code = first = index = 0; +    len = 1; +    next = h->count + 1; +    while (1) { +        while (left--) { +            code |= bitbuf & 1; +            bitbuf >>= 1; +            count = *next++; +            if (code < first + count) { /* if length len, return symbol */ +                s->bitbuf = bitbuf; +                s->bitcnt = (s->bitcnt - len) & 7; +                return h->symbol[index + (code - first)]; +            } +            index += count;             /* else update for next length */ +            first += count; +            first <<= 1; +            code <<= 1; +            len++; +        } +        left = (MAXBITS+1) - len; +        if (left == 0) break; +        if (s->incnt == s->inlen) longjmp(s->env, 1);   /* out of input */ +        bitbuf = s->in[s->incnt++]; +        if (left > 8) left = 8; +    } +    return -9;                          /* ran out of codes */ +} + +/* + * Given the list of code lengths length[0..n-1] representing a canonical + * Huffman code for n symbols, construct the tables required to decode those + * codes.  Those tables are the number of codes of each length, and the symbols + * sorted by length, retaining their original order within each length.  The + * return value is zero for a complete code set, negative for an over- + * subscribed code set, and positive for an incomplete code set.  The tables + * can be used if the return value is zero or positive, but they cannot be used + * if the return value is negative.  If the return value is zero, it is not + * possible for decode() using that table to return an error--any stream of + * enough bits will resolve to a symbol.  If the return value is positive, then + * it is possible for decode() using that table to return an error for received + * codes past the end of the incomplete lengths. + * + * Not used by decode(), but used for error checking, h->count[0] is the number + * of the n symbols not in the code.  So n - h->count[0] is the number of + * codes.  This is useful for checking for incomplete codes that have more than + * one symbol, which is an error in a dynamic block. + * + * Assumption: for all i in 0..n-1, 0 <= length[i] <= MAXBITS + * This is assured by the construction of the length arrays in dynamic() and + * fixed() and is not verified by construct(). + * + * Format notes: + * + * - Permitted and expected examples of incomplete codes are one of the fixed + *   codes and any code with a single symbol which in deflate is coded as one + *   bit instead of zero bits.  See the format notes for fixed() and dynamic(). + * + * - Within a given code length, the symbols are kept in ascending order for + *   the code bits definition. + */ +local int32_t construct(struct huffman *h, int16_t *length, int32_t n) +{ +    int32_t symbol;         /* current symbol when stepping through length[] */ +    int32_t len;            /* current length when stepping through h->count[] */ +    int32_t left;           /* number of possible codes left of current length */ +    int16_t offs[MAXBITS+1];      /* offsets in symbol table for each length */ + +    /* count number of codes of each length */ +    for (len = 0; len <= MAXBITS; len++) +        h->count[len] = 0; +    for (symbol = 0; symbol < n; symbol++) +        (h->count[length[symbol]])++;   /* assumes lengths are within bounds */ +    if (h->count[0] == n)               /* no codes! */ +        return 0;                       /* complete, but decode() will fail */ + +    /* check for an over-subscribed or incomplete set of lengths */ +    left = 1;                           /* one possible code of zero length */ +    for (len = 1; len <= MAXBITS; len++) { +        left <<= 1;                     /* one more bit, double codes left */ +        left -= h->count[len];          /* deduct count from possible codes */ +        if (left < 0) return left;      /* over-subscribed--return negative */ +    }                                   /* left > 0 means incomplete */ + +    /* generate offsets into symbol table for each length for sorting */ +    offs[1] = 0; +    for (len = 1; len < MAXBITS; len++) +        offs[len + 1] = offs[len] + h->count[len]; + +    /* +     * put symbols in table sorted by length, by symbol order within each +     * length +     */ +    for (symbol = 0; symbol < n; symbol++) +        if (length[symbol] != 0) +            h->symbol[offs[length[symbol]]++] = symbol; + +    /* return zero for complete set, positive for incomplete set */ +    return left; +} + +/* + * Decode literal/length and distance codes until an end-of-block code. + * + * Format notes: + * + * - Compressed data that is after the block type if fixed or after the code + *   description if dynamic is a combination of literals and length/distance + *   pairs terminated by and end-of-block code.  Literals are simply Huffman + *   coded bytes.  A length/distance pair is a coded length followed by a + *   coded distance to represent a string that occurs earlier in the + *   uncompressed data that occurs again at the current location. + * + * - Literals, lengths, and the end-of-block code are combined into a single + *   code of up to 286 symbols.  They are 256 literals (0..255), 29 length + *   symbols (257..285), and the end-of-block symbol (256). + * + * - There are 256 possible lengths (3..258), and so 29 symbols are not enough + *   to represent all of those.  Lengths 3..10 and 258 are in fact represented + *   by just a length symbol.  Lengths 11..257 are represented as a symbol and + *   some number of extra bits that are added as an integer to the base length + *   of the length symbol.  The number of extra bits is determined by the base + *   length symbol.  These are in the static arrays below, lens[] for the base + *   lengths and lext[] for the corresponding number of extra bits. + * + * - The reason that 258 gets its own symbol is that the longest length is used + *   often in highly redundant files.  Note that 258 can also be coded as the + *   base value 227 plus the maximum extra value of 31.  While a good deflate + *   should never do this, it is not an error, and should be decoded properly. + * + * - If a length is decoded, including its extra bits if any, then it is + *   followed a distance code.  There are up to 30 distance symbols.  Again + *   there are many more possible distances (1..32768), so extra bits are added + *   to a base value represented by the symbol.  The distances 1..4 get their + *   own symbol, but the rest require extra bits.  The base distances and + *   corresponding number of extra bits are below in the static arrays dist[] + *   and dext[]. + * + * - Literal bytes are simply written to the output.  A length/distance pair is + *   an instruction to copy previously uncompressed bytes to the output.  The + *   copy is from distance bytes back in the output stream, copying for length + *   bytes. + * + * - Distances pointing before the beginning of the output data are not + *   permitted. + * + * - Overlapped copies, where the length is greater than the distance, are + *   allowed and common.  For example, a distance of one and a length of 258 + *   simply copies the last byte 258 times.  A distance of four and a length of + *   twelve copies the last four bytes three times.  A simple forward copy + *   ignoring whether the length is greater than the distance or not implements + *   this correctly.  You should not use memcpy() since its behavior is not + *   defined for overlapped arrays.  You should not use memmove() or bcopy() + *   since though their behavior -is- defined for overlapping arrays, it is + *   defined to do the wrong thing in this case. + */ +local int32_t codes(struct state *s, +                struct huffman *lencode, +                struct huffman *distcode) +{ +    int32_t symbol;         /* decoded symbol */ +    int32_t len;            /* length for copy */ +    uint32_t dist;          /* distance for copy */ +    static const int16_t lens[29] = { /* Size base for length 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}; +    static const int16_t lext[29] = { /* Extra bits for length 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}; +    static const int16_t dists[30] = { /* Offset base 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 int16_t dext[30] = { /* Extra bits for distance codes 0..29 */ +        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}; + +    /* decode literals and length/distance pairs */ +    do { +        symbol = decode(s, lencode); +        if (symbol < 0) return symbol;  /* invalid symbol */ +        if (symbol < 256) {             /* literal: symbol is the byte */ +            /* write out the literal */ +            if (s->out != NULL) { +                if (s->outcnt == s->outlen) return 1; +                s->out[s->outcnt] = symbol; +            } +            s->outcnt++; +        } +        else if (symbol > 256) {        /* length */ +            /* get and compute length */ +            symbol -= 257; +            if (symbol >= 29) return -9;        /* invalid fixed code */ +            len = lens[symbol] + bits(s, lext[symbol]); + +            /* get and check distance */ +            symbol = decode(s, distcode); +            if (symbol < 0) return symbol;      /* invalid symbol */ +            dist = dists[symbol] + bits(s, dext[symbol]); +            if (dist > s->outcnt) +                return -10;     /* distance too far back */ + +            /* copy length bytes from distance bytes back */ +            if (s->out != NULL) { +                if (s->outcnt + len > s->outlen) return 1; +                while (len--) { +                    s->out[s->outcnt] = s->out[s->outcnt - dist]; +                    s->outcnt++; +                } +            } +            else +                s->outcnt += len; +        } +    } while (symbol != 256);            /* end of block symbol */ + +    /* done with a valid fixed or dynamic block */ +    return 0; +} + +/* + * Process a fixed codes block. + * + * Format notes: + * + * - This block type can be useful for compressing small amounts of data for + *   which the size of the code descriptions in a dynamic block exceeds the + *   benefit of custom codes for that block.  For fixed codes, no bits are + *   spent on code descriptions.  Instead the code lengths for literal/length + *   codes and distance codes are fixed.  The specific lengths for each symbol + *   can be seen in the "for" loops below. + * + * - The literal/length code is complete, but has two symbols that are invalid + *   and should result in an error if received.  This cannot be implemented + *   simply as an incomplete code since those two symbols are in the "middle" + *   of the code.  They are eight bits long and the longest literal/length\ + *   code is nine bits.  Therefore the code must be constructed with those + *   symbols, and the invalid symbols must be detected after decoding. + * + * - The fixed distance codes also have two invalid symbols that should result + *   in an error if received.  Since all of the distance codes are the same + *   length, this can be implemented as an incomplete code.  Then the invalid + *   codes are detected while decoding. + */ +local int32_t fixed(struct state *s) +{ +    static int32_t virgin = 1; +    static int16_t lencnt[MAXBITS+1], lensym[FIXLCODES]; +    static int16_t distcnt[MAXBITS+1], distsym[MAXDCODES]; +    static struct huffman lencode = {lencnt, lensym}; +    static struct huffman distcode = {distcnt, distsym}; + +    /* build fixed huffman tables if first call (may not be thread safe) */ +    if (virgin) { +        int32_t symbol; +        int16_t lengths[FIXLCODES]; + +        /* literal/length table */ +        for (symbol = 0; symbol < 144; symbol++) +            lengths[symbol] = 8; +        for (; symbol < 256; symbol++) +            lengths[symbol] = 9; +        for (; symbol < 280; symbol++) +            lengths[symbol] = 7; +        for (; symbol < FIXLCODES; symbol++) +            lengths[symbol] = 8; +        construct(&lencode, lengths, FIXLCODES); + +        /* distance table */ +        for (symbol = 0; symbol < MAXDCODES; symbol++) +            lengths[symbol] = 5; +        construct(&distcode, lengths, MAXDCODES); + +        /* do this just once */ +        virgin = 0; +    } + +    /* decode data until end-of-block code */ +    return codes(s, &lencode, &distcode); +} + +/* + * Process a dynamic codes block. + * + * Format notes: + * + * - A dynamic block starts with a description of the literal/length and + *   distance codes for that block.  New dynamic blocks allow the compressor to + *   rapidly adapt to changing data with new codes optimized for that data. + * + * - The codes used by the deflate format are "canonical", which means that + *   the actual bits of the codes are generated in an unambiguous way simply + *   from the number of bits in each code.  Therefore the code descriptions + *   are simply a list of code lengths for each symbol. + * + * - The code lengths are stored in order for the symbols, so lengths are + *   provided for each of the literal/length symbols, and for each of the + *   distance symbols. + * + * - If a symbol is not used in the block, this is represented by a zero as + *   as the code length.  This does not mean a zero-length code, but rather + *   that no code should be created for this symbol.  There is no way in the + *   deflate format to represent a zero-length code. + * + * - The maximum number of bits in a code is 15, so the possible lengths for + *   any code are 1..15. + * + * - The fact that a length of zero is not permitted for a code has an + *   interesting consequence.  Normally if only one symbol is used for a given + *   code, then in fact that code could be represented with zero bits.  However + *   in deflate, that code has to be at least one bit.  So for example, if + *   only a single distance base symbol appears in a block, then it will be + *   represented by a single code of length one, in particular one 0 bit.  This + *   is an incomplete code, since if a 1 bit is received, it has no meaning, + *   and should result in an error.  So incomplete distance codes of one symbol + *   should be permitted, and the receipt of invalid codes should be handled. + * + * - It is also possible to have a single literal/length code, but that code + *   must be the end-of-block code, since every dynamic block has one.  This + *   is not the most efficient way to create an empty block (an empty fixed + *   block is fewer bits), but it is allowed by the format.  So incomplete + *   literal/length codes of one symbol should also be permitted. + * + * - If there are only literal codes and no lengths, then there are no distance + *   codes.  This is represented by one distance code with zero bits. + * + * - The list of up to 286 length/literal lengths and up to 30 distance lengths + *   are themselves compressed using Huffman codes and run-length encoding.  In + *   the list of code lengths, a 0 symbol means no code, a 1..15 symbol means + *   that length, and the symbols 16, 17, and 18 are run-length instructions. + *   Each of 16, 17, and 18 are follwed by extra bits to define the length of + *   the run.  16 copies the last length 3 to 6 times.  17 represents 3 to 10 + *   zero lengths, and 18 represents 11 to 138 zero lengths.  Unused symbols + *   are common, hence the special coding for zero lengths. + * + * - The symbols for 0..18 are Huffman coded, and so that code must be + *   described first.  This is simply a sequence of up to 19 three-bit values + *   representing no code (0) or the code length for that symbol (1..7). + * + * - A dynamic block starts with three fixed-size counts from which is computed + *   the number of literal/length code lengths, the number of distance code + *   lengths, and the number of code length code lengths (ok, you come up with + *   a better name!) in the code descriptions.  For the literal/length and + *   distance codes, lengths after those provided are considered zero, i.e. no + *   code.  The code length code lengths are received in a permuted order (see + *   the order[] array below) to make a short code length code length list more + *   likely.  As it turns out, very short and very long codes are less likely + *   to be seen in a dynamic code description, hence what may appear initially + *   to be a peculiar ordering. + * + * - Given the number of literal/length code lengths (nlen) and distance code + *   lengths (ndist), then they are treated as one long list of nlen + ndist + *   code lengths.  Therefore run-length coding can and often does cross the + *   boundary between the two sets of lengths. + * + * - So to summarize, the code description at the start of a dynamic block is + *   three counts for the number of code lengths for the literal/length codes, + *   the distance codes, and the code length codes.  This is followed by the + *   code length code lengths, three bits each.  This is used to construct the + *   code length code which is used to read the remainder of the lengths.  Then + *   the literal/length code lengths and distance lengths are read as a single + *   set of lengths using the code length codes.  Codes are constructed from + *   the resulting two sets of lengths, and then finally you can start + *   decoding actual compressed data in the block. + * + * - For reference, a "typical" size for the code description in a dynamic + *   block is around 80 bytes. + */ +local int32_t dynamic(struct state *s) +{ +    int32_t nlen, ndist, ncode;             /* number of lengths in descriptor */ +    int32_t index;                          /* index of lengths[] */ +    int32_t err;                            /* construct() return value */ +    int16_t lengths[MAXCODES];            /* descriptor code lengths */ +    int16_t lencnt[MAXBITS+1], lensym[MAXLCODES];         /* lencode memory */ +    int16_t distcnt[MAXBITS+1], distsym[MAXDCODES];       /* distcode memory */ +    struct huffman lencode = {lencnt, lensym};          /* length code */ +    struct huffman distcode = {distcnt, distsym};       /* distance code */ +    static const int16_t order[19] =      /* permutation of code length codes */ +        {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +    /* get number of lengths in each table, check lengths */ +    nlen = bits(s, 5) + 257; +    ndist = bits(s, 5) + 1; +    ncode = bits(s, 4) + 4; +    if (nlen > MAXLCODES || ndist > MAXDCODES) +        return -3;                      /* bad counts */ + +    /* read code length code lengths (really), missing lengths are zero */ +    for (index = 0; index < ncode; index++) +        lengths[order[index]] = bits(s, 3); +    for (; index < 19; index++) +        lengths[order[index]] = 0; + +    /* build huffman table for code lengths codes (use lencode temporarily) */ +    err = construct(&lencode, lengths, 19); +    if (err != 0) return -4;            /* require complete code set here */ + +    /* read length/literal and distance code length tables */ +    index = 0; +    while (index < nlen + ndist) { +        int32_t symbol;             /* decoded value */ +        int32_t len;                /* last length to repeat */ + +        symbol = decode(s, &lencode); +        if (symbol < 16)                /* length in 0..15 */ +            lengths[index++] = symbol; +        else {                          /* repeat instruction */ +            len = 0;                    /* assume repeating zeros */ +            if (symbol == 16) {         /* repeat last length 3..6 times */ +                if (index == 0) return -5;      /* no last length! */ +                len = lengths[index - 1];       /* last length */ +                symbol = 3 + bits(s, 2); +            } +            else if (symbol == 17)      /* repeat zero 3..10 times */ +                symbol = 3 + bits(s, 3); +            else                        /* == 18, repeat zero 11..138 times */ +                symbol = 11 + bits(s, 7); +            if (index + symbol > nlen + ndist) +                return -6;              /* too many lengths! */ +            while (symbol--)            /* repeat last or zero symbol times */ +                lengths[index++] = len; +        } +    } + +    /* build huffman table for literal/length codes */ +    err = construct(&lencode, lengths, nlen); +    if (err < 0 || (err > 0 && nlen - lencode.count[0] != 1)) +        return -7;      /* only allow incomplete codes if just one code */ + +    /* build huffman table for distance codes */ +    err = construct(&distcode, lengths + nlen, ndist); +    if (err < 0 || (err > 0 && ndist - distcode.count[0] != 1)) +        return -8;      /* only allow incomplete codes if just one code */ + +    /* decode data until end-of-block code */ +    return codes(s, &lencode, &distcode); +} + +/* + * Inflate source to dest.  On return, destlen and sourcelen are updated to the + * size of the uncompressed data and the size of the deflate data respectively. + * On success, the return value of puff() is zero.  If there is an error in the + * source data, i.e. it is not in the deflate format, then a negative value is + * returned.  If there is not enough input available or there is not enough + * output space, then a positive error is returned.  In that case, destlen and + * sourcelen are not updated to facilitate retrying from the beginning with the + * provision of more input data or more output space.  In the case of invalid + * inflate data (a negative error), the dest and source pointers are updated to + * facilitate the debugging of deflators. + * + * puff() also has a mode to determine the size of the uncompressed output with + * no output written.  For this dest must be (uint8_t *)0.  In this case, + * the input value of *destlen is ignored, and on return *destlen is set to the + * size of the uncompressed output. + * + * The return codes are: + * + *   2:  available inflate data did not terminate + *   1:  output space exhausted before completing inflate + *   0:  successful inflate + *  -1:  invalid block type (type == 3) + *  -2:  stored block length did not match one's complement + *  -3:  dynamic block code description: too many length or distance codes + *  -4:  dynamic block code description: code lengths codes incomplete + *  -5:  dynamic block code description: repeat lengths with no first length + *  -6:  dynamic block code description: repeat more than specified lengths + *  -7:  dynamic block code description: invalid literal/length code lengths + *  -8:  dynamic block code description: invalid distance code lengths + *  -9:  invalid literal/length or distance code in fixed or dynamic block + * -10:  distance is too far back in fixed or dynamic block + * + * Format notes: + * + * - Three bits are read for each block to determine the kind of block and + *   whether or not it is the last block.  Then the block is decoded and the + *   process repeated if it was not the last block. + * + * - The leftover bits in the last byte of the deflate data after the last + *   block (if it was a fixed or dynamic block) are undefined and have no + *   expected values to check. + */ +int32_t puff(uint8_t  *dest,           /* pointer to destination pointer */ +             uint32_t *destlen,        /* amount of output space */ +             uint8_t  *source,         /* pointer to source data pointer */ +             uint32_t *sourcelen)      /* amount of input available */ +{ +    struct state s;             /* input/output state */ +    int32_t last, type;             /* block information */ +    int32_t err;                    /* return value */ + +    /* initialize output state */ +    s.out = dest; +    s.outlen = *destlen;                /* ignored if dest is NULL */ +    s.outcnt = 0; + +    /* initialize input state */ +    s.in = source; +    s.inlen = *sourcelen; +    s.incnt = 0; +    s.bitbuf = 0; +    s.bitcnt = 0; + +    /* return if bits() or decode() tries to read past available input */ +    if (setjmp(s.env) != 0)             /* if came back here via longjmp() */ +        err = 2;                        /* then skip do-loop, return error */ +    else { +        /* process blocks until last block or error */ +        do { +            last = bits(&s, 1);         /* one if last block */ +            type = bits(&s, 2);         /* block type 0..3 */ +            err = type == 0 ? stored(&s) : +                  (type == 1 ? fixed(&s) : +                   (type == 2 ? dynamic(&s) : +                    -1));               /* type == 3, invalid */ +            if (err != 0) break;        /* return with error */ +        } while (!last); +    } + +    /* update the lengths and return */ +    if (err <= 0) { +        *destlen = s.outcnt; +        *sourcelen = s.incnt; +    } +    return err; +} diff --git a/src/qcommon/puff.h b/src/qcommon/puff.h new file mode 100644 index 0000000..14070f6 --- /dev/null +++ b/src/qcommon/puff.h @@ -0,0 +1,43 @@ +/* + *  This is a modified version of Mark Adlers work, + *  see below for the original copyright. + *  2006 - Joerg Dietrich <dietrich_joerg@gmx.de> + */ + +/* puff.h +  Copyright (C) 2002, 2003 Mark Adler, all rights reserved +  version 1.7, 3 Mar 2002 + +  This software is provided 'as-is', without any express or implied +  warranty.  In no event will the author 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. + +  Mark Adler    madler@alumni.caltech.edu + */ + +#ifndef __PUFF_H +#define __PUFF_H + +#include "q_shared.h"			/* for definitions of the <stdint.h> types */ + +/* + * See puff.c for purpose and usage. + */ +int32_t puff(uint8_t  *dest,		/* pointer to destination pointer */ +             uint32_t *destlen,		/* amount of output space */ +             uint8_t  *source,		/* pointer to source data pointer */ +             uint32_t *sourcelen);	/* amount of input available */ + +#endif // __PUFF_H diff --git a/src/qcommon/q_math.c b/src/qcommon/q_math.c new file mode 100644 index 0000000..e435590 --- /dev/null +++ b/src/qcommon/q_math.c @@ -0,0 +1,1295 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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.2, 0.2, 0.2, 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 ); // 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 ) +{ +	floatint_t 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 + +	return y; +} + +float Q_fabs( float f ) { +	floatint_t fi; +	fi.f = f; +	fi.i &= 0x7FFFFFFF; +	return fi.f; +} +#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 +================== +*/ +int BoxOnPlaneSide(vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ +	float	dist[2]; +	int		sides, b, i; + +	// 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 +	dist[0] = dist[1] = 0; +	if (p->signbits < 8) // >= 8: default case is original code (dist[0]=dist[1]=0) +	{ +		for (i=0 ; i<3 ; i++) +		{ +			b = (p->signbits >> i) & 1; +			dist[ b] += p->normal[i]*emaxs[i]; +			dist[!b] += p->normal[i]*emins[i]; +		} +	} + +	sides = 0; +	if (dist[0] >= p->dist) +		sides = 1; +	if (dist[1] < p->dist) +		sides |= 2; + +	return sides; +} + + +/* +================= +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]; +	} +} + +qboolean BoundsIntersect(const vec3_t mins, const vec3_t maxs, +		const vec3_t mins2, const vec3_t maxs2) +{ +	if ( maxs[0] < mins2[0] || +		maxs[1] < mins2[1] || +		maxs[2] < mins2[2] || +		mins[0] > maxs2[0] || +		mins[1] > maxs2[1] || +		mins[2] > maxs2[2]) +	{ +		return qfalse; +	} + +	return qtrue; +} + +qboolean BoundsIntersectSphere(const vec3_t mins, const vec3_t maxs, +		const vec3_t origin, vec_t radius) +{ +	if ( origin[0] - radius > maxs[0] || +		origin[0] + radius < mins[0] || +		origin[1] - radius > maxs[1] || +		origin[1] + radius < mins[1] || +		origin[2] - radius > maxs[2] || +		origin[2] + radius < mins[2]) +	{ +		return qfalse; +	} + +	return qtrue; +} + +qboolean BoundsIntersectPoint(const vec3_t mins, const vec3_t maxs, +		const vec3_t origin) +{ +	if ( origin[0] > maxs[0] || +		origin[0] < mins[0] || +		origin[1] > maxs[1] || +		origin[1] < mins[1] || +		origin[2] > maxs[2] || +		origin[2] < mins[2]) +	{ +		return qfalse; +	} + +	return qtrue; +} + +vec_t VectorNormalize( vec3_t v ) { +	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) +	{ +		ilength = 1/length; +		out[0] = v[0]*ilength; +		out[1] = v[1]*ilength; +		out[2] = v[2]*ilength; +	} else { +		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 ) +{ +	floatint_t fi; + +	fi.f = x; +	fi.ui &= 0x7FFFFFFF; +	fi.ui = 0x7F800000 - fi.ui; + +	return (int)( (unsigned int)fi.ui >> 31 ); +} diff --git a/src/qcommon/q_platform.h b/src/qcommon/q_platform.h new file mode 100644 index 0000000..3e9619e --- /dev/null +++ b/src/qcommon/q_platform.h @@ -0,0 +1,380 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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 +#define idsparc 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 + +#if defined(__sparc__) && !defined(C_ONLY) +#define idsparc 1 +#else +#define idsparc 0 +#endif + +#endif + +#ifndef __ASM_I386__ // don't include the C bits if included from qasm.h + +// for windows fastcall option +#define QDECL + +//================================================================= WIN64/32 === + +#if defined(_WIN64) || defined(__WIN64__) + +#undef QDECL +#define QDECL __cdecl + +#if defined( _MSC_VER ) +#define OS_STRING "win_msvc64" +#elif defined __MINGW64__ +#define OS_STRING "win_mingw64" +#endif + +#define ID_INLINE inline +#define PATH_SEP '\\' + +#if defined( __WIN64__ )  +#define ARCH_STRING "x86_64" +#elif defined _M_ALPHA +#define ARCH_STRING "AXP" +#endif + +#define Q3_LITTLE_ENDIAN + +#define DLL_EXT ".dll" + +#elif defined(_WIN32) || defined(__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 +#elif defined __x86_64__ +#define ARCH_STRING "x86_64" +#define Q3_LITTLE_ENDIAN +#endif + +#define DLL_EXT ".dylib" + +#endif + +//================================================================= LINUX === + +#if defined(__linux__) || defined(__FreeBSD_kernel__) + +#include <endian.h> + +#if defined(__linux__) +#define OS_STRING "linux" +#else +#define OS_STRING "kFreeBSD" +#endif + +#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 + +//=================================================================== BSD === + +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) + +#include <sys/types.h> +#include <machine/endian.h> + +#ifndef __BSD__ +  #define __BSD__ +#endif + +#if defined(__FreeBSD__) +#define OS_STRING "freebsd" +#elif defined(__OpenBSD__) +#define OS_STRING "openbsd" +#elif defined(__NetBSD__) +#define OS_STRING "netbsd" +#endif + +#define ID_INLINE inline +#define PATH_SEP '/' + +#ifdef __i386__ +#define ARCH_STRING "x86" +#elif defined __amd64__ +#define ARCH_STRING "amd64" +#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 + +//================================================================= 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 + +//================================================================== IRIX === + +#ifdef __sgi + +#define OS_STRING "irix" +#define ID_INLINE __inline +#define PATH_SEP '/' + +#define ARCH_STRING "mips" + +#define Q3_BIG_ENDIAN // SGI's MIPS are always big endian + +#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..8b13aba --- /dev/null +++ b/src/qcommon/q_shared.c @@ -0,0 +1,1522 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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_GetExtension +============ +*/ +const char *COM_GetExtension( const char *name ) { +	int length, i; + +	length = strlen(name)-1; +	i = length; + +	while (name[i] != '.') +	{ +		i--; +		if (name[i] == '/' || i == 0) +			return ""; // no extension +	} + +	return &name[i+1]; +} + + +/* +============ +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; +} + +float FloatSwap (const float *f) { +	floatint_t out; + +	out.f = *f; +	out.ui = LongSwap(out.ui); + +	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); +	Q_vsnprintf (string, sizeof(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); +	Q_vsnprintf (string, sizeof(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; +} + +/* +================== +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, ")" ); +} + +/* +=================== +Com_HexStrToInt +=================== +*/ +int Com_HexStrToInt( const char *str ) +{ +	if ( !str || !str[ 0 ] ) +		return -1; + +	// check for hex code +	if( str[ 0 ] == '0' && str[ 1 ] == 'x' ) +	{ +		int i, n = 0; + +		for( i = 2; i < strlen( str ); i++ ) +		{ +			char digit; + +			n *= 16; + +			digit = tolower( str[ i ] ); + +			if( digit >= '0' && digit <= '9' ) +				digit -= '0'; +			else if( digit >= 'a' && digit <= 'f' ) +				digit = digit - 'a' + 10; +			else +				return -1; + +			n += digit; +		} + +		return n; +	} + +	return -1; +} + +/* +============================================================================ + +					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 ); +} + +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; +} + +qboolean Q_isanumber( const char *s ) +{ +	char *p; +	double d; + +	if( *s == '\0' ) +		return qfalse; + +	d = strtod( s, &p ); + +	return *p == '\0'; +} + +qboolean Q_isintegral( float f ) +{ +	return (int)f == f; +} + +#ifdef _MSC_VER +/* +============= +Q_vsnprintf +  +Special wrapper function for Microsoft's broken _vsnprintf() function. +MinGW comes with its own snprintf() which is not broken. +============= +*/ + +int Q_vsnprintf(char *str, size_t size, const char *format, va_list ap) +{ +	int retval; +	 +	retval = _vsnprintf(str, size, format, ap); + +	if(retval < 0 || retval == size) +	{ +		// Microsoft doesn't adhere to the C99 standard of vsnprintf, +		// which states that the return value must be the number of +		// bytes written if the output string had sufficient length. +		// +		// Obviously we cannot determine that value from Microsoft's +		// implementation, so we have no choice but to return size. +		 +		str[size - 1] = '\0'; +		return size; +	} +	 +	return retval; +} +#endif + +/* +============= +Q_strncpyz +  +Safe strncpy that ensures a trailing zero +============= +*/ +void Q_strncpyz( char *dest, const char *src, int destsize ) { +  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; + +        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 ); +} + +/* +* Find the first occurrence of find in s. +*/ +const char *Q_stristr( const char *s, const char *find) +{ +  char c, sc; +  size_t len; + +  if ((c = *find++) != 0) +  { +    if (c >= 'a' && c <= 'z') +    { +      c -= ('a' - 'A'); +    } +    len = strlen(find); +    do +    { +      do +      { +        if ((sc = *s++) == 0) +          return NULL; +        if (sc >= 'a' && sc <= 'z') +        { +          sc -= ('a' - 'A'); +        } +      } while (sc != c); +    } while (Q_stricmpn(s, find, len) != 0); +    s--; +  } +  return s; +} + + +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; +} + +int Q_CountChar(const char *string, char tocount) +{ +	int count; +	 +	for(count = 0; *string; string++) +	{ +		if(*string == tocount) +			count++; +	} +	 +	return count; +} + +void Q_StripIndentMarker(char *string) +{ +	int i, j; + +	for (i = j = 0; string[i]; i++) { +		if (string[i] != INDENT_MARKER) { +			string[j++] = string[i]; +		} +	} +	string[j] = 0; +} + +void Q_ParseNewlines( char *dest, const char *src, int destsize ) +{ +  for( ; *src && destsize > 1; src++, destsize-- ) +    *dest++ = ( ( *src == '\\' && *( ++src ) == 'n' ) ? '\n' : *src ); +  *dest++ = '\0'; +} + +void QDECL Com_sprintf(char *dest, int size, const char *fmt, ...) +{ +	int		len; +	va_list		argptr; + +	va_start (argptr,fmt); +	len = Q_vsnprintf(dest, size, fmt, argptr); +	va_end (argptr); + +	if(len >= size) +		Com_Printf("Com_sprintf: Output length %d too short, require %d bytes.\n", size, len); +} + +/* +============ +va + +does a varargs printf into a temp buffer, so I don't need to have +varargs versions of all text functions. +============ +*/ +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); +	Q_vsnprintf (buf, sizeof(*string), 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) ) +		{ +			memmove(start, s, strlen(s) + 1); // 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 ) { +	const char* ch = s; + +	while ( *ch != '\0' ) +	{ +		if( !Q_isprint( *ch ) ) +			return qfalse; + +		if( *ch == '\"' ) +			return qfalse; + +		if( *ch == ';' ) +			return qfalse; + +		++ch; +	} + +	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 +Includes and retains zero-length values +================== +*/ +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) +		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: setting %s to %s " +			"failed\n", key, value); +		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; +} + +/* +============ +Com_ClientListContains +============ +*/ +qboolean Com_ClientListContains( const clientList_t *list, int clientNum ) +{ +  if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list ) +    return qfalse; +  if( clientNum < 32 ) +    return ( ( list->lo & ( 1 << clientNum ) ) != 0 ); +  else +    return ( ( list->hi & ( 1 << ( clientNum - 32 ) ) ) != 0 ); +} + +/* +============ +Com_ClientListAdd +============ +*/ +void Com_ClientListAdd( clientList_t *list, int clientNum ) +{ +  if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list ) +    return; +  if( clientNum < 32 ) +    list->lo |= ( 1 << clientNum ); +  else +    list->hi |= ( 1 << ( clientNum - 32 ) ); +} + +/* +============ +Com_ClientListRemove +============ +*/ +void Com_ClientListRemove( clientList_t *list, int clientNum ) +{ +  if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list ) +    return; +  if( clientNum < 32 ) +    list->lo &= ~( 1 << clientNum ); +  else +    list->hi &= ~( 1 << ( clientNum - 32 ) ); +} + +/* +============ +Com_ClientListString +============ +*/ +char *Com_ClientListString( const clientList_t *list ) +{ +  static char s[ 17 ]; + +  s[ 0 ] = '\0'; +  if( !list ) +    return s; +  Com_sprintf( s, sizeof( s ), "%08x%08x", list->hi, list->lo ); +  return s; +} + +/* +============ +Com_ClientListParse +============ +*/ +void Com_ClientListParse( clientList_t *list, const char *s ) +{ +  if( !list ) +    return; +  list->lo = 0; +  list->hi = 0; +  if( !s ) +    return; +  if( strlen( s ) != 16 ) +    return; +  sscanf( s, "%x%x", &list->hi, &list->lo ); +} diff --git a/src/qcommon/q_shared.h b/src/qcommon/q_shared.h new file mode 100644 index 0000000..a93b630 --- /dev/null +++ b/src/qcommon/q_shared.h @@ -0,0 +1,1424 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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 PRODUCT_NAME              "tremulous" + +#ifdef _MSC_VER +# define PRODUCT_VERSION          "gpp1" +#endif + +#define CLIENT_WINDOW_TITLE       "Tremulous " PRODUCT_VERSION +#define CLIENT_WINDOW_MIN_TITLE   "Tremulous" +#define Q3_VERSION                 PRODUCT_NAME " " PRODUCT_VERSION + +#define GAMENAME_FOR_MASTER       "Tremulous" +#define HEARTBEAT_FOR_MASTER      GAMENAME_FOR_MASTER +#define FLATLINE_FOR_MASTER       GAMENAME_FOR_MASTER "dead" + +#define MAX_TEAMNAME 32 +#define MAX_MASTER_SERVERS      5 // number of supported master servers + +#define DEMOEXT	"dm_"			// standard demo extension + +#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 + +#if (defined _MSC_VER) +#define Q_EXPORT __declspec(dllexport) +#elif (defined __SUNPRO_C) +#define Q_EXPORT __global +#elif ((__GNUC__ >= 3) && (!__EMX__) && (!sun)) +#define Q_EXPORT __attribute__((visibility("default"))) +#else +#define Q_EXPORT +#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" + +typedef int intptr_t; + +#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> + +#ifdef _MSC_VER +  #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; + +  // vsnprintf is ISO/IEC 9899:1999 +  // abstracting this to make it portable +  int Q_vsnprintf(char *str, size_t size, const char *format, va_list ap); +#else +  #include <stdint.h> + +  #define Q_vsnprintf vsnprintf +  #define Q_snprintf snprintf +#endif + +#endif + + +#include "q_platform.h" + +//============================================================= + +typedef unsigned char 		byte; + +typedef enum {qfalse, qtrue}	qboolean; + +typedef union { +	float f; +	int i; +	unsigned int ui; +} floatint_t; + +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)) +#define PADLEN(x,y)	(PAD((x), (y)) - (x)) + +#ifdef __GNUC__ +#define QALIGN(x) __attribute__((aligned(x))) +#else +#define QALIGN(x) +#endif + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define STRING(s)			#s +// expand constants before stringifying them +#define XSTRING(s)			STRING(s) + +#define	MAX_QINT			0x7fffffff +#define	MIN_QINT			(-MAX_QINT-1) + +#define ARRAY_LEN(x)			(sizeof(x) / sizeof(*(x))) + + +// 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_NEWS_STRING		10000 + +#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 + +#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) && isalnum(*((p)+1))) // ^[0-9a-zA-Z] + +#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') & 0x07) + +#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" + +#define INDENT_MARKER '\v' +void Q_StripIndentMarker(char *string); + +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 Vector4Lerp( 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]),\ +  (r)[3]=(s)[3]+(f)*((e)[3]-(s)[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); + +qboolean BoundsIntersect(const vec3_t mins, const vec3_t maxs, +		const vec3_t mins2, const vec3_t maxs2); +qboolean BoundsIntersectSphere(const vec3_t mins, const vec3_t maxs, +		const vec3_t origin, vec_t radius); +qboolean BoundsIntersectPoint(const vec3_t mins, const vec3_t maxs, +		const vec3_t origin); + +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 ); +const char	*COM_GetExtension( const char *name ); +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); +int Com_HexStrToInt( const char *str ); + +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 ); + +typedef struct  +{ +  unsigned int hi; +  unsigned int lo; +} clientList_t; + +qboolean Com_ClientListContains( const clientList_t *list, int clientNum ); +void Com_ClientListAdd( clientList_t *list, int clientNum ); +void Com_ClientListRemove( clientList_t *list, int clientNum ); +char *Com_ClientListString( const clientList_t *list ); +void Com_ClientListParse( clientList_t *list, const char *s ); + +// 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 ); +qboolean Q_isanumber( const char *s ); +qboolean Q_isintegral( float f ); + +// 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 ); +const char	*Q_stristr( const char *s, const char *find); + +// 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 ); +// parse "\n" into '\n' +void Q_ParseNewlines( char *dest, const char *src, int destsize ); +// Count the number of char tocount encountered in string +int Q_CountChar(const char *string, char tocount); + +//============================================= + +// 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		0x0001	// set to cause it to be saved to vars.rc +					// used for system variables, not for player +					// specific configurations +#define	CVAR_USERINFO		0x0002	// sent to server on connect or change +#define	CVAR_SERVERINFO		0x0004	// sent in response to front end requests +#define	CVAR_SYSTEMINFO		0x0008	// these cvars will be duplicated on all clients +#define	CVAR_INIT		0x0010	// don't allow change from console at all, +					// but can be set from the command line +#define	CVAR_LATCH		0x0020	// 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		0x0040	// display only, cannot be set by user at all +#define	CVAR_USER_CREATED	0x0080	// created by a set command +#define	CVAR_TEMP		0x0100	// can be set even when cheats are disabled, but is not archived +#define CVAR_CHEAT		0x0200	// can not be changed if cheats are disabled +#define CVAR_NORESTART		0x0400	// do not clear when a cvar_restart is issued + +#define CVAR_SERVER_CREATED	0x0800	// cvar was created by a server the client connected to. +#define CVAR_VM_CREATED		0x1000	// cvar was created exclusively in one of the VMs. +#define CVAR_PROTECTED		0x2000	// prevent modifying this var from VMs or the server +#define CVAR_NONEXISTENT	0xFFFFFFFF	// Cvar doesn't exist. + +// nothing outside the Cvar_*() functions should modify these fields! +typedef struct cvar_s cvar_t; + +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 ) +	qboolean	validate; +	qboolean	integral; +	float			min; +	float			max; + +	cvar_t *next; +	cvar_t *prev; +	cvar_t *hashNext; +	cvar_t *hashPrev; +	int			hashIndex; +}; + +#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_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) +#define GENTITYNUM_MASK		(MAX_GENTITIES - 1) + +// 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_MISC    			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			tauntTimer;		// don't allow another taunt until this runs out + +	int			weaponAnim;		// 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			misc[MAX_MISC];	// misc data +	int			ammo;			// ammo held +	int			clips;			// clips held + +	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           // activate upgrade +#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_DODGE        64          // start a dodge or sprint motion +#define BUTTON_USE_EVOLVE   128         // use target or open evolve menu +#define BUTTON_SPRINT	256 + +#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 +} 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		misc;			// bit flags +	int		weapon;			// determines weapon and flash model, etc +	int		legsAnim;		// mask off ANIM_TOGGLEBIT +	int		torsoAnim;		// mask off ANIM_TOGGLEBIT +	int		weaponAnim;		// 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 +// AS_MPLAYER is no longer used +#define AS_GLOBAL			0 +#define AS_MPLAYER		1 +#define AS_LOCAL			2 +#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 MAX_EMOTICON_NAME_LEN 20 +#define MAX_EMOTICONS 300 + +typedef struct +{ +  char      name[ MAX_EMOTICON_NAME_LEN ]; +#ifndef GAME +  int       width; +  qhandle_t shader; +#endif +} emoticon_t; + +// flags for com_downloadPrompt +#define DLP_TYPE_MASK 0x0f +#define DLP_IGNORE    0x01 // don't download anything +#define DLP_CURL      0x02 // download via HTTP redirect +#define DLP_UDP       0x04 // download from server +#define DLP_SHOW      0x10 // prompt needs to be shown +#define DLP_PROMPTED  0x20 // prompt has been processed by client +#define DLP_STALE     0x40 // prompt is not being shown by UI VM + +#define LERP( a, b, w ) ( ( a ) * ( 1.0f - ( w ) ) + ( b ) * ( w ) ) +#define LUMA( red, green, blue ) ( 0.2126f * ( red ) + 0.7152f * ( green ) + 0.0722f * ( blue ) ) + +#endif	// __Q_SHARED_H diff --git a/src/qcommon/qcommon.h b/src/qcommon/qcommon.h new file mode 100644 index 0000000..db5812f --- /dev/null +++ b/src/qcommon/qcommon.h @@ -0,0 +1,1194 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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); +int MSG_HashKey(const char *string, int maxlen); + +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); +int		MSG_LookaheadByte (msg_t *msg); + +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 NET_ENABLEV4            0x01 +#define NET_ENABLEV6            0x02 +// if this flag is set, always attempt ipv6 connections instead of ipv4 if a v6 address is found. +#define NET_PRIOV6              0x04 +// disables ipv6 multicast support if set. +#define NET_DISABLEMCAST        0x08 + + +#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 = 0,					// an address lookup failed +	NA_LOOPBACK, +	NA_BROADCAST, +	NA_IP, +	NA_IP6, +	NA_MULTICAST6, +	NA_UNSPEC +} netadrtype_t; + +typedef enum { +	NS_CLIENT, +	NS_SERVER +} netsrc_t; + +#define NET_ADDRSTRMAXLEN 48	// maximum length of an IPv6 address string including trailing '\0' +typedef struct { +	netadrtype_t	type; + +	byte	ip[4]; +	byte	ip6[16]; + +	unsigned short	port; +	unsigned long	scope_id;	// Needed for IPv6 link-local addresses +} netadr_t; + +void		NET_Init( void ); +void		NET_Shutdown( void ); +void		NET_Restart_f( 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_CompareBaseAdrMask(netadr_t a, netadr_t b, int netmask); +qboolean	NET_CompareBaseAdr (netadr_t a, netadr_t b); +qboolean	NET_IsLocalAddress (netadr_t adr); +const char	*NET_AdrToString (netadr_t a); +const char	*NET_AdrToStringwPort (netadr_t a); +int		NET_StringToAdr ( const char *s, netadr_t *a, netadrtype_t family); +qboolean	NET_GetLoopPacket (netsrc_t sock, netadr_t *net_from, msg_t *net_message); +void		NET_JoinMulticast6(void); +void		NET_LeaveMulticast6(void); +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	70 + +// 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			30700 +#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, + +	// svc_extension follows a svc_EOF, followed by another svc_* ... +	//  this keeps legacy clients compatible. +	svc_extension, +	svc_voip,     // not wrapped in USE_VOIP, so this value is reserved. +}; + + +// +// 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, + +	// clc_extension follows a clc_EOF, followed by another clc_* ... +	//  this keeps legacy servers compatible. +	clc_extension, +	clc_voip,   // not wrapped in USE_VOIP, so this value is reserved. +}; + +/* +============================================================== + +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); +void	VM_Forced_Unload_Start(void); +void	VM_Forced_Unload_Done(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) +{ +	floatint_t fi; +	fi.i = (int) x; +	return fi.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 ); + +typedef void (*completionFunc_t)( char *args, int argNum ); + +// don't allow VMs to remove system commands +void	Cmd_RemoveCommandSafe( const char *cmd_name ); + +void	Cmd_CommandCompletion( void(*callback)(const char *s) ); +// callback with each valid string +void Cmd_SetCommandCompletionFunc( const char *command, +	completionFunc_t complete ); +void Cmd_CompleteArgument( const char *command, char *args, int argNum ); +void Cmd_CompleteCfgName( char *args, int argNum ); + +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); +void	Cmd_Args_Sanitize( 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_SetSafe( const char *var_name, const char *value ); +// sometimes we set variables from an untrusted source: fail if flags & CVAR_PROTECTED + +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 ); +void	Cvar_SetValueSafe( const char *var_name, float value ); +// expands value to a string and calls Cvar_Set/Cvar_SetSafe + +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_CheckRange( cvar_t *cv, float minVal, float maxVal, qboolean shouldBeIntegral ); + +void	Cvar_Restart(qboolean unsetVM); +void	Cvar_Restart_f( void ); + +void Cvar_CompleteCvarName( char *args, int argNum ); + +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 + +#define	MAX_FILE_HANDLES	64 + +#define BASEGAME "base" + +#ifdef DEDICATED +#	define Q3CONFIG_CFG "autogen_server.cfg" +#else +#	define Q3CONFIG_CFG "autogen.cfg" +#endif + +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 + +void FS_AddGameDirectory( const char *path, const char *dir ); + +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 ); + +qboolean FS_CreatePath (char *OSPath); + +char *FS_FindDll( const char *filename ); + +char   *FS_BuildOSPath( const char *base, const char *game, const char *qpath ); +qboolean FS_CompareZipChecksum(const char *zipfile); + +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 ); +fileHandle_t	FS_FOpenFileAppend( const char *filename ); +fileHandle_t	FS_FCreateOpenPipeFile( const char *filename ); +// will properly create any needed paths and deal with seperater character issues + +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_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), qboolean allowNonPureFilesOnDisk ); + +const char *FS_GetCurrentGameDir(void); + +/* +============================================================== + +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 ); +void Field_CompleteKeyname( void ); +void Field_CompleteFilename( const char *dir, +		const char *ext, qboolean stripExt, qboolean allowNonPureFilesOnDisk ); +void Field_CompleteCommand( char *cmd, +		qboolean doCommands, qboolean doCvars ); + +/* +============================================================== + +MISC + +============================================================== +*/ + +// returned by Sys_GetProcessorFeatures +typedef enum +{ +  CF_RDTSC      = 1 << 0, +  CF_MMX        = 1 << 1, +  CF_MMX_EXT    = 1 << 2, +  CF_3DNOW      = 1 << 3, +  CF_3DNOW_EXT  = 1 << 4, +  CF_SSE        = 1 << 5, +  CF_SSE2       = 1 << 6, +  CF_ALTIVEC    = 1 << 7 +} cpuFeatures_t; + +// centralized and cleaned, that's the max string you can send to a Com_Printf / Com_DPrintf (above gets truncated) +#define	MAXPRINTMSG	4096 + + +typedef enum { +	// SE_NONE must be 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* +} 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; + +void		Com_QueueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ); +int			Com_EventLoop( void ); +sysEvent_t	Com_GetSystemEvent( void ); + +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 ); +void		Com_GameRestart(int checksumFeed, qboolean clientRestart); + +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_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_RunAndTimeServerPacket(netadr_t *evFrom, msg_t *buf); + +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_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_ansiColor; +extern	cvar_t	*com_unfocused; +extern	cvar_t	*com_maxfpsUnfocused; +extern	cvar_t	*com_minimized; +extern	cvar_t	*com_maxfpsMinimized; +extern	cvar_t	*com_altivec; +extern	cvar_t	*com_homepath; + +// 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	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( char *finalmsg ); +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( qboolean rendererOnly ); +// start all the client stuff using the hunk + +void CL_Snd_Restart(void); +// Restart sound subsystem + +void Key_KeynameCompletion( void(*callback)(const char *s) ); +// for keyname autocompletion + +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? + +// AVI files have the start of pixel lines 4 byte-aligned +#define AVI_LINE_PADDING 4 + +// +// 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 ); +int SV_FrameMsec(void); +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; + +void	Sys_Init (void); + +// general development dll loading for virtual machine testing +void	* QDECL Sys_LoadDll( const char *name, 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 ); + +cpuFeatures_t Sys_GetProcessorFeatures( void ); + +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, netadrtype_t family ); +//Does NOT parse port numbers, only base addresses. + +qboolean	Sys_IsLANAddress (netadr_t adr); +void		Sys_ShowIP(void); + +qboolean Sys_Mkdir( const char *path ); +FILE	*Sys_Mkfifo( const char *ospath ); +char	*Sys_Cwd( void ); +void	Sys_SetDefaultInstallPath(const char *path); +char	*Sys_DefaultInstallPath(void); + +#ifdef MACOS_X +char    *Sys_DefaultAppPath(void); +#endif + +void  Sys_SetDefaultHomePath(const char *path); +char	*Sys_DefaultHomePath(void); +const char	*Sys_TempPath(void); +const char *Sys_Dirname( char *path ); +const char *Sys_Basename( char *path ); +char *Sys_ConsoleInput(void); + +char **Sys_ListFiles( const char *directory, const char *extension, char *filter, int *numfiles, qboolean wantsubs ); +void	Sys_FreeFileList( char **list ); +void	Sys_Sleep(int msec); + +qboolean Sys_LowPhysicalMemory( void ); + +void Sys_SetEnv(const char *name, const char *value); + +typedef enum +{ +	DR_YES = 0, +	DR_NO = 1, +	DR_OK = 0, +	DR_CANCEL = 1 +} dialogResult_t; + +typedef enum +{ +	DT_INFO, +	DT_WARNING, +	DT_ERROR, +	DT_YES_NO, +	DT_OK_CANCEL +} dialogType_t; + +dialogResult_t Sys_Dialog( dialogType_t type, const char *message, const char *title ); + +qboolean Sys_WritePIDFile( 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); + +// don't use if you don't know what you're doing. +int		Huff_getBloc(void); +void	Huff_setBloc(int _bloc); + +extern huffman_t clientHuffTables; + +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); + +#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..f6bd292 --- /dev/null +++ b/src/qcommon/qfiles.h @@ -0,0 +1,582 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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; + +/* +======================================================================== + +.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..c1f2a0d --- /dev/null +++ b/src/qcommon/surfaceflags.h @@ -0,0 +1,91 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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) + +// custominfoparms below +#define	CONTENTS_NOALIENBUILD			0x1000	//disallow alien building +#define	CONTENTS_NOHUMANBUILD			0x2000	//disallow human building +#define	CONTENTS_NOBUILD				0x4000	//disallow 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 + +// custominfoparms below +#define	SURF_NOALIENBUILD	0x80000  //disallow alien building +#define	SURF_NOHUMANBUILD	0x100000 //disallow human building +#define	SURF_NOBUILD		0x200000 //disallow building diff --git a/src/qcommon/unzip.c b/src/qcommon/unzip.c new file mode 100644 index 0000000..b307e98 --- /dev/null +++ b/src/qcommon/unzip.c @@ -0,0 +1,1608 @@ +/* unzip.c -- IO for uncompress .zip files using zlib +   Version 1.01e, February 12th, 2005 + +   Copyright (C) 1998-2005 Gilles Vollant + +   Read unzip.h for more info +*/ + +/* Decryption code comes from crypt.c by Info-ZIP but has been greatly reduced in terms of +compatibility with older software. The following is from the original crypt.c. Code +woven in by Terry Thorsen 1/2003. +*/ +/* +  Copyright (c) 1990-2000 Info-ZIP.  All rights reserved. + +  See the accompanying file LICENSE, version 2000-Apr-09 or later +  (the contents of which are also included in zip.h) for terms of use. +  If, for some reason, all these files are missing, the Info-ZIP license +  also may be found at:  ftp://ftp.info-zip.org/pub/infozip/license.html +*/ +/* +  crypt.c (full version) by Info-ZIP.      Last revised:  [see crypt.h] + +  The encryption/decryption parts of this source code (as opposed to the +  non-echoing password parts) were originally written in Europe.  The +  whole source package can be freely distributed, including from the USA. +  (Prior to January 2000, re-export from the US was a violation of US law.) + */ + +/* +  This encryption code is a direct transcription of the algorithm from +  Roger Schlafly, described by Phil Katz in the file appnote.txt.  This +  file (appnote.txt) is distributed with the PKZIP program (even in the +  version without encryption capabilities). + */ + + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" +#include "unzip.h" + +#ifndef local +#  define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + + +#ifndef CASESENSITIVITYDEFAULT_NO +#  if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) +#    define CASESENSITIVITYDEFAULT_NO +#  endif +#endif + + +#ifndef UNZ_BUFSIZE +#define UNZ_BUFSIZE (16384) +#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) + + + + +const char unz_copyright[] = +   " unzip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; + +/* unz_file_info_interntal contain internal info about a file in zipfile*/ +typedef struct unz_file_info_internal_s +{ +    uLong offset_curfile;/* relative offset of local header 4 bytes */ +} unz_file_info_internal; + + +/* 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 */ + +    uLong pos_in_zipfile;       /* position in byte on the zipfile, for fseek*/ +    uLong stream_initialised;   /* flag set if stream structure is initialised*/ + +    uLong offset_local_extrafield;/* offset of the local extra field */ +    uInt  size_local_extrafield;/* size of the local extra field */ +    uLong pos_local_extrafield;   /* position in the local extra field in read*/ + +    uLong crc32;                /* crc32 of all data uncompressed */ +    uLong crc32_wait;           /* crc32 we must obtain after decompress all */ +    uLong rest_read_compressed; /* number of byte to be decompressed */ +    uLong rest_read_uncompressed;/*number of byte to be obtained after decomp*/ +    zlib_filefunc_def z_filefunc; +    voidpf filestream;        /* io structore of the zipfile */ +    uLong compression_method;   /* compression method (0==store) */ +    uLong byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ +    int   raw; +} file_in_zip_read_info_s; + + +/* unz_s contain internal information about the zipfile +*/ +typedef struct +{ +    zlib_filefunc_def z_filefunc; +    voidpf filestream;        /* io structore of the zipfile */ +    unz_global_info gi;       /* public global information */ +    uLong byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ +    uLong num_file;             /* number of the current file in the zipfile*/ +    uLong pos_in_central_dir;   /* pos of the current file in the central dir*/ +    uLong current_file_ok;      /* flag about the usability of the current file*/ +    uLong central_pos;          /* position of the beginning of the central dir*/ + +    uLong size_central_dir;     /* size of the central directory  */ +    uLong 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 */ +    int encrypted; +#    ifndef NOUNCRYPT +    unsigned long keys[3];     /* keys defining the pseudo-random sequence */ +    const unsigned long* pcrc_32_tab; +#    endif +} unz_s; + + +#ifndef NOUNCRYPT +#include "crypt.h" +#endif + +/* =========================================================================== +     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. +*/ + + +local int unzlocal_getByte OF(( +    const zlib_filefunc_def* pzlib_filefunc_def, +    voidpf filestream, +    int *pi)); + +local int unzlocal_getByte(pzlib_filefunc_def,filestream,pi) +    const zlib_filefunc_def* pzlib_filefunc_def; +    voidpf filestream; +    int *pi; +{ +    unsigned char c; +    int err = (int)ZREAD(*pzlib_filefunc_def,filestream,&c,1); +    if (err==1) +    { +        *pi = (int)c; +        return UNZ_OK; +    } +    else +    { +        if (ZERROR(*pzlib_filefunc_def,filestream)) +            return UNZ_ERRNO; +        else +            return UNZ_EOF; +    } +} + + +/* =========================================================================== +   Reads a long in LSB order from the given gz_stream. Sets +*/ +local int unzlocal_getShort OF(( +    const zlib_filefunc_def* pzlib_filefunc_def, +    voidpf filestream, +    uLong *pX)); + +local int unzlocal_getShort (pzlib_filefunc_def,filestream,pX) +    const zlib_filefunc_def* pzlib_filefunc_def; +    voidpf filestream; +    uLong *pX; +{ +    uLong x ; +    int i = 0; +    int err; + +    err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); +    x = (uLong)i; + +    if (err==UNZ_OK) +        err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); +    x += ((uLong)i)<<8; + +    if (err==UNZ_OK) +        *pX = x; +    else +        *pX = 0; +    return err; +} + +local int unzlocal_getLong OF(( +    const zlib_filefunc_def* pzlib_filefunc_def, +    voidpf filestream, +    uLong *pX)); + +local int unzlocal_getLong (pzlib_filefunc_def,filestream,pX) +    const zlib_filefunc_def* pzlib_filefunc_def; +    voidpf filestream; +    uLong *pX; +{ +    uLong x ; +    int i = 0; +    int err; + +    err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); +    x = (uLong)i; + +    if (err==UNZ_OK) +        err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); +    x += ((uLong)i)<<8; + +    if (err==UNZ_OK) +        err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); +    x += ((uLong)i)<<16; + +    if (err==UNZ_OK) +        err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); +    x += ((uLong)i)<<24; + +    if (err==UNZ_OK) +        *pX = x; +    else +        *pX = 0; +    return err; +} + +local int strcmpcasenosensitive_internal OF(( +    const char* fileName1, +    const char* fileName2)); + +/* My own strcmpi / strcasecmp */ +local int strcmpcasenosensitive_internal (fileName1,fileName2) +    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 ZEXPORT unzStringFileNameCompare (fileName1,fileName2,iCaseSensitivity) +    const char* fileName1; +    const char* fileName2; +    int iCaseSensitivity; +{ +    if (iCaseSensitivity==0) +        iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE; + +    if (iCaseSensitivity==1) +        return strcmp(fileName1,fileName2); + +    return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2); +} + +#ifndef BUFREADCOMMENT +#define BUFREADCOMMENT (0x400) +#endif + +/* +  Locate the Central directory of a zipfile (at the end, just before +    the global comment) +*/ +local uLong unzlocal_SearchCentralDir OF(( +    const zlib_filefunc_def* pzlib_filefunc_def, +    voidpf filestream)); + +local uLong unzlocal_SearchCentralDir(pzlib_filefunc_def,filestream) +    const zlib_filefunc_def* pzlib_filefunc_def; +    voidpf filestream; +{ +    unsigned char* buf; +    uLong uSizeFile; +    uLong uBackRead; +    uLong uMaxBack=0xffff; /* maximum size of global comment */ +    uLong uPosFound=0; + +    if (ZSEEK(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) +        return 0; + + +    uSizeFile = ZTELL(*pzlib_filefunc_def,filestream); + +    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 (ZSEEK(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) +            break; + +        if (ZREAD(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) +            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; +} + +/* +  Open a Zip file. path contain the full pathname (by example, +     on a Windows NT computer "c:\\test\\zlib114.zip" or on an Unix computer +     "zlib/zlib114.zip". +     If the zipfile cannot be opened (file doesn'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 ZEXPORT unzOpen2 (path, pzlib_filefunc_def) +    const char *path; +    zlib_filefunc_def* pzlib_filefunc_def; +{ +    unz_s us; +    unz_s *s; +    uLong central_pos,uL; + +    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; + +    if (unz_copyright[0]!=' ') +        return NULL; + +    if (pzlib_filefunc_def==NULL) +        fill_fopen_filefunc(&us.z_filefunc); +    else +        us.z_filefunc = *pzlib_filefunc_def; + +    us.filestream= (*(us.z_filefunc.zopen_file))(us.z_filefunc.opaque, +                                                 path, +                                                 ZLIB_FILEFUNC_MODE_READ | +                                                 ZLIB_FILEFUNC_MODE_EXISTING); +    if (us.filestream==NULL) +        return NULL; + +    central_pos = unzlocal_SearchCentralDir(&us.z_filefunc,us.filestream); +    if (central_pos==0) +        err=UNZ_ERRNO; + +    if (ZSEEK(us.z_filefunc, us.filestream, +                                      central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) +        err=UNZ_ERRNO; + +    /* the signature, already checked */ +    if (unzlocal_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) +        err=UNZ_ERRNO; + +    /* number of this disk */ +    if (unzlocal_getShort(&us.z_filefunc, us.filestream,&number_disk)!=UNZ_OK) +        err=UNZ_ERRNO; + +    /* number of the disk with the start of the central directory */ +    if (unzlocal_getShort(&us.z_filefunc, us.filestream,&number_disk_with_CD)!=UNZ_OK) +        err=UNZ_ERRNO; + +    /* total number of entries in the central dir on this disk */ +    if (unzlocal_getShort(&us.z_filefunc, us.filestream,&us.gi.number_entry)!=UNZ_OK) +        err=UNZ_ERRNO; + +    /* total number of entries in the central dir */ +    if (unzlocal_getShort(&us.z_filefunc, us.filestream,&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(&us.z_filefunc, us.filestream,&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(&us.z_filefunc, us.filestream,&us.offset_central_dir)!=UNZ_OK) +        err=UNZ_ERRNO; + +    /* zipfile comment length */ +    if (unzlocal_getShort(&us.z_filefunc, us.filestream,&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) +    { +        ZCLOSE(us.z_filefunc, us.filestream); +        return NULL; +    } + +    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; +    us.encrypted = 0; + + +    s=(unz_s*)ALLOC(sizeof(unz_s)); +    *s=us; +    unzGoToFirstFile((unzFile)s); +    return (unzFile)s; +} + + +extern unzFile ZEXPORT unzOpen (path) +    const char *path; +{ +    return unzOpen2(path, NULL); +} + +/* +  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 ZEXPORT unzClose (file) +    unzFile file; +{ +    unz_s* s; +    if (file==NULL) +        return UNZ_PARAMERROR; +    s=(unz_s*)file; + +    if (s->pfile_in_zip_read!=NULL) +        unzCloseCurrentFile(file); + +    ZCLOSE(s->z_filefunc, s->filestream); +    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 ZEXPORT unzGetGlobalInfo (file,pglobal_info) +    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) +*/ + +local void unzlocal_DosDateToTmuDate OF(( +    uLong ulDosDate, +    tm_unz* ptm)); +         + +local void unzlocal_DosDateToTmuDate (ulDosDate, ptm) +    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 +*/ +local int unzlocal_GetCurrentFileInfoInternal OF((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)); + +local int unzlocal_GetCurrentFileInfoInternal (file, +                                              pfile_info, +                                              pfile_info_internal, +                                              szFileName, fileNameBufferSize, +                                              extraField, extraFieldBufferSize, +                                              szComment,  commentBufferSize) +    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 (ZSEEK(s->z_filefunc, s->filestream, +              s->pos_in_central_dir+s->byte_before_the_zipfile, +              ZLIB_FILEFUNC_SEEK_SET)!=0) +        err=UNZ_ERRNO; + + +    /* we check the magic */ +    if (err==UNZ_OK) +    { +        if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) +            err=UNZ_ERRNO; +        else if (uMagic!=0x02014b50) +            err=UNZ_BADZIPFILE; +    } + +    if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.version) != UNZ_OK) +        err=UNZ_ERRNO; + +    if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.version_needed) != UNZ_OK) +        err=UNZ_ERRNO; + +    if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.flag) != UNZ_OK) +        err=UNZ_ERRNO; + +    if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.compression_method) != UNZ_OK) +        err=UNZ_ERRNO; + +    if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.dosDate) != UNZ_OK) +        err=UNZ_ERRNO; + +    unzlocal_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date); + +    if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.crc) != UNZ_OK) +        err=UNZ_ERRNO; + +    if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.compressed_size) != UNZ_OK) +        err=UNZ_ERRNO; + +    if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.uncompressed_size) != UNZ_OK) +        err=UNZ_ERRNO; + +    if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.size_filename) != UNZ_OK) +        err=UNZ_ERRNO; + +    if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_extra) != UNZ_OK) +        err=UNZ_ERRNO; + +    if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_comment) != UNZ_OK) +        err=UNZ_ERRNO; + +    if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.disk_num_start) != UNZ_OK) +        err=UNZ_ERRNO; + +    if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.internal_fa) != UNZ_OK) +        err=UNZ_ERRNO; + +    if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.external_fa) != UNZ_OK) +        err=UNZ_ERRNO; + +    if (unzlocal_getLong(&s->z_filefunc, s->filestream,&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 (ZREAD(s->z_filefunc, s->filestream,szFileName,uSizeRead)!=uSizeRead) +                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 (ZSEEK(s->z_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) +                lSeek=0; +            else +                err=UNZ_ERRNO; +        } +        if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0)) +            if (ZREAD(s->z_filefunc, s->filestream,extraField,uSizeRead)!=uSizeRead) +                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 (ZSEEK(s->z_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) +                lSeek=0; +            else +                err=UNZ_ERRNO; +        } +        if ((file_info.size_file_comment>0) && (commentBufferSize>0)) +            if (ZREAD(s->z_filefunc, s->filestream,szComment,uSizeRead)!=uSizeRead) +                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 ZEXPORT unzGetCurrentFileInfo (file, +                                          pfile_info, +                                          szFileName, fileNameBufferSize, +                                          extraField, extraFieldBufferSize, +                                          szComment,  commentBufferSize) +    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 ZEXPORT unzGoToFirstFile (file) +    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 ZEXPORT unzGoToNextFile (file) +    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->gi.number_entry != 0xffff)    /* 2^16 files overflow hack */ +      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; +} + + +/* +  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 ZEXPORT unzLocateFile (file, szFileName, iCaseSensitivity) +    unzFile file; +    const char *szFileName; +    int iCaseSensitivity; +{ +    unz_s* s; +    int err; + +    /* We remember the 'current' position in the file so that we can jump +     * back there if we fail. +     */ +    unz_file_info cur_file_infoSaved; +    unz_file_info_internal cur_file_info_internalSaved; +    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; + +    /* Save the current state */ +    num_fileSaved = s->num_file; +    pos_in_central_dirSaved = s->pos_in_central_dir; +    cur_file_infoSaved = s->cur_file_info; +    cur_file_info_internalSaved = s->cur_file_info_internal; + +    err = unzGoToFirstFile(file); + +    while (err == UNZ_OK) +    { +        char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1]; +        err = unzGetCurrentFileInfo(file,NULL, +                                    szCurrentFileName,sizeof(szCurrentFileName)-1, +                                    NULL,0,NULL,0); +        if (err == UNZ_OK) +        { +            if (unzStringFileNameCompare(szCurrentFileName, +                                            szFileName,iCaseSensitivity)==0) +                return UNZ_OK; +            err = unzGoToNextFile(file); +        } +    } + +    /* We failed, so restore the state of the 'current file' to where we +     * were. +     */ +    s->num_file = num_fileSaved ; +    s->pos_in_central_dir = pos_in_central_dirSaved ; +    s->cur_file_info = cur_file_infoSaved; +    s->cur_file_info_internal = cur_file_info_internalSaved; +    return err; +} + + +/* +/////////////////////////////////////////// +// Contributed by Ryan Haksi (mailto://cryogen@infoserve.net) +// I need random access +// +// Further optimization could be realized by adding an ability +// to cache the directory in memory. The goal being a single +// comprehensive file read to put the file I need in a memory. +*/ + +/* +typedef struct unz_file_pos_s +{ +    uLong pos_in_zip_directory;   // offset in file +    uLong num_of_file;            // # of file +} unz_file_pos; +*/ + +extern int ZEXPORT unzGetFilePos(file, file_pos) +    unzFile file; +    unz_file_pos* file_pos; +{ +    unz_s* s; + +    if (file==NULL || file_pos==NULL) +        return UNZ_PARAMERROR; +    s=(unz_s*)file; +    if (!s->current_file_ok) +        return UNZ_END_OF_LIST_OF_FILE; + +    file_pos->pos_in_zip_directory  = s->pos_in_central_dir; +    file_pos->num_of_file           = s->num_file; + +    return UNZ_OK; +} + +extern int ZEXPORT unzGoToFilePos(file, file_pos) +    unzFile file; +    unz_file_pos* file_pos; +{ +    unz_s* s; +    int err; + +    if (file==NULL || file_pos==NULL) +        return UNZ_PARAMERROR; +    s=(unz_s*)file; + +    /* jump to the right spot */ +    s->pos_in_central_dir = file_pos->pos_in_zip_directory; +    s->num_file           = file_pos->num_of_file; + +    /* set the current file */ +    err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, +                                               &s->cur_file_info_internal, +                                               NULL,0,NULL,0,NULL,0); +    /* return results */ +    s->current_file_ok = (err == UNZ_OK); +    return err; +} + +/* +// Unzip Helper Functions - should be here? +/////////////////////////////////////////// +*/ + +/* +  Read the local header of the current zipfile +  Check the coherency of the local header and info in the end of central +        directory about this file +  store in *piSizeVar the size of extra info in local header +        (filename and size of extra field data) +*/ + +local int unzlocal_CheckCurrentFileCoherencyHeader OF(( +    unz_s* s, +    uInt* piSizeVar, +    uLong *poffset_local_extrafield, +    uInt  *psize_local_extrafield)); + + +local int unzlocal_CheckCurrentFileCoherencyHeader (s,piSizeVar, +                                                    poffset_local_extrafield, +                                                    psize_local_extrafield) +    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 (ZSEEK(s->z_filefunc, s->filestream,s->cur_file_info_internal.offset_curfile + +                                s->byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET)!=0) +        return UNZ_ERRNO; + + +    if (err==UNZ_OK) +    { +        if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) +            err=UNZ_ERRNO; +        else if (uMagic!=0x04034b50) +            err=UNZ_BADZIPFILE; +    } + +    if (unzlocal_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) +        err=UNZ_ERRNO; +/* +    else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion)) +        err=UNZ_BADZIPFILE; +*/ +    if (unzlocal_getShort(&s->z_filefunc, s->filestream,&uFlags) != UNZ_OK) +        err=UNZ_ERRNO; + +    if (unzlocal_getShort(&s->z_filefunc, s->filestream,&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->z_filefunc, s->filestream,&uData) != UNZ_OK) /* date/time */ +        err=UNZ_ERRNO; + +    if (unzlocal_getLong(&s->z_filefunc, s->filestream,&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->z_filefunc, s->filestream,&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->z_filefunc, s->filestream,&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->z_filefunc, s->filestream,&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->z_filefunc, s->filestream,&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 ZEXPORT unzOpenCurrentFile3 (file, method, level, raw, password) +    unzFile file; +    int* method; +    int* level; +    int raw; +    const char* password; +{ +    int err=UNZ_OK; +    uInt iSizeVar; +    unz_s* s; +    file_in_zip_read_info_s* pfile_in_zip_read_info; +    uLong offset_local_extrafield;  /* offset of the local extra field */ +    uInt  size_local_extrafield;    /* size of the local extra field */ +#    ifndef NOUNCRYPT +    char source[12]; +#    else +    if (password != NULL) +        return UNZ_PARAMERROR; +#    endif + +    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; +    pfile_in_zip_read_info->raw=raw; + +    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 (method!=NULL) +        *method = (int)s->cur_file_info.compression_method; + +    if (level!=NULL) +    { +        *level = 6; +        switch (s->cur_file_info.flag & 0x06) +        { +          case 6 : *level = 1; break; +          case 4 : *level = 2; break; +          case 2 : *level = 9; break; +        } +    } + +    if ((s->cur_file_info.compression_method!=0) && +        (s->cur_file_info.compression_method!=Z_DEFLATED)) +        err=UNZ_BADZIPFILE; + +    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->filestream=s->filestream; +    pfile_in_zip_read_info->z_filefunc=s->z_filefunc; +    pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile; + +    pfile_in_zip_read_info->stream.total_out = 0; + +    if ((s->cur_file_info.compression_method==Z_DEFLATED) && +        (!raw)) +    { +      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 = (voidpf)0; +      pfile_in_zip_read_info->stream.next_in = (voidpf)0; +      pfile_in_zip_read_info->stream.avail_in = 0; + +      err=inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS); +      if (err == Z_OK) +        pfile_in_zip_read_info->stream_initialised=1; +      else +      { +        TRYFREE(pfile_in_zip_read_info); +        return err; +      } +        /* 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; + +#    ifndef NOUNCRYPT +    if (password != NULL) +    { +        int i; +        s->pcrc_32_tab = get_crc_table(); +        init_keys(password,s->keys,s->pcrc_32_tab); +        if (ZSEEK(s->z_filefunc, s->filestream, +                  s->pfile_in_zip_read->pos_in_zipfile + +                     s->pfile_in_zip_read->byte_before_the_zipfile, +                  SEEK_SET)!=0) +            return UNZ_INTERNALERROR; +        if(ZREAD(s->z_filefunc, s->filestream,source, 12)<12) +            return UNZ_INTERNALERROR; + +        for (i = 0; i<12; i++) +            zdecode(s->keys,s->pcrc_32_tab,source[i]); + +        s->pfile_in_zip_read->pos_in_zipfile+=12; +        s->encrypted=1; +    } +#    endif + + +    return UNZ_OK; +} + +extern int ZEXPORT unzOpenCurrentFile (file) +    unzFile file; +{ +    return unzOpenCurrentFile3(file, NULL, NULL, 0, NULL); +} + +extern int ZEXPORT unzOpenCurrentFilePassword (file, password) +    unzFile file; +    const char* password; +{ +    return unzOpenCurrentFile3(file, NULL, NULL, 0, password); +} + +extern int ZEXPORT unzOpenCurrentFile2 (file,method,level,raw) +    unzFile file; +    int* method; +    int* level; +    int raw; +{ +    return unzOpenCurrentFile3(file, method, level, raw, NULL); +} + +/* +  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 ZEXPORT unzReadCurrentFile  (file, buf, len) +    unzFile file; +    voidp 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 = (Bytef*)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->raw))) +        pfile_in_zip_read_info->stream.avail_out = +            (uInt)pfile_in_zip_read_info->rest_read_uncompressed; + +    if ((len>pfile_in_zip_read_info->rest_read_compressed+ +           pfile_in_zip_read_info->stream.avail_in) && +         (pfile_in_zip_read_info->raw)) +        pfile_in_zip_read_info->stream.avail_out = +            (uInt)pfile_in_zip_read_info->rest_read_compressed+ +            pfile_in_zip_read_info->stream.avail_in; + +    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 (ZSEEK(pfile_in_zip_read_info->z_filefunc, +                      pfile_in_zip_read_info->filestream, +                      pfile_in_zip_read_info->pos_in_zipfile + +                         pfile_in_zip_read_info->byte_before_the_zipfile, +                         ZLIB_FILEFUNC_SEEK_SET)!=0) +                return UNZ_ERRNO; +            if (ZREAD(pfile_in_zip_read_info->z_filefunc, +                      pfile_in_zip_read_info->filestream, +                      pfile_in_zip_read_info->read_buffer, +                      uReadThis)!=uReadThis) +                return UNZ_ERRNO; + + +#            ifndef NOUNCRYPT +            if(s->encrypted) +            { +                uInt i; +                for(i=0;i<uReadThis;i++) +                  pfile_in_zip_read_info->read_buffer[i] = +                      zdecode(s->keys,s->pcrc_32_tab, +                              pfile_in_zip_read_info->read_buffer[i]); +            } +#            endif + + +            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 = +                (Bytef*)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) || (pfile_in_zip_read_info->raw)) +        { +            uInt uDoCopy,i ; + +            if ((pfile_in_zip_read_info->stream.avail_in == 0) && +                (pfile_in_zip_read_info->rest_read_compressed == 0)) +                return (iRead==0) ? UNZ_EOF : iRead; + +            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 Bytef *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); + +            if ((err>=0) && (pfile_in_zip_read_info->stream.msg!=NULL)) +              err = Z_DATA_ERROR; + +            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 z_off_t ZEXPORT unztell (file) +    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 (z_off_t)pfile_in_zip_read_info->stream.total_out; +} + + +/* +  return 1 if the end of file was reached, 0 elsewhere +*/ +extern int ZEXPORT unzeof (file) +    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 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 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 ZEXPORT unzGetLocalExtrafield (file,buf,len) +    unzFile file; +    voidp 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 (ZSEEK(pfile_in_zip_read_info->z_filefunc, +              pfile_in_zip_read_info->filestream, +              pfile_in_zip_read_info->offset_local_extrafield + +              pfile_in_zip_read_info->pos_local_extrafield, +              ZLIB_FILEFUNC_SEEK_SET)!=0) +        return UNZ_ERRNO; + +    if (ZREAD(pfile_in_zip_read_info->z_filefunc, +              pfile_in_zip_read_info->filestream, +              buf,read_now)!=read_now) +        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 ZEXPORT unzCloseCurrentFile (file) +    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) && +        (!pfile_in_zip_read_info->raw)) +    { +        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 ZEXPORT unzGetGlobalComment (file, szComment, uSizeBuf) +    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 (ZSEEK(s->z_filefunc,s->filestream,s->central_pos+22,ZLIB_FILEFUNC_SEEK_SET)!=0) +        return UNZ_ERRNO; + +    if (uReadThis>0) +    { +      *szComment='\0'; +      if (ZREAD(s->z_filefunc,s->filestream,szComment,uReadThis)!=uReadThis) +        return UNZ_ERRNO; +    } + +    if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment)) +        *(szComment+s->gi.size_comment)='\0'; +    return (int)uReadThis; +} + +/* Additions by RX '2004 */ +extern uLong ZEXPORT unzGetOffset (file) +    unzFile file; +{ +    unz_s* s; + +    if (file==NULL) +          return UNZ_PARAMERROR; +    s=(unz_s*)file; +    if (!s->current_file_ok) +      return 0; +    if (s->gi.number_entry != 0 && s->gi.number_entry != 0xffff) +      if (s->num_file==s->gi.number_entry) +         return 0; +    return s->pos_in_central_dir; +} + +extern int ZEXPORT unzSetOffset (file, pos) +        unzFile file; +        uLong pos; +{ +    unz_s* s; +    int err; + +    if (file==NULL) +        return UNZ_PARAMERROR; +    s=(unz_s*)file; + +    s->pos_in_central_dir = pos; +    s->num_file = s->gi.number_entry;      /* hack */ +    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; +} diff --git a/src/qcommon/unzip.h b/src/qcommon/unzip.h new file mode 100644 index 0000000..b4a839b --- /dev/null +++ b/src/qcommon/unzip.h @@ -0,0 +1,355 @@ +/* unzip.h -- IO for uncompress .zip files using zlib +   Version 1.01e, February 12th, 2005 + +   Copyright (C) 1998-2005 Gilles Vollant + +   This unzip package allow extract file from .ZIP file, compatible with PKZip 2.04g +     WinZip, InfoZip tools and compatible. + +   Multi volume ZipFile (span) are not supported. +   Encryption compatible with pkzip 2.04g only supported +   Old compressions used by old PKZip 1.x are not supported + + +   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 +      http://www.info-zip.org/pub/infozip/doc/appnote-981119-iz.zip +      http://www.info-zip.org/pub/infozip/doc/ +   PkWare has also a specification at : +      ftp://ftp.pkware.com/probdesc.zip +*/ + +#ifndef _unz_H +#define _unz_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef USE_LOCAL_HEADERS +  #include "../zlib/zlib.h" +#else +  #include <zlib.h> +#endif +#include "ioapi.h" + +#define NOUNCRYPT + +#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 voidp unzFile; +#endif + + +#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) + +/* tm_unz contain date/time info */ +typedef struct tm_unz_s +{ +    uInt tm_sec;            /* seconds after the minute - [0,59] */ +    uInt tm_min;            /* minutes after the hour - [0,59] */ +    uInt tm_hour;           /* hours since midnight - [0,23] */ +    uInt tm_mday;           /* day of the month - [1,31] */ +    uInt tm_mon;            /* months since January - [0,11] */ +    uInt 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 +{ +    uLong number_entry;         /* total number of entries in +                       the central dir on this disk */ +    uLong 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 +{ +    uLong version;              /* version made by                 2 bytes */ +    uLong version_needed;       /* version needed to extract       2 bytes */ +    uLong flag;                 /* general purpose bit flag        2 bytes */ +    uLong compression_method;   /* compression method              2 bytes */ +    uLong dosDate;              /* last mod file date in Dos fmt   4 bytes */ +    uLong crc;                  /* crc-32                          4 bytes */ +    uLong compressed_size;      /* compressed size                 4 bytes */ +    uLong uncompressed_size;    /* uncompressed size               4 bytes */ +    uLong size_filename;        /* filename length                 2 bytes */ +    uLong size_file_extra;      /* extra field length              2 bytes */ +    uLong size_file_comment;    /* file comment length             2 bytes */ + +    uLong disk_num_start;       /* disk number start               2 bytes */ +    uLong internal_fa;          /* internal file attributes        2 bytes */ +    uLong external_fa;          /* external file attributes        4 bytes */ + +    tm_unz tmu_date; +} unz_file_info; + +extern int ZEXPORT unzStringFileNameCompare OF ((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 ZEXPORT unzOpen OF((const char *path)); +/* +  Open a Zip file. path contain the full pathname (by example, +     on a Windows XP computer "c:\\zlib\\zlib113.zip" or on an Unix computer +     "zlib/zlib113.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 ZEXPORT unzOpen2 OF((const char *path, +                                    zlib_filefunc_def* pzlib_filefunc_def)); +/* +   Open a Zip file, like unzOpen, but provide a set of file low level API +      for read/write the zip file (see ioapi.h) +*/ + +extern int ZEXPORT unzClose OF((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 ZEXPORT unzGetGlobalInfo OF((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 ZEXPORT unzGetGlobalComment OF((unzFile file, +                                           char *szComment, +                                           uLong 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 byte copied or an error code <0 +*/ + + +/***************************************************************************/ +/* Unzip package allow you browse the directory of the zipfile */ + +extern int ZEXPORT unzGoToFirstFile OF((unzFile file)); +/* +  Set the current file of the zipfile to the first file. +  return UNZ_OK if there is no problem +*/ + +extern int ZEXPORT unzGoToNextFile OF((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 ZEXPORT unzLocateFile OF((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 +*/ + + +/* ****************************************** */ +/* Ryan supplied functions */ +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_pos_s +{ +    uLong pos_in_zip_directory;   /* offset in zip file directory */ +    uLong num_of_file;            /* # of file */ +} unz_file_pos; + +extern int ZEXPORT unzGetFilePos( +    unzFile file, +    unz_file_pos* file_pos); + +extern int ZEXPORT unzGoToFilePos( +    unzFile file, +    unz_file_pos* file_pos); + +/* ****************************************** */ + +extern int ZEXPORT unzGetCurrentFileInfo OF((unzFile file, +                         unz_file_info *pfile_info, +                         char *szFileName, +                         uLong fileNameBufferSize, +                         void *extraField, +                         uLong extraFieldBufferSize, +                         char *szComment, +                         uLong 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 ZEXPORT unzOpenCurrentFile OF((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 ZEXPORT unzOpenCurrentFilePassword OF((unzFile file, +                                                  const char* password)); +/* +  Open for reading data the current file in the zipfile. +  password is a crypting password +  If there is no error, the return value is UNZ_OK. +*/ + +extern int ZEXPORT unzOpenCurrentFile2 OF((unzFile file, +                                           int* method, +                                           int* level, +                                           int raw)); +/* +  Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) +    if raw==1 +  *method will receive method of compression, *level will receive level of +     compression +  note : you can set level parameter as NULL (if you did not want known level, +         but you CANNOT set method parameter as NULL +*/ + +extern int ZEXPORT unzOpenCurrentFile3 OF((unzFile file, +                                           int* method, +                                           int* level, +                                           int raw, +                                           const char* password)); +/* +  Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) +    if raw==1 +  *method will receive method of compression, *level will receive level of +     compression +  note : you can set level parameter as NULL (if you did not want known level, +         but you CANNOT set method parameter as NULL +*/ + + +extern int ZEXPORT unzCloseCurrentFile OF((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 ZEXPORT unzReadCurrentFile OF((unzFile file, +                      voidp buf, +                      unsigned len)); +/* +  Read bytes from the current file (opened by unzOpenCurrentFile) +  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 z_off_t ZEXPORT unztell OF((unzFile file)); +/* +  Give the current position in uncompressed data +*/ + +extern int ZEXPORT unzeof OF((unzFile file)); +/* +  return 1 if the end of file was reached, 0 elsewhere +*/ + +extern int ZEXPORT unzGetLocalExtrafield OF((unzFile file, +                                             voidp 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 bytes copied in buf, or (if <0) +    the error code +*/ + +/***************************************************************************/ + +/* Get the current file offset */ +extern uLong ZEXPORT unzGetOffset (unzFile file); + +/* Set the current file offset */ +extern int ZEXPORT unzSetOffset (unzFile file, uLong pos); + + + +#ifdef __cplusplus +} +#endif + +#endif /* _unz_H */ diff --git a/src/qcommon/vm.c b/src/qcommon/vm.c new file mode 100644 index 0000000..225119f --- /dev/null +++ b/src/qcommon/vm.c @@ -0,0 +1,917 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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; +vm_t	*lastVM    = NULL; +int		vm_debugLevel; + +// used by Com_Error to get rid of running vm's before longjmp +static int forced_unload; + +#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; +	union { +		char	*c; +		void	*v; +	} mapfile; +	char *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, &mapfile.v ); +	if ( !mapfile.c ) { +		Com_Printf( "Couldn't load symbol file: %s\n", symbols ); +		return; +	} + +	numInstructions = vm->instructionCount; + +	// parse the symbols +	text_p = mapfile.c; +	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.v ); +} + +/* +============ +VM_DllSyscall + +Dlls will call this directly + + rcg010206 The horror; the horror. + +  The syscall mechanism relies on stack manipulation to get its 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 < ARRAY_LEN (args); 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]; +	union { +		vmHeader_t	*h; +		void				*v; +	} 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, &header.v ); +	if ( !header.h ) { +		Com_Printf( "Failed.\n" ); +		VM_Free( vm ); +		return NULL; +	} + +	// show where the qvm was loaded from +	Cmd_ExecuteString( va( "which %s\n", filename ) ); + +	if( LittleLong( header.h->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.h)[i] = LittleLong( ((int *)header.h)[i] ); +		} + +		// validate +		if ( header.h->jtrgLength < 0 +			|| header.h->bssLength < 0 +			|| header.h->dataLength < 0 +			|| header.h->litLength < 0 +			|| header.h->codeLength <= 0 ) { +			VM_Free( vm ); +			Com_Error( ERR_FATAL, "%s has bad header", filename ); +		} +	} else if( LittleLong( header.h->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.h)[i] = LittleLong( ((int *)header.h)[i] ); +		} + +		// validate +		if ( header.h->bssLength < 0 +			|| header.h->dataLength < 0 +			|| header.h->litLength < 0 +			|| header.h->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.h->dataLength + header.h->litLength + +		header.h->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.h + header.h->dataOffset, +		header.h->dataLength + header.h->litLength ); + +	// byte swap the longs +	for ( i = 0 ; i < header.h->dataLength ; i += 4 ) { +		*(int *)(vm->dataBase + i) = LittleLong( *(int *)(vm->dataBase + i ) ); +	} + +	if( header.h->vmMagic == VM_MAGIC_VER2 ) { +		vm->numJumpTableTargets = header.h->jtrgLength >> 2; +		Com_Printf( "Loading %d jump table targets\n", vm->numJumpTableTargets ); + +		if( alloc ) { +			vm->jumpTableTargets = Hunk_Alloc( header.h->jtrgLength, h_high ); +		} else { +			Com_Memset( vm->jumpTableTargets, 0, header.h->jtrgLength ); +		} + +		Com_Memcpy( vm->jumpTableTargets, (byte *)header.h + header.h->dataOffset + +				header.h->dataLength + header.h->litLength, header.h->jtrgLength ); + +		// byte swap the longs +		for ( i = 0 ; i < header.h->jtrgLength ; i += 4 ) { +			*(int *)(vm->jumpTableTargets + i) = LittleLong( *(int *)(vm->jumpTableTargets + i ) ); +		} +	} + +	return header.h; +} + +/* +================= +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 +================ +*/ +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; + +	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->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->instructionCount = header->instructionCount; +	vm->instructionPointers = Hunk_Alloc( vm->instructionCount*4, 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 - PROGRAM_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) { +		return; +	} + +	if(vm->callLevel) { +		if(!forced_unload) { +			Com_Error( ERR_FATAL, "VM_Free(%s) on running vm", vm->name ); +			return; +		} else { +			Com_Printf( "forcefully unloading %s vm\n", vm->name ); +		} +	} + +	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++) { +		VM_Free(&vmTable[i]); +	} +} + +void VM_Forced_Unload_Start(void) { +	forced_unload = 1; +} + +void VM_Forced_Unload_Done(void) { +	forced_unload = 0; +} + +void *VM_ArgPtr( intptr_t intValue ) { +	if ( !intValue ) { +		return NULL; +	} +	// 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; +	} + +	// 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 +============== +*/ + +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 ); +	} + +	++vm->callLevel; +	// 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 < ARRAY_LEN(args); 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 || idsparc // i386/sparc 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 < ARRAY_LEN(a.args); 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 +	} +	--vm->callLevel; + +	if ( 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->instructionCount*4 ); +		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..2b9ffea --- /dev/null +++ b/src/qcommon/vm_interpreted.c @@ -0,0 +1,909 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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 +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		byte_pc; +	int		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 +	int_pc = byte_pc = 0; +	instruction = 0; +	code = (byte *)header + header->codeOffset; +	codeBase = (int *)vm->codeBase; + +	// Copy and expand instructions to words while building instruction table +	while ( instruction < header->instructionCount ) { +		vm->instructionPointers[ instruction ] = int_pc; +		instruction++; + +		op = (int)code[ byte_pc ]; +		codeBase[int_pc] = op; +		if ( byte_pc > header->codeLength ) { +			Com_Error( ERR_FATAL, "VM_PrepareInterpreter: pc > header->codeLength" ); +		} + +		byte_pc++; +		int_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[int_pc] = loadWord(&code[byte_pc]); +			byte_pc += 4; +			int_pc++; +			break; +		case OP_ARG: +			codeBase[int_pc] = (int)code[byte_pc]; +			byte_pc++; +			int_pc++; +			break; +		default: +			break; +		} + +	} +	int_pc = 0; +	instruction = 0; +	code = (byte *)header + header->codeOffset; +	 +	// Now that the code has been expanded to int-sized opcodes, we'll translate instruction index +	//into an index into codeBase[], which contains opcodes and operands. +	while ( instruction < header->instructionCount ) { +		op = codeBase[ int_pc ]; +		instruction++; +		int_pc++; +		 +		switch ( op ) { +		// These ops need to translate addresses in jumps from instruction index to int index +		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] is the instruction index. Convert that into an offset into +			//the int-aligned codeBase[] by the lookup table. +			codeBase[int_pc] = vm->instructionPointers[codeBase[int_pc]]; +			int_pc++; +			break; + +		// These opcodes have an operand that isn't an instruction index +		case OP_ENTER: +		case OP_CONST: +		case OP_LOCAL: +		case OP_LEAVE: +		case OP_BLOCK_COPY: +		case OP_ARG: +			int_pc++; +			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	DEBUGSTR va("%s%i", VM_Indent(vm), opStack-stack ) + +int	VM_CallInterpreted( vm_t *vm, int *args ) { +	int		stack[OPSTACK_SIZE]; +	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_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+OPSTACK_SIZE ) { +			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 += 1; +			goto nextInstruction2; +		case OP_LOCAL: +			opStack++; +			r1 = r0; +			r0 = *opStack = r2+programStack; + +			programCounter += 1; +			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&~3 ]; +			goto nextInstruction2; +		case OP_LOAD2: +			r0 = *opStack = *(unsigned short *)&image[ r0&dataMask&~1 ]; +			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)&dataMask&~3 ] = r0; +			opStack--; +			programCounter += 1; +			goto nextInstruction; + +		case OP_BLOCK_COPY: +			{ +				int		*src, *dest; +				int		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[ srci ]; +				dest = (int *)&image[ desti ]; +				 +				memcpy(dest, src, count); +				programCounter += 1; +				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 ] ); +				{ +					// the vm has ints on the stack, we expect +					// pointers so we might have to convert it +					if (sizeof(intptr_t) != sizeof(int)) { +						intptr_t argarr[16]; +						int *imagePtr = (int *)&image[programStack]; +						int i; +						for (i = 0; i < 16; ++i) { +							argarr[i] = *(++imagePtr); +						} +						r = vm->systemCall( argarr ); +					} else { +						intptr_t* argptr = (intptr_t *)&image[ programStack + 4 ]; +						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->instructionCount ) { +				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 += 1; +			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: +			if ( (unsigned)r0 >= vm->instructionCount ) +				Com_Error( ERR_DROP, "VM program counter out of range in OP_JUMP" ); + +			programCounter = vm->instructionPointers[ r0 ]; + +			opStack--; +			goto nextInstruction; + +		case OP_EQ: +			opStack -= 2; +			if ( r1 == r0 ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				goto nextInstruction; +			} else { +				programCounter += 1; +				goto nextInstruction; +			} + +		case OP_NE: +			opStack -= 2; +			if ( r1 != r0 ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				goto nextInstruction; +			} else { +				programCounter += 1; +				goto nextInstruction; +			} + +		case OP_LTI: +			opStack -= 2; +			if ( r1 < r0 ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				goto nextInstruction; +			} else { +				programCounter += 1; +				goto nextInstruction; +			} + +		case OP_LEI: +			opStack -= 2; +			if ( r1 <= r0 ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				goto nextInstruction; +			} else { +				programCounter += 1; +				goto nextInstruction; +			} + +		case OP_GTI: +			opStack -= 2; +			if ( r1 > r0 ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				goto nextInstruction; +			} else { +				programCounter += 1; +				goto nextInstruction; +			} + +		case OP_GEI: +			opStack -= 2; +			if ( r1 >= r0 ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				goto nextInstruction; +			} else { +				programCounter += 1; +				goto nextInstruction; +			} + +		case OP_LTU: +			opStack -= 2; +			if ( ((unsigned)r1) < ((unsigned)r0) ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				goto nextInstruction; +			} else { +				programCounter += 1; +				goto nextInstruction; +			} + +		case OP_LEU: +			opStack -= 2; +			if ( ((unsigned)r1) <= ((unsigned)r0) ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				goto nextInstruction; +			} else { +				programCounter += 1; +				goto nextInstruction; +			} + +		case OP_GTU: +			opStack -= 2; +			if ( ((unsigned)r1) > ((unsigned)r0) ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				goto nextInstruction; +			} else { +				programCounter += 1; +				goto nextInstruction; +			} + +		case OP_GEU: +			opStack -= 2; +			if ( ((unsigned)r1) >= ((unsigned)r0) ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				goto nextInstruction; +			} else { +				programCounter += 1; +				goto nextInstruction; +			} + +		case OP_EQF: +			if ( ((float *)opStack)[-1] == *(float *)opStack ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				opStack -= 2; +				goto nextInstruction; +			} else { +				programCounter += 1; +				opStack -= 2; +				goto nextInstruction; +			} + +		case OP_NEF: +			if ( ((float *)opStack)[-1] != *(float *)opStack ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				opStack -= 2; +				goto nextInstruction; +			} else { +				programCounter += 1; +				opStack -= 2; +				goto nextInstruction; +			} + +		case OP_LTF: +			if ( ((float *)opStack)[-1] < *(float *)opStack ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				opStack -= 2; +				goto nextInstruction; +			} else { +				programCounter += 1; +				opStack -= 2; +				goto nextInstruction; +			} + +		case OP_LEF: +			if ( ((float *)opStack)[-1] <= *(float *)opStack ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				opStack -= 2; +				goto nextInstruction; +			} else { +				programCounter += 1; +				opStack -= 2; +				goto nextInstruction; +			} + +		case OP_GTF: +			if ( ((float *)opStack)[-1] > *(float *)opStack ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				opStack -= 2; +				goto nextInstruction; +			} else { +				programCounter += 1; +				opStack -= 2; +				goto nextInstruction; +			} + +		case OP_GEF: +			if ( ((float *)opStack)[-1] >= *(float *)opStack ) { +				programCounter = r2;	//vm->instructionPointers[r2]; +				opStack -= 2; +				goto nextInstruction; +			} else { +				programCounter += 1; +				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..80430be --- /dev/null +++ b/src/qcommon/vm_local.h @@ -0,0 +1,189 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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" + +#define	OPSTACK_SIZE	256 +#define	OPSTACK_MASK	(OPSTACK_SIZE-1) + +// don't change +// Hardcoded in q3asm an reserved at end of bss +#define	PROGRAM_STACK_SIZE	0x10000 +#define	PROGRAM_STACK_MASK	(PROGRAM_STACK_SIZE-1) + +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			instructionCount; + +	byte		*dataBase; +	int			dataMask; + +	int			stackBottom;		// if programStack < stackBottom, error + +	int			numSymbols; +	struct vmSymbol_s	*symbols; + +	int			callLevel;		// counts recursive VM_Call +	int			breakFunction;		// increment breakCount on function entry to this +	int			breakCount; + +	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_powerpc.c b/src/qcommon/vm_powerpc.c new file mode 100644 index 0000000..831e567 --- /dev/null +++ b/src/qcommon/vm_powerpc.c @@ -0,0 +1,2170 @@ +/* +=========================================================================== +Copyright (C) 2008 Przemyslaw Iskra <sparky@pld-linux.org> + +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 <sys/types.h> /* needed by sys/mman.h on OSX */ +#include <sys/mman.h> +#include <sys/time.h> +#include <time.h> +#include <stddef.h> + +#ifndef MAP_ANONYMOUS +# define MAP_ANONYMOUS MAP_ANON +#endif + +#include "vm_local.h" +#include "vm_powerpc_asm.h" + +/* + * VM_TIMES enables showing information about time spent inside + * and outside generated code + */ +//#define VM_TIMES +#ifdef VM_TIMES +#include <sys/times.h> +static clock_t time_outside_vm = 0; +static clock_t time_total_vm = 0; +#endif + +/* exit() won't be called but use it because it is marked with noreturn */ +#define DIE( reason ) \ +	do { \ +		Com_Error(ERR_DROP, "vm_powerpc compiler error: " reason "\n"); \ +		exit(1); \ +	} while(0) + +/* + * vm_powerpc uses large quantities of memory during compilation, + * Z_Malloc memory may not be enough for some big qvm files + */ + +//#define VM_SYSTEM_MALLOC +#ifdef VM_SYSTEM_MALLOC +static inline void * +PPC_Malloc( size_t size ) +{ +	void *mem = malloc( size ); +	if ( ! mem ) +		DIE( "Not enough memory" ); + +	return mem; +} +# define PPC_Free free +#else +# define PPC_Malloc Z_Malloc +# define PPC_Free Z_Free +#endif + +/* + * optimizations: + * - hole: bubble optimization (OP_CONST+instruction) + * - copy: inline OP_BLOCK_COPY for lengths under 16/32 bytes + * - mask: use rlwinm instruction as dataMask + */ + +#ifdef __OPTIMIZE__ +# define OPTIMIZE_HOLE 1 +# define OPTIMIZE_COPY 1 +# define OPTIMIZE_MASK 1 +#else +# define OPTIMIZE_HOLE 0 +# define OPTIMIZE_COPY 0 +# define OPTIMIZE_MASK 0 +#endif + +/* + * SUPPORTED TARGETS: + * - Linux 32 bits + *   ( http://refspecs.freestandards.org/elf/elfspec_ppc.pdf ) + *   * LR at r0 + 4 + *   * Local variable space not needed + *     -> store caller safe regs at 16+ + * + * - Linux 64 bits (not fully conformant) + *   ( http://www.ibm.com/developerworks/linux/library/l-powasm4.html ) + *   * needs "official procedure descriptors" (only first function has one) + *   * LR at r0 + 16 + *   * local variable space required, min 64 bytes, starts at 48 + *     -> store caller safe regs at 128+ + * + * - OS X 32 bits + *   ( http://developer.apple.com/documentation/DeveloperTools/Conceptual/LowLevelABI/Articles/32bitPowerPC.html ) + *   * LR at r0 + 8 + *   * local variable space required, min 32 bytes (?), starts at 24 + *     -> store caller safe regs at 64+ + * + * - OS X 64 bits (completely untested) + *   ( http://developer.apple.com/documentation/DeveloperTools/Conceptual/LowLevelABI/Articles/64bitPowerPC.html ) + *   * LR at r0 + 16 + *   * local variable space required, min 64 bytes (?), starts at 48 + *     -> store caller safe regs at 128+ + */ + +/* Select Length - first value on 32 bits, second on 64 */ +#ifdef __PPC64__ +#  define SL( a, b ) (b) +#else +#  define SL( a, b ) (a) +#endif + +/* Select ABI - first for ELF, second for OS X */ +#ifdef __ELF__ +#  define SA( a, b ) (a) +#else +#  define SA( a, b ) (b) +#endif + +#define ELF32	SL( SA( 1, 0 ), 0 ) +#define ELF64	SL( 0, SA( 1, 0 ) ) +#define OSX32	SL( SA( 0, 1 ), 0 ) +#define OSX64	SL( 0, SA( 0, 1 ) ) + +/* native length load/store instructions ( L stands for long ) */ +#define iSTLU	SL( iSTWU, iSTDU ) +#define iSTL	SL( iSTW, iSTD ) +#define iLL	SL( iLWZ, iLD ) +#define iLLX	SL( iLWZX, iLDX ) + +/* register length */ +#define GPRLEN	SL( 4, 8 ) +#define FPRLEN	(8) +/* shift that many bits to obtain value miltiplied by GPRLEN */ +#define GPRLEN_SHIFT	SL( 2, 3 ) + +/* Link register position */ +#define STACK_LR	SL( SA( 4, 8 ), 16 ) +/* register save position */ +#define STACK_SAVE	SL( SA( 16, 64 ), 128 ) +/* temporary space, for float<->int exchange */ +#define STACK_TEMP	SL( SA( 8, 24 ), 48 ) +/* red zone temporary space, used instead of STACK_TEMP if stack isn't + * prepared properly */ +#define STACK_RTEMP	(-16) + +#if ELF64 +/* + * Official Procedure Descriptor + *  we need to prepare one for generated code if we want to call it + * as function + */ +typedef struct { +	void *function; +	void *toc; +	void *env; +} opd_t; +#endif + + +/* + * opcode information table: + * - length of immediate value + * - returned register type + * - required register(s) type + */ +#define opImm0	0x0000 /* no immediate */ +#define opImm1	0x0001 /* 1 byte immadiate value after opcode */ +#define opImm4	0x0002 /* 4 bytes immediate value after opcode */ + +#define opRet0	0x0000 /* returns nothing */ +#define opRetI	0x0004 /* returns integer */ +#define opRetF	0x0008 /* returns float */ +#define opRetIF	(opRetI | opRetF) /* returns integer or float */ + +#define opArg0	0x0000 /* requires nothing */ +#define opArgI	0x0010 /* requires integer(s) */ +#define opArgF	0x0020 /* requires float(s) */ +#define opArgIF	(opArgI | opArgF) /* requires integer or float */ + +#define opArg2I	0x0040 /* requires second argument, integer */ +#define opArg2F	0x0080 /* requires second argument, float */ +#define opArg2IF (opArg2I | opArg2F) /* requires second argument, integer or float */ + +static const unsigned char vm_opInfo[256] = +{ +	[OP_UNDEF]	= opImm0, +	[OP_IGNORE]	= opImm0, +	[OP_BREAK]	= opImm0, +	[OP_ENTER]	= opImm4, +			/* OP_LEAVE has to accept floats, they will be converted to ints */ +	[OP_LEAVE]	= opImm4 | opRet0 | opArgIF, +			/* only STORE4 and POP use values from OP_CALL, +			 * no need to convert floats back */ +	[OP_CALL]	= opImm0 | opRetI | opArgI, +	[OP_PUSH]	= opImm0 | opRetIF, +	[OP_POP]	= opImm0 | opRet0 | opArgIF, +	[OP_CONST]	= opImm4 | opRetIF, +	[OP_LOCAL]	= opImm4 | opRetI, +	[OP_JUMP]	= opImm0 | opRet0 | opArgI, + +	[OP_EQ]		= opImm4 | opRet0 | opArgI | opArg2I, +	[OP_NE]		= opImm4 | opRet0 | opArgI | opArg2I, +	[OP_LTI]	= opImm4 | opRet0 | opArgI | opArg2I, +	[OP_LEI]	= opImm4 | opRet0 | opArgI | opArg2I, +	[OP_GTI]	= opImm4 | opRet0 | opArgI | opArg2I, +	[OP_GEI]	= opImm4 | opRet0 | opArgI | opArg2I, +	[OP_LTU]	= opImm4 | opRet0 | opArgI | opArg2I, +	[OP_LEU]	= opImm4 | opRet0 | opArgI | opArg2I, +	[OP_GTU]	= opImm4 | opRet0 | opArgI | opArg2I, +	[OP_GEU]	= opImm4 | opRet0 | opArgI | opArg2I, +	[OP_EQF]	= opImm4 | opRet0 | opArgF | opArg2F, +	[OP_NEF]	= opImm4 | opRet0 | opArgF | opArg2F, +	[OP_LTF]	= opImm4 | opRet0 | opArgF | opArg2F, +	[OP_LEF]	= opImm4 | opRet0 | opArgF | opArg2F, +	[OP_GTF]	= opImm4 | opRet0 | opArgF | opArg2F, +	[OP_GEF]	= opImm4 | opRet0 | opArgF | opArg2F, + +	[OP_LOAD1]	= opImm0 | opRetI | opArgI, +	[OP_LOAD2]	= opImm0 | opRetI | opArgI, +	[OP_LOAD4]	= opImm0 | opRetIF| opArgI, +	[OP_STORE1]	= opImm0 | opRet0 | opArgI | opArg2I, +	[OP_STORE2]	= opImm0 | opRet0 | opArgI | opArg2I, +	[OP_STORE4]	= opImm0 | opRet0 | opArgIF| opArg2I, +	[OP_ARG]	= opImm1 | opRet0 | opArgIF, +	[OP_BLOCK_COPY]	= opImm4 | opRet0 | opArgI | opArg2I, + +	[OP_SEX8]	= opImm0 | opRetI | opArgI, +	[OP_SEX16]	= opImm0 | opRetI | opArgI, +	[OP_NEGI]	= opImm0 | opRetI | opArgI, +	[OP_ADD]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_SUB]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_DIVI]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_DIVU]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_MODI]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_MODU]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_MULI]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_MULU]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_BAND]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_BOR]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_BXOR]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_BCOM]	= opImm0 | opRetI | opArgI, +	[OP_LSH]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_RSHI]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_RSHU]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_NEGF]	= opImm0 | opRetF | opArgF, +	[OP_ADDF]	= opImm0 | opRetF | opArgF | opArg2F, +	[OP_SUBF]	= opImm0 | opRetF | opArgF | opArg2F, +	[OP_DIVF]	= opImm0 | opRetF | opArgF | opArg2F, +	[OP_MULF]	= opImm0 | opRetF | opArgF | opArg2F, +	[OP_CVIF]	= opImm0 | opRetF | opArgI, +	[OP_CVFI]	= opImm0 | opRetI | opArgF, +}; + +/* + * source instruction data + */ +typedef struct source_instruction_s source_instruction_t; +struct source_instruction_s { +	// opcode +	unsigned long int op; + +	// number of instruction +	unsigned long int i_count; + +	// immediate value (if any) +	union { +		unsigned int i; +		signed int si; +		signed short ss[2]; +		unsigned short us[2]; +		unsigned char b; +	} arg; + +	// required and returned registers +	unsigned char regA1; +	unsigned char regA2; +	unsigned char regR; +	unsigned char regPos; + +	// next instruction +	source_instruction_t *next; +}; + + + +/* + * read-only data needed by the generated code + */ +typedef struct VM_Data { +	// length of this struct + data +	size_t dataLength; +	// compiled code size (in bytes) +	// it only is code size, without the data +	size_t codeLength; + +	// function pointers, no use to waste registers for them +	long int (* AsmCall)( int, int ); +	void (* BlockCopy )( unsigned int, unsigned int, unsigned int ); + +	// instruction pointers, rarely used so don't waste register +	ppc_instruction_t *iPointers; + +	// data mask for load and store, not used if optimized +	unsigned int dataMask; + +	// fixed number used to convert from integer to float +	unsigned int floatBase; // 0x59800004 + +#if ELF64 +	// official procedure descriptor +	opd_t opd; +#endif + +	// additional constants, for floating point OP_CONST +	// this data has dynamic length, thus '0' here +	unsigned int data[0]; +} vm_data_t; + +#ifdef offsetof +# define VM_Data_Offset( field )	offsetof( vm_data_t, field ) +#else +# define OFFSET( structName, field ) \ +	( (void *)&(((structName *)NULL)->field) - NULL ) +# define VM_Data_Offset( field )	OFFSET( vm_data_t, field ) +#endif + + +/* + * functions used by generated code + */ +static long int +VM_AsmCall( int callSyscallInvNum, int callProgramStack ) +{ +	vm_t *savedVM = currentVM; +	long int i, ret; +#ifdef VM_TIMES +	struct tms start_time, stop_time; +	clock_t saved_time = time_outside_vm; +	times( &start_time ); +#endif + +	// save the stack to allow recursive VM entry +	currentVM->programStack = callProgramStack - 4; + +	// we need to convert ints to longs on 64bit powerpcs +	if ( sizeof( intptr_t ) == sizeof( int ) ) { +		intptr_t *argPosition = (intptr_t *)((byte *)currentVM->dataBase + callProgramStack + 4); + +		// generated code does not invert syscall number +		argPosition[ 0 ] = -1 - callSyscallInvNum; + +		ret = currentVM->systemCall( argPosition ); +	} else { +		intptr_t args[11]; + +		// generated code does not invert syscall number +		args[0] = -1 - callSyscallInvNum; + +		int *argPosition = (int *)((byte *)currentVM->dataBase + callProgramStack + 4); +		for( i = 1; i < 11; i++ ) +			args[ i ] = argPosition[ i ]; + +		ret = currentVM->systemCall( args ); +	} + +	currentVM = savedVM; + +#ifdef VM_TIMES +	times( &stop_time ); +	time_outside_vm = saved_time + ( stop_time.tms_utime - start_time.tms_utime ); +#endif + +	return ret; +} + +static void +VM_BlockCopy( unsigned int dest, unsigned int src, unsigned int count ) +{ +	unsigned dataMask = currentVM->dataMask; + +	if ( (dest & dataMask) != dest +		|| (src & dataMask) != src +		|| ((dest+count) & dataMask) != dest + count +		|| ((src+count) & dataMask) != src + count) +	{ +		DIE( "OP_BLOCK_COPY out of range!"); +	} + +	memcpy( currentVM->dataBase+dest, currentVM->dataBase+src, count ); +} + + +/* + * code-block descriptors + */ +typedef struct dest_instruction dest_instruction_t; +typedef struct symbolic_jump symbolic_jump_t; + +struct symbolic_jump { +	// number of source instruction it has to jump to +	unsigned long int jump_to; + +	// jump condition true/false, (4*cr7+(eq|gt..)) +	long int bo, bi; + +	// extensions / modifiers (branch-link) +	unsigned long ext; + +	// dest_instruction refering to this jump +	dest_instruction_t *parent; + +	// next jump +	symbolic_jump_t *nextJump; +}; + +struct dest_instruction { +	// position in the output chain +	unsigned long int count; + +	// source instruction number +	unsigned long int i_count; + +	// exact (for instructins), or maximum (for jump) length +	unsigned short length; + +	dest_instruction_t *next; + +	// if the instruction is a jump than jump will be non NULL +	symbolic_jump_t *jump; + +	// if jump is NULL than all the instructions will be here +	ppc_instruction_t code[0]; +}; + +// first and last instruction, +// di_first is a dummy instruction +static dest_instruction_t *di_first = NULL, *di_last = NULL; +// number of instructions +static unsigned long int di_count = 0; +// pointers needed to compute local jumps, those aren't pointers to +// actual instructions, just used to check how long the jump is going +// to be and whether it is positive or negative +static dest_instruction_t **di_pointers = NULL; + +// output instructions which does not come from source code +// use false i_count value +#define FALSE_ICOUNT 0xffffffff + + +/* + * append specified instructions at the end of instruction chain + */ +static void +PPC_Append( +		dest_instruction_t *di_now, +		unsigned long int i_count +  	  ) +{ +	di_now->count = di_count++; +	di_now->i_count = i_count; +	di_now->next = NULL; + +	di_last->next = di_now; +	di_last = di_now; + +	if ( i_count != FALSE_ICOUNT ) { +		if ( ! di_pointers[ i_count ] ) +			di_pointers[ i_count ] = di_now; +	} +} + +/* + * make space for instructions and append + */ +static void +PPC_AppendInstructions( +		unsigned long int i_count, +		size_t num_instructions, +		const ppc_instruction_t *is +	) +{ +	if ( num_instructions < 0 ) +		num_instructions = 0; +	size_t iBytes = sizeof( ppc_instruction_t ) * num_instructions; +	dest_instruction_t *di_now = PPC_Malloc( sizeof( dest_instruction_t ) + iBytes ); + +	di_now->length = num_instructions; +	di_now->jump = NULL; + +	if ( iBytes > 0 ) +		memcpy( &(di_now->code[0]), is, iBytes ); + +	PPC_Append( di_now, i_count ); +} + +/* + * create symbolic jump and append + */ +static symbolic_jump_t *sj_first = NULL, *sj_last = NULL; +static void +PPC_PrepareJump( +		unsigned long int i_count, +		unsigned long int dest, +		long int bo, +		long int bi, +		unsigned long int ext +	) +{ +	dest_instruction_t *di_now = PPC_Malloc( sizeof( dest_instruction_t ) ); +	symbolic_jump_t *sj = PPC_Malloc( sizeof( symbolic_jump_t ) ); + +	sj->jump_to = dest; +	sj->bo = bo; +	sj->bi = bi; +	sj->ext = ext; +	sj->parent = di_now; +	sj->nextJump = NULL; + +	sj_last->nextJump = sj; +	sj_last = sj; + +	di_now->length = (bo == branchAlways ? 1 : 2); +	di_now->jump = sj; + +	PPC_Append( di_now, i_count ); +} + +/* + * simplyfy instruction emission + */ +#define emitStart( i_cnt ) \ +	unsigned long int i_count = i_cnt; \ +	size_t num_instructions = 0; \ +	long int force_emit = 0; \ +	ppc_instruction_t instructions[50]; + +#define pushIn( inst ) \ +	(instructions[ num_instructions++ ] = inst) +#define in( inst, args... ) pushIn( IN( inst, args ) ) + +#define emitEnd() \ +	do{ \ +		if ( num_instructions || force_emit ) \ +			PPC_AppendInstructions( i_count, num_instructions, instructions );\ +		num_instructions = 0; \ +	} while(0) + +#define emitJump( dest, bo, bi, ext ) \ +	do { \ +		emitEnd(); \ +		PPC_PrepareJump( i_count, dest, bo, bi, ext ); \ +	} while(0) + + +/* + * definitions for creating .data section, + * used in cases where constant float is needed + */ +#define LOCAL_DATA_CHUNK 50 +typedef struct local_data_s local_data_t; +struct local_data_s { +	// number of data in this structure +	long int count; + +	// data placeholder +	unsigned int data[ LOCAL_DATA_CHUNK ]; + +	// next chunk, if this one wasn't enough +	local_data_t *next; +}; + +// first data chunk +static local_data_t *data_first = NULL; +// total number of data +static long int data_acc = 0; + +/* + * append the data and return its offset + */ +static size_t +PPC_PushData( unsigned int datum ) +{ +	local_data_t *d_now = data_first; +	long int accumulated = 0; + +	// check whether we have this one already +	do { +		long int i; +		for ( i = 0; i < d_now->count; i++ ) { +			if ( d_now->data[ i ] == datum ) { +				accumulated += i; +				return VM_Data_Offset( data[ accumulated ] ); +			} +		} +		if ( !d_now->next ) +			break; + +		accumulated += d_now->count; +		d_now = d_now->next; +	} while (1); + +	// not found, need to append +	accumulated += d_now->count; + +	// last chunk is full, create new one +	if ( d_now->count >= LOCAL_DATA_CHUNK ) { +		d_now->next = PPC_Malloc( sizeof( local_data_t ) ); +		d_now = d_now->next; +		d_now->count = 0; +		d_now->next = NULL; +	} + +	d_now->data[ d_now->count ] = datum; +	d_now->count += 1; + +	data_acc = accumulated + 1; + +	return VM_Data_Offset( data[ accumulated ] ); +} + +/* + * find leading zeros in dataMask to implement it with + * "rotate and mask" instruction + */ +static long int fastMaskHi = 0, fastMaskLo = 31; +static void +PPC_MakeFastMask( int mask ) +{ +#if defined( __GNUC__ ) && ( __GNUC__ >= 4 || ( __GNUC__ == 3 && __GNUC_MINOR__ >= 4 ) ) +	/* count leading zeros */ +	fastMaskHi = __builtin_clz( mask ); + +	/* count trailing zeros */ +	fastMaskLo = 31 - __builtin_ctz( mask ); +#else +	fastMaskHi = 0; +	while ( ( mask & ( 0x80000000 >> fastMaskHi ) ) == 0 ) +		fastMaskHi++; + +	fastMaskLo = 31; +	while ( ( mask & ( 0x80000000 >> fastMaskLo ) ) == 0 ) +		fastMaskLo--; +#endif +} + + +/* + * register definitions + */ + +/* registers which are global for generated code */ + +// pointer to VM_Data (constant) +#define rVMDATA r14 +// vm->dataBase (constant) +#define rDATABASE r15 +// programStack (variable) +#define rPSTACK r16 + +/* + * function local registers, + * + * normally only volatile registers are used, but if there aren't enough + * or function has to preserve some value while calling annother one + * then caller safe registers are used as well + */ +static const long int gpr_list[] = { +	/* caller safe registers, normally only one is used */ +	r24, r23, r22, r21, +	r20, r19, r18, r17, +	/* volatile registers (preferred), +	 * normally no more than 5 is used */ +	r3, r4, r5, r6, +	r7, r8, r9, r10, +}; +static const long int gpr_vstart = 8; /* position of first volatile register */ +static const long int gpr_total = ARRAY_LEN( gpr_list ); + +static const long int fpr_list[] = { +	/* static registers, normally none is used */ +	f20, f21, f19, f18, +	f17, f16, f15, f14, +	/* volatile registers (preferred), +	 * normally no more than 7 is used */ +	f0, f1, f2, f3, +	f4, f5, f6, f7, +	f8, f9, f10, f11, +	f12, f13, +}; +static const long int fpr_vstart = 8; +static const long int fpr_total = ARRAY_LEN( fpr_list ); + +/* + * prepare some dummy structures and emit init code + */ +static void +PPC_CompileInit( void ) +{ +	di_first = di_last = PPC_Malloc( sizeof( dest_instruction_t ) ); +	di_first->count = 0; +	di_first->next = NULL; +	di_first->jump = NULL; + +	sj_first = sj_last = PPC_Malloc( sizeof( symbolic_jump_t ) ); +	sj_first->nextJump = NULL; + +	data_first = PPC_Malloc( sizeof( local_data_t ) ); +	data_first->count = 0; +	data_first->next = NULL; + +	/* +	 * init function: +	 * saves old values of global registers and sets our values +	 * function prototype is: +	 *  int begin( void *data, int programStack, void *vm->dataBase ) +	 */ + +	/* first instruction must not be placed on instruction list */ +	emitStart( FALSE_ICOUNT ); + +	long int stack = STACK_SAVE + 4 * GPRLEN; + +	in( iMFLR, r0 ); +	in( iSTLU, r1, -stack, r1 ); +	in( iSTL, rVMDATA, STACK_SAVE + 0 * GPRLEN, r1 ); +	in( iSTL, rPSTACK, STACK_SAVE + 1 * GPRLEN, r1 ); +	in( iSTL, rDATABASE, STACK_SAVE + 2 * GPRLEN, r1 ); +	in( iSTL, r0, stack + STACK_LR, r1 ); +	in( iMR, rVMDATA, r3 ); +	in( iMR, rPSTACK, r4 ); +	in( iMR, rDATABASE, r5 ); +	in( iBL, +4*8 ); // LINK JUMP: first generated instruction | XXX jump ! +	in( iLL, rVMDATA, STACK_SAVE + 0 * GPRLEN, r1 ); +	in( iLL, rPSTACK, STACK_SAVE + 1 * GPRLEN, r1 ); +	in( iLL, rDATABASE, STACK_SAVE + 2 * GPRLEN, r1 ); +	in( iLL, r0, stack + STACK_LR, r1 ); +	in( iMTLR, r0 ); +	in( iADDI, r1, r1, stack ); +	in( iBLR ); + +	emitEnd(); +} + +// rFIRST is the copy of the top value on the opstack +#define rFIRST		(gpr_list[ gpr_pos - 1]) +// second value on the opstack +#define rSECOND		(gpr_list[ gpr_pos - 2 ]) +// temporary registers, not on the opstack +#define rTEMP(x)	(gpr_list[ gpr_pos + x ]) +#define rTMP		rTEMP(0) + +#define fFIRST		(fpr_list[ fpr_pos - 1 ]) +#define fSECOND		(fpr_list[ fpr_pos - 2 ]) +#define fTEMP(x)	(fpr_list[ fpr_pos + x ]) +#define fTMP		fTEMP(0) + +// register types +#define rTYPE_STATIC	0x01 +#define rTYPE_FLOAT	0x02 + +// what type should this opcode return +#define RET_INT		( !(i_now->regR & rTYPE_FLOAT) ) +#define RET_FLOAT	( i_now->regR & rTYPE_FLOAT ) +// what type should it accept +#define ARG_INT		( ! i_now->regA1 ) +#define ARG_FLOAT	( i_now->regA1 ) +#define ARG2_INT	( ! i_now->regA2 ) +#define ARG2_FLOAT	( i_now->regA2 ) + +/* + * emit OP_CONST, called if nothing has used the const value directly + */ +static void +PPC_EmitConst( source_instruction_t * const i_const ) +{ +	emitStart( i_const->i_count ); + +	if ( !(i_const->regR & rTYPE_FLOAT) ) { +		// gpr_pos needed for "rFIRST" to work +		long int gpr_pos = i_const->regPos; + +		if ( i_const->arg.si >= -0x8000 && i_const->arg.si < 0x8000 ) { +			in( iLI, rFIRST, i_const->arg.si ); +		} else if ( i_const->arg.i < 0x10000 ) { +			in( iLI, rFIRST, 0 ); +			in( iORI, rFIRST, rFIRST, i_const->arg.i ); +		} else { +			in( iLIS, rFIRST, i_const->arg.ss[ 0 ] ); +			if ( i_const->arg.us[ 1 ] != 0 ) +				in( iORI, rFIRST, rFIRST, i_const->arg.us[ 1 ] ); +		} + +	} else { +		// fpr_pos needed for "fFIRST" to work +		long int fpr_pos = i_const->regPos; + +		// there's no good way to generate the data, +		// just read it from data section +		in( iLFS, fFIRST, PPC_PushData( i_const->arg.i ), rVMDATA ); +	} + +	emitEnd(); +} +#define MAYBE_EMIT_CONST() if ( i_const ) PPC_EmitConst( i_const ) + +/* + * emit empty instruction, just sets the needed pointers + */ +static inline void +PPC_EmitNull( source_instruction_t * const i_null ) +{ +	PPC_AppendInstructions( i_null->i_count, 0, NULL ); +} +#define EMIT_FALSE_CONST() PPC_EmitNull( i_const ) + + +/* + * analize function for register usage and whether it needs stack (r1) prepared + */ +static void +VM_AnalyzeFunction( +		source_instruction_t * const i_first, +		long int *prepareStack, +		long int *gpr_start_pos, +		long int *fpr_start_pos +		) +{ +	source_instruction_t *i_now = i_first; + +	source_instruction_t *value_provider[20] = { NULL }; +	unsigned long int opstack_depth = 0; + +	/* +	 * first step: +	 *  remember what codes returned some value and mark the value type +	 *  when we get to know what it should be +	 */ +	while ( (i_now = i_now->next) ) { +		unsigned long int op = i_now->op; +		unsigned long int opi = vm_opInfo[ op ]; + +		if ( opi & opArgIF ) { +			assert( opstack_depth > 0 ); + +			opstack_depth--; +			source_instruction_t *vp = value_provider[ opstack_depth ]; +			unsigned long int vpopi = vm_opInfo[ vp->op ]; + +			if ( (opi & opArgI) && (vpopi & opRetI) ) { +				// instruction accepts integer, provider returns integer +				//vp->regR |= rTYPE_INT; +				//i_now->regA1 = rTYPE_INT; +			} else if ( (opi & opArgF) && (vpopi & opRetF) ) { +				// instruction accepts float, provider returns float +				vp->regR |= rTYPE_FLOAT; // use OR here - could be marked as static +				i_now->regA1 = rTYPE_FLOAT; +			} else { +				// instruction arg type does not agree with +				// provider return type +				DIE( "unrecognized instruction combination" ); +			} + +		} +		if ( opi & opArg2IF ) { +			assert( opstack_depth > 0 ); + +			opstack_depth--; +			source_instruction_t *vp = value_provider[ opstack_depth ]; +			unsigned long int vpopi = vm_opInfo[ vp->op ]; + +			if ( (opi & opArg2I) && (vpopi & opRetI) ) { +				// instruction accepts integer, provider returns integer +				//vp->regR |= rTYPE_INT; +				//i_now->regA2 = rTYPE_INT; +			} else if ( (opi & opArg2F) && (vpopi & opRetF) ) { +				// instruction accepts float, provider returns float +				vp->regR |= rTYPE_FLOAT; // use OR here - could be marked as static +				i_now->regA2 = rTYPE_FLOAT; +			} else { +				// instruction arg type does not agree with +				// provider return type +				DIE( "unrecognized instruction combination" ); +			} +		} + + +		if ( +			( op == OP_CALL ) +				|| +			( op == OP_BLOCK_COPY && ( i_now->arg.i > SL( 16, 32 ) || !OPTIMIZE_COPY ) ) +		) { +			long int i; +			*prepareStack = 1; +			// force caller safe registers so we won't have to save them +			for ( i = 0; i < opstack_depth; i++ ) { +				source_instruction_t *vp = value_provider[ i ]; +				vp->regR |= rTYPE_STATIC; +			} +		} + + +		if ( opi & opRetIF ) { +			value_provider[ opstack_depth ] = i_now; +			opstack_depth++; +		} +	} + +	/* +	 * second step: +	 *  now that we know register types; compute exactly how many registers +	 *  of each type we need +	 */ + +	i_now = i_first; +	long int needed_reg[4] = {0,0,0,0}, max_reg[4] = {0,0,0,0}; +	opstack_depth = 0; +	while ( (i_now = i_now->next) ) { +		unsigned long int op = i_now->op; +		unsigned long int opi = vm_opInfo[ op ]; + +		if ( opi & opArgIF ) { +			assert( opstack_depth > 0 ); +			opstack_depth--; +			source_instruction_t *vp = value_provider[ opstack_depth ]; + +			needed_reg[ ( vp->regR & 2 ) ] -= 1; +			if ( vp->regR & 1 ) // static +				needed_reg[ ( vp->regR & 3 ) ] -= 1; +		} +		if ( opi & opArg2IF ) { +			assert( opstack_depth > 0 ); +			opstack_depth--; +			source_instruction_t *vp = value_provider[ opstack_depth ]; + +			needed_reg[ ( vp->regR & 2 ) ] -= 1; +			if ( vp->regR & 1 ) // static +				needed_reg[ ( vp->regR & 3 ) ] -= 1; +		} + +		if ( opi & opRetIF ) { +			long int i; +			value_provider[ opstack_depth ] = i_now; +			opstack_depth++; + +			i = i_now->regR & 2; +			needed_reg[ i ] += 1; +			if ( max_reg[ i ] < needed_reg[ i ] ) +				max_reg[ i ] = needed_reg[ i ]; + +			i = i_now->regR & 3; +			if ( i & 1 ) { +				needed_reg[ i ] += 1; +				if ( max_reg[ i ] < needed_reg[ i ] ) +					max_reg[ i ] = needed_reg[ i ]; +			} +		} +	} + +	long int gpr_start = gpr_vstart; +	const long int gpr_volatile = gpr_total - gpr_vstart; +	if ( max_reg[ 1 ] > 0 || max_reg[ 0 ] > gpr_volatile ) { +		// max_reg[ 0 ] - all gprs needed +		// max_reg[ 1 ] - static gprs needed +		long int max = max_reg[ 0 ] - gpr_volatile; +		if ( max_reg[ 1 ] > max ) +			max = max_reg[ 1 ]; +		if ( max > gpr_vstart ) { +			/* error */ +			DIE( "Need more GPRs" ); +		} + +		gpr_start -= max; + +		// need stack to save caller safe registers +		*prepareStack = 1; +	} +	*gpr_start_pos = gpr_start; + +	long int fpr_start = fpr_vstart; +	const long int fpr_volatile = fpr_total - fpr_vstart; +	if ( max_reg[ 3 ] > 0 || max_reg[ 2 ] > fpr_volatile ) { +		// max_reg[ 2 ] - all fprs needed +		// max_reg[ 3 ] - static fprs needed +		long int max = max_reg[ 2 ] - fpr_volatile; +		if ( max_reg[ 3 ] > max ) +			max = max_reg[ 3 ]; +		if ( max > fpr_vstart ) { +			/* error */ +			DIE( "Need more FPRs" ); +		} + +		fpr_start -= max; + +		// need stack to save caller safe registers +		*prepareStack = 1; +	} +	*fpr_start_pos = fpr_start; +} + +/* + * translate opcodes to ppc instructions, + * it works on functions, not on whole code at once + */ +static void +VM_CompileFunction( source_instruction_t * const i_first ) +{ +	long int prepareStack = 0; +	long int gpr_start_pos, fpr_start_pos; + +	VM_AnalyzeFunction( i_first, &prepareStack, &gpr_start_pos, &fpr_start_pos ); + +	long int gpr_pos = gpr_start_pos, fpr_pos = fpr_start_pos; + +	// OP_CONST combines well with many opcodes so we treat it in a special way +	source_instruction_t *i_const = NULL; +	source_instruction_t *i_now = i_first; + +	// how big the stack has to be +	long int save_space = STACK_SAVE; +	{ +		if ( gpr_start_pos < gpr_vstart ) +			save_space += (gpr_vstart - gpr_start_pos) * GPRLEN; +		save_space = ( save_space + 15 ) & ~0x0f; + +		if ( fpr_start_pos < fpr_vstart ) +			save_space += (fpr_vstart - fpr_start_pos) * FPRLEN; +		save_space = ( save_space + 15 ) & ~0x0f; +	} + +	long int stack_temp = prepareStack ? STACK_TEMP : STACK_RTEMP; + +	while ( (i_now = i_now->next) ) { +		emitStart( i_now->i_count ); + +		switch ( i_now->op ) +		{ +			default: +			case OP_UNDEF: +			case OP_IGNORE: +				MAYBE_EMIT_CONST(); +				in( iNOP ); +				break; + +			case OP_BREAK: +				MAYBE_EMIT_CONST(); +				// force SEGV +				in( iLWZ, r0, 0, r0 ); +				break; + +			case OP_ENTER: +				if ( i_const ) +					DIE( "Weird opcode order" ); + +				// don't prepare stack if not needed +				if ( prepareStack ) { +					long int i, save_pos = STACK_SAVE; + +					in( iMFLR, r0 ); +					in( iSTLU, r1, -save_space, r1 ); +					in( iSTL, r0, save_space + STACK_LR, r1 ); + +					/* save registers */ +					for ( i = gpr_start_pos; i < gpr_vstart; i++ ) { +						in( iSTL, gpr_list[ i ], save_pos, r1 ); +						save_pos += GPRLEN; +					} +					save_pos = ( save_pos + 15 ) & ~0x0f; + +					for ( i = fpr_start_pos; i < fpr_vstart; i++ ) { +						in( iSTFD, fpr_list[ i ], save_pos, r1 ); +						save_pos += FPRLEN; +					} +					prepareStack = 2; +				} + +				in( iADDI, rPSTACK, rPSTACK, - i_now->arg.si ); +				break; + +			case OP_LEAVE: +				if ( i_const ) { +					EMIT_FALSE_CONST(); + +					if ( i_const->regR & rTYPE_FLOAT) +						DIE( "constant float in OP_LEAVE" ); + +					if ( i_const->arg.si >= -0x8000 && i_const->arg.si < 0x8000 ) { +						in( iLI, r3, i_const->arg.si ); +					} else if ( i_const->arg.i < 0x10000 ) { +						in( iLI, r3, 0 ); +						in( iORI, r3, r3, i_const->arg.i ); +					} else { +						in( iLIS, r3, i_const->arg.ss[ 0 ] ); +						if ( i_const->arg.us[ 1 ] != 0 ) +							in( iORI, r3, r3, i_const->arg.us[ 1 ] ); +					} +					gpr_pos--; +				} else { +					MAYBE_EMIT_CONST(); + +					/* place return value in r3 */ +					if ( ARG_INT ) { +						if ( rFIRST != r3 ) +							in( iMR, r3, rFIRST ); +						gpr_pos--; +					} else { +						in( iSTFS, fFIRST, stack_temp, r1 ); +						in( iLWZ, r3, stack_temp, r1 ); +						fpr_pos--; +					} +				} + +				// don't undo stack if not prepared +				if ( prepareStack >= 2 ) { +					long int i, save_pos = STACK_SAVE; + +					in( iLL, r0, save_space + STACK_LR, r1 ); + + +					/* restore registers */ +					for ( i = gpr_start_pos; i < gpr_vstart; i++ ) { +						in( iLL, gpr_list[ i ], save_pos, r1 ); +						save_pos += GPRLEN; +					} +					save_pos = ( save_pos + 15 ) & ~0x0f; +					for ( i = fpr_start_pos; i < fpr_vstart; i++ ) { +						in( iLFD, fpr_list[ i ], save_pos, r1 ); +						save_pos += FPRLEN; +					} + +					in( iMTLR, r0 ); +					in( iADDI, r1, r1, save_space ); +				} +				in( iADDI, rPSTACK, rPSTACK, i_now->arg.si); +				in( iBLR ); +				assert( gpr_pos == gpr_start_pos ); +				assert( fpr_pos == fpr_start_pos ); +				break; + +			case OP_CALL: +				if ( i_const ) { +					EMIT_FALSE_CONST(); + +					if ( i_const->arg.si >= 0 ) { +						emitJump( +							i_const->arg.i, +							branchAlways, 0, branchExtLink +						); +					} else { +						/* syscall */ +						in( iLL, r0, VM_Data_Offset( AsmCall ), rVMDATA ); + +						in( iLI, r3, i_const->arg.si ); // negative value +						in( iMR, r4, rPSTACK ); // push PSTACK on argument list + +						in( iMTCTR, r0 ); +						in( iBCTRL ); +					} +					if ( rFIRST != r3 ) +						in( iMR, rFIRST, r3 ); +				} else { +					MAYBE_EMIT_CONST(); + +					in( iCMPWI, cr7, rFIRST, 0 ); +					in( iBLTm, cr7, +4*5 /* syscall */ ); // XXX jump ! +					/* instruction call */ + +					// get instruction address +					in( iLL, r0, VM_Data_Offset( iPointers ), rVMDATA ); +					in( iRLWINM, rFIRST, rFIRST, GPRLEN_SHIFT, 0, 31-GPRLEN_SHIFT ); // mul * GPRLEN +					in( iLLX, r0, rFIRST, r0 ); // load pointer + +					in( iB, +4*(3 + (rFIRST != r3 ? 1 : 0) ) ); // XXX jump ! + +					/* syscall */ +					in( iLL, r0, VM_Data_Offset( AsmCall ), rVMDATA ); // get asmCall pointer +					/* rFIRST can be r3 or some static register */ +					if ( rFIRST != r3 ) +						in( iMR, r3, rFIRST ); // push OPSTACK top value on argument list +					in( iMR, r4, rPSTACK ); // push PSTACK on argument list + +					/* common code */ +					in( iMTCTR, r0 ); +					in( iBCTRL ); + +					if ( rFIRST != r3 ) +						in( iMR, rFIRST, r3 ); // push return value on the top of the opstack +				} +				break; + +			case OP_PUSH: +				MAYBE_EMIT_CONST(); +				if ( RET_INT ) +					gpr_pos++; +				else +					fpr_pos++; +				/* no instructions here */ +				force_emit = 1; +				break; + +			case OP_POP: +				MAYBE_EMIT_CONST(); +				if ( ARG_INT ) +					gpr_pos--; +				else +					fpr_pos--; +				/* no instructions here */ +				force_emit = 1; +				break; + +			case OP_CONST: +				MAYBE_EMIT_CONST(); +				/* nothing here */ +				break; + +			case OP_LOCAL: +				MAYBE_EMIT_CONST(); +				{ +					signed long int hi, lo; +					hi = i_now->arg.ss[ 0 ]; +					lo = i_now->arg.ss[ 1 ]; +					if ( lo < 0 ) +						hi += 1; + +					gpr_pos++; +					if ( hi == 0 ) { +						in( iADDI, rFIRST, rPSTACK, lo ); +					} else { +						in( iADDIS, rFIRST, rPSTACK, hi ); +						if ( lo != 0 ) +							in( iADDI, rFIRST, rFIRST, lo ); +					} +				} +				break; + +			case OP_JUMP: +				if ( i_const ) { +					EMIT_FALSE_CONST(); + +					emitJump( +						i_const->arg.i, +						branchAlways, 0, 0 +					); +				} else { +					MAYBE_EMIT_CONST(); + +					in( iLL, r0, VM_Data_Offset( iPointers ), rVMDATA ); +					in( iRLWINM, rFIRST, rFIRST, GPRLEN_SHIFT, 0, 31-GPRLEN_SHIFT ); // mul * GPRLEN +					in( iLLX, r0, rFIRST, r0 ); // load pointer +					in( iMTCTR, r0 ); +					in( iBCTR ); +				} +				gpr_pos--; +				break; + +			case OP_EQ: +			case OP_NE: +				if ( i_const && i_const->arg.si >= -0x8000 && i_const->arg.si < 0x10000 ) { +					EMIT_FALSE_CONST(); +					if ( i_const->arg.si >= 0x8000 ) +						in( iCMPLWI, cr7, rSECOND, i_const->arg.i ); +					else +						in( iCMPWI, cr7, rSECOND, i_const->arg.si ); +				} else { +					MAYBE_EMIT_CONST(); +					in( iCMPW, cr7, rSECOND, rFIRST ); +				} +				emitJump( +					i_now->arg.i, +					(i_now->op == OP_EQ ? branchTrue : branchFalse), +					4*cr7+eq, 0 +				); +				gpr_pos -= 2; +				break; + +			case OP_LTI: +			case OP_GEI: +				if ( i_const && i_const->arg.si >= -0x8000 && i_const->arg.si < 0x8000 ) { +					EMIT_FALSE_CONST(); +					in( iCMPWI, cr7, rSECOND, i_const->arg.si ); +				} else { +					MAYBE_EMIT_CONST(); +					in( iCMPW, cr7, rSECOND, rFIRST ); +				} +				emitJump( +					i_now->arg.i, +					( i_now->op == OP_LTI ? branchTrue : branchFalse ), +					4*cr7+lt, 0 +				); +				gpr_pos -= 2; +				break; + +			case OP_GTI: +			case OP_LEI: +				if ( i_const && i_const->arg.si >= -0x8000 && i_const->arg.si < 0x8000 ) { +					EMIT_FALSE_CONST(); +					in( iCMPWI, cr7, rSECOND, i_const->arg.si ); +				} else { +					MAYBE_EMIT_CONST(); +					in( iCMPW, cr7, rSECOND, rFIRST ); +				} +				emitJump( +					i_now->arg.i, +					( i_now->op == OP_GTI ? branchTrue : branchFalse ), +					4*cr7+gt, 0 +				); +				gpr_pos -= 2; +				break; + +			case OP_LTU: +			case OP_GEU: +				if ( i_const && i_const->arg.i < 0x10000 ) { +					EMIT_FALSE_CONST(); +					in( iCMPLWI, cr7, rSECOND, i_const->arg.i ); +				} else { +					MAYBE_EMIT_CONST(); +					in( iCMPLW, cr7, rSECOND, rFIRST ); +				} +				emitJump( +					i_now->arg.i, +					( i_now->op == OP_LTU ? branchTrue : branchFalse ), +					4*cr7+lt, 0 +				); +				gpr_pos -= 2; +				break; + +			case OP_GTU: +			case OP_LEU: +				if ( i_const && i_const->arg.i < 0x10000 ) { +					EMIT_FALSE_CONST(); +					in( iCMPLWI, cr7, rSECOND, i_const->arg.i ); +				} else { +					MAYBE_EMIT_CONST(); +					in( iCMPLW, cr7, rSECOND, rFIRST ); +				} +				emitJump( +					i_now->arg.i, +					( i_now->op == OP_GTU ? branchTrue : branchFalse ), +					4*cr7+gt, 0 +				); +				gpr_pos -= 2; +				break; + +			case OP_EQF: +			case OP_NEF: +				MAYBE_EMIT_CONST(); +				in( iFCMPU, cr7, fSECOND, fFIRST ); +				emitJump( +					i_now->arg.i, +					( i_now->op == OP_EQF ? branchTrue : branchFalse ), +					4*cr7+eq, 0 +				); +				fpr_pos -= 2; +				break; + +			case OP_LTF: +			case OP_GEF: +				MAYBE_EMIT_CONST(); +				in( iFCMPU, cr7, fSECOND, fFIRST ); +				emitJump( +					i_now->arg.i, +					( i_now->op == OP_LTF ? branchTrue : branchFalse ), +					4*cr7+lt, 0 +				); +				fpr_pos -= 2; +				break; + +			case OP_GTF: +			case OP_LEF: +				MAYBE_EMIT_CONST(); +				in( iFCMPU, cr7, fSECOND, fFIRST ); +				emitJump( +					i_now->arg.i, +					( i_now->op == OP_GTF ? branchTrue : branchFalse ), +					4*cr7+gt, 0 +				); +				fpr_pos -= 2; +				break; + +			case OP_LOAD1: +				MAYBE_EMIT_CONST(); +#if OPTIMIZE_MASK +				in( iRLWINM, rFIRST, rFIRST, 0, fastMaskHi, fastMaskLo ); +#else +				in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA ); +				in( iAND, rFIRST, rFIRST, r0 ); +#endif +				in( iLBZX, rFIRST, rFIRST, rDATABASE ); +				break; + +			case OP_LOAD2: +				MAYBE_EMIT_CONST(); +#if OPTIMIZE_MASK +				in( iRLWINM, rFIRST, rFIRST, 0, fastMaskHi, fastMaskLo ); +#else +				in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA ); +				in( iAND, rFIRST, rFIRST, r0 ); +#endif +				in( iLHZX, rFIRST, rFIRST, rDATABASE ); +				break; + +			case OP_LOAD4: +				MAYBE_EMIT_CONST(); +#if OPTIMIZE_MASK +				in( iRLWINM, rFIRST, rFIRST, 0, fastMaskHi, fastMaskLo ); +#else +				in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA ); +				in( iAND, rFIRST, rFIRST, r0 ); +#endif +				if ( RET_INT ) { +					in( iLWZX, rFIRST, rFIRST, rDATABASE ); +				} else { +					fpr_pos++; +					in( iLFSX, fFIRST, rFIRST, rDATABASE ); +					gpr_pos--; +				} +				break; + +			case OP_STORE1: +				MAYBE_EMIT_CONST(); +#if OPTIMIZE_MASK +				in( iRLWINM, rSECOND, rSECOND, 0, fastMaskHi, fastMaskLo ); +#else +				in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA ); +				in( iAND, rSECOND, rSECOND, r0 ); +#endif +				in( iSTBX, rFIRST, rSECOND, rDATABASE ); +				gpr_pos -= 2; +				break; + +			case OP_STORE2: +				MAYBE_EMIT_CONST(); +#if OPTIMIZE_MASK +				in( iRLWINM, rSECOND, rSECOND, 0, fastMaskHi, fastMaskLo ); +#else +				in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA ); +				in( iAND, rSECOND, rSECOND, r0 ); +#endif +				in( iSTHX, rFIRST, rSECOND, rDATABASE ); +				gpr_pos -= 2; +				break; + +			case OP_STORE4: +				MAYBE_EMIT_CONST(); +				if ( ARG_INT ) { +#if OPTIMIZE_MASK +					in( iRLWINM, rSECOND, rSECOND, 0, fastMaskHi, fastMaskLo ); +#else +					in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA ); +					in( iAND, rSECOND, rSECOND, r0 ); +#endif + +					in( iSTWX, rFIRST, rSECOND, rDATABASE ); +					gpr_pos--; +				} else { +#if OPTIMIZE_MASK +					in( iRLWINM, rFIRST, rFIRST, 0, fastMaskHi, fastMaskLo ); +#else +					in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA ); +					in( iAND, rFIRST, rFIRST, r0 ); +#endif + +					in( iSTFSX, fFIRST, rFIRST, rDATABASE ); +					fpr_pos--; +				} +				gpr_pos--; +				break; + +			case OP_ARG: +				MAYBE_EMIT_CONST(); +				in( iADDI, r0, rPSTACK, i_now->arg.b ); +				if ( ARG_INT ) { +					in( iSTWX, rFIRST, rDATABASE, r0 ); +					gpr_pos--; +				} else { +					in( iSTFSX, fFIRST, rDATABASE, r0 ); +					fpr_pos--; +				} +				break; + +			case OP_BLOCK_COPY: +				MAYBE_EMIT_CONST(); +#if OPTIMIZE_COPY +				if ( i_now->arg.i <= SL( 16, 32 ) ) { +					/* block is very short so copy it in-place */ + +					unsigned int len = i_now->arg.i; +					unsigned int copied = 0, left = len; + +					in( iADD, rFIRST, rFIRST, rDATABASE ); +					in( iADD, rSECOND, rSECOND, rDATABASE ); + +					if ( len >= GPRLEN ) { +						long int i, words = len / GPRLEN; +						in( iLL, r0, 0, rFIRST ); +						for ( i = 1; i < words; i++ ) +							in( iLL, rTEMP( i - 1 ), GPRLEN * i, rFIRST ); + +						in( iSTL, r0, 0, rSECOND ); +						for ( i = 1; i < words; i++ ) +							in( iSTL, rTEMP( i - 1 ), GPRLEN * i, rSECOND ); + +						copied += words * GPRLEN; +						left -= copied; +					} + +					if ( SL( 0, left >= 4 ) ) { +						in( iLWZ, r0, copied+0, rFIRST ); +						in( iSTW, r0, copied+0, rSECOND ); +						copied += 4; +						left -= 4; +					} +					if ( left >= 4 ) { +						DIE("Bug in OP_BLOCK_COPY"); +					} +					if ( left == 3 ) { +						in( iLHZ, r0,	copied+0, rFIRST ); +						in( iLBZ, rTMP,	copied+2, rFIRST ); +						in( iSTH, r0,	copied+0, rSECOND ); +						in( iSTB, rTMP,	copied+2, rSECOND ); +					} else if ( left == 2 ) { +						in( iLHZ, r0, copied+0, rFIRST ); +						in( iSTH, r0, copied+0, rSECOND ); +					} else if ( left == 1 ) { +						in( iLBZ, r0, copied+0, rFIRST ); +						in( iSTB, r0, copied+0, rSECOND ); +					} +				} else +#endif +				{ +					unsigned long int r5_ori = 0; +					if ( i_now->arg.si >= -0x8000 && i_now->arg.si < 0x8000 ) { +						in( iLI, r5, i_now->arg.si ); +					} else if ( i_now->arg.i < 0x10000 ) { +						in( iLI, r5, 0 ); +						r5_ori = i_now->arg.i; +					} else { +						in( iLIS, r5, i_now->arg.ss[ 0 ] ); +						r5_ori = i_now->arg.us[ 1 ]; +					} + +					in( iLL, r0, VM_Data_Offset( BlockCopy ), rVMDATA ); // get blockCopy pointer + +					if ( r5_ori ) +						in( iORI, r5, r5, r5_ori ); + +					in( iMTCTR, r0 ); + +					if ( rFIRST != r4 ) +						in( iMR, r4, rFIRST ); +					if ( rSECOND != r3 ) +						in( iMR, r3, rSECOND ); + +					in( iBCTRL ); +				} + +				gpr_pos -= 2; +				break; + +			case OP_SEX8: +				MAYBE_EMIT_CONST(); +				in( iEXTSB, rFIRST, rFIRST ); +				break; + +			case OP_SEX16: +				MAYBE_EMIT_CONST(); +				in( iEXTSH, rFIRST, rFIRST ); +				break; + +			case OP_NEGI: +				MAYBE_EMIT_CONST(); +				in( iNEG, rFIRST, rFIRST ); +				break; + +			case OP_ADD: +				if ( i_const ) { +					EMIT_FALSE_CONST(); + +					signed short int hi, lo; +					hi = i_const->arg.ss[ 0 ]; +					lo = i_const->arg.ss[ 1 ]; +					if ( lo < 0 ) +						hi += 1; + +					if ( hi != 0 ) +						in( iADDIS, rSECOND, rSECOND, hi ); +					if ( lo != 0 ) +						in( iADDI, rSECOND, rSECOND, lo ); + +					// if both are zero no instruction will be written +					if ( hi == 0 && lo == 0 ) +						force_emit = 1; +				} else { +					MAYBE_EMIT_CONST(); +					in( iADD, rSECOND, rSECOND, rFIRST ); +				} +				gpr_pos--; +				break; + +			case OP_SUB: +				MAYBE_EMIT_CONST(); +				in( iSUB, rSECOND, rSECOND, rFIRST ); +				gpr_pos--; +				break; + +			case OP_DIVI: +				MAYBE_EMIT_CONST(); +				in( iDIVW, rSECOND, rSECOND, rFIRST ); +				gpr_pos--; +				break; + +			case OP_DIVU: +				MAYBE_EMIT_CONST(); +				in( iDIVWU, rSECOND, rSECOND, rFIRST ); +				gpr_pos--; +				break; + +			case OP_MODI: +				MAYBE_EMIT_CONST(); +				in( iDIVW, r0, rSECOND, rFIRST ); +				in( iMULLW, r0, r0, rFIRST ); +				in( iSUB, rSECOND, rSECOND, r0 ); +				gpr_pos--; +				break; + +			case OP_MODU: +				MAYBE_EMIT_CONST(); +				in( iDIVWU, r0, rSECOND, rFIRST ); +				in( iMULLW, r0, r0, rFIRST ); +				in( iSUB, rSECOND, rSECOND, r0 ); +				gpr_pos--; +				break; + +			case OP_MULI: +			case OP_MULU: +				MAYBE_EMIT_CONST(); +				in( iMULLW, rSECOND, rSECOND, rFIRST ); +				gpr_pos--; +				break; + +			case OP_BAND: +				MAYBE_EMIT_CONST(); +				in( iAND, rSECOND, rSECOND, rFIRST ); +				gpr_pos--; +				break; + +			case OP_BOR: +				MAYBE_EMIT_CONST(); +				in( iOR, rSECOND, rSECOND, rFIRST ); +				gpr_pos--; +				break; + +			case OP_BXOR: +				MAYBE_EMIT_CONST(); +				in( iXOR, rSECOND, rSECOND, rFIRST ); +				gpr_pos--; +				break; + +			case OP_BCOM: +				MAYBE_EMIT_CONST(); +				in( iNOT, rFIRST, rFIRST ); +				break; + +			case OP_LSH: +				MAYBE_EMIT_CONST(); +				in( iSLW, rSECOND, rSECOND, rFIRST ); +				gpr_pos--; +				break; + +			case OP_RSHI: +				MAYBE_EMIT_CONST(); +				in( iSRAW, rSECOND, rSECOND, rFIRST ); +				gpr_pos--; +				break; + +			case OP_RSHU: +				MAYBE_EMIT_CONST(); +				in( iSRW, rSECOND, rSECOND, rFIRST ); +				gpr_pos--; +				break; + +			case OP_NEGF: +				MAYBE_EMIT_CONST(); +				in( iFNEG, fFIRST, fFIRST ); +				break; + +			case OP_ADDF: +				MAYBE_EMIT_CONST(); +				in( iFADDS, fSECOND, fSECOND, fFIRST ); +				fpr_pos--; +				break; + +			case OP_SUBF: +				MAYBE_EMIT_CONST(); +				in( iFSUBS, fSECOND, fSECOND, fFIRST ); +				fpr_pos--; +				break; + +			case OP_DIVF: +				MAYBE_EMIT_CONST(); +				in( iFDIVS, fSECOND, fSECOND, fFIRST ); +				fpr_pos--; +				break; + +			case OP_MULF: +				MAYBE_EMIT_CONST(); +				in( iFMULS, fSECOND, fSECOND, fFIRST ); +				fpr_pos--; +				break; + +			case OP_CVIF: +				MAYBE_EMIT_CONST(); +				fpr_pos++; +				in( iXORIS, rFIRST, rFIRST, 0x8000 ); +				in( iLIS, r0, 0x4330 ); +				in( iSTW, rFIRST, stack_temp + 4, r1 ); +				in( iSTW, r0, stack_temp, r1 ); +				in( iLFS, fTMP, VM_Data_Offset( floatBase ), rVMDATA ); +				in( iLFD, fFIRST, stack_temp, r1 ); +				in( iFSUB, fFIRST, fFIRST, fTMP ); +				in( iFRSP, fFIRST, fFIRST ); +				gpr_pos--; +				break; + +			case OP_CVFI: +				MAYBE_EMIT_CONST(); +				gpr_pos++; +				in( iFCTIWZ, fFIRST, fFIRST ); +				in( iSTFD, fFIRST, stack_temp, r1 ); +				in( iLWZ, rFIRST, stack_temp + 4, r1 ); +				fpr_pos--; +				break; +		} + +		i_const = NULL; + +		if ( i_now->op != OP_CONST ) { +			// emit the instructions if it isn't OP_CONST +			emitEnd(); +		} else { +			// mark in what register the value should be saved +			if ( RET_INT ) +				i_now->regPos = ++gpr_pos; +			else +				i_now->regPos = ++fpr_pos; + +#if OPTIMIZE_HOLE +			i_const = i_now; +#else +			PPC_EmitConst( i_now ); +#endif +		} +	} +	if ( i_const ) +		DIE( "left (unused) OP_CONST" ); + +	{ +		// free opcode information, don't free first dummy one +		source_instruction_t *i_next = i_first->next; +		while ( i_next ) { +			i_now = i_next; +			i_next = i_now->next; +			PPC_Free( i_now ); +		} +	} +} + + +/* + * check which jumps are short enough to use signed 16bit immediate branch + */ +static void +PPC_ShrinkJumps( void ) +{ +	symbolic_jump_t *sj_now = sj_first; +	while ( (sj_now = sj_now->nextJump) ) { +		if ( sj_now->bo == branchAlways ) +			// non-conditional branch has 26bit immediate +			sj_now->parent->length = 1; + +		else { +			dest_instruction_t *di = di_pointers[ sj_now->jump_to ]; +			dest_instruction_t *ji = sj_now->parent; +			long int jump_length = 0; +			if ( ! di ) +				DIE( "No instruction to jump to" ); +			if ( ji->count > di->count ) { +				do { +					jump_length += di->length; +				} while ( ( di = di->next ) != ji ); +			} else { +				jump_length = 1; +				while ( ( ji = ji->next ) != di ) +					jump_length += ji->length; +			} +			if ( jump_length < 0x2000 ) +				// jump is short, use normal instruction +				sj_now->parent->length = 1; +		} +	} +} + +/* + * puts all the data in one place, it consists of many different tasks + */ +static void +PPC_ComputeCode( vm_t *vm ) +{ +	dest_instruction_t *di_now = di_first; + +	unsigned long int codeInstructions = 0; +	// count total instruciton number +	while ( (di_now = di_now->next ) ) +		codeInstructions += di_now->length; + +	size_t codeLength = sizeof( vm_data_t ) +		+ sizeof( unsigned int ) * data_acc +		+ sizeof( ppc_instruction_t ) * codeInstructions; + +	// get the memory for the generated code, smarter ppcs need the +	// mem to be marked as executable (whill change later) +	unsigned char *dataAndCode = mmap( NULL, codeLength, +		PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0 ); + +	if (dataAndCode == MAP_FAILED) +		DIE( "Not enough memory" ); + +	ppc_instruction_t *codeNow, *codeBegin; +	codeNow = codeBegin = (ppc_instruction_t *)( dataAndCode + VM_Data_Offset( data[ data_acc ] ) ); + +	ppc_instruction_t nop = IN( iNOP ); + +	// copy instructions to the destination +	// fills the jump instructions with nops +	// saves pointers of all instructions +	di_now = di_first; +	while ( (di_now = di_now->next ) ) { +		unsigned long int i_count = di_now->i_count; +		if ( i_count != FALSE_ICOUNT ) { +			if ( ! di_pointers[ i_count ] ) +				di_pointers[ i_count ] = (void *) codeNow; +		} + +		if ( di_now->jump == NULL ) { +			memcpy( codeNow, &(di_now->code[0]), di_now->length * sizeof( ppc_instruction_t ) ); +			codeNow += di_now->length; +		} else { +			long int i; +			symbolic_jump_t *sj; +			for ( i = 0; i < di_now->length; i++ ) +				codeNow[ i ] = nop; +			codeNow += di_now->length; + +			sj = di_now->jump; +			// save position of jumping instruction +			sj->parent = (void *)(codeNow - 1); +		} +	} + +	// compute the jumps and write corresponding instructions +	symbolic_jump_t *sj_now = sj_first; +	while ( (sj_now = sj_now->nextJump ) ) { +		ppc_instruction_t *jumpFrom = (void *) sj_now->parent; +		ppc_instruction_t *jumpTo = (void *) di_pointers[ sj_now->jump_to ]; +		signed long int jumpLength = jumpTo - jumpFrom; + +		// if jump is short, just write it +		if ( jumpLength >= - 8192 && jumpLength < 8192 ) { +			powerpc_iname_t branchConditional = sj_now->ext & branchExtLink ? iBCL : iBC; +			*jumpFrom = IN( branchConditional, sj_now->bo, sj_now->bi, jumpLength * 4 ); +			continue; +		} + +		// jump isn't short so write it as two instructions +		// +		// the letter one is a non-conditional branch instruction which +		// accepts immediate values big enough (26 bits) +		*jumpFrom = IN( (sj_now->ext & branchExtLink ? iBL : iB), jumpLength * 4 ); +		if ( sj_now->bo == branchAlways ) +			continue; + +		// there should have been additional space prepared for this case +		if ( jumpFrom[ -1 ] != nop ) +			DIE( "additional space for long jump not prepared" ); + +		// invert instruction condition +		long int bo = 0; +		switch ( sj_now->bo ) { +			case branchTrue: +				bo = branchFalse; +				break; +			case branchFalse: +				bo = branchTrue; +				break; +			default: +				DIE( "unrecognized branch type" ); +				break; +		} + +		// the former instruction is an inverted conditional branch which +		// jumps over the non-conditional one +		jumpFrom[ -1 ] = IN( iBC, bo, sj_now->bi, +2*4 ); +	} + +	vm->codeBase = dataAndCode; +	vm->codeLength = codeLength; + +	vm_data_t *data = (vm_data_t *)dataAndCode; + +#if ELF64 +	// prepare Official Procedure Descriptor for the generated code +	// and retrieve real function pointer for helper functions + +	opd_t *ac = (void *)VM_AsmCall, *bc = (void *)VM_BlockCopy; +	data->opd.function = codeBegin; +	// trick it into using the same TOC +	// this way we won't have to switch TOC before calling AsmCall or BlockCopy +	data->opd.toc = ac->toc; +	data->opd.env = ac->env; + +	data->AsmCall = ac->function; +	data->BlockCopy = bc->function; +#else +	data->AsmCall = VM_AsmCall; +	data->BlockCopy = VM_BlockCopy; +#endif + +	data->dataMask = vm->dataMask; +	data->iPointers = (ppc_instruction_t *)vm->instructionPointers; +	data->dataLength = VM_Data_Offset( data[ data_acc ] ); +	data->codeLength = ( codeNow - codeBegin ) * sizeof( ppc_instruction_t ); +	data->floatBase = 0x59800004; + + +	/* write dynamic data (float constants) */ +	{ +		local_data_t *d_next, *d_now = data_first; +		long int accumulated = 0; + +		do { +			long int i; +			for ( i = 0; i < d_now->count; i++ ) +				data->data[ accumulated + i ] = d_now->data[ i ]; + +			accumulated += d_now->count; +			d_next = d_now->next; +			PPC_Free( d_now ); + +			if ( !d_next ) +				break; +			d_now = d_next; +		} while (1); +		data_first = NULL; +	} + +	/* free most of the compilation memory */ +	{ +		di_now = di_first->next; +		PPC_Free( di_first ); +		PPC_Free( sj_first ); + +		while ( di_now ) { +			di_first = di_now->next; +			if ( di_now->jump ) +				PPC_Free( di_now->jump ); +			PPC_Free( di_now ); +			di_now = di_first; +		} +	} + +	return; +} + +static void +VM_Destroy_Compiled( vm_t *self ) +{ +	if ( self->codeBase ) { +		if ( munmap( self->codeBase, self->codeLength ) ) +			Com_Printf( S_COLOR_RED "Memory unmap failed, possible memory leak\n" ); +	} +	self->codeBase = NULL; +} + +void +VM_Compile( vm_t *vm, vmHeader_t *header ) +{ +	long int pc = 0; +	unsigned long int i_count; +	char* code; +	struct timeval tvstart = {0, 0}; +	source_instruction_t *i_first /* dummy */, *i_last = NULL, *i_now; + +	vm->compiled = qfalse; + +	gettimeofday(&tvstart, NULL); + +	PPC_MakeFastMask( vm->dataMask ); + +	i_first = PPC_Malloc( sizeof( source_instruction_t ) ); +	i_first->next = NULL; + +	// realloc instructionPointers with correct size +	// use Z_Malloc so vm.c will be able to free the memory +	if ( sizeof( void * ) != sizeof( int ) ) { +		Z_Free( vm->instructionPointers ); +		vm->instructionPointers = Z_Malloc( header->instructionCount * sizeof( void * ) ); +	} +	di_pointers = (void *)vm->instructionPointers; +	memset( di_pointers, 0, header->instructionCount * sizeof( void * ) ); + + +	PPC_CompileInit(); + +	/* +	 * read the input program +	 * divide it into functions and send each function to compiler +	 */ +	code = (char *)header + header->codeOffset; +	for ( i_count = 0; i_count < header->instructionCount; ++i_count ) +	{ +		unsigned char op = code[ pc++ ]; + +		if ( op == OP_ENTER ) { +			if ( i_first->next ) +				VM_CompileFunction( i_first ); +			i_first->next = NULL; +			i_last = i_first; +		} + +		i_now = PPC_Malloc( sizeof( source_instruction_t ) ); +		i_now->op = op; +		i_now->i_count = i_count; +		i_now->arg.i = 0; +		i_now->regA1 = 0; +		i_now->regA2 = 0; +		i_now->regR = 0; +		i_now->regPos = 0; +		i_now->next = NULL; + +		if ( vm_opInfo[op] & opImm4 ) { +			union { +				unsigned char b[4]; +				unsigned int i; +			} c = { { code[ pc + 3 ], code[ pc + 2 ], code[ pc + 1 ], code[ pc + 0 ] }, }; + +			i_now->arg.i = c.i; +			pc += 4; +		} else if ( vm_opInfo[op] & opImm1 ) { +			i_now->arg.b = code[ pc++ ]; +		} + +		i_last->next = i_now; +		i_last = i_now; +	} +	VM_CompileFunction( i_first ); +	PPC_Free( i_first ); + +	PPC_ShrinkJumps(); +	memset( di_pointers, 0, header->instructionCount * sizeof( void * ) ); +	PPC_ComputeCode( vm ); + +	/* check for uninitialized pointers */ +#ifdef DEBUG_VM +	long int i; +	for ( i = 0; i < header->instructionCount; i++ ) +		if ( di_pointers[ i ] == 0 ) +			Com_Printf( S_COLOR_RED "Pointer %ld not initialized !\n", i ); +#endif + +	/* mark memory as executable and not writeable */ +	if ( mprotect( vm->codeBase, vm->codeLength, PROT_READ|PROT_EXEC ) ) { + +		// it has failed, make sure memory is unmapped before throwing the error +		VM_Destroy_Compiled( vm ); +		DIE( "mprotect failed" ); +	} + +	vm->destroy = VM_Destroy_Compiled; +	vm->compiled = qtrue; + +	{ +		struct timeval tvdone = {0, 0}; +		struct timeval dur = {0, 0}; +		Com_Printf( "VM file %s compiled to %i bytes of code (%p - %p)\n", +			vm->name, vm->codeLength, vm->codeBase, vm->codeBase+vm->codeLength ); + +		gettimeofday(&tvdone, NULL); +		timersub(&tvdone, &tvstart, &dur); +		Com_Printf( "compilation took %lu.%06lu seconds\n", +			(long unsigned int)dur.tv_sec, (long unsigned int)dur.tv_usec ); +	} +} + +int +VM_CallCompiled( vm_t *vm, int *args ) +{ +	int retVal; +	int *argPointer; + +	vm_data_t *vm_dataAndCode = (void *)( vm->codeBase ); +	int programStack = vm->programStack; +	int stackOnEntry = programStack; + +	byte *image = vm->dataBase; + +	currentVM = vm; + +	vm->currentlyInterpreting = qtrue; + +	programStack -= 48; +	argPointer = (int *)&image[ programStack + 8 ]; +	memcpy( argPointer, args, 4 * 9 ); +	argPointer[ -1 ] = 0; +	argPointer[ -2 ] = -1; + +#ifdef VM_TIMES +	struct tms start_time, stop_time; +	clock_t time_diff; + +	times( &start_time ); +	time_outside_vm = 0; +#endif + +	/* call generated code */ +	{ +		int ( *entry )( void *, int, void * ); +#ifdef __PPC64__ +		entry = (void *)&(vm_dataAndCode->opd); +#else +		entry = (void *)(vm->codeBase + vm_dataAndCode->dataLength); +#endif +		retVal = entry( vm->codeBase, programStack, vm->dataBase ); +	} + +#ifdef VM_TIMES +	times( &stop_time ); +	time_diff = stop_time.tms_utime - start_time.tms_utime; +	time_total_vm += time_diff - time_outside_vm; +	if ( time_diff > 100 ) { +		printf( "App clock: %ld, vm total: %ld, vm this: %ld, vm real: %ld, vm out: %ld\n" +			"Inside VM %f%% of app time\n", +			stop_time.tms_utime, +			time_total_vm, +			time_diff, +			time_diff - time_outside_vm, +			time_outside_vm, +			(double)100 * time_total_vm / stop_time.tms_utime ); +	} +#endif + +	vm->programStack = stackOnEntry; +	vm->currentlyInterpreting = qfalse; + +	return retVal; +} diff --git a/src/qcommon/vm_powerpc_asm.c b/src/qcommon/vm_powerpc_asm.c new file mode 100644 index 0000000..58d65a0 --- /dev/null +++ b/src/qcommon/vm_powerpc_asm.c @@ -0,0 +1,1006 @@ +/* +=========================================================================== +Copyright (C) 2008 Przemyslaw Iskra <sparky@pld-linux.org> + +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 +=========================================================================== + + * File includes code from GNU binutils, exactly: + * - include/opcode/ppc.h - licensed under GPL v1 or later + * - opcodes/ppc-opc.c - licensed under GPL v2 or later + * + * ppc.h -- Header file for PowerPC opcode table + *   Copyright 1994, 1995, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, + *   2007 Free Software Foundation, Inc. + *   Written by Ian Lance Taylor, Cygnus Suppor + * + *   This file is part of GDB, GAS, and the GNU binutils. + * + * ppc-opc.c -- PowerPC opcode list + *   Copyright 1994, 1995, 1996, 1997, 1998, 2000, 2001, 2002, 2003, 2004, + *   2005, 2006, 2007 Free Software Foundation, Inc. + *   Written by Ian Lance Taylor, Cygnus Support + * + *   This file is part of GDB, GAS, and the GNU binutils. + * + */ + +#include "vm_local.h" +#include "vm_powerpc_asm.h" + +#include <string.h> +#include <stdio.h> +#include <inttypes.h> + +/* return nop on error */ +#define ASM_ERROR_OPC (0x60000000) + +/* + * BEGIN OF ppc.h + */ + +#define ppc_cpu_t int + +struct powerpc_opcode +{ +	const char *name; +	unsigned long opcode; +	unsigned long mask; +	ppc_cpu_t flags; +	unsigned char operands[8]; +}; + +static const struct powerpc_opcode powerpc_opcodes[]; +static const int powerpc_num_opcodes; + +#define PPC_OPCODE_PPC			 1 +#define PPC_OPCODE_POWER		 2 +#define PPC_OPCODE_POWER2		 4 +#define PPC_OPCODE_32			 8 +#define PPC_OPCODE_64		      0x10 +#define PPC_OPCODE_601		      0x20 +#define PPC_OPCODE_COMMON	      0x40 +#define PPC_OPCODE_ANY		      0x80 +#define PPC_OPCODE_64_BRIDGE	     0x100 +#define PPC_OPCODE_ALTIVEC	     0x200 +#define PPC_OPCODE_403		     0x400 +#define PPC_OPCODE_BOOKE	     0x800 +#define PPC_OPCODE_BOOKE64	    0x1000 +#define PPC_OPCODE_440		    0x2000 +#define PPC_OPCODE_POWER4	    0x4000 +#define PPC_OPCODE_NOPOWER4	    0x8000 +#define PPC_OPCODE_CLASSIC	   0x10000 +#define PPC_OPCODE_SPE		   0x20000 +#define PPC_OPCODE_ISEL		   0x40000 +#define PPC_OPCODE_EFS		   0x80000 +#define PPC_OPCODE_BRLOCK	  0x100000 +#define PPC_OPCODE_PMR		  0x200000 +#define PPC_OPCODE_CACHELCK	  0x400000 +#define PPC_OPCODE_RFMCI	  0x800000 +#define PPC_OPCODE_POWER5	 0x1000000 +#define PPC_OPCODE_E300		 0x2000000 +#define PPC_OPCODE_POWER6	 0x4000000 +#define PPC_OPCODE_CELL		 0x8000000 +#define PPC_OPCODE_PPCPS	0x10000000 +#define PPC_OPCODE_E500MC	0x20000000 +#define PPC_OPCODE_405		0x40000000 +#define PPC_OPCODE_VSX		0x80000000 + +#define PPC_OP(i) (((i) >> 26) & 0x3f) + +struct powerpc_operand +{ +	unsigned int bitm; +	int shift; +	unsigned long (*insert) +		(unsigned long, long, int, const char **); +	unsigned long flags; +}; + +static const struct powerpc_operand powerpc_operands[]; +static const unsigned int num_powerpc_operands; + +#define PPC_OPERAND_SIGNED (0x1) +#define PPC_OPERAND_SIGNOPT (0x2) +#define PPC_OPERAND_FAKE (0x4) +#define PPC_OPERAND_PARENS (0x8) +#define PPC_OPERAND_CR (0x10) +#define PPC_OPERAND_GPR (0x20) +#define PPC_OPERAND_GPR_0 (0x40) +#define PPC_OPERAND_FPR (0x80) +#define PPC_OPERAND_RELATIVE (0x100) +#define PPC_OPERAND_ABSOLUTE (0x200) +#define PPC_OPERAND_OPTIONAL (0x400) +#define PPC_OPERAND_NEXT (0x800) +#define PPC_OPERAND_NEGATIVE (0x1000) +#define PPC_OPERAND_VR (0x2000) +#define PPC_OPERAND_DS (0x4000) +#define PPC_OPERAND_DQ (0x8000) +#define PPC_OPERAND_PLUS1 (0x10000) +#define PPC_OPERAND_FSL (0x20000) +#define PPC_OPERAND_FCR (0x40000) +#define PPC_OPERAND_UDI (0x80000) +#define PPC_OPERAND_VSR (0x100000) + +/* + * END OF ppc.h + */ + +#define PPC_DEST_ARCH PPC_OPCODE_PPC + +ppc_instruction_t +asm_instruction( powerpc_iname_t sname, const int argc, const long int *argv ) +{ +	const char *errmsg = NULL; +	const char *name; +	unsigned long int ret; +	const struct powerpc_opcode *opcode = NULL; +	int argi, argj; + +	opcode = &powerpc_opcodes[ sname ]; +	name = opcode->name; + +	if ( ! opcode ) { +		printf( "Can't find opcode %d\n", sname ); +		return ASM_ERROR_OPC; +	} +	if ( ( opcode->flags & PPC_DEST_ARCH ) != PPC_DEST_ARCH ) { +		printf( "opcode %s not defined for this arch\n", name ); +		return ASM_ERROR_OPC; +	} + +	ret = opcode->opcode; + +	argi = argj = 0; +	while ( opcode->operands[ argi ] != 0 ) { +		long int op = 0; +		const struct powerpc_operand *operand = &powerpc_operands[ opcode->operands[ argi ] ]; + +		if ( ! (operand->flags & PPC_OPERAND_FAKE) ) { +			if ( argj >= argc ) { +				printf( "Not enough arguments for %s, got %d\n", name, argc ); +				return ASM_ERROR_OPC; +			} + +			op = argv[ argj++ ]; +		} + +		if ( operand->insert ) { +			errmsg = NULL; +			ret = operand->insert( ret, op, PPC_DEST_ARCH, &errmsg ); +			if ( errmsg ) { +				printf( "%s: error while inserting operand %d (0x%.2lx): %s\n", +					name, argi, op, errmsg ); +			} +		} else { +			unsigned long int opu = *(unsigned long int *)&op; +			unsigned long int bitm = operand->bitm; +			unsigned long int bitm_full = bitm | ( bitm & 1 ? 0 : 0xf ); + +			if ( operand->flags & PPC_OPERAND_SIGNED ) { +				bitm_full >>= 1; + +				if ( ( opu & ~bitm_full ) != 0 && ( opu | bitm_full ) != -1 ) +					printf( "%s: signed operand nr.%d to wide. op: %.8lx, mask: %.8lx\n", +						name, argi, opu, bitm ); +			} else { +				if ( ( opu & ~bitm_full ) != 0 ) +					printf( "%s: unsigned operand nr.%d to wide. op: %.8lx, mask: %.8lx\n", +						name, argi, opu, bitm ); +			} +			if ( (bitm & 1) == 0 ) { +				if ( opu & 0xf & ~bitm ) +					printf( "%s: operand nr.%d not aligned correctly. op: %.8lx, mask: %.8lx\n", +						name, argi, opu, bitm ); +			} + +			ret |= ( op & operand->bitm ) << operand->shift; +		} +		argi++; +	} +	if ( argc > argj ) { +		printf( "Too many arguments for %s, got %d\n", name, argc ); +		return ASM_ERROR_OPC; +	} + +	return ret; +} + + +/* + * BEGIN OF ppc-opc.c + */ + +#define ATTRIBUTE_UNUSED +#define _(x) (x) + +/* Local insertion and extraction functions. */ + +static unsigned long insert_bdm (unsigned long, long, int, const char **); +static unsigned long insert_bo (unsigned long, long, int, const char **); +static unsigned long insert_ras (unsigned long, long, int, const char **); +static unsigned long insert_rbs (unsigned long, long, int, const char **); + +/* The operands table. + +   The fields are bitm, shift, insert, extract, flags. +   */ + +static const struct powerpc_operand powerpc_operands[] = +{ +  /* The zero index is used to indicate the end of the list of +     operands.  */ +#define UNUSED 0 +  { 0, 0, NULL, 0 }, + +  /* The BA field in an XL form instruction.  */ +#define BA UNUSED + 1 +  /* The BI field in a B form or XL form instruction.  */ +#define BI BA +#define BI_MASK (0x1f << 16) +  { 0x1f, 16, NULL, PPC_OPERAND_CR }, + +  /* The BD field in a B form instruction.  The lower two bits are +     forced to zero.  */ +#define BD BA + 1 +  { 0xfffc, 0, NULL, PPC_OPERAND_RELATIVE | PPC_OPERAND_SIGNED }, + +  /* The BD field in a B form instruction when the - modifier is used. +     This sets the y bit of the BO field appropriately.  */ +#define BDM BD + 1 +  { 0xfffc, 0, insert_bdm, +      PPC_OPERAND_RELATIVE | PPC_OPERAND_SIGNED }, + +  /* The BF field in an X or XL form instruction.  */ +#define BF BDM + 1 +  /* The CRFD field in an X form instruction.  */ +#define CRFD BF +  { 0x7, 23, NULL, PPC_OPERAND_CR }, + +  /* An optional BF field.  This is used for comparison instructions, +     in which an omitted BF field is taken as zero.  */ +#define OBF BF + 1 +  { 0x7, 23, NULL, PPC_OPERAND_CR | PPC_OPERAND_OPTIONAL }, + +  /* The BO field in a B form instruction.  Certain values are +     illegal.  */ +#define BO OBF + 1 +#define BO_MASK (0x1f << 21) +  { 0x1f, 21, insert_bo, 0 }, + +  /* The condition register number portion of the BI field in a B form +     or XL form instruction.  This is used for the extended +     conditional branch mnemonics, which set the lower two bits of the +     BI field.  This field is optional.  */ +#define CR BO + 1 +  { 0x7, 18, NULL, PPC_OPERAND_CR | PPC_OPERAND_OPTIONAL }, + +  /* The D field in a D form instruction.  This is a displacement off +     a register, and implies that the next operand is a register in +     parentheses.  */ +#define D CR + 1 +  { 0xffff, 0, NULL, PPC_OPERAND_PARENS | PPC_OPERAND_SIGNED }, + +  /* The DS field in a DS form instruction.  This is like D, but the +     lower two bits are forced to zero.  */ +#define DS D + 1 +  { 0xfffc, 0, NULL, +    PPC_OPERAND_PARENS | PPC_OPERAND_SIGNED | PPC_OPERAND_DS }, + +  /* The FRA field in an X or A form instruction.  */ +#define FRA DS + 1 +#define FRA_MASK (0x1f << 16) +  { 0x1f, 16, NULL, PPC_OPERAND_FPR }, + +  /* The FRB field in an X or A form instruction.  */ +#define FRB FRA + 1 +#define FRB_MASK (0x1f << 11) +  { 0x1f, 11, NULL, PPC_OPERAND_FPR }, + +  /* The FRC field in an A form instruction.  */ +#define FRC FRB + 1 +#define FRC_MASK (0x1f << 6) +  { 0x1f, 6, NULL, PPC_OPERAND_FPR }, + +  /* The FRS field in an X form instruction or the FRT field in a D, X +     or A form instruction.  */ +#define FRS FRC + 1 +#define FRT FRS +  { 0x1f, 21, NULL, PPC_OPERAND_FPR }, + +  /* The LI field in an I form instruction.  The lower two bits are +     forced to zero.  */ +#define LI FRS + 1 +  { 0x3fffffc, 0, NULL, PPC_OPERAND_RELATIVE | PPC_OPERAND_SIGNED }, + +  /* The ME field in an M form instruction.  */ +#define ME LI + 1 +#define ME_MASK (0x1f << 1) +  { 0x1f, 1, NULL, 0 }, + +  /* The MB and ME fields in an M form instruction expressed a single +     operand which is a bitmask indicating which bits to select.  This +     is a two operand form using PPC_OPERAND_NEXT.  See the +     description in opcode/ppc.h for what this means.  */ +#define MBE ME + 1 +  { 0x1f, 6, NULL, PPC_OPERAND_OPTIONAL | PPC_OPERAND_NEXT }, + +  /* The RA field in an D, DS, DQ, X, XO, M, or MDS form instruction.  */ +#define RA MBE + 1 +#define RA_MASK (0x1f << 16) +  { 0x1f, 16, NULL, PPC_OPERAND_GPR }, + +  /* As above, but 0 in the RA field means zero, not r0.  */ +#define RA0 RA + 1 +  { 0x1f, 16, NULL, PPC_OPERAND_GPR_0 }, + +  /* The RA field in a D or X form instruction which is an updating +     store or an updating floating point load, which means that the RA +     field may not be zero.  */ +#define RAS RA0 + 1 +  { 0x1f, 16, insert_ras, PPC_OPERAND_GPR_0 }, + +  /* The RB field in an X, XO, M, or MDS form instruction.  */ +#define RB RAS + 1 +#define RB_MASK (0x1f << 11) +  { 0x1f, 11, NULL, PPC_OPERAND_GPR }, + +  /* The RB field in an X form instruction when it must be the same as +     the RS field in the instruction.  This is used for extended +     mnemonics like mr.  */ +#define RBS RB + 1 +  { 0x1f, 11, insert_rbs, PPC_OPERAND_FAKE }, + +  /* The RS field in a D, DS, X, XFX, XS, M, MD or MDS form +     instruction or the RT field in a D, DS, X, XFX or XO form +     instruction.  */ +#define RS RBS + 1 +#define RT RS +#define RT_MASK (0x1f << 21) +  { 0x1f, 21, NULL, PPC_OPERAND_GPR }, + +  /* The SH field in an X or M form instruction.  */ +#define SH RS + 1 +#define SH_MASK (0x1f << 11) +  /* The other UIMM field in a EVX form instruction.  */ +#define EVUIMM SH +  { 0x1f, 11, NULL, 0 }, + +  /* The SI field in a D form instruction.  */ +#define SI SH + 1 +  { 0xffff, 0, NULL, PPC_OPERAND_SIGNED }, + +  /* The UI field in a D form instruction.  */ +#define UI SI + 1 +  { 0xffff, 0, NULL, 0 }, + +}; + +static const unsigned int num_powerpc_operands = ARRAY_LEN (powerpc_operands); + +/* The functions used to insert and extract complicated operands.  */ + +/* The BD field in a B form instruction when the - modifier is used. +   This modifier means that the branch is not expected to be taken. +   For chips built to versions of the architecture prior to version 2 +   (ie. not Power4 compatible), we set the y bit of the BO field to 1 +   if the offset is negative.  When extracting, we require that the y +   bit be 1 and that the offset be positive, since if the y bit is 0 +   we just want to print the normal form of the instruction. +   Power4 compatible targets use two bits, "a", and "t", instead of +   the "y" bit.  "at" == 00 => no hint, "at" == 01 => unpredictable, +   "at" == 10 => not taken, "at" == 11 => taken.  The "t" bit is 00001 +   in BO field, the "a" bit is 00010 for branch on CR(BI) and 01000 +   for branch on CTR.  We only handle the taken/not-taken hint here. +   Note that we don't relax the conditions tested here when +   disassembling with -Many because insns using extract_bdm and +   extract_bdp always occur in pairs.  One or the other will always +   be valid.  */ + +static unsigned long +insert_bdm (unsigned long insn, +	    long value, +	    int dialect, +	    const char **errmsg ATTRIBUTE_UNUSED) +{ +  if ((dialect & PPC_OPCODE_POWER4) == 0) +    { +      if ((value & 0x8000) != 0) +	insn |= 1 << 21; +    } +  else +    { +      if ((insn & (0x14 << 21)) == (0x04 << 21)) +	insn |= 0x02 << 21; +      else if ((insn & (0x14 << 21)) == (0x10 << 21)) +	insn |= 0x08 << 21; +    } +  return insn | (value & 0xfffc); +} + + +/* Check for legal values of a BO field.  */ + +static int +valid_bo (long value, int dialect, int extract) +{ +  if ((dialect & PPC_OPCODE_POWER4) == 0) +    { +      int valid; +      /* Certain encodings have bits that are required to be zero. +	 These are (z must be zero, y may be anything): +	     001zy +	     011zy +	     1z00y +	     1z01y +	     1z1zz +      */ +      switch (value & 0x14) +	{ +	default: +	case 0: +	  valid = 1; +	  break; +	case 0x4: +	  valid = (value & 0x2) == 0; +	  break; +	case 0x10: +	  valid = (value & 0x8) == 0; +	  break; +	case 0x14: +	  valid = value == 0x14; +	  break; +	} +      /* When disassembling with -Many, accept power4 encodings too.  */ +      if (valid +	  || (dialect & PPC_OPCODE_ANY) == 0 +	  || !extract) +	return valid; +    } + +  /* Certain encodings have bits that are required to be zero. +     These are (z must be zero, a & t may be anything): +	 0000z +	 0001z +	 0100z +	 0101z +	 001at +	 011at +	 1a00t +	 1a01t +	 1z1zz +  */ +  if ((value & 0x14) == 0) +    return (value & 0x1) == 0; +  else if ((value & 0x14) == 0x14) +    return value == 0x14; +  else +    return 1; +} + +/* The BO field in a B form instruction.  Warn about attempts to set +   the field to an illegal value.  */ + +static unsigned long +insert_bo (unsigned long insn, +	   long value, +	   int dialect, +	   const char **errmsg) +{ +  if (!valid_bo (value, dialect, 0)) +    *errmsg = _("invalid conditional option"); +  return insn | ((value & 0x1f) << 21); +} + +/* The RA field in a D or X form instruction which is an updating +   store or an updating floating point load, which means that the RA +   field may not be zero.  */ + +static unsigned long +insert_ras (unsigned long insn, +	    long value, +	    int dialect ATTRIBUTE_UNUSED, +	    const char **errmsg) +{ +  if (value == 0) +    *errmsg = _("invalid register operand when updating"); +  return insn | ((value & 0x1f) << 16); +} + +/* The RB field in an X form instruction when it must be the same as +   the RS field in the instruction.  This is used for extended +   mnemonics like mr.  This operand is marked FAKE.  The insertion +   function just copies the BT field into the BA field, and the +   extraction function just checks that the fields are the same.  */ + +static unsigned long +insert_rbs (unsigned long insn, +	    long value ATTRIBUTE_UNUSED, +	    int dialect ATTRIBUTE_UNUSED, +	    const char **errmsg ATTRIBUTE_UNUSED) +{ +  return insn | (((insn >> 21) & 0x1f) << 11); +} + + +/* Macros used to form opcodes.  */ + +/* The main opcode.  */ +#define OP(x) ((((unsigned long)(x)) & 0x3f) << 26) +#define OP_MASK OP (0x3f) + +/* The main opcode combined with a trap code in the TO field of a D +   form instruction.  Used for extended mnemonics for the trap +   instructions.  */ +#define OPTO(x,to) (OP (x) | ((((unsigned long)(to)) & 0x1f) << 21)) +#define OPTO_MASK (OP_MASK | TO_MASK) + +/* The main opcode combined with a comparison size bit in the L field +   of a D form or X form instruction.  Used for extended mnemonics for +   the comparison instructions.  */ +#define OPL(x,l) (OP (x) | ((((unsigned long)(l)) & 1) << 21)) +#define OPL_MASK OPL (0x3f,1) + +/* An A form instruction.  */ +#define A(op, xop, rc) (OP (op) | ((((unsigned long)(xop)) & 0x1f) << 1) | (((unsigned long)(rc)) & 1)) +#define A_MASK A (0x3f, 0x1f, 1) + +/* An A_MASK with the FRB field fixed.  */ +#define AFRB_MASK (A_MASK | FRB_MASK) + +/* An A_MASK with the FRC field fixed.  */ +#define AFRC_MASK (A_MASK | FRC_MASK) + +/* An A_MASK with the FRA and FRC fields fixed.  */ +#define AFRAFRC_MASK (A_MASK | FRA_MASK | FRC_MASK) + +/* An AFRAFRC_MASK, but with L bit clear.  */ +#define AFRALFRC_MASK (AFRAFRC_MASK & ~((unsigned long) 1 << 16)) + +/* A B form instruction.  */ +#define B(op, aa, lk) (OP (op) | ((((unsigned long)(aa)) & 1) << 1) | ((lk) & 1)) +#define B_MASK B (0x3f, 1, 1) + +/* A B form instruction setting the BO field.  */ +#define BBO(op, bo, aa, lk) (B ((op), (aa), (lk)) | ((((unsigned long)(bo)) & 0x1f) << 21)) +#define BBO_MASK BBO (0x3f, 0x1f, 1, 1) + +/* A BBO_MASK with the y bit of the BO field removed.  This permits +   matching a conditional branch regardless of the setting of the y +   bit.  Similarly for the 'at' bits used for power4 branch hints.  */ +#define Y_MASK   (((unsigned long) 1) << 21) +#define AT1_MASK (((unsigned long) 3) << 21) +#define AT2_MASK (((unsigned long) 9) << 21) +#define BBOY_MASK  (BBO_MASK &~ Y_MASK) +#define BBOAT_MASK (BBO_MASK &~ AT1_MASK) + +/* A B form instruction setting the BO field and the condition bits of +   the BI field.  */ +#define BBOCB(op, bo, cb, aa, lk) \ +  (BBO ((op), (bo), (aa), (lk)) | ((((unsigned long)(cb)) & 0x3) << 16)) +#define BBOCB_MASK BBOCB (0x3f, 0x1f, 0x3, 1, 1) + +/* A BBOCB_MASK with the y bit of the BO field removed.  */ +#define BBOYCB_MASK (BBOCB_MASK &~ Y_MASK) +#define BBOATCB_MASK (BBOCB_MASK &~ AT1_MASK) +#define BBOAT2CB_MASK (BBOCB_MASK &~ AT2_MASK) + +/* A BBOYCB_MASK in which the BI field is fixed.  */ +#define BBOYBI_MASK (BBOYCB_MASK | BI_MASK) +#define BBOATBI_MASK (BBOAT2CB_MASK | BI_MASK) + +/* An Context form instruction.  */ +#define CTX(op, xop)   (OP (op) | (((unsigned long)(xop)) & 0x7)) +#define CTX_MASK CTX(0x3f, 0x7) + +/* An User Context form instruction.  */ +#define UCTX(op, xop)  (OP (op) | (((unsigned long)(xop)) & 0x1f)) +#define UCTX_MASK UCTX(0x3f, 0x1f) + +/* The main opcode mask with the RA field clear.  */ +#define DRA_MASK (OP_MASK | RA_MASK) + +/* A DS form instruction.  */ +#define DSO(op, xop) (OP (op) | ((xop) & 0x3)) +#define DS_MASK DSO (0x3f, 3) + +/* A DE form instruction.  */ +#define DEO(op, xop) (OP (op) | ((xop) & 0xf)) +#define DE_MASK DEO (0x3e, 0xf) + +/* An EVSEL form instruction.  */ +#define EVSEL(op, xop) (OP (op) | (((unsigned long)(xop)) & 0xff) << 3) +#define EVSEL_MASK EVSEL(0x3f, 0xff) + +/* An M form instruction.  */ +#define M(op, rc) (OP (op) | ((rc) & 1)) +#define M_MASK M (0x3f, 1) + +/* An M form instruction with the ME field specified.  */ +#define MME(op, me, rc) (M ((op), (rc)) | ((((unsigned long)(me)) & 0x1f) << 1)) + +/* An M_MASK with the MB and ME fields fixed.  */ +#define MMBME_MASK (M_MASK | MB_MASK | ME_MASK) + +/* An M_MASK with the SH and ME fields fixed.  */ +#define MSHME_MASK (M_MASK | SH_MASK | ME_MASK) + +/* An MD form instruction.  */ +#define MD(op, xop, rc) (OP (op) | ((((unsigned long)(xop)) & 0x7) << 2) | ((rc) & 1)) +#define MD_MASK MD (0x3f, 0x7, 1) + +/* An MD_MASK with the MB field fixed.  */ +#define MDMB_MASK (MD_MASK | MB6_MASK) + +/* An MD_MASK with the SH field fixed.  */ +#define MDSH_MASK (MD_MASK | SH6_MASK) + +/* An MDS form instruction.  */ +#define MDS(op, xop, rc) (OP (op) | ((((unsigned long)(xop)) & 0xf) << 1) | ((rc) & 1)) +#define MDS_MASK MDS (0x3f, 0xf, 1) + +/* An MDS_MASK with the MB field fixed.  */ +#define MDSMB_MASK (MDS_MASK | MB6_MASK) + +/* An SC form instruction.  */ +#define SC(op, sa, lk) (OP (op) | ((((unsigned long)(sa)) & 1) << 1) | ((lk) & 1)) +#define SC_MASK (OP_MASK | (((unsigned long)0x3ff) << 16) | (((unsigned long)1) << 1) | 1) + +/* An VX form instruction.  */ +#define VX(op, xop) (OP (op) | (((unsigned long)(xop)) & 0x7ff)) + +/* The mask for an VX form instruction.  */ +#define VX_MASK	VX(0x3f, 0x7ff) + +/* An VA form instruction.  */ +#define VXA(op, xop) (OP (op) | (((unsigned long)(xop)) & 0x03f)) + +/* The mask for an VA form instruction.  */ +#define VXA_MASK VXA(0x3f, 0x3f) + +/* An VXR form instruction.  */ +#define VXR(op, xop, rc) (OP (op) | (((rc) & 1) << 10) | (((unsigned long)(xop)) & 0x3ff)) + +/* The mask for a VXR form instruction.  */ +#define VXR_MASK VXR(0x3f, 0x3ff, 1) + +/* An X form instruction.  */ +#define X(op, xop) (OP (op) | ((((unsigned long)(xop)) & 0x3ff) << 1)) + +/* A Z form instruction.  */ +#define Z(op, xop) (OP (op) | ((((unsigned long)(xop)) & 0x1ff) << 1)) + +/* An X form instruction with the RC bit specified.  */ +#define XRC(op, xop, rc) (X ((op), (xop)) | ((rc) & 1)) + +/* A Z form instruction with the RC bit specified.  */ +#define ZRC(op, xop, rc) (Z ((op), (xop)) | ((rc) & 1)) + +/* The mask for an X form instruction.  */ +#define X_MASK XRC (0x3f, 0x3ff, 1) + +/* The mask for a Z form instruction.  */ +#define Z_MASK ZRC (0x3f, 0x1ff, 1) +#define Z2_MASK ZRC (0x3f, 0xff, 1) + +/* An X_MASK with the RA field fixed.  */ +#define XRA_MASK (X_MASK | RA_MASK) + +/* An XRA_MASK with the W field clear.  */ +#define XWRA_MASK (XRA_MASK & ~((unsigned long) 1 << 16)) + +/* An X_MASK with the RB field fixed.  */ +#define XRB_MASK (X_MASK | RB_MASK) + +/* An X_MASK with the RT field fixed.  */ +#define XRT_MASK (X_MASK | RT_MASK) + +/* An XRT_MASK mask with the L bits clear.  */ +#define XLRT_MASK (XRT_MASK & ~((unsigned long) 0x3 << 21)) + +/* An X_MASK with the RA and RB fields fixed.  */ +#define XRARB_MASK (X_MASK | RA_MASK | RB_MASK) + +/* An XRARB_MASK, but with the L bit clear.  */ +#define XRLARB_MASK (XRARB_MASK & ~((unsigned long) 1 << 16)) + +/* An X_MASK with the RT and RA fields fixed.  */ +#define XRTRA_MASK (X_MASK | RT_MASK | RA_MASK) + +/* An XRTRA_MASK, but with L bit clear.  */ +#define XRTLRA_MASK (XRTRA_MASK & ~((unsigned long) 1 << 21)) + +/* An X form instruction with the L bit specified.  */ +#define XOPL(op, xop, l) (X ((op), (xop)) | ((((unsigned long)(l)) & 1) << 21)) + +/* The mask for an X form comparison instruction.  */ +#define XCMP_MASK (X_MASK | (((unsigned long)1) << 22)) + +/* The mask for an X form comparison instruction with the L field +   fixed.  */ +#define XCMPL_MASK (XCMP_MASK | (((unsigned long)1) << 21)) + +/* An X form trap instruction with the TO field specified.  */ +#define XTO(op, xop, to) (X ((op), (xop)) | ((((unsigned long)(to)) & 0x1f) << 21)) +#define XTO_MASK (X_MASK | TO_MASK) + +/* An X form tlb instruction with the SH field specified.  */ +#define XTLB(op, xop, sh) (X ((op), (xop)) | ((((unsigned long)(sh)) & 0x1f) << 11)) +#define XTLB_MASK (X_MASK | SH_MASK) + +/* An X form sync instruction.  */ +#define XSYNC(op, xop, l) (X ((op), (xop)) | ((((unsigned long)(l)) & 3) << 21)) + +/* An X form sync instruction with everything filled in except the LS field.  */ +#define XSYNC_MASK (0xff9fffff) + +/* An X_MASK, but with the EH bit clear.  */ +#define XEH_MASK (X_MASK & ~((unsigned long )1)) + +/* An X form AltiVec dss instruction.  */ +#define XDSS(op, xop, a) (X ((op), (xop)) | ((((unsigned long)(a)) & 1) << 25)) +#define XDSS_MASK XDSS(0x3f, 0x3ff, 1) + +/* An XFL form instruction.  */ +#define XFL(op, xop, rc) (OP (op) | ((((unsigned long)(xop)) & 0x3ff) << 1) | (((unsigned long)(rc)) & 1)) +#define XFL_MASK XFL (0x3f, 0x3ff, 1) + +/* An X form isel instruction.  */ +#define XISEL(op, xop)  (OP (op) | ((((unsigned long)(xop)) & 0x1f) << 1)) +#define XISEL_MASK      XISEL(0x3f, 0x1f) + +/* An XL form instruction with the LK field set to 0.  */ +#define XL(op, xop) (OP (op) | ((((unsigned long)(xop)) & 0x3ff) << 1)) + +/* An XL form instruction which uses the LK field.  */ +#define XLLK(op, xop, lk) (XL ((op), (xop)) | ((lk) & 1)) + +/* The mask for an XL form instruction.  */ +#define XL_MASK XLLK (0x3f, 0x3ff, 1) + +/* An XL form instruction which explicitly sets the BO field.  */ +#define XLO(op, bo, xop, lk) \ +  (XLLK ((op), (xop), (lk)) | ((((unsigned long)(bo)) & 0x1f) << 21)) +#define XLO_MASK (XL_MASK | BO_MASK) + +/* An XL form instruction which explicitly sets the y bit of the BO +   field.  */ +#define XLYLK(op, xop, y, lk) (XLLK ((op), (xop), (lk)) | ((((unsigned long)(y)) & 1) << 21)) +#define XLYLK_MASK (XL_MASK | Y_MASK) + +/* An XL form instruction which sets the BO field and the condition +   bits of the BI field.  */ +#define XLOCB(op, bo, cb, xop, lk) \ +  (XLO ((op), (bo), (xop), (lk)) | ((((unsigned long)(cb)) & 3) << 16)) +#define XLOCB_MASK XLOCB (0x3f, 0x1f, 0x3, 0x3ff, 1) + +#define BB_MASK (0x1f << 11) +/* An XL_MASK or XLYLK_MASK or XLOCB_MASK with the BB field fixed.  */ +#define XLBB_MASK (XL_MASK | BB_MASK) +#define XLYBB_MASK (XLYLK_MASK | BB_MASK) +#define XLBOCBBB_MASK (XLOCB_MASK | BB_MASK) + +/* A mask for branch instructions using the BH field.  */ +#define XLBH_MASK (XL_MASK | (0x1c << 11)) + +/* An XL_MASK with the BO and BB fields fixed.  */ +#define XLBOBB_MASK (XL_MASK | BO_MASK | BB_MASK) + +/* An XL_MASK with the BO, BI and BB fields fixed.  */ +#define XLBOBIBB_MASK (XL_MASK | BO_MASK | BI_MASK | BB_MASK) + +/* An XO form instruction.  */ +#define XO(op, xop, oe, rc) \ +  (OP (op) | ((((unsigned long)(xop)) & 0x1ff) << 1) | ((((unsigned long)(oe)) & 1) << 10) | (((unsigned long)(rc)) & 1)) +#define XO_MASK XO (0x3f, 0x1ff, 1, 1) + +/* An XO_MASK with the RB field fixed.  */ +#define XORB_MASK (XO_MASK | RB_MASK) + +/* An XS form instruction.  */ +#define XS(op, xop, rc) (OP (op) | ((((unsigned long)(xop)) & 0x1ff) << 2) | (((unsigned long)(rc)) & 1)) +#define XS_MASK XS (0x3f, 0x1ff, 1) + +/* A mask for the FXM version of an XFX form instruction.  */ +#define XFXFXM_MASK (X_MASK | (1 << 11) | (1 << 20)) + +/* An XFX form instruction with the FXM field filled in.  */ +#define XFXM(op, xop, fxm, p4) \ +  (X ((op), (xop)) | ((((unsigned long)(fxm)) & 0xff) << 12) \ +   | ((unsigned long)(p4) << 20)) + +#define SPR_MASK (0x3ff << 11) +/* An XFX form instruction with the SPR field filled in.  */ +#define XSPR(op, xop, spr) \ +  (X ((op), (xop)) | ((((unsigned long)(spr)) & 0x1f) << 16) | ((((unsigned long)(spr)) & 0x3e0) << 6)) +#define XSPR_MASK (X_MASK | SPR_MASK) + +/* An XFX form instruction with the SPR field filled in except for the +   SPRBAT field.  */ +#define XSPRBAT_MASK (XSPR_MASK &~ SPRBAT_MASK) + +/* An XFX form instruction with the SPR field filled in except for the +   SPRG field.  */ +#define XSPRG_MASK (XSPR_MASK & ~(0x1f << 16)) + +/* An X form instruction with everything filled in except the E field.  */ +#define XE_MASK (0xffff7fff) + +/* An X form user context instruction.  */ +#define XUC(op, xop)  (OP (op) | (((unsigned long)(xop)) & 0x1f)) +#define XUC_MASK      XUC(0x3f, 0x1f) + +/* The BO encodings used in extended conditional branch mnemonics.  */ +#define BODNZF	(0x0) +#define BODNZFP	(0x1) +#define BODZF	(0x2) +#define BODZFP	(0x3) +#define BODNZT	(0x8) +#define BODNZTP	(0x9) +#define BODZT	(0xa) +#define BODZTP	(0xb) + +#define BOF	(0x4) +#define BOFP	(0x5) +#define BOFM4	(0x6) +#define BOFP4	(0x7) +#define BOT	(0xc) +#define BOTP	(0xd) +#define BOTM4	(0xe) +#define BOTP4	(0xf) + +#define BODNZ	(0x10) +#define BODNZP	(0x11) +#define BODZ	(0x12) +#define BODZP	(0x13) +#define BODNZM4 (0x18) +#define BODNZP4 (0x19) +#define BODZM4	(0x1a) +#define BODZP4	(0x1b) + +#define BOU	(0x14) + +/* The BI condition bit encodings used in extended conditional branch +   mnemonics.  */ +#define CBLT	(0) +#define CBGT	(1) +#define CBEQ	(2) +#define CBSO	(3) + +/* The TO encodings used in extended trap mnemonics.  */ +#define TOLGT	(0x1) +#define TOLLT	(0x2) +#define TOEQ	(0x4) +#define TOLGE	(0x5) +#define TOLNL	(0x5) +#define TOLLE	(0x6) +#define TOLNG	(0x6) +#define TOGT	(0x8) +#define TOGE	(0xc) +#define TONL	(0xc) +#define TOLT	(0x10) +#define TOLE	(0x14) +#define TONG	(0x14) +#define TONE	(0x18) +#define TOU	(0x1f) + +/* Smaller names for the flags so each entry in the opcodes table will +   fit on a single line.  */ +#undef	PPC +#define PPC     PPC_OPCODE_PPC +#define PPCCOM	PPC_OPCODE_PPC | PPC_OPCODE_COMMON +#define PPC64   PPC_OPCODE_64 | PPC_OPCODE_PPC +#define	COM     PPC_OPCODE_POWER | PPC_OPCODE_PPC | PPC_OPCODE_COMMON +#define	COM32   PPC_OPCODE_POWER | PPC_OPCODE_PPC | PPC_OPCODE_COMMON | PPC_OPCODE_32 + +/* The opcode table. + +   The format of the opcode table is: + +   NAME	     OPCODE	MASK		FLAGS		{ OPERANDS } + +   NAME is the name of the instruction. +   OPCODE is the instruction opcode. +   MASK is the opcode mask; this is used to tell the disassembler +     which bits in the actual opcode must match OPCODE. +   FLAGS are flags indicated what processors support the instruction. +   OPERANDS is the list of operands. + +   The disassembler reads the table in order and prints the first +   instruction which matches, so this table is sorted to put more +   specific instructions before more general instructions.  It is also +   sorted by major opcode.  */ + +static const struct powerpc_opcode powerpc_opcodes[] = { + +{ "cmplwi",  OPL(10,0),	OPL_MASK,	PPCCOM,		{ OBF, RA, UI } }, +{ "cmpwi",   OPL(11,0),	OPL_MASK,	PPCCOM,		{ OBF, RA, SI } }, +{ "cmpw",    XOPL(31,0,0), XCMPL_MASK,	PPCCOM,		{ OBF, RA, RB } }, +{ "cmplw",   XOPL(31,32,0), XCMPL_MASK, PPCCOM,	{ OBF, RA, RB } }, +{ "fcmpu",   X(63,0),	X_MASK|(3<<21),	COM,		{ BF, FRA, FRB } }, + +{ "li",	     OP(14),	DRA_MASK,	PPCCOM,		{ RT, SI } }, +{ "lis",     OP(15),	DRA_MASK,	PPCCOM,		{ RT, SI } }, + +{ "addi",    OP(14),	OP_MASK,	PPCCOM,		{ RT, RA0, SI } }, +{ "addis",   OP(15),	OP_MASK,	PPCCOM,		{ RT,RA0,SI } }, +{ "blt-",    BBOCB(16,BOT,CBLT,0,0), BBOATCB_MASK, PPCCOM,	{ CR, BDM } }, +{ "bc",	     B(16,0,0),	B_MASK,		COM,		{ BO, BI, BD } }, +{ "bcl",     B(16,0,1),	B_MASK,		COM,		{ BO, BI, BD } }, +{ "b",	     B(18,0,0),	B_MASK,		COM,		{ LI } }, +{ "bl",      B(18,0,1),	B_MASK,		COM,		{ LI } }, +{ "blr",     XLO(19,BOU,16,0), XLBOBIBB_MASK, PPCCOM,	{ 0 } }, +{ "bctr",    XLO(19,BOU,528,0), XLBOBIBB_MASK, COM,	{ 0 } }, +{ "bctrl",   XLO(19,BOU,528,1), XLBOBIBB_MASK, COM,	{ 0 } }, + +{ "rlwinm",  M(21,0),	M_MASK,		PPCCOM,		{ RA,RS,SH,MBE,ME } }, +{ "nop",     OP(24),	0xffffffff,	PPCCOM,		{ 0 } }, +{ "ori",     OP(24),	OP_MASK,	PPCCOM,		{ RA, RS, UI } }, +{ "xoris",   OP(27),	OP_MASK,	PPCCOM,		{ RA, RS, UI } }, +{ "ldx",     X(31,21),	X_MASK,		PPC64,		{ RT, RA0, RB } }, +{ "lwzx",    X(31,23),	X_MASK,		PPCCOM,		{ RT, RA0, RB } }, +{ "slw",     XRC(31,24,0), X_MASK,	PPCCOM,		{ RA, RS, RB } }, +{ "and",     XRC(31,28,0), X_MASK,	COM,		{ RA, RS, RB } }, +{ "sub",     XO(31,40,0,0), XO_MASK,	PPC,		{ RT, RB, RA } }, +{ "lbzx",    X(31,87),	X_MASK,		COM,		{ RT, RA0, RB } }, +{ "neg",     XO(31,104,0,0), XORB_MASK,	COM,		{ RT, RA } }, +{ "not",     XRC(31,124,0), X_MASK,	COM,		{ RA, RS, RBS } }, +{ "stwx",    X(31,151), X_MASK,		PPCCOM,		{ RS, RA0, RB } }, +{ "stbx",    X(31,215),	X_MASK,		COM,		{ RS, RA0, RB } }, +{ "mullw",   XO(31,235,0,0), XO_MASK,	PPCCOM,		{ RT, RA, RB } }, +{ "add",     XO(31,266,0,0), XO_MASK,	PPCCOM,		{ RT, RA, RB } }, +{ "lhzx",    X(31,279),	X_MASK,		COM,		{ RT, RA0, RB } }, +{ "xor",     XRC(31,316,0), X_MASK,	COM,		{ RA, RS, RB } }, +{ "mflr",    XSPR(31,339,8), XSPR_MASK, COM,		{ RT } }, +{ "sthx",    X(31,407),	X_MASK,		COM,		{ RS, RA0, RB } }, +{ "mr",	     XRC(31,444,0), X_MASK,	COM,		{ RA, RS, RBS } }, +{ "or",      XRC(31,444,0), X_MASK,	COM,		{ RA, RS, RB } }, +{ "divwu",   XO(31,459,0,0), XO_MASK,	PPC,		{ RT, RA, RB } }, +{ "mtlr",    XSPR(31,467,8), XSPR_MASK, COM,		{ RS } }, +{ "mtctr",   XSPR(31,467,9), XSPR_MASK, COM,		{ RS } }, +{ "divw",    XO(31,491,0,0), XO_MASK,	PPC,		{ RT, RA, RB } }, +{ "lfsx",    X(31,535),	X_MASK,		COM,		{ FRT, RA0, RB } }, +{ "srw",     XRC(31,536,0), X_MASK,	PPCCOM,		{ RA, RS, RB } }, +{ "stfsx",   X(31,663), X_MASK,		COM,		{ FRS, RA0, RB } }, +{ "sraw",    XRC(31,792,0), X_MASK,	PPCCOM,		{ RA, RS, RB } }, +{ "extsh",   XRC(31,922,0), XRB_MASK,	PPCCOM,		{ RA, RS } }, +{ "extsb",   XRC(31,954,0), XRB_MASK,	PPC,		{ RA, RS} }, + +{ "lwz",     OP(32),	OP_MASK,	PPCCOM,		{ RT, D, RA0 } }, +{ "lbz",     OP(34),	OP_MASK,	COM,		{ RT, D, RA0 } }, +{ "stw",     OP(36),	OP_MASK,	PPCCOM,		{ RS, D, RA0 } }, +{ "stwu",    OP(37),	OP_MASK,	PPCCOM,		{ RS, D, RAS } }, +{ "stb",     OP(38),	OP_MASK,	COM,		{ RS, D, RA0 } }, +{ "lhz",     OP(40),	OP_MASK,	COM,		{ RT, D, RA0 } }, +{ "sth",     OP(44),	OP_MASK,	COM,		{ RS, D, RA0 } }, +{ "lfs",     OP(48),	OP_MASK,	COM,		{ FRT, D, RA0 } }, +{ "lfd",     OP(50),	OP_MASK,	COM,		{ FRT, D, RA0 } }, +{ "stfs",    OP(52),	OP_MASK,	COM,		{ FRS, D, RA0 } }, +{ "stfd",    OP(54),	OP_MASK,	COM,		{ FRS, D, RA0 } }, +{ "ld",      DSO(58,0),	DS_MASK,	PPC64,		{ RT, DS, RA0 } }, + +{ "fdivs",   A(59,18,0), AFRC_MASK,	PPC,		{ FRT, FRA, FRB } }, +{ "fsubs",   A(59,20,0), AFRC_MASK,	PPC,		{ FRT, FRA, FRB } }, +{ "fadds",   A(59,21,0), AFRC_MASK,	PPC,		{ FRT, FRA, FRB } }, +{ "fmuls",   A(59,25,0), AFRB_MASK,	PPC,		{ FRT, FRA, FRC } }, +{ "std",     DSO(62,0),	DS_MASK,	PPC64,		{ RS, DS, RA0 } }, +{ "stdu",    DSO(62,1),	DS_MASK,	PPC64,		{ RS, DS, RAS } }, +{ "frsp",    XRC(63,12,0), XRA_MASK,	COM,		{ FRT, FRB } }, +{ "fctiwz",  XRC(63,15,0), XRA_MASK,	PPCCOM,		{ FRT, FRB } }, +{ "fsub",    A(63,20,0), AFRC_MASK,	PPCCOM,		{ FRT, FRA, FRB } }, +{ "fneg",    XRC(63,40,0), XRA_MASK,	COM,		{ FRT, FRB } }, +}; diff --git a/src/qcommon/vm_powerpc_asm.h b/src/qcommon/vm_powerpc_asm.h new file mode 100644 index 0000000..68e6984 --- /dev/null +++ b/src/qcommon/vm_powerpc_asm.h @@ -0,0 +1,156 @@ +/* +=========================================================================== +Copyright (C) 2008 Przemyslaw Iskra <sparky@pld-linux.org> + +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 VM_POWERPC_ASM_H +#define VM_POWERPC_ASM_H + +/* + * Register information according to: + * http://refspecs.freestandards.org/elf/elfspec_ppc.pdf + */ + +#define r0	0	// volatile +#define r1	1	// caller safe ( stack pointer ) +#define r2	2	// reserved +#define r3	3	// callee safe +#define r4	4	// callee safe +#define r5	5	// callee safe +#define r6	6	// callee safe +#define r7	7	// callee safe +#define r8	8	// callee safe +#define r9	9	// callee safe +#define r10	10	// callee safe +#define r11	11	// volatile +#define r12	12	// volatile +#define r13	13	// reserved ( small data area ) +#define r14	14	// caller safe +#define r15	15	// caller safe +#define r16	16	// caller safe +#define r17	17	// caller safe +#define r18	18	// caller safe +#define r19	19	// caller safe +#define r20	20	// caller safe +#define r21	21	// caller safe +#define r22	22	// caller safe +#define r23	23	// caller safe +#define r24	24	// caller safe +#define r25	25	// caller safe +#define r26	26	// caller safe +#define r27	27	// caller safe +#define r28	28	// caller safe +#define r29	29	// caller safe +#define r30	30	// caller safe +#define r31	31	// caller safe ( environment pointers ) + +#define f0	0	// callee safe +#define f1	1	// callee safe +#define f2	2	// callee safe +#define f3	3	// callee safe +#define f4	4	// callee safe +#define f5	5	// callee safe +#define f6	6	// callee safe +#define f7	7	// callee safe +#define f8	8	// callee safe +#define f9	9	// callee safe +#define f10	10	// callee safe +#define f11	11	// callee safe +#define f12	12	// callee safe +#define f13	13	// callee safe +#define f14	14	// caller safe +#define f15	15	// caller safe +#define f16	16	// caller safe +#define f17	17	// caller safe +#define f18	18	// caller safe +#define f19	19	// caller safe +#define f20	20	// caller safe +#define f21	21	// caller safe +#define f22	22	// caller safe +#define f23	23	// caller safe +#define f24	24	// caller safe +#define f25	25	// caller safe +#define f26	26	// caller safe +#define f27	27	// caller safe +#define f28	28	// caller safe +#define f29	29	// caller safe +#define f30	30	// caller safe +#define f31	31	// caller safe + +#define cr0	0	// volatile +#define cr1	1	// volatile +#define cr2	2	// caller safe +#define cr3	3	// caller safe +#define cr4	4	// caller safe +#define cr5	5	// volatile +#define cr6	6	// volatile +#define cr7	7	// volatile + +#define lt	0 +#define gt	1 +#define eq	2 +#define so	3 + +// branch bo field values +#define branchLikely	1 +#define branchFalse	4 +#define branchTrue	12 +#define branchAlways	20 + +// branch extensions (change branch type) +#define branchExtLink	0x0001 + + +/* + * This list must match exactly the powerpc_opcodes list from vm_powerpc_asm.c + * If you're changing the original list remember to regenerate this one. You + * may do so using this perl script: +   perl -p -e 'BEGIN{%t=("-"=>m=>"+"=>p=>"."=>d=>);$l=""}$o=0 if/^}/; +	if($o && s/^{ "(.*?)([\.+-])?".+/i\U$1\E$t{$2}/s){$_.="_" while$l{$_}; +	$l{$_}=1;if(length $l.$_ > 70){$s=$_;$_="\t$l\n";$l="$s,"}else +	{$l.=" $_,";$_=undef}}else{$o=1 if/powerpc_opcodes.*=/;$_=undef}; +	END{print "\t$l\n"}' < vm_powerpc_asm.c + */ + +typedef enum powerpc_iname { +	iCMPLWI, iCMPWI, iCMPW, iCMPLW, iFCMPU, iLI, iLIS, iADDI, iADDIS, +	iBLTm, iBC, iBCL, iB, iBL, iBLR, iBCTR, iBCTRL, iRLWINM, iNOP, iORI, +	iXORIS, iLDX, iLWZX, iSLW, iAND, iSUB, iLBZX, iNEG, iNOT, iSTWX, iSTBX, +	iMULLW, iADD, iLHZX, iXOR, iMFLR, iSTHX, iMR, iOR, iDIVWU, iMTLR, +	iMTCTR, iDIVW, iLFSX, iSRW, iSTFSX, iSRAW, iEXTSH, iEXTSB, iLWZ, iLBZ, +	iSTW, iSTWU, iSTB, iLHZ, iSTH, iLFS, iLFD, iSTFS, iSTFD, iLD, iFDIVS, +	iFSUBS, iFADDS, iFMULS, iSTD, iSTDU, iFRSP, iFCTIWZ, iFSUB, iFNEG, +} powerpc_iname_t; + +#include <stdint.h> + +typedef uint32_t ppc_instruction_t; + +extern ppc_instruction_t +asm_instruction( powerpc_iname_t, const int, const long int * ); + +#define IN( inst, args... ) \ +({\ +	const long int argv[] = { args };\ +	const int argc = sizeof( argv ) / sizeof( argv[0] ); \ +	asm_instruction( inst, argc, argv );\ +}) + +#endif diff --git a/src/qcommon/vm_sparc.c b/src/qcommon/vm_sparc.c new file mode 100644 index 0000000..370ee20 --- /dev/null +++ b/src/qcommon/vm_sparc.c @@ -0,0 +1,1648 @@ +/* +=========================================================================== +Copyright (C) 2009 David S. Miller <davem@davemloft.net> + +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 code is based almost entirely upon the vm_powerpc.c code by + * Przemyslaw Iskra.  All I did was make it work on Sparc :-) -DaveM + */ + +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/time.h> +#include <time.h> +#include <stddef.h> + +#include "vm_local.h" +#include "vm_sparc.h" + +/* exit() won't be called but use it because it is marked with noreturn */ +#define DIE( reason ) \ +	do { \ +		Com_Error(ERR_DROP, "vm_sparc compiler error: " reason "\n"); \ +		exit(1); \ +	} while(0) + +/* Select Length - first value on 32 bits, second on 64 */ +#ifdef __arch64__ +#define SL(a, b) (b) +#else +#define SL(a, b) (a) +#endif + +#define rTMP		G1 +#define rVMDATA		G2 +#define rPSTACK		G3 +#define rDATABASE	G4 +#define rDATAMASK	G5 + +struct sparc_opcode { +	const char	*name; +	unsigned int	opcode; +	unsigned int	mask; +	unsigned char	args[4]; +#define ARG_NONE	0 +#define ARG_RS1		1 +#define ARG_RS2		2 +#define ARG_RD		3 +#define ARG_SIMM13	4 +#define ARG_DISP30	5 +#define ARG_IMM22	6 +#define ARG_DISP22	7 +#define ARG_SWTRAP	8 +}; + +#define ARG_RS1_RS2_RD		{ ARG_RS1, ARG_RS2, ARG_RD } +#define ARG_RS1_SIMM13_RD	{ ARG_RS1, ARG_SIMM13, ARG_RD } +#define ARG_RS1_RS2		{ ARG_RS1, ARG_RS2 } +#define ARG_RS2_RD		{ ARG_RS2, ARG_RD } + +#define OP_MASK		0xc0000000 +#define OP2_MASK	0x01c00000 +#define OP3_MASK	0x01f80000 +#define OPF_MASK	0x00003fe0 + +#define IMM		0x00002000 + +#define FMT1(op)		((op) << 30), OP_MASK +#define FMT2(op,op2)		((op) << 30)|((op2)<<22), (OP_MASK | OP2_MASK) +#define FMT3(op,op3)		((op) << 30)|((op3)<<19), (OP_MASK | OP3_MASK | IMM) +#define FMT3I(op,op3)		((op) << 30)|((op3)<<19)|IMM, (OP_MASK | OP3_MASK | IMM) +#define FMT3F(op,op3,opf)	((op) << 30)|((op3)<<19)|((opf)<<5), \ +				(OP_MASK | OP3_MASK | OPF_MASK) + +#define BICC(A,COND)		FMT2(0,((A<<7)|(COND<<3)|0x2)) +#define BFCC(A,COND)		FMT2(0,((A<<7)|(COND<<3)|0x6)) +#define TICC(COND)		FMT3I(0,((COND<<6)|0x3a)) + +enum sparc_iname { +	CALL, NOP, SETHI, + +	BA, BN, BNE, BE, BG, BLE, BGE, BL, BGU, BLEU, BCC, BCS, +	BPOS, BNEG, BVC, BVS, + +	ADDI, ADD, +	ANDI, AND, +	ORI, OR, +	XORI, XOR, +	SUBI, SUB, +	ANDNI, ANDN, +	ORNI, ORN, +	XNORI, XNOR, + +	UMULI, UMUL, +	SMULI, SMUL, +	UDIVI, UDIV, +	SDIVI, SDIV, + +	SUBCCI, SUBCC, + +	SLLI, SLL, +	SRLI, SRL, +	SRAI, SRA, + +	WRI, WR, + +	SAVEI, SAVE, +	RESTOREI, RESTORE, + +	TA, + +	JMPLI, JMPL, + +	LDXI, LDX, +	LDUWI, LDUW, +	LDUHI, LDUH, +	LDUBI, LDUB, + +	STXI, STX, +	STWI, STW, +	STHI, STH, +	STBI, STB, + +	LDFI, LDF, +	STFI, STF, + +	FADD, FSUB, FCMP, FSTOI, FITOS, FNEG, FDIV, FMUL, +	FBE, FBNE, FBL, FBGE, FBG, FBLE, +}; + +#define LDLI	SL(LDUWI, LDXI) +#define LDL	SL(LDUW, LDX) +#define STLI	SL(STWI, STXI) +#define STL	SL(STW, STX) + +#define SPARC_NOP	0x01000000 + +static const struct sparc_opcode sparc_opcodes[] = { +	{ "call",	FMT1(1), { ARG_DISP30 }, }, +	{ "nop",	SPARC_NOP, 0xffffffff, { ARG_NONE }, }, /* sethi %hi(0), %g0 */ +	{ "sethi",	FMT2(0,4), { ARG_IMM22, ARG_RD }, }, +	{ "ba",		BICC(0,8), { ARG_DISP22 }, }, +	{ "bn",		BICC(0,0), { ARG_DISP22 }, }, +	{ "bne",	BICC(0,9), { ARG_DISP22 }, }, +	{ "be",		BICC(0,1), { ARG_DISP22 }, }, +	{ "bg",		BICC(0,10), { ARG_DISP22 }, }, +	{ "ble",	BICC(0,2), { ARG_DISP22 }, }, +	{ "bge",	BICC(0,11), { ARG_DISP22 }, }, +	{ "bl",		BICC(0,3), { ARG_DISP22 }, }, +	{ "bgu",	BICC(0,12), { ARG_DISP22 }, }, +	{ "bleu",	BICC(0,4), { ARG_DISP22 }, }, +	{ "bcc",	BICC(0,13), { ARG_DISP22 }, }, +	{ "bcs",	BICC(0,5), { ARG_DISP22 }, }, +	{ "bpos",	BICC(0,14), { ARG_DISP22 }, }, +	{ "bneg",	BICC(0,6), { ARG_DISP22 }, }, +	{ "bvc",	BICC(0,15), { ARG_DISP22 }, }, +	{ "bvs",	BICC(0,7), { ARG_DISP22 }, }, + +	{ "add",	FMT3I(2, 0x00), ARG_RS1_SIMM13_RD, }, +	{ "add",	FMT3 (2, 0x00), ARG_RS1_RS2_RD,    }, +	{ "and",	FMT3I(2, 0x01), ARG_RS1_SIMM13_RD, }, +	{ "and",	FMT3 (2, 0x01), ARG_RS1_RS2_RD,    }, +	{ "or",		FMT3I(2, 0x02), ARG_RS1_SIMM13_RD, }, +	{ "or",		FMT3 (2, 0x02), ARG_RS1_RS2_RD,    }, +	{ "xor",	FMT3I(2, 0x03), ARG_RS1_SIMM13_RD, }, +	{ "xor",	FMT3 (2, 0x03), ARG_RS1_RS2_RD,    }, +	{ "sub",	FMT3I(2, 0x04), ARG_RS1_SIMM13_RD, }, +	{ "sub",	FMT3 (2, 0x04), ARG_RS1_RS2_RD,    }, +	{ "andn",	FMT3I(2, 0x05), ARG_RS1_SIMM13_RD, }, +	{ "andn",	FMT3 (2, 0x05), ARG_RS1_RS2_RD,    }, +	{ "orn",	FMT3I(2, 0x06), ARG_RS1_SIMM13_RD, }, +	{ "orn",	FMT3 (2, 0x06), ARG_RS1_RS2_RD,    }, +	{ "xnor",	FMT3I(2, 0x07), ARG_RS1_SIMM13_RD, }, +	{ "xnor",	FMT3 (2, 0x07), ARG_RS1_RS2_RD,    }, + +	{ "umul",	FMT3I(2, 0x0a), ARG_RS1_SIMM13_RD, }, +	{ "umul",	FMT3 (2, 0x0a), ARG_RS1_RS2_RD,    }, +	{ "smul",	FMT3I(2, 0x0b), ARG_RS1_SIMM13_RD, }, +	{ "smul",	FMT3 (2, 0x0b), ARG_RS1_RS2_RD,    }, +	{ "udiv",	FMT3I(2, 0x0e), ARG_RS1_SIMM13_RD, }, +	{ "udiv",	FMT3 (2, 0x0e), ARG_RS1_RS2_RD,    }, +	{ "sdiv",	FMT3I(2, 0x0f), ARG_RS1_SIMM13_RD, }, +	{ "sdiv",	FMT3 (2, 0x0f), ARG_RS1_RS2_RD,    }, + +	{ "subcc",	FMT3I(2, 0x14), ARG_RS1_SIMM13_RD, }, +	{ "subcc",	FMT3 (2, 0x14), ARG_RS1_RS2_RD,    }, + +	{ "sll",	FMT3I(2, 0x25), ARG_RS1_SIMM13_RD, }, +	{ "sll",	FMT3 (2, 0x25), ARG_RS1_RS2_RD,    }, +	{ "srl",	FMT3I(2, 0x26), ARG_RS1_SIMM13_RD, }, +	{ "srl",	FMT3 (2, 0x26), ARG_RS1_RS2_RD,    }, +	{ "sra",	FMT3I(2, 0x27), ARG_RS1_SIMM13_RD, }, +	{ "sra",	FMT3 (2, 0x27), ARG_RS1_RS2_RD,    }, + +	{ "wr",		FMT3I(2, 0x30), ARG_RS1_SIMM13_RD, }, +	{ "wr",		FMT3 (2, 0x30), ARG_RS1_SIMM13_RD, }, + +	{ "save",	FMT3I(2,0x3c), ARG_RS1_SIMM13_RD, }, +	{ "save",	FMT3 (2,0x3c), ARG_RS1_RS2_RD,    }, +	{ "restore",	FMT3I(2,0x3d), ARG_RS1_SIMM13_RD, }, +	{ "restore",	FMT3 (2,0x3d), ARG_RS1_RS2_RD,    }, +	{ "ta",		TICC(8), { ARG_SWTRAP, ARG_NONE }, }, +	{ "jmpl",	FMT3I(2,0x38), ARG_RS1_SIMM13_RD, }, +	{ "jmpl",	FMT3 (2,0x38), ARG_RS1_RS2_RD,    }, + +	{ "ldx",	FMT3I(3,0x0b), ARG_RS1_SIMM13_RD, }, +	{ "ldx",	FMT3 (3,0x0b), ARG_RS1_RS2_RD,    }, +	{ "lduw",	FMT3I(3,0x00), ARG_RS1_SIMM13_RD, }, +	{ "lduw",	FMT3 (3,0x00), ARG_RS1_RS2_RD,    }, +	{ "lduh",	FMT3I(3,0x02), ARG_RS1_SIMM13_RD, }, +	{ "lduh",	FMT3 (3,0x02), ARG_RS1_RS2_RD,    }, +	{ "ldub",	FMT3I(3,0x01), ARG_RS1_SIMM13_RD, }, +	{ "ldub",	FMT3 (3,0x01), ARG_RS1_RS2_RD,    }, + +	{ "stx",	FMT3I(3,0x0e), ARG_RS1_SIMM13_RD, }, +	{ "stx",	FMT3 (3,0x0e), ARG_RS1_RS2_RD,    }, +	{ "stw",	FMT3I(3,0x04), ARG_RS1_SIMM13_RD, }, +	{ "stw",	FMT3 (3,0x04), ARG_RS1_RS2_RD,    }, +	{ "sth",	FMT3I(3,0x06), ARG_RS1_SIMM13_RD, }, +	{ "sth",	FMT3 (3,0x06), ARG_RS1_RS2_RD,    }, +	{ "stb",	FMT3I(3,0x05), ARG_RS1_SIMM13_RD, }, +	{ "stb",	FMT3 (3,0x05), ARG_RS1_RS2_RD,    }, + +	{ "ldf",	FMT3I(3,0x20), ARG_RS1_SIMM13_RD, }, +	{ "ldf",	FMT3 (3,0x20), ARG_RS1_RS2_RD,    }, +	{ "stf",	FMT3I(3,0x24), ARG_RS1_SIMM13_RD, }, +	{ "stf",	FMT3 (3,0x24), ARG_RS1_RS2_RD, }, + +	{ "fadd",	FMT3F(2,0x34,0x041), ARG_RS1_RS2_RD, }, +	{ "fsub",	FMT3F(2,0x34,0x045), ARG_RS1_RS2_RD, }, +	{ "fcmp",	FMT3F(2,0x35,0x051), ARG_RS1_RS2, }, +	{ "fstoi",	FMT3F(2,0x34,0x0d1), ARG_RS2_RD, }, +	{ "fitos",	FMT3F(2,0x34,0x0c4), ARG_RS2_RD, }, + +	{ "fneg",	FMT3F(2,0x34,0x005), ARG_RS2_RD, }, +	{ "fdiv",	FMT3F(2,0x34,0x04d), ARG_RS1_RS2_RD, }, +	{ "fmul",	FMT3F(2,0x34,0x049), ARG_RS1_RS2_RD, }, + +	{ "fbe",	BFCC(0,9), { ARG_DISP22 }, }, +	{ "fbne",	BFCC(0,1), { ARG_DISP22 }, }, +	{ "fbl",	BFCC(0,4), { ARG_DISP22 }, }, +	{ "fbge",	BFCC(0,11), { ARG_DISP22 }, }, +	{ "fbg",	BFCC(0,6), { ARG_DISP22 }, }, +	{ "fble",	BFCC(0,13), { ARG_DISP22 }, }, +}; +#define SPARC_NUM_OPCODES (ARRAY_LEN(sparc_opcodes)) + +#define RS1(X)			(((X) & 0x1f) << 14) +#define RS2(X)			(((X) & 0x1f) << 0) +#define RD(X)			(((X) & 0x1f) << 25) +#define SIMM13(X)		(((X) & 0x1fff) << 0) +#define IMM22(X)		(((X) & 0x3fffff) << 0) +#define DISP30(X)		((((X) >> 2) & 0x3fffffff) << 0) +#define DISP22(X)		((((X) >> 2) & 0x3fffff) << 0) +#define SWTRAP(X)		(((X) & 0x7f) << 0) + +#define SIMM13_P(X)		((unsigned int) (X) + 0x1000 < 0x2000) + +static void vimm(unsigned int val, int bits, int shift, int sgned, int arg_index) +{ +	unsigned int orig_val = val; +	int orig_bits = bits; + +	if (sgned) { +		int x = (int) val; +		if (x < 0) +			x = -x; +		val = (unsigned int) x; +		bits--; +	} +	if (val & ~((1U << bits) - 1U)) { +		Com_Printf("VM ERROR: immediate value 0x%08x out of %d bit range\n", +			   orig_val, orig_bits); +		DIE("sparc VM bug"); +	} +} + +static unsigned int sparc_assemble(enum sparc_iname iname, const int argc, const int *argv) +{ +	const struct sparc_opcode *op = &sparc_opcodes[iname]; +	unsigned int insn = op->opcode; +	int i, flt, rd_flt; + +	flt = (op->name[0] == 'f'); +	rd_flt = flt || (op->name[2] == 'f'); + +	for (i = 0; op->args[i] != ARG_NONE; i++) { +		int val = argv[i]; + +		switch (op->args[i]) { +		case ARG_RS1: insn |= RS1(val); break; +		case ARG_RS2: insn |= RS2(val); break; +		case ARG_RD:  insn |= RD(val); break; +		case ARG_SIMM13: insn |= SIMM13(val); vimm(val,13,0,1,i); break; +		case ARG_DISP30: insn |= DISP30(val); vimm(val,30,0,1,i); break; +		case ARG_IMM22: insn |= IMM22(val); vimm(val,22,0,0,i); break; +		case ARG_DISP22: insn |= DISP22(val); vimm(val,22,0,1,i); break; +		case ARG_SWTRAP: insn |= SWTRAP(val); vimm(val,7,0,0,i); break; +		} +	} + +	return insn; +} + +#define IN(inst, args...) \ +({	const int argv[] = { args }; \ +	const int argc = ARRAY_LEN(argv); \ +	sparc_assemble(inst, argc, argv); \ +}) + +#if 0 +static void pgreg(int reg_num, int arg_index, int flt) +{ +	if (!flt) { +		const char *fmt[] = { "%g", "%o", "%l", "%i" }; + +		Com_Printf("%s%s%d", +			   (arg_index ? ", " : ""), +			   fmt[reg_num >> 3], reg_num & 7); +	} else +		Com_Printf("%s%%f%d", (arg_index ? ", " : ""), reg_num); +} + +static void pimm(unsigned int val, int bits, int shift, int sgned, int arg_index) +	 +{ +	val >>= shift; +	val &= ((1 << bits) - 1); +	if (sgned) { +		int sval = val << (32 - bits); +		sval >>= (32 - bits); +		Com_Printf("%s%d", +			   (arg_index ? ", " : ""), sval); +	} else +		Com_Printf("%s0x%08x", +			   (arg_index ? ", " : ""), val); +} + +static void sparc_disassemble(unsigned int insn) +{ +	int op_idx; + +	for (op_idx = 0; op_idx < SPARC_NUM_OPCODES; op_idx++) { +		const struct sparc_opcode *op = &sparc_opcodes[op_idx]; +		int i, flt, rd_flt; + +		if ((insn & op->mask) != op->opcode) +			continue; + +		flt = (op->name[0] == 'f'); +		rd_flt = flt || (op->name[2] == 'f'); + +		Com_Printf("ASM: %7s\t", op->name); +		for (i = 0; op->args[i] != ARG_NONE; i++) { +			switch (op->args[i]) { +			case ARG_RS1: pgreg((insn >> 14) & 0x1f, i, flt); break; +			case ARG_RS2: pgreg((insn >> 0) & 0x1f, i, flt); break; +			case ARG_RD:  pgreg((insn >> 25) & 0x1f, i, rd_flt); break; +			case ARG_SIMM13: pimm(insn, 13, 0, 1, i); break; +			case ARG_DISP30: pimm(insn, 30, 0, 0, i); break; +			case ARG_IMM22: pimm(insn, 22, 0, 0, i); break; +			case ARG_DISP22: pimm(insn, 22, 0, 0, i); break; +			case ARG_SWTRAP: pimm(insn, 7, 0, 0, i); break; +			} +		} +		Com_Printf("\n"); +		return; +	} +} +#endif + +/* + * opcode information table: + * - length of immediate value + * - returned register type + * - required register(s) type + */ +#define opImm0	0x0000 /* no immediate */ +#define opImm1	0x0001 /* 1 byte immadiate value after opcode */ +#define opImm4	0x0002 /* 4 bytes immediate value after opcode */ + +#define opRet0	0x0000 /* returns nothing */ +#define opRetI	0x0004 /* returns integer */ +#define opRetF	0x0008 /* returns float */ +#define opRetIF	(opRetI | opRetF) /* returns integer or float */ + +#define opArg0	0x0000 /* requires nothing */ +#define opArgI	0x0010 /* requires integer(s) */ +#define opArgF	0x0020 /* requires float(s) */ +#define opArgIF	(opArgI | opArgF) /* requires integer or float */ + +#define opArg2I	0x0040 /* requires second argument, integer */ +#define opArg2F	0x0080 /* requires second argument, float */ +#define opArg2IF (opArg2I | opArg2F) /* requires second argument, integer or float */ + +static const unsigned char vm_opInfo[256] = +{ +	[OP_UNDEF]	= opImm0, +	[OP_IGNORE]	= opImm0, +	[OP_BREAK]	= opImm0, +	[OP_ENTER]	= opImm4, +			/* OP_LEAVE has to accept floats, they will be converted to ints */ +	[OP_LEAVE]	= opImm4 | opRet0 | opArgIF, +			/* only STORE4 and POP use values from OP_CALL, +			 * no need to convert floats back */ +	[OP_CALL]	= opImm0 | opRetI | opArgI, +	[OP_PUSH]	= opImm0 | opRetIF, +	[OP_POP]	= opImm0 | opRet0 | opArgIF, +	[OP_CONST]	= opImm4 | opRetIF, +	[OP_LOCAL]	= opImm4 | opRetI, +	[OP_JUMP]	= opImm0 | opRet0 | opArgI, + +	[OP_EQ]		= opImm4 | opRet0 | opArgI | opArg2I, +	[OP_NE]		= opImm4 | opRet0 | opArgI | opArg2I, +	[OP_LTI]	= opImm4 | opRet0 | opArgI | opArg2I, +	[OP_LEI]	= opImm4 | opRet0 | opArgI | opArg2I, +	[OP_GTI]	= opImm4 | opRet0 | opArgI | opArg2I, +	[OP_GEI]	= opImm4 | opRet0 | opArgI | opArg2I, +	[OP_LTU]	= opImm4 | opRet0 | opArgI | opArg2I, +	[OP_LEU]	= opImm4 | opRet0 | opArgI | opArg2I, +	[OP_GTU]	= opImm4 | opRet0 | opArgI | opArg2I, +	[OP_GEU]	= opImm4 | opRet0 | opArgI | opArg2I, +	[OP_EQF]	= opImm4 | opRet0 | opArgF | opArg2F, +	[OP_NEF]	= opImm4 | opRet0 | opArgF | opArg2F, +	[OP_LTF]	= opImm4 | opRet0 | opArgF | opArg2F, +	[OP_LEF]	= opImm4 | opRet0 | opArgF | opArg2F, +	[OP_GTF]	= opImm4 | opRet0 | opArgF | opArg2F, +	[OP_GEF]	= opImm4 | opRet0 | opArgF | opArg2F, + +	[OP_LOAD1]	= opImm0 | opRetI | opArgI, +	[OP_LOAD2]	= opImm0 | opRetI | opArgI, +	[OP_LOAD4]	= opImm0 | opRetIF| opArgI, +	[OP_STORE1]	= opImm0 | opRet0 | opArgI | opArg2I, +	[OP_STORE2]	= opImm0 | opRet0 | opArgI | opArg2I, +	[OP_STORE4]	= opImm0 | opRet0 | opArgIF| opArg2I, +	[OP_ARG]	= opImm1 | opRet0 | opArgIF, +	[OP_BLOCK_COPY]	= opImm4 | opRet0 | opArgI | opArg2I, + +	[OP_SEX8]	= opImm0 | opRetI | opArgI, +	[OP_SEX16]	= opImm0 | opRetI | opArgI, +	[OP_NEGI]	= opImm0 | opRetI | opArgI, +	[OP_ADD]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_SUB]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_DIVI]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_DIVU]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_MODI]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_MODU]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_MULI]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_MULU]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_BAND]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_BOR]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_BXOR]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_BCOM]	= opImm0 | opRetI | opArgI, +	[OP_LSH]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_RSHI]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_RSHU]	= opImm0 | opRetI | opArgI | opArg2I, +	[OP_NEGF]	= opImm0 | opRetF | opArgF, +	[OP_ADDF]	= opImm0 | opRetF | opArgF | opArg2F, +	[OP_SUBF]	= opImm0 | opRetF | opArgF | opArg2F, +	[OP_DIVF]	= opImm0 | opRetF | opArgF | opArg2F, +	[OP_MULF]	= opImm0 | opRetF | opArgF | opArg2F, +	[OP_CVIF]	= opImm0 | opRetF | opArgI, +	[OP_CVFI]	= opImm0 | opRetI | opArgF, +}; + +static const 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", +}; + +static void VM_Destroy_Compiled(vm_t *vm) +{ +	if (vm->codeBase) { +		if (munmap(vm->codeBase, vm->codeLength)) +			Com_Printf(S_COLOR_RED "Memory unmap failed, possible memory leak\n"); +	} +	vm->codeBase = NULL; +} + +typedef struct VM_Data { +	unsigned int dataLength; +	unsigned int codeLength; +	unsigned int *CallThunk; +	int (*AsmCall)(int, int); +	void (*BlockCopy)(unsigned int, unsigned int, unsigned int); +	unsigned int *iPointers; +	unsigned int data[0]; +} vm_data_t; + +#ifdef offsetof +# define VM_Data_Offset(field)		offsetof(vm_data_t, field) +#else +# define OFFSET(structName, field) \ +	((void *)&(((structName *)NULL)->field) - NULL) +# define VM_Data_Offset(field)		OFFSET(vm_data_t, field) +#endif + +struct src_insn { +	unsigned char		op; +	unsigned int		i_count; + +	union { +		unsigned int	i; +		signed int	si; +		signed short	ss[2]; +		unsigned short	us[2]; +		unsigned char	b; +	} arg; + +	unsigned char		dst_reg_flags; +	unsigned char		src1_reg_flags; +	unsigned char		src2_reg_flags; +#define REG_FLAGS_FLOAT		0x1 + +	struct src_insn		*next; +}; + +struct dst_insn; +struct jump_insn { +	enum sparc_iname	jump_iname; +	int			jump_dest_insn; +	struct dst_insn		*parent; +	struct jump_insn	*next; +}; + +struct dst_insn { +	struct dst_insn		*next; + +	unsigned int		count; +	unsigned int		i_count; + +	struct jump_insn	*jump; +	unsigned int		length; +	unsigned int		code[0]; +}; + +#define HUNK_SIZE		29 +struct data_hunk { +	struct data_hunk *next; +	int count; +	unsigned int data[HUNK_SIZE]; +}; + +struct func_info { +	struct src_insn		*first; +	struct src_insn		*last; +	int			has_call; +	int			need_float_tmp; + +	struct src_insn		*cached_const; + +	int			stack_space; +	int			gpr_pos; +#define rFIRST(fp)		((fp)->gpr_pos - 1) +#define rSECOND(fp)		((fp)->gpr_pos - 2) +#define POP_GPR(fp)		((fp)->gpr_pos--) +#define PUSH_GPR(fp)		((fp)->gpr_pos++) + +	int			fpr_pos; +#define fFIRST(fp)		((fp)->fpr_pos - 1) +#define fSECOND(fp)		((fp)->fpr_pos - 2) +#define POP_FPR(fp)		((fp)->fpr_pos--) +#define PUSH_FPR(fp)		((fp)->fpr_pos++) + +#define INSN_BUF_SIZE		50 +	unsigned int		insn_buf[INSN_BUF_SIZE]; +	int			insn_index; + +	int			saved_icount; +	int			force_emit; + +	struct jump_insn	*jump_first; +	struct jump_insn	*jump_last; + +	struct dst_insn		*dst_first; +	struct dst_insn		*dst_last; +	int			dst_count; + +	struct dst_insn		**dst_by_i_count; + +	struct data_hunk	*data_first; +	int			data_num; +}; + +#define THUNK_ICOUNT		-1 + +static unsigned int sparc_push_data(struct func_info * const fp, unsigned int val) +{ +	struct data_hunk *last, *dp = fp->data_first; +	int off = 0; + +	last = NULL; +	while (dp) { +		int i; + +		for (i = 0; i < dp->count; i++) { +			if (dp->data[i] == val) { +				off += i; +				return VM_Data_Offset(data[off]); +			} +		} +		off += dp->count; +		last = dp; +		dp = dp->next; +	} + +	dp = last; +	if (!dp || dp->count >= HUNK_SIZE) { +		struct data_hunk *new = Z_Malloc(sizeof(*new)); +		if (!dp) +			fp->data_first = new; +		else +			dp->next = new; +		dp = new; +		dp->count = 0; +		dp->next = NULL; +	} +	dp->data[dp->count++] = val; +	fp->data_num = off + 1; +	return VM_Data_Offset(data[off]); +} + +static void dst_insn_insert_tail(struct func_info * const fp, +				 struct dst_insn *dp) +{ +	if (!fp->dst_first) { +		fp->dst_first = fp->dst_last = dp; +	} else { +		fp->dst_last->next = dp; +		fp->dst_last = dp; +	} +} + +static void jump_insn_insert_tail(struct func_info * const fp, +				  struct jump_insn *jp) +{ +	if (!fp->jump_first) { +		fp->jump_first = fp->jump_last = jp; +	} else { +		fp->jump_last->next = jp; +		fp->jump_last = jp; +	} +} + +static struct dst_insn *dst_new(struct func_info * const fp, unsigned int length, +				struct jump_insn *jp, int insns_size) +{ +	struct dst_insn *dp = Z_Malloc(sizeof(struct dst_insn) + insns_size); + +	dp->length = length; +	dp->jump = jp; +	dp->count = fp->dst_count++; +	dp->i_count = fp->saved_icount; +	dp->next = NULL; +	if (fp->saved_icount != THUNK_ICOUNT) +		fp->dst_by_i_count[fp->saved_icount] = dp; + +	return dp; +} + +static void dst_insn_append(struct func_info * const fp) +{ +	int insns_size = (sizeof(unsigned int) * fp->insn_index); +	struct dst_insn *dp; + +	dp = dst_new(fp, fp->insn_index, NULL, insns_size); +	if (insns_size) +		memcpy(&dp->code[0], fp->insn_buf, insns_size); +	dst_insn_insert_tail(fp, dp); + +	fp->insn_index = 0; +} + +static void jump_insn_append(struct func_info * const fp, enum sparc_iname iname, int dest) +{ +	struct jump_insn *jp = Z_Malloc(sizeof(*jp)); +	struct dst_insn *dp; + +	dp = dst_new(fp, 2, jp, 0); + +	jp->jump_iname = iname; +	jp->jump_dest_insn = dest; +	jp->parent = dp; +	jp->next = NULL; + +	jump_insn_insert_tail(fp, jp); +	dst_insn_insert_tail(fp, dp); +} + +static void start_emit(struct func_info * const fp, int i_count) +{ +	fp->saved_icount = i_count; +	fp->insn_index = 0; +	fp->force_emit = 0; +} + +static void __do_emit_one(struct func_info * const fp, unsigned int insn) +{ +	fp->insn_buf[fp->insn_index++] = insn; +} + +#define in(inst, args...) __do_emit_one(fp,  IN(inst, args)) + +static void end_emit(struct func_info * const fp) +{ +	if (fp->insn_index || fp->force_emit) +		dst_insn_append(fp); +} + +static void emit_jump(struct func_info * const fp, enum sparc_iname iname, int dest) +{ +	end_emit(fp); +	jump_insn_append(fp, iname, dest); +} + +static void analyze_function(struct func_info * const fp) +{ +	struct src_insn *value_provider[20] = { NULL }; +	struct src_insn *sp = fp->first; +	int opstack_depth = 0; + +	while ((sp = sp->next) != NULL) { +		unsigned char opi, op = sp->op; + +		opi = vm_opInfo[op]; +		if (opi & opArgIF) { +			struct src_insn *vp = value_provider[--opstack_depth]; +			unsigned char vpopi = vm_opInfo[vp->op]; + +			if ((opi & opArgI) && (vpopi & opRetI)) { +				/* src1 and dst are integers */ +			} else if ((opi & opArgF) && (vpopi & opRetF)) { +				/* src1 and dst are floats */ +				vp->dst_reg_flags |= REG_FLAGS_FLOAT; +				sp->src1_reg_flags = REG_FLAGS_FLOAT; +			} else { +				/* illegal combination */ +				DIE("unrecognized instruction combination"); +			} +		} +		if (opi & opArg2IF) { +			struct src_insn *vp = value_provider[--opstack_depth]; +			unsigned char vpopi = vm_opInfo[vp->op]; + +			if ((opi & opArg2I) && (vpopi & opRetI)) { +				/* src2 and dst are integers */ +			} else if ( (opi & opArg2F) && (vpopi & opRetF) ) { +				/* src2 and dst are floats */ +				vp->dst_reg_flags |= REG_FLAGS_FLOAT; +				sp->src2_reg_flags = REG_FLAGS_FLOAT; +			} else { +				/* illegal combination */ +				DIE("unrecognized instruction combination"); +			} +		} +		if (opi & opRetIF) { +			value_provider[opstack_depth] = sp; +			opstack_depth++; +		} +	} +} + +static int asmcall(int call, int pstack) +{ +	vm_t *savedVM = currentVM; +	int i, ret; + +	currentVM->programStack = pstack - 4; +	if (sizeof(intptr_t) == sizeof(int)) { +		intptr_t *argPosition = (intptr_t *)((byte *)currentVM->dataBase + pstack + 4); +		argPosition[0] = -1 - call; +		ret = currentVM->systemCall(argPosition); +	} else { +		intptr_t args[11]; + +		args[0] = -1 - call; +		int *argPosition = (int *)((byte *)currentVM->dataBase + pstack + 4); +		for( i = 1; i < 11; i++ ) +			args[i] = argPosition[i]; + +		ret = currentVM->systemCall(args); +	} + +	currentVM = savedVM; + +	return ret; +} + +static void blockcopy(unsigned int dest, unsigned int src, unsigned int count) +{ +	unsigned int dataMask = currentVM->dataMask; + +	if ((dest & dataMask) != dest || +	    (src & dataMask) != src || +	    ((dest+count) & dataMask) != dest + count || +	    ((src+count) & dataMask) != src + count) { +		DIE("OP_BLOCK_COPY out of range!"); +	} + +	memcpy(currentVM->dataBase+dest, currentVM->dataBase+src, count); +} + +static void do_emit_const(struct func_info * const fp, struct src_insn *sp) +{ +	start_emit(fp, sp->i_count); +	if (sp->dst_reg_flags & REG_FLAGS_FLOAT) { +		in(LDFI, rVMDATA, sparc_push_data(fp, sp->arg.i), fFIRST(fp)); +	} else { +		if ((sp->arg.i & ~0x3ff) == 0) { +			in(ORI, G0, sp->arg.i & 0x3ff, rFIRST(fp)); +		} else if ((sp->arg.i & 0x3ff) == 0) { +			in(SETHI, sp->arg.i >> 10, rFIRST(fp)); +		} else { +			in(SETHI, sp->arg.i >> 10, rFIRST(fp)); +			in(ORI, rFIRST(fp), sp->arg.i & 0x3ff, rFIRST(fp)); +		} +	} +	end_emit(fp); +} + +#define MAYBE_EMIT_CONST(fp)	\ +do {	if ((fp)->cached_const) {	       \ +		int saved_i_count = (fp)->saved_icount; \ +		do_emit_const(fp, (fp)->cached_const); \ +		(fp)->saved_icount = saved_i_count; \ +	} \ +} while (0) + +#define EMIT_FALSE_CONST(fp)					\ +do {	int saved_i_count = (fp)->saved_icount;			\ +	(fp)->saved_icount = (fp)->cached_const->i_count;	\ +	dst_insn_append(fp);					\ +	(fp)->saved_icount = saved_i_count;			\ +} while (0) + +static void compile_one_insn(struct func_info * const fp, struct src_insn *sp) +{ +	start_emit(fp, sp->i_count); + +	switch (sp->op) { +	default: +		Com_Printf("VM: Unhandled opcode 0x%02x[%s]\n", +			   sp->op, +			   opnames[sp->op] ? opnames[sp->op] : "UNKNOWN"); +		DIE("Unsupported opcode"); +		break; + +	case OP_ENTER: { +		int stack = SL(64, 128); + +		if (fp->need_float_tmp) +			stack += 16; + +		in(SAVEI, O6, -stack, O6); +		if (!SIMM13_P(sp->arg.si)) { +			in(SETHI, sp->arg.i >> 10, rTMP); +			in(ORI, rTMP, sp->arg.i & 0x3ff, rTMP); +			in(SUB, rPSTACK, rTMP, rPSTACK); +		} else +			in(SUBI, rPSTACK, sp->arg.si, rPSTACK); +		break; +	} +	case OP_LEAVE: +		if (fp->cached_const && SIMM13_P(fp->cached_const->arg.si)) { +			EMIT_FALSE_CONST(fp); +			if (fp->cached_const->src1_reg_flags & REG_FLAGS_FLOAT) +				DIE("constant float in OP_LEAVE"); + +			if (!SIMM13_P(sp->arg.si)) { +				in(SETHI, sp->arg.i >> 10, rTMP); +				in(ORI, rTMP, sp->arg.i & 0x3ff, rTMP); +				in(ADD, rPSTACK, rTMP, rPSTACK); +			} else +				in(ADDI, rPSTACK, sp->arg.si, rPSTACK); +			in(JMPLI, I7, 8, G0); +			in(RESTOREI, G0, fp->cached_const->arg.si, O0); +			POP_GPR(fp); +		} else { +			MAYBE_EMIT_CONST(fp); +			if (!SIMM13_P(sp->arg.si)) { +				in(SETHI, sp->arg.i >> 10, rTMP); +				in(ORI, rTMP, sp->arg.i & 0x3ff, rTMP); +				in(ADD, rPSTACK, rTMP, rPSTACK); +			} else +				in(ADDI, rPSTACK, sp->arg.si, rPSTACK); +			if (sp->src1_reg_flags & REG_FLAGS_FLOAT) { +				in(STFI, O6, SL(64, 128), fFIRST(fp)); +				in(LDUWI, O6, SL(64, 128), O0); +				in(JMPLI, I7, 8, G0); +				in(RESTORE, O0, G0, O0); +				POP_FPR(fp); +			} else { +				in(JMPLI, I7, 8, G0); +				in(RESTORE, rFIRST(fp), G0, O0); +				POP_GPR(fp); +			} +		} +		assert(fp->gpr_pos == L0); +		assert(fp->fpr_pos == F0); +		break; +	case OP_JUMP: +		if (fp->cached_const) { +			EMIT_FALSE_CONST(fp); +			emit_jump(fp, BA, fp->cached_const->arg.i); +		} else { +			MAYBE_EMIT_CONST(fp); +			in(LDLI, rVMDATA, VM_Data_Offset(iPointers), rTMP); +			in(SLLI, rFIRST(fp), 2, rFIRST(fp)); +			in(LDL, rTMP, rFIRST(fp), rTMP); +			in(JMPL, rTMP, G0, G0); +			in(NOP); +		} +		POP_GPR(fp); +		break; +	case OP_CALL: +		if (fp->cached_const) { +			EMIT_FALSE_CONST(fp); +			if (fp->cached_const->arg.si >= 0) { +				emit_jump(fp, CALL, fp->cached_const->arg.i); +			} else { +				in(LDLI, rVMDATA, VM_Data_Offset(CallThunk), rTMP); +				in(LDLI, rVMDATA, VM_Data_Offset(AsmCall), O3); +				in(ORI, G0, fp->cached_const->arg.si, O0); +				in(JMPL, rTMP, G0, O7); +				in(OR, G0, rPSTACK, O1); +			} +			in(OR, G0, O0, rFIRST(fp)); +		} else { +			MAYBE_EMIT_CONST(fp); +			in(SUBCCI, rFIRST(fp), 0, G0); +			in(BL, +4*7); +			in(NOP); + +			/* normal call */ +			in(LDLI, rVMDATA, VM_Data_Offset(iPointers), O5); +			in(SLLI, rFIRST(fp), 2, rFIRST(fp)); +			in(LDL, O5, rFIRST(fp), rTMP); +			in(BA, +4*4); +			in(NOP); + +			/* syscall */ +			in(LDLI, rVMDATA, VM_Data_Offset(CallThunk), rTMP); +			in(LDLI, rVMDATA, VM_Data_Offset(AsmCall), O3); + +			in(OR, G0, rFIRST(fp), O0); +			in(JMPL, rTMP, G0, O7); +			in(OR, G0, rPSTACK, O1); + +			/* return value */ +			in(OR, G0, O0, rFIRST(fp)); +		} +		break; +	case OP_BLOCK_COPY: +		MAYBE_EMIT_CONST(fp); +		in(LDLI, rVMDATA, VM_Data_Offset(CallThunk), rTMP); +		in(LDLI, rVMDATA, VM_Data_Offset(BlockCopy), O3); +		in(OR, G0, rSECOND(fp), O0); +		in(OR, G0, rFIRST(fp), O1); +		if ((sp->arg.i & ~0x3ff) == 0) { +			in(ORI, G0, sp->arg.i & 0x3ff, O2); +		} else if ((sp->arg.i & 0x3ff) == 0) { +			in(SETHI, sp->arg.i >> 10, O2); +		} else { +			in(SETHI, sp->arg.i >> 10, O2); +			in(ORI, O2, sp->arg.i & 0x3ff, O2); +		} +		in(JMPL, rTMP, G0, O7); +		in(NOP); +		POP_GPR(fp); +		POP_GPR(fp); +		break; + +	case OP_PUSH: +		MAYBE_EMIT_CONST(fp); +		if (sp->dst_reg_flags & REG_FLAGS_FLOAT) +			PUSH_FPR(fp); +		else +			PUSH_GPR(fp); +		fp->force_emit = 1; +		break; +	case OP_POP: +		MAYBE_EMIT_CONST(fp); +		if (sp->src1_reg_flags & REG_FLAGS_FLOAT) +			POP_FPR(fp); +		else +			POP_GPR(fp); +		fp->force_emit = 1; +		break; +	case OP_ARG: +		MAYBE_EMIT_CONST(fp); +		in(ADDI, rPSTACK, sp->arg.b, rTMP); +		if (sp->src1_reg_flags & REG_FLAGS_FLOAT) { +			in(STF, rDATABASE, rTMP, fFIRST(fp)); +			POP_FPR(fp); +		} else { +			in(STW, rDATABASE, rTMP, rFIRST(fp)); +			POP_GPR(fp); +		} +		break; +	case OP_IGNORE: +		MAYBE_EMIT_CONST(fp); +		in(NOP); +		break; +	case OP_BREAK: +		MAYBE_EMIT_CONST(fp); +		in(TA, 0x5); +		break; +	case OP_LOCAL: +		MAYBE_EMIT_CONST(fp); +		PUSH_GPR(fp); +		if (!SIMM13_P(sp->arg.i)) { +			in(SETHI, sp->arg.i >> 10, rTMP); +			in(ORI, rTMP, sp->arg.i & 0x3ff, rTMP); +			in(ADD, rPSTACK, rTMP, rFIRST(fp)); +		} else +			in(ADDI, rPSTACK, sp->arg.i, rFIRST(fp)); +		break; +	case OP_CONST: +		MAYBE_EMIT_CONST(fp); +		break; +	case OP_LOAD4: +		MAYBE_EMIT_CONST(fp); +		in(AND, rFIRST(fp), rDATAMASK, rFIRST(fp)); +		if (sp->dst_reg_flags & REG_FLAGS_FLOAT) { +			PUSH_FPR(fp); +			in(LDF, rFIRST(fp), rDATABASE, fFIRST(fp)); +			POP_GPR(fp); +		} else { +			in(LDUW, rFIRST(fp), rDATABASE, rFIRST(fp)); +		} +		break; +	case OP_LOAD2: +		MAYBE_EMIT_CONST(fp); +		in(AND, rFIRST(fp), rDATAMASK, rFIRST(fp)); +		in(LDUH, rFIRST(fp), rDATABASE, rFIRST(fp)); +		break; +	case OP_LOAD1: +		MAYBE_EMIT_CONST(fp); +		in(AND, rFIRST(fp), rDATAMASK, rFIRST(fp)); +		in(LDUB, rFIRST(fp), rDATABASE, rFIRST(fp)); +		break; +	case OP_STORE4: +		MAYBE_EMIT_CONST(fp); +		if (sp->src1_reg_flags & REG_FLAGS_FLOAT) { +			in(AND, rFIRST(fp), rDATAMASK, rFIRST(fp)); +			in(STF, rFIRST(fp), rDATABASE, fFIRST(fp)); +			POP_FPR(fp); +		} else { +			in(AND, rSECOND(fp), rDATAMASK, rSECOND(fp)); +			in(STW, rSECOND(fp), rDATABASE, rFIRST(fp)); +			POP_GPR(fp); +		} +		POP_GPR(fp); +		break; +	case OP_STORE2: +		MAYBE_EMIT_CONST(fp); +		in(AND, rSECOND(fp), rDATAMASK, rSECOND(fp)); +		in(STH, rSECOND(fp), rDATABASE, rFIRST(fp)); +		POP_GPR(fp); +		POP_GPR(fp); +		break; +	case OP_STORE1: +		MAYBE_EMIT_CONST(fp); +		in(AND, rSECOND(fp), rDATAMASK, rSECOND(fp)); +		in(STB, rSECOND(fp), rDATABASE, rFIRST(fp)); +		POP_GPR(fp); +		POP_GPR(fp); +		break; +	case OP_EQ: +	case OP_NE: +	case OP_LTI: +	case OP_GEI: +	case OP_GTI: +	case OP_LEI: +	case OP_LTU: +	case OP_GEU: +	case OP_GTU: +	case OP_LEU: { +		enum sparc_iname iname = BA; + +		if (fp->cached_const && SIMM13_P(fp->cached_const->arg.si)) { +			EMIT_FALSE_CONST(fp); +			in(SUBCCI, rSECOND(fp), fp->cached_const->arg.si, G0); +		} else { +			MAYBE_EMIT_CONST(fp); +			in(SUBCC, rSECOND(fp), rFIRST(fp), G0); +		} +		switch(sp->op) { +		case OP_EQ: iname = BE; break; +		case OP_NE: iname = BNE; break; +		case OP_LTI: iname = BL; break; +		case OP_GEI: iname = BGE; break; +		case OP_GTI: iname = BG; break; +		case OP_LEI: iname = BLE; break; +		case OP_LTU: iname = BCS; break; +		case OP_GEU: iname = BCC; break; +		case OP_GTU: iname = BGU; break; +		case OP_LEU: iname = BLEU; break; +		} +		emit_jump(fp, iname, sp->arg.i); +		POP_GPR(fp); +		POP_GPR(fp); +		break; +	} + +	case OP_SEX8: +		MAYBE_EMIT_CONST(fp); +		in(SLLI, rFIRST(fp), 24, rFIRST(fp)); +		in(SRAI, rFIRST(fp), 24, rFIRST(fp)); +		break; +	case OP_SEX16: +		MAYBE_EMIT_CONST(fp); +		in(SLLI, rFIRST(fp), 16, rFIRST(fp)); +		in(SRAI, rFIRST(fp), 16, rFIRST(fp)); +		break; +	case OP_NEGI: +		MAYBE_EMIT_CONST(fp); +		in(SUB, G0, rFIRST(fp), rFIRST(fp)); +		break; +	case OP_ADD: +		if (fp->cached_const && SIMM13_P(fp->cached_const->arg.si)) { +			EMIT_FALSE_CONST(fp); +			in(ADDI, rSECOND(fp), fp->cached_const->arg.si, rSECOND(fp)); +		} else { +			MAYBE_EMIT_CONST(fp); +			in(ADD, rSECOND(fp), rFIRST(fp), rSECOND(fp)); +		} +		POP_GPR(fp); +		break; +	case OP_SUB: +		if (fp->cached_const && SIMM13_P(fp->cached_const->arg.si)) { +			EMIT_FALSE_CONST(fp); +			in(SUBI, rSECOND(fp), fp->cached_const->arg.si, rSECOND(fp)); +		} else { +			MAYBE_EMIT_CONST(fp); +			in(SUB, rSECOND(fp), rFIRST(fp), rSECOND(fp)); +		} +		POP_GPR(fp); +		break; +	case OP_DIVI: +		MAYBE_EMIT_CONST(fp); +		in(SRAI, rSECOND(fp), 31, rTMP); +		in(WRI, rTMP, 0, Y_REG); +		in(SDIV, rSECOND(fp), rFIRST(fp), rSECOND(fp)); +		POP_GPR(fp); +		break; +	case OP_DIVU: +		MAYBE_EMIT_CONST(fp); +		in(WRI, G0, 0, Y_REG); +		in(UDIV, rSECOND(fp), rFIRST(fp), rSECOND(fp)); +		POP_GPR(fp); +		break; +	case OP_MODI: +		MAYBE_EMIT_CONST(fp); +		in(SRAI, rSECOND(fp), 31, rTMP); +		in(WRI, rTMP, 0, Y_REG); +		in(SDIV, rSECOND(fp), rFIRST(fp), rTMP); +		in(SMUL, rTMP, rFIRST(fp), rTMP); +		in(SUB, rSECOND(fp), rTMP, rSECOND(fp)); +		POP_GPR(fp); +		break; +	case OP_MODU: +		MAYBE_EMIT_CONST(fp); +		in(WRI, G0, 0, Y_REG); +		in(UDIV, rSECOND(fp), rFIRST(fp), rTMP); +		in(SMUL, rTMP, rFIRST(fp), rTMP); +		in(SUB, rSECOND(fp), rTMP, rSECOND(fp)); +		POP_GPR(fp); +		break; +	case OP_MULI: +		MAYBE_EMIT_CONST(fp); +		in(SMUL, rSECOND(fp), rFIRST(fp), rSECOND(fp)); +		POP_GPR(fp); +		break; +	case OP_MULU: +		MAYBE_EMIT_CONST(fp); +		in(UMUL, rSECOND(fp), rFIRST(fp), rSECOND(fp)); +		POP_GPR(fp); +		break; +	case OP_BAND: +		MAYBE_EMIT_CONST(fp); +		in(AND, rSECOND(fp), rFIRST(fp), rSECOND(fp)); +		POP_GPR(fp); +		break; +	case OP_BOR: +		MAYBE_EMIT_CONST(fp); +		in(OR, rSECOND(fp), rFIRST(fp), rSECOND(fp)); +		POP_GPR(fp); +		break; +	case OP_BXOR: +		MAYBE_EMIT_CONST(fp); +		in(XOR, rSECOND(fp), rFIRST(fp), rSECOND(fp)); +		POP_GPR(fp); +		break; +	case OP_BCOM: +		MAYBE_EMIT_CONST(fp); +		in(XNOR, rFIRST(fp), G0, rFIRST(fp)); +		break; +	case OP_LSH: +		if (fp->cached_const) { +			EMIT_FALSE_CONST(fp); +			in(SLLI, rSECOND(fp), fp->cached_const->arg.si, rSECOND(fp)); +		} else { +			MAYBE_EMIT_CONST(fp); +			in(SLL, rSECOND(fp), rFIRST(fp), rSECOND(fp)); +		} +		POP_GPR(fp); +		break; +	case OP_RSHI: +		if (fp->cached_const) { +			EMIT_FALSE_CONST(fp); +			in(SRAI, rSECOND(fp), fp->cached_const->arg.si, rSECOND(fp)); +		} else { +			MAYBE_EMIT_CONST(fp); +			in(SRA, rSECOND(fp), rFIRST(fp), rSECOND(fp)); +		} +		POP_GPR(fp); +		break; +	case OP_RSHU: +		if (fp->cached_const) { +			EMIT_FALSE_CONST(fp); +			in(SRLI, rSECOND(fp), fp->cached_const->arg.si, rSECOND(fp)); +		} else { +			MAYBE_EMIT_CONST(fp); +			in(SRL, rSECOND(fp), rFIRST(fp), rSECOND(fp)); +		} +		POP_GPR(fp); +		break; + +	case OP_NEGF: +		MAYBE_EMIT_CONST(fp); +		in(FNEG, fFIRST(fp), fFIRST(fp)); +		break; +	case OP_ADDF: +		MAYBE_EMIT_CONST(fp); +		in(FADD, fSECOND(fp), fFIRST(fp), fSECOND(fp)); +		POP_FPR(fp); +		break; +	case OP_SUBF: +		MAYBE_EMIT_CONST(fp); +		in(FSUB, fSECOND(fp), fFIRST(fp), fSECOND(fp)); +		POP_FPR(fp); +		break; +	case OP_DIVF: +		MAYBE_EMIT_CONST(fp); +		in(FDIV, fSECOND(fp), fFIRST(fp), fSECOND(fp)); +		POP_FPR(fp); +		break; +	case OP_MULF: +		MAYBE_EMIT_CONST(fp); +		in(FMUL, fSECOND(fp), fFIRST(fp), fSECOND(fp)); +		POP_FPR(fp); +		break; + +	case OP_EQF: +	case OP_NEF: +	case OP_LTF: +	case OP_GEF: +	case OP_GTF: +	case OP_LEF: { +		enum sparc_iname iname = FBE; + +		MAYBE_EMIT_CONST(fp); +		in(FCMP, fSECOND(fp), fFIRST(fp)); +		switch(sp->op) { +		case OP_EQF: iname = FBE; break; +		case OP_NEF: iname = FBNE; break; +		case OP_LTF: iname = FBL; break; +		case OP_GEF: iname = FBGE; break; +		case OP_GTF: iname = FBG; break; +		case OP_LEF: iname = FBLE; break; +		} +		emit_jump(fp, iname, sp->arg.i); +		POP_FPR(fp); +		POP_FPR(fp); +		break; +	} +	case OP_CVIF: +		MAYBE_EMIT_CONST(fp); +		PUSH_FPR(fp); +		in(STWI, O6, SL(64, 128), rFIRST(fp)); +		in(LDFI, O6, SL(64, 128), fFIRST(fp)); +		in(FITOS, fFIRST(fp), fFIRST(fp)); +		POP_GPR(fp); +		break; +	case OP_CVFI: +		MAYBE_EMIT_CONST(fp); +		PUSH_GPR(fp); +		in(FSTOI, fFIRST(fp), fFIRST(fp)); +		in(STFI, O6, SL(64, 128), fFIRST(fp)); +		in(LDUWI, O6, SL(64, 128), rFIRST(fp)); +		POP_FPR(fp); +		break; +	} +	if (sp->op != OP_CONST) { +		fp->cached_const = NULL; +		end_emit(fp); +	} else { +		fp->cached_const = sp; +		if (sp->dst_reg_flags & REG_FLAGS_FLOAT) { +			PUSH_FPR(fp); +		} else { +			PUSH_GPR(fp); +		} +	} +	end_emit(fp); +} + +static void free_source_insns(struct func_info * const fp) +{ +	struct src_insn *sp = fp->first->next; + +	while (sp) { +		struct src_insn *next = sp->next; +		Z_Free(sp); +		sp = next; +	} +} + +static void compile_function(struct func_info * const fp) +{ +	struct src_insn *sp; + +	analyze_function(fp); + +	fp->gpr_pos = L0; +	fp->fpr_pos = F0; +	fp->insn_index = 0; + +	fp->stack_space = SL(64, 128); +	fp->cached_const = NULL; + +	sp = fp->first; +	while ((sp = sp->next) != NULL) +		compile_one_insn(fp, sp); + +	free_source_insns(fp); +} + +/* We have two thunks for sparc.  The first is for the entry into + * the VM, where setup the fixed global registers.  The second is + * for calling out to C code from the VM, where we need to preserve + * those fixed globals across the call. + */ +static void emit_vm_thunk(struct func_info * const fp) +{ +	/* int vm_thunk(void *vmdata, int programstack, void *database, int datamask) */ +	start_emit(fp, THUNK_ICOUNT); + +	in(OR, G0, O0, rVMDATA); +	in(OR, G0, O1, rPSTACK); +	in(OR, G0, O2, rDATABASE); +	in(BA, +4*17); +	in(OR, G0, O3, rDATAMASK); + +	/* int call_thunk(int arg0, int arg1, int arg2, int (*func)(int int int)) */ +#define CALL_THUNK_INSN_OFFSET		5 +	in(SAVEI, O6, -SL(64, 128), O6); + +	in(OR, G0, rVMDATA, L0); +	in(OR, G0, rPSTACK, L1); +	in(OR, G0, rDATABASE, L2); +	in(OR, G0, rDATAMASK, L3); + +	in(OR, G0, I0, O0); +	in(OR, G0, I1, O1); +	in(JMPL, I3, G0, O7); +	in(OR, G0, I2, O2); + +	in(OR, G0, L0, rVMDATA); +	in(OR, G0, L1, rPSTACK); +	in(OR, G0, L2, rDATABASE); +	in(OR, G0, L3, rDATAMASK); + +	in(JMPLI, I7, 8, G0); +	in(RESTORE, O0, G0, O0); + +	end_emit(fp); +} + +static void sparc_compute_code(vm_t *vm, struct func_info * const fp) +{ +	struct dst_insn *dp = fp->dst_first; +	unsigned int *code_now, *code_begin; +	unsigned char *data_and_code; +	unsigned int code_length; +	int code_insns = 0, off; +	struct data_hunk *dhp; +	struct jump_insn *jp; +	vm_data_t *data; + +	while (dp) { +		code_insns += dp->length; +		dp = dp->next; +	} + +	code_length = (sizeof(vm_data_t) + +		       (fp->data_num * sizeof(unsigned int)) + +		       (code_insns * sizeof(unsigned int))); + +	data_and_code = mmap(NULL, code_length, PROT_READ | PROT_WRITE, +			     MAP_SHARED | MAP_ANONYMOUS, -1, 0); +	if (data_and_code == MAP_FAILED) +		DIE("Not enough memory"); + +	code_now = code_begin = (unsigned int *) +		(data_and_code + VM_Data_Offset(data[fp->data_num])); + +	dp = fp->dst_first; +	while (dp) { +		int i_count = dp->i_count; + +		if (i_count != THUNK_ICOUNT) { +			if (!fp->dst_by_i_count[i_count]) +				fp->dst_by_i_count[i_count] = (void *) code_now; +		} +		if (!dp->jump) { +			memcpy(code_now, &dp->code[0], dp->length * sizeof(unsigned int)); +			code_now += dp->length; +		} else { +			int i; + +			dp->jump->parent = (void *) code_now; + +			for (i = 0; i < dp->length; i++) +				code_now[i] = SPARC_NOP; +			code_now += dp->length; +		} + +		dp = dp->next; +	} + +	jp = fp->jump_first; +	while (jp) { +		unsigned int *from = (void *) jp->parent; +		unsigned int *to = (void *) fp->dst_by_i_count[jp->jump_dest_insn]; +		signed int disp = (to - from); + +		*from = IN(jp->jump_iname, disp << 2); + +		jp = jp->next; +	} + +	vm->codeBase = data_and_code; +	vm->codeLength = code_length; + +	data = (vm_data_t *) data_and_code; +	data->CallThunk = code_begin + CALL_THUNK_INSN_OFFSET; +	data->AsmCall = asmcall; +	data->BlockCopy = blockcopy; +	data->iPointers = (unsigned int *) vm->instructionPointers; +	data->dataLength = VM_Data_Offset(data[fp->data_num]); +	data->codeLength = (code_now - code_begin) * sizeof(unsigned int); + +#if 0 +	{ +		unsigned int *insn = code_begin; +		int i; + +		Com_Printf("INSN DUMP\n"); +		for (i = 0; i < data->codeLength / 4; i+= 8) { +			Com_Printf("\t.word\t0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x\n", +				   insn[i + 0], insn[i + 1], +				   insn[i + 2], insn[i + 3], +				   insn[i + 4], insn[i + 5], +				   insn[i + 6], insn[i + 7]); +		} +	} +#endif + +	dhp = fp->data_first; +	off = 0; +	while (dhp) { +		struct data_hunk *next = dhp->next; +		int i; + +		for (i = 0; i < dhp->count; i++) +			data->data[off + i] = dhp->data[i]; + +		off += dhp->count; + +		Z_Free(dhp); + +		dhp = next; +	} +	fp->data_first = NULL; +	fp->data_num = 0; + +	dp = fp->dst_first; +	while (dp) { +		struct dst_insn *next = dp->next; +		if (dp->jump) +			Z_Free(dp->jump); +		Z_Free(dp); +		dp = next; +	} +	fp->dst_first = fp->dst_last = NULL; +} + +void VM_Compile(vm_t *vm, vmHeader_t *header) +{ +	struct func_info fi; +	unsigned char *code; +	int i_count, pc, i; + +	memset(&fi, 0, sizeof(fi)); + +	fi.first = Z_Malloc(sizeof(struct src_insn)); +	fi.first->next = NULL; + +#ifdef __arch64__ +	Z_Free(vm->instructionPointers); +	vm->instructionPointers = Z_Malloc(header->instructionCount * +					   sizeof(void *)); +#endif + +	fi.dst_by_i_count = (struct dst_insn **) vm->instructionPointers; +	memset(fi.dst_by_i_count, 0, header->instructionCount * sizeof(void *)); + +	vm->compiled = qfalse; + +	emit_vm_thunk(&fi); + +	code = (unsigned char *) header + header->codeOffset; +	pc = 0; + +	for (i_count = 0; i_count < header->instructionCount; i_count++) { +		unsigned char opi, op = code[pc++]; +		struct src_insn *sp; + +		if (op == OP_CALL || op == OP_BLOCK_COPY) +			fi.has_call = 1; +		opi = vm_opInfo[op]; +		if (op == OP_CVIF || op == OP_CVFI || +		    (op == OP_LEAVE && (opi & opArgF))) +			fi.need_float_tmp = 1; + +		if (op == OP_ENTER) { +			if (fi.first->next) +				compile_function(&fi); +			fi.first->next = NULL; +			fi.last = fi.first; +			fi.has_call = fi.need_float_tmp = 0; +		} + +		sp = Z_Malloc(sizeof(*sp)); +		sp->op = op; +		sp->i_count = i_count; +		sp->arg.i = 0; +		sp->next = NULL; + +		if (vm_opInfo[op] & opImm4) { +			union { +				unsigned char b[4]; +				unsigned int i; +			} c = { { code[ pc + 3 ], code[ pc + 2 ], +				  code[ pc + 1 ], code[ pc + 0 ] }, }; + +			sp->arg.i = c.i; +			pc += 4; +		} else if (vm_opInfo[op] & opImm1) { +			sp->arg.b = code[pc++]; +		} + +		fi.last->next = sp; +		fi.last = sp; +	} +	compile_function(&fi); + +	Z_Free(fi.first); + +	memset(fi.dst_by_i_count, 0, header->instructionCount * sizeof(void *)); +	sparc_compute_code(vm, &fi); + +	for (i = 0; i < header->instructionCount; i++) { +		if (!fi.dst_by_i_count[i]) { +			Com_Printf(S_COLOR_RED "Pointer %d not initialized !\n", i); +			DIE("sparc JIT bug"); +		} +	} + +	if (mprotect(vm->codeBase, vm->codeLength, PROT_READ|PROT_EXEC)) { +		VM_Destroy_Compiled(vm); +		DIE("mprotect failed"); +	} + +	vm->destroy = VM_Destroy_Compiled; +	vm->compiled = qtrue; +} + +int VM_CallCompiled(vm_t *vm, int *args) +{ +	vm_data_t *vm_dataAndCode = (void *) vm->codeBase; +	int programStack = vm->programStack; +	int stackOnEntry = programStack; +	byte *image = vm->dataBase; +	int *argPointer; +	int retVal; + +	currentVM = vm; + +	vm->currentlyInterpreting = qtrue; + +	programStack -= 48; +	argPointer = (int *)&image[ programStack + 8 ]; +	memcpy( argPointer, args, 4 * 9 ); +	argPointer[-1] = 0; +	argPointer[-2] = -1; + +	/* call generated code */ +	{ +		int (*entry)(void *, int, void *, int); +		entry = (void *)(vm->codeBase + vm_dataAndCode->dataLength); +		retVal = entry(vm->codeBase, programStack, vm->dataBase, vm->dataMask); +	} + +	vm->programStack = stackOnEntry; +	vm->currentlyInterpreting = qfalse; + +	return retVal; +} diff --git a/src/qcommon/vm_sparc.h b/src/qcommon/vm_sparc.h new file mode 100644 index 0000000..dbed627 --- /dev/null +++ b/src/qcommon/vm_sparc.h @@ -0,0 +1,78 @@ +#ifndef VM_SPARC_H +#define VM_SPARC_H + +/* integer regs */ +#define G0	0 +#define G1	1 +#define G2	2 +#define G3	3 +#define G4	4 +#define G5	5 +#define G6	6 +#define G7	7 +#define O0	8 +#define O1	9 +#define O2	10 +#define O3	11 +#define O4	12 +#define O5	13 +#define O6	14 +#define O7	15 +#define L0	16 +#define L1	17 +#define L2	18 +#define L3	19 +#define L4	20 +#define L5	21 +#define L6	22 +#define L7	23 +#define I0	24 +#define I1	25 +#define I2	26 +#define I3	27 +#define I4	28 +#define I5	29 +#define I6	30 +#define I7	31 + +/* float regs */ +#define F0	0 +#define F1	1 +#define F2	2 +#define F3	3 +#define F4	4 +#define F5	5 +#define F6	6 +#define F7	7 +#define F8	8 +#define F9	9 +#define F10	10 +#define F11	11 +#define F12	12 +#define F13	13 +#define F14	14 +#define F15	15 +#define F16	16 +#define F17	17 +#define F18	18 +#define F19	19 +#define F20	20 +#define F21	21 +#define F22	22 +#define F23	23 +#define F24	24 +#define F25	25 +#define F26	26 +#define F27	27 +#define F28	28 +#define F29	29 +#define F30	30 +#define F31	31 + +/* state registers */ +#define Y_REG		0 +#define CCR_REG		2 +#define ASI_REG		3 +#define FPRS_REG	6 + +#endif diff --git a/src/qcommon/vm_x86.c b/src/qcommon/vm_x86.c new file mode 100644 index 0000000..f25be99 --- /dev/null +++ b/src/qcommon/vm_x86.c @@ -0,0 +1,1245 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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__ +#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) */ +#if defined(__linux__) || defined(__FreeBSD__) +#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 + +*/ + +#define VMFREE_BUFFERS() do {Z_Free(buf); Z_Free(jused);} while(0) +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 + +#else // _MSC_VER + +#if defined( FTOL_PTR ) + +int qftol( void ); +int qftol027F( void ); +int qftol037F( void ); +int qftol0E7F( void ); +int qftol0F7F( void ); + + +static	int		ftolPtr = (int)qftol0F7F; +#endif // FTOL_PTR + +#endif + +void AsmCall(void); +static void (*const asmCallPtr)(void) = AsmCall; + + +static	int		callMask = 0; + +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 +	test	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 +	not		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 CMANGVAR(sym) "_"#sym +#define CMANGFUNC(sym) "_"#sym +#elif defined(__ICC) && (__ICC >= 1000) +#define CMANGVAR(sym) #sym".0" +#define CMANGFUNC(sym) #sym +#else +#define CMANGVAR(sym) #sym +#define CMANGFUNC(sym) #sym +#endif + +static void __attribute__((cdecl, used)) CallAsmCall(int const syscallNum, +		int const programStack, int* const opStack) +{ +	vm_t     *const vm   = currentVM; +	intptr_t *const data = (intptr_t*)(vm->dataBase + programStack + 4); + +	// save the stack to allow recursive VM entry +	vm->programStack = programStack - 4; +	*data = syscallNum; +	opStack[1] = vm->systemCall(data); + +	currentVM = vm; +} + +__asm__( +	".text\n\t" +	".p2align 4,,15\n\t" +#if defined __ELF__ +	".type " CMANGFUNC(AsmCall) ", @function\n" +#endif +	CMANGFUNC(AsmCall) ":\n\t" +	"movl  (%edi), %eax\n\t" +	"subl  $4, %edi\n\t" +	"testl %eax, %eax\n\t" +	"jl    0f\n\t" +	"shll  $2, %eax\n\t" +	"addl  " CMANGVAR(instructionPointers) ", %eax\n\t" +	"call  *(%eax)\n\t" +	"movl  (%edi), %eax\n\t" +	"andl  " CMANGVAR(callMask) ", %eax\n\t" +	"ret\n" +	"0:\n\t" // system call +	"notl  %eax\n\t" +	"pushl %ebp\n\t" +	"movl  %esp, %ebp\n\t" +	"andl $-16, %esp\n\t" // align the stack so engine can use sse +	"pushl %ecx\n\t" +	"pushl %edi\n\t" // opStack +	"pushl %esi\n\t" // programStack +	"pushl %eax\n\t" // syscallNum +	"call  " CMANGFUNC(CallAsmCall) "\n\t" +	"addl  $12, %esp\n\t" +	"popl  %ecx\n\t" +	"movl  %ebp, %esp\n\t" +	"popl  %ebp\n\t" +	"addl  $4, %edi\n\t" +	"ret\n\t" +#if defined __ELF__ +	".size " CMANGFUNC(AsmCall)", .-" CMANGFUNC(AsmCall) +#endif +); + +#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'; +	} + +	VMFREE_BUFFERS(); +	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; +} + +#define JUSED(x) \ +	do { \ +		if (x < 0 || x >= jusedSize) { \ +		        VMFREE_BUFFERS(); \ +			Com_Error( ERR_DROP, \ +					"VM_CompileX86: jump target out of range at offset %d", pc ); \ +		} \ +		jused[x] = 1; \ +	} while(0) + +/* +================= +VM_Compile +================= +*/ +void VM_Compile( vm_t *vm, vmHeader_t *header ) { +	int		op; +	int		maxLength; +	int		v; +	int		i; +	qboolean opt; +	int jusedSize = header->instructionCount + 2; + +	// allocate a very large temp buffer, we will shrink it later +	maxLength = header->codeLength * 8; +	buf = Z_Malloc(maxLength); +	jused = Z_Malloc(jusedSize); +	 +	Com_Memset(jused, 0, jusedSize); + +	// 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) +		{ +	        	VMFREE_BUFFERS(); +			Com_Error(ERR_DROP, "VM_CompileX86: maxLength exceeded"); +		} + +		vm->instructionPointers[ instruction ] = compiledOfs; +		instruction++; + +		if(pc > header->codeLength) +		{ +		        VMFREE_BUFFERS(); +			Com_Error(ERR_DROP, "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); +			} +			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); +			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); +			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); +			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); +			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); +			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); +			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); +			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); +			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); +			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); +			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); +			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); +			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); +			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); +			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); +			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); +			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 +			// 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: +		        VMFREE_BUFFERS(); +			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 == MAP_FAILED) +		Com_Error(ERR_FATAL, "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_FATAL, "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_FATAL, "VM_CompileX86: mprotect failed"); +#elif _WIN32 +	{ +		DWORD oldProtect = 0; +		 +		// remove write permissions. +		if(!VirtualProtect(vm->codeBase, compiledOfs, PAGE_EXECUTE_READ, &oldProtect)) +			Com_Error(ERR_FATAL, "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, 0, 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	*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... +	opStack = &stack; + +	{ +#ifdef _MSC_VER +		void *entryPoint = vm->codeBase; + +		__asm { +			pushad +			mov    esi, programStack +			mov    edi, opStack +			call   entryPoint +			mov    programStack, esi +			mov    opStack, edi +			popad +		} +#else +		/* These registers are used as scratch registers and are destroyed after the +		 * call.  Do not use clobber, so they can be used as input for the asm. */ +		unsigned eax; +		unsigned ebx; +		unsigned ecx; +		unsigned edx; + +		__asm__ volatile( +			"call *%6" +			: "+S" (programStack), "+D" (opStack), +			  "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx) +			: "mr" (vm->codeBase) +			: "cc", "memory" +		); +#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..6b33544 --- /dev/null +++ b/src/qcommon/vm_x86_64.c @@ -0,0 +1,1099 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development +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/stat.h> +#include <sys/types.h> +#include <sys/time.h> +#include <time.h> +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <stdarg.h> + +#include <inttypes.h> + +#ifdef __WIN64__ +	#include <windows.h> +	#define CROSSCALL __attribute__ ((sysv_abi))//fool the vm we're SYSV ABI +	//#define __USE_MINGW_ANSI_STDIO 1 //very slow - avoid if possible +#else +	#include <sys/mman.h> +	#include <sys/wait.h> +	#define VM_X86_64_MMAP +	#define CROSSCALL +#endif + +//#define DEBUG_VM + +#ifdef DEBUG_VM +#define Dfprintf(fd, args...) fprintf(fd, ##args) +static FILE* qdasmout; +#else +#define Dfprintf(args...) +#endif + +#define VM_FREEBUFFERS(vm) do {assembler_init(0); VM_Destroy_Compiled(vm);} while(0) + +void assembler_set_output(char* buf); +size_t assembler_get_code_size(void); +void assembler_init(int pass); +void assemble_line(const char* input, size_t len); + +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 int64_t CROSSCALL callAsmCall(int64_t callProgramStack, int64_t callSyscallNum) +{ +	vm_t *savedVM; +	int64_t ret = 0x77; +	int64_t 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 +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, +}; + +void emit(const char* fmt, ...) +{ +	va_list ap; +	char line[4096]; +	va_start(ap, fmt); +	Q_vsnprintf(line, sizeof(line), fmt, ap); +	va_end(ap); +	assemble_line(line, strlen(line)); +} + +#define CHECK_INSTR_REG(reg) \ +	emit("cmpl $%u, %%"#reg, header->instructionCount); \ +	emit("jb jmp_ok_i_%08x", instruction); \ +	emit("movq $%"PRIu64", %%rax", (uint64_t)jmpviolation); \ +	emit("callq *%%rax"); \ +	emit("jmp_ok_i_%08x:", instruction); + +#define PREPARE_JMP(reg) \ +	CHECK_INSTR_REG(reg) \ +	emit("movq $%"PRIu64", %%rbx", (uint64_t)vm->instructionPointers); \ +	emit("movl (%%rbx, %%rax, 4), %%eax"); \ +	emit("addq %%r10, %%rax"); + +#define CHECK_INSTR(nr) \ +	do { if(nr < 0 || nr >= header->instructionCount) { \ +		VM_FREEBUFFERS(vm); \ +		Com_Error( ERR_DROP, \ +			"%s: jump target 0x%x out of range at offset %d", __func__, nr, pc ); \ +	} } while(0) + +#define JMPIARG \ +	CHECK_INSTR(iarg); \ +	emit("movq $%"PRIu64", %%rax", vm->codeBase+vm->instructionPointers[iarg]); \ +	emit("jmpq *%%rax"); +  +#define CONST_OPTIMIZE +#ifdef CONST_OPTIMIZE +#define MAYBE_EMIT_CONST() \ +	if (got_const) \ +	{ \ +		got_const = 0; \ +		vm->instructionPointers[instruction-1] = assembler_get_code_size(); \ +		emit("addq $4, %%rsi"); \ +		emit("movl $%d, 0(%%rsi)", const_value); \ +	} +#else +#define MAYBE_EMIT_CONST() +#endif + +// integer compare and jump +#define IJ(op) \ +	MAYBE_EMIT_CONST(); \ +	emit("subq $8, %%rsi"); \ +	emit("movl 4(%%rsi), %%eax"); \ +	emit("cmpl 8(%%rsi), %%eax"); \ +	emit(op " i_%08x", instruction+1); \ +	JMPIARG \ +	neednilabel = 1; + +#ifdef USE_X87 +#define FJ(bits, op) \ +	MAYBE_EMIT_CONST(); \ +	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);\ +	JMPIARG \ +	neednilabel = 1; +#define XJ(x) +#else +#define FJ(x, y) +#define XJ(op) \ +	MAYBE_EMIT_CONST(); \ +	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);\ +	JMPIARG \ +	neednilabel = 1; +#endif + +#define SIMPLE(op) \ +	MAYBE_EMIT_CONST(); \ +	emit("subq $4, %%rsi"); \ +	emit("movl 4(%%rsi), %%eax"); \ +	emit(op " %%eax, 0(%%rsi)"); + +#ifdef USE_X87 +#define FSIMPLE(op) \ +	MAYBE_EMIT_CONST(); \ +	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) \ +	MAYBE_EMIT_CONST(); \ +	emit("subq $4, %%rsi"); \ +	emit("movss 0(%%rsi), %%xmm0"); \ +	emit(op " 4(%%rsi), %%xmm0"); \ +	emit("movss %%xmm0, 0(%%rsi)"); +#endif + +#define SHIFT(op) \ +	MAYBE_EMIT_CONST(); \ +	emit("subq $4, %%rsi"); \ +	emit("movl 4(%%rsi), %%ecx"); \ +	emit("movl 0(%%rsi), %%eax"); \ +	emit(op " %%cl, %%eax"); \ +	emit("movl %%eax, 0(%%rsi)"); + +#ifdef DEBUG_VM +#define RANGECHECK(reg, bytes) \ +	emit("movl %%" #reg ", %%ecx"); \ +	emit("andl $0x%x, %%ecx", vm->dataMask &~(bytes-1)); \ +	emit("cmpl %%" #reg ", %%ecx"); \ +	emit("jz rc_ok_i_%08x", instruction); \ +	emit("movq $%"PRIu64", %%rax", (uint64_t)memviolation); \ +	emit("callq *%%rax"); \ +	emit("rc_ok_i_%08x:", instruction); +#elif 1 +// check is too expensive, so just confine memory access +#define RANGECHECK(reg, bytes) \ +	emit("andl $0x%x, %%" #reg, vm->dataMask &~(bytes-1)); +#else +#define RANGECHECK(reg, bytes) +#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; +} + +static void CROSSCALL 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); +} + +static void CROSSCALL eop(void) +{ +	Com_Error(ERR_DROP, "end of program reached without return!\n"); +	exit(1); +} + +static void CROSSCALL jmpviolation(void) +{ +	Com_Error(ERR_DROP, "program tried to execute code outside VM\n"); +	exit(1); +} + +#ifdef DEBUG_VM +static void CROSSCALL memviolation(void) +{ +	Com_Error(ERR_DROP, "program tried to access memory outside VM\n"); +	exit(1); +} +#endif + +/* +================= +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; +	int neednilabel = 0; +	struct timeval tvstart =  {0, 0}; +#ifdef DEBUG_VM +	char fn_d[MAX_QPATH]; // disassembled +#endif + +	int pass; +	size_t compiledOfs = 0; + +	// const optimization +	unsigned got_const = 0, const_value = 0; +	 +	vm->codeBase = NULL; + +	gettimeofday(&tvstart, NULL); + +	for (pass = 0; pass < 2; ++pass) { + +	if(pass) +	{ +		compiledOfs = assembler_get_code_size(); +		vm->codeLength = compiledOfs; + +		#ifdef VM_X86_64_MMAP +			vm->codeBase = mmap(NULL, compiledOfs, PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); +			if(vm->codeBase == MAP_FAILED) +				Com_Error(ERR_FATAL, "VM_CompileX86_64: can't mmap memory"); +		#elif __WIN64__ +			// allocate memory with write permissions under windows. +			vm->codeBase = VirtualAlloc(NULL, compiledOfs, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); +			if(!vm->codeBase) +				Com_Error(ERR_FATAL, "VM_CompileX86_64: VirtualAlloc failed"); +		#else +			vm->codeBase = malloc(compiledOfs); +			if(!vm_codeBase) +				Com_Error(ERR_FATAL, "VM_CompileX86_64: Failed to allocate memory"); +		#endif + +		assembler_set_output((char*)vm->codeBase); +	} + +	assembler_init(pass); + +#ifdef DEBUG_VM +	strcpy(fn_d,vm->name); +	strcat(fn_d, ".qdasm"); + +	qdasmout = fopen(fn_d, "w"); +#endif + +	// translate all instructions +	pc = 0; +	code = (char *)header + header->codeOffset; + +	for ( instruction = 0; instruction < header->instructionCount; ++instruction ) +	{ +		op = code[ pc ]; +		++pc; + +		vm->instructionPointers[instruction] = assembler_get_code_size(); + +		/* store current instruction number in r15 for debugging */ +#if DEBUG_VM0 +		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 %8hu\n", opnames[op], barg); +		} +		else +		{ +			Dfprintf(qdasmout, "%s\n", opnames[op]); +		} + +		if(neednilabel) +		{ +			emit("i_%08x:", instruction); +			neednilabel = 0; +		} + +		switch ( op ) +		{ +			case OP_UNDEF: +				NOTIMPL(op); +				break; +			case OP_IGNORE: +				MAYBE_EMIT_CONST(); +				emit("nop"); +				break; +			case OP_BREAK: +				MAYBE_EMIT_CONST(); +				emit("int3"); +				break; +			case OP_ENTER: +				MAYBE_EMIT_CONST(); +				emit("subl $%d, %%edi", iarg); +				break; +			case OP_LEAVE: +				MAYBE_EMIT_CONST(); +				emit("addl $%d, %%edi", iarg);          // get rid of stack frame +				emit("ret"); +				break; +			case OP_CALL: +				RANGECHECK(edi, 4); +				emit("movl $%d, 0(%%r8, %%rdi, 1)", instruction+1);  // save next instruction +				if(got_const) +				{ +					if ((int)const_value < 0) +						goto emit_do_syscall; + +					CHECK_INSTR(const_value); +					emit("movq $%"PRIu64", %%rax", vm->codeBase+vm->instructionPointers[const_value]); +					emit("callq *%%rax"); +					got_const = 0; +					break; +				} +				else +				{ +					MAYBE_EMIT_CONST(); +					emit("movl 0(%%rsi), %%eax");  // get instr from stack +					emit("subq $4, %%rsi"); + +					emit("orl %%eax, %%eax"); +					emit("jl callSyscall%d", instruction); + +					PREPARE_JMP(eax); +					emit("callq *%%rax"); + +					emit("jmp i_%08x", instruction+1); +					emit("callSyscall%d:", instruction); +				} +emit_do_syscall: +//				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"); +				if(got_const) { +					got_const = 0; +					emit("movq $%u, %%rsi", -1-const_value); // second argument in rsi +				} else { +					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 $%"PRIu64", %%rax", (uint64_t)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 +				neednilabel = 1; +				break; +			case OP_PUSH: +				MAYBE_EMIT_CONST(); +				emit("addq $4, %%rsi"); +				break; +			case OP_POP: +				MAYBE_EMIT_CONST(); +				emit("subq $4, %%rsi"); +				break; +			case OP_CONST: +				MAYBE_EMIT_CONST(); +#ifdef CONST_OPTIMIZE +				got_const = 1; +				const_value = iarg; +#else +				emit("addq $4, %%rsi"); +				emit("movl $%d, 0(%%rsi)", iarg); +#endif +				break; +			case OP_LOCAL: +				MAYBE_EMIT_CONST(); +				emit("movl %%edi, %%ebx"); +				emit("addl $%d,%%ebx", iarg); +				emit("addq $4, %%rsi"); +				emit("movl %%ebx, 0(%%rsi)"); +				break; +			case OP_JUMP: +				if(got_const) { +					iarg = const_value; +					got_const = 0; +					JMPIARG; +				} else { +					emit("movl 0(%%rsi), %%eax"); // get instr from stack +					emit("subq $4, %%rsi"); + +					PREPARE_JMP(eax); +					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 +				MAYBE_EMIT_CONST(); +				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); +				JMPIARG +				neednilabel = 1; +#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: +				MAYBE_EMIT_CONST(); +				emit("movl 0(%%rsi), %%eax"); // get value from stack +				RANGECHECK(eax, 1); +				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: +				MAYBE_EMIT_CONST(); +				emit("movl 0(%%rsi), %%eax"); // get value from stack +				RANGECHECK(eax, 2); +				emit("movw 0(%%r8, %%rax, 1), %%ax"); // deref into eax +				emit("movl %%eax, 0(%%rsi)"); // store on stack +				break; +			case OP_LOAD4: +				MAYBE_EMIT_CONST(); +				emit("movl 0(%%rsi), %%eax"); // get value from stack +				RANGECHECK(eax, 4); // 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: +				MAYBE_EMIT_CONST(); +				emit("movl 0(%%rsi), %%eax"); // get value from stack +				emit("andq $255, %%rax"); +				emit("movl -4(%%rsi), %%ebx"); // get pointer from stack +				RANGECHECK(ebx, 1); +				emit("movb %%al, 0(%%r8, %%rbx, 1)"); // store in memory +				emit("subq $8, %%rsi"); +				break; +			case OP_STORE2: +				MAYBE_EMIT_CONST(); +				emit("movl 0(%%rsi), %%eax"); // get value from stack +				emit("movl -4(%%rsi), %%ebx"); // get pointer from stack +				RANGECHECK(ebx, 2); +				emit("movw %%ax, 0(%%r8, %%rbx, 1)"); // store in memory +				emit("subq $8, %%rsi"); +				break; +			case OP_STORE4: +				MAYBE_EMIT_CONST(); +				emit("movl -4(%%rsi), %%ebx"); // get pointer from stack +				RANGECHECK(ebx, 4); +				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: +				MAYBE_EMIT_CONST(); +				emit("subq $4, %%rsi"); +				emit("movl 4(%%rsi), %%eax"); // get value from stack +				emit("movl $0x%hx, %%ebx", barg); +				emit("addl %%edi, %%ebx"); +				RANGECHECK(ebx, 4); +				emit("movl %%eax, 0(%%r8,%%rbx, 1)"); // store in args space +				break; +			case OP_BLOCK_COPY: + +				MAYBE_EMIT_CONST(); +				emit("subq $8, %%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("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 $%"PRIu64", %%rax", (uint64_t)block_copy_vm); +				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"); + +				break; +			case OP_SEX8: +				MAYBE_EMIT_CONST(); +				emit("movw 0(%%rsi), %%ax"); +				emit("andq $255, %%rax"); +				emit("cbw"); +				emit("cwde"); +				emit("movl %%eax, 0(%%rsi)"); +				break; +			case OP_SEX16: +				MAYBE_EMIT_CONST(); +				emit("movw 0(%%rsi), %%ax"); +				emit("cwde"); +				emit("movl %%eax, 0(%%rsi)"); +				break; +			case OP_NEGI: +				MAYBE_EMIT_CONST(); +				emit("negl 0(%%rsi)"); +				break; +			case OP_ADD: +				SIMPLE("addl"); +				break; +			case OP_SUB: +				SIMPLE("subl"); +				break; +			case OP_DIVI: +				MAYBE_EMIT_CONST(); +				emit("subq $4, %%rsi"); +				emit("movl 0(%%rsi), %%eax"); +				emit("cdq"); +				emit("idivl 4(%%rsi)"); +				emit("movl %%eax, 0(%%rsi)"); +				break; +			case OP_DIVU: +				MAYBE_EMIT_CONST(); +				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: +				MAYBE_EMIT_CONST(); +				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: +				MAYBE_EMIT_CONST(); +				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: +				MAYBE_EMIT_CONST(); +				emit("subq $4, %%rsi"); +				emit("movl 0(%%rsi), %%eax"); +				emit("imull 4(%%rsi)"); +				emit("movl %%eax, 0(%%rsi)"); +				break; +			case OP_MULU: +				MAYBE_EMIT_CONST(); +				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: +				MAYBE_EMIT_CONST(); +				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: +				MAYBE_EMIT_CONST(); +#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: +				MAYBE_EMIT_CONST(); +#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: +				MAYBE_EMIT_CONST(); +#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; +		} + + +	} + +	if(got_const) +	{ +		VM_FREEBUFFERS(vm); +		Com_Error(ERR_DROP, "leftover const\n"); +	} + +	emit("movq $%"PRIu64", %%rax", (uint64_t)eop); +	emit("callq *%%rax"); + +	} // pass loop + +	assembler_init(0); + +	#ifdef VM_X86_64_MMAP +		if(mprotect(vm->codeBase, compiledOfs, PROT_READ|PROT_EXEC)) +			Com_Error(ERR_FATAL, "VM_CompileX86_64: mprotect failed"); +	#elif __WIN64__ +		{ +			DWORD oldProtect = 0; +			 +			// remove write permissions; give exec permision +			if(!VirtualProtect(vm->codeBase, compiledOfs, PAGE_EXECUTE_READ, &oldProtect)) +				Com_Error(ERR_FATAL, "VM_CompileX86_64: VirtualProtect failed"); +		} +	#endif + +	vm->destroy = VM_Destroy_Compiled; + +#ifdef DEBUG_VM +	fflush(qdasmout); +	fclose(qdasmout); + +#if 0 +	strcpy(fn_d,vm->name); +	strcat(fn_d, ".bin"); +	qdasmout = fopen(fn_d, "w"); +	fwrite(vm->codeBase, compiledOfs, 1, qdasmout); +	fflush(qdasmout); +	fclose(qdasmout); +#endif +#endif + +	#ifndef __WIN64__ //timersub and gettimeofday +		if(vm->compiled) +		{ +			struct timeval tvdone =  {0, 0}; +			struct timeval dur =  {0, 0}; +			Com_Printf( "VM file %s compiled to %i bytes of code (%p - %p)\n", vm->name, vm->codeLength, vm->codeBase, vm->codeBase+vm->codeLength ); + +			gettimeofday(&tvdone, NULL); +			timersub(&tvdone, &tvstart, &dur); +			Com_Printf( "compilation took %"PRIu64".%06"PRIu64" seconds\n", dur.tv_sec, dur.tv_usec ); +		} +	#endif +} + + +void VM_Destroy_Compiled(vm_t* self) +{ +	if(self && self->codeBase) +	{ +#ifdef VM_X86_64_MMAP +		munmap(self->codeBase, self->codeLength); +#elif __WIN64__ +		VirtualFree(self->codeBase, 0, MEM_RELEASE); +#else +		free(self->codeBase); +#endif +	} +} + +/* +============== +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; + +//	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 %"PRId64")\n", (int64_t) ((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->programStack = stackOnEntry; + +	return *(int *)opStack; +} diff --git a/src/qcommon/vm_x86_64_assembler.c b/src/qcommon/vm_x86_64_assembler.c new file mode 100644 index 0000000..06695d8 --- /dev/null +++ b/src/qcommon/vm_x86_64_assembler.c @@ -0,0 +1,1447 @@ +/* +=========================================================================== +vm_x86_64_assembler.c -- assembler for x86-64 + +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development +Copyright (C) 2007 Ludwig Nussel <ludwig.nussel@suse.de>, Novell inc. + +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 _ISOC99_SOURCE + +#include "vm_local.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> + +#include <inttypes.h> + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +static char* out; +static unsigned compiledOfs; +static unsigned assembler_pass; + +static const char* cur_line; + +static FILE* fout; + +#define MIN(a,b)  ((a) < (b) ? (a) : (b)) +#define MAX(a,b)  ((a) > (b) ? (a) : (b)) + +#define crap(fmt, args...) do { \ +	_crap(__FUNCTION__, fmt, ##args); \ +} while(0) + +#define CRAP_INVALID_ARGS crap("invalid arguments %s, %s", argtype2str(arg1.type),argtype2str(arg2.type)); + +#ifdef DEBUG +#define debug(fmt, args...) printf(fmt, ##args) +#else +#define debug(fmt, args...) +#endif + +static void _crap(const char* func, const char* fmt, ...) +{ +	va_list ap; +	fprintf(stderr, "%s() - ", func); +	va_start(ap, fmt); +	vfprintf(stderr, fmt, ap); +	va_end(ap); +	fputc('\n', stderr); +	if(cur_line && cur_line[0]) +		fprintf(stderr, "-> %s\n", cur_line); +	exit(1); +} + +static void emit1(unsigned char v) +{ +	int writecnt; +	 +	if(assembler_pass) +	{ +		out[compiledOfs++] = v; + +		if(fout) +			writecnt = fwrite(&v, 1, 1, fout); +			 +		debug("%02hx ", v); +	} +	else +	{ +		++compiledOfs; +	} +} + +static inline void emit2(u16 v) +{ +	emit1(v&0xFF); +	emit1((v>>8)&0xFF); +} + +static inline void emit4(u32 v) +{ +	emit1(v&0xFF); +	emit1((v>>8)&0xFF); +	emit1((v>>16)&0xFF); +	emit1((v>>24)&0xFF); +} + +static inline void emit8(u64 v) +{ +	emit4(v&0xFFFFFFFF); +	emit4((v>>32)&0xFFFFFFFF); +} + +enum { +	REX_W = 0x08, +	REX_R = 0x04, +	REX_X = 0x02, +	REX_B = 0x01, +}; + +enum { +	MODRM_MOD_00 = 0x00, +	MODRM_MOD_01 = 0x01 << 6, +	MODRM_MOD_10 = 0x02 << 6, +	MODRM_MOD_11 = 0x03 << 6, +	MODRM_RM_SIB = 0x04, +}; + +typedef enum +{ +	T_NONE      = 0x00, +	T_REGISTER  = 0x01, +	T_IMMEDIATE = 0x02, +	T_MEMORY    = 0x04, +	T_LABEL     = 0x08, +	T_ABSOLUTE  = 0x80 +} argtype_t; + +typedef enum { +	R_8   = 0x100,  +	R_16  = 0x200,  +	R_64  = 0x800,  +	R_MSZ = 0xF00,  // size mask +	R_XMM = 0x2000, // xmm register. year, sucks +	R_EAX =  0x00, +	R_EBX =  0x03, +	R_ECX =  0x01, +	R_EDX =  0x02, +	R_ESI =  0x06, +	R_EDI =  0x07, +	R_ESP =  0x04, +	R_RAX =  R_EAX | R_64, +	R_RBX =  R_EBX | R_64, +	R_RCX =  R_ECX | R_64, +	R_RDX =  R_EDX | R_64, +	R_RSI =  R_ESI | R_64, +	R_RDI =  R_EDI | R_64, +	R_RSP =  R_ESP | R_64, +	R_R8  =  0x08  | R_64, +	R_R9  =  0x09  | R_64, +	R_R10 =  0x0A  | R_64, +	R_R15 =  0x0F  | R_64, +	R_AL  =  R_EAX | R_8, +	R_AX  =  R_EAX | R_16, +	R_CL  =  R_ECX | R_8, +	R_XMM0 = 0x00  | R_XMM, +	R_MGP =  0x0F, // mask for general purpose registers +} reg_t; + +typedef enum { +	MODRM_SIB = 0, +	MODRM_NOSIB = 0x3, +} modrm_sib_t; + +typedef struct { +	unsigned disp; +	argtype_t basetype; +	union { +		u64 imm; +		reg_t reg; +	} base; +	argtype_t indextype; +	union { +		u64 imm; +		reg_t reg; +	} index; +	unsigned scale; +} memref_t; + +#define LABELLEN 32 + +typedef struct { +	argtype_t type; +	union { +		u64 imm; +		reg_t reg; +		memref_t mem; +		char label[LABELLEN]; +	} v; +	int absolute:1; +} arg_t; + +typedef void (*emitfunc)(const char* op, arg_t arg1, arg_t arg2, void* data); + +typedef struct { +	char* mnemonic; +	emitfunc func; +	void* data; +} op_t; + +typedef struct { +	u8 xmmprefix; +	u8 subcode; // in modrm +	u8 rmcode;  // opcode for reg/mem, reg +	u8 mrcode;  // opcode for reg, reg/mem +	u8 rcode8;  // opcode for reg8/mem8 +	u8 rcode;  // opcode for reg/mem +} opparam_t; + +/* ************************* */ + +static unsigned hashkey(const char *string, unsigned len) { +	unsigned register hash, i; + +	hash = 0; +	for (i = 0; i < len && string[i] != '\0'; ++i) { +		hash += string[i] * (119 + i); +	} +	hash = (hash ^ (hash >> 10) ^ (hash >> 20)); +	return hash; +} + +struct hashentry { +	char* label; +	unsigned address; +	struct hashentry* next; +}; +static struct hashentry* labelhash[1021]; + +// no dup check! +static void hash_add_label(const char* label, unsigned address) +{ +	struct hashentry* h; +	unsigned i = hashkey(label, -1U); +	int labellen; +	 +	i %= ARRAY_LEN(labelhash); +	h = Z_Malloc(sizeof(struct hashentry)); +	 +	labellen = strlen(label) + 1; +	h->label = Z_Malloc(labellen); +	memcpy(h->label, label, labellen); +	 +	h->address = address; +	h->next = labelhash[i]; +	labelhash[i] = h; +} + +static unsigned lookup_label(const char* label) +{ +	struct hashentry* h; +	unsigned i = hashkey(label, -1U); +	i %= ARRAY_LEN(labelhash); +	for(h = labelhash[i]; h; h = h->next ) +	{ +		if(!strcmp(h->label, label)) +			return h->address; +	} +	if(assembler_pass) +		crap("label %s undefined", label); +	return 0; +} + +static void labelhash_free(void) +{ +	struct hashentry* h; +	unsigned i; +	unsigned z = 0, min = -1U, max = 0, t = 0; +	for ( i = 0; i < ARRAY_LEN(labelhash); ++i) +	{ +		unsigned n = 0; +		h = labelhash[i]; +		while(h) +		{ +			struct hashentry* next = h->next; +			Z_Free(h->label); +			Z_Free(h); +			h = next; +			++n; +		} +		t+=n; +		if(!n) ++z; +		//else printf("%u\n", n); +		min = MIN(min, n); +		max = MAX(max, n); +	} +	printf("total %u, hsize %"PRIu64", zero %u, min %u, max %u\n", t, ARRAY_LEN(labelhash), z, min, max); +	memset(labelhash, 0, sizeof(labelhash)); +} + +/* ************************* */ + + +static const char* argtype2str(argtype_t t) +{ +	switch(t) +	{ +		case T_NONE: return "none"; +		case T_REGISTER: return "register"; +		case T_IMMEDIATE: return "immediate"; +		case T_MEMORY: return "memory"; +		case T_LABEL: return "label"; +		default: crap("invalid type"); +	} +	/* not reached */ +	return T_NONE; +} + +/* ************************* */ + +static inline int iss8(u64 v) +{ +	return (llabs(v) <= 0x80); //llabs instead of labs required for __WIN64 +} + +static inline int isu8(u64 v) +{ +	return (v <= 0xff); +} + +static inline int iss16(u64 v) +{ +	return (llabs(v) <= 0x8000); +} + +static inline int isu16(u64 v) +{ +	return (v <= 0xffff); +} + +static inline int iss32(u64 v) +{ +	return (llabs(v) <= 0x80000000); +} + +static inline int isu32(u64 v) +{ +	return (v <= 0xffffffff); +} + +static void emit_opsingle(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ +	u8 op = (u8)((uint64_t) data); + +	if(arg1.type != T_NONE || arg2.type != T_NONE) +		CRAP_INVALID_ARGS; + +	emit1(op); +} + +static void emit_opsingle16(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ +	emit1(0x66); +	emit_opsingle(mnemonic, arg1, arg2, data); +} + +static void compute_rexmodrmsib(u8* rex_r, u8* modrm_r, u8* sib_r, arg_t* arg1, arg_t* arg2) +{ +	u8 rex = 0; +	u8 modrm = 0; +	u8 sib = 0; + +	if((arg1->type == T_REGISTER && arg2->type == T_REGISTER) +	&& ((arg1->v.reg & R_MSZ) != (arg2->v.reg & R_MSZ)) +	&& !((arg1->v.reg & R_XMM) || (arg2->v.reg & R_XMM))) +		crap("both registers must be of same width"); + +	if((arg1->type == T_REGISTER && arg1->v.reg & R_64) +	|| (arg2->type == T_REGISTER && arg2->v.reg & R_64)) +	{ +		rex |= REX_W; +	} + +	if(arg1->type == T_REGISTER) +	{ +		if((arg1->v.reg & R_MGP) > 0x07) +			rex |= REX_R; + +		modrm |= (arg1->v.reg & 0x07) << 3; +	} + +	if(arg2->type == T_REGISTER) +	{ +		if((arg2->v.reg & R_MGP) > 0x07) +			rex |= REX_B; + +		modrm |= (arg2->v.reg & 0x07); +	} + +	if(arg2->type == T_MEMORY) +	{ +		if((arg2->v.mem.basetype == T_REGISTER && !(arg2->v.mem.base.reg & R_64)) +		|| (arg2->v.mem.indextype == T_REGISTER && !(arg2->v.mem.index.reg & R_64))) +		{ +			crap("only 64bit base/index registers are %x %x", arg2->v.mem.base.reg, arg2->v.mem.index.reg); +		} + +		if(arg2->v.mem.indextype == T_REGISTER) +		{ +			modrm |= MODRM_RM_SIB; +			if(!arg2->v.mem.disp) +			{ +				modrm |= MODRM_MOD_00; +			} +			else if(iss8(arg2->v.mem.disp)) +			{ +				modrm |= MODRM_MOD_01; +			} +			else if(isu32(arg2->v.mem.disp)) +			{ +				modrm |= MODRM_MOD_10; +			} +			else +			{ +				crap("invalid displacement"); +			} + +			if((arg2->v.mem.index.reg & R_MGP) > 0x07) +				rex |= REX_X; + +			if((arg2->v.mem.base.reg & R_MGP) > 0x07) +				rex |= REX_B; + +			if(arg2->v.mem.basetype != T_REGISTER) +				crap("base must be register"); +			switch(arg2->v.mem.scale) +			{ +				case 1: break; +				case 2: sib |= 1 << 6; break; +				case 4: sib |= 2 << 6; break; +				case 8: sib |= 3 << 6; break; +			} +			sib |= (arg2->v.mem.index.reg & 0x07) << 3; +			sib |= (arg2->v.mem.base.reg & 0x07); +		} +		else if(arg2->v.mem.indextype == T_NONE) +		{ +			if(!arg2->v.mem.disp) +			{ +				modrm |= MODRM_MOD_00; +			} +			else if(iss8(arg2->v.mem.disp)) +			{ +				modrm |= MODRM_MOD_01; +			} +			else if(isu32(arg2->v.mem.disp)) +			{ +				modrm |= MODRM_MOD_10; +			} +			else +			{ +				crap("invalid displacement"); +			} + +			if(arg2->v.mem.basetype != T_REGISTER) +				crap("todo: base != register"); + +			if((arg2->v.mem.base.reg & R_MGP) > 0x07) +				rex |= REX_B; + +			modrm |= arg2->v.mem.base.reg & 0x07; +		} +		else +		{ +			crap("invalid indextype"); +		} +	} +	else +	{ +		modrm |= MODRM_MOD_11; +	} + +	if(rex) +		rex |= 0x40; // XXX + +	*rex_r = rex; +	*modrm_r = modrm; +	*sib_r = sib; +} + +static void maybe_emit_displacement(arg_t* arg) +{ +	if(arg->type != T_MEMORY) +		return; + +	if(arg->v.mem.disp) +	{ +		if(iss8(arg->v.mem.disp)) +		{ +			emit1((u8)arg->v.mem.disp); +		} +		else if(isu32(arg->v.mem.disp)) +		{ +			emit4(arg->v.mem.disp); +		} +		else +		{ +			crap("invalid displacement"); +		} +	} +} + +/* one byte operator with register added to operator */ +static void emit_opreg(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ +	u8 op = (u8)((uint64_t) data); + +	if(arg1.type != T_REGISTER || arg2.type != T_NONE) +		CRAP_INVALID_ARGS; + +	if((arg1.v.reg & R_MGP) > 0x07) +		emit1(0x40 | REX_B); + +	op |= (arg1.v.reg & 0x07); + +	emit1(op); +} + +/* operator which operates on reg/mem */ +static void emit_op_rm(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ +	u8 rex, modrm, sib; +	opparam_t* params = data; + +	if((arg1.type != T_REGISTER && arg1.type != T_MEMORY) || arg2.type != T_NONE) +		CRAP_INVALID_ARGS; + +	compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1); + +	modrm |= params->subcode << 3; + +	if(arg1.v.reg & R_16) +		emit1(0x66); + +	if(rex) emit1(rex); +	if(arg1.v.reg & R_8) +		emit1(params->rcode8); // op reg8/mem8, +	else +		emit1(params->rcode); // op reg/mem, +	emit1(modrm); +	if((modrm & 0x07) == MODRM_RM_SIB) +		emit1(sib); + +	maybe_emit_displacement(&arg1); +} + +/* operator which operates on reg/mem with cl */ +static void emit_op_rm_cl(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ +	u8 rex, modrm, sib; +	opparam_t* params = data; + +	if(arg2.type != T_REGISTER || arg1.type != T_REGISTER) +		CRAP_INVALID_ARGS; + +	if((arg1.v.reg & R_MGP) != R_ECX && !(arg1.v.reg & R_8)) +		crap("only cl register is valid"); + +	arg1.type = T_NONE; // don't complain, we know it's cl anyways + +	compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); + +	modrm |= params->subcode << 3; + +	if(arg2.v.reg & R_16) +		emit1(0x66); + +	if(rex) emit1(rex); +	if(arg2.v.reg & R_8) +		emit1(params->rcode8); // op reg8/mem8, +	else +		emit1(params->rcode); // op reg/mem, +	emit1(modrm); +	if((modrm & 0x07) == MODRM_RM_SIB) +		emit1(sib); + +	maybe_emit_displacement(&arg2); +} + +static void emit_mov(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ +	u8 rex = 0; +	u8 modrm = 0; +	u8 sib = 0; + +	if(arg1.type == T_IMMEDIATE && arg2.type == T_REGISTER) +	{ +		u8 op = 0xb8; +		 +		if(arg2.v.reg & R_8) +		{ +			if(!isu8(arg1.v.imm)) +				crap("value too large for 8bit register"); + +			op = 0xb0; +		} +		else if(arg2.v.reg & R_16) +		{ +			if(!isu16(arg1.v.imm)) +				crap("value too large for 16bit register"); +			emit1(0x66); +		} +		else if(!(arg2.v.reg & R_64)) +		{ +			if(!isu32(arg1.v.imm)) +				crap("value too large for 32bit register"); +		} + +		compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); + +		if(rex) emit1(rex); + +		op |= (arg2.v.reg & 0x07); + +		emit1(op); + +		if(arg2.v.reg & R_8) emit1(arg1.v.imm); +		else if(arg2.v.reg & R_16) emit2(arg1.v.imm); +		else if(arg2.v.reg & R_64) emit8(arg1.v.imm); +		else emit4(arg1.v.imm); +	} +	else if(arg1.type == T_IMMEDIATE && arg2.type == T_MEMORY) +	{ +		if(!iss32(arg1.v.imm)) +		{ +			crap("only 32bit immediates supported"); +		} + +		compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); +		if(rex) emit1(rex); +		emit1(0xc7); // mov reg/mem, imm +		emit1(modrm); +		if((modrm & 0x07) == MODRM_RM_SIB) +			emit1(sib); + +		emit4(arg1.v.imm); +	} +	else if(arg1.type == T_REGISTER && arg2.type == T_REGISTER) // XXX: same as next +	{ +		if(arg1.type != T_REGISTER || arg2.type != T_REGISTER) +			crap("both args must be registers"); + +		if((arg1.v.reg & R_MSZ) != (arg2.v.reg & R_MSZ)) +			crap("both registers must be same width"); + +		compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); + +		if(rex) emit1(rex); +		emit1(0x89); // mov reg reg/mem, +		emit1(modrm); +	} +	else if(arg1.type == T_REGISTER && arg2.type == T_MEMORY) +	{ +		compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); + +		if(arg1.v.reg & R_16) +			emit1(0x66); + +		if(rex) emit1(rex); +		if(arg1.v.reg & R_8) +			emit1(0x88); // mov reg reg/mem, +		else +			emit1(0x89); // mov reg reg/mem, +		emit1(modrm); +		if((modrm & 0x07) == MODRM_RM_SIB) +			emit1(sib); + +		maybe_emit_displacement(&arg2); +	} +	else if(arg1.type == T_MEMORY && arg2.type == T_REGISTER) +	{ +		compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1); + +		if(arg2.v.reg & R_16) +			emit1(0x66); + +		if(rex) emit1(rex); +		if(arg2.v.reg & R_8) +			emit1(0x8a); // mov reg/mem, reg +		else +			emit1(0x8b); // mov reg/mem, reg +		emit1(modrm); +		if((modrm & 0x07) == MODRM_RM_SIB) +			emit1(sib); + +		maybe_emit_displacement(&arg1); +	} +	else +		CRAP_INVALID_ARGS; +} + +static void emit_subaddand(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ +	u8 rex = 0; +	u8 modrm = 0; +	u8 sib = 0; + +	opparam_t* params = data; + +	if(arg1.type == T_IMMEDIATE && arg2.type == T_REGISTER) +	{ +		if(!iss32(arg1.v.imm)) +		{ +			crap("only 8 and 32 bit immediates supported"); +		} + +		compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); + +		modrm |= params->subcode << 3; + +		if(rex) emit1(rex); +#if 0 +		if(isu8(arg1.v.imm)) +		{ +			emit1(0x83); // sub reg/mem, imm8 +			emit1(modrm); +			emit1(arg1.v.imm&0xFF); +		} +		else +#endif +		{ +			emit1(0x81); // sub reg/mem, imm32 +			emit1(modrm); +			emit4(arg1.v.imm); +		} +	} +	else if(arg1.type == T_REGISTER && (arg2.type == T_MEMORY || arg2.type == T_REGISTER)) +	{ +		compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); + +		if(rex) emit1(rex); +		emit1(params->rmcode); // sub reg/mem, reg +		emit1(modrm); +		if(arg2.type == T_MEMORY && (modrm & 0x07) == MODRM_RM_SIB) +			emit1(sib); + +		maybe_emit_displacement(&arg2); +	} +	else if(arg1.type == T_MEMORY && arg2.type == T_REGISTER && params->mrcode) +	{ +		compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1); + +		if(rex) emit1(rex); +		emit1(params->mrcode); // sub reg, reg/mem +		emit1(modrm); +		if((modrm & 0x07) == MODRM_RM_SIB) +			emit1(sib); + +		maybe_emit_displacement(&arg1); +	} +	else +		CRAP_INVALID_ARGS; +} + +static void emit_condjump(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ +	unsigned off; +	int disp; +	unsigned char opcode = (unsigned char)(((uint64_t)data)&0xFF); + +	if(arg1.type != T_LABEL || arg2.type != T_NONE) +		crap("%s: argument must be label", mnemonic); + +	emit1(opcode); + +	off = lookup_label(arg1.v.label); +	disp = off-(compiledOfs+1); +	if(assembler_pass && abs(disp) > 127) +		crap("cannot jump that far (%x -> %x = %x)", compiledOfs, off, disp); + +	emit1(disp); +} + +static void emit_jmp(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ +	if((arg1.type != T_LABEL && arg1.type != T_REGISTER && arg1.type != T_MEMORY) || arg2.type != T_NONE) +		CRAP_INVALID_ARGS; + +	if(arg1.type == T_LABEL) +	{ +		unsigned off; +		int disp; + +		off = lookup_label(arg1.v.label); +		disp = off-(compiledOfs+5); +		emit1(0xe9); +		emit4(disp); +	} +	else +	{ +		u8 rex, modrm, sib; + +		if(arg1.type == T_REGISTER) +		{ +			if(!arg1.absolute) +				crap("jmp must be absolute"); + +			if((arg1.v.reg & R_64) != R_64) +				crap("register must be 64bit"); + +			arg1.v.reg ^= R_64; // no rex required for call +		} + +		compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1); + +		modrm |= 0x4 << 3; + +		if(rex) emit1(rex); +		emit1(0xff); +		emit1(modrm); +		if((modrm & 0x07) == MODRM_RM_SIB) +			emit1(sib); +		maybe_emit_displacement(&arg1); +	} +} + +static void emit_call(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ +	u8 rex, modrm, sib; + +	if((arg1.type != T_REGISTER && arg1.type != T_IMMEDIATE) || arg2.type != T_NONE) +		CRAP_INVALID_ARGS; + +	if(arg1.type == T_REGISTER) +	{ +		if(!arg1.absolute) +			crap("call must be absolute"); + +		if((arg1.v.reg & R_64) != R_64) +			crap("register must be 64bit"); + +		arg1.v.reg ^= R_64; // no rex required for call + +		compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1); + +		modrm |= 0x2 << 3; + +		if(rex) emit1(rex); +		emit1(0xff); +		emit1(modrm); +	} +	else  +	{ +		if(!isu32(arg1.v.imm)) +			crap("must be 32bit argument"); +		emit1(0xe8); +		emit4(arg1.v.imm); +	} +} + + +static void emit_twobyte(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ +	u8 rex, modrm, sib; + +	opparam_t* params = data; + +	if(arg1.type == T_REGISTER && (arg2.type == T_MEMORY || arg2.type == T_REGISTER)) +	{ +		compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); + +		if(params->xmmprefix) emit1(params->xmmprefix); +		if(rex) emit1(rex); +		emit1(0x0f); +		emit1(params->rmcode); // sub reg/mem, reg +		emit1(modrm); +		if((modrm & 0x07) == MODRM_RM_SIB) +			emit1(sib); + +		maybe_emit_displacement(&arg2); +	} +	else if(arg1.type == T_MEMORY && arg2.type == T_REGISTER && params->mrcode) +	{ +		compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1); + +		if(params->xmmprefix) emit1(params->xmmprefix); +		if(rex) emit1(rex); +		emit1(0x0f); +		emit1(params->mrcode); // sub reg, reg/mem +		emit1(modrm); +		if((modrm & 0x07) == MODRM_RM_SIB) +			emit1(sib); + +		maybe_emit_displacement(&arg1); +	} +	else +		CRAP_INVALID_ARGS; +} + +static opparam_t params_add = { subcode: 0, rmcode: 0x01, }; +static opparam_t params_or = { subcode: 1, rmcode: 0x09, }; +static opparam_t params_and = { subcode: 4, rmcode: 0x21, }; +static opparam_t params_sub = { subcode: 5, rmcode: 0x29, }; +static opparam_t params_xor = { subcode: 6, rmcode: 0x31, }; +static opparam_t params_cmp = { subcode: 7, rmcode: 0x39, mrcode: 0x3b, }; +static opparam_t params_dec = { subcode: 1, rcode: 0xff, rcode8: 0xfe, }; +static opparam_t params_sar = { subcode: 7, rcode: 0xd3, rcode8: 0xd2, }; +static opparam_t params_shl = { subcode: 4, rcode: 0xd3, rcode8: 0xd2, }; +static opparam_t params_shr = { subcode: 5, rcode: 0xd3, rcode8: 0xd2, }; +static opparam_t params_idiv = { subcode: 7, rcode: 0xf7, rcode8: 0xf6, }; +static opparam_t params_div = { subcode: 6, rcode: 0xf7, rcode8: 0xf6, }; +static opparam_t params_imul = { subcode: 5, rcode: 0xf7, rcode8: 0xf6, }; +static opparam_t params_mul = { subcode: 4, rcode: 0xf7, rcode8: 0xf6, }; +static opparam_t params_neg = { subcode: 3, rcode: 0xf7, rcode8: 0xf6, }; +static opparam_t params_not = { subcode: 2, rcode: 0xf7, rcode8: 0xf6, }; + +static opparam_t params_cvtsi2ss = { xmmprefix: 0xf3, rmcode: 0x2a }; +static opparam_t params_cvttss2si = { xmmprefix: 0xf3, rmcode: 0x2c }; +static opparam_t params_addss = { xmmprefix: 0xf3, mrcode: 0x58 }; +static opparam_t params_divss = { xmmprefix: 0xf3, mrcode: 0x5e }; +static opparam_t params_movss = { xmmprefix: 0xf3, mrcode: 0x10, rmcode: 0x11 }; +static opparam_t params_mulss = { xmmprefix: 0xf3, mrcode: 0x59 }; +static opparam_t params_subss = { xmmprefix: 0xf3, mrcode: 0x5c }; +static opparam_t params_ucomiss = { mrcode: 0x2e }; + +static int ops_sorted = 0; +static op_t ops[] = { +	{ "addl", emit_subaddand, ¶ms_add }, +	{ "addq", emit_subaddand, ¶ms_add }, +	{ "addss", emit_twobyte, ¶ms_addss }, +	{ "andl", emit_subaddand, ¶ms_and }, +	{ "andq", emit_subaddand, ¶ms_and }, +	{ "callq", emit_call, NULL }, +	{ "cbw", emit_opsingle16, (void*)0x98 }, +	{ "cdq", emit_opsingle, (void*)0x99 }, +	{ "cmpl", emit_subaddand, ¶ms_cmp }, +	{ "cmpq", emit_subaddand, ¶ms_cmp }, +	{ "cvtsi2ss", emit_twobyte, ¶ms_cvtsi2ss }, +	{ "cvttss2si", emit_twobyte, ¶ms_cvttss2si }, +	{ "cwde", emit_opsingle, (void*)0x98 }, +	{ "decl", emit_op_rm, ¶ms_dec }, +	{ "decq", emit_op_rm, ¶ms_dec }, +	{ "divl", emit_op_rm, ¶ms_div }, +	{ "divq", emit_op_rm, ¶ms_div }, +	{ "divss", emit_twobyte, ¶ms_divss }, +	{ "idivl", emit_op_rm, ¶ms_idiv }, +	{ "imull", emit_op_rm, ¶ms_imul }, +	{ "int3", emit_opsingle, (void*)0xcc }, +	{ "ja", emit_condjump, (void*)0x77 }, +	{ "jbe", emit_condjump, (void*)0x76 }, +	{ "jb", emit_condjump, (void*)0x72 }, +	{ "je", emit_condjump, (void*)0x74 }, +	{ "jl", emit_condjump, (void*)0x7c }, +	{ "jmp", emit_jmp, NULL }, +	{ "jmpq", emit_jmp, NULL }, +	{ "jnae", emit_condjump, (void*)0x72 }, +	{ "jna", emit_condjump, (void*)0x76 }, +	{ "jnbe", emit_condjump, (void*)0x77 }, +	{ "jnb", emit_condjump, (void*)0x73 }, +	{ "jnc", emit_condjump, (void*)0x73 }, +	{ "jne", emit_condjump, (void*)0x75 }, +	{ "jnge", emit_condjump, (void*)0x7c }, +	{ "jng", emit_condjump, (void*)0x7e }, +	{ "jnle", emit_condjump, (void*)0x7f }, +	{ "jnl", emit_condjump, (void*)0x7d }, +	{ "jnz", emit_condjump, (void*)0x75 }, +	{ "jp", emit_condjump, (void*)0x7a }, +	{ "jz", emit_condjump, (void*)0x74 }, +	{ "movb", emit_mov, NULL }, +	{ "movl", emit_mov, NULL }, +	{ "movq", emit_mov, NULL }, +	{ "movss", emit_twobyte, ¶ms_movss }, +	{ "movw", emit_mov, NULL }, +	{ "mull", emit_op_rm, ¶ms_mul }, +	{ "mulss", emit_twobyte, ¶ms_mulss }, +	{ "negl", emit_op_rm, ¶ms_neg }, +	{ "negq", emit_op_rm, ¶ms_neg }, +	{ "nop", emit_opsingle, (void*)0x90 }, +	{ "notl", emit_op_rm, ¶ms_not }, +	{ "notq", emit_op_rm, ¶ms_not }, +	{ "or",   emit_subaddand, ¶ms_or }, +	{ "orl",  emit_subaddand, ¶ms_or }, +	{ "pop", emit_opreg, (void*)0x58 }, +	{ "push", emit_opreg, (void*)0x50 }, +	{ "ret", emit_opsingle, (void*)0xc3 }, +	{ "sarl", emit_op_rm_cl, ¶ms_sar }, +	{ "shl", emit_op_rm_cl, ¶ms_shl }, +	{ "shrl", emit_op_rm_cl, ¶ms_shr }, +	{ "subl", emit_subaddand, ¶ms_sub }, +	{ "subq", emit_subaddand, ¶ms_sub }, +	{ "subss", emit_twobyte, ¶ms_subss }, +	{ "ucomiss", emit_twobyte, ¶ms_ucomiss }, +	{ "xorl",  emit_subaddand, ¶ms_xor }, +	{ "xorq",  emit_subaddand, ¶ms_xor }, +	{ NULL, NULL, NULL } +}; + +static int opsort(const void* A, const void* B) +{ +	const op_t* a = A; +	const op_t* b = B; +	return strcmp(a->mnemonic, b->mnemonic); +} + +static op_t* getop(const char* n) +{ +#if 0 +	op_t* o = ops; +	while(o->mnemonic) +	{ +		if(!strcmp(o->mnemonic, n)) +			return o; +		++o; +	} + +#else +	unsigned m, t, b; +	int r; +	t = ARRAY_LEN(ops)-1; +	b = 0; + +	while(b <= t) +	{ +		m = ((t-b)>>1) + b; +		if((r = strcmp(ops[m].mnemonic, n)) == 0) +		{ +			return &ops[m]; +		} +		else if(r < 0) +		{ +			b = m + 1; +		} +		else +		{ +			t = m - 1; +		} +	} +#endif + +	return NULL; +} + +static reg_t parsereg(const char* str) +{ +	const char* s = str; +	if(*s == 'a' && s[1] == 'l' && !s[2]) +	{ +		return R_AL; +	} +	else if(*s == 'a' && s[1] == 'x' && !s[2]) +	{ +		return R_AX; +	} +	if(*s == 'c' && s[1] == 'l' && !s[2]) +	{ +		return R_CL; +	} +	if(*s == 'x') +	{ +		if(!strcmp(s, "xmm0")) +			return R_XMM0; +	} +	else if(*s == 'r' && s[1]) +	{ +		++s; +		if(s[1] == 'x') +		{ +			switch(*s++) +			{ +				case 'a': return R_RAX; +				case 'b': return R_RBX; +				case 'c': return R_RCX; +				case 'd': return R_RDX; +			} +		} +		else if(s[1] == 'i') +		{ +			switch(*s++) +			{ +				case 's': return R_RSI; +				case 'd': return R_RDI; +			} +		} +		else if(s[0] == 's' && s[1] == 'p' && !s[2]) +		{ +			return R_RSP; +		} +		else if(*s == '8' && !s[1]) +			return R_R8; +		else if(*s == '9' && !s[1]) +			return R_R9; +		else if(*s == '1' && s[1] == '0') +			return R_R10; +		else if(*s == '1' && s[1] == '5') +			return R_R15; +	} +	else if(*s == 'e' && s[1]) +	{ +		++s; +		if(s[1] == 'x') +		{ +			switch(*s++) +			{ +				case 'a': return R_EAX; +				case 'b': return R_EBX; +				case 'c': return R_ECX; +				case 'd': return R_EDX; +			} +		} +		else if(s[1] == 'i') +		{ +			switch(*s++) +			{ +				case 's': return R_ESI; +				case 'd': return R_EDI; +			} +		} +	} + +	crap("invalid register %s", str); + +	return 0; +} + +typedef enum { +	TOK_LABEL = 0x80, +	TOK_INT = 0x81, +	TOK_END = 0x82, +	TOK_INVALID = 0x83, +} token_t; + +static unsigned char nexttok(const char** str, char* label, u64* val) +{ +	const char* s = *str; + +	if(label) *label = 0; +	if(val) *val = 0; + +	while(*s && *s == ' ') ++s; + +	if(!*s) +	{ +		return TOK_END; +	} +	else if(*s == '$' || *s == '*' || *s == '%' || *s == '-' || *s == ')' || *s == '(' || *s == ',') +	{ +		*str = s+1; +		return *s; +	} +	else if(*s >= 'a' && *s <= 'z') +	{ +		size_t a = strspn(s+1, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"); +		if(a+1 >= LABELLEN) +			crap("label %s too long", s); +		if(label) +		{ +			strncpy(label, s, a+1); +			label[a+1] = 0; +		} +		*str = s+a+1; +		return TOK_LABEL; +	} +	else if(*s >= '0' && *s <= '9') +	{ +		char* endptr = NULL; +		u64 v = strtoull(s, &endptr, 0); +		if(endptr && (endptr-s == 0)) +			crap("invalid integer %s", s); +		if(val) *val = v; +		*str = endptr; +		return TOK_INT; +	} +	crap("can't parse '%s'", *str); +	return TOK_INVALID; +} + +static arg_t parsearg(const char** str) +{ +	arg_t arg; +	const char* s = *str; +	char label[20]; +	u64 val; +	int negative = 1; +	unsigned ttype; + +	arg.type = T_NONE; +	arg.absolute = 0; + +	while(*s && *s == ' ') ++s; + +	switch(nexttok(&s, label, &val)) +	{ +		case '$' : +			ttype = nexttok(&s, NULL, &val); +			if(ttype == '-') +			{ +				negative = -1; +				ttype = nexttok(&s, NULL, &val); +			} +			if(ttype != TOK_INT) +				crap("expected integer"); +			arg.type = T_IMMEDIATE; +			arg.v.imm = negative * val; +			break; +		case '*' : +			if((ttype = nexttok(&s, NULL, NULL)) != '%') +			{ +				if(ttype == '(') +					goto tok_memory; +				crap("expected '%%'"); +			} +			arg.absolute = 1; +			/* fall through */ +		case '%' : +			if(nexttok(&s, label, &val) != TOK_LABEL) +				crap("expected label"); +			arg.type = T_REGISTER; +			arg.v.reg = parsereg(label); +			break; +		case TOK_LABEL: +			arg.type = T_LABEL; +			strncpy(arg.v.label, label, LABELLEN); +			break; +		case '-': +			negative = -1; +			if(nexttok(&s, NULL, &val) != TOK_INT) +				crap("expected integer"); +			/* fall through */ +		case TOK_INT: +			if(nexttok(&s, label, NULL) != '(') +				crap("expected '('"); // mov to/from fixed address not supported +			/* fall through */ +		case '(': +tok_memory: +			arg.type = T_MEMORY; +			arg.v.mem.indextype = T_NONE; +			arg.v.mem.disp = negative * val; +			ttype = nexttok(&s, label, &val); +			if(ttype == '%' && nexttok(&s, label, &val) != TOK_LABEL) +			{ +				crap("expected register"); +			} +			if (ttype == '%') +			{ +				arg.v.mem.basetype = T_REGISTER; +				arg.v.mem.base.reg = parsereg(label); +			} +			else if (ttype == TOK_INT) +			{ +				arg.v.mem.basetype = T_IMMEDIATE; +				arg.v.mem.base.imm = val; +			} +			if((ttype = nexttok(&s, NULL, NULL)) == ',') +			{ +				ttype = nexttok(&s, label, &val); +				if(ttype == '%' && nexttok(&s, label, &val) != TOK_LABEL) +				{ +					crap("expected register"); +				} +				if (ttype == '%') +				{ +					arg.v.mem.indextype = T_REGISTER; +					arg.v.mem.index.reg = parsereg(label); +				} +				else if (ttype == TOK_INT) +				{ +					crap("index must be register"); +					arg.v.mem.indextype = T_IMMEDIATE; +					arg.v.mem.index.imm = val; +				} +				if(nexttok(&s, NULL, NULL) != ',') +					crap("expected ','"); +				if(nexttok(&s, NULL, &val) != TOK_INT) +					crap("expected integer"); +				if(val != 1 && val != 2 && val != 4 && val != 8) +					crap("scale must 1, 2, 4 or 8"); +				arg.v.mem.scale = val; + +				ttype = nexttok(&s, NULL, NULL); +			} +			if(ttype != ')') +			{ +				crap("expected ')' or ','"); +			} +			break; +		default: +			crap("invalid token %hu in %s", *(unsigned char*)s, *str); +			break; +	} + +	*str = s; + +	return arg; +} + +/* ************************* */ + +void assembler_init(int pass) +{ +	compiledOfs = 0; +	assembler_pass = pass; +	if(!pass) +	{ +		labelhash_free(); +		cur_line = NULL; +	} +	if(!ops_sorted) +	{ +		ops_sorted = 1; +		qsort(ops, ARRAY_LEN(ops)-1, sizeof(ops[0]), opsort); +	} +} + +size_t assembler_get_code_size(void) +{ +	return compiledOfs; +} + +void assembler_set_output(char* buf) +{ +	out = buf; +} + +void assemble_line(const char* input, size_t len) +{ +	char line[4096]; +	char* s; +	op_t* o; +	char* opn; +	arg_t arg1, arg2; + +	arg1.type = T_NONE; +	arg2.type = T_NONE; +	opn = NULL; +	o = NULL; + +	if(len < 1) +		return; + +	if(len >= sizeof(line)) +		crap("line too long"); + +	memcpy(line, input, sizeof(line)); +	cur_line = input; + +	if(line[len-1] == '\n') line[--len] = 0; +	if(line[len-1] == ':') +	{ +		line[--len] = 0; +		if(assembler_pass) +			debug("%s: 0x%x\n", line, compiledOfs); +		else +			hash_add_label(line, compiledOfs); +	} +	else +	{ +		opn = line; +		s = strchr(line, ' '); +		if(s) +		{ +			*s++ = 0; +			arg1 = parsearg((const char**)&s); +			if(*s) +			{ +				if(*s != ',') +					crap("expected ',', got '%c'", *s); +				++s; +				arg2 = parsearg((const char**)&s); +			} +		} + +		if(!opn) +		{ +			crap("no operator in %s", line); +		} + +		o = getop(opn); +		if(!o) +		{ +			crap("cannot handle op %s", opn); +		} +		o->func(opn, arg1, arg2, o->data); +		if(assembler_pass) +			debug("   - %s%s", cur_line, cur_line[strlen(cur_line)-1]=='\n'?"":"\n"); +	} +} + +#ifdef SA_STANDALONE +int main(int argc, char* argv[]) +{ +	char line[4096]; +	size_t len; +	int pass; +	FILE* file = NULL; + +	if(argc < 2) +	{ +		crap("specify file"); +	} + +	file = fopen(argv[1], "r"); +	if(!file) +	{ +		crap("can't open file"); +	} + +	if(argc > 2) +	{ +		fout = fopen(argv[2], "w"); +		if(!fout) +		{ +			crap("can't open %s for writing", argv[2]); +		} +	} + +	for(pass = 0; pass < 2; ++pass) +	{ +		if(fseek(file, 0, SEEK_SET)) +			crap("can't rewind file"); + +		if(pass) +		{ +			char* b = malloc(assembler_get_code_size()); +			if(!b) +				crap("cannot allocate memory"); +			assembler_set_output(b); +		} + +		assembler_init(pass); + +		while(fgets(line, sizeof(line), file)) +		{ +			len = strlen(line); +			if(!len) continue; + +			assemble_line(line, len); +		} +	} + +	assembler_init(0); + +	fclose(file); + +	return 0; +} +#endif  | 
