diff options
author | Paweł Redman <trem.redman@gmail.com> | 2013-09-06 22:40:51 +0200 |
---|---|---|
committer | Paweł Redman <trem.redman@gmail.com> | 2013-09-06 22:40:51 +0200 |
commit | 5a85e81685300e2299dabfeb25d513b99df471be (patch) | |
tree | 45c3e342a9af062528c6c32b695629a65eede91b /src/qcommon |
Initial commit
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..761fe72 --- /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 128 +#define DEF_COMHUNKMEGS 128 +#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..d80f254 --- /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 16 +#define MAX_EMOTICONS 64 + +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 |