summaryrefslogtreecommitdiff
path: root/src/qcommon
diff options
context:
space:
mode:
Diffstat (limited to 'src/qcommon')
-rw-r--r--src/qcommon/IpToCountryResolver.c152
-rw-r--r--src/qcommon/IpToCountryResolver.h30
-rw-r--r--src/qcommon/cm_load.c1021
-rw-r--r--src/qcommon/cm_local.h214
-rw-r--r--src/qcommon/cm_patch.c1768
-rw-r--r--src/qcommon/cm_patch.h104
-rw-r--r--src/qcommon/cm_polylib.c738
-rw-r--r--src/qcommon/cm_polylib.h69
-rw-r--r--src/qcommon/cm_public.h84
-rw-r--r--src/qcommon/cm_test.c479
-rw-r--r--src/qcommon/cm_trace.c1802
-rw-r--r--src/qcommon/cmd.c771
-rw-r--r--src/qcommon/common.c3102
-rw-r--r--src/qcommon/cvar.c947
-rw-r--r--src/qcommon/files.c3428
-rw-r--r--src/qcommon/huffman.c438
-rw-r--r--src/qcommon/md4.c208
-rw-r--r--src/qcommon/md5.c310
-rw-r--r--src/qcommon/msg.c1767
-rw-r--r--src/qcommon/net_chan.c801
-rw-r--r--src/qcommon/parse.c3585
-rw-r--r--src/qcommon/q_math.c1562
-rw-r--r--src/qcommon/q_platform.h335
-rw-r--r--src/qcommon/q_shared.c1368
-rw-r--r--src/qcommon/q_shared.h1330
-rw-r--r--src/qcommon/qcommon.h1106
-rw-r--r--src/qcommon/qfiles.h626
-rw-r--r--src/qcommon/surfaceflags.h91
-rw-r--r--src/qcommon/unzip.c4297
-rw-r--r--src/qcommon/unzip.h337
-rw-r--r--src/qcommon/vm.c895
-rw-r--r--src/qcommon/vm_interpreted.c922
-rw-r--r--src/qcommon/vm_local.h185
-rw-r--r--src/qcommon/vm_none.c10
-rw-r--r--src/qcommon/vm_ppc.c2065
-rw-r--r--src/qcommon/vm_x86.c1240
-rw-r--r--src/qcommon/vm_x86_64.c1023
37 files changed, 39210 insertions, 0 deletions
diff --git a/src/qcommon/IpToCountryResolver.c b/src/qcommon/IpToCountryResolver.c
new file mode 100644
index 0000000..f2fd97e
--- /dev/null
+++ b/src/qcommon/IpToCountryResolver.c
@@ -0,0 +1,152 @@
+#include "IpToCountryResolver.h"
+#include "q_shared.h"
+
+void convertToCamelCase(char *str)
+{
+ int upper = 1;
+ char *pos = str;
+ while (*pos != '\0') {
+ if (*pos != ' ') {
+ if (upper) {
+ (*pos) = toupper(*pos);
+ upper = 0;
+ }
+ else
+ (*pos) = tolower(*pos);
+ } else {
+ upper = 1;
+ }
+ pos++;
+ }
+}
+
+IpToCountryInfo *FindCountryInfoUI(unsigned int ip, IpToCountryList *infoList)
+{
+ int abound = 0;
+ int zbound = infoList->size-1;
+ int tbound = 0;
+ IpToCountryInfo *telement;
+
+ if (infoList->size == 0)
+ return NULL;
+
+ while (abound <= zbound)
+ {
+ tbound = (abound+zbound)/2;
+ telement = &infoList->infoArray[tbound];
+
+ if (ip < telement->ipFrom)
+ zbound = tbound-1;
+ else if (ip > telement->ipTo)
+ abound = tbound+1;
+ else
+ return telement;
+ }
+ return NULL;
+}
+
+IpToCountryInfo *FindCountryInfoB(unsigned int parts[4], unsigned int length, IpToCountryList *infoList)
+{
+ return FindCountryInfoUI(ipbyte_to_int(parts, length),infoList);
+}
+
+IpToCountryInfo *FindCountryInfoS(const char *ip_addr, IpToCountryList *infoList)
+{
+ return FindCountryInfoUI(ipstr_to_int(ip_addr),infoList);
+}
+
+unsigned int ipbyte_to_int(unsigned int parts[4], unsigned int length)
+{
+ unsigned int val = parts[length-1];
+
+ switch (length) {
+
+ case 1: /* a -- 32 bits */
+ break;
+
+ case 2: /* a.b -- 8.24 bits */
+ if (val > 0xffffff)
+ return (0);
+ val |= parts[0] << 24;
+ break;
+
+ case 3: /* a.b.c -- 8.8.16 bits */
+ if (val > 0xffff)
+ return (0);
+ val |= (parts[0] << 24) | (parts[1] << 16);
+ break;
+
+ case 4: /* a.b.c.d -- 8.8.8.8 bits */
+ if (val > 0xff)
+ return (0);
+ val |= (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8);
+ break;
+ }
+
+ return val;
+}
+
+unsigned int ipstr_to_int(const char *cp_arg)
+{
+ register unsigned long val;
+ register int base, n;
+ register unsigned char c;
+ register unsigned const char *cp = (unsigned const char *) cp_arg;
+ unsigned int parts[4];
+ register unsigned int *pp = parts;
+
+ for (;;) {
+ /*
+ * Collect number up to ``.''.
+ * Values are specified as for C:
+ * 0x=hex, 0=octal, other=decimal.
+ */
+ val = 0; base = 10;
+ if (*cp == '0') {
+ if (*++cp == 'x' || *cp == 'X')
+ base = 16, cp++;
+ else
+ base = 8;
+ }
+ while ((c = *cp) != '\0') {
+ if (isascii(c) && isdigit(c)) {
+ val = (val * base) + (c - '0');
+ cp++;
+ continue;
+ }
+ if (base == 16 && isascii(c) && isxdigit(c)) {
+ val = (val << 4) +
+ (c + 10 - (islower(c) ? 'a' : 'A'));
+ cp++;
+ continue;
+ }
+ break;
+ }
+ if (*cp == '.') {
+ /*
+ * Internet format:
+ * a.b.c.d
+ * a.b.c (with c treated as 16-bits)
+ * a.b (with b treated as 24 bits)
+ */
+ if (pp >= parts + 3 || val > 0xff)
+ return (0);
+ *pp++ = val, cp++;
+ } else {
+ *pp = val;
+ break;
+ }
+ }
+ /*
+ * Check for trailing characters.
+ */
+ if (*cp && (!isascii(*cp) || !isspace(*cp)))
+ return (0);
+ /*
+ * Concoct the address according to
+ * the number of parts specified.
+ */
+ n = pp - parts + 1;
+
+ return ipbyte_to_int(parts,n);
+}
diff --git a/src/qcommon/IpToCountryResolver.h b/src/qcommon/IpToCountryResolver.h
new file mode 100644
index 0000000..cf97cf8
--- /dev/null
+++ b/src/qcommon/IpToCountryResolver.h
@@ -0,0 +1,30 @@
+#ifndef IPTOCOUNTRYRESOLVER_H_INCLUDED
+#define IPTOCOUNTRYRESOLVER_H_INCLUDED
+
+#define FULL_NAME_LENGTH 64
+
+typedef struct {
+ unsigned int ipFrom;
+ unsigned int ipTo;
+ char name2[2+1]; //1 for zero character (line end)
+ char name3[3+1];
+ char nameFull[FULL_NAME_LENGTH];
+} IpToCountryInfo;
+
+typedef struct {
+ unsigned int size;
+ IpToCountryInfo *infoArray;
+} IpToCountryList;
+
+unsigned int ipstr_to_int (const char *cp_arg);
+unsigned int ipbyte_to_int(unsigned int parts[4], unsigned int length);
+unsigned int LoadIp2CountryInfo(const char *fileName, IpToCountryList *infoList, int camelCaseNames);
+IpToCountryInfo *FindCountryInfoS(const char *ip_addr, IpToCountryList *infoList);
+IpToCountryInfo *FindCountryInfoB(unsigned int parts[4], unsigned int length, IpToCountryList *infoList);
+IpToCountryInfo *FindCountryInfoUI(unsigned int ip, IpToCountryList *infoList);
+
+#endif // IPTOCOUNTRYRESOLVER_H_INCLUDED
+
+
+
+
diff --git a/src/qcommon/cm_load.c b/src/qcommon/cm_load.c
new file mode 100644
index 0000000..06a037a
--- /dev/null
+++ b/src/qcommon/cm_load.c
@@ -0,0 +1,1021 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// cmodel.c -- model loading
+
+#include "cm_local.h"
+
+#ifdef BSPC
+
+#include "../bspc/l_qfiles.h"
+
+void SetPlaneSignbits (cplane_t *out) {
+ int bits, j;
+
+ // for fast box on planeside test
+ bits = 0;
+ for (j=0 ; j<3 ; j++) {
+ if (out->normal[j] < 0) {
+ bits |= 1<<j;
+ }
+ }
+ out->signbits = bits;
+}
+#endif //BSPC
+
+// to allow boxes to be treated as brush models, we allocate
+// some extra indexes along with those needed by the map
+#define BOX_BRUSHES 1
+#define BOX_SIDES 6
+#define BOX_LEAFS 2
+#define BOX_PLANES 12
+
+#define LL(x) x=LittleLong(x)
+
+
+clipMap_t cm;
+int c_pointcontents;
+int c_traces, c_brush_traces, c_patch_traces;
+
+
+byte *cmod_base;
+
+#ifndef BSPC
+cvar_t *cm_noAreas;
+cvar_t *cm_noCurves;
+cvar_t *cm_playerCurveClip;
+#endif
+
+cmodel_t box_model;
+cplane_t *box_planes;
+cbrush_t *box_brush;
+
+
+
+void CM_InitBoxHull (void);
+void CM_FloodAreaConnections (void);
+
+
+/*
+===============================================================================
+
+ MAP LOADING
+
+===============================================================================
+*/
+
+/*
+=================
+CMod_LoadShaders
+=================
+*/
+void CMod_LoadShaders( lump_t *l ) {
+ dshader_t *in, *out;
+ int i, count;
+
+ in = (void *)(cmod_base + l->fileofs);
+ if (l->filelen % sizeof(*in)) {
+ Com_Error (ERR_DROP, "CMod_LoadShaders: funny lump size");
+ }
+ count = l->filelen / sizeof(*in);
+
+ if (count < 1) {
+ Com_Error (ERR_DROP, "Map with no shaders");
+ }
+ cm.shaders = Hunk_Alloc( count * sizeof( *cm.shaders ), h_high );
+ cm.numShaders = count;
+
+ Com_Memcpy( cm.shaders, in, count * sizeof( *cm.shaders ) );
+
+ out = cm.shaders;
+ for ( i=0 ; i<count ; i++, in++, out++ ) {
+ out->contentFlags = LittleLong( out->contentFlags );
+ out->surfaceFlags = LittleLong( out->surfaceFlags );
+ }
+}
+
+
+/*
+=================
+CMod_LoadSubmodels
+=================
+*/
+void CMod_LoadSubmodels( lump_t *l ) {
+ dmodel_t *in;
+ cmodel_t *out;
+ int i, j, count;
+ int *indexes;
+
+ in = (void *)(cmod_base + l->fileofs);
+ if (l->filelen % sizeof(*in))
+ Com_Error (ERR_DROP, "CMod_LoadSubmodels: funny lump size");
+ count = l->filelen / sizeof(*in);
+
+ if (count < 1)
+ Com_Error (ERR_DROP, "Map with no models");
+ cm.cmodels = Hunk_Alloc( count * sizeof( *cm.cmodels ), h_high );
+ cm.numSubModels = count;
+
+ if ( count > MAX_SUBMODELS ) {
+ Com_Error( ERR_DROP, "MAX_SUBMODELS exceeded" );
+ }
+
+ for ( i=0 ; i<count ; i++, in++, out++)
+ {
+ out = &cm.cmodels[i];
+
+ for (j=0 ; j<3 ; j++)
+ { // spread the mins / maxs by a pixel
+ out->mins[j] = LittleFloat (in->mins[j]) - 1;
+ out->maxs[j] = LittleFloat (in->maxs[j]) + 1;
+ }
+
+ if ( i == 0 ) {
+ continue; // world model doesn't need other info
+ }
+
+ // make a "leaf" just to hold the model's brushes and surfaces
+ out->leaf.numLeafBrushes = LittleLong( in->numBrushes );
+ indexes = Hunk_Alloc( out->leaf.numLeafBrushes * 4, h_high );
+ out->leaf.firstLeafBrush = indexes - cm.leafbrushes;
+ for ( j = 0 ; j < out->leaf.numLeafBrushes ; j++ ) {
+ indexes[j] = LittleLong( in->firstBrush ) + j;
+ }
+
+ out->leaf.numLeafSurfaces = LittleLong( in->numSurfaces );
+ indexes = Hunk_Alloc( out->leaf.numLeafSurfaces * 4, h_high );
+ out->leaf.firstLeafSurface = indexes - cm.leafsurfaces;
+ for ( j = 0 ; j < out->leaf.numLeafSurfaces ; j++ ) {
+ indexes[j] = LittleLong( in->firstSurface ) + j;
+ }
+ }
+}
+
+
+/*
+=================
+CMod_LoadNodes
+
+=================
+*/
+void CMod_LoadNodes( lump_t *l ) {
+ dnode_t *in;
+ int child;
+ cNode_t *out;
+ int i, j, count;
+
+ in = (void *)(cmod_base + l->fileofs);
+ if (l->filelen % sizeof(*in))
+ Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size");
+ count = l->filelen / sizeof(*in);
+
+ if (count < 1)
+ Com_Error (ERR_DROP, "Map has no nodes");
+ cm.nodes = Hunk_Alloc( count * sizeof( *cm.nodes ), h_high );
+ cm.numNodes = count;
+
+ out = cm.nodes;
+
+ for (i=0 ; i<count ; i++, out++, in++)
+ {
+ out->plane = cm.planes + LittleLong( in->planeNum );
+ for (j=0 ; j<2 ; j++)
+ {
+ child = LittleLong (in->children[j]);
+ out->children[j] = child;
+ }
+ }
+
+}
+
+/*
+=================
+CM_BoundBrush
+
+=================
+*/
+void CM_BoundBrush( cbrush_t *b ) {
+ b->bounds[0][0] = -b->sides[0].plane->dist;
+ b->bounds[1][0] = b->sides[1].plane->dist;
+
+ b->bounds[0][1] = -b->sides[2].plane->dist;
+ b->bounds[1][1] = b->sides[3].plane->dist;
+
+ b->bounds[0][2] = -b->sides[4].plane->dist;
+ b->bounds[1][2] = b->sides[5].plane->dist;
+}
+
+
+/*
+=================
+CMod_LoadBrushes
+
+=================
+*/
+void CMod_LoadBrushes( lump_t *l ) {
+ dbrush_t *in;
+ cbrush_t *out;
+ int i, count;
+
+ in = (void *)(cmod_base + l->fileofs);
+ if (l->filelen % sizeof(*in)) {
+ Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size");
+ }
+ count = l->filelen / sizeof(*in);
+
+ cm.brushes = Hunk_Alloc( ( BOX_BRUSHES + count ) * sizeof( *cm.brushes ), h_high );
+ cm.numBrushes = count;
+
+ out = cm.brushes;
+
+ for ( i=0 ; i<count ; i++, out++, in++ ) {
+ out->sides = cm.brushsides + LittleLong(in->firstSide);
+ out->numsides = LittleLong(in->numSides);
+
+ out->shaderNum = LittleLong( in->shaderNum );
+ if ( out->shaderNum < 0 || out->shaderNum >= cm.numShaders ) {
+ Com_Error( ERR_DROP, "CMod_LoadBrushes: bad shaderNum: %i", out->shaderNum );
+ }
+ out->contents = cm.shaders[out->shaderNum].contentFlags;
+
+ CM_BoundBrush( out );
+ }
+
+}
+
+/*
+=================
+CMod_LoadLeafs
+=================
+*/
+void CMod_LoadLeafs (lump_t *l)
+{
+ int i;
+ cLeaf_t *out;
+ dleaf_t *in;
+ int count;
+
+ in = (void *)(cmod_base + l->fileofs);
+ if (l->filelen % sizeof(*in))
+ Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size");
+ count = l->filelen / sizeof(*in);
+
+ if (count < 1)
+ Com_Error (ERR_DROP, "Map with no leafs");
+
+ cm.leafs = Hunk_Alloc( ( BOX_LEAFS + count ) * sizeof( *cm.leafs ), h_high );
+ cm.numLeafs = count;
+
+ out = cm.leafs;
+ for ( i=0 ; i<count ; i++, in++, out++)
+ {
+ out->cluster = LittleLong (in->cluster);
+ out->area = LittleLong (in->area);
+ out->firstLeafBrush = LittleLong (in->firstLeafBrush);
+ out->numLeafBrushes = LittleLong (in->numLeafBrushes);
+ out->firstLeafSurface = LittleLong (in->firstLeafSurface);
+ out->numLeafSurfaces = LittleLong (in->numLeafSurfaces);
+
+ if (out->cluster >= cm.numClusters)
+ cm.numClusters = out->cluster + 1;
+ if (out->area >= cm.numAreas)
+ cm.numAreas = out->area + 1;
+ }
+
+ cm.areas = Hunk_Alloc( cm.numAreas * sizeof( *cm.areas ), h_high );
+ cm.areaPortals = Hunk_Alloc( cm.numAreas * cm.numAreas * sizeof( *cm.areaPortals ), h_high );
+}
+
+/*
+=================
+CMod_LoadPlanes
+=================
+*/
+void CMod_LoadPlanes (lump_t *l)
+{
+ int i, j;
+ cplane_t *out;
+ dplane_t *in;
+ int count;
+ int bits;
+
+ in = (void *)(cmod_base + l->fileofs);
+ if (l->filelen % sizeof(*in))
+ Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size");
+ count = l->filelen / sizeof(*in);
+
+ if (count < 1)
+ Com_Error (ERR_DROP, "Map with no planes");
+ cm.planes = Hunk_Alloc( ( BOX_PLANES + count ) * sizeof( *cm.planes ), h_high );
+ cm.numPlanes = count;
+
+ out = cm.planes;
+
+ for ( i=0 ; i<count ; i++, in++, out++)
+ {
+ bits = 0;
+ for (j=0 ; j<3 ; j++)
+ {
+ out->normal[j] = LittleFloat (in->normal[j]);
+ if (out->normal[j] < 0)
+ bits |= 1<<j;
+ }
+
+ out->dist = LittleFloat (in->dist);
+ out->type = PlaneTypeForNormal( out->normal );
+ out->signbits = bits;
+ }
+}
+
+/*
+=================
+CMod_LoadLeafBrushes
+=================
+*/
+void CMod_LoadLeafBrushes (lump_t *l)
+{
+ int i;
+ int *out;
+ int *in;
+ int count;
+
+ in = (void *)(cmod_base + l->fileofs);
+ if (l->filelen % sizeof(*in))
+ Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size");
+ count = l->filelen / sizeof(*in);
+
+ cm.leafbrushes = Hunk_Alloc( (count + BOX_BRUSHES) * sizeof( *cm.leafbrushes ), h_high );
+ cm.numLeafBrushes = count;
+
+ out = cm.leafbrushes;
+
+ for ( i=0 ; i<count ; i++, in++, out++) {
+ *out = LittleLong (*in);
+ }
+}
+
+/*
+=================
+CMod_LoadLeafSurfaces
+=================
+*/
+void CMod_LoadLeafSurfaces( lump_t *l )
+{
+ int i;
+ int *out;
+ int *in;
+ int count;
+
+ in = (void *)(cmod_base + l->fileofs);
+ if (l->filelen % sizeof(*in))
+ Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size");
+ count = l->filelen / sizeof(*in);
+
+ cm.leafsurfaces = Hunk_Alloc( count * sizeof( *cm.leafsurfaces ), h_high );
+ cm.numLeafSurfaces = count;
+
+ out = cm.leafsurfaces;
+
+ for ( i=0 ; i<count ; i++, in++, out++) {
+ *out = LittleLong (*in);
+ }
+}
+
+/*
+=================
+CMod_LoadBrushSides
+=================
+*/
+void CMod_LoadBrushSides (lump_t *l)
+{
+ int i;
+ cbrushside_t *out;
+ dbrushside_t *in;
+ int count;
+ int num;
+
+ in = (void *)(cmod_base + l->fileofs);
+ if ( l->filelen % sizeof(*in) ) {
+ Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size");
+ }
+ count = l->filelen / sizeof(*in);
+
+ cm.brushsides = Hunk_Alloc( ( BOX_SIDES + count ) * sizeof( *cm.brushsides ), h_high );
+ cm.numBrushSides = count;
+
+ out = cm.brushsides;
+
+ for ( i=0 ; i<count ; i++, in++, out++) {
+ num = LittleLong( in->planeNum );
+ out->planeNum = num;
+ out->plane = &cm.planes[num];
+ out->shaderNum = LittleLong( in->shaderNum );
+ if ( out->shaderNum < 0 || out->shaderNum >= cm.numShaders ) {
+ Com_Error( ERR_DROP, "CMod_LoadBrushSides: bad shaderNum: %i", out->shaderNum );
+ }
+ out->surfaceFlags = cm.shaders[out->shaderNum].surfaceFlags;
+ }
+}
+
+#define CM_EDGE_VERTEX_EPSILON 0.1f
+
+/*
+=================
+CMod_BrushEdgesAreTheSame
+=================
+*/
+static qboolean CMod_BrushEdgesAreTheSame( const vec3_t p0, const vec3_t p1,
+ const vec3_t q0, const vec3_t q1 )
+{
+ if( VectorCompareEpsilon( p0, q0, CM_EDGE_VERTEX_EPSILON ) &&
+ VectorCompareEpsilon( p1, q1, CM_EDGE_VERTEX_EPSILON ) )
+ return qtrue;
+
+ if( VectorCompareEpsilon( p1, q0, CM_EDGE_VERTEX_EPSILON ) &&
+ VectorCompareEpsilon( p0, q1, CM_EDGE_VERTEX_EPSILON ) )
+ return qtrue;
+
+ return qfalse;
+}
+
+/*
+=================
+CMod_AddEdgeToBrush
+=================
+*/
+static qboolean CMod_AddEdgeToBrush( const vec3_t p0, const vec3_t p1,
+ cbrushedge_t *edges, int *numEdges )
+{
+ int i;
+
+ if( !edges || !numEdges )
+ return qfalse;
+
+ for( i = 0; i < *numEdges; i++ )
+ {
+ if( CMod_BrushEdgesAreTheSame( p0, p1,
+ edges[ i ].p0, edges[ i ].p1 ) )
+ return qfalse;
+ }
+
+ VectorCopy( p0, edges[ *numEdges ].p0 );
+ VectorCopy( p1, edges[ *numEdges ].p1 );
+ (*numEdges)++;
+
+ return qtrue;
+}
+
+/*
+=================
+CMod_CreateBrushSideWindings
+=================
+*/
+static void CMod_CreateBrushSideWindings( void )
+{
+ int i, j, k;
+ winding_t *w;
+ cbrushside_t *side, *chopSide;
+ cplane_t *plane;
+ cbrush_t *brush;
+ cbrushedge_t *tempEdges;
+ int numEdges;
+ int edgesAlloc;
+ int totalEdgesAlloc = 0;
+ int totalEdges = 0;
+
+ for( i = 0; i < cm.numBrushes; i++ )
+ {
+ brush = &cm.brushes[ i ];
+ numEdges = 0;
+
+ // walk the list of brush sides
+ for( j = 0; j < brush->numsides; j++ )
+ {
+ // get side and plane
+ side = &brush->sides[ j ];
+ plane = side->plane;
+
+ w = BaseWindingForPlane( plane->normal, plane->dist );
+
+ // walk the list of brush sides
+ for( k = 0; k < brush->numsides && w != NULL; k++ )
+ {
+ chopSide = &brush->sides[ k ];
+
+ if( chopSide == side )
+ continue;
+
+ if( chopSide->planeNum == ( side->planeNum ^ 1 ) )
+ continue; // back side clipaway
+
+ plane = &cm.planes[ chopSide->planeNum ^ 1 ];
+ ChopWindingInPlace( &w, plane->normal, plane->dist, 0 );
+ }
+
+ if( w )
+ numEdges += w->numpoints;
+
+ // set side winding
+ side->winding = w;
+ }
+
+ // Allocate a temporary buffer of the maximal size
+ tempEdges = (cbrushedge_t *)Z_Malloc( sizeof( cbrushedge_t ) * numEdges );
+ brush->numEdges = 0;
+
+ // compose the points into edges
+ for( j = 0; j < brush->numsides; j++ )
+ {
+ side = &brush->sides[ j ];
+
+ if( side->winding )
+ {
+ for( k = 0; k < side->winding->numpoints - 1; k++ )
+ {
+ if( brush->numEdges == numEdges )
+ Com_Error( ERR_FATAL,
+ "Insufficient memory allocated for collision map edges" );
+
+ CMod_AddEdgeToBrush( side->winding->p[ k ],
+ side->winding->p[ k + 1 ], tempEdges, &brush->numEdges );
+ }
+
+ FreeWinding( side->winding );
+ side->winding = NULL;
+ }
+ }
+
+ // Allocate a buffer of the actual size
+ edgesAlloc = sizeof( cbrushedge_t ) * brush->numEdges;
+ totalEdgesAlloc += edgesAlloc;
+ brush->edges = (cbrushedge_t *)Hunk_Alloc( edgesAlloc, h_low );
+
+ // Copy temporary buffer to permanent buffer
+ Com_Memcpy( brush->edges, tempEdges, edgesAlloc );
+
+ // Free temporary buffer
+ Z_Free( tempEdges );
+
+ totalEdges += brush->numEdges;
+ }
+
+ Com_DPrintf( "Allocated %d bytes for %d collision map edges...\n",
+ totalEdgesAlloc, totalEdges );
+}
+
+/*
+=================
+CMod_LoadEntityString
+=================
+*/
+void CMod_LoadEntityString( lump_t *l ) {
+ cm.entityString = Hunk_Alloc( l->filelen, h_high );
+ cm.numEntityChars = l->filelen;
+ Com_Memcpy (cm.entityString, cmod_base + l->fileofs, l->filelen);
+}
+
+/*
+=================
+CMod_LoadVisibility
+=================
+*/
+#define VIS_HEADER 8
+void CMod_LoadVisibility( lump_t *l ) {
+ int len;
+ byte *buf;
+
+ len = l->filelen;
+ if ( !len ) {
+ cm.clusterBytes = ( cm.numClusters + 31 ) & ~31;
+ cm.visibility = Hunk_Alloc( cm.clusterBytes, h_high );
+ Com_Memset( cm.visibility, 255, cm.clusterBytes );
+ return;
+ }
+ buf = cmod_base + l->fileofs;
+
+ cm.vised = qtrue;
+ cm.visibility = Hunk_Alloc( len, h_high );
+ cm.numClusters = LittleLong( ((int *)buf)[0] );
+ cm.clusterBytes = LittleLong( ((int *)buf)[1] );
+ Com_Memcpy (cm.visibility, buf + VIS_HEADER, len - VIS_HEADER );
+}
+
+//==================================================================
+
+
+/*
+=================
+CMod_LoadPatches
+=================
+*/
+#define MAX_PATCH_VERTS 1024
+void CMod_LoadPatches( lump_t *surfs, lump_t *verts ) {
+ drawVert_t *dv, *dv_p;
+ dsurface_t *in;
+ int count;
+ int i, j;
+ int c;
+ cPatch_t *patch;
+ vec3_t points[MAX_PATCH_VERTS];
+ int width, height;
+ int shaderNum;
+
+ in = (void *)(cmod_base + surfs->fileofs);
+ if (surfs->filelen % sizeof(*in))
+ Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size");
+ cm.numSurfaces = count = surfs->filelen / sizeof(*in);
+ cm.surfaces = Hunk_Alloc( cm.numSurfaces * sizeof( cm.surfaces[0] ), h_high );
+
+ dv = (void *)(cmod_base + verts->fileofs);
+ if (verts->filelen % sizeof(*dv))
+ Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size");
+
+ // scan through all the surfaces, but only load patches,
+ // not planar faces
+ for ( i = 0 ; i < count ; i++, in++ ) {
+ if ( LittleLong( in->surfaceType ) != MST_PATCH ) {
+ continue; // ignore other surfaces
+ }
+ // FIXME: check for non-colliding patches
+
+ cm.surfaces[ i ] = patch = Hunk_Alloc( sizeof( *patch ), h_high );
+
+ // load the full drawverts onto the stack
+ width = LittleLong( in->patchWidth );
+ height = LittleLong( in->patchHeight );
+ c = width * height;
+ if ( c > MAX_PATCH_VERTS ) {
+ Com_Error( ERR_DROP, "ParseMesh: MAX_PATCH_VERTS" );
+ }
+
+ dv_p = dv + LittleLong( in->firstVert );
+ for ( j = 0 ; j < c ; j++, dv_p++ ) {
+ points[j][0] = LittleFloat( dv_p->xyz[0] );
+ points[j][1] = LittleFloat( dv_p->xyz[1] );
+ points[j][2] = LittleFloat( dv_p->xyz[2] );
+ }
+
+ shaderNum = LittleLong( in->shaderNum );
+ patch->contents = cm.shaders[shaderNum].contentFlags;
+ patch->surfaceFlags = cm.shaders[shaderNum].surfaceFlags;
+
+ // create the internal facet structure
+ patch->pc = CM_GeneratePatchCollide( width, height, points );
+ }
+}
+
+//==================================================================
+
+unsigned CM_LumpChecksum(lump_t *lump) {
+ return LittleLong (Com_BlockChecksum (cmod_base + lump->fileofs, lump->filelen));
+}
+
+unsigned CM_Checksum(dheader_t *header) {
+ unsigned checksums[16];
+ checksums[0] = CM_LumpChecksum(&header->lumps[LUMP_SHADERS]);
+ checksums[1] = CM_LumpChecksum(&header->lumps[LUMP_LEAFS]);
+ checksums[2] = CM_LumpChecksum(&header->lumps[LUMP_LEAFBRUSHES]);
+ checksums[3] = CM_LumpChecksum(&header->lumps[LUMP_LEAFSURFACES]);
+ checksums[4] = CM_LumpChecksum(&header->lumps[LUMP_PLANES]);
+ checksums[5] = CM_LumpChecksum(&header->lumps[LUMP_BRUSHSIDES]);
+ checksums[6] = CM_LumpChecksum(&header->lumps[LUMP_BRUSHES]);
+ checksums[7] = CM_LumpChecksum(&header->lumps[LUMP_MODELS]);
+ checksums[8] = CM_LumpChecksum(&header->lumps[LUMP_NODES]);
+ checksums[9] = CM_LumpChecksum(&header->lumps[LUMP_SURFACES]);
+ checksums[10] = CM_LumpChecksum(&header->lumps[LUMP_DRAWVERTS]);
+
+ return LittleLong(Com_BlockChecksum(checksums, 11 * 4));
+}
+
+/*
+==================
+CM_LoadMap
+
+Loads in the map and all submodels
+==================
+*/
+void CM_LoadMap( const char *name, qboolean clientload, int *checksum ) {
+ int *buf;
+ int i;
+ dheader_t header;
+ int length;
+ static unsigned last_checksum;
+
+ if ( !name || !name[0] ) {
+ Com_Error( ERR_DROP, "CM_LoadMap: NULL name" );
+ }
+
+#ifndef BSPC
+ cm_noAreas = Cvar_Get ("cm_noAreas", "0", CVAR_CHEAT);
+ cm_noCurves = Cvar_Get ("cm_noCurves", "0", CVAR_CHEAT);
+ cm_playerCurveClip = Cvar_Get ("cm_playerCurveClip", "1", CVAR_ARCHIVE|CVAR_CHEAT );
+#endif
+ Com_DPrintf( "CM_LoadMap( %s, %i )\n", name, clientload );
+
+ if ( !strcmp( cm.name, name ) && clientload ) {
+ *checksum = last_checksum;
+ return;
+ }
+
+ // free old stuff
+ Com_Memset( &cm, 0, sizeof( cm ) );
+ CM_ClearLevelPatches();
+
+ if ( !name[0] ) {
+ cm.numLeafs = 1;
+ cm.numClusters = 1;
+ cm.numAreas = 1;
+ cm.cmodels = Hunk_Alloc( sizeof( *cm.cmodels ), h_high );
+ *checksum = 0;
+ return;
+ }
+
+ //
+ // load the file
+ //
+#ifndef BSPC
+ length = FS_ReadFile( name, (void **)&buf );
+#else
+ length = LoadQuakeFile((quakefile_t *) name, (void **)&buf);
+#endif
+
+ if ( !buf ) {
+ Com_Error (ERR_DROP, "Couldn't load %s", name);
+ }
+
+ last_checksum = LittleLong (Com_BlockChecksum (buf, length));
+ *checksum = last_checksum;
+
+ header = *(dheader_t *)buf;
+ for (i=0 ; i<sizeof(dheader_t)/4 ; i++) {
+ ((int *)&header)[i] = LittleLong ( ((int *)&header)[i]);
+ }
+
+ if ( header.version != BSP_VERSION ) {
+ Com_Error (ERR_DROP, "CM_LoadMap: %s has wrong version number (%i should be %i)"
+ , name, header.version, BSP_VERSION );
+ }
+
+ cmod_base = (byte *)buf;
+
+ // load into heap
+ CMod_LoadShaders( &header.lumps[LUMP_SHADERS] );
+ CMod_LoadLeafs (&header.lumps[LUMP_LEAFS]);
+ CMod_LoadLeafBrushes (&header.lumps[LUMP_LEAFBRUSHES]);
+ CMod_LoadLeafSurfaces (&header.lumps[LUMP_LEAFSURFACES]);
+ CMod_LoadPlanes (&header.lumps[LUMP_PLANES]);
+ CMod_LoadBrushSides (&header.lumps[LUMP_BRUSHSIDES]);
+ CMod_LoadBrushes (&header.lumps[LUMP_BRUSHES]);
+ CMod_LoadSubmodels (&header.lumps[LUMP_MODELS]);
+ CMod_LoadNodes (&header.lumps[LUMP_NODES]);
+ CMod_LoadEntityString (&header.lumps[LUMP_ENTITIES]);
+ CMod_LoadVisibility( &header.lumps[LUMP_VISIBILITY] );
+ CMod_LoadPatches( &header.lumps[LUMP_SURFACES], &header.lumps[LUMP_DRAWVERTS] );
+
+ CMod_CreateBrushSideWindings( );
+
+ // we are NOT freeing the file, because it is cached for the ref
+ FS_FreeFile (buf);
+
+ CM_InitBoxHull ();
+
+ CM_FloodAreaConnections ();
+
+ // allow this to be cached if it is loaded by the server
+ if ( !clientload ) {
+ Q_strncpyz( cm.name, name, sizeof( cm.name ) );
+ }
+}
+
+/*
+==================
+CM_ClearMap
+==================
+*/
+void CM_ClearMap( void ) {
+ Com_Memset( &cm, 0, sizeof( cm ) );
+ CM_ClearLevelPatches();
+}
+
+/*
+==================
+CM_ClipHandleToModel
+==================
+*/
+cmodel_t *CM_ClipHandleToModel( clipHandle_t handle ) {
+ if ( handle < 0 ) {
+ Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i", handle );
+ }
+ if ( handle < cm.numSubModels ) {
+ return &cm.cmodels[handle];
+ }
+ if ( handle == BOX_MODEL_HANDLE ) {
+ return &box_model;
+ }
+ if ( handle < MAX_SUBMODELS ) {
+ Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i < %i < %i",
+ cm.numSubModels, handle, MAX_SUBMODELS );
+ }
+ Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i", handle + MAX_SUBMODELS );
+
+ return NULL;
+
+}
+
+/*
+==================
+CM_InlineModel
+==================
+*/
+clipHandle_t CM_InlineModel( int index ) {
+ if ( index < 0 || index >= cm.numSubModels ) {
+ Com_Error (ERR_DROP, "CM_InlineModel: bad number");
+ }
+ return index;
+}
+
+int CM_NumClusters( void ) {
+ return cm.numClusters;
+}
+
+int CM_NumInlineModels( void ) {
+ return cm.numSubModels;
+}
+
+char *CM_EntityString( void ) {
+ return cm.entityString;
+}
+
+int CM_LeafCluster( int leafnum ) {
+ if (leafnum < 0 || leafnum >= cm.numLeafs) {
+ Com_Error (ERR_DROP, "CM_LeafCluster: bad number");
+ }
+ return cm.leafs[leafnum].cluster;
+}
+
+int CM_LeafArea( int leafnum ) {
+ if ( leafnum < 0 || leafnum >= cm.numLeafs ) {
+ Com_Error (ERR_DROP, "CM_LeafArea: bad number");
+ }
+ return cm.leafs[leafnum].area;
+}
+
+//=======================================================================
+
+
+/*
+===================
+CM_InitBoxHull
+
+Set up the planes and nodes so that the six floats of a bounding box
+can just be stored out and get a proper clipping hull structure.
+===================
+*/
+void CM_InitBoxHull (void)
+{
+ int i;
+ int side;
+ cplane_t *p;
+ cbrushside_t *s;
+
+ box_planes = &cm.planes[cm.numPlanes];
+
+ box_brush = &cm.brushes[cm.numBrushes];
+ box_brush->numsides = 6;
+ box_brush->sides = cm.brushsides + cm.numBrushSides;
+ box_brush->contents = CONTENTS_BODY;
+ box_brush->edges = (cbrushedge_t *)Hunk_Alloc(
+ sizeof( cbrushedge_t ) * 12, h_low );
+ box_brush->numEdges = 12;
+
+ box_model.leaf.numLeafBrushes = 1;
+// box_model.leaf.firstLeafBrush = cm.numBrushes;
+ box_model.leaf.firstLeafBrush = cm.numLeafBrushes;
+ cm.leafbrushes[cm.numLeafBrushes] = cm.numBrushes;
+
+ for (i=0 ; i<6 ; i++)
+ {
+ side = i&1;
+
+ // brush sides
+ s = &cm.brushsides[cm.numBrushSides+i];
+ s->plane = cm.planes + (cm.numPlanes+i*2+side);
+ s->surfaceFlags = 0;
+
+ // planes
+ p = &box_planes[i*2];
+ p->type = i>>1;
+ p->signbits = 0;
+ VectorClear (p->normal);
+ p->normal[i>>1] = 1;
+
+ p = &box_planes[i*2+1];
+ p->type = 3 + (i>>1);
+ p->signbits = 0;
+ VectorClear (p->normal);
+ p->normal[i>>1] = -1;
+
+ SetPlaneSignbits( p );
+ }
+}
+
+/*
+===================
+CM_TempBoxModel
+
+To keep everything totally uniform, bounding boxes are turned into small
+BSP trees instead of being compared directly.
+Capsules are handled differently though.
+===================
+*/
+clipHandle_t CM_TempBoxModel( const vec3_t mins, const vec3_t maxs, int capsule ) {
+
+ VectorCopy( mins, box_model.mins );
+ VectorCopy( maxs, box_model.maxs );
+
+ if ( capsule ) {
+ return CAPSULE_MODEL_HANDLE;
+ }
+
+ box_planes[0].dist = maxs[0];
+ box_planes[1].dist = -maxs[0];
+ box_planes[2].dist = mins[0];
+ box_planes[3].dist = -mins[0];
+ box_planes[4].dist = maxs[1];
+ box_planes[5].dist = -maxs[1];
+ box_planes[6].dist = mins[1];
+ box_planes[7].dist = -mins[1];
+ box_planes[8].dist = maxs[2];
+ box_planes[9].dist = -maxs[2];
+ box_planes[10].dist = mins[2];
+ box_planes[11].dist = -mins[2];
+
+ // First side
+ VectorSet( box_brush->edges[ 0 ].p0, mins[ 0 ], mins[ 1 ], mins[ 2 ] );
+ VectorSet( box_brush->edges[ 0 ].p1, mins[ 0 ], maxs[ 1 ], mins[ 2 ] );
+ VectorSet( box_brush->edges[ 1 ].p0, mins[ 0 ], maxs[ 1 ], mins[ 2 ] );
+ VectorSet( box_brush->edges[ 1 ].p1, mins[ 0 ], maxs[ 1 ], maxs[ 2 ] );
+ VectorSet( box_brush->edges[ 2 ].p0, mins[ 0 ], maxs[ 1 ], maxs[ 2 ] );
+ VectorSet( box_brush->edges[ 2 ].p1, mins[ 0 ], mins[ 1 ], maxs[ 2 ] );
+ VectorSet( box_brush->edges[ 3 ].p0, mins[ 0 ], mins[ 1 ], maxs[ 2 ] );
+ VectorSet( box_brush->edges[ 3 ].p1, mins[ 0 ], mins[ 1 ], mins[ 2 ] );
+
+ // Opposite side
+ VectorSet( box_brush->edges[ 4 ].p0, maxs[ 0 ], mins[ 1 ], mins[ 2 ] );
+ VectorSet( box_brush->edges[ 4 ].p1, maxs[ 0 ], maxs[ 1 ], mins[ 2 ] );
+ VectorSet( box_brush->edges[ 5 ].p0, maxs[ 0 ], maxs[ 1 ], mins[ 2 ] );
+ VectorSet( box_brush->edges[ 5 ].p1, maxs[ 0 ], maxs[ 1 ], maxs[ 2 ] );
+ VectorSet( box_brush->edges[ 6 ].p0, maxs[ 0 ], maxs[ 1 ], maxs[ 2 ] );
+ VectorSet( box_brush->edges[ 6 ].p1, maxs[ 0 ], mins[ 1 ], maxs[ 2 ] );
+ VectorSet( box_brush->edges[ 7 ].p0, maxs[ 0 ], mins[ 1 ], maxs[ 2 ] );
+ VectorSet( box_brush->edges[ 7 ].p1, maxs[ 0 ], mins[ 1 ], mins[ 2 ] );
+
+ // Connecting edges
+ VectorSet( box_brush->edges[ 8 ].p0, mins[ 0 ], mins[ 1 ], mins[ 2 ] );
+ VectorSet( box_brush->edges[ 8 ].p1, maxs[ 0 ], mins[ 1 ], mins[ 2 ] );
+ VectorSet( box_brush->edges[ 9 ].p0, mins[ 0 ], maxs[ 1 ], mins[ 2 ] );
+ VectorSet( box_brush->edges[ 9 ].p1, maxs[ 0 ], maxs[ 1 ], mins[ 2 ] );
+ VectorSet( box_brush->edges[ 10 ].p0, mins[ 0 ], maxs[ 1 ], maxs[ 2 ] );
+ VectorSet( box_brush->edges[ 10 ].p1, maxs[ 0 ], maxs[ 1 ], maxs[ 2 ] );
+ VectorSet( box_brush->edges[ 11 ].p0, mins[ 0 ], mins[ 1 ], maxs[ 2 ] );
+ VectorSet( box_brush->edges[ 11 ].p1, maxs[ 0 ], mins[ 1 ], maxs[ 2 ] );
+
+ VectorCopy( mins, box_brush->bounds[0] );
+ VectorCopy( maxs, box_brush->bounds[1] );
+
+ return BOX_MODEL_HANDLE;
+}
+
+/*
+===================
+CM_ModelBounds
+===================
+*/
+void CM_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) {
+ cmodel_t *cmod;
+
+ cmod = CM_ClipHandleToModel( model );
+ VectorCopy( cmod->mins, mins );
+ VectorCopy( cmod->maxs, maxs );
+}
+
+
diff --git a/src/qcommon/cm_local.h b/src/qcommon/cm_local.h
new file mode 100644
index 0000000..cc69482
--- /dev/null
+++ b/src/qcommon/cm_local.h
@@ -0,0 +1,214 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "q_shared.h"
+#include "qcommon.h"
+#include "cm_polylib.h"
+
+#define MAX_SUBMODELS 256
+#define BOX_MODEL_HANDLE 255
+#define CAPSULE_MODEL_HANDLE 254
+
+
+typedef struct {
+ cplane_t *plane;
+ int children[2]; // negative numbers are leafs
+} cNode_t;
+
+typedef struct {
+ int cluster;
+ int area;
+
+ int firstLeafBrush;
+ int numLeafBrushes;
+
+ int firstLeafSurface;
+ int numLeafSurfaces;
+} cLeaf_t;
+
+typedef struct cmodel_s {
+ vec3_t mins, maxs;
+ cLeaf_t leaf; // submodels don't reference the main tree
+} cmodel_t;
+
+typedef struct cbrushedge_s
+{
+ vec3_t p0;
+ vec3_t p1;
+} cbrushedge_t;
+
+typedef struct {
+ cplane_t *plane;
+ int planeNum;
+ int surfaceFlags;
+ int shaderNum;
+ winding_t *winding;
+} cbrushside_t;
+
+typedef struct {
+ int shaderNum; // the shader that determined the contents
+ int contents;
+ vec3_t bounds[2];
+ int numsides;
+ cbrushside_t *sides;
+ int checkcount; // to avoid repeated testings
+ qboolean collided; // marker for optimisation
+ cbrushedge_t *edges;
+ int numEdges;
+} cbrush_t;
+
+
+typedef struct {
+ int checkcount; // to avoid repeated testings
+ int surfaceFlags;
+ int contents;
+ struct patchCollide_s *pc;
+} cPatch_t;
+
+
+typedef struct {
+ int floodnum;
+ int floodvalid;
+} cArea_t;
+
+typedef struct {
+ char name[MAX_QPATH];
+
+ int numShaders;
+ dshader_t *shaders;
+
+ int numBrushSides;
+ cbrushside_t *brushsides;
+
+ int numPlanes;
+ cplane_t *planes;
+
+ int numNodes;
+ cNode_t *nodes;
+
+ int numLeafs;
+ cLeaf_t *leafs;
+
+ int numLeafBrushes;
+ int *leafbrushes;
+
+ int numLeafSurfaces;
+ int *leafsurfaces;
+
+ int numSubModels;
+ cmodel_t *cmodels;
+
+ int numBrushes;
+ cbrush_t *brushes;
+
+ int numClusters;
+ int clusterBytes;
+ byte *visibility;
+ qboolean vised; // if false, visibility is just a single cluster of ffs
+
+ int numEntityChars;
+ char *entityString;
+
+ int numAreas;
+ cArea_t *areas;
+ int *areaPortals; // [ numAreas*numAreas ] reference counts
+
+ int numSurfaces;
+ cPatch_t **surfaces; // non-patches will be NULL
+
+ int floodvalid;
+ int checkcount; // incremented on each trace
+} clipMap_t;
+
+
+// keep 1/8 unit away to keep the position valid before network snapping
+// and to avoid various numeric issues
+#define SURFACE_CLIP_EPSILON (0.125)
+
+extern clipMap_t cm;
+extern int c_pointcontents;
+extern int c_traces, c_brush_traces, c_patch_traces;
+extern cvar_t *cm_noAreas;
+extern cvar_t *cm_noCurves;
+extern cvar_t *cm_playerCurveClip;
+
+// cm_test.c
+
+typedef struct
+{
+ float startRadius;
+ float endRadius;
+} biSphere_t;
+
+// Used for oriented capsule collision detection
+typedef struct
+{
+ float radius;
+ float halfheight;
+ vec3_t offset;
+} sphere_t;
+
+typedef struct {
+ traceType_t type;
+ vec3_t start;
+ vec3_t end;
+ vec3_t size[2]; // size of the box being swept through the model
+ vec3_t offsets[8]; // [signbits][x] = either size[0][x] or size[1][x]
+ float maxOffset; // longest corner length from origin
+ vec3_t extents; // greatest of abs(size[0]) and abs(size[1])
+ vec3_t bounds[2]; // enclosing box of start and end surrounding by size
+ vec3_t modelOrigin;// origin of the model tracing through
+ int contents; // ored contents of the model tracing through
+ qboolean isPoint; // optimized case
+ trace_t trace; // returned from trace call
+ sphere_t sphere; // sphere for oriendted capsule collision
+ biSphere_t biSphere;
+ qboolean testLateralCollision; // whether or not to test for lateral collision
+} traceWork_t;
+
+typedef struct leafList_s {
+ int count;
+ int maxcount;
+ qboolean overflowed;
+ int *list;
+ vec3_t bounds[2];
+ int lastLeaf; // for overflows where each leaf can't be stored individually
+ void (*storeLeafs)( struct leafList_s *ll, int nodenum );
+} leafList_t;
+
+
+int CM_BoxBrushes( const vec3_t mins, const vec3_t maxs, cbrush_t **list, int listsize );
+
+void CM_StoreLeafs( leafList_t *ll, int nodenum );
+void CM_StoreBrushes( leafList_t *ll, int nodenum );
+
+void CM_BoxLeafnums_r( leafList_t *ll, int nodenum );
+
+cmodel_t *CM_ClipHandleToModel( clipHandle_t handle );
+
+// cm_patch.c
+
+struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points );
+void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc );
+qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc );
+void CM_ClearLevelPatches( void );
diff --git a/src/qcommon/cm_patch.c b/src/qcommon/cm_patch.c
new file mode 100644
index 0000000..f262db9
--- /dev/null
+++ b/src/qcommon/cm_patch.c
@@ -0,0 +1,1768 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "cm_local.h"
+#include "cm_patch.h"
+
+/*
+
+This file does not reference any globals, and has these entry points:
+
+void CM_ClearLevelPatches( void );
+struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, const vec3_t *points );
+void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc );
+qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc );
+void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, flaot *points) );
+
+
+WARNING: this may misbehave with meshes that have rows or columns that only
+degenerate a few triangles. Completely degenerate rows and columns are handled
+properly.
+*/
+
+/*
+#define MAX_FACETS 1024
+#define MAX_PATCH_PLANES 2048
+
+typedef struct {
+ float plane[4];
+ int signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision
+} patchPlane_t;
+
+typedef struct {
+ int surfacePlane;
+ int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels
+ int borderPlanes[4+6+16];
+ int borderInward[4+6+16];
+ qboolean borderNoAdjust[4+6+16];
+} facet_t;
+
+typedef struct patchCollide_s {
+ vec3_t bounds[2];
+ int numPlanes; // surface planes plus edge planes
+ patchPlane_t *planes;
+ int numFacets;
+ facet_t *facets;
+} patchCollide_t;
+
+
+#define MAX_GRID_SIZE 129
+
+typedef struct {
+ int width;
+ int height;
+ qboolean wrapWidth;
+ qboolean wrapHeight;
+ vec3_t points[MAX_GRID_SIZE][MAX_GRID_SIZE]; // [width][height]
+} cGrid_t;
+
+#define SUBDIVIDE_DISTANCE 16 //4 // never more than this units away from curve
+#define PLANE_TRI_EPSILON 0.1
+#define WRAP_POINT_EPSILON 0.1
+*/
+
+int c_totalPatchBlocks;
+int c_totalPatchSurfaces;
+int c_totalPatchEdges;
+
+static const patchCollide_t *debugPatchCollide;
+static const facet_t *debugFacet;
+static qboolean debugBlock;
+static vec3_t debugBlockPoints[4];
+
+/*
+=================
+CM_ClearLevelPatches
+=================
+*/
+void CM_ClearLevelPatches( void ) {
+ debugPatchCollide = NULL;
+ debugFacet = NULL;
+}
+
+/*
+=================
+CM_SignbitsForNormal
+=================
+*/
+static int CM_SignbitsForNormal( vec3_t normal ) {
+ int bits, j;
+
+ bits = 0;
+ for (j=0 ; j<3 ; j++) {
+ if ( normal[j] < 0 ) {
+ bits |= 1<<j;
+ }
+ }
+ return bits;
+}
+
+/*
+=====================
+CM_PlaneFromPoints
+
+Returns false if the triangle is degenrate.
+The normal will point out of the clock for clockwise ordered points
+=====================
+*/
+static qboolean CM_PlaneFromPoints( vec4_t plane, vec3_t a, vec3_t b, vec3_t c ) {
+ vec3_t d1, d2;
+
+ VectorSubtract( b, a, d1 );
+ VectorSubtract( c, a, d2 );
+ CrossProduct( d2, d1, plane );
+ if ( VectorNormalize( plane ) == 0 ) {
+ return qfalse;
+ }
+
+ plane[3] = DotProduct( a, plane );
+ return qtrue;
+}
+
+
+/*
+================================================================================
+
+GRID SUBDIVISION
+
+================================================================================
+*/
+
+/*
+=================
+CM_NeedsSubdivision
+
+Returns true if the given quadratic curve is not flat enough for our
+collision detection purposes
+=================
+*/
+static qboolean CM_NeedsSubdivision( vec3_t a, vec3_t b, vec3_t c ) {
+ vec3_t cmid;
+ vec3_t lmid;
+ vec3_t delta;
+ float dist;
+ int i;
+
+ // calculate the linear midpoint
+ for ( i = 0 ; i < 3 ; i++ ) {
+ lmid[i] = 0.5*(a[i] + c[i]);
+ }
+
+ // calculate the exact curve midpoint
+ for ( i = 0 ; i < 3 ; i++ ) {
+ cmid[i] = 0.5 * ( 0.5*(a[i] + b[i]) + 0.5*(b[i] + c[i]) );
+ }
+
+ // see if the curve is far enough away from the linear mid
+ VectorSubtract( cmid, lmid, delta );
+ dist = VectorLength( delta );
+
+ return dist >= SUBDIVIDE_DISTANCE;
+}
+
+/*
+===============
+CM_Subdivide
+
+a, b, and c are control points.
+the subdivided sequence will be: a, out1, out2, out3, c
+===============
+*/
+static void CM_Subdivide( vec3_t a, vec3_t b, vec3_t c, vec3_t out1, vec3_t out2, vec3_t out3 ) {
+ int i;
+
+ for ( i = 0 ; i < 3 ; i++ ) {
+ out1[i] = 0.5 * (a[i] + b[i]);
+ out3[i] = 0.5 * (b[i] + c[i]);
+ out2[i] = 0.5 * (out1[i] + out3[i]);
+ }
+}
+
+/*
+=================
+CM_TransposeGrid
+
+Swaps the rows and columns in place
+=================
+*/
+static void CM_TransposeGrid( cGrid_t *grid ) {
+ int i, j, l;
+ vec3_t temp;
+ qboolean tempWrap;
+
+ if ( grid->width > grid->height ) {
+ for ( i = 0 ; i < grid->height ; i++ ) {
+ for ( j = i + 1 ; j < grid->width ; j++ ) {
+ if ( j < grid->height ) {
+ // swap the value
+ VectorCopy( grid->points[i][j], temp );
+ VectorCopy( grid->points[j][i], grid->points[i][j] );
+ VectorCopy( temp, grid->points[j][i] );
+ } else {
+ // just copy
+ VectorCopy( grid->points[j][i], grid->points[i][j] );
+ }
+ }
+ }
+ } else {
+ for ( i = 0 ; i < grid->width ; i++ ) {
+ for ( j = i + 1 ; j < grid->height ; j++ ) {
+ if ( j < grid->width ) {
+ // swap the value
+ VectorCopy( grid->points[j][i], temp );
+ VectorCopy( grid->points[i][j], grid->points[j][i] );
+ VectorCopy( temp, grid->points[i][j] );
+ } else {
+ // just copy
+ VectorCopy( grid->points[i][j], grid->points[j][i] );
+ }
+ }
+ }
+ }
+
+ l = grid->width;
+ grid->width = grid->height;
+ grid->height = l;
+
+ tempWrap = grid->wrapWidth;
+ grid->wrapWidth = grid->wrapHeight;
+ grid->wrapHeight = tempWrap;
+}
+
+/*
+===================
+CM_SetGridWrapWidth
+
+If the left and right columns are exactly equal, set grid->wrapWidth qtrue
+===================
+*/
+static void CM_SetGridWrapWidth( cGrid_t *grid ) {
+ int i, j;
+ float d;
+
+ for ( i = 0 ; i < grid->height ; i++ ) {
+ for ( j = 0 ; j < 3 ; j++ ) {
+ d = grid->points[0][i][j] - grid->points[grid->width-1][i][j];
+ if ( d < -WRAP_POINT_EPSILON || d > WRAP_POINT_EPSILON ) {
+ break;
+ }
+ }
+ if ( j != 3 ) {
+ break;
+ }
+ }
+ if ( i == grid->height ) {
+ grid->wrapWidth = qtrue;
+ } else {
+ grid->wrapWidth = qfalse;
+ }
+}
+
+/*
+=================
+CM_SubdivideGridColumns
+
+Adds columns as necessary to the grid until
+all the aproximating points are within SUBDIVIDE_DISTANCE
+from the true curve
+=================
+*/
+static void CM_SubdivideGridColumns( cGrid_t *grid ) {
+ int i, j, k;
+
+ for ( i = 0 ; i < grid->width - 2 ; ) {
+ // grid->points[i][x] is an interpolating control point
+ // grid->points[i+1][x] is an aproximating control point
+ // grid->points[i+2][x] is an interpolating control point
+
+ //
+ // first see if we can collapse the aproximating collumn away
+ //
+ for ( j = 0 ; j < grid->height ; j++ ) {
+ if ( CM_NeedsSubdivision( grid->points[i][j], grid->points[i+1][j], grid->points[i+2][j] ) ) {
+ break;
+ }
+ }
+ if ( j == grid->height ) {
+ // all of the points were close enough to the linear midpoints
+ // that we can collapse the entire column away
+ for ( j = 0 ; j < grid->height ; j++ ) {
+ // remove the column
+ for ( k = i + 2 ; k < grid->width ; k++ ) {
+ VectorCopy( grid->points[k][j], grid->points[k-1][j] );
+ }
+ }
+
+ grid->width--;
+
+ // go to the next curve segment
+ i++;
+ continue;
+ }
+
+ //
+ // we need to subdivide the curve
+ //
+ for ( j = 0 ; j < grid->height ; j++ ) {
+ vec3_t prev, mid, next;
+
+ // save the control points now
+ VectorCopy( grid->points[i][j], prev );
+ VectorCopy( grid->points[i+1][j], mid );
+ VectorCopy( grid->points[i+2][j], next );
+
+ // make room for two additional columns in the grid
+ // columns i+1 will be replaced, column i+2 will become i+4
+ // i+1, i+2, and i+3 will be generated
+ for ( k = grid->width - 1 ; k > i + 1 ; k-- ) {
+ VectorCopy( grid->points[k][j], grid->points[k+2][j] );
+ }
+
+ // generate the subdivided points
+ CM_Subdivide( prev, mid, next, grid->points[i+1][j], grid->points[i+2][j], grid->points[i+3][j] );
+ }
+
+ grid->width += 2;
+
+ // the new aproximating point at i+1 may need to be removed
+ // or subdivided farther, so don't advance i
+ }
+}
+
+/*
+======================
+CM_ComparePoints
+======================
+*/
+#define POINT_EPSILON 0.1
+static qboolean CM_ComparePoints( float *a, float *b ) {
+ float d;
+
+ d = a[0] - b[0];
+ if ( d < -POINT_EPSILON || d > POINT_EPSILON ) {
+ return qfalse;
+ }
+ d = a[1] - b[1];
+ if ( d < -POINT_EPSILON || d > POINT_EPSILON ) {
+ return qfalse;
+ }
+ d = a[2] - b[2];
+ if ( d < -POINT_EPSILON || d > POINT_EPSILON ) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+/*
+=================
+CM_RemoveDegenerateColumns
+
+If there are any identical columns, remove them
+=================
+*/
+static void CM_RemoveDegenerateColumns( cGrid_t *grid ) {
+ int i, j, k;
+
+ for ( i = 0 ; i < grid->width - 1 ; i++ ) {
+ for ( j = 0 ; j < grid->height ; j++ ) {
+ if ( !CM_ComparePoints( grid->points[i][j], grid->points[i+1][j] ) ) {
+ break;
+ }
+ }
+
+ if ( j != grid->height ) {
+ continue; // not degenerate
+ }
+
+ for ( j = 0 ; j < grid->height ; j++ ) {
+ // remove the column
+ for ( k = i + 2 ; k < grid->width ; k++ ) {
+ VectorCopy( grid->points[k][j], grid->points[k-1][j] );
+ }
+ }
+ grid->width--;
+
+ // check against the next column
+ i--;
+ }
+}
+
+/*
+================================================================================
+
+PATCH COLLIDE GENERATION
+
+================================================================================
+*/
+
+static int numPlanes;
+static patchPlane_t planes[MAX_PATCH_PLANES];
+
+static int numFacets;
+static facet_t facets[MAX_PATCH_PLANES]; //maybe MAX_FACETS ??
+
+#define NORMAL_EPSILON 0.0001
+#define DIST_EPSILON 0.02
+
+/*
+==================
+CM_PlaneEqual
+==================
+*/
+int CM_PlaneEqual(patchPlane_t *p, float plane[4], int *flipped) {
+ float invplane[4];
+
+ if (
+ fabs(p->plane[0] - plane[0]) < NORMAL_EPSILON
+ && fabs(p->plane[1] - plane[1]) < NORMAL_EPSILON
+ && fabs(p->plane[2] - plane[2]) < NORMAL_EPSILON
+ && fabs(p->plane[3] - plane[3]) < DIST_EPSILON )
+ {
+ *flipped = qfalse;
+ return qtrue;
+ }
+
+ VectorNegate(plane, invplane);
+ invplane[3] = -plane[3];
+
+ if (
+ fabs(p->plane[0] - invplane[0]) < NORMAL_EPSILON
+ && fabs(p->plane[1] - invplane[1]) < NORMAL_EPSILON
+ && fabs(p->plane[2] - invplane[2]) < NORMAL_EPSILON
+ && fabs(p->plane[3] - invplane[3]) < DIST_EPSILON )
+ {
+ *flipped = qtrue;
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+/*
+==================
+CM_SnapVector
+==================
+*/
+void CM_SnapVector(vec3_t normal) {
+ int i;
+
+ for (i=0 ; i<3 ; i++)
+ {
+ if ( fabs(normal[i] - 1) < NORMAL_EPSILON )
+ {
+ VectorClear (normal);
+ normal[i] = 1;
+ break;
+ }
+ if ( fabs(normal[i] - -1) < NORMAL_EPSILON )
+ {
+ VectorClear (normal);
+ normal[i] = -1;
+ break;
+ }
+ }
+}
+
+/*
+==================
+CM_FindPlane2
+==================
+*/
+int CM_FindPlane2(float plane[4], int *flipped) {
+ int i;
+
+ // see if the points are close enough to an existing plane
+ for ( i = 0 ; i < numPlanes ; i++ ) {
+ if (CM_PlaneEqual(&planes[i], plane, flipped)) return i;
+ }
+
+ // add a new plane
+ if ( numPlanes == MAX_PATCH_PLANES ) {
+ Com_Error( ERR_DROP, "MAX_PATCH_PLANES" );
+ }
+
+ Vector4Copy( plane, planes[numPlanes].plane );
+ planes[numPlanes].signbits = CM_SignbitsForNormal( plane );
+
+ numPlanes++;
+
+ *flipped = qfalse;
+
+ return numPlanes-1;
+}
+
+/*
+==================
+CM_FindPlane
+==================
+*/
+static int CM_FindPlane( float *p1, float *p2, float *p3 ) {
+ float plane[4];
+ int i;
+ float d;
+
+ if ( !CM_PlaneFromPoints( plane, p1, p2, p3 ) ) {
+ return -1;
+ }
+
+ // see if the points are close enough to an existing plane
+ for ( i = 0 ; i < numPlanes ; i++ ) {
+ if ( DotProduct( plane, planes[i].plane ) < 0 ) {
+ continue; // allow backwards planes?
+ }
+
+ d = DotProduct( p1, planes[i].plane ) - planes[i].plane[3];
+ if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) {
+ continue;
+ }
+
+ d = DotProduct( p2, planes[i].plane ) - planes[i].plane[3];
+ if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) {
+ continue;
+ }
+
+ d = DotProduct( p3, planes[i].plane ) - planes[i].plane[3];
+ if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) {
+ continue;
+ }
+
+ // found it
+ return i;
+ }
+
+ // add a new plane
+ if ( numPlanes == MAX_PATCH_PLANES ) {
+ Com_Error( ERR_DROP, "MAX_PATCH_PLANES" );
+ }
+
+ Vector4Copy( plane, planes[numPlanes].plane );
+ planes[numPlanes].signbits = CM_SignbitsForNormal( plane );
+
+ numPlanes++;
+
+ return numPlanes-1;
+}
+
+/*
+==================
+CM_PointOnPlaneSide
+==================
+*/
+static int CM_PointOnPlaneSide( float *p, int planeNum ) {
+ float *plane;
+ float d;
+
+ if ( planeNum == -1 ) {
+ return SIDE_ON;
+ }
+ plane = planes[ planeNum ].plane;
+
+ d = DotProduct( p, plane ) - plane[3];
+
+ if ( d > PLANE_TRI_EPSILON ) {
+ return SIDE_FRONT;
+ }
+
+ if ( d < -PLANE_TRI_EPSILON ) {
+ return SIDE_BACK;
+ }
+
+ return SIDE_ON;
+}
+
+/*
+==================
+CM_GridPlane
+==================
+*/
+static int CM_GridPlane( int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2], int i, int j, int tri ) {
+ int p;
+
+ p = gridPlanes[i][j][tri];
+ if ( p != -1 ) {
+ return p;
+ }
+ p = gridPlanes[i][j][!tri];
+ if ( p != -1 ) {
+ return p;
+ }
+
+ // should never happen
+ Com_Printf( "WARNING: CM_GridPlane unresolvable\n" );
+ return -1;
+}
+
+/*
+==================
+CM_EdgePlaneNum
+==================
+*/
+static int CM_EdgePlaneNum( cGrid_t *grid, int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2], int i, int j, int k ) {
+ float *p1, *p2;
+ vec3_t up;
+ int p;
+
+ switch ( k ) {
+ case 0: // top border
+ p1 = grid->points[i][j];
+ p2 = grid->points[i+1][j];
+ p = CM_GridPlane( gridPlanes, i, j, 0 );
+ VectorMA( p1, 4, planes[ p ].plane, up );
+ return CM_FindPlane( p1, p2, up );
+
+ case 2: // bottom border
+ p1 = grid->points[i][j+1];
+ p2 = grid->points[i+1][j+1];
+ p = CM_GridPlane( gridPlanes, i, j, 1 );
+ VectorMA( p1, 4, planes[ p ].plane, up );
+ return CM_FindPlane( p2, p1, up );
+
+ case 3: // left border
+ p1 = grid->points[i][j];
+ p2 = grid->points[i][j+1];
+ p = CM_GridPlane( gridPlanes, i, j, 1 );
+ VectorMA( p1, 4, planes[ p ].plane, up );
+ return CM_FindPlane( p2, p1, up );
+
+ case 1: // right border
+ p1 = grid->points[i+1][j];
+ p2 = grid->points[i+1][j+1];
+ p = CM_GridPlane( gridPlanes, i, j, 0 );
+ VectorMA( p1, 4, planes[ p ].plane, up );
+ return CM_FindPlane( p1, p2, up );
+
+ case 4: // diagonal out of triangle 0
+ p1 = grid->points[i+1][j+1];
+ p2 = grid->points[i][j];
+ p = CM_GridPlane( gridPlanes, i, j, 0 );
+ VectorMA( p1, 4, planes[ p ].plane, up );
+ return CM_FindPlane( p1, p2, up );
+
+ case 5: // diagonal out of triangle 1
+ p1 = grid->points[i][j];
+ p2 = grid->points[i+1][j+1];
+ p = CM_GridPlane( gridPlanes, i, j, 1 );
+ VectorMA( p1, 4, planes[ p ].plane, up );
+ return CM_FindPlane( p1, p2, up );
+
+ }
+
+ Com_Error( ERR_DROP, "CM_EdgePlaneNum: bad k" );
+ return -1;
+}
+
+/*
+===================
+CM_SetBorderInward
+===================
+*/
+static void CM_SetBorderInward( facet_t *facet, cGrid_t *grid, int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2],
+ int i, int j, int which ) {
+ int k, l;
+ float *points[4];
+ int numPoints;
+
+ switch ( which ) {
+ case -1:
+ points[0] = grid->points[i][j];
+ points[1] = grid->points[i+1][j];
+ points[2] = grid->points[i+1][j+1];
+ points[3] = grid->points[i][j+1];
+ numPoints = 4;
+ break;
+ case 0:
+ points[0] = grid->points[i][j];
+ points[1] = grid->points[i+1][j];
+ points[2] = grid->points[i+1][j+1];
+ numPoints = 3;
+ break;
+ case 1:
+ points[0] = grid->points[i+1][j+1];
+ points[1] = grid->points[i][j+1];
+ points[2] = grid->points[i][j];
+ numPoints = 3;
+ break;
+ default:
+ Com_Error( ERR_FATAL, "CM_SetBorderInward: bad parameter" );
+ numPoints = 0;
+ break;
+ }
+
+ for ( k = 0 ; k < facet->numBorders ; k++ ) {
+ int front, back;
+
+ front = 0;
+ back = 0;
+
+ for ( l = 0 ; l < numPoints ; l++ ) {
+ int side;
+
+ side = CM_PointOnPlaneSide( points[l], facet->borderPlanes[k] );
+ if ( side == SIDE_FRONT ) {
+ front++;
+ } if ( side == SIDE_BACK ) {
+ back++;
+ }
+ }
+
+ if ( front && !back ) {
+ facet->borderInward[k] = qtrue;
+ } else if ( back && !front ) {
+ facet->borderInward[k] = qfalse;
+ } else if ( !front && !back ) {
+ // flat side border
+ facet->borderPlanes[k] = -1;
+ } else {
+ // bisecting side border
+ Com_DPrintf( "WARNING: CM_SetBorderInward: mixed plane sides\n" );
+ facet->borderInward[k] = qfalse;
+ if ( !debugBlock ) {
+ debugBlock = qtrue;
+ VectorCopy( grid->points[i][j], debugBlockPoints[0] );
+ VectorCopy( grid->points[i+1][j], debugBlockPoints[1] );
+ VectorCopy( grid->points[i+1][j+1], debugBlockPoints[2] );
+ VectorCopy( grid->points[i][j+1], debugBlockPoints[3] );
+ }
+ }
+ }
+}
+
+/*
+==================
+CM_ValidateFacet
+
+If the facet isn't bounded by its borders, we screwed up.
+==================
+*/
+static qboolean CM_ValidateFacet( facet_t *facet ) {
+ float plane[4];
+ int j;
+ winding_t *w;
+ vec3_t bounds[2];
+
+ if ( facet->surfacePlane == -1 ) {
+ return qfalse;
+ }
+
+ Vector4Copy( planes[ facet->surfacePlane ].plane, plane );
+ w = BaseWindingForPlane( plane, plane[3] );
+ for ( j = 0 ; j < facet->numBorders && w ; j++ ) {
+ if ( facet->borderPlanes[j] == -1 ) {
+ FreeWinding( w );
+ return qfalse;
+ }
+ Vector4Copy( planes[ facet->borderPlanes[j] ].plane, plane );
+ if ( !facet->borderInward[j] ) {
+ VectorSubtract( vec3_origin, plane, plane );
+ plane[3] = -plane[3];
+ }
+ ChopWindingInPlace( &w, plane, plane[3], 0.1f );
+ }
+
+ if ( !w ) {
+ return qfalse; // winding was completely chopped away
+ }
+
+ // see if the facet is unreasonably large
+ WindingBounds( w, bounds[0], bounds[1] );
+ FreeWinding( w );
+
+ for ( j = 0 ; j < 3 ; j++ ) {
+ if ( bounds[1][j] - bounds[0][j] > MAX_MAP_BOUNDS ) {
+ return qfalse; // we must be missing a plane
+ }
+ if ( bounds[0][j] >= MAX_MAP_BOUNDS ) {
+ return qfalse;
+ }
+ if ( bounds[1][j] <= -MAX_MAP_BOUNDS ) {
+ return qfalse;
+ }
+ }
+ return qtrue; // winding is fine
+}
+
+/*
+==================
+CM_AddFacetBevels
+==================
+*/
+void CM_AddFacetBevels( facet_t *facet ) {
+
+ int i, j, k, l;
+ int axis, dir, order, flipped;
+ float plane[4], d, newplane[4];
+ winding_t *w, *w2;
+ vec3_t mins, maxs, vec, vec2;
+
+ Vector4Copy( planes[ facet->surfacePlane ].plane, plane );
+
+ w = BaseWindingForPlane( plane, plane[3] );
+ for ( j = 0 ; j < facet->numBorders && w ; j++ ) {
+ if (facet->borderPlanes[j] == facet->surfacePlane) continue;
+ Vector4Copy( planes[ facet->borderPlanes[j] ].plane, plane );
+
+ if ( !facet->borderInward[j] ) {
+ VectorSubtract( vec3_origin, plane, plane );
+ plane[3] = -plane[3];
+ }
+
+ ChopWindingInPlace( &w, plane, plane[3], 0.1f );
+ }
+ if ( !w ) {
+ return;
+ }
+
+ WindingBounds(w, mins, maxs);
+
+ // add the axial planes
+ order = 0;
+ for ( axis = 0 ; axis < 3 ; axis++ )
+ {
+ for ( dir = -1 ; dir <= 1 ; dir += 2, order++ )
+ {
+ VectorClear(plane);
+ plane[axis] = dir;
+ if (dir == 1) {
+ plane[3] = maxs[axis];
+ }
+ else {
+ plane[3] = -mins[axis];
+ }
+ //if it's the surface plane
+ if (CM_PlaneEqual(&planes[facet->surfacePlane], plane, &flipped)) {
+ continue;
+ }
+ // see if the plane is allready present
+ for ( i = 0 ; i < facet->numBorders ; i++ ) {
+ if (CM_PlaneEqual(&planes[facet->borderPlanes[i]], plane, &flipped))
+ break;
+ }
+
+ if ( i == facet->numBorders ) {
+ if (facet->numBorders > 4 + 6 + 16) Com_Printf("ERROR: too many bevels\n");
+ facet->borderPlanes[facet->numBorders] = CM_FindPlane2(plane, &flipped);
+ facet->borderNoAdjust[facet->numBorders] = 0;
+ facet->borderInward[facet->numBorders] = flipped;
+ facet->numBorders++;
+ }
+ }
+ }
+ //
+ // add the edge bevels
+ //
+ // test the non-axial plane edges
+ for ( j = 0 ; j < w->numpoints ; j++ )
+ {
+ k = (j+1)%w->numpoints;
+ VectorSubtract (w->p[j], w->p[k], vec);
+ //if it's a degenerate edge
+ if (VectorNormalize (vec) < 0.5)
+ continue;
+ CM_SnapVector(vec);
+ for ( k = 0; k < 3 ; k++ )
+ if ( vec[k] == -1 || vec[k] == 1 )
+ break; // axial
+ if ( k < 3 )
+ continue; // only test non-axial edges
+
+ // try the six possible slanted axials from this edge
+ for ( axis = 0 ; axis < 3 ; axis++ )
+ {
+ for ( dir = -1 ; dir <= 1 ; dir += 2 )
+ {
+ // construct a plane
+ VectorClear (vec2);
+ vec2[axis] = dir;
+ CrossProduct (vec, vec2, plane);
+ if (VectorNormalize (plane) < 0.5)
+ continue;
+ plane[3] = DotProduct (w->p[j], plane);
+
+ // if all the points of the facet winding are
+ // behind this plane, it is a proper edge bevel
+ for ( l = 0 ; l < w->numpoints ; l++ )
+ {
+ d = DotProduct (w->p[l], plane) - plane[3];
+ if (d > 0.1)
+ break; // point in front
+ }
+ if ( l < w->numpoints )
+ continue;
+
+ //if it's the surface plane
+ if (CM_PlaneEqual(&planes[facet->surfacePlane], plane, &flipped)) {
+ continue;
+ }
+ // see if the plane is allready present
+ for ( i = 0 ; i < facet->numBorders ; i++ ) {
+ if (CM_PlaneEqual(&planes[facet->borderPlanes[i]], plane, &flipped)) {
+ break;
+ }
+ }
+
+ if ( i == facet->numBorders ) {
+ if (facet->numBorders > 4 + 6 + 16) Com_Printf("ERROR: too many bevels\n");
+ facet->borderPlanes[facet->numBorders] = CM_FindPlane2(plane, &flipped);
+
+ for ( k = 0 ; k < facet->numBorders ; k++ ) {
+ if (facet->borderPlanes[facet->numBorders] ==
+ facet->borderPlanes[k]) Com_Printf("WARNING: bevel plane already used\n");
+ }
+
+ facet->borderNoAdjust[facet->numBorders] = 0;
+ facet->borderInward[facet->numBorders] = flipped;
+ //
+ w2 = CopyWinding(w);
+ Vector4Copy(planes[facet->borderPlanes[facet->numBorders]].plane, newplane);
+ if (!facet->borderInward[facet->numBorders])
+ {
+ VectorNegate(newplane, newplane);
+ newplane[3] = -newplane[3];
+ } //end if
+ ChopWindingInPlace( &w2, newplane, newplane[3], 0.1f );
+ if (!w2) {
+ Com_DPrintf("WARNING: CM_AddFacetBevels... invalid bevel\n");
+ continue;
+ }
+ else {
+ FreeWinding(w2);
+ }
+ //
+ facet->numBorders++;
+ //already got a bevel
+// break;
+ }
+ }
+ }
+ }
+ FreeWinding( w );
+
+#ifndef BSPC
+ //add opposite plane
+ facet->borderPlanes[facet->numBorders] = facet->surfacePlane;
+ facet->borderNoAdjust[facet->numBorders] = 0;
+ facet->borderInward[facet->numBorders] = qtrue;
+ facet->numBorders++;
+#endif //BSPC
+
+}
+
+typedef enum {
+ EN_TOP,
+ EN_RIGHT,
+ EN_BOTTOM,
+ EN_LEFT
+} edgeName_t;
+
+/*
+==================
+CM_PatchCollideFromGrid
+==================
+*/
+static void CM_PatchCollideFromGrid( cGrid_t *grid, patchCollide_t *pf ) {
+ int i, j;
+ float *p1, *p2, *p3;
+ int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2];
+ facet_t *facet;
+ int borders[4];
+ int noAdjust[4];
+
+ numPlanes = 0;
+ numFacets = 0;
+
+ // find the planes for each triangle of the grid
+ for ( i = 0 ; i < grid->width - 1 ; i++ ) {
+ for ( j = 0 ; j < grid->height - 1 ; j++ ) {
+ p1 = grid->points[i][j];
+ p2 = grid->points[i+1][j];
+ p3 = grid->points[i+1][j+1];
+ gridPlanes[i][j][0] = CM_FindPlane( p1, p2, p3 );
+
+ p1 = grid->points[i+1][j+1];
+ p2 = grid->points[i][j+1];
+ p3 = grid->points[i][j];
+ gridPlanes[i][j][1] = CM_FindPlane( p1, p2, p3 );
+ }
+ }
+
+ // create the borders for each facet
+ for ( i = 0 ; i < grid->width - 1 ; i++ ) {
+ for ( j = 0 ; j < grid->height - 1 ; j++ ) {
+
+ borders[EN_TOP] = -1;
+ if ( j > 0 ) {
+ borders[EN_TOP] = gridPlanes[i][j-1][1];
+ } else if ( grid->wrapHeight ) {
+ borders[EN_TOP] = gridPlanes[i][grid->height-2][1];
+ }
+ noAdjust[EN_TOP] = ( borders[EN_TOP] == gridPlanes[i][j][0] );
+ if ( borders[EN_TOP] == -1 || noAdjust[EN_TOP] ) {
+ borders[EN_TOP] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 0 );
+ }
+
+ borders[EN_BOTTOM] = -1;
+ if ( j < grid->height - 2 ) {
+ borders[EN_BOTTOM] = gridPlanes[i][j+1][0];
+ } else if ( grid->wrapHeight ) {
+ borders[EN_BOTTOM] = gridPlanes[i][0][0];
+ }
+ noAdjust[EN_BOTTOM] = ( borders[EN_BOTTOM] == gridPlanes[i][j][1] );
+ if ( borders[EN_BOTTOM] == -1 || noAdjust[EN_BOTTOM] ) {
+ borders[EN_BOTTOM] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 2 );
+ }
+
+ borders[EN_LEFT] = -1;
+ if ( i > 0 ) {
+ borders[EN_LEFT] = gridPlanes[i-1][j][0];
+ } else if ( grid->wrapWidth ) {
+ borders[EN_LEFT] = gridPlanes[grid->width-2][j][0];
+ }
+ noAdjust[EN_LEFT] = ( borders[EN_LEFT] == gridPlanes[i][j][1] );
+ if ( borders[EN_LEFT] == -1 || noAdjust[EN_LEFT] ) {
+ borders[EN_LEFT] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 3 );
+ }
+
+ borders[EN_RIGHT] = -1;
+ if ( i < grid->width - 2 ) {
+ borders[EN_RIGHT] = gridPlanes[i+1][j][1];
+ } else if ( grid->wrapWidth ) {
+ borders[EN_RIGHT] = gridPlanes[0][j][1];
+ }
+ noAdjust[EN_RIGHT] = ( borders[EN_RIGHT] == gridPlanes[i][j][0] );
+ if ( borders[EN_RIGHT] == -1 || noAdjust[EN_RIGHT] ) {
+ borders[EN_RIGHT] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 1 );
+ }
+
+ if ( numFacets == MAX_FACETS ) {
+ Com_Error( ERR_DROP, "MAX_FACETS" );
+ }
+ facet = &facets[numFacets];
+ Com_Memset( facet, 0, sizeof( *facet ) );
+
+ if ( gridPlanes[i][j][0] == gridPlanes[i][j][1] ) {
+ if ( gridPlanes[i][j][0] == -1 ) {
+ continue; // degenrate
+ }
+ facet->surfacePlane = gridPlanes[i][j][0];
+ facet->numBorders = 4;
+ facet->borderPlanes[0] = borders[EN_TOP];
+ facet->borderNoAdjust[0] = noAdjust[EN_TOP];
+ facet->borderPlanes[1] = borders[EN_RIGHT];
+ facet->borderNoAdjust[1] = noAdjust[EN_RIGHT];
+ facet->borderPlanes[2] = borders[EN_BOTTOM];
+ facet->borderNoAdjust[2] = noAdjust[EN_BOTTOM];
+ facet->borderPlanes[3] = borders[EN_LEFT];
+ facet->borderNoAdjust[3] = noAdjust[EN_LEFT];
+ CM_SetBorderInward( facet, grid, gridPlanes, i, j, -1 );
+ if ( CM_ValidateFacet( facet ) ) {
+ CM_AddFacetBevels( facet );
+ numFacets++;
+ }
+ } else {
+ // two seperate triangles
+ facet->surfacePlane = gridPlanes[i][j][0];
+ facet->numBorders = 3;
+ facet->borderPlanes[0] = borders[EN_TOP];
+ facet->borderNoAdjust[0] = noAdjust[EN_TOP];
+ facet->borderPlanes[1] = borders[EN_RIGHT];
+ facet->borderNoAdjust[1] = noAdjust[EN_RIGHT];
+ facet->borderPlanes[2] = gridPlanes[i][j][1];
+ if ( facet->borderPlanes[2] == -1 ) {
+ facet->borderPlanes[2] = borders[EN_BOTTOM];
+ if ( facet->borderPlanes[2] == -1 ) {
+ facet->borderPlanes[2] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 4 );
+ }
+ }
+ CM_SetBorderInward( facet, grid, gridPlanes, i, j, 0 );
+ if ( CM_ValidateFacet( facet ) ) {
+ CM_AddFacetBevels( facet );
+ numFacets++;
+ }
+
+ if ( numFacets == MAX_FACETS ) {
+ Com_Error( ERR_DROP, "MAX_FACETS" );
+ }
+ facet = &facets[numFacets];
+ Com_Memset( facet, 0, sizeof( *facet ) );
+
+ facet->surfacePlane = gridPlanes[i][j][1];
+ facet->numBorders = 3;
+ facet->borderPlanes[0] = borders[EN_BOTTOM];
+ facet->borderNoAdjust[0] = noAdjust[EN_BOTTOM];
+ facet->borderPlanes[1] = borders[EN_LEFT];
+ facet->borderNoAdjust[1] = noAdjust[EN_LEFT];
+ facet->borderPlanes[2] = gridPlanes[i][j][0];
+ if ( facet->borderPlanes[2] == -1 ) {
+ facet->borderPlanes[2] = borders[EN_TOP];
+ if ( facet->borderPlanes[2] == -1 ) {
+ facet->borderPlanes[2] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 5 );
+ }
+ }
+ CM_SetBorderInward( facet, grid, gridPlanes, i, j, 1 );
+ if ( CM_ValidateFacet( facet ) ) {
+ CM_AddFacetBevels( facet );
+ numFacets++;
+ }
+ }
+ }
+ }
+
+ // copy the results out
+ pf->numPlanes = numPlanes;
+ pf->numFacets = numFacets;
+ pf->facets = Hunk_Alloc( numFacets * sizeof( *pf->facets ), h_high );
+ Com_Memcpy( pf->facets, facets, numFacets * sizeof( *pf->facets ) );
+ pf->planes = Hunk_Alloc( numPlanes * sizeof( *pf->planes ), h_high );
+ Com_Memcpy( pf->planes, planes, numPlanes * sizeof( *pf->planes ) );
+}
+
+
+/*
+===================
+CM_GeneratePatchCollide
+
+Creates an internal structure that will be used to perform
+collision detection with a patch mesh.
+
+Points is packed as concatenated rows.
+===================
+*/
+struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points ) {
+ patchCollide_t *pf;
+ cGrid_t grid;
+ int i, j;
+
+ if ( width <= 2 || height <= 2 || !points ) {
+ Com_Error( ERR_DROP, "CM_GeneratePatchFacets: bad parameters: (%i, %i, %p)",
+ width, height, points );
+ }
+
+ if ( !(width & 1) || !(height & 1) ) {
+ Com_Error( ERR_DROP, "CM_GeneratePatchFacets: even sizes are invalid for quadratic meshes" );
+ }
+
+ if ( width > MAX_GRID_SIZE || height > MAX_GRID_SIZE ) {
+ Com_Error( ERR_DROP, "CM_GeneratePatchFacets: source is > MAX_GRID_SIZE" );
+ }
+
+ // build a grid
+ grid.width = width;
+ grid.height = height;
+ grid.wrapWidth = qfalse;
+ grid.wrapHeight = qfalse;
+ for ( i = 0 ; i < width ; i++ ) {
+ for ( j = 0 ; j < height ; j++ ) {
+ VectorCopy( points[j*width + i], grid.points[i][j] );
+ }
+ }
+
+ // subdivide the grid
+ CM_SetGridWrapWidth( &grid );
+ CM_SubdivideGridColumns( &grid );
+ CM_RemoveDegenerateColumns( &grid );
+
+ CM_TransposeGrid( &grid );
+
+ CM_SetGridWrapWidth( &grid );
+ CM_SubdivideGridColumns( &grid );
+ CM_RemoveDegenerateColumns( &grid );
+
+ // we now have a grid of points exactly on the curve
+ // the aproximate surface defined by these points will be
+ // collided against
+ pf = Hunk_Alloc( sizeof( *pf ), h_high );
+ ClearBounds( pf->bounds[0], pf->bounds[1] );
+ for ( i = 0 ; i < grid.width ; i++ ) {
+ for ( j = 0 ; j < grid.height ; j++ ) {
+ AddPointToBounds( grid.points[i][j], pf->bounds[0], pf->bounds[1] );
+ }
+ }
+
+ c_totalPatchBlocks += ( grid.width - 1 ) * ( grid.height - 1 );
+
+ // generate a bsp tree for the surface
+ CM_PatchCollideFromGrid( &grid, pf );
+
+ // expand by one unit for epsilon purposes
+ pf->bounds[0][0] -= 1;
+ pf->bounds[0][1] -= 1;
+ pf->bounds[0][2] -= 1;
+
+ pf->bounds[1][0] += 1;
+ pf->bounds[1][1] += 1;
+ pf->bounds[1][2] += 1;
+
+ return pf;
+}
+
+/*
+================================================================================
+
+TRACE TESTING
+
+================================================================================
+*/
+
+/*
+====================
+CM_TracePointThroughPatchCollide
+
+ special case for point traces because the patch collide "brushes" have no volume
+====================
+*/
+void CM_TracePointThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) {
+ qboolean frontFacing[MAX_PATCH_PLANES];
+ float intersection[MAX_PATCH_PLANES];
+ float intersect;
+ const patchPlane_t *planes;
+ const facet_t *facet;
+ int i, j, k;
+ float offset;
+ float d1, d2;
+#ifndef BSPC
+ static cvar_t *cv;
+#endif //BSPC
+
+#ifndef BSPC
+ if ( !cm_playerCurveClip->integer || !tw->isPoint ) {
+ return;
+ }
+#endif
+
+ // determine the trace's relationship to all planes
+ planes = pc->planes;
+ for ( i = 0 ; i < pc->numPlanes ; i++, planes++ ) {
+ offset = DotProduct( tw->offsets[ planes->signbits ], planes->plane );
+ d1 = DotProduct( tw->start, planes->plane ) - planes->plane[3] + offset;
+ d2 = DotProduct( tw->end, planes->plane ) - planes->plane[3] + offset;
+ if ( d1 <= 0 ) {
+ frontFacing[i] = qfalse;
+ } else {
+ frontFacing[i] = qtrue;
+ }
+ if ( d1 == d2 ) {
+ intersection[i] = 99999;
+ } else {
+ intersection[i] = d1 / ( d1 - d2 );
+ if ( intersection[i] <= 0 ) {
+ intersection[i] = 99999;
+ }
+ }
+ }
+
+
+ // see if any of the surface planes are intersected
+ facet = pc->facets;
+ for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) {
+ if ( !frontFacing[facet->surfacePlane] ) {
+ continue;
+ }
+ intersect = intersection[facet->surfacePlane];
+ if ( intersect < 0 ) {
+ continue; // surface is behind the starting point
+ }
+ if ( intersect > tw->trace.fraction ) {
+ continue; // already hit something closer
+ }
+ for ( j = 0 ; j < facet->numBorders ; j++ ) {
+ k = facet->borderPlanes[j];
+ if ( frontFacing[k] ^ facet->borderInward[j] ) {
+ if ( intersection[k] > intersect ) {
+ break;
+ }
+ } else {
+ if ( intersection[k] < intersect ) {
+ break;
+ }
+ }
+ }
+ if ( j == facet->numBorders ) {
+ // we hit this facet
+#ifndef BSPC
+ if (!cv) {
+ cv = Cvar_Get( "r_debugSurfaceUpdate", "1", 0 );
+ }
+ if (cv->integer) {
+ debugPatchCollide = pc;
+ debugFacet = facet;
+ }
+#endif //BSPC
+ planes = &pc->planes[facet->surfacePlane];
+
+ // calculate intersection with a slight pushoff
+ offset = DotProduct( tw->offsets[ planes->signbits ], planes->plane );
+ d1 = DotProduct( tw->start, planes->plane ) - planes->plane[3] + offset;
+ d2 = DotProduct( tw->end, planes->plane ) - planes->plane[3] + offset;
+ tw->trace.fraction = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 );
+
+ if ( tw->trace.fraction < 0 ) {
+ tw->trace.fraction = 0;
+ }
+
+ VectorCopy( planes->plane, tw->trace.plane.normal );
+ tw->trace.plane.dist = planes->plane[3];
+ }
+ }
+}
+
+/*
+====================
+CM_CheckFacetPlane
+====================
+*/
+int CM_CheckFacetPlane(float *plane, vec3_t start, vec3_t end, float *enterFrac, float *leaveFrac, int *hit) {
+ float d1, d2, f;
+
+ *hit = qfalse;
+
+ d1 = DotProduct( start, plane ) - plane[3];
+ d2 = DotProduct( end, plane ) - plane[3];
+
+ // if completely in front of face, no intersection with the entire facet
+ if (d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) {
+ return qfalse;
+ }
+
+ // if it doesn't cross the plane, the plane isn't relevent
+ if (d1 <= 0 && d2 <= 0 ) {
+ return qtrue;
+ }
+
+ // crosses face
+ if (d1 > d2) { // enter
+ f = (d1-SURFACE_CLIP_EPSILON) / (d1-d2);
+ if ( f < 0 ) {
+ f = 0;
+ }
+ //always favor previous plane hits and thus also the surface plane hit
+ if (f > *enterFrac) {
+ *enterFrac = f;
+ *hit = qtrue;
+ }
+ } else { // leave
+ f = (d1+SURFACE_CLIP_EPSILON) / (d1-d2);
+ if ( f > 1 ) {
+ f = 1;
+ }
+ if (f < *leaveFrac) {
+ *leaveFrac = f;
+ }
+ }
+ return qtrue;
+}
+
+/*
+====================
+CM_TraceThroughPatchCollide
+====================
+*/
+void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) {
+ int i, j, hit, hitnum;
+ float offset, enterFrac, leaveFrac, t;
+ patchPlane_t *planes;
+ facet_t *facet;
+ float plane[4] = {0, 0, 0, 0}, bestplane[4] = {0, 0, 0, 0};
+ vec3_t startp, endp;
+#ifndef BSPC
+ static cvar_t *cv;
+#endif //BSPC
+
+ if (tw->isPoint) {
+ CM_TracePointThroughPatchCollide( tw, pc );
+ return;
+ }
+
+ facet = pc->facets;
+ for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) {
+ enterFrac = -1.0;
+ leaveFrac = 1.0;
+ hitnum = -1;
+ //
+ planes = &pc->planes[ facet->surfacePlane ];
+ VectorCopy(planes->plane, plane);
+ plane[3] = planes->plane[3];
+ if ( tw->type == TT_CAPSULE ) {
+ // adjust the plane distance apropriately for radius
+ plane[3] += tw->sphere.radius;
+
+ // find the closest point on the capsule to the plane
+ t = DotProduct( plane, tw->sphere.offset );
+ if ( t > 0.0f ) {
+ VectorSubtract( tw->start, tw->sphere.offset, startp );
+ VectorSubtract( tw->end, tw->sphere.offset, endp );
+ }
+ else {
+ VectorAdd( tw->start, tw->sphere.offset, startp );
+ VectorAdd( tw->end, tw->sphere.offset, endp );
+ }
+ }
+ else {
+ offset = DotProduct( tw->offsets[ planes->signbits ], plane);
+ plane[3] -= offset;
+ VectorCopy( tw->start, startp );
+ VectorCopy( tw->end, endp );
+ }
+
+ if (!CM_CheckFacetPlane(plane, startp, endp, &enterFrac, &leaveFrac, &hit)) {
+ continue;
+ }
+ if (hit) {
+ Vector4Copy(plane, bestplane);
+ }
+
+ for ( j = 0; j < facet->numBorders; j++ ) {
+ planes = &pc->planes[ facet->borderPlanes[j] ];
+ if (facet->borderInward[j]) {
+ VectorNegate(planes->plane, plane);
+ plane[3] = -planes->plane[3];
+ }
+ else {
+ VectorCopy(planes->plane, plane);
+ plane[3] = planes->plane[3];
+ }
+ if ( tw->type == TT_CAPSULE ) {
+ // adjust the plane distance apropriately for radius
+ plane[3] += tw->sphere.radius;
+
+ // find the closest point on the capsule to the plane
+ t = DotProduct( plane, tw->sphere.offset );
+ if ( t > 0.0f ) {
+ VectorSubtract( tw->start, tw->sphere.offset, startp );
+ VectorSubtract( tw->end, tw->sphere.offset, endp );
+ }
+ else {
+ VectorAdd( tw->start, tw->sphere.offset, startp );
+ VectorAdd( tw->end, tw->sphere.offset, endp );
+ }
+ }
+ else {
+ // NOTE: this works even though the plane might be flipped because the bbox is centered
+ offset = DotProduct( tw->offsets[ planes->signbits ], plane);
+ plane[3] += fabs(offset);
+ VectorCopy( tw->start, startp );
+ VectorCopy( tw->end, endp );
+ }
+
+ if (!CM_CheckFacetPlane(plane, startp, endp, &enterFrac, &leaveFrac, &hit)) {
+ break;
+ }
+ if (hit) {
+ hitnum = j;
+ Vector4Copy(plane, bestplane);
+ }
+ }
+ if (j < facet->numBorders) continue;
+ //never clip against the back side
+ if (hitnum == facet->numBorders - 1) continue;
+
+ if (enterFrac < leaveFrac && enterFrac >= 0) {
+ if (enterFrac < tw->trace.fraction) {
+ if (enterFrac < 0) {
+ enterFrac = 0;
+ }
+#ifndef BSPC
+ if (!cv) {
+ cv = Cvar_Get( "r_debugSurfaceUpdate", "1", 0 );
+ }
+ if (cv && cv->integer) {
+ debugPatchCollide = pc;
+ debugFacet = facet;
+ }
+#endif //BSPC
+
+ tw->trace.fraction = enterFrac;
+ VectorCopy( bestplane, tw->trace.plane.normal );
+ tw->trace.plane.dist = bestplane[3];
+ }
+ }
+ }
+}
+
+
+/*
+=======================================================================
+
+POSITION TEST
+
+=======================================================================
+*/
+
+/*
+====================
+CM_PositionTestInPatchCollide
+====================
+*/
+qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) {
+ int i, j;
+ float offset, t;
+ patchPlane_t *planes;
+ facet_t *facet;
+ float plane[4];
+ vec3_t startp;
+
+ if (tw->isPoint) {
+ return qfalse;
+ }
+ //
+ facet = pc->facets;
+ for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) {
+ planes = &pc->planes[ facet->surfacePlane ];
+ VectorCopy(planes->plane, plane);
+ plane[3] = planes->plane[3];
+ if ( tw->type == TT_CAPSULE ) {
+ // adjust the plane distance apropriately for radius
+ plane[3] += tw->sphere.radius;
+
+ // find the closest point on the capsule to the plane
+ t = DotProduct( plane, tw->sphere.offset );
+ if ( t > 0 ) {
+ VectorSubtract( tw->start, tw->sphere.offset, startp );
+ }
+ else {
+ VectorAdd( tw->start, tw->sphere.offset, startp );
+ }
+ }
+ else {
+ offset = DotProduct( tw->offsets[ planes->signbits ], plane);
+ plane[3] -= offset;
+ VectorCopy( tw->start, startp );
+ }
+
+ if ( DotProduct( plane, startp ) - plane[3] > 0.0f ) {
+ continue;
+ }
+
+ for ( j = 0; j < facet->numBorders; j++ ) {
+ planes = &pc->planes[ facet->borderPlanes[j] ];
+ if (facet->borderInward[j]) {
+ VectorNegate(planes->plane, plane);
+ plane[3] = -planes->plane[3];
+ }
+ else {
+ VectorCopy(planes->plane, plane);
+ plane[3] = planes->plane[3];
+ }
+ if ( tw->type == TT_CAPSULE ) {
+ // adjust the plane distance apropriately for radius
+ plane[3] += tw->sphere.radius;
+
+ // find the closest point on the capsule to the plane
+ t = DotProduct( plane, tw->sphere.offset );
+ if ( t > 0.0f ) {
+ VectorSubtract( tw->start, tw->sphere.offset, startp );
+ }
+ else {
+ VectorAdd( tw->start, tw->sphere.offset, startp );
+ }
+ }
+ else {
+ // NOTE: this works even though the plane might be flipped because the bbox is centered
+ offset = DotProduct( tw->offsets[ planes->signbits ], plane);
+ plane[3] += fabs(offset);
+ VectorCopy( tw->start, startp );
+ }
+
+ if ( DotProduct( plane, startp ) - plane[3] > 0.0f ) {
+ break;
+ }
+ }
+ if (j < facet->numBorders) {
+ continue;
+ }
+ // inside this patch facet
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+=======================================================================
+
+DEBUGGING
+
+=======================================================================
+*/
+
+
+/*
+==================
+CM_DrawDebugSurface
+
+Called from the renderer
+==================
+*/
+void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, float *points) ) {
+ static cvar_t *cv;
+#ifndef BSPC
+ static cvar_t *cv2;
+#endif
+ const patchCollide_t *pc;
+ facet_t *facet;
+ winding_t *w;
+ int i, j, k, n;
+ int curplanenum, planenum, curinward, inward;
+ float plane[4];
+ vec3_t mins = {-15, -15, -28}, maxs = {15, 15, 28};
+ //vec3_t mins = {0, 0, 0}, maxs = {0, 0, 0};
+ vec3_t v1, v2;
+
+#ifndef BSPC
+ if ( !cv2 )
+ {
+ cv2 = Cvar_Get( "r_debugSurface", "0", 0 );
+ }
+
+ if (cv2->integer != 1)
+ {
+ return;
+ }
+#endif
+
+ if ( !debugPatchCollide ) {
+ return;
+ }
+
+#ifndef BSPC
+ if ( !cv ) {
+ cv = Cvar_Get( "cm_debugSize", "2", 0 );
+ }
+#endif
+ pc = debugPatchCollide;
+
+ for ( i = 0, facet = pc->facets ; i < pc->numFacets ; i++, facet++ ) {
+
+ for ( k = 0 ; k < facet->numBorders + 1; k++ ) {
+ //
+ if (k < facet->numBorders) {
+ planenum = facet->borderPlanes[k];
+ inward = facet->borderInward[k];
+ }
+ else {
+ planenum = facet->surfacePlane;
+ inward = qfalse;
+ //continue;
+ }
+
+ Vector4Copy( pc->planes[ planenum ].plane, plane );
+
+ //planenum = facet->surfacePlane;
+ if ( inward ) {
+ VectorSubtract( vec3_origin, plane, plane );
+ plane[3] = -plane[3];
+ }
+
+ plane[3] += cv->value;
+ //*
+ for (n = 0; n < 3; n++)
+ {
+ if (plane[n] > 0) v1[n] = maxs[n];
+ else v1[n] = mins[n];
+ } //end for
+ VectorNegate(plane, v2);
+ plane[3] += fabs(DotProduct(v1, v2));
+ //*/
+
+ w = BaseWindingForPlane( plane, plane[3] );
+ for ( j = 0 ; j < facet->numBorders + 1 && w; j++ ) {
+ //
+ if (j < facet->numBorders) {
+ curplanenum = facet->borderPlanes[j];
+ curinward = facet->borderInward[j];
+ }
+ else {
+ curplanenum = facet->surfacePlane;
+ curinward = qfalse;
+ //continue;
+ }
+ //
+ if (curplanenum == planenum) continue;
+
+ Vector4Copy( pc->planes[ curplanenum ].plane, plane );
+ if ( !curinward ) {
+ VectorSubtract( vec3_origin, plane, plane );
+ plane[3] = -plane[3];
+ }
+ // if ( !facet->borderNoAdjust[j] ) {
+ plane[3] -= cv->value;
+ // }
+ for (n = 0; n < 3; n++)
+ {
+ if (plane[n] > 0) v1[n] = maxs[n];
+ else v1[n] = mins[n];
+ } //end for
+ VectorNegate(plane, v2);
+ plane[3] -= fabs(DotProduct(v1, v2));
+
+ ChopWindingInPlace( &w, plane, plane[3], 0.1f );
+ }
+ if ( w ) {
+ if ( facet == debugFacet ) {
+ drawPoly( 4, w->numpoints, w->p[0] );
+ //Com_Printf("blue facet has %d border planes\n", facet->numBorders);
+ } else {
+ drawPoly( 1, w->numpoints, w->p[0] );
+ }
+ FreeWinding( w );
+ }
+ else
+ Com_Printf("winding chopped away by border planes\n");
+ }
+ }
+
+ // draw the debug block
+ {
+ vec3_t v[3];
+
+ VectorCopy( debugBlockPoints[0], v[0] );
+ VectorCopy( debugBlockPoints[1], v[1] );
+ VectorCopy( debugBlockPoints[2], v[2] );
+ drawPoly( 2, 3, v[0] );
+
+ VectorCopy( debugBlockPoints[2], v[0] );
+ VectorCopy( debugBlockPoints[3], v[1] );
+ VectorCopy( debugBlockPoints[0], v[2] );
+ drawPoly( 2, 3, v[0] );
+ }
+
+#if 0
+ vec3_t v[4];
+
+ v[0][0] = pc->bounds[1][0];
+ v[0][1] = pc->bounds[1][1];
+ v[0][2] = pc->bounds[1][2];
+
+ v[1][0] = pc->bounds[1][0];
+ v[1][1] = pc->bounds[0][1];
+ v[1][2] = pc->bounds[1][2];
+
+ v[2][0] = pc->bounds[0][0];
+ v[2][1] = pc->bounds[0][1];
+ v[2][2] = pc->bounds[1][2];
+
+ v[3][0] = pc->bounds[0][0];
+ v[3][1] = pc->bounds[1][1];
+ v[3][2] = pc->bounds[1][2];
+
+ drawPoly( 4, v[0] );
+#endif
+}
diff --git a/src/qcommon/cm_patch.h b/src/qcommon/cm_patch.h
new file mode 100644
index 0000000..826fa17
--- /dev/null
+++ b/src/qcommon/cm_patch.h
@@ -0,0 +1,104 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+//#define CULL_BBOX
+
+/*
+
+This file does not reference any globals, and has these entry points:
+
+void CM_ClearLevelPatches( void );
+struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, const vec3_t *points );
+void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc );
+qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc );
+void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, flaot *points) );
+
+
+Issues for collision against curved surfaces:
+
+Surface edges need to be handled differently than surface planes
+
+Plane expansion causes raw surfaces to expand past expanded bounding box
+
+Position test of a volume against a surface is tricky.
+
+Position test of a point against a surface is not well defined, because the surface has no volume.
+
+
+Tracing leading edge points instead of volumes?
+Position test by tracing corner to corner? (8*7 traces -- ouch)
+
+coplanar edges
+triangulated patches
+degenerate patches
+
+ endcaps
+ degenerate
+
+WARNING: this may misbehave with meshes that have rows or columns that only
+degenerate a few triangles. Completely degenerate rows and columns are handled
+properly.
+*/
+
+
+#define MAX_FACETS 1024
+#define MAX_PATCH_PLANES 2048
+
+typedef struct {
+ float plane[4];
+ int signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision
+} patchPlane_t;
+
+typedef struct {
+ int surfacePlane;
+ int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels
+ int borderPlanes[4+6+16];
+ int borderInward[4+6+16];
+ qboolean borderNoAdjust[4+6+16];
+} facet_t;
+
+typedef struct patchCollide_s {
+ vec3_t bounds[2];
+ int numPlanes; // surface planes plus edge planes
+ patchPlane_t *planes;
+ int numFacets;
+ facet_t *facets;
+} patchCollide_t;
+
+
+#define MAX_GRID_SIZE 129
+
+typedef struct {
+ int width;
+ int height;
+ qboolean wrapWidth;
+ qboolean wrapHeight;
+ vec3_t points[MAX_GRID_SIZE][MAX_GRID_SIZE]; // [width][height]
+} cGrid_t;
+
+#define SUBDIVIDE_DISTANCE 16 //4 // never more than this units away from curve
+#define PLANE_TRI_EPSILON 0.1
+#define WRAP_POINT_EPSILON 0.1
+
+
+struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points );
diff --git a/src/qcommon/cm_polylib.c b/src/qcommon/cm_polylib.c
new file mode 100644
index 0000000..1a93754
--- /dev/null
+++ b/src/qcommon/cm_polylib.c
@@ -0,0 +1,738 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// this is only used for visualization tools in cm_ debug functions
+
+
+#include "cm_local.h"
+
+
+// counters are only bumped when running single threaded,
+// because they are an awful coherence problem
+int c_active_windings;
+int c_peak_windings;
+int c_winding_allocs;
+int c_winding_points;
+
+void pw(winding_t *w)
+{
+ int i;
+ for (i=0 ; i<w->numpoints ; i++)
+ printf ("(%5.1f, %5.1f, %5.1f)\n",w->p[i][0], w->p[i][1],w->p[i][2]);
+}
+
+
+/*
+=============
+AllocWinding
+=============
+*/
+winding_t *AllocWinding (int points)
+{
+ winding_t *w;
+ int s;
+
+ c_winding_allocs++;
+ c_winding_points += points;
+ c_active_windings++;
+ if (c_active_windings > c_peak_windings)
+ c_peak_windings = c_active_windings;
+
+ s = sizeof(vec_t)*3*points + sizeof(int);
+ w = Z_Malloc (s);
+ Com_Memset (w, 0, s);
+ return w;
+}
+
+void FreeWinding (winding_t *w)
+{
+ if (*(unsigned *)w == 0xdeaddead)
+ Com_Error (ERR_FATAL, "FreeWinding: freed a freed winding");
+ *(unsigned *)w = 0xdeaddead;
+
+ c_active_windings--;
+ Z_Free (w);
+}
+
+/*
+============
+RemoveColinearPoints
+============
+*/
+int c_removed;
+
+void RemoveColinearPoints (winding_t *w)
+{
+ int i, j, k;
+ vec3_t v1, v2;
+ int nump;
+ vec3_t p[MAX_POINTS_ON_WINDING];
+
+ nump = 0;
+ for (i=0 ; i<w->numpoints ; i++)
+ {
+ j = (i+1)%w->numpoints;
+ k = (i+w->numpoints-1)%w->numpoints;
+ VectorSubtract (w->p[j], w->p[i], v1);
+ VectorSubtract (w->p[i], w->p[k], v2);
+ VectorNormalize2(v1,v1);
+ VectorNormalize2(v2,v2);
+ if (DotProduct(v1, v2) < 0.999)
+ {
+ VectorCopy (w->p[i], p[nump]);
+ nump++;
+ }
+ }
+
+ if (nump == w->numpoints)
+ return;
+
+ c_removed += w->numpoints - nump;
+ w->numpoints = nump;
+ Com_Memcpy (w->p, p, nump*sizeof(p[0]));
+}
+
+/*
+============
+WindingPlane
+============
+*/
+void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist)
+{
+ vec3_t v1, v2;
+
+ VectorSubtract (w->p[1], w->p[0], v1);
+ VectorSubtract (w->p[2], w->p[0], v2);
+ CrossProduct (v2, v1, normal);
+ VectorNormalize2(normal, normal);
+ *dist = DotProduct (w->p[0], normal);
+
+}
+
+/*
+=============
+WindingArea
+=============
+*/
+vec_t WindingArea (winding_t *w)
+{
+ int i;
+ vec3_t d1, d2, cross;
+ vec_t total;
+
+ total = 0;
+ for (i=2 ; i<w->numpoints ; i++)
+ {
+ VectorSubtract (w->p[i-1], w->p[0], d1);
+ VectorSubtract (w->p[i], w->p[0], d2);
+ CrossProduct (d1, d2, cross);
+ total += 0.5 * VectorLength ( cross );
+ }
+ return total;
+}
+
+/*
+=============
+WindingBounds
+=============
+*/
+void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs)
+{
+ vec_t v;
+ int i,j;
+
+ mins[0] = mins[1] = mins[2] = MAX_MAP_BOUNDS;
+ maxs[0] = maxs[1] = maxs[2] = -MAX_MAP_BOUNDS;
+
+ for (i=0 ; i<w->numpoints ; i++)
+ {
+ for (j=0 ; j<3 ; j++)
+ {
+ v = w->p[i][j];
+ if (v < mins[j])
+ mins[j] = v;
+ if (v > maxs[j])
+ maxs[j] = v;
+ }
+ }
+}
+
+/*
+=============
+WindingCenter
+=============
+*/
+void WindingCenter (winding_t *w, vec3_t center)
+{
+ int i;
+ float scale;
+
+ VectorCopy (vec3_origin, center);
+ for (i=0 ; i<w->numpoints ; i++)
+ VectorAdd (w->p[i], center, center);
+
+ scale = 1.0/w->numpoints;
+ VectorScale (center, scale, center);
+}
+
+/*
+=================
+BaseWindingForPlane
+=================
+*/
+winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist)
+{
+ int i, x;
+ vec_t max, v;
+ vec3_t org, vright, vup;
+ winding_t *w;
+
+// find the major axis
+
+ max = -MAX_MAP_BOUNDS;
+ x = -1;
+ for (i=0 ; i<3; i++)
+ {
+ v = fabs(normal[i]);
+ if (v > max)
+ {
+ x = i;
+ max = v;
+ }
+ }
+ if (x==-1)
+ Com_Error (ERR_DROP, "BaseWindingForPlane: no axis found");
+
+ VectorCopy (vec3_origin, vup);
+ switch (x)
+ {
+ case 0:
+ case 1:
+ vup[2] = 1;
+ break;
+ case 2:
+ vup[0] = 1;
+ break;
+ }
+
+ v = DotProduct (vup, normal);
+ VectorMA (vup, -v, normal, vup);
+ VectorNormalize2(vup, vup);
+
+ VectorScale (normal, dist, org);
+
+ CrossProduct (vup, normal, vright);
+
+ VectorScale (vup, MAX_MAP_BOUNDS, vup);
+ VectorScale (vright, MAX_MAP_BOUNDS, vright);
+
+// project a really big axis aligned box onto the plane
+ w = AllocWinding (4);
+
+ VectorSubtract (org, vright, w->p[0]);
+ VectorAdd (w->p[0], vup, w->p[0]);
+
+ VectorAdd (org, vright, w->p[1]);
+ VectorAdd (w->p[1], vup, w->p[1]);
+
+ VectorAdd (org, vright, w->p[2]);
+ VectorSubtract (w->p[2], vup, w->p[2]);
+
+ VectorSubtract (org, vright, w->p[3]);
+ VectorSubtract (w->p[3], vup, w->p[3]);
+
+ w->numpoints = 4;
+
+ return w;
+}
+
+/*
+==================
+CopyWinding
+==================
+*/
+winding_t *CopyWinding (winding_t *w)
+{
+ unsigned long size;
+ winding_t *c;
+
+ c = AllocWinding (w->numpoints);
+ size = (long)((winding_t *)0)->p[w->numpoints];
+ Com_Memcpy (c, w, size);
+ return c;
+}
+
+/*
+==================
+ReverseWinding
+==================
+*/
+winding_t *ReverseWinding (winding_t *w)
+{
+ int i;
+ winding_t *c;
+
+ c = AllocWinding (w->numpoints);
+ for (i=0 ; i<w->numpoints ; i++)
+ {
+ VectorCopy (w->p[w->numpoints-1-i], c->p[i]);
+ }
+ c->numpoints = w->numpoints;
+ return c;
+}
+
+
+/*
+=============
+ClipWindingEpsilon
+=============
+*/
+void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist,
+ vec_t epsilon, winding_t **front, winding_t **back)
+{
+ vec_t dists[MAX_POINTS_ON_WINDING+4];
+ int sides[MAX_POINTS_ON_WINDING+4];
+ int counts[3];
+ static vec_t dot; // VC 4.2 optimizer bug if not static
+ int i, j;
+ vec_t *p1, *p2;
+ vec3_t mid;
+ winding_t *f, *b;
+ int maxpts;
+
+ counts[0] = counts[1] = counts[2] = 0;
+
+// determine sides for each point
+ for (i=0 ; i<in->numpoints ; i++)
+ {
+ dot = DotProduct (in->p[i], normal);
+ dot -= dist;
+ dists[i] = dot;
+ if (dot > epsilon)
+ sides[i] = SIDE_FRONT;
+ else if (dot < -epsilon)
+ sides[i] = SIDE_BACK;
+ else
+ {
+ sides[i] = SIDE_ON;
+ }
+ counts[sides[i]]++;
+ }
+ sides[i] = sides[0];
+ dists[i] = dists[0];
+
+ *front = *back = NULL;
+
+ if (!counts[0])
+ {
+ *back = CopyWinding (in);
+ return;
+ }
+ if (!counts[1])
+ {
+ *front = CopyWinding (in);
+ return;
+ }
+
+ maxpts = in->numpoints+4; // cant use counts[0]+2 because
+ // of fp grouping errors
+
+ *front = f = AllocWinding (maxpts);
+ *back = b = AllocWinding (maxpts);
+
+ for (i=0 ; i<in->numpoints ; i++)
+ {
+ p1 = in->p[i];
+
+ if (sides[i] == SIDE_ON)
+ {
+ VectorCopy (p1, f->p[f->numpoints]);
+ f->numpoints++;
+ VectorCopy (p1, b->p[b->numpoints]);
+ b->numpoints++;
+ continue;
+ }
+
+ if (sides[i] == SIDE_FRONT)
+ {
+ VectorCopy (p1, f->p[f->numpoints]);
+ f->numpoints++;
+ }
+ if (sides[i] == SIDE_BACK)
+ {
+ VectorCopy (p1, b->p[b->numpoints]);
+ b->numpoints++;
+ }
+
+ if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
+ continue;
+
+ // generate a split point
+ p2 = in->p[(i+1)%in->numpoints];
+
+ dot = dists[i] / (dists[i]-dists[i+1]);
+ for (j=0 ; j<3 ; j++)
+ { // avoid round off error when possible
+ if (normal[j] == 1)
+ mid[j] = dist;
+ else if (normal[j] == -1)
+ mid[j] = -dist;
+ else
+ mid[j] = p1[j] + dot*(p2[j]-p1[j]);
+ }
+
+ VectorCopy (mid, f->p[f->numpoints]);
+ f->numpoints++;
+ VectorCopy (mid, b->p[b->numpoints]);
+ b->numpoints++;
+ }
+
+ if (f->numpoints > maxpts || b->numpoints > maxpts)
+ Com_Error (ERR_DROP, "ClipWinding: points exceeded estimate");
+ if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING)
+ Com_Error (ERR_DROP, "ClipWinding: MAX_POINTS_ON_WINDING");
+}
+
+
+/*
+=============
+ChopWindingInPlace
+=============
+*/
+void ChopWindingInPlace (winding_t **inout, vec3_t normal, vec_t dist, vec_t epsilon)
+{
+ winding_t *in;
+ vec_t dists[MAX_POINTS_ON_WINDING+4];
+ int sides[MAX_POINTS_ON_WINDING+4];
+ int counts[3];
+ static vec_t dot; // VC 4.2 optimizer bug if not static
+ int i, j;
+ vec_t *p1, *p2;
+ vec3_t mid;
+ winding_t *f;
+ int maxpts;
+
+ in = *inout;
+ counts[0] = counts[1] = counts[2] = 0;
+
+// determine sides for each point
+ for (i=0 ; i<in->numpoints ; i++)
+ {
+ dot = DotProduct (in->p[i], normal);
+ dot -= dist;
+ dists[i] = dot;
+ if (dot > epsilon)
+ sides[i] = SIDE_FRONT;
+ else if (dot < -epsilon)
+ sides[i] = SIDE_BACK;
+ else
+ {
+ sides[i] = SIDE_ON;
+ }
+ counts[sides[i]]++;
+ }
+ sides[i] = sides[0];
+ dists[i] = dists[0];
+
+ if (!counts[0])
+ {
+ FreeWinding (in);
+ *inout = NULL;
+ return;
+ }
+ if (!counts[1])
+ return; // inout stays the same
+
+ maxpts = in->numpoints+4; // cant use counts[0]+2 because
+ // of fp grouping errors
+
+ f = AllocWinding (maxpts);
+
+ for (i=0 ; i<in->numpoints ; i++)
+ {
+ p1 = in->p[i];
+
+ if (sides[i] == SIDE_ON)
+ {
+ VectorCopy (p1, f->p[f->numpoints]);
+ f->numpoints++;
+ continue;
+ }
+
+ if (sides[i] == SIDE_FRONT)
+ {
+ VectorCopy (p1, f->p[f->numpoints]);
+ f->numpoints++;
+ }
+
+ if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
+ continue;
+
+ // generate a split point
+ p2 = in->p[(i+1)%in->numpoints];
+
+ dot = dists[i] / (dists[i]-dists[i+1]);
+ for (j=0 ; j<3 ; j++)
+ { // avoid round off error when possible
+ if (normal[j] == 1)
+ mid[j] = dist;
+ else if (normal[j] == -1)
+ mid[j] = -dist;
+ else
+ mid[j] = p1[j] + dot*(p2[j]-p1[j]);
+ }
+
+ VectorCopy (mid, f->p[f->numpoints]);
+ f->numpoints++;
+ }
+
+ if (f->numpoints > maxpts)
+ Com_Error (ERR_DROP, "ClipWinding: points exceeded estimate");
+ if (f->numpoints > MAX_POINTS_ON_WINDING)
+ Com_Error (ERR_DROP, "ClipWinding: MAX_POINTS_ON_WINDING");
+
+ FreeWinding (in);
+ *inout = f;
+}
+
+
+/*
+=================
+ChopWinding
+
+Returns the fragment of in that is on the front side
+of the cliping plane. The original is freed.
+=================
+*/
+winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist)
+{
+ winding_t *f, *b;
+
+ ClipWindingEpsilon (in, normal, dist, ON_EPSILON, &f, &b);
+ FreeWinding (in);
+ if (b)
+ FreeWinding (b);
+ return f;
+}
+
+
+/*
+=================
+CheckWinding
+
+=================
+*/
+void CheckWinding (winding_t *w)
+{
+ int i, j;
+ vec_t *p1, *p2;
+ vec_t d, edgedist;
+ vec3_t dir, edgenormal, facenormal;
+ vec_t area;
+ vec_t facedist;
+
+ if (w->numpoints < 3)
+ Com_Error (ERR_DROP, "CheckWinding: %i points",w->numpoints);
+
+ area = WindingArea(w);
+ if (area < 1)
+ Com_Error (ERR_DROP, "CheckWinding: %f area", area);
+
+ WindingPlane (w, facenormal, &facedist);
+
+ for (i=0 ; i<w->numpoints ; i++)
+ {
+ p1 = w->p[i];
+
+ for (j=0 ; j<3 ; j++)
+ if (p1[j] > MAX_MAP_BOUNDS || p1[j] < -MAX_MAP_BOUNDS)
+ Com_Error (ERR_DROP, "CheckFace: BUGUS_RANGE: %f",p1[j]);
+
+ j = i+1 == w->numpoints ? 0 : i+1;
+
+ // check the point is on the face plane
+ d = DotProduct (p1, facenormal) - facedist;
+ if (d < -ON_EPSILON || d > ON_EPSILON)
+ Com_Error (ERR_DROP, "CheckWinding: point off plane");
+
+ // check the edge isnt degenerate
+ p2 = w->p[j];
+ VectorSubtract (p2, p1, dir);
+
+ if (VectorLength (dir) < ON_EPSILON)
+ Com_Error (ERR_DROP, "CheckWinding: degenerate edge");
+
+ CrossProduct (facenormal, dir, edgenormal);
+ VectorNormalize2 (edgenormal, edgenormal);
+ edgedist = DotProduct (p1, edgenormal);
+ edgedist += ON_EPSILON;
+
+ // all other points must be on front side
+ for (j=0 ; j<w->numpoints ; j++)
+ {
+ if (j == i)
+ continue;
+ d = DotProduct (w->p[j], edgenormal);
+ if (d > edgedist)
+ Com_Error (ERR_DROP, "CheckWinding: non-convex");
+ }
+ }
+}
+
+
+/*
+============
+WindingOnPlaneSide
+============
+*/
+int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist)
+{
+ qboolean front, back;
+ int i;
+ vec_t d;
+
+ front = qfalse;
+ back = qfalse;
+ for (i=0 ; i<w->numpoints ; i++)
+ {
+ d = DotProduct (w->p[i], normal) - dist;
+ if (d < -ON_EPSILON)
+ {
+ if (front)
+ return SIDE_CROSS;
+ back = qtrue;
+ continue;
+ }
+ if (d > ON_EPSILON)
+ {
+ if (back)
+ return SIDE_CROSS;
+ front = qtrue;
+ continue;
+ }
+ }
+
+ if (back)
+ return SIDE_BACK;
+ if (front)
+ return SIDE_FRONT;
+ return SIDE_ON;
+}
+
+
+/*
+=================
+AddWindingToConvexHull
+
+Both w and *hull are on the same plane
+=================
+*/
+#define MAX_HULL_POINTS 128
+void AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal ) {
+ int i, j, k;
+ float *p, *copy;
+ vec3_t dir;
+ float d;
+ int numHullPoints, numNew;
+ vec3_t hullPoints[MAX_HULL_POINTS];
+ vec3_t newHullPoints[MAX_HULL_POINTS];
+ vec3_t hullDirs[MAX_HULL_POINTS];
+ qboolean hullSide[MAX_HULL_POINTS];
+ qboolean outside;
+
+ if ( !*hull ) {
+ *hull = CopyWinding( w );
+ return;
+ }
+
+ numHullPoints = (*hull)->numpoints;
+ Com_Memcpy( hullPoints, (*hull)->p, numHullPoints * sizeof(vec3_t) );
+
+ for ( i = 0 ; i < w->numpoints ; i++ ) {
+ p = w->p[i];
+
+ // calculate hull side vectors
+ for ( j = 0 ; j < numHullPoints ; j++ ) {
+ k = ( j + 1 ) % numHullPoints;
+
+ VectorSubtract( hullPoints[k], hullPoints[j], dir );
+ VectorNormalize2( dir, dir );
+ CrossProduct( normal, dir, hullDirs[j] );
+ }
+
+ outside = qfalse;
+ for ( j = 0 ; j < numHullPoints ; j++ ) {
+ VectorSubtract( p, hullPoints[j], dir );
+ d = DotProduct( dir, hullDirs[j] );
+ if ( d >= ON_EPSILON ) {
+ outside = qtrue;
+ }
+ if ( d >= -ON_EPSILON ) {
+ hullSide[j] = qtrue;
+ } else {
+ hullSide[j] = qfalse;
+ }
+ }
+
+ // if the point is effectively inside, do nothing
+ if ( !outside ) {
+ continue;
+ }
+
+ // find the back side to front side transition
+ for ( j = 0 ; j < numHullPoints ; j++ ) {
+ if ( !hullSide[ j % numHullPoints ] && hullSide[ (j + 1) % numHullPoints ] ) {
+ break;
+ }
+ }
+ if ( j == numHullPoints ) {
+ continue;
+ }
+
+ // insert the point here
+ VectorCopy( p, newHullPoints[0] );
+ numNew = 1;
+
+ // copy over all points that aren't double fronts
+ j = (j+1)%numHullPoints;
+ for ( k = 0 ; k < numHullPoints ; k++ ) {
+ if ( hullSide[ (j+k) % numHullPoints ] && hullSide[ (j+k+1) % numHullPoints ] ) {
+ continue;
+ }
+ copy = hullPoints[ (j+k+1) % numHullPoints ];
+ VectorCopy( copy, newHullPoints[numNew] );
+ numNew++;
+ }
+
+ numHullPoints = numNew;
+ Com_Memcpy( hullPoints, newHullPoints, numHullPoints * sizeof(vec3_t) );
+ }
+
+ FreeWinding( *hull );
+ w = AllocWinding( numHullPoints );
+ w->numpoints = numHullPoints;
+ *hull = w;
+ Com_Memcpy( w->p, hullPoints, numHullPoints * sizeof(vec3_t) );
+}
+
+
diff --git a/src/qcommon/cm_polylib.h b/src/qcommon/cm_polylib.h
new file mode 100644
index 0000000..f1edc5c
--- /dev/null
+++ b/src/qcommon/cm_polylib.h
@@ -0,0 +1,69 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// this is only used for visualization tools in cm_ debug functions
+
+typedef struct
+{
+ int numpoints;
+ vec3_t p[4]; // variable sized
+} winding_t;
+
+#define MAX_POINTS_ON_WINDING 64
+
+#define SIDE_FRONT 0
+#define SIDE_BACK 1
+#define SIDE_ON 2
+#define SIDE_CROSS 3
+
+#define CLIP_EPSILON 0.1f
+
+#define MAX_MAP_BOUNDS 65535
+
+// you can define on_epsilon in the makefile as tighter
+#ifndef ON_EPSILON
+#define ON_EPSILON 0.1f
+#endif
+
+winding_t *AllocWinding (int points);
+vec_t WindingArea (winding_t *w);
+void WindingCenter (winding_t *w, vec3_t center);
+void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist,
+ vec_t epsilon, winding_t **front, winding_t **back);
+winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist);
+winding_t *CopyWinding (winding_t *w);
+winding_t *ReverseWinding (winding_t *w);
+winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist);
+void CheckWinding (winding_t *w);
+void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist);
+void RemoveColinearPoints (winding_t *w);
+int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist);
+void FreeWinding (winding_t *w);
+void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs);
+
+void AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal );
+
+void ChopWindingInPlace (winding_t **w, vec3_t normal, vec_t dist, vec_t epsilon);
+// frees the original if clipped
+
+void pw(winding_t *w);
diff --git a/src/qcommon/cm_public.h b/src/qcommon/cm_public.h
new file mode 100644
index 0000000..68d04af
--- /dev/null
+++ b/src/qcommon/cm_public.h
@@ -0,0 +1,84 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "qfiles.h"
+
+
+void CM_LoadMap( const char *name, qboolean clientload, int *checksum);
+void CM_ClearMap( void );
+clipHandle_t CM_InlineModel( int index ); // 0 = world, 1 + are bmodels
+clipHandle_t CM_TempBoxModel( const vec3_t mins, const vec3_t maxs, int capsule );
+
+void CM_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs );
+
+int CM_NumClusters (void);
+int CM_NumInlineModels( void );
+char *CM_EntityString (void);
+
+// returns an ORed contents mask
+int CM_PointContents( const vec3_t p, clipHandle_t model );
+int CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles );
+
+void CM_BoxTrace ( trace_t *results, const vec3_t start, const vec3_t end,
+ vec3_t mins, vec3_t maxs,
+ clipHandle_t model, int brushmask, traceType_t type );
+void CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end,
+ vec3_t mins, vec3_t maxs,
+ clipHandle_t model, int brushmask,
+ const vec3_t origin, const vec3_t angles, traceType_t type );
+void CM_BiSphereTrace( trace_t *results, const vec3_t start,
+ const vec3_t end, float startRad, float endRad,
+ clipHandle_t model, int mask );
+void CM_TransformedBiSphereTrace( trace_t *results, const vec3_t start,
+ const vec3_t end, float startRad, float endRad,
+ clipHandle_t model, int mask,
+ const vec3_t origin );
+
+byte *CM_ClusterPVS (int cluster);
+
+int CM_PointLeafnum( const vec3_t p );
+
+// only returns non-solid leafs
+// overflow if return listsize and if *lastLeaf != list[listsize-1]
+int CM_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *list,
+ int listsize, int *lastLeaf );
+
+int CM_LeafCluster (int leafnum);
+int CM_LeafArea (int leafnum);
+
+void CM_AdjustAreaPortalState( int area1, int area2, qboolean open );
+qboolean CM_AreasConnected( int area1, int area2 );
+
+int CM_WriteAreaBits( byte *buffer, int area );
+
+// cm_tag.c
+int CM_LerpTag( orientation_t *tag, clipHandle_t model, int startFrame, int endFrame,
+ float frac, const char *tagName );
+
+
+// cm_marks.c
+int CM_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection,
+ int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer );
+
+// cm_patch.c
+void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, float *points) );
diff --git a/src/qcommon/cm_test.c b/src/qcommon/cm_test.c
new file mode 100644
index 0000000..9e95060
--- /dev/null
+++ b/src/qcommon/cm_test.c
@@ -0,0 +1,479 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+#include "cm_local.h"
+
+
+/*
+==================
+CM_PointLeafnum_r
+
+==================
+*/
+int CM_PointLeafnum_r( const vec3_t p, int num ) {
+ float d;
+ cNode_t *node;
+ cplane_t *plane;
+
+ while (num >= 0)
+ {
+ node = cm.nodes + num;
+ plane = node->plane;
+
+ if (plane->type < 3)
+ d = p[plane->type] - plane->dist;
+ else
+ d = DotProduct (plane->normal, p) - plane->dist;
+ if (d < 0)
+ num = node->children[1];
+ else
+ num = node->children[0];
+ }
+
+ c_pointcontents++; // optimize counter
+
+ return -1 - num;
+}
+
+int CM_PointLeafnum( const vec3_t p ) {
+ if ( !cm.numNodes ) { // map not loaded
+ return 0;
+ }
+ return CM_PointLeafnum_r (p, 0);
+}
+
+
+/*
+======================================================================
+
+LEAF LISTING
+
+======================================================================
+*/
+
+
+void CM_StoreLeafs( leafList_t *ll, int nodenum ) {
+ int leafNum;
+
+ leafNum = -1 - nodenum;
+
+ // store the lastLeaf even if the list is overflowed
+ if ( cm.leafs[ leafNum ].cluster != -1 ) {
+ ll->lastLeaf = leafNum;
+ }
+
+ if ( ll->count >= ll->maxcount) {
+ ll->overflowed = qtrue;
+ return;
+ }
+ ll->list[ ll->count++ ] = leafNum;
+}
+
+void CM_StoreBrushes( leafList_t *ll, int nodenum ) {
+ int i, k;
+ int leafnum;
+ int brushnum;
+ cLeaf_t *leaf;
+ cbrush_t *b;
+
+ leafnum = -1 - nodenum;
+
+ leaf = &cm.leafs[leafnum];
+
+ for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) {
+ brushnum = cm.leafbrushes[leaf->firstLeafBrush+k];
+ b = &cm.brushes[brushnum];
+ if ( b->checkcount == cm.checkcount ) {
+ continue; // already checked this brush in another leaf
+ }
+ b->checkcount = cm.checkcount;
+ for ( i = 0 ; i < 3 ; i++ ) {
+ if ( b->bounds[0][i] >= ll->bounds[1][i] || b->bounds[1][i] <= ll->bounds[0][i] ) {
+ break;
+ }
+ }
+ if ( i != 3 ) {
+ continue;
+ }
+ if ( ll->count >= ll->maxcount) {
+ ll->overflowed = qtrue;
+ return;
+ }
+ ((cbrush_t **)ll->list)[ ll->count++ ] = b;
+ }
+#if 0
+ // store patches?
+ for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) {
+ patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstleafsurface + k ] ];
+ if ( !patch ) {
+ continue;
+ }
+ }
+#endif
+}
+
+/*
+=============
+CM_BoxLeafnums
+
+Fills in a list of all the leafs touched
+=============
+*/
+void CM_BoxLeafnums_r( leafList_t *ll, int nodenum ) {
+ cplane_t *plane;
+ cNode_t *node;
+ int s;
+
+ while (1) {
+ if (nodenum < 0) {
+ ll->storeLeafs( ll, nodenum );
+ return;
+ }
+
+ node = &cm.nodes[nodenum];
+ plane = node->plane;
+ s = BoxOnPlaneSide( ll->bounds[0], ll->bounds[1], plane );
+ if (s == 1) {
+ nodenum = node->children[0];
+ } else if (s == 2) {
+ nodenum = node->children[1];
+ } else {
+ // go down both
+ CM_BoxLeafnums_r( ll, node->children[0] );
+ nodenum = node->children[1];
+ }
+
+ }
+}
+
+/*
+==================
+CM_BoxLeafnums
+==================
+*/
+int CM_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *list, int listsize, int *lastLeaf) {
+ leafList_t ll;
+
+ cm.checkcount++;
+
+ VectorCopy( mins, ll.bounds[0] );
+ VectorCopy( maxs, ll.bounds[1] );
+ ll.count = 0;
+ ll.maxcount = listsize;
+ ll.list = list;
+ ll.storeLeafs = CM_StoreLeafs;
+ ll.lastLeaf = 0;
+ ll.overflowed = qfalse;
+
+ CM_BoxLeafnums_r( &ll, 0 );
+
+ *lastLeaf = ll.lastLeaf;
+ return ll.count;
+}
+
+/*
+==================
+CM_BoxBrushes
+==================
+*/
+int CM_BoxBrushes( const vec3_t mins, const vec3_t maxs, cbrush_t **list, int listsize ) {
+ leafList_t ll;
+
+ cm.checkcount++;
+
+ VectorCopy( mins, ll.bounds[0] );
+ VectorCopy( maxs, ll.bounds[1] );
+ ll.count = 0;
+ ll.maxcount = listsize;
+ ll.list = (void *)list;
+ ll.storeLeafs = CM_StoreBrushes;
+ ll.lastLeaf = 0;
+ ll.overflowed = qfalse;
+
+ CM_BoxLeafnums_r( &ll, 0 );
+
+ return ll.count;
+}
+
+
+//====================================================================
+
+
+/*
+==================
+CM_PointContents
+
+==================
+*/
+int CM_PointContents( const vec3_t p, clipHandle_t model ) {
+ int leafnum;
+ int i, k;
+ int brushnum;
+ cLeaf_t *leaf;
+ cbrush_t *b;
+ int contents;
+ float d;
+ cmodel_t *clipm;
+
+ if (!cm.numNodes) { // map not loaded
+ return 0;
+ }
+
+ if ( model ) {
+ clipm = CM_ClipHandleToModel( model );
+ leaf = &clipm->leaf;
+ } else {
+ leafnum = CM_PointLeafnum_r (p, 0);
+ leaf = &cm.leafs[leafnum];
+ }
+
+ contents = 0;
+ for (k=0 ; k<leaf->numLeafBrushes ; k++) {
+ brushnum = cm.leafbrushes[leaf->firstLeafBrush+k];
+ b = &cm.brushes[brushnum];
+
+ // see if the point is in the brush
+ for ( i = 0 ; i < b->numsides ; i++ ) {
+ d = DotProduct( p, b->sides[i].plane->normal );
+// FIXME test for Cash
+// if ( d >= b->sides[i].plane->dist ) {
+ if ( d > b->sides[i].plane->dist ) {
+ break;
+ }
+ }
+
+ if ( i == b->numsides ) {
+ contents |= b->contents;
+ }
+ }
+
+ return contents;
+}
+
+/*
+==================
+CM_TransformedPointContents
+
+Handles offseting and rotation of the end points for moving and
+rotating entities
+==================
+*/
+int CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles) {
+ vec3_t p_l;
+ vec3_t temp;
+ vec3_t forward, right, up;
+
+ // subtract origin offset
+ VectorSubtract (p, origin, p_l);
+
+ // rotate start and end into the models frame of reference
+ if ( model != BOX_MODEL_HANDLE &&
+ (angles[0] || angles[1] || angles[2]) )
+ {
+ AngleVectors (angles, forward, right, up);
+
+ VectorCopy (p_l, temp);
+ p_l[0] = DotProduct (temp, forward);
+ p_l[1] = -DotProduct (temp, right);
+ p_l[2] = DotProduct (temp, up);
+ }
+
+ return CM_PointContents( p_l, model );
+}
+
+
+
+/*
+===============================================================================
+
+PVS
+
+===============================================================================
+*/
+
+byte *CM_ClusterPVS (int cluster) {
+ if (cluster < 0 || cluster >= cm.numClusters || !cm.vised ) {
+ return cm.visibility;
+ }
+
+ return cm.visibility + cluster * cm.clusterBytes;
+}
+
+
+
+/*
+===============================================================================
+
+AREAPORTALS
+
+===============================================================================
+*/
+
+void CM_FloodArea_r( int areaNum, int floodnum) {
+ int i;
+ cArea_t *area;
+ int *con;
+
+ area = &cm.areas[ areaNum ];
+
+ if ( area->floodvalid == cm.floodvalid ) {
+ if (area->floodnum == floodnum)
+ return;
+ Com_Error (ERR_DROP, "FloodArea_r: reflooded");
+ }
+
+ area->floodnum = floodnum;
+ area->floodvalid = cm.floodvalid;
+ con = cm.areaPortals + areaNum * cm.numAreas;
+ for ( i=0 ; i < cm.numAreas ; i++ ) {
+ if ( con[i] > 0 ) {
+ CM_FloodArea_r( i, floodnum );
+ }
+ }
+}
+
+/*
+====================
+CM_FloodAreaConnections
+
+====================
+*/
+void CM_FloodAreaConnections( void ) {
+ int i;
+ cArea_t *area;
+ int floodnum;
+
+ // all current floods are now invalid
+ cm.floodvalid++;
+ floodnum = 0;
+
+ for (i = 0 ; i < cm.numAreas ; i++) {
+ area = &cm.areas[i];
+ if (area->floodvalid == cm.floodvalid) {
+ continue; // already flooded into
+ }
+ floodnum++;
+ CM_FloodArea_r (i, floodnum);
+ }
+
+}
+
+/*
+====================
+CM_AdjustAreaPortalState
+
+====================
+*/
+void CM_AdjustAreaPortalState( int area1, int area2, qboolean open ) {
+ if ( area1 < 0 || area2 < 0 ) {
+ return;
+ }
+
+ if ( area1 >= cm.numAreas || area2 >= cm.numAreas ) {
+ Com_Error (ERR_DROP, "CM_ChangeAreaPortalState: bad area number");
+ }
+
+ if ( open ) {
+ cm.areaPortals[ area1 * cm.numAreas + area2 ]++;
+ cm.areaPortals[ area2 * cm.numAreas + area1 ]++;
+ } else {
+ cm.areaPortals[ area1 * cm.numAreas + area2 ]--;
+ cm.areaPortals[ area2 * cm.numAreas + area1 ]--;
+ if ( cm.areaPortals[ area2 * cm.numAreas + area1 ] < 0 ) {
+ Com_Error (ERR_DROP, "CM_AdjustAreaPortalState: negative reference count");
+ }
+ }
+
+ CM_FloodAreaConnections ();
+}
+
+/*
+====================
+CM_AreasConnected
+
+====================
+*/
+qboolean CM_AreasConnected( int area1, int area2 ) {
+#ifndef BSPC
+ if ( cm_noAreas->integer ) {
+ return qtrue;
+ }
+#endif
+
+ if ( area1 < 0 || area2 < 0 ) {
+ return qfalse;
+ }
+
+ if (area1 >= cm.numAreas || area2 >= cm.numAreas) {
+ Com_Error (ERR_DROP, "area >= cm.numAreas");
+ }
+
+ if (cm.areas[area1].floodnum == cm.areas[area2].floodnum) {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+
+/*
+=================
+CM_WriteAreaBits
+
+Writes a bit vector of all the areas
+that are in the same flood as the area parameter
+Returns the number of bytes needed to hold all the bits.
+
+The bits are OR'd in, so you can CM_WriteAreaBits from multiple
+viewpoints and get the union of all visible areas.
+
+This is used to cull non-visible entities from snapshots
+=================
+*/
+int CM_WriteAreaBits (byte *buffer, int area)
+{
+ int i;
+ int floodnum;
+ int bytes;
+
+ bytes = (cm.numAreas+7)>>3;
+
+#ifndef BSPC
+ if (cm_noAreas->integer || area == -1)
+#else
+ if ( area == -1)
+#endif
+ { // for debugging, send everything
+ Com_Memset (buffer, 255, bytes);
+ }
+ else
+ {
+ floodnum = cm.areas[area].floodnum;
+ for (i=0 ; i<cm.numAreas ; i++)
+ {
+ if (cm.areas[i].floodnum == floodnum || area == -1)
+ buffer[i>>3] |= 1<<(i&7);
+ }
+ }
+
+ return bytes;
+}
+
diff --git a/src/qcommon/cm_trace.c b/src/qcommon/cm_trace.c
new file mode 100644
index 0000000..ee9540e
--- /dev/null
+++ b/src/qcommon/cm_trace.c
@@ -0,0 +1,1802 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+#include "cm_local.h"
+
+// always use bbox vs. bbox collision and never capsule vs. bbox or vice versa
+//#define ALWAYS_BBOX_VS_BBOX
+// always use capsule vs. capsule collision and never capsule vs. bbox or vice versa
+//#define ALWAYS_CAPSULE_VS_CAPSULE
+
+//#define CAPSULE_DEBUG
+
+/*
+===============================================================================
+
+BASIC MATH
+
+===============================================================================
+*/
+
+/*
+================
+RotatePoint
+================
+*/
+void RotatePoint(vec3_t point, /*const*/ vec3_t matrix[3]) { // bk: FIXME
+ vec3_t tvec;
+
+ VectorCopy(point, tvec);
+ point[0] = DotProduct(matrix[0], tvec);
+ point[1] = DotProduct(matrix[1], tvec);
+ point[2] = DotProduct(matrix[2], tvec);
+}
+
+/*
+================
+TransposeMatrix
+================
+*/
+void TransposeMatrix(/*const*/ vec3_t matrix[3], vec3_t transpose[3]) { // bk: FIXME
+ int i, j;
+ for (i = 0; i < 3; i++) {
+ for (j = 0; j < 3; j++) {
+ transpose[i][j] = matrix[j][i];
+ }
+ }
+}
+
+/*
+================
+CreateRotationMatrix
+================
+*/
+void CreateRotationMatrix(const vec3_t angles, vec3_t matrix[3]) {
+ AngleVectors(angles, matrix[0], matrix[1], matrix[2]);
+ VectorInverse(matrix[1]);
+}
+
+/*
+================
+CM_ProjectPointOntoVector
+================
+*/
+void CM_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vDir, vec3_t vProj )
+{
+ vec3_t pVec;
+
+ VectorSubtract( point, vStart, pVec );
+ // project onto the directional vector for this segment
+ VectorMA( vStart, DotProduct( pVec, vDir ), vDir, vProj );
+}
+
+/*
+================
+CM_DistanceFromLineSquared
+================
+*/
+float CM_DistanceFromLineSquared(vec3_t p, vec3_t lp1, vec3_t lp2, vec3_t dir) {
+ vec3_t proj, t;
+ int j;
+
+ CM_ProjectPointOntoVector(p, lp1, dir, proj);
+ for (j = 0; j < 3; j++)
+ if ((proj[j] > lp1[j] && proj[j] > lp2[j]) ||
+ (proj[j] < lp1[j] && proj[j] < lp2[j]))
+ break;
+ if (j < 3) {
+ if (fabs(proj[j] - lp1[j]) < fabs(proj[j] - lp2[j]))
+ VectorSubtract(p, lp1, t);
+ else
+ VectorSubtract(p, lp2, t);
+ return VectorLengthSquared(t);
+ }
+ VectorSubtract(p, proj, t);
+ return VectorLengthSquared(t);
+}
+
+/*
+================
+CM_VectorDistanceSquared
+================
+*/
+float CM_VectorDistanceSquared(vec3_t p1, vec3_t p2) {
+ vec3_t dir;
+
+ VectorSubtract(p2, p1, dir);
+ return VectorLengthSquared(dir);
+}
+
+/*
+================
+SquareRootFloat
+================
+*/
+float SquareRootFloat(float number) {
+ union {
+ float f;
+ int i;
+ } t;
+ float x, y;
+ const float f = 1.5F;
+
+ x = number * 0.5F;
+ t.f = number;
+ t.i = 0x5f3759df - ( t.i >> 1 );
+ y = t.f;
+ y = y * ( f - ( x * y * y ) );
+ y = y * ( f - ( x * y * y ) );
+ return number * y;
+}
+
+
+/*
+===============================================================================
+
+POSITION TESTING
+
+===============================================================================
+*/
+
+/*
+================
+CM_TestBoxInBrush
+================
+*/
+void CM_TestBoxInBrush( traceWork_t *tw, cbrush_t *brush ) {
+ int i;
+ cplane_t *plane;
+ float dist;
+ float d1;
+ cbrushside_t *side;
+ float t;
+ vec3_t startp;
+
+ if (!brush->numsides) {
+ return;
+ }
+
+ // special test for axial
+ if ( tw->bounds[0][0] > brush->bounds[1][0]
+ || tw->bounds[0][1] > brush->bounds[1][1]
+ || tw->bounds[0][2] > brush->bounds[1][2]
+ || tw->bounds[1][0] < brush->bounds[0][0]
+ || tw->bounds[1][1] < brush->bounds[0][1]
+ || tw->bounds[1][2] < brush->bounds[0][2]
+ ) {
+ return;
+ }
+
+ if ( tw->type == TT_CAPSULE ) {
+ // the first six planes are the axial planes, so we only
+ // need to test the remainder
+ for ( i = 6 ; i < brush->numsides ; i++ ) {
+ side = brush->sides + i;
+ plane = side->plane;
+
+ // adjust the plane distance apropriately for radius
+ dist = plane->dist + tw->sphere.radius;
+ // find the closest point on the capsule to the plane
+ t = DotProduct( plane->normal, tw->sphere.offset );
+ if ( t > 0 )
+ {
+ VectorSubtract( tw->start, tw->sphere.offset, startp );
+ }
+ else
+ {
+ VectorAdd( tw->start, tw->sphere.offset, startp );
+ }
+ d1 = DotProduct( startp, plane->normal ) - dist;
+ // if completely in front of face, no intersection
+ if ( d1 > 0 ) {
+ return;
+ }
+ }
+ } else {
+ // the first six planes are the axial planes, so we only
+ // need to test the remainder
+ for ( i = 6 ; i < brush->numsides ; i++ ) {
+ side = brush->sides + i;
+ plane = side->plane;
+
+ // adjust the plane distance apropriately for mins/maxs
+ dist = plane->dist - DotProduct( tw->offsets[ plane->signbits ], plane->normal );
+
+ d1 = DotProduct( tw->start, plane->normal ) - dist;
+
+ // if completely in front of face, no intersection
+ if ( d1 > 0 ) {
+ return;
+ }
+ }
+ }
+
+ // inside this brush
+ tw->trace.startsolid = tw->trace.allsolid = qtrue;
+ tw->trace.fraction = 0;
+ tw->trace.contents = brush->contents;
+}
+
+
+
+/*
+================
+CM_TestInLeaf
+================
+*/
+void CM_TestInLeaf( traceWork_t *tw, cLeaf_t *leaf ) {
+ int k;
+ int brushnum;
+ cbrush_t *b;
+ cPatch_t *patch;
+
+ // test box position against all brushes in the leaf
+ for (k=0 ; k<leaf->numLeafBrushes ; k++) {
+ brushnum = cm.leafbrushes[leaf->firstLeafBrush+k];
+ b = &cm.brushes[brushnum];
+ if (b->checkcount == cm.checkcount) {
+ continue; // already checked this brush in another leaf
+ }
+ b->checkcount = cm.checkcount;
+
+ if ( !(b->contents & tw->contents)) {
+ continue;
+ }
+
+ CM_TestBoxInBrush( tw, b );
+ if ( tw->trace.allsolid ) {
+ return;
+ }
+ }
+
+ // test against all patches
+#ifdef BSPC
+ if (1) {
+#else
+ if ( !cm_noCurves->integer ) {
+#endif //BSPC
+ for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) {
+ patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ];
+ if ( !patch ) {
+ continue;
+ }
+ if ( patch->checkcount == cm.checkcount ) {
+ continue; // already checked this brush in another leaf
+ }
+ patch->checkcount = cm.checkcount;
+
+ if ( !(patch->contents & tw->contents)) {
+ continue;
+ }
+
+ if ( CM_PositionTestInPatchCollide( tw, patch->pc ) ) {
+ tw->trace.startsolid = tw->trace.allsolid = qtrue;
+ tw->trace.fraction = 0;
+ tw->trace.contents = patch->contents;
+ return;
+ }
+ }
+ }
+}
+
+/*
+==================
+CM_TestCapsuleInCapsule
+
+capsule inside capsule check
+==================
+*/
+void CM_TestCapsuleInCapsule( traceWork_t *tw, clipHandle_t model ) {
+ int i;
+ vec3_t mins, maxs;
+ vec3_t top, bottom;
+ vec3_t p1, p2, tmp;
+ vec3_t offset, symetricSize[2];
+ float radius, halfwidth, halfheight, offs, r;
+
+ CM_ModelBounds(model, mins, maxs);
+
+ VectorAdd(tw->start, tw->sphere.offset, top);
+ VectorSubtract(tw->start, tw->sphere.offset, bottom);
+ for ( i = 0 ; i < 3 ; i++ ) {
+ offset[i] = ( mins[i] + maxs[i] ) * 0.5;
+ symetricSize[0][i] = mins[i] - offset[i];
+ symetricSize[1][i] = maxs[i] - offset[i];
+ }
+ halfwidth = symetricSize[ 1 ][ 0 ];
+ halfheight = symetricSize[ 1 ][ 2 ];
+ radius = ( halfwidth > halfheight ) ? halfheight : halfwidth;
+ offs = halfheight - radius;
+
+ r = Square(tw->sphere.radius + radius);
+ // check if any of the spheres overlap
+ VectorCopy(offset, p1);
+ p1[2] += offs;
+ VectorSubtract(p1, top, tmp);
+ if ( VectorLengthSquared(tmp) < r ) {
+ tw->trace.startsolid = tw->trace.allsolid = qtrue;
+ tw->trace.fraction = 0;
+ }
+ VectorSubtract(p1, bottom, tmp);
+ if ( VectorLengthSquared(tmp) < r ) {
+ tw->trace.startsolid = tw->trace.allsolid = qtrue;
+ tw->trace.fraction = 0;
+ }
+ VectorCopy(offset, p2);
+ p2[2] -= offs;
+ VectorSubtract(p2, top, tmp);
+ if ( VectorLengthSquared(tmp) < r ) {
+ tw->trace.startsolid = tw->trace.allsolid = qtrue;
+ tw->trace.fraction = 0;
+ }
+ VectorSubtract(p2, bottom, tmp);
+ if ( VectorLengthSquared(tmp) < r ) {
+ tw->trace.startsolid = tw->trace.allsolid = qtrue;
+ tw->trace.fraction = 0;
+ }
+ // if between cylinder up and lower bounds
+ if ( (top[2] >= p1[2] && top[2] <= p2[2]) ||
+ (bottom[2] >= p1[2] && bottom[2] <= p2[2]) ) {
+ // 2d coordinates
+ top[2] = p1[2] = 0;
+ // if the cylinders overlap
+ VectorSubtract(top, p1, tmp);
+ if ( VectorLengthSquared(tmp) < r ) {
+ tw->trace.startsolid = tw->trace.allsolid = qtrue;
+ tw->trace.fraction = 0;
+ }
+ }
+}
+
+/*
+==================
+CM_TestBoundingBoxInCapsule
+
+bounding box inside capsule check
+==================
+*/
+void CM_TestBoundingBoxInCapsule( traceWork_t *tw, clipHandle_t model ) {
+ vec3_t mins, maxs, offset, size[2];
+ clipHandle_t h;
+ cmodel_t *cmod;
+ int i;
+
+ // mins maxs of the capsule
+ CM_ModelBounds(model, mins, maxs);
+
+ // offset for capsule center
+ for ( i = 0 ; i < 3 ; i++ ) {
+ offset[i] = ( mins[i] + maxs[i] ) * 0.5;
+ size[0][i] = mins[i] - offset[i];
+ size[1][i] = maxs[i] - offset[i];
+ tw->start[i] -= offset[i];
+ tw->end[i] -= offset[i];
+ }
+
+ // replace the bounding box with the capsule
+ tw->type = TT_CAPSULE;
+ tw->sphere.radius = ( size[1][0] > size[1][2] ) ? size[1][2]: size[1][0];
+ tw->sphere.halfheight = size[1][2];
+ VectorSet( tw->sphere.offset, 0, 0, size[1][2] - tw->sphere.radius );
+
+ // replace the capsule with the bounding box
+ h = CM_TempBoxModel(tw->size[0], tw->size[1], qfalse);
+ // calculate collision
+ cmod = CM_ClipHandleToModel( h );
+ CM_TestInLeaf( tw, &cmod->leaf );
+}
+
+/*
+==================
+CM_PositionTest
+==================
+*/
+#define MAX_POSITION_LEAFS 1024
+void CM_PositionTest( traceWork_t *tw ) {
+ int leafs[MAX_POSITION_LEAFS];
+ int i;
+ leafList_t ll;
+
+ // identify the leafs we are touching
+ VectorAdd( tw->start, tw->size[0], ll.bounds[0] );
+ VectorAdd( tw->start, tw->size[1], ll.bounds[1] );
+
+ for (i=0 ; i<3 ; i++) {
+ ll.bounds[0][i] -= 1;
+ ll.bounds[1][i] += 1;
+ }
+
+ ll.count = 0;
+ ll.maxcount = MAX_POSITION_LEAFS;
+ ll.list = leafs;
+ ll.storeLeafs = CM_StoreLeafs;
+ ll.lastLeaf = 0;
+ ll.overflowed = qfalse;
+
+ cm.checkcount++;
+
+ CM_BoxLeafnums_r( &ll, 0 );
+
+
+ cm.checkcount++;
+
+ // test the contents of the leafs
+ for (i=0 ; i < ll.count ; i++) {
+ CM_TestInLeaf( tw, &cm.leafs[leafs[i]] );
+ if ( tw->trace.allsolid ) {
+ break;
+ }
+ }
+}
+
+/*
+===============================================================================
+
+TRACING
+
+===============================================================================
+*/
+
+/*
+================
+CM_TraceThroughPatch
+================
+*/
+
+void CM_TraceThroughPatch( traceWork_t *tw, cPatch_t *patch ) {
+ float oldFrac;
+
+ c_patch_traces++;
+
+ oldFrac = tw->trace.fraction;
+
+ CM_TraceThroughPatchCollide( tw, patch->pc );
+
+ if ( tw->trace.fraction < oldFrac ) {
+ tw->trace.surfaceFlags = patch->surfaceFlags;
+ tw->trace.contents = patch->contents;
+ }
+}
+
+/*
+================
+CM_TraceThroughBrush
+================
+*/
+void CM_TraceThroughBrush( traceWork_t *tw, cbrush_t *brush ) {
+ int i;
+ cplane_t *plane, *clipplane;
+ float dist;
+ float enterFrac, leaveFrac;
+ float d1, d2;
+ qboolean getout, startout;
+ float f;
+ cbrushside_t *side, *leadside;
+ float t;
+ vec3_t startp;
+ vec3_t endp;
+
+ enterFrac = -1.0;
+ leaveFrac = 1.0;
+ clipplane = NULL;
+
+ if ( !brush->numsides ) {
+ return;
+ }
+
+ c_brush_traces++;
+
+ getout = qfalse;
+ startout = qfalse;
+
+ leadside = NULL;
+
+ if( tw->type == TT_BISPHERE )
+ {
+ //
+ // compare the trace against all planes of the brush
+ // find the latest time the trace crosses a plane towards the interior
+ // and the earliest time the trace crosses a plane towards the exterior
+ //
+ for( i = 0; i < brush->numsides; i++ )
+ {
+ side = brush->sides + i;
+ plane = side->plane;
+
+ // adjust the plane distance apropriately for radius
+ d1 = DotProduct( tw->start, plane->normal ) -
+ ( plane->dist + tw->biSphere.startRadius );
+ d2 = DotProduct( tw->end, plane->normal ) -
+ ( plane->dist + tw->biSphere.endRadius );
+
+ if( d2 > 0 )
+ getout = qtrue; // endpoint is not in solid
+
+ if( d1 > 0 )
+ startout = qtrue;
+
+ // if completely in front of face, no intersection with the entire brush
+ if( d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) )
+ return;
+
+ // if it doesn't cross the plane, the plane isn't relevent
+ if( d1 <= 0 && d2 <= 0 )
+ continue;
+
+ brush->collided = qtrue;
+
+ // crosses face
+ if( d1 > d2 )
+ {
+ // enter
+ f = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 );
+
+ if( f < 0 )
+ f = 0;
+
+ if( f > enterFrac )
+ {
+ enterFrac = f;
+ clipplane = plane;
+ leadside = side;
+ }
+ }
+ else
+ {
+ // leave
+ f = ( d1 + SURFACE_CLIP_EPSILON ) / ( d1 - d2 );
+
+ if( f > 1 )
+ f = 1;
+
+ if( f < leaveFrac )
+ leaveFrac = f;
+ }
+ }
+ }
+ else if ( tw->type == TT_CAPSULE ) {
+ //
+ // compare the trace against all planes of the brush
+ // find the latest time the trace crosses a plane towards the interior
+ // and the earliest time the trace crosses a plane towards the exterior
+ //
+ for (i = 0; i < brush->numsides; i++) {
+ side = brush->sides + i;
+ plane = side->plane;
+
+ // adjust the plane distance apropriately for radius
+ dist = plane->dist + tw->sphere.radius;
+
+ // find the closest point on the capsule to the plane
+ t = DotProduct( plane->normal, tw->sphere.offset );
+ if ( t > 0 )
+ {
+ VectorSubtract( tw->start, tw->sphere.offset, startp );
+ VectorSubtract( tw->end, tw->sphere.offset, endp );
+ }
+ else
+ {
+ VectorAdd( tw->start, tw->sphere.offset, startp );
+ VectorAdd( tw->end, tw->sphere.offset, endp );
+ }
+
+ d1 = DotProduct( startp, plane->normal ) - dist;
+ d2 = DotProduct( endp, plane->normal ) - dist;
+
+ if (d2 > 0) {
+ getout = qtrue; // endpoint is not in solid
+ }
+ if (d1 > 0) {
+ startout = qtrue;
+ }
+
+ // if completely in front of face, no intersection with the entire brush
+ if (d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) {
+ return;
+ }
+
+ // if it doesn't cross the plane, the plane isn't relevent
+ if (d1 <= 0 && d2 <= 0 ) {
+ continue;
+ }
+
+ brush->collided = qtrue;
+
+ // crosses face
+ if (d1 > d2) { // enter
+ f = (d1-SURFACE_CLIP_EPSILON) / (d1-d2);
+ if ( f < 0 ) {
+ f = 0;
+ }
+ if (f > enterFrac) {
+ enterFrac = f;
+ clipplane = plane;
+ leadside = side;
+ }
+ } else { // leave
+ f = (d1+SURFACE_CLIP_EPSILON) / (d1-d2);
+ if ( f > 1 ) {
+ f = 1;
+ }
+ if (f < leaveFrac) {
+ leaveFrac = f;
+ }
+ }
+ }
+ } else {
+ //
+ // compare the trace against all planes of the brush
+ // find the latest time the trace crosses a plane towards the interior
+ // and the earliest time the trace crosses a plane towards the exterior
+ //
+ for (i = 0; i < brush->numsides; i++) {
+ side = brush->sides + i;
+ plane = side->plane;
+
+ // adjust the plane distance apropriately for mins/maxs
+ dist = plane->dist - DotProduct( tw->offsets[ plane->signbits ], plane->normal );
+
+ d1 = DotProduct( tw->start, plane->normal ) - dist;
+ d2 = DotProduct( tw->end, plane->normal ) - dist;
+
+ if (d2 > 0) {
+ getout = qtrue; // endpoint is not in solid
+ }
+ if (d1 > 0) {
+ startout = qtrue;
+ }
+
+ // if completely in front of face, no intersection with the entire brush
+ if (d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) {
+ return;
+ }
+
+ // if it doesn't cross the plane, the plane isn't relevent
+ if (d1 <= 0 && d2 <= 0 ) {
+ continue;
+ }
+
+ brush->collided = qtrue;
+
+ // crosses face
+ if (d1 > d2) { // enter
+ f = (d1-SURFACE_CLIP_EPSILON) / (d1-d2);
+ if ( f < 0 ) {
+ f = 0;
+ }
+ if (f > enterFrac) {
+ enterFrac = f;
+ clipplane = plane;
+ leadside = side;
+ }
+ } else { // leave
+ f = (d1+SURFACE_CLIP_EPSILON) / (d1-d2);
+ if ( f > 1 ) {
+ f = 1;
+ }
+ if (f < leaveFrac) {
+ leaveFrac = f;
+ }
+ }
+ }
+ }
+
+ //
+ // all planes have been checked, and the trace was not
+ // completely outside the brush
+ //
+ if (!startout) { // original point was inside brush
+ tw->trace.startsolid = qtrue;
+ if (!getout) {
+ tw->trace.allsolid = qtrue;
+ tw->trace.fraction = 0;
+ tw->trace.contents = brush->contents;
+ }
+ return;
+ }
+
+ if (enterFrac < leaveFrac) {
+ if (enterFrac > -1 && enterFrac < tw->trace.fraction) {
+ if (enterFrac < 0) {
+ enterFrac = 0;
+ }
+ tw->trace.fraction = enterFrac;
+ tw->trace.plane = *clipplane;
+ tw->trace.surfaceFlags = leadside->surfaceFlags;
+ tw->trace.contents = brush->contents;
+ }
+ }
+}
+
+/*
+================
+CM_ProximityToBrush
+================
+*/
+static void CM_ProximityToBrush( traceWork_t *tw, cbrush_t *brush )
+{
+ int i;
+ cbrushedge_t *edge;
+ float dist, minDist = 1e+10f;
+ float s, t;
+ float sAtMin = 0.0f;
+ float radius = 0.0f, fraction;
+ traceWork_t tw2;
+
+ // cheapish purely linear trace to test for intersection
+ Com_Memset( &tw2, 0, sizeof( tw2 ) );
+ tw2.trace.fraction = 1.0f;
+ tw2.type = TT_CAPSULE;
+ tw2.sphere.radius = 0.0f;
+ VectorClear( tw2.sphere.offset );
+ VectorCopy( tw->start, tw2.start );
+ VectorCopy( tw->end, tw2.end );
+
+ CM_TraceThroughBrush( &tw2, brush );
+
+ if( tw2.trace.fraction == 1.0f && !tw2.trace.allsolid && !tw2.trace.startsolid )
+ {
+ for( i = 0; i < brush->numEdges; i++ )
+ {
+ edge = &brush->edges[ i ];
+
+ dist = DistanceBetweenLineSegmentsSquared( tw->start, tw->end,
+ edge->p0, edge->p1, &s, &t );
+
+ if( dist < minDist )
+ {
+ minDist = dist;
+ sAtMin = s;
+ }
+ }
+
+ if( tw->type == TT_BISPHERE )
+ {
+ radius = tw->biSphere.startRadius +
+ ( sAtMin * ( tw->biSphere.endRadius - tw->biSphere.startRadius ) );
+ }
+ else if( tw->type == TT_CAPSULE )
+ {
+ radius = tw->sphere.radius;
+ }
+ else if( tw->type == TT_AABB )
+ {
+ //FIXME
+ }
+
+ fraction = minDist / ( radius * radius );
+
+ if( fraction < tw->trace.lateralFraction )
+ tw->trace.lateralFraction = fraction;
+ }
+ else
+ tw->trace.lateralFraction = 0.0f;
+}
+
+/*
+================
+CM_ProximityToPatch
+================
+*/
+static void CM_ProximityToPatch( traceWork_t *tw, cPatch_t *patch )
+{
+ traceWork_t tw2;
+
+ // cheapish purely linear trace to test for intersection
+ Com_Memset( &tw2, 0, sizeof( tw2 ) );
+ tw2.trace.fraction = 1.0f;
+ tw2.type = TT_CAPSULE;
+ tw2.sphere.radius = 0.0f;
+ VectorClear( tw2.sphere.offset );
+ VectorCopy( tw->start, tw2.start );
+ VectorCopy( tw->end, tw2.end );
+
+ CM_TraceThroughPatch( &tw2, patch );
+
+ if( tw2.trace.fraction == 1.0f && !tw2.trace.allsolid && !tw2.trace.startsolid )
+ {
+ //FIXME: implement me
+ }
+ else
+ tw->trace.lateralFraction = 0.0f;
+}
+
+/*
+================
+CM_TraceThroughLeaf
+================
+*/
+void CM_TraceThroughLeaf( traceWork_t *tw, cLeaf_t *leaf ) {
+ int k;
+ int brushnum;
+ cbrush_t *b;
+ cPatch_t *patch;
+
+ // trace line against all brushes in the leaf
+ for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) {
+ brushnum = cm.leafbrushes[leaf->firstLeafBrush+k];
+
+ b = &cm.brushes[brushnum];
+ if ( b->checkcount == cm.checkcount ) {
+ continue; // already checked this brush in another leaf
+ }
+ b->checkcount = cm.checkcount;
+
+ if ( !(b->contents & tw->contents) ) {
+ continue;
+ }
+
+ b->collided = qfalse;
+
+ CM_TraceThroughBrush( tw, b );
+ if ( !tw->trace.fraction ) {
+ tw->trace.lateralFraction = 0.0f;
+ return;
+ }
+ }
+
+ // trace line against all patches in the leaf
+#ifdef BSPC
+ if (1) {
+#else
+ if ( !cm_noCurves->integer ) {
+#endif
+ for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) {
+ patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ];
+ if ( !patch ) {
+ continue;
+ }
+ if ( patch->checkcount == cm.checkcount ) {
+ continue; // already checked this patch in another leaf
+ }
+ patch->checkcount = cm.checkcount;
+
+ if ( !(patch->contents & tw->contents) ) {
+ continue;
+ }
+
+ CM_TraceThroughPatch( tw, patch );
+
+ if ( !tw->trace.fraction ) {
+ tw->trace.lateralFraction = 0.0f;
+ return;
+ }
+ }
+ }
+
+ if( tw->testLateralCollision && tw->trace.fraction < 1.0f )
+ {
+ for( k = 0; k < leaf->numLeafBrushes; k++ )
+ {
+ brushnum = cm.leafbrushes[ leaf->firstLeafBrush + k ];
+
+ b = &cm.brushes[ brushnum ];
+
+ // This brush never collided, so don't bother
+ if( !b->collided )
+ continue;
+
+ if( !( b->contents & tw->contents ) )
+ continue;
+
+ CM_ProximityToBrush( tw, b );
+
+ if( !tw->trace.lateralFraction )
+ return;
+ }
+
+ for( k = 0; k < leaf->numLeafSurfaces; k++ )
+ {
+ patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ];
+ if( !patch )
+ continue;
+
+ if( !( patch->contents & tw->contents ) )
+ continue;
+
+ CM_ProximityToPatch( tw, patch );
+
+ if( !tw->trace.lateralFraction )
+ return;
+ }
+ }
+}
+
+#define RADIUS_EPSILON 1.0f
+
+/*
+================
+CM_TraceThroughSphere
+
+get the first intersection of the ray with the sphere
+================
+*/
+void CM_TraceThroughSphere( traceWork_t *tw, vec3_t origin, float radius, vec3_t start, vec3_t end ) {
+ float l1, l2, length, scale, fraction;
+ float a, b, c, d, sqrtd;
+ vec3_t v1, dir, intersection;
+
+ // if inside the sphere
+ VectorSubtract(start, origin, dir);
+ l1 = VectorLengthSquared(dir);
+ if (l1 < Square(radius)) {
+ tw->trace.fraction = 0;
+ tw->trace.startsolid = qtrue;
+ // test for allsolid
+ VectorSubtract(end, origin, dir);
+ l1 = VectorLengthSquared(dir);
+ if (l1 < Square(radius)) {
+ tw->trace.allsolid = qtrue;
+ }
+ return;
+ }
+ //
+ VectorSubtract(end, start, dir);
+ length = VectorNormalize(dir);
+ //
+ l1 = CM_DistanceFromLineSquared(origin, start, end, dir);
+ VectorSubtract(end, origin, v1);
+ l2 = VectorLengthSquared(v1);
+ // if no intersection with the sphere and the end point is at least an epsilon away
+ if (l1 >= Square(radius) && l2 > Square(radius+SURFACE_CLIP_EPSILON)) {
+ return;
+ }
+ //
+ // | origin - (start + t * dir) | = radius
+ // a = dir[0]^2 + dir[1]^2 + dir[2]^2;
+ // b = 2 * (dir[0] * (start[0] - origin[0]) + dir[1] * (start[1] - origin[1]) + dir[2] * (start[2] - origin[2]));
+ // c = (start[0] - origin[0])^2 + (start[1] - origin[1])^2 + (start[2] - origin[2])^2 - radius^2;
+ //
+ VectorSubtract(start, origin, v1);
+ // dir is normalized so a = 1
+ a = 1.0f;//dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2];
+ b = 2.0f * (dir[0] * v1[0] + dir[1] * v1[1] + dir[2] * v1[2]);
+ c = v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2] - (radius+RADIUS_EPSILON) * (radius+RADIUS_EPSILON);
+
+ d = b * b - 4.0f * c;// * a;
+ if (d > 0) {
+ sqrtd = SquareRootFloat(d);
+ // = (- b + sqrtd) * 0.5f; // / (2.0f * a);
+ fraction = (- b - sqrtd) * 0.5f; // / (2.0f * a);
+ //
+ if (fraction < 0) {
+ fraction = 0;
+ }
+ else {
+ fraction /= length;
+ }
+ if ( fraction < tw->trace.fraction ) {
+ tw->trace.fraction = fraction;
+ VectorSubtract(end, start, dir);
+ VectorMA(start, fraction, dir, intersection);
+ VectorSubtract(intersection, origin, dir);
+ #ifdef CAPSULE_DEBUG
+ l2 = VectorLength(dir);
+ if (l2 < radius) {
+ int bah = 1;
+ }
+ #endif
+ scale = 1 / (radius+RADIUS_EPSILON);
+ VectorScale(dir, scale, dir);
+ VectorCopy(dir, tw->trace.plane.normal);
+ VectorAdd( tw->modelOrigin, intersection, intersection);
+ tw->trace.plane.dist = DotProduct(tw->trace.plane.normal, intersection);
+ tw->trace.contents = CONTENTS_BODY;
+ }
+ }
+ else if (d == 0) {
+ //t1 = (- b ) / 2;
+ // slide along the sphere
+ }
+ // no intersection at all
+}
+
+/*
+================
+CM_TraceThroughVerticalCylinder
+
+get the first intersection of the ray with the cylinder
+the cylinder extends halfheight above and below the origin
+================
+*/
+void CM_TraceThroughVerticalCylinder( traceWork_t *tw, vec3_t origin, float radius, float halfheight, vec3_t start, vec3_t end) {
+ float length, scale, fraction, l1, l2;
+ float a, b, c, d, sqrtd;
+ vec3_t v1, dir, start2d, end2d, org2d, intersection;
+
+ // 2d coordinates
+ VectorSet(start2d, start[0], start[1], 0);
+ VectorSet(end2d, end[0], end[1], 0);
+ VectorSet(org2d, origin[0], origin[1], 0);
+ // if between lower and upper cylinder bounds
+ if (start[2] <= origin[2] + halfheight &&
+ start[2] >= origin[2] - halfheight) {
+ // if inside the cylinder
+ VectorSubtract(start2d, org2d, dir);
+ l1 = VectorLengthSquared(dir);
+ if (l1 < Square(radius)) {
+ tw->trace.fraction = 0;
+ tw->trace.startsolid = qtrue;
+ VectorSubtract(end2d, org2d, dir);
+ l1 = VectorLengthSquared(dir);
+ if (l1 < Square(radius)) {
+ tw->trace.allsolid = qtrue;
+ }
+ return;
+ }
+ }
+ //
+ VectorSubtract(end2d, start2d, dir);
+ length = VectorNormalize(dir);
+ //
+ l1 = CM_DistanceFromLineSquared(org2d, start2d, end2d, dir);
+ VectorSubtract(end2d, org2d, v1);
+ l2 = VectorLengthSquared(v1);
+ // if no intersection with the cylinder and the end point is at least an epsilon away
+ if (l1 >= Square(radius) && l2 > Square(radius+SURFACE_CLIP_EPSILON)) {
+ return;
+ }
+ //
+ //
+ // (start[0] - origin[0] - t * dir[0]) ^ 2 + (start[1] - origin[1] - t * dir[1]) ^ 2 = radius ^ 2
+ // (v1[0] + t * dir[0]) ^ 2 + (v1[1] + t * dir[1]) ^ 2 = radius ^ 2;
+ // v1[0] ^ 2 + 2 * v1[0] * t * dir[0] + (t * dir[0]) ^ 2 +
+ // v1[1] ^ 2 + 2 * v1[1] * t * dir[1] + (t * dir[1]) ^ 2 = radius ^ 2
+ // t ^ 2 * (dir[0] ^ 2 + dir[1] ^ 2) + t * (2 * v1[0] * dir[0] + 2 * v1[1] * dir[1]) +
+ // v1[0] ^ 2 + v1[1] ^ 2 - radius ^ 2 = 0
+ //
+ VectorSubtract(start, origin, v1);
+ // dir is normalized so we can use a = 1
+ a = 1.0f;// * (dir[0] * dir[0] + dir[1] * dir[1]);
+ b = 2.0f * (v1[0] * dir[0] + v1[1] * dir[1]);
+ c = v1[0] * v1[0] + v1[1] * v1[1] - (radius+RADIUS_EPSILON) * (radius+RADIUS_EPSILON);
+
+ d = b * b - 4.0f * c;// * a;
+ if (d > 0) {
+ sqrtd = SquareRootFloat(d);
+ // = (- b + sqrtd) * 0.5f;// / (2.0f * a);
+ fraction = (- b - sqrtd) * 0.5f;// / (2.0f * a);
+ //
+ if (fraction < 0) {
+ fraction = 0;
+ }
+ else {
+ fraction /= length;
+ }
+ if ( fraction < tw->trace.fraction ) {
+ VectorSubtract(end, start, dir);
+ VectorMA(start, fraction, dir, intersection);
+ // if the intersection is between the cylinder lower and upper bound
+ if (intersection[2] <= origin[2] + halfheight &&
+ intersection[2] >= origin[2] - halfheight) {
+ //
+ tw->trace.fraction = fraction;
+ VectorSubtract(intersection, origin, dir);
+ dir[2] = 0;
+ #ifdef CAPSULE_DEBUG
+ l2 = VectorLength(dir);
+ if (l2 <= radius) {
+ int bah = 1;
+ }
+ #endif
+ scale = 1 / (radius+RADIUS_EPSILON);
+ VectorScale(dir, scale, dir);
+ VectorCopy(dir, tw->trace.plane.normal);
+ VectorAdd( tw->modelOrigin, intersection, intersection);
+ tw->trace.plane.dist = DotProduct(tw->trace.plane.normal, intersection);
+ tw->trace.contents = CONTENTS_BODY;
+ }
+ }
+ }
+ else if (d == 0) {
+ //t[0] = (- b ) / 2 * a;
+ // slide along the cylinder
+ }
+ // no intersection at all
+}
+
+/*
+================
+CM_TraceCapsuleThroughCapsule
+
+capsule vs. capsule collision (not rotated)
+================
+*/
+void CM_TraceCapsuleThroughCapsule( traceWork_t *tw, clipHandle_t model ) {
+ int i;
+ vec3_t mins, maxs;
+ vec3_t top, bottom, starttop, startbottom, endtop, endbottom;
+ vec3_t offset, symetricSize[2];
+ float radius, halfwidth, halfheight, offs, h;
+
+ CM_ModelBounds(model, mins, maxs);
+ // test trace bounds vs. capsule bounds
+ if ( tw->bounds[0][0] > maxs[0] + RADIUS_EPSILON
+ || tw->bounds[0][1] > maxs[1] + RADIUS_EPSILON
+ || tw->bounds[0][2] > maxs[2] + RADIUS_EPSILON
+ || tw->bounds[1][0] < mins[0] - RADIUS_EPSILON
+ || tw->bounds[1][1] < mins[1] - RADIUS_EPSILON
+ || tw->bounds[1][2] < mins[2] - RADIUS_EPSILON
+ ) {
+ return;
+ }
+ // top origin and bottom origin of each sphere at start and end of trace
+ VectorAdd(tw->start, tw->sphere.offset, starttop);
+ VectorSubtract(tw->start, tw->sphere.offset, startbottom);
+ VectorAdd(tw->end, tw->sphere.offset, endtop);
+ VectorSubtract(tw->end, tw->sphere.offset, endbottom);
+
+ // calculate top and bottom of the capsule spheres to collide with
+ for ( i = 0 ; i < 3 ; i++ ) {
+ offset[i] = ( mins[i] + maxs[i] ) * 0.5;
+ symetricSize[0][i] = mins[i] - offset[i];
+ symetricSize[1][i] = maxs[i] - offset[i];
+ }
+ halfwidth = symetricSize[ 1 ][ 0 ];
+ halfheight = symetricSize[ 1 ][ 2 ];
+ radius = ( halfwidth > halfheight ) ? halfheight : halfwidth;
+ offs = halfheight - radius;
+ VectorCopy(offset, top);
+ top[2] += offs;
+ VectorCopy(offset, bottom);
+ bottom[2] -= offs;
+ // expand radius of spheres
+ radius += tw->sphere.radius;
+ // if there is horizontal movement
+ if ( tw->start[0] != tw->end[0] || tw->start[1] != tw->end[1] ) {
+ // height of the expanded cylinder is the height of both cylinders minus the radius of both spheres
+ h = halfheight + tw->sphere.halfheight - radius;
+ // if the cylinder has a height
+ if ( h > 0 ) {
+ // test for collisions between the cylinders
+ CM_TraceThroughVerticalCylinder(tw, offset, radius, h, tw->start, tw->end);
+ }
+ }
+ // test for collision between the spheres
+ CM_TraceThroughSphere(tw, top, radius, startbottom, endbottom);
+ CM_TraceThroughSphere(tw, bottom, radius, starttop, endtop);
+}
+
+/*
+================
+CM_TraceBoundingBoxThroughCapsule
+
+bounding box vs. capsule collision
+================
+*/
+void CM_TraceBoundingBoxThroughCapsule( traceWork_t *tw, clipHandle_t model ) {
+ vec3_t mins, maxs, offset, size[2];
+ clipHandle_t h;
+ cmodel_t *cmod;
+ int i;
+
+ // mins maxs of the capsule
+ CM_ModelBounds(model, mins, maxs);
+
+ // offset for capsule center
+ for ( i = 0 ; i < 3 ; i++ ) {
+ offset[i] = ( mins[i] + maxs[i] ) * 0.5;
+ size[0][i] = mins[i] - offset[i];
+ size[1][i] = maxs[i] - offset[i];
+ tw->start[i] -= offset[i];
+ tw->end[i] -= offset[i];
+ }
+
+ // replace the bounding box with the capsule
+ tw->type = TT_CAPSULE;
+ tw->sphere.radius = ( size[1][0] > size[1][2] ) ? size[1][2]: size[1][0];
+ tw->sphere.halfheight = size[1][2];
+ VectorSet( tw->sphere.offset, 0, 0, size[1][2] - tw->sphere.radius );
+
+ // replace the capsule with the bounding box
+ h = CM_TempBoxModel(tw->size[0], tw->size[1], qfalse);
+ // calculate collision
+ cmod = CM_ClipHandleToModel( h );
+ CM_TraceThroughLeaf( tw, &cmod->leaf );
+}
+
+//=========================================================================================
+
+/*
+==================
+CM_TraceThroughTree
+
+Traverse all the contacted leafs from the start to the end position.
+If the trace is a point, they will be exactly in order, but for larger
+trace volumes it is possible to hit something in a later leaf with
+a smaller intercept fraction.
+==================
+*/
+void CM_TraceThroughTree( traceWork_t *tw, int num, float p1f, float p2f, vec3_t p1, vec3_t p2) {
+ cNode_t *node;
+ cplane_t *plane;
+ float t1, t2, offset;
+ float frac, frac2;
+ float idist;
+ vec3_t mid;
+ int side;
+ float midf;
+
+ if (tw->trace.fraction <= p1f) {
+ return; // already hit something nearer
+ }
+
+ // if < 0, we are in a leaf node
+ if (num < 0) {
+ CM_TraceThroughLeaf( tw, &cm.leafs[-1-num] );
+ return;
+ }
+
+ //
+ // find the point distances to the seperating plane
+ // and the offset for the size of the box
+ //
+ node = cm.nodes + num;
+ plane = node->plane;
+
+ // adjust the plane distance apropriately for mins/maxs
+ if ( plane->type < 3 ) {
+ t1 = p1[plane->type] - plane->dist;
+ t2 = p2[plane->type] - plane->dist;
+ offset = tw->extents[plane->type];
+ } else {
+ t1 = DotProduct (plane->normal, p1) - plane->dist;
+ t2 = DotProduct (plane->normal, p2) - plane->dist;
+ if ( tw->isPoint ) {
+ offset = 0;
+ } else {
+#if 0 // bk010201 - DEAD
+ // an axial brush right behind a slanted bsp plane
+ // will poke through when expanded, so adjust
+ // by sqrt(3)
+ offset = fabs(tw->extents[0]*plane->normal[0]) +
+ fabs(tw->extents[1]*plane->normal[1]) +
+ fabs(tw->extents[2]*plane->normal[2]);
+
+ offset *= 2;
+ offset = tw->maxOffset;
+#endif
+ // this is silly
+ offset = 2048;
+ }
+ }
+
+ // see which sides we need to consider
+ if ( t1 >= offset + 1 && t2 >= offset + 1 ) {
+ CM_TraceThroughTree( tw, node->children[0], p1f, p2f, p1, p2 );
+ return;
+ }
+ if ( t1 < -offset - 1 && t2 < -offset - 1 ) {
+ CM_TraceThroughTree( tw, node->children[1], p1f, p2f, p1, p2 );
+ return;
+ }
+
+ // put the crosspoint SURFACE_CLIP_EPSILON pixels on the near side
+ if ( t1 < t2 ) {
+ idist = 1.0/(t1-t2);
+ side = 1;
+ frac2 = (t1 + offset + SURFACE_CLIP_EPSILON)*idist;
+ frac = (t1 - offset + SURFACE_CLIP_EPSILON)*idist;
+ } else if (t1 > t2) {
+ idist = 1.0/(t1-t2);
+ side = 0;
+ frac2 = (t1 - offset - SURFACE_CLIP_EPSILON)*idist;
+ frac = (t1 + offset + SURFACE_CLIP_EPSILON)*idist;
+ } else {
+ side = 0;
+ frac = 1;
+ frac2 = 0;
+ }
+
+ // move up to the node
+ if ( frac < 0 ) {
+ frac = 0;
+ }
+ if ( frac > 1 ) {
+ frac = 1;
+ }
+
+ midf = p1f + (p2f - p1f)*frac;
+
+ mid[0] = p1[0] + frac*(p2[0] - p1[0]);
+ mid[1] = p1[1] + frac*(p2[1] - p1[1]);
+ mid[2] = p1[2] + frac*(p2[2] - p1[2]);
+
+ CM_TraceThroughTree( tw, node->children[side], p1f, midf, p1, mid );
+
+
+ // go past the node
+ if ( frac2 < 0 ) {
+ frac2 = 0;
+ }
+ if ( frac2 > 1 ) {
+ frac2 = 1;
+ }
+
+ midf = p1f + (p2f - p1f)*frac2;
+
+ mid[0] = p1[0] + frac2*(p2[0] - p1[0]);
+ mid[1] = p1[1] + frac2*(p2[1] - p1[1]);
+ mid[2] = p1[2] + frac2*(p2[2] - p1[2]);
+
+ CM_TraceThroughTree( tw, node->children[side^1], midf, p2f, mid, p2 );
+}
+
+//======================================================================
+
+
+/*
+==================
+CM_Trace
+==================
+*/
+void CM_Trace( trace_t *results, const vec3_t start,
+ const vec3_t end, vec3_t mins, vec3_t maxs,
+ clipHandle_t model, const vec3_t origin, int brushmask,
+ traceType_t type, sphere_t *sphere ) {
+ int i;
+ traceWork_t tw;
+ vec3_t offset;
+ cmodel_t *cmod;
+
+ cmod = CM_ClipHandleToModel( model );
+
+ cm.checkcount++; // for multi-check avoidance
+
+ c_traces++; // for statistics, may be zeroed
+
+ // fill in a default trace
+ Com_Memset( &tw, 0, sizeof(tw) );
+ tw.trace.fraction = 1; // assume it goes the entire distance until shown otherwise
+ VectorCopy(origin, tw.modelOrigin);
+ tw.type = type;
+
+ if (!cm.numNodes) {
+ *results = tw.trace;
+
+ return; // map not loaded, shouldn't happen
+ }
+
+ // allow NULL to be passed in for 0,0,0
+ if ( !mins ) {
+ mins = vec3_origin;
+ }
+ if ( !maxs ) {
+ maxs = vec3_origin;
+ }
+
+ // set basic parms
+ tw.contents = brushmask;
+
+ // adjust so that mins and maxs are always symetric, which
+ // avoids some complications with plane expanding of rotated
+ // bmodels
+ for ( i = 0 ; i < 3 ; i++ ) {
+ offset[i] = ( mins[i] + maxs[i] ) * 0.5;
+ tw.size[0][i] = mins[i] - offset[i];
+ tw.size[1][i] = maxs[i] - offset[i];
+ tw.start[i] = start[i] + offset[i];
+ tw.end[i] = end[i] + offset[i];
+ }
+
+ // if a sphere is already specified
+ if ( sphere ) {
+ tw.sphere = *sphere;
+ }
+ else {
+ tw.sphere.radius = ( tw.size[1][0] > tw.size[1][2] ) ? tw.size[1][2]: tw.size[1][0];
+ tw.sphere.halfheight = tw.size[1][2];
+ VectorSet( tw.sphere.offset, 0, 0, tw.size[1][2] - tw.sphere.radius );
+ }
+
+ tw.maxOffset = tw.size[1][0] + tw.size[1][1] + tw.size[1][2];
+
+ // tw.offsets[signbits] = vector to apropriate corner from origin
+ tw.offsets[0][0] = tw.size[0][0];
+ tw.offsets[0][1] = tw.size[0][1];
+ tw.offsets[0][2] = tw.size[0][2];
+
+ tw.offsets[1][0] = tw.size[1][0];
+ tw.offsets[1][1] = tw.size[0][1];
+ tw.offsets[1][2] = tw.size[0][2];
+
+ tw.offsets[2][0] = tw.size[0][0];
+ tw.offsets[2][1] = tw.size[1][1];
+ tw.offsets[2][2] = tw.size[0][2];
+
+ tw.offsets[3][0] = tw.size[1][0];
+ tw.offsets[3][1] = tw.size[1][1];
+ tw.offsets[3][2] = tw.size[0][2];
+
+ tw.offsets[4][0] = tw.size[0][0];
+ tw.offsets[4][1] = tw.size[0][1];
+ tw.offsets[4][2] = tw.size[1][2];
+
+ tw.offsets[5][0] = tw.size[1][0];
+ tw.offsets[5][1] = tw.size[0][1];
+ tw.offsets[5][2] = tw.size[1][2];
+
+ tw.offsets[6][0] = tw.size[0][0];
+ tw.offsets[6][1] = tw.size[1][1];
+ tw.offsets[6][2] = tw.size[1][2];
+
+ tw.offsets[7][0] = tw.size[1][0];
+ tw.offsets[7][1] = tw.size[1][1];
+ tw.offsets[7][2] = tw.size[1][2];
+
+ //
+ // calculate bounds
+ //
+ if ( tw.type == TT_CAPSULE ) {
+ for ( i = 0 ; i < 3 ; i++ ) {
+ if ( tw.start[i] < tw.end[i] ) {
+ tw.bounds[0][i] = tw.start[i] - fabs(tw.sphere.offset[i]) - tw.sphere.radius;
+ tw.bounds[1][i] = tw.end[i] + fabs(tw.sphere.offset[i]) + tw.sphere.radius;
+ } else {
+ tw.bounds[0][i] = tw.end[i] - fabs(tw.sphere.offset[i]) - tw.sphere.radius;
+ tw.bounds[1][i] = tw.start[i] + fabs(tw.sphere.offset[i]) + tw.sphere.radius;
+ }
+ }
+ }
+ else {
+ for ( i = 0 ; i < 3 ; i++ ) {
+ if ( tw.start[i] < tw.end[i] ) {
+ tw.bounds[0][i] = tw.start[i] + tw.size[0][i];
+ tw.bounds[1][i] = tw.end[i] + tw.size[1][i];
+ } else {
+ tw.bounds[0][i] = tw.end[i] + tw.size[0][i];
+ tw.bounds[1][i] = tw.start[i] + tw.size[1][i];
+ }
+ }
+ }
+
+ //
+ // check for position test special case
+ //
+ if (start[0] == end[0] && start[1] == end[1] && start[2] == end[2]) {
+ if ( model ) {
+#ifdef ALWAYS_BBOX_VS_BBOX // bk010201 - FIXME - compile time flag?
+ if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) {
+ tw.type = TT_AABB;
+ CM_TestInLeaf( &tw, &cmod->leaf );
+ }
+ else
+#elif defined(ALWAYS_CAPSULE_VS_CAPSULE)
+ if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) {
+ CM_TestCapsuleInCapsule( &tw, model );
+ }
+ else
+#endif
+ if ( model == CAPSULE_MODEL_HANDLE ) {
+ if ( tw.type == TT_CAPSULE ) {
+ CM_TestCapsuleInCapsule( &tw, model );
+ }
+ else {
+ CM_TestBoundingBoxInCapsule( &tw, model );
+ }
+ }
+ else {
+ CM_TestInLeaf( &tw, &cmod->leaf );
+ }
+ } else {
+ CM_PositionTest( &tw );
+ }
+ } else {
+ //
+ // check for point special case
+ //
+ if ( tw.size[0][0] == 0 && tw.size[0][1] == 0 && tw.size[0][2] == 0 ) {
+ tw.isPoint = qtrue;
+ VectorClear( tw.extents );
+ } else {
+ tw.isPoint = qfalse;
+ tw.extents[0] = tw.size[1][0];
+ tw.extents[1] = tw.size[1][1];
+ tw.extents[2] = tw.size[1][2];
+ }
+
+ //
+ // general sweeping through world
+ //
+ if ( model ) {
+#ifdef ALWAYS_BBOX_VS_BBOX
+ if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) {
+ tw.type = TT_AABB;
+ CM_TraceThroughLeaf( &tw, &cmod->leaf );
+ }
+ else
+#elif defined(ALWAYS_CAPSULE_VS_CAPSULE)
+ if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) {
+ CM_TraceCapsuleThroughCapsule( &tw, model );
+ }
+ else
+#endif
+ if ( model == CAPSULE_MODEL_HANDLE ) {
+ if ( tw.type == TT_CAPSULE ) {
+ CM_TraceCapsuleThroughCapsule( &tw, model );
+ }
+ else {
+ CM_TraceBoundingBoxThroughCapsule( &tw, model );
+ }
+ }
+ else {
+ CM_TraceThroughLeaf( &tw, &cmod->leaf );
+ }
+ } else {
+ CM_TraceThroughTree( &tw, 0, 0, 1, tw.start, tw.end );
+ }
+ }
+
+ // generate endpos from the original, unmodified start/end
+ if ( tw.trace.fraction == 1 ) {
+ VectorCopy (end, tw.trace.endpos);
+ } else {
+ for ( i=0 ; i<3 ; i++ ) {
+ tw.trace.endpos[i] = start[i] + tw.trace.fraction * (end[i] - start[i]);
+ }
+ }
+
+ // If allsolid is set (was entirely inside something solid), the plane is not valid.
+ // If fraction == 1.0, we never hit anything, and thus the plane is not valid.
+ // Otherwise, the normal on the plane should have unit length
+ assert(tw.trace.allsolid ||
+ tw.trace.fraction == 1.0 ||
+ VectorLengthSquared(tw.trace.plane.normal) > 0.9999);
+ *results = tw.trace;
+}
+
+/*
+==================
+CM_BoxTrace
+==================
+*/
+void CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end,
+ vec3_t mins, vec3_t maxs,
+ clipHandle_t model, int brushmask, traceType_t type ) {
+ CM_Trace( results, start, end, mins, maxs, model, vec3_origin, brushmask, type, NULL );
+}
+
+/*
+==================
+CM_TransformedBoxTrace
+
+Handles offseting and rotation of the end points for moving and
+rotating entities
+==================
+*/
+void CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end,
+ vec3_t mins, vec3_t maxs,
+ clipHandle_t model, int brushmask,
+ const vec3_t origin, const vec3_t angles, traceType_t type ) {
+ trace_t trace;
+ vec3_t start_l, end_l;
+ qboolean rotated;
+ vec3_t offset;
+ vec3_t symetricSize[2];
+ vec3_t matrix[3], transpose[3];
+ int i;
+ float halfwidth;
+ float halfheight;
+ float t;
+ sphere_t sphere;
+
+ if ( !mins ) {
+ mins = vec3_origin;
+ }
+ if ( !maxs ) {
+ maxs = vec3_origin;
+ }
+
+ // adjust so that mins and maxs are always symetric, which
+ // avoids some complications with plane expanding of rotated
+ // bmodels
+ for ( i = 0 ; i < 3 ; i++ ) {
+ offset[i] = ( mins[i] + maxs[i] ) * 0.5;
+ symetricSize[0][i] = mins[i] - offset[i];
+ symetricSize[1][i] = maxs[i] - offset[i];
+ start_l[i] = start[i] + offset[i];
+ end_l[i] = end[i] + offset[i];
+ }
+
+ // subtract origin offset
+ VectorSubtract( start_l, origin, start_l );
+ VectorSubtract( end_l, origin, end_l );
+
+ // rotate start and end into the models frame of reference
+ if ( model != BOX_MODEL_HANDLE &&
+ (angles[0] || angles[1] || angles[2]) ) {
+ rotated = qtrue;
+ } else {
+ rotated = qfalse;
+ }
+
+ halfwidth = symetricSize[ 1 ][ 0 ];
+ halfheight = symetricSize[ 1 ][ 2 ];
+
+ sphere.radius = ( halfwidth > halfheight ) ? halfheight : halfwidth;
+ sphere.halfheight = halfheight;
+ t = halfheight - sphere.radius;
+
+ if (rotated) {
+ // rotation on trace line (start-end) instead of rotating the bmodel
+ // NOTE: This is still incorrect for bounding boxes because the actual bounding
+ // box that is swept through the model is not rotated. We cannot rotate
+ // the bounding box or the bmodel because that would make all the brush
+ // bevels invalid.
+ // However this is correct for capsules since a capsule itself is rotated too.
+ CreateRotationMatrix(angles, matrix);
+ RotatePoint(start_l, matrix);
+ RotatePoint(end_l, matrix);
+ // rotated sphere offset for capsule
+ sphere.offset[0] = matrix[0][ 2 ] * t;
+ sphere.offset[1] = -matrix[1][ 2 ] * t;
+ sphere.offset[2] = matrix[2][ 2 ] * t;
+ }
+ else {
+ VectorSet( sphere.offset, 0, 0, t );
+ }
+
+ // sweep the box through the model
+ CM_Trace( &trace, start_l, end_l, symetricSize[0], symetricSize[1],
+ model, origin, brushmask, type, &sphere );
+
+ // if the bmodel was rotated and there was a collision
+ if ( rotated && trace.fraction != 1.0 ) {
+ // rotation of bmodel collision plane
+ TransposeMatrix(matrix, transpose);
+ RotatePoint(trace.plane.normal, transpose);
+ }
+
+ // re-calculate the end position of the trace because the trace.endpos
+ // calculated by CM_Trace could be rotated and have an offset
+ trace.endpos[0] = start[0] + trace.fraction * (end[0] - start[0]);
+ trace.endpos[1] = start[1] + trace.fraction * (end[1] - start[1]);
+ trace.endpos[2] = start[2] + trace.fraction * (end[2] - start[2]);
+
+ *results = trace;
+}
+
+/*
+==================
+CM_BiSphereTrace
+==================
+*/
+void CM_BiSphereTrace( trace_t *results, const vec3_t start,
+ const vec3_t end, float startRad, float endRad,
+ clipHandle_t model, int mask )
+{
+ int i;
+ traceWork_t tw;
+ float largestRadius = startRad > endRad ? startRad : endRad;
+ cmodel_t *cmod;
+
+ cmod = CM_ClipHandleToModel( model );
+
+ cm.checkcount++; // for multi-check avoidance
+
+ c_traces++; // for statistics, may be zeroed
+
+ // fill in a default trace
+ Com_Memset( &tw, 0, sizeof( tw ) );
+ tw.trace.fraction = 1.0f; // assume it goes the entire distance until shown otherwise
+ VectorCopy( vec3_origin, tw.modelOrigin );
+ tw.type = TT_BISPHERE;
+ tw.testLateralCollision = qtrue;
+ tw.trace.lateralFraction = 1.0f;
+
+ if( !cm.numNodes )
+ {
+ *results = tw.trace;
+
+ return; // map not loaded, shouldn't happen
+ }
+
+ // set basic parms
+ tw.contents = mask;
+
+ VectorCopy( start, tw.start );
+ VectorCopy( end, tw.end );
+
+ tw.biSphere.startRadius = startRad;
+ tw.biSphere.endRadius = endRad;
+
+ //
+ // calculate bounds
+ //
+ for( i = 0 ; i < 3 ; i++ )
+ {
+ if( tw.start[ i ] < tw.end[ i ] )
+ {
+ tw.bounds[ 0 ][ i ] = tw.start[ i ] - tw.biSphere.startRadius;
+ tw.bounds[ 1 ][ i ] = tw.end[ i ] + tw.biSphere.endRadius;
+ }
+ else
+ {
+ tw.bounds[ 0 ][ i ] = tw.end[ i ] + tw.biSphere.endRadius;
+ tw.bounds[ 1 ][ i ] = tw.start[ i ] - tw.biSphere.startRadius;
+ }
+ }
+
+ tw.isPoint = qfalse;
+ tw.extents[ 0 ] = largestRadius;
+ tw.extents[ 1 ] = largestRadius;
+ tw.extents[ 2 ] = largestRadius;
+
+ //
+ // general sweeping through world
+ //
+ if( model )
+ CM_TraceThroughLeaf( &tw, &cmod->leaf );
+ else
+ CM_TraceThroughTree( &tw, 0, 0.0f, 1.0f, tw.start, tw.end );
+
+ // generate endpos from the original, unmodified start/end
+ if( tw.trace.fraction == 1.0f )
+ {
+ VectorCopy( end, tw.trace.endpos );
+ }
+ else
+ {
+ for( i = 0; i < 3; i++ )
+ tw.trace.endpos[ i ] = start[ i ] + tw.trace.fraction * ( end[ i ] - start[ i ] );
+ }
+
+ // If allsolid is set (was entirely inside something solid), the plane is not valid.
+ // If fraction == 1.0, we never hit anything, and thus the plane is not valid.
+ // Otherwise, the normal on the plane should have unit length
+ assert( tw.trace.allsolid ||
+ tw.trace.fraction == 1.0 ||
+ VectorLengthSquared(tw.trace.plane.normal ) > 0.9999 );
+
+ *results = tw.trace;
+}
+
+/*
+==================
+CM_TransformedBiSphereTrace
+
+Handles offseting and rotation of the end points for moving and
+rotating entities
+==================
+*/
+void CM_TransformedBiSphereTrace( trace_t *results, const vec3_t start,
+ const vec3_t end, float startRad, float endRad,
+ clipHandle_t model, int mask,
+ const vec3_t origin )
+{
+ trace_t trace;
+ vec3_t start_l, end_l;
+
+ // subtract origin offset
+ VectorSubtract( start, origin, start_l );
+ VectorSubtract( end, origin, end_l );
+
+ CM_BiSphereTrace( &trace, start_l, end_l, startRad, endRad, model, mask );
+
+ // re-calculate the end position of the trace because the trace.endpos
+ // calculated by CM_BiSphereTrace could be rotated and have an offset
+ trace.endpos[0] = start[0] + trace.fraction * (end[0] - start[0]);
+ trace.endpos[1] = start[1] + trace.fraction * (end[1] - start[1]);
+ trace.endpos[2] = start[2] + trace.fraction * (end[2] - start[2]);
+
+ *results = trace;
+}
diff --git a/src/qcommon/cmd.c b/src/qcommon/cmd.c
new file mode 100644
index 0000000..7495bf1
--- /dev/null
+++ b/src/qcommon/cmd.c
@@ -0,0 +1,771 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// cmd.c -- Quake script command processing module
+
+#include "q_shared.h"
+#include "qcommon.h"
+
+#define MAX_CMD_BUFFER 16384
+#define MAX_CMD_LINE 1024
+
+typedef struct {
+ byte *data;
+ int maxsize;
+ int cursize;
+} cmd_t;
+
+int cmd_wait;
+cmd_t cmd_text;
+byte cmd_text_buf[MAX_CMD_BUFFER];
+
+
+//=============================================================================
+
+/*
+============
+Cmd_Wait_f
+
+Causes execution of the remainder of the command buffer to be delayed until
+next frame. This allows commands like:
+bind g "cmd use rocket ; +attack ; wait ; -attack ; cmd use blaster"
+============
+*/
+void Cmd_Wait_f( void ) {
+ if ( Cmd_Argc() == 2 ) {
+ cmd_wait = atoi( Cmd_Argv( 1 ) );
+ } else {
+ cmd_wait = 1;
+ }
+}
+
+
+/*
+=============================================================================
+
+ COMMAND BUFFER
+
+=============================================================================
+*/
+
+/*
+============
+Cbuf_Init
+============
+*/
+void Cbuf_Init (void)
+{
+ cmd_text.data = cmd_text_buf;
+ cmd_text.maxsize = MAX_CMD_BUFFER;
+ cmd_text.cursize = 0;
+}
+
+/*
+============
+Cbuf_AddText
+
+Adds command text at the end of the buffer, does NOT add a final \n
+============
+*/
+void Cbuf_AddText( const char *text ) {
+ int l;
+
+ l = strlen (text);
+
+ if (cmd_text.cursize + l >= cmd_text.maxsize)
+ {
+ Com_Printf ("Cbuf_AddText: overflow\n");
+ return;
+ }
+ Com_Memcpy(&cmd_text.data[cmd_text.cursize], text, l);
+ cmd_text.cursize += l;
+}
+
+
+/*
+============
+Cbuf_InsertText
+
+Adds command text immediately after the current command
+Adds a \n to the text
+============
+*/
+void Cbuf_InsertText( const char *text ) {
+ int len;
+ int i;
+
+ len = strlen( text ) + 1;
+ if ( len + cmd_text.cursize > cmd_text.maxsize ) {
+ Com_Printf( "Cbuf_InsertText overflowed\n" );
+ return;
+ }
+
+ // move the existing command text
+ for ( i = cmd_text.cursize - 1 ; i >= 0 ; i-- ) {
+ cmd_text.data[ i + len ] = cmd_text.data[ i ];
+ }
+
+ // copy the new text in
+ Com_Memcpy( cmd_text.data, text, len - 1 );
+
+ // add a \n
+ cmd_text.data[ len - 1 ] = '\n';
+
+ cmd_text.cursize += len;
+}
+
+
+/*
+============
+Cbuf_ExecuteText
+============
+*/
+void Cbuf_ExecuteText (int exec_when, const char *text)
+{
+ switch (exec_when)
+ {
+ case EXEC_NOW:
+ if (text && strlen(text) > 0) {
+ Cmd_ExecuteString (text);
+ } else {
+ Cbuf_Execute();
+ }
+ break;
+ case EXEC_INSERT:
+ Cbuf_InsertText (text);
+ break;
+ case EXEC_APPEND:
+ Cbuf_AddText (text);
+ break;
+ default:
+ Com_Error (ERR_FATAL, "Cbuf_ExecuteText: bad exec_when");
+ }
+}
+
+/*
+============
+Cbuf_Execute
+============
+*/
+void Cbuf_Execute (void)
+{
+ int i;
+ char *text;
+ char line[MAX_CMD_LINE];
+ int quotes;
+
+ while (cmd_text.cursize)
+ {
+ if ( cmd_wait ) {
+ // skip out while text still remains in buffer, leaving it
+ // for next frame
+ cmd_wait--;
+ break;
+ }
+
+ // find a \n or ; line break
+ text = (char *)cmd_text.data;
+
+ quotes = 0;
+ for (i=0 ; i< cmd_text.cursize ; i++)
+ {
+ if (text[i] == '"')
+ quotes++;
+ if ( !(quotes&1) && text[i] == ';')
+ break; // don't break if inside a quoted string
+ if (text[i] == '\n' || text[i] == '\r' )
+ break;
+ }
+
+ if( i >= (MAX_CMD_LINE - 1)) {
+ i = MAX_CMD_LINE - 1;
+ }
+
+ Com_Memcpy (line, text, i);
+ line[i] = 0;
+
+// delete the text from the command buffer and move remaining commands down
+// this is necessary because commands (exec) can insert data at the
+// beginning of the text buffer
+
+ if (i == cmd_text.cursize)
+ cmd_text.cursize = 0;
+ else
+ {
+ i++;
+ cmd_text.cursize -= i;
+ memmove (text, text+i, cmd_text.cursize);
+ }
+
+// execute the command line
+
+ Cmd_ExecuteString (line);
+ }
+}
+
+
+/*
+==============================================================================
+
+ SCRIPT COMMANDS
+
+==============================================================================
+*/
+
+
+/*
+===============
+Cmd_Exec_f
+===============
+*/
+void Cmd_Exec_f( void ) {
+ char *f;
+ int len;
+ char filename[MAX_QPATH];
+
+ if (Cmd_Argc () != 2) {
+ Com_Printf ("exec <filename> : execute a script file\n");
+ return;
+ }
+
+ Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) );
+ COM_DefaultExtension( filename, sizeof( filename ), ".cfg" );
+ len = FS_ReadFile( filename, (void **)&f);
+ if (!f) {
+ Com_Printf ("couldn't exec %s\n",Cmd_Argv(1));
+ return;
+ }
+ Com_Printf ("execing %s\n",Cmd_Argv(1));
+
+ Cbuf_InsertText (f);
+
+ FS_FreeFile (f);
+}
+
+
+/*
+===============
+Cmd_Vstr_f
+
+Inserts the current value of a variable as command text
+===============
+*/
+void Cmd_Vstr_f( void ) {
+ char *v;
+
+ if (Cmd_Argc () != 2) {
+ Com_Printf ("vstr <variablename> : execute a variable command\n");
+ return;
+ }
+
+ v = Cvar_VariableString( Cmd_Argv( 1 ) );
+ Cbuf_InsertText( va("%s\n", v ) );
+}
+
+
+/*
+===============
+Cmd_Echo_f
+
+Just prints the rest of the line to the console
+===============
+*/
+void Cmd_Echo_f (void)
+{
+ int i;
+
+ for (i=1 ; i<Cmd_Argc() ; i++)
+ Com_Printf ("%s ",Cmd_Argv(i));
+ Com_Printf ("\n");
+}
+
+
+/*
+=============================================================================
+
+ COMMAND EXECUTION
+
+=============================================================================
+*/
+
+typedef struct cmd_function_s
+{
+ struct cmd_function_s *next;
+ char *name;
+ xcommand_t function;
+} cmd_function_t;
+
+
+typedef struct cmdContext_s
+{
+ int argc;
+ char *argv[ MAX_STRING_TOKENS ]; // points into cmd.tokenized
+ char tokenized[ BIG_INFO_STRING + MAX_STRING_TOKENS ]; // will have 0 bytes inserted
+ char cmd[ BIG_INFO_STRING ]; // the original command we received (no token processing)
+} cmdContext_t;
+
+static cmdContext_t cmd;
+static cmdContext_t savedCmd;
+static cmd_function_t *cmd_functions; // possible commands to execute
+
+/*
+============
+Cmd_SaveCmdContext
+============
+*/
+void Cmd_SaveCmdContext( void )
+{
+ Com_Memcpy( &savedCmd, &cmd, sizeof( cmdContext_t ) );
+}
+
+/*
+============
+Cmd_RestoreCmdContext
+============
+*/
+void Cmd_RestoreCmdContext( void )
+{
+ Com_Memcpy( &cmd, &savedCmd, sizeof( cmdContext_t ) );
+}
+
+/*
+============
+Cmd_Argc
+============
+*/
+int Cmd_Argc( void ) {
+ return cmd.argc;
+}
+
+/*
+============
+Cmd_Argv
+============
+*/
+char *Cmd_Argv( int arg ) {
+ if ( (unsigned)arg >= cmd.argc ) {
+ return "";
+ }
+ return cmd.argv[arg];
+}
+
+/*
+============
+Cmd_ArgvBuffer
+
+The interpreted versions use this because
+they can't have pointers returned to them
+============
+*/
+void Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength ) {
+ Q_strncpyz( buffer, Cmd_Argv( arg ), bufferLength );
+}
+
+
+/*
+============
+Cmd_Args
+
+Returns a single string containing argv(1) to argv(argc()-1)
+============
+*/
+char *Cmd_Args( void ) {
+ static char cmd_args[MAX_STRING_CHARS];
+ int i;
+
+ cmd_args[0] = 0;
+ for ( i = 1 ; i < cmd.argc ; i++ ) {
+ strcat( cmd_args, cmd.argv[i] );
+ if ( i != cmd.argc-1 ) {
+ strcat( cmd_args, " " );
+ }
+ }
+
+ return cmd_args;
+}
+
+/*
+============
+Cmd_Args
+
+Returns a single string containing argv(arg) to argv(argc()-1)
+============
+*/
+char *Cmd_ArgsFrom( int arg ) {
+ static char cmd_args[BIG_INFO_STRING];
+ int i;
+
+ cmd_args[0] = 0;
+ if (arg < 0)
+ arg = 0;
+ for ( i = arg ; i < cmd.argc ; i++ ) {
+ strcat( cmd_args, cmd.argv[i] );
+ if ( i != cmd.argc-1 ) {
+ strcat( cmd_args, " " );
+ }
+ }
+
+ return cmd_args;
+}
+
+/*
+============
+Cmd_ArgsBuffer
+
+The interpreted versions use this because
+they can't have pointers returned to them
+============
+*/
+void Cmd_ArgsBuffer( char *buffer, int bufferLength ) {
+ Q_strncpyz( buffer, Cmd_Args(), bufferLength );
+}
+
+/*
+============
+Cmd_LiteralArgsBuffer
+
+The interpreted versions use this because
+they can't have pointers returned to them
+============
+*/
+void Cmd_LiteralArgsBuffer( char *buffer, int bufferLength ) {
+ Q_strncpyz( buffer, cmd.cmd, bufferLength );
+}
+
+/*
+============
+Cmd_Cmd
+
+Retrieve the unmodified command string
+For rcon use when you want to transmit without altering quoting
+https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543
+============
+*/
+char *Cmd_Cmd(void)
+{
+ return cmd.cmd;
+}
+
+/*
+============
+Cmd_TokenizeString
+
+Parses the given string into command line tokens.
+The text is copied to a seperate buffer and 0 characters
+are inserted in the apropriate place, The argv array
+will point into this temporary buffer.
+============
+*/
+// NOTE TTimo define that to track tokenization issues
+//#define TKN_DBG
+static void Cmd_TokenizeString2( const char *text_in, qboolean ignoreQuotes ) {
+ const char *text;
+ char *textOut;
+
+#ifdef TKN_DBG
+ // FIXME TTimo blunt hook to try to find the tokenization of userinfo
+ Com_DPrintf("Cmd_TokenizeString: %s\n", text_in);
+#endif
+
+ // clear previous args
+ cmd.argc = 0;
+ cmd.cmd[ 0 ] = '\0';
+
+ if ( !text_in ) {
+ return;
+ }
+
+ Q_strncpyz( cmd.cmd, text_in, sizeof(cmd.cmd) );
+
+ text = text_in;
+ textOut = cmd.tokenized;
+
+ while ( 1 ) {
+ if ( cmd.argc == MAX_STRING_TOKENS ) {
+ return; // this is usually something malicious
+ }
+
+ while ( 1 ) {
+ // skip whitespace
+ while ( *text && *text <= ' ' ) {
+ text++;
+ }
+ if ( !*text ) {
+ return; // all tokens parsed
+ }
+
+ // skip // comments
+ if ( text[0] == '/' && text[1] == '/' ) {
+ return; // all tokens parsed
+ }
+
+ // skip /* */ comments
+ if ( text[0] == '/' && text[1] =='*' ) {
+ while ( *text && ( text[0] != '*' || text[1] != '/' ) ) {
+ text++;
+ }
+ if ( !*text ) {
+ return; // all tokens parsed
+ }
+ text += 2;
+ } else {
+ break; // we are ready to parse a token
+ }
+ }
+
+ // handle quoted strings
+ // NOTE TTimo this doesn't handle \" escaping
+ if ( !ignoreQuotes && *text == '"' ) {
+ cmd.argv[cmd.argc] = textOut;
+ cmd.argc++;
+ text++;
+ while ( *text && *text != '"' ) {
+ *textOut++ = *text++;
+ }
+ *textOut++ = 0;
+ if ( !*text ) {
+ return; // all tokens parsed
+ }
+ text++;
+ continue;
+ }
+
+ // regular token
+ cmd.argv[cmd.argc] = textOut;
+ cmd.argc++;
+
+ // skip until whitespace, quote, or command
+ while ( *text > ' ' ) {
+ if ( !ignoreQuotes && text[0] == '"' ) {
+ break;
+ }
+
+ if ( text[0] == '/' && text[1] == '/' ) {
+ break;
+ }
+
+ // skip /* */ comments
+ if ( text[0] == '/' && text[1] =='*' ) {
+ break;
+ }
+
+ *textOut++ = *text++;
+ }
+
+ *textOut++ = 0;
+
+ if ( !*text ) {
+ return; // all tokens parsed
+ }
+ }
+
+}
+
+/*
+============
+Cmd_TokenizeString
+============
+*/
+void Cmd_TokenizeString( const char *text_in ) {
+ Cmd_TokenizeString2( text_in, qfalse );
+}
+
+/*
+============
+Cmd_TokenizeStringIgnoreQuotes
+============
+*/
+void Cmd_TokenizeStringIgnoreQuotes( const char *text_in ) {
+ Cmd_TokenizeString2( text_in, qtrue );
+}
+
+/*
+============
+Cmd_AddCommand
+============
+*/
+void Cmd_AddCommand( const char *cmd_name, xcommand_t function ) {
+ cmd_function_t *cmd;
+
+ // fail if the command already exists
+ for ( cmd = cmd_functions ; cmd ; cmd=cmd->next ) {
+ if ( !strcmp( cmd_name, cmd->name ) ) {
+ // allow completion-only commands to be silently doubled
+ if ( function != NULL ) {
+ Com_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name);
+ }
+ return;
+ }
+ }
+
+ // use a small malloc to avoid zone fragmentation
+ cmd = S_Malloc (sizeof(cmd_function_t));
+ cmd->name = CopyString( cmd_name );
+ cmd->function = function;
+ cmd->next = cmd_functions;
+ cmd_functions = cmd;
+}
+
+/*
+============
+Cmd_RemoveCommand
+============
+*/
+void Cmd_RemoveCommand( const char *cmd_name ) {
+ cmd_function_t *cmd, **back;
+
+ back = &cmd_functions;
+ while( 1 ) {
+ cmd = *back;
+ if ( !cmd ) {
+ // command wasn't active
+ return;
+ }
+ if ( !strcmp( cmd_name, cmd->name ) ) {
+ *back = cmd->next;
+ if (cmd->name) {
+ Z_Free(cmd->name);
+ }
+ Z_Free (cmd);
+ return;
+ }
+ back = &cmd->next;
+ }
+}
+
+
+/*
+============
+Cmd_CommandCompletion
+============
+*/
+void Cmd_CommandCompletion( void(*callback)(const char *s) ) {
+ cmd_function_t *cmd;
+
+ for (cmd=cmd_functions ; cmd ; cmd=cmd->next) {
+ callback( cmd->name );
+ }
+}
+
+
+/*
+============
+Cmd_ExecuteString
+
+A complete command line has been parsed, so try to execute it
+============
+*/
+void Cmd_ExecuteString( const char *text ) {
+ cmd_function_t *cmdFunc, **prev;
+
+ // execute the command line
+ Cmd_TokenizeString( text );
+ if ( !Cmd_Argc() ) {
+ return; // no tokens
+ }
+
+ // check registered command functions
+ for ( prev = &cmd_functions ; *prev ; prev = &cmdFunc->next ) {
+ cmdFunc = *prev;
+ if ( !Q_stricmp( cmd.argv[0], cmdFunc->name ) ) {
+ // rearrange the links so that the command will be
+ // near the head of the list next time it is used
+ *prev = cmdFunc->next;
+ cmdFunc->next = cmd_functions;
+ cmd_functions = cmdFunc;
+
+ // perform the action
+ if ( !cmdFunc->function ) {
+ // let the cgame or game handle it
+ break;
+ } else {
+ cmdFunc->function ();
+ }
+ return;
+ }
+ }
+
+ // check cvars
+ if ( Cvar_Command() ) {
+ return;
+ }
+
+ // check client game commands
+ if ( com_cl_running && com_cl_running->integer && CL_GameCommand() ) {
+ return;
+ }
+
+ // check server game commands
+ if ( com_sv_running && com_sv_running->integer && SV_GameCommand() ) {
+ return;
+ }
+
+ // check ui commands
+ if ( com_cl_running && com_cl_running->integer && UI_GameCommand() ) {
+ return;
+ }
+
+ // send it as a server command if we are connected
+ // this will usually result in a chat message
+ CL_ForwardCommandToServer ( text );
+}
+
+/*
+============
+Cmd_List_f
+============
+*/
+void Cmd_List_f (void)
+{
+ cmd_function_t *cmd;
+ int i;
+ char *match;
+
+ if ( Cmd_Argc() > 1 ) {
+ match = Cmd_Argv( 1 );
+ } else {
+ match = NULL;
+ }
+
+ i = 0;
+ for (cmd=cmd_functions ; cmd ; cmd=cmd->next) {
+ if (match && !Com_Filter(match, cmd->name, qfalse)) continue;
+
+ Com_Printf ("%s\n", cmd->name);
+ i++;
+ }
+ Com_Printf ("%i commands\n", i);
+}
+
+/*
+============
+Cmd_Init
+============
+*/
+void Cmd_Init (void) {
+ Cmd_AddCommand ("cmdlist",Cmd_List_f);
+ Cmd_AddCommand ("exec",Cmd_Exec_f);
+ Cmd_AddCommand ("vstr",Cmd_Vstr_f);
+ Cmd_AddCommand ("echo",Cmd_Echo_f);
+ Cmd_AddCommand ("wait", Cmd_Wait_f);
+}
+
diff --git a/src/qcommon/common.c b/src/qcommon/common.c
new file mode 100644
index 0000000..e1484f9
--- /dev/null
+++ b/src/qcommon/common.c
@@ -0,0 +1,3102 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// common.c -- misc functions used in client and server
+
+#include "q_shared.h"
+#include "qcommon.h"
+#include <setjmp.h>
+#ifndef _WIN32
+#include <netinet/in.h>
+#include <sys/stat.h> // umask
+#else
+#include <winsock.h>
+#endif
+
+int demo_protocols[] =
+{ 66, 67, 68, 69, 0 };
+
+#define MAX_NUM_ARGVS 50
+
+#define MIN_DEDICATED_COMHUNKMEGS 1
+#define MIN_COMHUNKMEGS 128
+#define DEF_COMHUNKMEGS 128
+#define DEF_COMZONEMEGS 24
+#define XSTRING(x) STRING(x)
+#define STRING(x) #x
+#define DEF_COMHUNKMEGS_S XSTRING(DEF_COMHUNKMEGS)
+#define DEF_COMZONEMEGS_S XSTRING(DEF_COMZONEMEGS)
+
+int com_argc;
+char *com_argv[MAX_NUM_ARGVS+1];
+
+jmp_buf abortframe; // an ERR_DROP occured, exit the entire frame
+
+
+FILE *debuglogfile;
+static fileHandle_t logfile;
+fileHandle_t com_journalFile; // events are written here
+fileHandle_t com_journalDataFile; // config files are written here
+
+cvar_t *com_viewlog;
+cvar_t *com_speeds;
+cvar_t *com_developer;
+cvar_t *com_dedicated;
+cvar_t *com_timescale;
+cvar_t *com_fixedtime;
+cvar_t *com_dropsim; // 0.0 to 1.0, simulated packet drops
+cvar_t *com_journal;
+cvar_t *com_maxfps;
+cvar_t *com_altivec;
+cvar_t *com_timedemo;
+cvar_t *com_sv_running;
+cvar_t *com_cl_running;
+cvar_t *com_logfile; // 1 = buffer log, 2 = flush after each print
+cvar_t *com_showtrace;
+cvar_t *com_version;
+cvar_t *com_blood;
+cvar_t *com_buildScript; // for automated data building scripts
+cvar_t *cl_paused;
+cvar_t *sv_paused;
+cvar_t *cl_packetdelay;
+cvar_t *sv_packetdelay;
+cvar_t *com_cameraMode;
+#if defined(_WIN32) && defined(_DEBUG)
+cvar_t *com_noErrorInterrupt;
+#endif
+
+// com_speeds times
+int time_game;
+int time_frontend; // renderer frontend time
+int time_backend; // renderer backend time
+
+int com_frameTime;
+int com_frameMsec;
+int com_frameNumber;
+
+qboolean com_errorEntered;
+qboolean com_fullyInitialized;
+
+char com_errorMessage[MAXPRINTMSG];
+
+void Com_WriteConfig_f( void );
+void CIN_CloseAllVideos( void );
+
+//============================================================================
+
+static char *rd_buffer;
+static int rd_buffersize;
+static void (*rd_flush)( char *buffer );
+
+void Com_BeginRedirect (char *buffer, int buffersize, void (*flush)( char *) )
+{
+ if (!buffer || !buffersize || !flush)
+ return;
+ rd_buffer = buffer;
+ rd_buffersize = buffersize;
+ rd_flush = flush;
+
+ *rd_buffer = 0;
+}
+
+void Com_EndRedirect (void)
+{
+ if ( rd_flush ) {
+ rd_flush(rd_buffer);
+ }
+
+ rd_buffer = NULL;
+ rd_buffersize = 0;
+ rd_flush = NULL;
+}
+
+/*
+=============
+Com_Printf
+
+Both client and server can use this, and it will output
+to the apropriate place.
+
+A raw string should NEVER be passed as fmt, because of "%f" type crashers.
+=============
+*/
+void QDECL Com_Printf( const char *fmt, ... ) {
+ va_list argptr;
+ char msg[MAXPRINTMSG];
+ static qboolean opening_qconsole = qfalse;
+
+
+ va_start (argptr,fmt);
+ Q_vsnprintf (msg, sizeof(msg), fmt, argptr);
+ va_end (argptr);
+
+ if ( rd_buffer ) {
+ if ((strlen (msg) + strlen(rd_buffer)) > (rd_buffersize - 1)) {
+ rd_flush(rd_buffer);
+ *rd_buffer = 0;
+ }
+ Q_strcat(rd_buffer, rd_buffersize, msg);
+ // TTimo nooo .. that would defeat the purpose
+ //rd_flush(rd_buffer);
+ //*rd_buffer = 0;
+ return;
+ }
+
+ // echo to console if we're not a dedicated server
+ if ( com_dedicated && !com_dedicated->integer ) {
+ CL_ConsolePrint( msg );
+ }
+
+ // echo to dedicated console and early console
+ Sys_Print( msg );
+
+ // logfile
+ if ( com_logfile && com_logfile->integer ) {
+ // TTimo: only open the qconsole.log if the filesystem is in an initialized state
+ // also, avoid recursing in the qconsole.log opening (i.e. if fs_debug is on)
+ if ( !logfile && FS_Initialized() && !opening_qconsole) {
+ struct tm *newtime;
+ time_t aclock;
+
+ opening_qconsole = qtrue;
+
+ time( &aclock );
+ newtime = localtime( &aclock );
+
+ logfile = FS_FOpenFileWrite( "qconsole.log" );
+
+ if(logfile)
+ {
+ Com_Printf( "logfile opened on %s\n", asctime( newtime ) );
+
+ if ( com_logfile->integer > 1 )
+ {
+ // force it to not buffer so we get valid
+ // data even if we are crashing
+ FS_ForceFlush(logfile);
+ }
+ }
+ else
+ {
+ Com_Printf("Opening qconsole.log failed!\n");
+ Cvar_SetValue("logfile", 0);
+ }
+
+ opening_qconsole = qfalse;
+ }
+ if ( logfile && FS_Initialized()) {
+ FS_Write(msg, strlen(msg), logfile);
+ }
+ }
+}
+
+
+/*
+================
+Com_DPrintf
+
+A Com_Printf that only shows up if the "developer" cvar is set
+================
+*/
+void QDECL Com_DPrintf( const char *fmt, ...) {
+ va_list argptr;
+ char msg[MAXPRINTMSG];
+
+ if ( !com_developer || !com_developer->integer ) {
+ return; // don't confuse non-developers with techie stuff...
+ }
+
+ va_start (argptr,fmt);
+ Q_vsnprintf (msg, sizeof(msg), fmt, argptr);
+ va_end (argptr);
+
+ Com_Printf ("%s", msg);
+}
+
+/*
+=============
+Com_Error
+
+Both client and server can use this, and it will
+do the apropriate things.
+=============
+*/
+void QDECL Com_Error( int code, const char *fmt, ... ) {
+ va_list argptr;
+ static int lastErrorTime;
+ static int errorCount;
+ int currentTime;
+
+#if defined(_WIN32) && defined(_DEBUG)
+ if ( code != ERR_DISCONNECT && code != ERR_NEED_CD ) {
+ if (!com_noErrorInterrupt->integer) {
+ __asm {
+ int 0x03
+ }
+ }
+ }
+#endif
+ Cvar_Set("com_errorCode", va("%i", code));
+
+ // when we are running automated scripts, make sure we
+ // know if anything failed
+ if ( com_buildScript && com_buildScript->integer ) {
+ code = ERR_FATAL;
+ }
+
+ // if we are getting a solid stream of ERR_DROP, do an ERR_FATAL
+ currentTime = Sys_Milliseconds();
+ if ( currentTime - lastErrorTime < 100 ) {
+ if ( ++errorCount > 3 ) {
+ code = ERR_FATAL;
+ }
+ } else {
+ errorCount = 0;
+ }
+ lastErrorTime = currentTime;
+
+ if ( com_errorEntered ) {
+ Sys_Error( "recursive error after: %s", com_errorMessage );
+ }
+ com_errorEntered = qtrue;
+
+ va_start (argptr,fmt);
+ vsprintf (com_errorMessage,fmt,argptr);
+ va_end (argptr);
+
+ if (code != ERR_DISCONNECT && code != ERR_NEED_CD)
+ Cvar_Set("com_errorMessage", com_errorMessage);
+
+ if (code == ERR_DISCONNECT || code == ERR_SERVERDISCONNECT) {
+ CL_Disconnect( qtrue );
+ CL_FlushMemory( );
+ // make sure we can get at our local stuff
+ FS_PureServerSetLoadedPaks("", "");
+ com_errorEntered = qfalse;
+ longjmp (abortframe, -1);
+ } else if (code == ERR_DROP) {
+ Com_Printf ("********************\nERROR: %s\n********************\n", com_errorMessage);
+ SV_Shutdown (va("Server crashed: %s", com_errorMessage));
+ CL_Disconnect( qtrue );
+ CL_FlushMemory( );
+ FS_PureServerSetLoadedPaks("", "");
+ com_errorEntered = qfalse;
+ longjmp (abortframe, -1);
+ } else if ( code == ERR_NEED_CD ) {
+ SV_Shutdown( "Server didn't have CD" );
+ if ( com_cl_running && com_cl_running->integer ) {
+ CL_Disconnect( qtrue );
+ CL_FlushMemory( );
+ com_errorEntered = qfalse;
+ CL_CDDialog();
+ } else {
+ Com_Printf("Server didn't have CD\n" );
+ }
+ FS_PureServerSetLoadedPaks("", "");
+ longjmp (abortframe, -1);
+ } else {
+ CL_Shutdown ();
+ SV_Shutdown (va("Server fatal crashed: %s", com_errorMessage));
+ }
+
+ Com_Shutdown ();
+
+ Sys_Error ("%s", com_errorMessage);
+}
+
+
+/*
+=============
+Com_Quit_f
+
+Both client and server can use this, and it will
+do the apropriate things.
+=============
+*/
+void Com_Quit_f( void ) {
+ // don't try to shutdown if we are in a recursive error
+ if ( !com_errorEntered ) {
+ SV_Shutdown ("Server quit");
+ CL_Shutdown ();
+ Com_Shutdown ();
+ FS_Shutdown(qtrue);
+ }
+ Sys_Quit ();
+}
+
+
+
+/*
+============================================================================
+
+COMMAND LINE FUNCTIONS
+
++ characters seperate the commandLine string into multiple console
+command lines.
+
+All of these are valid:
+
+tremulous +set test blah +map test
+tremulous set test blah+map test
+tremulous set test blah + map test
+
+============================================================================
+*/
+
+#define MAX_CONSOLE_LINES 32
+int com_numConsoleLines;
+char *com_consoleLines[MAX_CONSOLE_LINES];
+
+/*
+==================
+Com_ParseCommandLine
+
+Break it up into multiple console lines
+==================
+*/
+void Com_ParseCommandLine( char *commandLine ) {
+ int inq = 0;
+ com_consoleLines[0] = commandLine;
+ com_numConsoleLines = 1;
+
+ while ( *commandLine ) {
+ if (*commandLine == '"') {
+ inq = !inq;
+ }
+ // look for a + seperating character
+ // if commandLine came from a file, we might have real line seperators
+ if ( (*commandLine == '+' && !inq) || *commandLine == '\n' || *commandLine == '\r' ) {
+ if ( com_numConsoleLines == MAX_CONSOLE_LINES ) {
+ return;
+ }
+ com_consoleLines[com_numConsoleLines] = commandLine + 1;
+ com_numConsoleLines++;
+ *commandLine = 0;
+ }
+ commandLine++;
+ }
+}
+
+
+/*
+===================
+Com_SafeMode
+
+Check for "safe" on the command line, which will
+skip loading of autogen.cfg
+===================
+*/
+qboolean Com_SafeMode( void ) {
+ int i;
+
+ for ( i = 0 ; i < com_numConsoleLines ; i++ ) {
+ Cmd_TokenizeString( com_consoleLines[i] );
+ if ( !Q_stricmp( Cmd_Argv(0), "safe" )
+ || !Q_stricmp( Cmd_Argv(0), "cvar_restart" ) ) {
+ com_consoleLines[i][0] = 0;
+ return qtrue;
+ }
+ }
+ return qfalse;
+}
+
+
+/*
+===============
+Com_StartupVariable
+
+Searches for command line parameters that are set commands.
+If match is not NULL, only that cvar will be looked for.
+That is necessary because cddir and basedir need to be set
+before the filesystem is started, but all other sets should
+be after execing the config and default.
+===============
+*/
+void Com_StartupVariable( const char *match ) {
+ int i;
+ char *s;
+ cvar_t *cv;
+
+ for (i=0 ; i < com_numConsoleLines ; i++) {
+ Cmd_TokenizeString( com_consoleLines[i] );
+ if ( strcmp( Cmd_Argv(0), "set" ) ) {
+ continue;
+ }
+
+ s = Cmd_Argv(1);
+ if ( !match || !strcmp( s, match ) ) {
+ Cvar_Set( s, Cmd_Argv(2) );
+ cv = Cvar_Get( s, "", 0 );
+ cv->flags |= CVAR_USER_CREATED;
+// com_consoleLines[i] = 0;
+ }
+ }
+}
+
+
+/*
+=================
+Com_AddStartupCommands
+
+Adds command line parameters as script statements
+Commands are seperated by + signs
+
+Returns qtrue if any late commands were added, which
+will keep the demoloop from immediately starting
+=================
+*/
+qboolean Com_AddStartupCommands( void ) {
+ int i;
+ qboolean added;
+
+ added = qfalse;
+ // quote every token, so args with semicolons can work
+ for (i=0 ; i < com_numConsoleLines ; i++) {
+ if ( !com_consoleLines[i] || !com_consoleLines[i][0] ) {
+ continue;
+ }
+
+ // set commands won't override menu startup
+ if ( Q_stricmpn( com_consoleLines[i], "set", 3 ) ) {
+ added = qtrue;
+ }
+ Cbuf_AddText( com_consoleLines[i] );
+ Cbuf_AddText( "\n" );
+ }
+
+ return added;
+}
+
+
+//============================================================================
+
+void Info_Print( const char *s ) {
+ char key[512];
+ char value[512];
+ char *o;
+ int l;
+
+ if (*s == '\\')
+ s++;
+ while (*s)
+ {
+ o = key;
+ while (*s && *s != '\\')
+ *o++ = *s++;
+
+ l = o - key;
+ if (l < 20)
+ {
+ Com_Memset (o, ' ', 20-l);
+ key[20] = 0;
+ }
+ else
+ *o = 0;
+ Com_Printf ("%s", key);
+
+ if (!*s)
+ {
+ Com_Printf ("MISSING VALUE\n");
+ return;
+ }
+
+ o = value;
+ s++;
+ while (*s && *s != '\\')
+ *o++ = *s++;
+ *o = 0;
+
+ if (*s)
+ s++;
+ Com_Printf ("%s\n", value);
+ }
+}
+
+/*
+============
+Com_StringContains
+============
+*/
+char *Com_StringContains(char *str1, char *str2, int casesensitive) {
+ int len, i, j;
+
+ len = strlen(str1) - strlen(str2);
+ for (i = 0; i <= len; i++, str1++) {
+ for (j = 0; str2[j]; j++) {
+ if (casesensitive) {
+ if (str1[j] != str2[j]) {
+ break;
+ }
+ }
+ else {
+ if (toupper(str1[j]) != toupper(str2[j])) {
+ break;
+ }
+ }
+ }
+ if (!str2[j]) {
+ return str1;
+ }
+ }
+ return NULL;
+}
+
+/*
+============
+Com_Filter
+============
+*/
+int Com_Filter(char *filter, char *name, int casesensitive)
+{
+ char buf[MAX_TOKEN_CHARS];
+ char *ptr;
+ int i, found;
+
+ while(*filter) {
+ if (*filter == '*') {
+ filter++;
+ for (i = 0; *filter; i++) {
+ if (*filter == '*' || *filter == '?') break;
+ buf[i] = *filter;
+ filter++;
+ }
+ buf[i] = '\0';
+ if (strlen(buf)) {
+ ptr = Com_StringContains(name, buf, casesensitive);
+ if (!ptr) return qfalse;
+ name = ptr + strlen(buf);
+ }
+ }
+ else if (*filter == '?') {
+ filter++;
+ name++;
+ }
+ else if (*filter == '[' && *(filter+1) == '[') {
+ filter++;
+ }
+ else if (*filter == '[') {
+ filter++;
+ found = qfalse;
+ while(*filter && !found) {
+ if (*filter == ']' && *(filter+1) != ']') break;
+ if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']')) {
+ if (casesensitive) {
+ if (*name >= *filter && *name <= *(filter+2)) found = qtrue;
+ }
+ else {
+ if (toupper(*name) >= toupper(*filter) &&
+ toupper(*name) <= toupper(*(filter+2))) found = qtrue;
+ }
+ filter += 3;
+ }
+ else {
+ if (casesensitive) {
+ if (*filter == *name) found = qtrue;
+ }
+ else {
+ if (toupper(*filter) == toupper(*name)) found = qtrue;
+ }
+ filter++;
+ }
+ }
+ if (!found) return qfalse;
+ while(*filter) {
+ if (*filter == ']' && *(filter+1) != ']') break;
+ filter++;
+ }
+ filter++;
+ name++;
+ }
+ else {
+ if (casesensitive) {
+ if (*filter != *name) return qfalse;
+ }
+ else {
+ if (toupper(*filter) != toupper(*name)) return qfalse;
+ }
+ filter++;
+ name++;
+ }
+ }
+ return qtrue;
+}
+
+/*
+============
+Com_FilterPath
+============
+*/
+int Com_FilterPath(char *filter, char *name, int casesensitive)
+{
+ int i;
+ char new_filter[MAX_QPATH];
+ char new_name[MAX_QPATH];
+
+ for (i = 0; i < MAX_QPATH-1 && filter[i]; i++) {
+ if ( filter[i] == '\\' || filter[i] == ':' ) {
+ new_filter[i] = '/';
+ }
+ else {
+ new_filter[i] = filter[i];
+ }
+ }
+ new_filter[i] = '\0';
+ for (i = 0; i < MAX_QPATH-1 && name[i]; i++) {
+ if ( name[i] == '\\' || name[i] == ':' ) {
+ new_name[i] = '/';
+ }
+ else {
+ new_name[i] = name[i];
+ }
+ }
+ new_name[i] = '\0';
+ return Com_Filter(new_filter, new_name, casesensitive);
+}
+
+/*
+============
+Com_HashKey
+============
+*/
+int Com_HashKey(char *string, int maxlen) {
+ int register hash, i;
+
+ hash = 0;
+ for (i = 0; i < maxlen && string[i] != '\0'; i++) {
+ hash += string[i] * (119 + i);
+ }
+ hash = (hash ^ (hash >> 10) ^ (hash >> 20));
+ return hash;
+}
+
+/*
+================
+Com_RealTime
+================
+*/
+int Com_RealTime(qtime_t *qtime) {
+ time_t t;
+ struct tm *tms;
+
+ t = time(NULL);
+ if (!qtime)
+ return t;
+ tms = localtime(&t);
+ if (tms) {
+ qtime->tm_sec = tms->tm_sec;
+ qtime->tm_min = tms->tm_min;
+ qtime->tm_hour = tms->tm_hour;
+ qtime->tm_mday = tms->tm_mday;
+ qtime->tm_mon = tms->tm_mon;
+ qtime->tm_year = tms->tm_year;
+ qtime->tm_wday = tms->tm_wday;
+ qtime->tm_yday = tms->tm_yday;
+ qtime->tm_isdst = tms->tm_isdst;
+ }
+ return t;
+}
+
+
+/*
+==============================================================================
+
+ ZONE MEMORY ALLOCATION
+
+There is never any space between memblocks, and there will never be two
+contiguous free memblocks.
+
+The rover can be left pointing at a non-empty block
+
+The zone calls are pretty much only used for small strings and structures,
+all big things are allocated on the hunk.
+==============================================================================
+*/
+
+#define ZONEID 0x1d4a11
+#define MINFRAGMENT 64
+
+typedef struct zonedebug_s {
+ char *label;
+ char *file;
+ int line;
+ int allocSize;
+} zonedebug_t;
+
+typedef struct memblock_s {
+ int size; // including the header and possibly tiny fragments
+ int tag; // a tag of 0 is a free block
+ struct memblock_s *next, *prev;
+ int id; // should be ZONEID
+#ifdef ZONE_DEBUG
+ zonedebug_t d;
+#endif
+} memblock_t;
+
+typedef struct {
+ int size; // total bytes malloced, including header
+ int used; // total bytes used
+ memblock_t blocklist; // start / end cap for linked list
+ memblock_t *rover;
+} memzone_t;
+
+// main zone for all "dynamic" memory allocation
+memzone_t *mainzone;
+// we also have a small zone for small allocations that would only
+// fragment the main zone (think of cvar and cmd strings)
+memzone_t *smallzone;
+
+void Z_CheckHeap( void );
+
+/*
+========================
+Z_ClearZone
+========================
+*/
+void Z_ClearZone( memzone_t *zone, int size ) {
+ memblock_t *block;
+
+ // set the entire zone to one free block
+
+ zone->blocklist.next = zone->blocklist.prev = block =
+ (memblock_t *)( (byte *)zone + sizeof(memzone_t) );
+ zone->blocklist.tag = 1; // in use block
+ zone->blocklist.id = 0;
+ zone->blocklist.size = 0;
+ zone->rover = block;
+ zone->size = size;
+ zone->used = 0;
+
+ block->prev = block->next = &zone->blocklist;
+ block->tag = 0; // free block
+ block->id = ZONEID;
+ block->size = size - sizeof(memzone_t);
+}
+
+/*
+========================
+Z_AvailableZoneMemory
+========================
+*/
+int Z_AvailableZoneMemory( memzone_t *zone ) {
+ return zone->size - zone->used;
+}
+
+/*
+========================
+Z_AvailableMemory
+========================
+*/
+int Z_AvailableMemory( void ) {
+ return Z_AvailableZoneMemory( mainzone );
+}
+
+/*
+========================
+Z_Free
+========================
+*/
+void Z_Free( void *ptr ) {
+ memblock_t *block, *other;
+ memzone_t *zone;
+
+ if (!ptr) {
+ Com_Error( ERR_DROP, "Z_Free: NULL pointer" );
+ }
+
+ block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t));
+ if (block->id != ZONEID) {
+ Com_Error( ERR_FATAL, "Z_Free: freed a pointer without ZONEID" );
+ }
+ if (block->tag == 0) {
+ Com_Error( ERR_FATAL, "Z_Free: freed a freed pointer" );
+ }
+ // if static memory
+ if (block->tag == TAG_STATIC) {
+ return;
+ }
+
+ // check the memory trash tester
+ if ( *(int *)((byte *)block + block->size - 4 ) != ZONEID ) {
+ Com_Error( ERR_FATAL, "Z_Free: memory block wrote past end" );
+ }
+
+ if (block->tag == TAG_SMALL) {
+ zone = smallzone;
+ }
+ else {
+ zone = mainzone;
+ }
+
+ zone->used -= block->size;
+ // set the block to something that should cause problems
+ // if it is referenced...
+ Com_Memset( ptr, 0xaa, block->size - sizeof( *block ) );
+
+ block->tag = 0; // mark as free
+
+ other = block->prev;
+ if (!other->tag) {
+ // merge with previous free block
+ other->size += block->size;
+ other->next = block->next;
+ other->next->prev = other;
+ if (block == zone->rover) {
+ zone->rover = other;
+ }
+ block = other;
+ }
+
+ zone->rover = block;
+
+ other = block->next;
+ if ( !other->tag ) {
+ // merge the next free block onto the end
+ block->size += other->size;
+ block->next = other->next;
+ block->next->prev = block;
+ if (other == zone->rover) {
+ zone->rover = block;
+ }
+ }
+}
+
+
+/*
+================
+Z_FreeTags
+================
+*/
+void Z_FreeTags( int tag ) {
+ int count;
+ memzone_t *zone;
+
+ if ( tag == TAG_SMALL ) {
+ zone = smallzone;
+ }
+ else {
+ zone = mainzone;
+ }
+ count = 0;
+ // use the rover as our pointer, because
+ // Z_Free automatically adjusts it
+ zone->rover = zone->blocklist.next;
+ do {
+ if ( zone->rover->tag == tag ) {
+ count++;
+ Z_Free( (void *)(zone->rover + 1) );
+ continue;
+ }
+ zone->rover = zone->rover->next;
+ } while ( zone->rover != &zone->blocklist );
+}
+
+
+/*
+================
+Z_TagMalloc
+================
+*/
+#ifdef ZONE_DEBUG
+void *Z_TagMallocDebug( int size, int tag, char *label, char *file, int line ) {
+#else
+void *Z_TagMalloc( int size, int tag ) {
+#endif
+ int extra, allocSize;
+ memblock_t *start, *rover, *new, *base;
+ memzone_t *zone;
+
+ if (!tag) {
+ Com_Error( ERR_FATAL, "Z_TagMalloc: tried to use a 0 tag" );
+ }
+
+ if ( tag == TAG_SMALL ) {
+ zone = smallzone;
+ }
+ else {
+ zone = mainzone;
+ }
+
+ allocSize = size;
+ //
+ // scan through the block list looking for the first free block
+ // of sufficient size
+ //
+ size += sizeof(memblock_t); // account for size of block header
+ size += 4; // space for memory trash tester
+ size = PAD(size, sizeof(intptr_t)); // align to 32/64 bit boundary
+
+ base = rover = zone->rover;
+ start = base->prev;
+
+ do {
+ if (rover == start) {
+#ifdef ZONE_DEBUG
+ Z_LogHeap();
+#endif
+ // scaned all the way around the list
+ Com_Error( ERR_FATAL, "Z_Malloc: failed on allocation of %i bytes from the %s zone",
+ size, zone == smallzone ? "small" : "main");
+ return NULL;
+ }
+ if (rover->tag) {
+ base = rover = rover->next;
+ } else {
+ rover = rover->next;
+ }
+ } while (base->tag || base->size < size);
+
+ //
+ // found a block big enough
+ //
+ extra = base->size - size;
+ if (extra > MINFRAGMENT) {
+ // there will be a free fragment after the allocated block
+ new = (memblock_t *) ((byte *)base + size );
+ new->size = extra;
+ new->tag = 0; // free block
+ new->prev = base;
+ new->id = ZONEID;
+ new->next = base->next;
+ new->next->prev = new;
+ base->next = new;
+ base->size = size;
+ }
+
+ base->tag = tag; // no longer a free block
+
+ zone->rover = base->next; // next allocation will start looking here
+ zone->used += base->size; //
+
+ base->id = ZONEID;
+
+#ifdef ZONE_DEBUG
+ base->d.label = label;
+ base->d.file = file;
+ base->d.line = line;
+ base->d.allocSize = allocSize;
+#endif
+
+ // marker for memory trash testing
+ *(int *)((byte *)base + base->size - 4) = ZONEID;
+
+ return (void *) ((byte *)base + sizeof(memblock_t));
+}
+
+/*
+========================
+Z_Malloc
+========================
+*/
+#ifdef ZONE_DEBUG
+void *Z_MallocDebug( int size, char *label, char *file, int line ) {
+#else
+void *Z_Malloc( int size ) {
+#endif
+ void *buf;
+
+ //Z_CheckHeap (); // DEBUG
+
+#ifdef ZONE_DEBUG
+ buf = Z_TagMallocDebug( size, TAG_GENERAL, label, file, line );
+#else
+ buf = Z_TagMalloc( size, TAG_GENERAL );
+#endif
+ Com_Memset( buf, 0, size );
+
+ return buf;
+}
+
+#ifdef ZONE_DEBUG
+void *S_MallocDebug( int size, char *label, char *file, int line ) {
+ return Z_TagMallocDebug( size, TAG_SMALL, label, file, line );
+}
+#else
+void *S_Malloc( int size ) {
+ return Z_TagMalloc( size, TAG_SMALL );
+}
+#endif
+
+/*
+========================
+Z_CheckHeap
+========================
+*/
+void Z_CheckHeap( void ) {
+ memblock_t *block;
+
+ for (block = mainzone->blocklist.next ; ; block = block->next) {
+ if (block->next == &mainzone->blocklist) {
+ break; // all blocks have been hit
+ }
+ if ( (byte *)block + block->size != (byte *)block->next)
+ Com_Error( ERR_FATAL, "Z_CheckHeap: block size does not touch the next block\n" );
+ if ( block->next->prev != block) {
+ Com_Error( ERR_FATAL, "Z_CheckHeap: next block doesn't have proper back link\n" );
+ }
+ if ( !block->tag && !block->next->tag ) {
+ Com_Error( ERR_FATAL, "Z_CheckHeap: two consecutive free blocks\n" );
+ }
+ }
+}
+
+/*
+========================
+Z_LogZoneHeap
+========================
+*/
+void Z_LogZoneHeap( memzone_t *zone, char *name ) {
+#ifdef ZONE_DEBUG
+ char dump[32], *ptr;
+ int i, j;
+#endif
+ memblock_t *block;
+ char buf[4096];
+ int size, allocSize, numBlocks;
+
+ if (!logfile || !FS_Initialized())
+ return;
+ size = allocSize = numBlocks = 0;
+ Com_sprintf(buf, sizeof(buf), "\r\n================\r\n%s log\r\n================\r\n", name);
+ FS_Write(buf, strlen(buf), logfile);
+ for (block = zone->blocklist.next ; block->next != &zone->blocklist; block = block->next) {
+ if (block->tag) {
+#ifdef ZONE_DEBUG
+ ptr = ((char *) block) + sizeof(memblock_t);
+ j = 0;
+ for (i = 0; i < 20 && i < block->d.allocSize; i++) {
+ if (ptr[i] >= 32 && ptr[i] < 127) {
+ dump[j++] = ptr[i];
+ }
+ else {
+ dump[j++] = '_';
+ }
+ }
+ dump[j] = '\0';
+ Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s) [%s]\r\n", block->d.allocSize, block->d.file, block->d.line, block->d.label, dump);
+ FS_Write(buf, strlen(buf), logfile);
+ allocSize += block->d.allocSize;
+#endif
+ size += block->size;
+ numBlocks++;
+ }
+ }
+#ifdef ZONE_DEBUG
+ // subtract debug memory
+ size -= numBlocks * sizeof(zonedebug_t);
+#else
+ allocSize = numBlocks * sizeof(memblock_t); // + 32 bit alignment
+#endif
+ Com_sprintf(buf, sizeof(buf), "%d %s memory in %d blocks\r\n", size, name, numBlocks);
+ FS_Write(buf, strlen(buf), logfile);
+ Com_sprintf(buf, sizeof(buf), "%d %s memory overhead\r\n", size - allocSize, name);
+ FS_Write(buf, strlen(buf), logfile);
+}
+
+/*
+========================
+Z_LogHeap
+========================
+*/
+void Z_LogHeap( void ) {
+ Z_LogZoneHeap( mainzone, "MAIN" );
+ Z_LogZoneHeap( smallzone, "SMALL" );
+}
+
+// static mem blocks to reduce a lot of small zone overhead
+typedef struct memstatic_s {
+ memblock_t b;
+ byte mem[2];
+} memstatic_t;
+
+// bk001204 - initializer brackets
+memstatic_t emptystring =
+ { {(sizeof(memblock_t)+2 + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'\0', '\0'} };
+memstatic_t numberstring[] = {
+ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'0', '\0'} },
+ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'1', '\0'} },
+ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'2', '\0'} },
+ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'3', '\0'} },
+ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'4', '\0'} },
+ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'5', '\0'} },
+ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'6', '\0'} },
+ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'7', '\0'} },
+ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'8', '\0'} },
+ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'9', '\0'} }
+};
+
+/*
+========================
+CopyString
+
+ NOTE: never write over the memory CopyString returns because
+ memory from a memstatic_t might be returned
+========================
+*/
+char *CopyString( const char *in ) {
+ char *out;
+
+ if (!in[0]) {
+ return ((char *)&emptystring) + sizeof(memblock_t);
+ }
+ else if (!in[1]) {
+ if (in[0] >= '0' && in[0] <= '9') {
+ return ((char *)&numberstring[in[0]-'0']) + sizeof(memblock_t);
+ }
+ }
+ out = S_Malloc (strlen(in)+1);
+ strcpy (out, in);
+ return out;
+}
+
+/*
+==============================================================================
+
+Goals:
+ reproducable without history effects -- no out of memory errors on weird map to map changes
+ allow restarting of the client without fragmentation
+ minimize total pages in use at run time
+ minimize total pages needed during load time
+
+ Single block of memory with stack allocators coming from both ends towards the middle.
+
+ One side is designated the temporary memory allocator.
+
+ Temporary memory can be allocated and freed in any order.
+
+ A highwater mark is kept of the most in use at any time.
+
+ When there is no temporary memory allocated, the permanent and temp sides
+ can be switched, allowing the already touched temp memory to be used for
+ permanent storage.
+
+ Temp memory must never be allocated on two ends at once, or fragmentation
+ could occur.
+
+ If we have any in-use temp memory, additional temp allocations must come from
+ that side.
+
+ If not, we can choose to make either side the new temp side and push future
+ permanent allocations to the other side. Permanent allocations should be
+ kept on the side that has the current greatest wasted highwater mark.
+
+==============================================================================
+*/
+
+
+#define HUNK_MAGIC 0x89537892
+#define HUNK_FREE_MAGIC 0x89537893
+
+typedef struct {
+ int magic;
+ int size;
+} hunkHeader_t;
+
+typedef struct {
+ int mark;
+ int permanent;
+ int temp;
+ int tempHighwater;
+} hunkUsed_t;
+
+typedef struct hunkblock_s {
+ int size;
+ byte printed;
+ struct hunkblock_s *next;
+ char *label;
+ char *file;
+ int line;
+} hunkblock_t;
+
+static hunkblock_t *hunkblocks;
+
+static hunkUsed_t hunk_low, hunk_high;
+static hunkUsed_t *hunk_permanent, *hunk_temp;
+
+static byte *s_hunkData = NULL;
+static int s_hunkTotal;
+
+static int s_zoneTotal;
+static int s_smallZoneTotal;
+
+
+/*
+=================
+Com_Meminfo_f
+=================
+*/
+void Com_Meminfo_f( void ) {
+ memblock_t *block;
+ int zoneBytes, zoneBlocks;
+ int smallZoneBytes, smallZoneBlocks;
+ int botlibBytes, rendererBytes;
+ int unused;
+
+ zoneBytes = 0;
+ botlibBytes = 0;
+ rendererBytes = 0;
+ zoneBlocks = 0;
+ for (block = mainzone->blocklist.next ; ; block = block->next) {
+ if ( Cmd_Argc() != 1 ) {
+ Com_Printf ("block:%p size:%7i tag:%3i\n",
+ block, block->size, block->tag);
+ }
+ if ( block->tag ) {
+ zoneBytes += block->size;
+ zoneBlocks++;
+ if ( block->tag == TAG_BOTLIB ) {
+ botlibBytes += block->size;
+ } else if ( block->tag == TAG_RENDERER ) {
+ rendererBytes += block->size;
+ }
+ }
+
+ if (block->next == &mainzone->blocklist) {
+ break; // all blocks have been hit
+ }
+ if ( (byte *)block + block->size != (byte *)block->next) {
+ Com_Printf ("ERROR: block size does not touch the next block\n");
+ }
+ if ( block->next->prev != block) {
+ Com_Printf ("ERROR: next block doesn't have proper back link\n");
+ }
+ if ( !block->tag && !block->next->tag ) {
+ Com_Printf ("ERROR: two consecutive free blocks\n");
+ }
+ }
+
+ smallZoneBytes = 0;
+ smallZoneBlocks = 0;
+ for (block = smallzone->blocklist.next ; ; block = block->next) {
+ if ( block->tag ) {
+ smallZoneBytes += block->size;
+ smallZoneBlocks++;
+ }
+
+ if (block->next == &smallzone->blocklist) {
+ break; // all blocks have been hit
+ }
+ }
+
+ Com_Printf( "%8i bytes total hunk\n", s_hunkTotal );
+ Com_Printf( "%8i bytes total zone\n", s_zoneTotal );
+ Com_Printf( "\n" );
+ Com_Printf( "%8i low mark\n", hunk_low.mark );
+ Com_Printf( "%8i low permanent\n", hunk_low.permanent );
+ if ( hunk_low.temp != hunk_low.permanent ) {
+ Com_Printf( "%8i low temp\n", hunk_low.temp );
+ }
+ Com_Printf( "%8i low tempHighwater\n", hunk_low.tempHighwater );
+ Com_Printf( "\n" );
+ Com_Printf( "%8i high mark\n", hunk_high.mark );
+ Com_Printf( "%8i high permanent\n", hunk_high.permanent );
+ if ( hunk_high.temp != hunk_high.permanent ) {
+ Com_Printf( "%8i high temp\n", hunk_high.temp );
+ }
+ Com_Printf( "%8i high tempHighwater\n", hunk_high.tempHighwater );
+ Com_Printf( "\n" );
+ Com_Printf( "%8i total hunk in use\n", hunk_low.permanent + hunk_high.permanent );
+ unused = 0;
+ if ( hunk_low.tempHighwater > hunk_low.permanent ) {
+ unused += hunk_low.tempHighwater - hunk_low.permanent;
+ }
+ if ( hunk_high.tempHighwater > hunk_high.permanent ) {
+ unused += hunk_high.tempHighwater - hunk_high.permanent;
+ }
+ Com_Printf( "%8i unused highwater\n", unused );
+ Com_Printf( "\n" );
+ Com_Printf( "%8i bytes in %i zone blocks\n", zoneBytes, zoneBlocks );
+ Com_Printf( " %8i bytes in dynamic botlib\n", botlibBytes );
+ Com_Printf( " %8i bytes in dynamic renderer\n", rendererBytes );
+ Com_Printf( " %8i bytes in dynamic other\n", zoneBytes - ( botlibBytes + rendererBytes ) );
+ Com_Printf( " %8i bytes in small Zone memory\n", smallZoneBytes );
+}
+
+/*
+===============
+Com_TouchMemory
+
+Touch all known used data to make sure it is paged in
+===============
+*/
+void Com_TouchMemory( void ) {
+ int start, end;
+ int i, j;
+ int sum;
+ memblock_t *block;
+
+ Z_CheckHeap();
+
+ start = Sys_Milliseconds();
+
+ sum = 0;
+
+ j = hunk_low.permanent >> 2;
+ for ( i = 0 ; i < j ; i+=64 ) { // only need to touch each page
+ sum += ((int *)s_hunkData)[i];
+ }
+
+ i = ( s_hunkTotal - hunk_high.permanent ) >> 2;
+ j = hunk_high.permanent >> 2;
+ for ( ; i < j ; i+=64 ) { // only need to touch each page
+ sum += ((int *)s_hunkData)[i];
+ }
+
+ for (block = mainzone->blocklist.next ; ; block = block->next) {
+ if ( block->tag ) {
+ j = block->size >> 2;
+ for ( i = 0 ; i < j ; i+=64 ) { // only need to touch each page
+ sum += ((int *)block)[i];
+ }
+ }
+ if ( block->next == &mainzone->blocklist ) {
+ break; // all blocks have been hit
+ }
+ }
+
+ end = Sys_Milliseconds();
+
+ Com_Printf( "Com_TouchMemory: %i msec\n", end - start );
+}
+
+
+
+/*
+=================
+Com_InitZoneMemory
+=================
+*/
+void Com_InitSmallZoneMemory( void ) {
+ s_smallZoneTotal = 512 * 1024;
+ // bk001205 - was malloc
+ smallzone = calloc( s_smallZoneTotal, 1 );
+ if ( !smallzone ) {
+ Com_Error( ERR_FATAL, "Small zone data failed to allocate %1.1f megs", (float)s_smallZoneTotal / (1024*1024) );
+ }
+ Z_ClearZone( smallzone, s_smallZoneTotal );
+
+ return;
+}
+
+void Com_InitZoneMemory( void ) {
+ cvar_t *cv;
+
+ //FIXME: 05/01/06 com_zoneMegs is useless right now as neither q3config.cfg nor
+ // Com_StartupVariable have been executed by this point. The net result is that
+ // s_zoneTotal will always be set to the default value.
+
+ // allocate the random block zone
+ cv = Cvar_Get( "com_zoneMegs", DEF_COMZONEMEGS_S, CVAR_LATCH | CVAR_ARCHIVE );
+
+ if ( cv->integer < DEF_COMZONEMEGS ) {
+ s_zoneTotal = 1024 * 1024 * DEF_COMZONEMEGS;
+ } else {
+ s_zoneTotal = cv->integer * 1024 * 1024;
+ }
+
+ // bk001205 - was malloc
+ mainzone = calloc( s_zoneTotal, 1 );
+ if ( !mainzone ) {
+ Com_Error( ERR_FATAL, "Zone data failed to allocate %i megs", s_zoneTotal / (1024*1024) );
+ }
+ Z_ClearZone( mainzone, s_zoneTotal );
+
+}
+
+/*
+=================
+Hunk_Log
+=================
+*/
+void Hunk_Log( void) {
+ hunkblock_t *block;
+ char buf[4096];
+ int size, numBlocks;
+
+ if (!logfile || !FS_Initialized())
+ return;
+ size = 0;
+ numBlocks = 0;
+ Com_sprintf(buf, sizeof(buf), "\r\n================\r\nHunk log\r\n================\r\n");
+ FS_Write(buf, strlen(buf), logfile);
+ for (block = hunkblocks ; block; block = block->next) {
+#ifdef HUNK_DEBUG
+ Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s)\r\n", block->size, block->file, block->line, block->label);
+ FS_Write(buf, strlen(buf), logfile);
+#endif
+ size += block->size;
+ numBlocks++;
+ }
+ Com_sprintf(buf, sizeof(buf), "%d Hunk memory\r\n", size);
+ FS_Write(buf, strlen(buf), logfile);
+ Com_sprintf(buf, sizeof(buf), "%d hunk blocks\r\n", numBlocks);
+ FS_Write(buf, strlen(buf), logfile);
+}
+
+/*
+=================
+Hunk_SmallLog
+=================
+*/
+void Hunk_SmallLog( void) {
+ hunkblock_t *block, *block2;
+ char buf[4096];
+ int size, locsize, numBlocks;
+
+ if (!logfile || !FS_Initialized())
+ return;
+ for (block = hunkblocks ; block; block = block->next) {
+ block->printed = qfalse;
+ }
+ size = 0;
+ numBlocks = 0;
+ Com_sprintf(buf, sizeof(buf), "\r\n================\r\nHunk Small log\r\n================\r\n");
+ FS_Write(buf, strlen(buf), logfile);
+ for (block = hunkblocks; block; block = block->next) {
+ if (block->printed) {
+ continue;
+ }
+ locsize = block->size;
+ for (block2 = block->next; block2; block2 = block2->next) {
+ if (block->line != block2->line) {
+ continue;
+ }
+ if (Q_stricmp(block->file, block2->file)) {
+ continue;
+ }
+ size += block2->size;
+ locsize += block2->size;
+ block2->printed = qtrue;
+ }
+#ifdef HUNK_DEBUG
+ Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s)\r\n", locsize, block->file, block->line, block->label);
+ FS_Write(buf, strlen(buf), logfile);
+#endif
+ size += block->size;
+ numBlocks++;
+ }
+ Com_sprintf(buf, sizeof(buf), "%d Hunk memory\r\n", size);
+ FS_Write(buf, strlen(buf), logfile);
+ Com_sprintf(buf, sizeof(buf), "%d hunk blocks\r\n", numBlocks);
+ FS_Write(buf, strlen(buf), logfile);
+}
+
+/*
+=================
+Com_InitZoneMemory
+=================
+*/
+void Com_InitHunkMemory( void ) {
+ cvar_t *cv;
+ int nMinAlloc;
+ char *pMsg = NULL;
+
+ // make sure the file system has allocated and "not" freed any temp blocks
+ // this allows the config and product id files ( journal files too ) to be loaded
+ // by the file system without redunant routines in the file system utilizing different
+ // memory systems
+ if (FS_LoadStack() != 0) {
+ Com_Error( ERR_FATAL, "Hunk initialization failed. File system load stack not zero");
+ }
+
+ // allocate the stack based hunk allocator
+ cv = Cvar_Get( "com_hunkMegs", DEF_COMHUNKMEGS_S, CVAR_LATCH | CVAR_ARCHIVE );
+
+ // if we are not dedicated min allocation is 56, otherwise min is 1
+ if (com_dedicated && com_dedicated->integer) {
+ nMinAlloc = MIN_DEDICATED_COMHUNKMEGS;
+ pMsg = "Minimum com_hunkMegs for a dedicated server is %i, allocating %i megs.\n";
+ }
+ else {
+ nMinAlloc = MIN_COMHUNKMEGS;
+ pMsg = "Minimum com_hunkMegs is %i, allocating %i megs.\n";
+ }
+
+ if ( cv->integer < nMinAlloc ) {
+ s_hunkTotal = 1024 * 1024 * nMinAlloc;
+ Com_Printf(pMsg, nMinAlloc, s_hunkTotal / (1024 * 1024));
+ } else {
+ s_hunkTotal = cv->integer * 1024 * 1024;
+ }
+
+
+ // bk001205 - was malloc
+ s_hunkData = calloc( s_hunkTotal + 31, 1 );
+ if ( !s_hunkData ) {
+ Com_Error( ERR_FATAL, "Hunk data failed to allocate %i megs", s_hunkTotal / (1024*1024) );
+ }
+ // cacheline align
+ s_hunkData = (byte *) ( ( (intptr_t)s_hunkData + 31 ) & ~31 );
+ Hunk_Clear();
+
+ Cmd_AddCommand( "meminfo", Com_Meminfo_f );
+#ifdef ZONE_DEBUG
+ Cmd_AddCommand( "zonelog", Z_LogHeap );
+#endif
+#ifdef HUNK_DEBUG
+ Cmd_AddCommand( "hunklog", Hunk_Log );
+ Cmd_AddCommand( "hunksmalllog", Hunk_SmallLog );
+#endif
+}
+
+/*
+====================
+Hunk_MemoryRemaining
+====================
+*/
+int Hunk_MemoryRemaining( void ) {
+ int low, high;
+
+ low = hunk_low.permanent > hunk_low.temp ? hunk_low.permanent : hunk_low.temp;
+ high = hunk_high.permanent > hunk_high.temp ? hunk_high.permanent : hunk_high.temp;
+
+ return s_hunkTotal - ( low + high );
+}
+
+/*
+===================
+Hunk_SetMark
+
+The server calls this after the level and game VM have been loaded
+===================
+*/
+void Hunk_SetMark( void ) {
+ hunk_low.mark = hunk_low.permanent;
+ hunk_high.mark = hunk_high.permanent;
+}
+
+/*
+=================
+Hunk_ClearToMark
+
+The client calls this before starting a vid_restart or snd_restart
+=================
+*/
+void Hunk_ClearToMark( void ) {
+ hunk_low.permanent = hunk_low.temp = hunk_low.mark;
+ hunk_high.permanent = hunk_high.temp = hunk_high.mark;
+}
+
+/*
+=================
+Hunk_CheckMark
+=================
+*/
+qboolean Hunk_CheckMark( void ) {
+ if( hunk_low.mark || hunk_high.mark ) {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+void CL_ShutdownCGame( void );
+void CL_ShutdownUI( void );
+void SV_ShutdownGameProgs( void );
+
+/*
+=================
+Hunk_Clear
+
+The server calls this before shutting down or loading a new map
+=================
+*/
+void Hunk_Clear( void ) {
+
+#ifndef DEDICATED
+ CL_ShutdownCGame();
+ CL_ShutdownUI();
+#endif
+ SV_ShutdownGameProgs();
+#ifndef DEDICATED
+ CIN_CloseAllVideos();
+#endif
+ hunk_low.mark = 0;
+ hunk_low.permanent = 0;
+ hunk_low.temp = 0;
+ hunk_low.tempHighwater = 0;
+
+ hunk_high.mark = 0;
+ hunk_high.permanent = 0;
+ hunk_high.temp = 0;
+ hunk_high.tempHighwater = 0;
+
+ hunk_permanent = &hunk_low;
+ hunk_temp = &hunk_high;
+
+ Com_Printf( "Hunk_Clear: reset the hunk ok\n" );
+ VM_Clear();
+#ifdef HUNK_DEBUG
+ hunkblocks = NULL;
+#endif
+}
+
+static void Hunk_SwapBanks( void ) {
+ hunkUsed_t *swap;
+
+ // can't swap banks if there is any temp already allocated
+ if ( hunk_temp->temp != hunk_temp->permanent ) {
+ return;
+ }
+
+ // if we have a larger highwater mark on this side, start making
+ // our permanent allocations here and use the other side for temp
+ if ( hunk_temp->tempHighwater - hunk_temp->permanent >
+ hunk_permanent->tempHighwater - hunk_permanent->permanent ) {
+ swap = hunk_temp;
+ hunk_temp = hunk_permanent;
+ hunk_permanent = swap;
+ }
+}
+
+/*
+=================
+Hunk_Alloc
+
+Allocate permanent (until the hunk is cleared) memory
+=================
+*/
+#ifdef HUNK_DEBUG
+void *Hunk_AllocDebug( int size, ha_pref preference, char *label, char *file, int line ) {
+#else
+void *Hunk_Alloc( int size, ha_pref preference ) {
+#endif
+ void *buf;
+
+ if ( s_hunkData == NULL)
+ {
+ Com_Error( ERR_FATAL, "Hunk_Alloc: Hunk memory system not initialized" );
+ }
+
+ // can't do preference if there is any temp allocated
+ if (preference == h_dontcare || hunk_temp->temp != hunk_temp->permanent) {
+ Hunk_SwapBanks();
+ } else {
+ if (preference == h_low && hunk_permanent != &hunk_low) {
+ Hunk_SwapBanks();
+ } else if (preference == h_high && hunk_permanent != &hunk_high) {
+ Hunk_SwapBanks();
+ }
+ }
+
+#ifdef HUNK_DEBUG
+ size += sizeof(hunkblock_t);
+#endif
+
+ // round to cacheline
+ size = (size+31)&~31;
+
+ if ( hunk_low.temp + hunk_high.temp + size > s_hunkTotal ) {
+#ifdef HUNK_DEBUG
+ Hunk_Log();
+ Hunk_SmallLog();
+#endif
+ Com_Error( ERR_DROP, "Hunk_Alloc failed on %i", size );
+ }
+
+ if ( hunk_permanent == &hunk_low ) {
+ buf = (void *)(s_hunkData + hunk_permanent->permanent);
+ hunk_permanent->permanent += size;
+ } else {
+ hunk_permanent->permanent += size;
+ buf = (void *)(s_hunkData + s_hunkTotal - hunk_permanent->permanent );
+ }
+
+ hunk_permanent->temp = hunk_permanent->permanent;
+
+ Com_Memset( buf, 0, size );
+
+#ifdef HUNK_DEBUG
+ {
+ hunkblock_t *block;
+
+ block = (hunkblock_t *) buf;
+ block->size = size - sizeof(hunkblock_t);
+ block->file = file;
+ block->label = label;
+ block->line = line;
+ block->next = hunkblocks;
+ hunkblocks = block;
+ buf = ((byte *) buf) + sizeof(hunkblock_t);
+ }
+#endif
+ return buf;
+}
+
+/*
+=================
+Hunk_AllocateTempMemory
+
+This is used by the file loading system.
+Multiple files can be loaded in temporary memory.
+When the files-in-use count reaches zero, all temp memory will be deleted
+=================
+*/
+void *Hunk_AllocateTempMemory( int size ) {
+ void *buf;
+ hunkHeader_t *hdr;
+
+ // return a Z_Malloc'd block if the hunk has not been initialized
+ // this allows the config and product id files ( journal files too ) to be loaded
+ // by the file system without redunant routines in the file system utilizing different
+ // memory systems
+ if ( s_hunkData == NULL )
+ {
+ return Z_Malloc(size);
+ }
+
+ Hunk_SwapBanks();
+
+ size = PAD(size, sizeof(intptr_t)) + sizeof( hunkHeader_t );
+
+ if ( hunk_temp->temp + hunk_permanent->permanent + size > s_hunkTotal ) {
+ Com_Error( ERR_DROP, "Hunk_AllocateTempMemory: failed on %i", size );
+ }
+
+ if ( hunk_temp == &hunk_low ) {
+ buf = (void *)(s_hunkData + hunk_temp->temp);
+ hunk_temp->temp += size;
+ } else {
+ hunk_temp->temp += size;
+ buf = (void *)(s_hunkData + s_hunkTotal - hunk_temp->temp );
+ }
+
+ if ( hunk_temp->temp > hunk_temp->tempHighwater ) {
+ hunk_temp->tempHighwater = hunk_temp->temp;
+ }
+
+ hdr = (hunkHeader_t *)buf;
+ buf = (void *)(hdr+1);
+
+ hdr->magic = HUNK_MAGIC;
+ hdr->size = size;
+
+ // don't bother clearing, because we are going to load a file over it
+ return buf;
+}
+
+
+/*
+==================
+Hunk_FreeTempMemory
+==================
+*/
+void Hunk_FreeTempMemory( void *buf ) {
+ hunkHeader_t *hdr;
+
+ // free with Z_Free if the hunk has not been initialized
+ // this allows the config and product id files ( journal files too ) to be loaded
+ // by the file system without redunant routines in the file system utilizing different
+ // memory systems
+ if ( s_hunkData == NULL )
+ {
+ Z_Free(buf);
+ return;
+ }
+
+
+ hdr = ( (hunkHeader_t *)buf ) - 1;
+ if ( hdr->magic != HUNK_MAGIC ) {
+ Com_Error( ERR_FATAL, "Hunk_FreeTempMemory: bad magic" );
+ }
+
+ hdr->magic = HUNK_FREE_MAGIC;
+
+ // this only works if the files are freed in stack order,
+ // otherwise the memory will stay around until Hunk_ClearTempMemory
+ if ( hunk_temp == &hunk_low ) {
+ if ( hdr == (void *)(s_hunkData + hunk_temp->temp - hdr->size ) ) {
+ hunk_temp->temp -= hdr->size;
+ } else {
+ Com_Printf( "Hunk_FreeTempMemory: not the final block\n" );
+ }
+ } else {
+ if ( hdr == (void *)(s_hunkData + s_hunkTotal - hunk_temp->temp ) ) {
+ hunk_temp->temp -= hdr->size;
+ } else {
+ Com_Printf( "Hunk_FreeTempMemory: not the final block\n" );
+ }
+ }
+}
+
+
+/*
+=================
+Hunk_ClearTempMemory
+
+The temp space is no longer needed. If we have left more
+touched but unused memory on this side, have future
+permanent allocs use this side.
+=================
+*/
+void Hunk_ClearTempMemory( void ) {
+ if ( s_hunkData != NULL ) {
+ hunk_temp->temp = hunk_temp->permanent;
+ }
+}
+
+/*
+=================
+Hunk_Trash
+=================
+*/
+void Hunk_Trash( void ) {
+ int length, i, rnd;
+ char *buf, value;
+
+ return;
+
+ if ( s_hunkData == NULL )
+ return;
+
+#ifdef _DEBUG
+ Com_Error(ERR_DROP, "hunk trashed\n");
+ return;
+#endif
+
+ Cvar_Set("com_jp", "1");
+ Hunk_SwapBanks();
+
+ if ( hunk_permanent == &hunk_low ) {
+ buf = (void *)(s_hunkData + hunk_permanent->permanent);
+ } else {
+ buf = (void *)(s_hunkData + s_hunkTotal - hunk_permanent->permanent );
+ }
+ length = hunk_permanent->permanent;
+
+ if (length > 0x7FFFF) {
+ //randomly trash data within buf
+ rnd = random() * (length - 0x7FFFF);
+ value = 31;
+ for (i = 0; i < 0x7FFFF; i++) {
+ value *= 109;
+ buf[rnd+i] ^= value;
+ }
+ }
+}
+
+/*
+===================================================================
+
+EVENTS AND JOURNALING
+
+In addition to these events, .cfg files are also copied to the
+journaled file
+===================================================================
+*/
+
+// bk001129 - here we go again: upped from 64
+// FIXME TTimo blunt upping from 256 to 1024
+// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=5
+#define MAX_PUSHED_EVENTS 1024
+// bk001129 - init, also static
+static int com_pushedEventsHead = 0;
+static int com_pushedEventsTail = 0;
+// bk001129 - static
+static sysEvent_t com_pushedEvents[MAX_PUSHED_EVENTS];
+
+/*
+=================
+Com_InitJournaling
+=================
+*/
+void Com_InitJournaling( void ) {
+ Com_StartupVariable( "journal" );
+ com_journal = Cvar_Get ("journal", "0", CVAR_INIT);
+ if ( !com_journal->integer ) {
+ return;
+ }
+
+ if ( com_journal->integer == 1 ) {
+ Com_Printf( "Journaling events\n");
+ com_journalFile = FS_FOpenFileWrite( "journal.dat" );
+ com_journalDataFile = FS_FOpenFileWrite( "journaldata.dat" );
+ } else if ( com_journal->integer == 2 ) {
+ Com_Printf( "Replaying journaled events\n");
+ FS_FOpenFileRead( "journal.dat", &com_journalFile, qtrue );
+ FS_FOpenFileRead( "journaldata.dat", &com_journalDataFile, qtrue );
+ }
+
+ if ( !com_journalFile || !com_journalDataFile ) {
+ Cvar_Set( "com_journal", "0" );
+ com_journalFile = 0;
+ com_journalDataFile = 0;
+ Com_Printf( "Couldn't open journal files\n" );
+ }
+}
+
+/*
+=================
+Com_GetRealEvent
+=================
+*/
+sysEvent_t Com_GetRealEvent( void ) {
+ int r;
+ sysEvent_t ev;
+
+ // either get an event from the system or the journal file
+ if ( com_journal->integer == 2 ) {
+ r = FS_Read( &ev, sizeof(ev), com_journalFile );
+ if ( r != sizeof(ev) ) {
+ Com_Error( ERR_FATAL, "Error reading from journal file" );
+ }
+ if ( ev.evPtrLength ) {
+ ev.evPtr = Z_Malloc( ev.evPtrLength );
+ r = FS_Read( ev.evPtr, ev.evPtrLength, com_journalFile );
+ if ( r != ev.evPtrLength ) {
+ Com_Error( ERR_FATAL, "Error reading from journal file" );
+ }
+ }
+ } else {
+ ev = Sys_GetEvent();
+
+ // write the journal value out if needed
+ if ( com_journal->integer == 1 ) {
+ r = FS_Write( &ev, sizeof(ev), com_journalFile );
+ if ( r != sizeof(ev) ) {
+ Com_Error( ERR_FATAL, "Error writing to journal file" );
+ }
+ if ( ev.evPtrLength ) {
+ r = FS_Write( ev.evPtr, ev.evPtrLength, com_journalFile );
+ if ( r != ev.evPtrLength ) {
+ Com_Error( ERR_FATAL, "Error writing to journal file" );
+ }
+ }
+ }
+ }
+
+ return ev;
+}
+
+
+/*
+=================
+Com_InitPushEvent
+=================
+*/
+// bk001129 - added
+void Com_InitPushEvent( void ) {
+ // clear the static buffer array
+ // this requires SE_NONE to be accepted as a valid but NOP event
+ memset( com_pushedEvents, 0, sizeof(com_pushedEvents) );
+ // reset counters while we are at it
+ // beware: GetEvent might still return an SE_NONE from the buffer
+ com_pushedEventsHead = 0;
+ com_pushedEventsTail = 0;
+}
+
+
+/*
+=================
+Com_PushEvent
+=================
+*/
+void Com_PushEvent( sysEvent_t *event ) {
+ sysEvent_t *ev;
+ static int printedWarning = 0; // bk001129 - init, bk001204 - explicit int
+
+ ev = &com_pushedEvents[ com_pushedEventsHead & (MAX_PUSHED_EVENTS-1) ];
+
+ if ( com_pushedEventsHead - com_pushedEventsTail >= MAX_PUSHED_EVENTS ) {
+
+ // don't print the warning constantly, or it can give time for more...
+ if ( !printedWarning ) {
+ printedWarning = qtrue;
+ Com_Printf( "WARNING: Com_PushEvent overflow\n" );
+ }
+
+ if ( ev->evPtr ) {
+ Z_Free( ev->evPtr );
+ }
+ com_pushedEventsTail++;
+ } else {
+ printedWarning = qfalse;
+ }
+
+ *ev = *event;
+ com_pushedEventsHead++;
+}
+
+/*
+=================
+Com_GetEvent
+=================
+*/
+sysEvent_t Com_GetEvent( void ) {
+ if ( com_pushedEventsHead > com_pushedEventsTail ) {
+ com_pushedEventsTail++;
+ return com_pushedEvents[ (com_pushedEventsTail-1) & (MAX_PUSHED_EVENTS-1) ];
+ }
+ return Com_GetRealEvent();
+}
+
+/*
+=================
+Com_RunAndTimeServerPacket
+=================
+*/
+void Com_RunAndTimeServerPacket( netadr_t *evFrom, msg_t *buf ) {
+ int t1, t2, msec;
+
+ t1 = 0;
+
+ if ( com_speeds->integer ) {
+ t1 = Sys_Milliseconds ();
+ }
+
+ SV_PacketEvent( *evFrom, buf );
+
+ if ( com_speeds->integer ) {
+ t2 = Sys_Milliseconds ();
+ msec = t2 - t1;
+ if ( com_speeds->integer == 3 ) {
+ Com_Printf( "SV_PacketEvent time: %i\n", msec );
+ }
+ }
+}
+
+/*
+=================
+Com_EventLoop
+
+Returns last event time
+=================
+*/
+int Com_EventLoop( void ) {
+ sysEvent_t ev;
+ netadr_t evFrom;
+ byte bufData[MAX_MSGLEN];
+ msg_t buf;
+
+ MSG_Init( &buf, bufData, sizeof( bufData ) );
+
+ while ( 1 ) {
+ NET_FlushPacketQueue();
+ ev = Com_GetEvent();
+
+ // if no more events are available
+ if ( ev.evType == SE_NONE ) {
+ // manually send packet events for the loopback channel
+ while ( NET_GetLoopPacket( NS_CLIENT, &evFrom, &buf ) ) {
+ CL_PacketEvent( evFrom, &buf );
+ }
+
+ while ( NET_GetLoopPacket( NS_SERVER, &evFrom, &buf ) ) {
+ // if the server just shut down, flush the events
+ if ( com_sv_running->integer ) {
+ Com_RunAndTimeServerPacket( &evFrom, &buf );
+ }
+ }
+
+ return ev.evTime;
+ }
+
+
+ switch ( ev.evType ) {
+ default:
+ // bk001129 - was ev.evTime
+ Com_Error( ERR_FATAL, "Com_EventLoop: bad event type %i", ev.evType );
+ break;
+ case SE_NONE:
+ break;
+ case SE_KEY:
+ CL_KeyEvent( ev.evValue, ev.evValue2, ev.evTime );
+ break;
+ case SE_CHAR:
+ CL_CharEvent( ev.evValue );
+ break;
+ case SE_MOUSE:
+ CL_MouseEvent( ev.evValue, ev.evValue2, ev.evTime );
+ break;
+ case SE_JOYSTICK_AXIS:
+ CL_JoystickEvent( ev.evValue, ev.evValue2, ev.evTime );
+ break;
+ case SE_CONSOLE:
+ Cbuf_AddText( (char *)ev.evPtr );
+ Cbuf_AddText( "\n" );
+ break;
+ case SE_PACKET:
+ // this cvar allows simulation of connections that
+ // drop a lot of packets. Note that loopback connections
+ // don't go through here at all.
+ if ( com_dropsim->value > 0 ) {
+ static int seed;
+
+ if ( Q_random( &seed ) < com_dropsim->value ) {
+ break; // drop this packet
+ }
+ }
+
+ evFrom = *(netadr_t *)ev.evPtr;
+ buf.cursize = ev.evPtrLength - sizeof( evFrom );
+
+ // we must copy the contents of the message out, because
+ // the event buffers are only large enough to hold the
+ // exact payload, but channel messages need to be large
+ // enough to hold fragment reassembly
+ if ( (unsigned)buf.cursize > buf.maxsize ) {
+ Com_Printf("Com_EventLoop: oversize packet\n");
+ continue;
+ }
+ Com_Memcpy( buf.data, (byte *)((netadr_t *)ev.evPtr + 1), buf.cursize );
+ if ( com_sv_running->integer ) {
+ Com_RunAndTimeServerPacket( &evFrom, &buf );
+ } else {
+ CL_PacketEvent( evFrom, &buf );
+ }
+ break;
+ }
+
+ // free any block data
+ if ( ev.evPtr ) {
+ Z_Free( ev.evPtr );
+ }
+ }
+
+ return 0; // never reached
+}
+
+/*
+================
+Com_Milliseconds
+
+Can be used for profiling, but will be journaled accurately
+================
+*/
+int Com_Milliseconds (void) {
+ sysEvent_t ev;
+
+ // get events and push them until we get a null event with the current time
+ do {
+
+ ev = Com_GetRealEvent();
+ if ( ev.evType != SE_NONE ) {
+ Com_PushEvent( &ev );
+ }
+ } while ( ev.evType != SE_NONE );
+
+ return ev.evTime;
+}
+
+//============================================================================
+
+/*
+=============
+Com_Error_f
+
+Just throw a fatal error to
+test error shutdown procedures
+=============
+*/
+static void Com_Error_f (void) {
+ if ( Cmd_Argc() > 1 ) {
+ Com_Error( ERR_DROP, "Testing drop error" );
+ } else {
+ Com_Error( ERR_FATAL, "Testing fatal error" );
+ }
+}
+
+
+/*
+=============
+Com_Freeze_f
+
+Just freeze in place for a given number of seconds to test
+error recovery
+=============
+*/
+static void Com_Freeze_f (void) {
+ float s;
+ int start, now;
+
+ if ( Cmd_Argc() != 2 ) {
+ Com_Printf( "freeze <seconds>\n" );
+ return;
+ }
+ s = atof( Cmd_Argv(1) );
+
+ start = Com_Milliseconds();
+
+ while ( 1 ) {
+ now = Com_Milliseconds();
+ if ( ( now - start ) * 0.001 > s ) {
+ break;
+ }
+ }
+}
+
+/*
+=================
+Com_Crash_f
+
+A way to force a bus error for development reasons
+=================
+*/
+static void Com_Crash_f( void ) {
+ * ( int * ) 0 = 0x12345678;
+}
+
+static void Com_DetectAltivec(void)
+{
+ // Only detect if user hasn't forcibly disabled it.
+ if (com_altivec->integer) {
+ static qboolean altivec = qfalse;
+ static qboolean detected = qfalse;
+ if (!detected) {
+ altivec = Sys_DetectAltivec();
+ detected = qtrue;
+ }
+
+ if (!altivec) {
+ Cvar_Set( "com_altivec", "0" ); // we don't have it! Disable support!
+ }
+ }
+}
+
+
+/*
+=================
+Com_Init
+=================
+*/
+void Com_Init( char *commandLine ) {
+ char *s;
+
+ Com_Printf( "%s %s %s\n", SVN_VERSION, PLATFORM_STRING, __DATE__ );
+
+ if ( setjmp (abortframe) ) {
+ Sys_Error ("Error during initialization");
+ }
+
+ // bk001129 - do this before anything else decides to push events
+ Com_InitPushEvent();
+
+ Com_InitSmallZoneMemory();
+ Cvar_Init ();
+
+ // prepare enough of the subsystems to handle
+ // cvar and command buffer management
+ Com_ParseCommandLine( commandLine );
+
+// Swap_Init ();
+ Cbuf_Init ();
+
+ Com_InitZoneMemory();
+ Cmd_Init ();
+
+ // override anything from the config files with command line args
+ Com_StartupVariable( NULL );
+
+ // get the developer cvar set as early as possible
+ Com_StartupVariable( "developer" );
+
+ // done early so bind command exists
+ CL_InitKeyCommands();
+
+ FS_InitFilesystem ();
+
+ Com_InitJournaling();
+
+ Cbuf_AddText ("exec default.cfg\n");
+
+ // skip the autogen.cfg if "safe" is on the command line
+ if ( !Com_SafeMode() ) {
+ Cbuf_AddText ("exec autogen.cfg\n");
+ }
+
+ Cbuf_AddText ("exec autoexec.cfg\n");
+
+ Cbuf_Execute ();
+
+ // override anything from the config files with command line args
+ Com_StartupVariable( NULL );
+
+ // get dedicated here for proper hunk megs initialization
+#ifdef DEDICATED
+ com_dedicated = Cvar_Get ("dedicated", "1", CVAR_ROM);
+#else
+ com_dedicated = Cvar_Get ("dedicated", "0", CVAR_LATCH);
+#endif
+ // allocate the stack based hunk allocator
+ Com_InitHunkMemory();
+
+ // if any archived cvars are modified after this, we will trigger a writing
+ // of the config file
+ cvar_modifiedFlags &= ~CVAR_ARCHIVE;
+
+ //
+ // init commands and vars
+ //
+ com_altivec = Cvar_Get ("com_altivec", "1", CVAR_ARCHIVE);
+ com_maxfps = Cvar_Get ("com_maxfps", "85", CVAR_ARCHIVE);
+ com_blood = Cvar_Get ("com_blood", "1", CVAR_ARCHIVE);
+
+ com_developer = Cvar_Get ("developer", "0", CVAR_TEMP );
+ com_logfile = Cvar_Get ("logfile", "0", CVAR_TEMP );
+
+ com_timescale = Cvar_Get ("timescale", "1", CVAR_CHEAT | CVAR_SYSTEMINFO );
+ com_fixedtime = Cvar_Get ("fixedtime", "0", CVAR_CHEAT);
+ com_showtrace = Cvar_Get ("com_showtrace", "0", CVAR_CHEAT);
+ com_dropsim = Cvar_Get ("com_dropsim", "0", CVAR_CHEAT);
+ com_viewlog = Cvar_Get( "viewlog", "0", CVAR_CHEAT );
+ com_speeds = Cvar_Get ("com_speeds", "0", 0);
+ com_timedemo = Cvar_Get ("timedemo", "0", CVAR_CHEAT);
+ com_cameraMode = Cvar_Get ("com_cameraMode", "0", CVAR_CHEAT);
+
+ cl_paused = Cvar_Get ("cl_paused", "0", CVAR_ROM);
+ sv_paused = Cvar_Get ("sv_paused", "0", CVAR_ROM);
+ cl_packetdelay = Cvar_Get ("cl_packetdelay", "0", CVAR_CHEAT);
+ sv_packetdelay = Cvar_Get ("sv_packetdelay", "0", CVAR_CHEAT);
+ com_sv_running = Cvar_Get ("sv_running", "0", CVAR_ROM);
+ com_cl_running = Cvar_Get ("cl_running", "0", CVAR_ROM);
+ com_buildScript = Cvar_Get( "com_buildScript", "0", 0 );
+
+#if defined(_WIN32) && defined(_DEBUG)
+ com_noErrorInterrupt = Cvar_Get( "com_noErrorInterrupt", "0", 0 );
+#endif
+
+ if ( com_dedicated->integer ) {
+ if ( !com_viewlog->integer ) {
+ Cvar_Set( "viewlog", "1" );
+ }
+ }
+
+ if ( com_developer && com_developer->integer ) {
+ Cmd_AddCommand ("error", Com_Error_f);
+ Cmd_AddCommand ("crash", Com_Crash_f );
+ Cmd_AddCommand ("freeze", Com_Freeze_f);
+ }
+ Cmd_AddCommand ("quit", Com_Quit_f);
+ Cmd_AddCommand ("changeVectors", MSG_ReportChangeVectors_f );
+ Cmd_AddCommand ("writeconfig", Com_WriteConfig_f );
+
+ s = va("%s %s %s", Q3_VERSION, PLATFORM_STRING, __DATE__ );
+ com_version = Cvar_Get ("version", s, CVAR_ROM | CVAR_SERVERINFO );
+
+ Sys_Init();
+ Netchan_Init( Com_Milliseconds() & 0xffff ); // pick a port value that should be nice and random
+ VM_Init();
+ SV_Init();
+
+ com_dedicated->modified = qfalse;
+ if ( !com_dedicated->integer ) {
+ CL_Init();
+ Sys_ShowConsole( com_viewlog->integer, qfalse );
+ }
+
+ // set com_frameTime so that if a map is started on the
+ // command line it will still be able to count on com_frameTime
+ // being random enough for a serverid
+ com_frameTime = Com_Milliseconds();
+
+ // add + commands from command line
+ if ( !Com_AddStartupCommands() ) {
+ // if the user didn't give any commands, run default action
+ if ( !com_dedicated->integer ) {
+ Cbuf_AddText ("cinematic splash.RoQ\n");
+ }
+ }
+
+ // start in full screen ui mode
+ Cvar_Set("r_uiFullScreen", "1");
+
+ CL_StartHunkUsers();
+
+ // make sure single player is off by default
+ Cvar_Set("ui_singlePlayerActive", "0");
+
+ com_fullyInitialized = qtrue;
+
+ // always set the cvar, but only print the info if it makes sense.
+ Com_DetectAltivec();
+ #if idppc
+ Com_Printf ("Altivec support is %s\n", com_altivec->integer ? "enabled" : "disabled");
+ #endif
+
+ Com_Printf ("--- Common Initialization Complete ---\n");
+}
+
+//==================================================================
+
+void Com_WriteConfigToFile( const char *filename ) {
+ fileHandle_t f;
+
+ f = FS_FOpenFileWrite( filename );
+ if ( !f ) {
+ Com_Printf ("Couldn't write %s.\n", filename );
+ return;
+ }
+
+ FS_Printf (f, "// generated by tremulous, do not modify\n");
+ Key_WriteBindings (f);
+ Cvar_WriteVariables (f);
+ FS_FCloseFile( f );
+}
+
+
+/*
+===============
+Com_WriteConfiguration
+
+Writes key bindings and archived cvars to config file if modified
+===============
+*/
+void Com_WriteConfiguration( void ) {
+ // if we are quiting without fully initializing, make sure
+ // we don't write out anything
+ if ( !com_fullyInitialized ) {
+ return;
+ }
+
+ if ( !(cvar_modifiedFlags & CVAR_ARCHIVE ) ) {
+ return;
+ }
+ cvar_modifiedFlags &= ~CVAR_ARCHIVE;
+
+ Com_WriteConfigToFile( "autogen.cfg" );
+}
+
+
+/*
+===============
+Com_WriteConfig_f
+
+Write the config file to a specific name
+===============
+*/
+void Com_WriteConfig_f( void ) {
+ char filename[MAX_QPATH];
+
+ if ( Cmd_Argc() != 2 ) {
+ Com_Printf( "Usage: writeconfig <filename>\n" );
+ return;
+ }
+
+ Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) );
+ COM_DefaultExtension( filename, sizeof( filename ), ".cfg" );
+ Com_Printf( "Writing %s.\n", filename );
+ Com_WriteConfigToFile( filename );
+}
+
+/*
+================
+Com_ModifyMsec
+================
+*/
+int Com_ModifyMsec( int msec ) {
+ int clampTime;
+
+ //
+ // modify time for debugging values
+ //
+ if ( com_fixedtime->integer ) {
+ msec = com_fixedtime->integer;
+ } else if ( com_timescale->value ) {
+ msec *= com_timescale->value;
+ } else if (com_cameraMode->integer) {
+ msec *= com_timescale->value;
+ }
+
+ // don't let it scale below 1 msec
+ if ( msec < 1 && com_timescale->value) {
+ msec = 1;
+ }
+
+ if ( com_dedicated->integer ) {
+ // dedicated servers don't want to clamp for a much longer
+ // period, because it would mess up all the client's views
+ // of time.
+ if (com_sv_running->integer && msec > 500)
+ Com_Printf( "Hitch warning: %i msec frame time\n", msec );
+
+ clampTime = 5000;
+ } else
+ if ( !com_sv_running->integer ) {
+ // clients of remote servers do not want to clamp time, because
+ // it would skew their view of the server's time temporarily
+ clampTime = 5000;
+ } else {
+ // for local single player gaming
+ // we may want to clamp the time to prevent players from
+ // flying off edges when something hitches.
+ clampTime = 200;
+ }
+
+ if ( msec > clampTime ) {
+ msec = clampTime;
+ }
+
+ return msec;
+}
+
+/*
+=================
+Com_Frame
+=================
+*/
+void Com_Frame( void ) {
+
+ int msec, minMsec;
+ static int lastTime;
+ int key;
+
+ int timeBeforeFirstEvents;
+ int timeBeforeServer;
+ int timeBeforeEvents;
+ int timeBeforeClient;
+ int timeAfter;
+
+
+
+
+
+ if ( setjmp (abortframe) ) {
+ return; // an ERR_DROP was thrown
+ }
+
+ // bk001204 - init to zero.
+ // also: might be clobbered by `longjmp' or `vfork'
+ timeBeforeFirstEvents =0;
+ timeBeforeServer =0;
+ timeBeforeEvents =0;
+ timeBeforeClient = 0;
+ timeAfter = 0;
+
+
+ // old net chan encryption key
+ key = 0x87243987;
+
+ // write config file if anything changed
+ Com_WriteConfiguration();
+
+ // if "viewlog" has been modified, show or hide the log console
+ if ( com_viewlog->modified ) {
+ if ( !com_dedicated->value ) {
+ Sys_ShowConsole( com_viewlog->integer, qfalse );
+ }
+ com_viewlog->modified = qfalse;
+ }
+
+ //
+ // main event loop
+ //
+ if ( com_speeds->integer ) {
+ timeBeforeFirstEvents = Sys_Milliseconds ();
+ }
+
+ // we may want to spin here if things are going too fast
+ if ( !com_dedicated->integer && com_maxfps->integer > 0 && !com_timedemo->integer ) {
+ minMsec = 1000 / com_maxfps->integer;
+ } else {
+ minMsec = 1;
+ }
+ do {
+ com_frameTime = Com_EventLoop();
+ if ( lastTime > com_frameTime ) {
+ lastTime = com_frameTime; // possible on first frame
+ }
+ msec = com_frameTime - lastTime;
+ } while ( msec < minMsec );
+ Cbuf_Execute ();
+
+ if (com_altivec->modified)
+ {
+ Com_DetectAltivec();
+ com_altivec->modified = qfalse;
+ }
+
+ lastTime = com_frameTime;
+
+ // mess with msec if needed
+ com_frameMsec = msec;
+ msec = Com_ModifyMsec( msec );
+
+ //
+ // server side
+ //
+ if ( com_speeds->integer ) {
+ timeBeforeServer = Sys_Milliseconds ();
+ }
+
+ SV_Frame( msec );
+
+ // if "dedicated" has been modified, start up
+ // or shut down the client system.
+ // Do this after the server may have started,
+ // but before the client tries to auto-connect
+ if ( com_dedicated->modified ) {
+ // get the latched value
+ Cvar_Get( "dedicated", "0", 0 );
+ com_dedicated->modified = qfalse;
+ if ( !com_dedicated->integer ) {
+ CL_Init();
+ Sys_ShowConsole( com_viewlog->integer, qfalse );
+ } else {
+ CL_Shutdown();
+ Sys_ShowConsole( 1, qtrue );
+ }
+ }
+
+ //
+ // client system
+ //
+ if ( !com_dedicated->integer ) {
+ //
+ // run event loop a second time to get server to client packets
+ // without a frame of latency
+ //
+ if ( com_speeds->integer ) {
+ timeBeforeEvents = Sys_Milliseconds ();
+ }
+ Com_EventLoop();
+ Cbuf_Execute ();
+
+
+ //
+ // client side
+ //
+ if ( com_speeds->integer ) {
+ timeBeforeClient = Sys_Milliseconds ();
+ }
+
+ CL_Frame( msec );
+
+ if ( com_speeds->integer ) {
+ timeAfter = Sys_Milliseconds ();
+ }
+ }
+
+ //
+ // report timing information
+ //
+ if ( com_speeds->integer ) {
+ int all, sv, ev, cl;
+
+ all = timeAfter - timeBeforeServer;
+ sv = timeBeforeEvents - timeBeforeServer;
+ ev = timeBeforeServer - timeBeforeFirstEvents + timeBeforeClient - timeBeforeEvents;
+ cl = timeAfter - timeBeforeClient;
+ sv -= time_game;
+ cl -= time_frontend + time_backend;
+
+ Com_Printf ("frame:%i all:%3i sv:%3i ev:%3i cl:%3i gm:%3i rf:%3i bk:%3i\n",
+ com_frameNumber, all, sv, ev, cl, time_game, time_frontend, time_backend );
+ }
+
+ //
+ // trace optimization tracking
+ //
+ if ( com_showtrace->integer ) {
+
+ extern int c_traces, c_brush_traces, c_patch_traces;
+ extern int c_pointcontents;
+
+ Com_Printf ("%4i traces (%ib %ip) %4i points\n", c_traces,
+ c_brush_traces, c_patch_traces, c_pointcontents);
+ c_traces = 0;
+ c_brush_traces = 0;
+ c_patch_traces = 0;
+ c_pointcontents = 0;
+ }
+
+ // old net chan encryption key
+ key = lastTime * 0x87243987;
+
+ com_frameNumber++;
+}
+
+/*
+=================
+Com_Shutdown
+=================
+*/
+void Com_Shutdown (void) {
+ if (logfile) {
+ FS_FCloseFile (logfile);
+ logfile = 0;
+ }
+
+ if ( com_journalFile ) {
+ FS_FCloseFile( com_journalFile );
+ com_journalFile = 0;
+ }
+
+}
+
+//------------------------------------------------------------------------
+
+
+/*
+=====================
+Q_acos
+
+the msvc acos doesn't always return a value between -PI and PI:
+
+int i;
+i = 1065353246;
+acos(*(float*) &i) == -1.#IND0
+
+ This should go in q_math but it is too late to add new traps
+ to game and ui
+=====================
+*/
+float Q_acos(float c) {
+ float angle;
+
+ angle = acos(c);
+
+ if (angle > M_PI) {
+ return (float)M_PI;
+ }
+ if (angle < -M_PI) {
+ return (float)M_PI;
+ }
+ return angle;
+}
+
+/*
+===========================================
+command line completion
+===========================================
+*/
+
+/*
+==================
+Field_Clear
+==================
+*/
+void Field_Clear( field_t *edit ) {
+ memset(edit->buffer, 0, MAX_EDIT_LINE);
+ edit->cursor = 0;
+ edit->scroll = 0;
+}
+
+static const char *completionString;
+static char shortestMatch[MAX_TOKEN_CHARS];
+static int matchCount;
+// field we are working on, passed to Field_AutoComplete(&g_consoleCommand for instance)
+static field_t *completionField;
+
+/*
+===============
+FindMatches
+
+===============
+*/
+static void FindMatches( const char *s ) {
+ int i;
+
+ if ( Q_stricmpn( s, completionString, strlen( completionString ) ) ) {
+ return;
+ }
+ matchCount++;
+ if ( matchCount == 1 ) {
+ Q_strncpyz( shortestMatch, s, sizeof( shortestMatch ) );
+ return;
+ }
+
+ // cut shortestMatch to the amount common with s
+ for ( i = 0 ; shortestMatch[i] ; i++ ) {
+ if ( i >= strlen( s ) ) {
+ shortestMatch[i] = 0;
+ break;
+ }
+
+ if ( tolower(shortestMatch[i]) != tolower(s[i]) ) {
+ shortestMatch[i] = 0;
+ }
+ }
+}
+
+/*
+===============
+PrintMatches
+
+===============
+*/
+static void PrintMatches( const char *s ) {
+ if ( !Q_stricmpn( s, shortestMatch, strlen( shortestMatch ) ) ) {
+ Com_Printf( " %s\n", s );
+ }
+}
+
+/*
+===============
+PrintCvarMatches
+
+===============
+*/
+static void PrintCvarMatches( const char *s ) {
+ char value[ TRUNCATE_LENGTH ];
+
+ if ( !Q_stricmpn( s, shortestMatch, strlen( shortestMatch ) ) ) {
+ Com_TruncateLongString( value, Cvar_VariableString( s ) );
+ Com_Printf( " %s = \"%s\"\n", s, value );
+ }
+}
+
+/*
+===============
+Field_FindFirstSeparator
+===============
+*/
+static char *Field_FindFirstSeparator( char *s )
+{
+ int i;
+
+ for( i = 0; i < strlen( s ); i++ )
+ {
+ if( s[ i ] == ';' )
+ return &s[ i ];
+ }
+
+ return NULL;
+}
+
+/*
+===============
+Field_CompleteFilename
+===============
+*/
+static void Field_CompleteFilename( const char *dir,
+ const char *ext, qboolean stripExt )
+{
+ matchCount = 0;
+ shortestMatch[ 0 ] = 0;
+
+ FS_FilenameCompletion( dir, ext, stripExt, FindMatches );
+
+ if( matchCount == 0 )
+ return;
+
+ Q_strcat( completionField->buffer, sizeof( completionField->buffer ),
+ shortestMatch + strlen( completionString ) );
+ completionField->cursor = strlen( completionField->buffer );
+
+ if( matchCount == 1 )
+ {
+ Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " );
+ completionField->cursor++;
+ return;
+ }
+
+ Com_Printf( "]%s\n", completionField->buffer );
+
+ FS_FilenameCompletion( dir, ext, stripExt, PrintMatches );
+}
+
+/*
+===============
+Field_CompleteCommand
+===============
+*/
+static void Field_CompleteCommand( char *cmd,
+ qboolean doCommands, qboolean doCvars )
+{
+ int completionArgument = 0;
+ char *p;
+
+ // Skip leading whitespace and quotes
+ cmd = Com_SkipCharset( cmd, " \"" );
+
+ Cmd_TokenizeStringIgnoreQuotes( cmd );
+ completionArgument = Cmd_Argc( );
+
+ // If there is trailing whitespace on the cmd
+ if( *( cmd + strlen( cmd ) - 1 ) == ' ' )
+ {
+ completionString = "";
+ completionArgument++;
+ }
+ else
+ completionString = Cmd_Argv( completionArgument - 1 );
+
+ if( completionArgument > 1 )
+ {
+ const char *baseCmd = Cmd_Argv( 0 );
+
+#ifndef DEDICATED
+ // If the very first token does not have a leading \ or /,
+ // refuse to autocomplete
+ if( cmd == completionField->buffer )
+ {
+ if( baseCmd[ 0 ] != '\\' && baseCmd[ 0 ] != '/' )
+ return;
+
+ baseCmd++;
+ }
+#endif
+
+ if( ( p = Field_FindFirstSeparator( cmd ) ) )
+ {
+ // Compound command
+ Field_CompleteCommand( p + 1, qtrue, qtrue );
+ }
+ else
+ {
+ // FIXME: all this junk should really be associated with the respective
+ // commands, instead of being hard coded here
+ if( ( !Q_stricmp( baseCmd, "map" ) ||
+ !Q_stricmp( baseCmd, "devmap" ) ||
+ !Q_stricmp( baseCmd, "spmap" ) ||
+ !Q_stricmp( baseCmd, "spdevmap" ) ) &&
+ completionArgument == 2 )
+ {
+ Field_CompleteFilename( "maps", "bsp", qtrue );
+ }
+ else if( ( !Q_stricmp( baseCmd, "exec" ) ||
+ !Q_stricmp( baseCmd, "writeconfig" ) ) &&
+ completionArgument == 2 )
+ {
+ Field_CompleteFilename( "", "cfg", qfalse );
+ }
+ else if( !Q_stricmp( baseCmd, "condump" ) &&
+ completionArgument == 2 )
+ {
+ Field_CompleteFilename( "", "txt", qfalse );
+ }
+ else if( !Q_stricmp( baseCmd, "demo" ) && completionArgument == 2 )
+ {
+ char demoExt[ 16 ];
+
+ Com_sprintf( demoExt, sizeof( demoExt ), ".dm_%d", PROTOCOL_VERSION );
+ Field_CompleteFilename( "demos", demoExt, qtrue );
+ }
+ else if( ( !Q_stricmp( baseCmd, "toggle" ) ||
+ !Q_stricmp( baseCmd, "vstr" ) ||
+ !Q_stricmp( baseCmd, "set" ) ||
+ !Q_stricmp( baseCmd, "seta" ) ||
+ !Q_stricmp( baseCmd, "setu" ) ||
+ !Q_stricmp( baseCmd, "sets" ) ) &&
+ completionArgument == 2 )
+ {
+ // Skip "<cmd> "
+ p = Com_SkipTokens( cmd, 1, " " );
+
+ if( p > cmd )
+ Field_CompleteCommand( p, qfalse, qtrue );
+ }
+ else if( !Q_stricmp( baseCmd, "rcon" ) && completionArgument == 2 )
+ {
+ // Skip "rcon "
+ p = Com_SkipTokens( cmd, 1, " " );
+
+ if( p > cmd )
+ Field_CompleteCommand( p, qtrue, qtrue );
+ }
+ else if( !Q_stricmp( baseCmd, "bind" ) && completionArgument >= 3 )
+ {
+ // Skip "bind <key> "
+ p = Com_SkipTokens( cmd, 2, " " );
+
+ if( p > cmd )
+ Field_CompleteCommand( p, qtrue, qtrue );
+ }
+ }
+ }
+ else
+ {
+ if( completionString[0] == '\\' || completionString[0] == '/' )
+ completionString++;
+
+ matchCount = 0;
+ shortestMatch[ 0 ] = 0;
+
+ if( strlen( completionString ) == 0 )
+ return;
+
+ if( doCommands )
+ Cmd_CommandCompletion( FindMatches );
+
+ if( doCvars )
+ Cvar_CommandCompletion( FindMatches );
+
+ if( matchCount == 0 )
+ return; // no matches
+
+ if( cmd == completionField->buffer )
+ {
+#ifndef DEDICATED
+ Com_sprintf( completionField->buffer,
+ sizeof( completionField->buffer ), "\\%s", shortestMatch );
+#else
+ Com_sprintf( completionField->buffer,
+ sizeof( completionField->buffer ), "%s", shortestMatch );
+#endif
+ }
+ else
+ {
+ Q_strcat( completionField->buffer, sizeof( completionField->buffer ),
+ shortestMatch + strlen( completionString ) );
+ }
+
+ completionField->cursor = strlen( completionField->buffer );
+
+ if( matchCount == 1 )
+ {
+ Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " );
+ completionField->cursor++;
+ return;
+ }
+
+ Com_Printf( "]%s\n", completionField->buffer );
+
+ // run through again, printing matches
+ if( doCommands )
+ Cmd_CommandCompletion( PrintMatches );
+
+ if( doCvars )
+ Cvar_CommandCompletion( PrintCvarMatches );
+ }
+}
+
+/*
+===============
+Field_AutoComplete
+
+Perform Tab expansion
+===============
+*/
+void Field_AutoComplete( field_t *field )
+{
+ completionField = field;
+
+ Field_CompleteCommand( completionField->buffer, qtrue, qtrue );
+}
+
+/*
+==================
+Com_RandomBytes
+
+fills string array with len radom bytes, peferably from the OS randomizer
+==================
+*/
+void Com_RandomBytes( byte *string, int len )
+{
+ int i;
+
+ if( Sys_RandomBytes( string, len ) )
+ return;
+
+ Com_Printf( "Com_RandomBytes: using weak randomization\n" );
+ srand( time( 0 ) );
+ for( i = 0; i < len; i++ )
+ string[i] = (unsigned char)( rand() % 255 );
+}
diff --git a/src/qcommon/cvar.c b/src/qcommon/cvar.c
new file mode 100644
index 0000000..91d3dfb
--- /dev/null
+++ b/src/qcommon/cvar.c
@@ -0,0 +1,947 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// cvar.c -- dynamic variable tracking
+
+#include "q_shared.h"
+#include "qcommon.h"
+
+cvar_t *cvar_vars;
+cvar_t *cvar_cheats;
+int cvar_modifiedFlags;
+
+#define MAX_CVARS 1024
+cvar_t cvar_indexes[MAX_CVARS];
+int cvar_numIndexes;
+
+#define FILE_HASH_SIZE 256
+static cvar_t* hashTable[FILE_HASH_SIZE];
+
+cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force);
+
+/*
+================
+return a hash value for the filename
+================
+*/
+static long generateHashValue( const char *fname ) {
+ int i;
+ long hash;
+ char letter;
+
+ hash = 0;
+ i = 0;
+ while (fname[i] != '\0') {
+ letter = tolower(fname[i]);
+ hash+=(long)(letter)*(i+119);
+ i++;
+ }
+ hash &= (FILE_HASH_SIZE-1);
+ return hash;
+}
+
+/*
+============
+Cvar_ValidateString
+============
+*/
+static qboolean Cvar_ValidateString( const char *s ) {
+ if ( !s ) {
+ return qfalse;
+ }
+ if ( strchr( s, '\\' ) ) {
+ return qfalse;
+ }
+ if ( strchr( s, '\"' ) ) {
+ return qfalse;
+ }
+ if ( strchr( s, ';' ) ) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+/*
+============
+Cvar_FindVar
+============
+*/
+static cvar_t *Cvar_FindVar( const char *var_name ) {
+ cvar_t *var;
+ long hash;
+
+ hash = generateHashValue(var_name);
+
+ for (var=hashTable[hash] ; var ; var=var->hashNext) {
+ if (!Q_stricmp(var_name, var->name)) {
+ return var;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+============
+Cvar_VariableValue
+============
+*/
+float Cvar_VariableValue( const char *var_name ) {
+ cvar_t *var;
+
+ var = Cvar_FindVar (var_name);
+ if (!var)
+ return 0;
+ return var->value;
+}
+
+
+/*
+============
+Cvar_VariableIntegerValue
+============
+*/
+int Cvar_VariableIntegerValue( const char *var_name ) {
+ cvar_t *var;
+
+ var = Cvar_FindVar (var_name);
+ if (!var)
+ return 0;
+ return var->integer;
+}
+
+
+/*
+============
+Cvar_VariableString
+============
+*/
+char *Cvar_VariableString( const char *var_name ) {
+ cvar_t *var;
+
+ var = Cvar_FindVar (var_name);
+ if (!var)
+ return "";
+ return var->string;
+}
+
+
+/*
+============
+Cvar_VariableStringBuffer
+============
+*/
+void Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) {
+ cvar_t *var;
+
+ var = Cvar_FindVar (var_name);
+ if (!var) {
+ *buffer = 0;
+ }
+ else {
+ Q_strncpyz( buffer, var->string, bufsize );
+ }
+}
+
+/*
+============
+Cvar_Flags
+============
+*/
+int Cvar_Flags(const char *var_name)
+{
+ cvar_t *var;
+
+ if(! (var = Cvar_FindVar(var_name)) )
+ return CVAR_NONEXISTENT;
+ else
+ return var->flags;
+}
+
+/*
+============
+Cvar_CommandCompletion
+============
+*/
+void Cvar_CommandCompletion( void(*callback)(const char *s) ) {
+ cvar_t *cvar;
+
+ for ( cvar = cvar_vars ; cvar ; cvar = cvar->next ) {
+ callback( cvar->name );
+ }
+}
+
+
+/*
+============
+Cvar_Get
+
+If the variable already exists, the value will not be set unless CVAR_ROM
+The flags will be or'ed in if the variable exists.
+============
+*/
+cvar_t *Cvar_Get( const char *var_name, const char *var_value, int flags ) {
+ cvar_t *var;
+ long hash;
+
+ if ( !var_name || ! var_value ) {
+ Com_Error( ERR_FATAL, "Cvar_Get: NULL parameter" );
+ }
+
+ if ( !Cvar_ValidateString( var_name ) ) {
+ Com_Printf("invalid cvar name string: %s\n", var_name );
+ var_name = "BADNAME";
+ }
+
+#if 0 // FIXME: values with backslash happen
+ if ( !Cvar_ValidateString( var_value ) ) {
+ Com_Printf("invalid cvar value string: %s\n", var_value );
+ var_value = "BADVALUE";
+ }
+#endif
+
+ var = Cvar_FindVar (var_name);
+ if ( var ) {
+ // if the C code is now specifying a variable that the user already
+ // set a value for, take the new value as the reset value
+ if ( ( var->flags & CVAR_USER_CREATED ) && !( flags & CVAR_USER_CREATED )
+ && var_value[0] ) {
+ var->flags &= ~CVAR_USER_CREATED;
+ Z_Free( var->resetString );
+ var->resetString = CopyString( var_value );
+
+ // ZOID--needs to be set so that cvars the game sets as
+ // SERVERINFO get sent to clients
+ cvar_modifiedFlags |= flags;
+ }
+
+ var->flags |= flags;
+ // only allow one non-empty reset string without a warning
+ if ( !var->resetString[0] ) {
+ // we don't have a reset string yet
+ Z_Free( var->resetString );
+ var->resetString = CopyString( var_value );
+ } else if ( var_value[0] && strcmp( var->resetString, var_value ) ) {
+ Com_DPrintf( "Warning: cvar \"%s\" given initial values: \"%s\" and \"%s\"\n",
+ var_name, var->resetString, var_value );
+ }
+ // if we have a latched string, take that value now
+ if ( var->latchedString ) {
+ char *s;
+
+ s = var->latchedString;
+ var->latchedString = NULL; // otherwise cvar_set2 would free it
+ Cvar_Set2( var_name, s, qtrue );
+ Z_Free( s );
+ }
+
+// use a CVAR_SET for rom sets, get won't override
+#if 0
+ // CVAR_ROM always overrides
+ if ( flags & CVAR_ROM ) {
+ Cvar_Set2( var_name, var_value, qtrue );
+ }
+#endif
+ return var;
+ }
+
+ //
+ // allocate a new cvar
+ //
+ if ( cvar_numIndexes >= MAX_CVARS ) {
+ Com_Error( ERR_FATAL, "MAX_CVARS" );
+ }
+ var = &cvar_indexes[cvar_numIndexes];
+ cvar_numIndexes++;
+ var->name = CopyString (var_name);
+ var->string = CopyString (var_value);
+ var->modified = qtrue;
+ var->modificationCount = 1;
+ var->value = atof (var->string);
+ var->integer = atoi(var->string);
+ var->resetString = CopyString( var_value );
+
+ // link the variable in
+ var->next = cvar_vars;
+ cvar_vars = var;
+
+ var->flags = flags;
+
+ hash = generateHashValue(var_name);
+ var->hashNext = hashTable[hash];
+ hashTable[hash] = var;
+
+ return var;
+}
+
+/*
+============
+Cvar_Set2
+============
+*/
+cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force ) {
+ cvar_t *var;
+
+// Com_DPrintf( "Cvar_Set2: %s %s\n", var_name, value );
+
+ if ( !Cvar_ValidateString( var_name ) ) {
+ Com_Printf("invalid cvar name string: %s\n", var_name );
+ var_name = "BADNAME";
+ }
+
+#if 0 // FIXME
+ if ( value && !Cvar_ValidateString( value ) ) {
+ Com_Printf("invalid cvar value string: %s\n", value );
+ var_value = "BADVALUE";
+ }
+#endif
+
+ var = Cvar_FindVar (var_name);
+ if (!var) {
+ if ( !value ) {
+ return NULL;
+ }
+ // create it
+ if ( !force ) {
+ return Cvar_Get( var_name, value, CVAR_USER_CREATED );
+ } else {
+ return Cvar_Get (var_name, value, 0);
+ }
+ }
+
+ if (!value ) {
+ value = var->resetString;
+ }
+
+ if((var->flags & CVAR_LATCH) && var->latchedString) {
+ if(!strcmp(value,var->latchedString))
+ return var;
+ }
+ else if (!strcmp(value,var->string)) {
+ return var;
+ }
+ // note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo)
+ cvar_modifiedFlags |= var->flags;
+
+ if (!force)
+ {
+ if (var->flags & CVAR_ROM)
+ {
+ Com_Printf ("%s is read only.\n", var_name);
+ return var;
+ }
+
+ if (var->flags & CVAR_INIT)
+ {
+ Com_Printf ("%s is write protected.\n", var_name);
+ return var;
+ }
+
+ if (var->flags & CVAR_LATCH)
+ {
+ if (var->latchedString)
+ {
+ if (strcmp(value, var->latchedString) == 0)
+ return var;
+ Z_Free (var->latchedString);
+ }
+ else
+ {
+ if (strcmp(value, var->string) == 0)
+ return var;
+ }
+
+ Com_Printf ("%s will be changed upon restarting.\n", var_name);
+ var->latchedString = CopyString(value);
+ var->modified = qtrue;
+ var->modificationCount++;
+ return var;
+ }
+
+ if ( (var->flags & CVAR_CHEAT) && !cvar_cheats->integer )
+ {
+ Com_Printf ("%s is cheat protected.\n", var_name);
+ return var;
+ }
+
+ }
+ else
+ {
+ if (var->latchedString)
+ {
+ Z_Free (var->latchedString);
+ var->latchedString = NULL;
+ }
+ }
+
+ if (!strcmp(value, var->string))
+ return var; // not changed
+
+ var->modified = qtrue;
+ var->modificationCount++;
+
+ Z_Free (var->string); // free the old value string
+
+ var->string = CopyString(value);
+ var->value = atof (var->string);
+ var->integer = atoi (var->string);
+
+ return var;
+}
+
+/*
+============
+Cvar_Set
+============
+*/
+void Cvar_Set( const char *var_name, const char *value) {
+ Cvar_Set2 (var_name, value, qtrue);
+}
+
+/*
+============
+Cvar_SetLatched
+============
+*/
+void Cvar_SetLatched( const char *var_name, const char *value) {
+ Cvar_Set2 (var_name, value, qfalse);
+}
+
+/*
+============
+Cvar_SetValue
+============
+*/
+void Cvar_SetValue( const char *var_name, float value) {
+ char val[32];
+
+ if ( value == (int)value ) {
+ Com_sprintf (val, sizeof(val), "%i",(int)value);
+ } else {
+ Com_sprintf (val, sizeof(val), "%f",value);
+ }
+ Cvar_Set (var_name, val);
+}
+
+
+/*
+============
+Cvar_Reset
+============
+*/
+void Cvar_Reset( const char *var_name ) {
+ Cvar_Set2( var_name, NULL, qfalse );
+}
+
+/*
+============
+Cvar_ForceReset
+============
+*/
+void Cvar_ForceReset(const char *var_name)
+{
+ Cvar_Set2(var_name, NULL, qtrue);
+}
+
+/*
+============
+Cvar_SetCheatState
+
+Any testing variables will be reset to the safe values
+============
+*/
+void Cvar_SetCheatState( void ) {
+ cvar_t *var;
+
+ // set all default vars to the safe value
+ for ( var = cvar_vars ; var ; var = var->next ) {
+ if ( var->flags & CVAR_CHEAT ) {
+ // the CVAR_LATCHED|CVAR_CHEAT vars might escape the reset here
+ // because of a different var->latchedString
+ if (var->latchedString)
+ {
+ Z_Free(var->latchedString);
+ var->latchedString = NULL;
+ }
+ if (strcmp(var->resetString,var->string)) {
+ Cvar_Set( var->name, var->resetString );
+ }
+ }
+ }
+}
+
+/*
+============
+Cvar_Command
+
+Handles variable inspection and changing from the console
+============
+*/
+qboolean Cvar_Command( void ) {
+ cvar_t *v;
+ char string[ TRUNCATE_LENGTH ];
+ char resetString[ TRUNCATE_LENGTH ];
+ char latchedString[ TRUNCATE_LENGTH ];
+
+ // check variables
+ v = Cvar_FindVar (Cmd_Argv(0));
+ if (!v) {
+ return qfalse;
+ }
+
+ // perform a variable print or set
+ if ( Cmd_Argc() == 1 ) {
+ Com_TruncateLongString( string, v->string );
+ Com_TruncateLongString( resetString, v->resetString );
+ Com_Printf ("\"%s\" is:\"%s" S_COLOR_WHITE "\" default:\"%s" S_COLOR_WHITE "\"\n",
+ v->name, string, resetString );
+ if ( v->latchedString ) {
+ Com_TruncateLongString( latchedString, v->latchedString );
+ Com_Printf( "latched: \"%s\"\n", latchedString );
+ }
+ return qtrue;
+ }
+
+ // set the value if forcing isn't required
+ Cvar_Set2 (v->name, Cmd_Argv(1), qfalse);
+ return qtrue;
+}
+
+
+/*
+============
+Cvar_Toggle_f
+
+Toggles a cvar for easy single key binding
+============
+*/
+void Cvar_Toggle_f( void ) {
+ int v;
+
+ if ( Cmd_Argc() != 2 ) {
+ Com_Printf ("usage: toggle <variable>\n");
+ return;
+ }
+
+ v = Cvar_VariableValue( Cmd_Argv( 1 ) );
+ v = !v;
+
+ Cvar_Set2 (Cmd_Argv(1), va("%i", v), qfalse);
+}
+
+/*
+============
+Cvar_Set_f
+
+Allows setting and defining of arbitrary cvars from console, even if they
+weren't declared in C code.
+============
+*/
+void Cvar_Set_f( void ) {
+ int i, c, l, len;
+ char combined[MAX_STRING_TOKENS];
+
+ c = Cmd_Argc();
+ if ( c < 3 ) {
+ Com_Printf ("usage: set <variable> <value>\n");
+ return;
+ }
+
+ combined[0] = 0;
+ l = 0;
+ for ( i = 2 ; i < c ; i++ ) {
+ len = strlen ( Cmd_Argv( i ) + 1 );
+ if ( l + len >= MAX_STRING_TOKENS - 2 ) {
+ break;
+ }
+ strcat( combined, Cmd_Argv( i ) );
+ if ( i != c-1 ) {
+ strcat( combined, " " );
+ }
+ l += len;
+ }
+ Cvar_Set2 (Cmd_Argv(1), combined, qfalse);
+}
+
+/*
+============
+Cvar_SetU_f
+
+As Cvar_Set, but also flags it as userinfo
+============
+*/
+void Cvar_SetU_f( void ) {
+ cvar_t *v;
+
+ if ( Cmd_Argc() != 3 ) {
+ Com_Printf ("usage: setu <variable> <value>\n");
+ return;
+ }
+ Cvar_Set_f();
+ v = Cvar_FindVar( Cmd_Argv( 1 ) );
+ if ( !v ) {
+ return;
+ }
+ v->flags |= CVAR_USERINFO;
+}
+
+/*
+============
+Cvar_SetS_f
+
+As Cvar_Set, but also flags it as userinfo
+============
+*/
+void Cvar_SetS_f( void ) {
+ cvar_t *v;
+
+ if ( Cmd_Argc() != 3 ) {
+ Com_Printf ("usage: sets <variable> <value>\n");
+ return;
+ }
+ Cvar_Set_f();
+ v = Cvar_FindVar( Cmd_Argv( 1 ) );
+ if ( !v ) {
+ return;
+ }
+ v->flags |= CVAR_SERVERINFO;
+}
+
+/*
+============
+Cvar_SetA_f
+
+As Cvar_Set, but also flags it as archived
+============
+*/
+void Cvar_SetA_f( void ) {
+ cvar_t *v;
+
+ if ( Cmd_Argc() != 3 ) {
+ Com_Printf ("usage: seta <variable> <value>\n");
+ return;
+ }
+ Cvar_Set_f();
+ v = Cvar_FindVar( Cmd_Argv( 1 ) );
+ if ( !v ) {
+ return;
+ }
+ v->flags |= CVAR_ARCHIVE;
+}
+
+/*
+============
+Cvar_Reset_f
+============
+*/
+void Cvar_Reset_f( void ) {
+ if ( Cmd_Argc() != 2 ) {
+ Com_Printf ("usage: reset <variable>\n");
+ return;
+ }
+ Cvar_Reset( Cmd_Argv( 1 ) );
+}
+
+/*
+============
+Cvar_WriteVariables
+
+Appends lines containing "set variable value" for all variables
+with the archive flag set to qtrue.
+============
+*/
+void Cvar_WriteVariables( fileHandle_t f ) {
+ cvar_t *var;
+ char buffer[1024];
+
+ for (var = cvar_vars ; var ; var = var->next) {
+ if( var->flags & CVAR_ARCHIVE ) {
+ // write the latched value, even if it hasn't taken effect yet
+ if ( var->latchedString ) {
+ if( strlen( var->name ) + strlen( var->latchedString ) + 10 > sizeof( buffer ) ) {
+ Com_Printf( S_COLOR_YELLOW "WARNING: value of variable "
+ "\"%s\" too long to write to file\n", var->name );
+ continue;
+ }
+ Com_sprintf (buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->latchedString);
+ } else {
+ if( strlen( var->name ) + strlen( var->string ) + 10 > sizeof( buffer ) ) {
+ Com_Printf( S_COLOR_YELLOW "WARNING: value of variable "
+ "\"%s\" too long to write to file\n", var->name );
+ continue;
+ }
+ Com_sprintf (buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->string);
+ }
+ FS_Write( buffer, strlen( buffer ), f );
+ }
+ }
+}
+
+/*
+============
+Cvar_List_f
+============
+*/
+void Cvar_List_f( void ) {
+ cvar_t *var;
+ int i;
+ char *match;
+
+ if ( Cmd_Argc() > 1 ) {
+ match = Cmd_Argv( 1 );
+ } else {
+ match = NULL;
+ }
+
+ i = 0;
+ for (var = cvar_vars ; var ; var = var->next, i++)
+ {
+ if (match && !Com_Filter(match, var->name, qfalse)) continue;
+
+ if (var->flags & CVAR_SERVERINFO) {
+ Com_Printf("S");
+ } else {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_USERINFO) {
+ Com_Printf("U");
+ } else {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_ROM) {
+ Com_Printf("R");
+ } else {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_INIT) {
+ Com_Printf("I");
+ } else {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_ARCHIVE) {
+ Com_Printf("A");
+ } else {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_LATCH) {
+ Com_Printf("L");
+ } else {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_CHEAT) {
+ Com_Printf("C");
+ } else {
+ Com_Printf(" ");
+ }
+
+ Com_Printf (" %s \"%s\"\n", var->name, var->string);
+ }
+
+ Com_Printf ("\n%i total cvars\n", i);
+ Com_Printf ("%i cvar indexes\n", cvar_numIndexes);
+}
+
+/*
+============
+Cvar_Restart_f
+
+Resets all cvars to their hardcoded values
+============
+*/
+void Cvar_Restart_f( void ) {
+ cvar_t *var;
+ cvar_t **prev;
+
+ prev = &cvar_vars;
+ while ( 1 ) {
+ var = *prev;
+ if ( !var ) {
+ break;
+ }
+
+ // don't mess with rom values, or some inter-module
+ // communication will get broken (com_cl_running, etc)
+ if ( var->flags & ( CVAR_ROM | CVAR_INIT | CVAR_NORESTART ) ) {
+ prev = &var->next;
+ continue;
+ }
+
+ // throw out any variables the user created
+ if ( var->flags & CVAR_USER_CREATED ) {
+ *prev = var->next;
+ if ( var->name ) {
+ Z_Free( var->name );
+ }
+ if ( var->string ) {
+ Z_Free( var->string );
+ }
+ if ( var->latchedString ) {
+ Z_Free( var->latchedString );
+ }
+ if ( var->resetString ) {
+ Z_Free( var->resetString );
+ }
+ // clear the var completely, since we
+ // can't remove the index from the list
+ Com_Memset( var, 0, sizeof( var ) );
+ continue;
+ }
+
+ Cvar_Set( var->name, var->resetString );
+
+ prev = &var->next;
+ }
+}
+
+
+
+/*
+=====================
+Cvar_InfoString
+=====================
+*/
+char *Cvar_InfoString( int bit ) {
+ static char info[MAX_INFO_STRING];
+ cvar_t *var;
+
+ info[0] = 0;
+
+ for (var = cvar_vars ; var ; var = var->next) {
+ if (var->flags & bit) {
+ Info_SetValueForKey (info, var->name, var->string);
+ }
+ }
+ return info;
+}
+
+/*
+=====================
+Cvar_InfoString_Big
+
+ handles large info strings ( CS_SYSTEMINFO )
+=====================
+*/
+char *Cvar_InfoString_Big( int bit ) {
+ static char info[BIG_INFO_STRING];
+ cvar_t *var;
+
+ info[0] = 0;
+
+ for (var = cvar_vars ; var ; var = var->next) {
+ if (var->flags & bit) {
+ Info_SetValueForKey_Big (info, var->name, var->string);
+ }
+ }
+ return info;
+}
+
+
+
+/*
+=====================
+Cvar_InfoStringBuffer
+=====================
+*/
+void Cvar_InfoStringBuffer( int bit, char* buff, int buffsize ) {
+ Q_strncpyz(buff,Cvar_InfoString(bit),buffsize);
+}
+
+/*
+=====================
+Cvar_Register
+
+basically a slightly modified Cvar_Get for the interpreted modules
+=====================
+*/
+void Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ) {
+ cvar_t *cv;
+
+ cv = Cvar_Get( varName, defaultValue, flags );
+ if ( !vmCvar ) {
+ return;
+ }
+ vmCvar->handle = cv - cvar_indexes;
+ vmCvar->modificationCount = -1;
+ Cvar_Update( vmCvar );
+}
+
+
+/*
+=====================
+Cvar_Register
+
+updates an interpreted modules' version of a cvar
+=====================
+*/
+void Cvar_Update( vmCvar_t *vmCvar ) {
+ cvar_t *cv = NULL; // bk001129
+ assert(vmCvar); // bk
+
+ if ( (unsigned)vmCvar->handle >= cvar_numIndexes ) {
+ Com_Error( ERR_DROP, "Cvar_Update: handle out of range" );
+ }
+
+ cv = cvar_indexes + vmCvar->handle;
+
+ if ( cv->modificationCount == vmCvar->modificationCount ) {
+ return;
+ }
+ if ( !cv->string ) {
+ return; // variable might have been cleared by a cvar_restart
+ }
+ vmCvar->modificationCount = cv->modificationCount;
+ // bk001129 - mismatches.
+ if ( strlen(cv->string)+1 > MAX_CVAR_VALUE_STRING )
+ Com_Error( ERR_DROP, "Cvar_Update: src %s length %zd exceeds MAX_CVAR_VALUE_STRING",
+ cv->string,
+ strlen(cv->string));
+ // bk001212 - Q_strncpyz guarantees zero padding and dest[MAX_CVAR_VALUE_STRING-1]==0
+ // bk001129 - paranoia. Never trust the destination string.
+ // bk001129 - beware, sizeof(char*) is always 4 (for cv->string).
+ // sizeof(vmCvar->string) always MAX_CVAR_VALUE_STRING
+ //Q_strncpyz( vmCvar->string, cv->string, sizeof( vmCvar->string ) ); // id
+ Q_strncpyz( vmCvar->string, cv->string, MAX_CVAR_VALUE_STRING );
+
+ vmCvar->value = cv->value;
+ vmCvar->integer = cv->integer;
+}
+
+
+/*
+============
+Cvar_Init
+
+Reads in all archived cvars
+============
+*/
+void Cvar_Init (void) {
+ cvar_cheats = Cvar_Get("sv_cheats", "1", CVAR_ROM | CVAR_SYSTEMINFO );
+
+ Cmd_AddCommand ("toggle", Cvar_Toggle_f);
+ Cmd_AddCommand ("set", Cvar_Set_f);
+ Cmd_AddCommand ("sets", Cvar_SetS_f);
+ Cmd_AddCommand ("setu", Cvar_SetU_f);
+ Cmd_AddCommand ("seta", Cvar_SetA_f);
+ Cmd_AddCommand ("reset", Cvar_Reset_f);
+ Cmd_AddCommand ("cvarlist", Cvar_List_f);
+ Cmd_AddCommand ("cvar_restart", Cvar_Restart_f);
+}
diff --git a/src/qcommon/files.c b/src/qcommon/files.c
new file mode 100644
index 0000000..cd36990
--- /dev/null
+++ b/src/qcommon/files.c
@@ -0,0 +1,3428 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+/*****************************************************************************
+ * name: files.c
+ *
+ * desc: handle based filesystem for Quake III Arena
+ *
+ * $Archive: /MissionPack/code/qcommon/files.c $
+ *
+ *****************************************************************************/
+
+
+#include "q_shared.h"
+#include "qcommon.h"
+#include "unzip.h"
+
+/*
+=============================================================================
+
+QUAKE3 FILESYSTEM
+
+All of Quake's data access is through a hierarchical file system, but the contents of
+the file system can be transparently merged from several sources.
+
+A "qpath" is a reference to game file data. MAX_ZPATH is 256 characters, which must include
+a terminating zero. "..", "\\", and ":" are explicitly illegal in qpaths to prevent any
+references outside the quake directory system.
+
+The "base path" is the path to the directory holding all the game directories and usually
+the executable. It defaults to ".", but can be overridden with a "+set fs_basepath c:\quake3"
+command line to allow code debugging in a different directory. Basepath cannot
+be modified at all after startup. Any files that are created (demos, screenshots,
+etc) will be created reletive to the base path, so base path should usually be writable.
+
+The "cd path" is the path to an alternate hierarchy that will be searched if a file
+is not located in the base path. A user can do a partial install that copies some
+data to a base path created on their hard drive and leave the rest on the cd. Files
+are never writen to the cd path. It defaults to a value set by the installer, like
+"e:\quake3", but it can be overridden with "+set fs_cdpath g:\quake3".
+
+If a user runs the game directly from a CD, the base path would be on the CD. This
+should still function correctly, but all file writes will fail (harmlessly).
+
+The "home path" is the path used for all write access. On win32 systems we have "base path"
+== "home path", but on *nix systems the base installation is usually readonly, and
+"home path" points to ~/.q3a or similar
+
+The user can also install custom mods and content in "home path", so it should be searched
+along with "home path" and "cd path" for game content.
+
+
+The "base game" is the directory under the paths where data comes from by default, and
+can be "base".
+
+The "current game" may be the same as the base game, or it may be the name of another
+directory under the paths that should be searched for files before looking in the base game.
+This is the basis for addons.
+
+Clients automatically set the game directory after receiving a gamestate from a server,
+so only servers need to worry about +set fs_game.
+
+No other directories outside of the base game and current game will ever be referenced by
+filesystem functions.
+
+To save disk space and speed loading, directory trees can be collapsed into zip files.
+The files use a ".pk3" extension to prevent users from unzipping them accidentally, but
+otherwise the are simply normal uncompressed zip files. A game directory can have multiple
+zip files of the form "pak0.pk3", "pak1.pk3", etc. Zip files are searched in decending order
+from the highest number to the lowest, and will always take precedence over the filesystem.
+This allows a pk3 distributed as a patch to override all existing data.
+
+Because we will have updated executables freely available online, there is no point to
+trying to restrict demo / oem versions of the game with code changes. Demo / oem versions
+should be exactly the same executables as release versions, but with different data that
+automatically restricts where game media can come from to prevent add-ons from working.
+
+After the paths are initialized, quake will look for the product.txt file. If not
+found and verified, the game will run in restricted mode. In restricted mode, only
+files contained in demoq3/pak0.pk3 will be available for loading, and only if the zip header is
+verified to not have been modified. A single exception is made for autogen.cfg. Files
+can still be written out in restricted mode, so screenshots and demos are allowed.
+Restricted mode can be tested by setting "+set fs_restrict 1" on the command line, even
+if there is a valid product.txt under the basepath or cdpath.
+
+If not running in restricted mode, and a file is not found in any local filesystem,
+an attempt will be made to download it and save it under the base path.
+
+If the "fs_copyfiles" cvar is set to 1, then every time a file is sourced from the cd
+path, it will be copied over to the base path. This is a development aid to help build
+test releases and to copy working sets over slow network links.
+
+File search order: when FS_FOpenFileRead gets called it will go through the fs_searchpaths
+structure and stop on the first successful hit. fs_searchpaths is built with successive
+calls to FS_AddGameDirectory
+
+Additionaly, we search in several subdirectories:
+current game is the current mode
+base game is a variable to allow mods based on other mods
+(such as base + missionpack content combination in a mod for instance)
+BASEGAME is the hardcoded base game ("base")
+
+e.g. the qpath "sound/newstuff/test.wav" would be searched for in the following places:
+
+home path + current game's zip files
+home path + current game's directory
+base path + current game's zip files
+base path + current game's directory
+cd path + current game's zip files
+cd path + current game's directory
+
+home path + base game's zip file
+home path + base game's directory
+base path + base game's zip file
+base path + base game's directory
+cd path + base game's zip file
+cd path + base game's directory
+
+home path + BASEGAME's zip file
+home path + BASEGAME's directory
+base path + BASEGAME's zip file
+base path + BASEGAME's directory
+cd path + BASEGAME's zip file
+cd path + BASEGAME's directory
+
+server download, to be written to home path + current game's directory
+
+
+The filesystem can be safely shutdown and reinitialized with different
+basedir / cddir / game combinations, but all other subsystems that rely on it
+(sound, video) must also be forced to restart.
+
+Because the same files are loaded by both the clip model (CM_) and renderer (TR_)
+subsystems, a simple single-file caching scheme is used. The CM_ subsystems will
+load the file with a request to cache. Only one file will be kept cached at a time,
+so any models that are going to be referenced by both subsystems should alternate
+between the CM_ load function and the ref load function.
+
+TODO: A qpath that starts with a leading slash will always refer to the base game, even if another
+game is currently active. This allows character models, skins, and sounds to be downloaded
+to a common directory no matter which game is active.
+
+How to prevent downloading zip files?
+Pass pk3 file names in systeminfo, and download before FS_Restart()?
+
+Aborting a download disconnects the client from the server.
+
+How to mark files as downloadable? Commercial add-ons won't be downloadable.
+
+Non-commercial downloads will want to download the entire zip file.
+the game would have to be reset to actually read the zip in
+
+Auto-update information
+
+Path separators
+
+Casing
+
+ separate server gamedir and client gamedir, so if the user starts
+ a local game after having connected to a network game, it won't stick
+ with the network game.
+
+ allow menu options for game selection?
+
+Read / write config to floppy option.
+
+Different version coexistance?
+
+When building a pak file, make sure a autogen.cfg isn't present in it,
+or configs will never get loaded from disk!
+
+ todo:
+
+ downloading (outside fs?)
+ game directory passing and restarting
+
+=============================================================================
+
+*/
+
+// every time a new demo pk3 file is built, this checksum must be updated.
+// the easiest way to get it is to just run the game and see what it spits out
+#define DEMO_PAK0_CHECKSUM 2985612116u
+static const unsigned pak_checksums[] = {
+ 1566731103u,
+ 298122907u,
+ 412165236u,
+ 2991495316u,
+ 1197932710u,
+ 4087071573u,
+ 3709064859u,
+ 908855077u,
+ 977125798u
+};
+
+// if this is defined, the executable positively won't work with any paks other
+// than the demo pak, even if productid is present. This is only used for our
+// last demo release to prevent the mac and linux users from using the demo
+// executable with the production windows pak before the mac/linux products
+// hit the shelves a little later
+// NOW defined in build files
+//#define PRE_RELEASE_TADEMO
+
+#define MAX_ZPATH 256
+#define MAX_SEARCH_PATHS 4096
+#define MAX_FILEHASH_SIZE 1024
+
+typedef struct fileInPack_s {
+ char *name; // name of the file
+ unsigned long pos; // file info position in zip
+ struct fileInPack_s* next; // next file in the hash
+} fileInPack_t;
+
+typedef struct {
+ char pakFilename[MAX_OSPATH]; // /tremulous/base/pak0.pk3
+ char pakBasename[MAX_OSPATH]; // pak0
+ char pakGamename[MAX_OSPATH]; // base
+ unzFile handle; // handle to zip file
+ int checksum; // regular checksum
+ int pure_checksum; // checksum for pure
+ int numfiles; // number of files in pk3
+ int referenced; // referenced file flags
+ int hashSize; // hash table size (power of 2)
+ fileInPack_t* *hashTable; // hash table
+ fileInPack_t* buildBuffer; // buffer with the filenames etc.
+} pack_t;
+
+typedef struct {
+ char path[MAX_OSPATH];
+ char gamedir[MAX_OSPATH]; // base
+} directory_t;
+
+typedef struct searchpath_s {
+ struct searchpath_s *next;
+
+ pack_t *pack; // only one of pack / dir will be non NULL
+ directory_t *dir;
+} searchpath_t;
+
+static char fs_gamedir[MAX_OSPATH]; // this will be a single file name with no separators
+static cvar_t *fs_debug;
+static cvar_t *fs_homepath;
+static cvar_t *fs_basepath;
+static cvar_t *fs_basegame;
+static cvar_t *fs_cdpath;
+static cvar_t *fs_copyfiles;
+static cvar_t *fs_gamedirvar;
+static cvar_t *fs_restrict;
+static searchpath_t *fs_searchpaths;
+static int fs_readCount; // total bytes read
+static int fs_loadCount; // total files read
+static int fs_loadStack; // total files in memory
+static int fs_packFiles; // total number of files in packs
+
+static int fs_fakeChkSum;
+static int fs_checksumFeed;
+
+typedef union qfile_gus {
+ FILE* o;
+ unzFile z;
+} qfile_gut;
+
+typedef struct qfile_us {
+ qfile_gut file;
+ qboolean unique;
+} qfile_ut;
+
+typedef struct {
+ qfile_ut handleFiles;
+ qboolean handleSync;
+ int baseOffset;
+ int fileSize;
+ int zipFilePos;
+ qboolean zipFile;
+ qboolean streamed;
+ char name[MAX_ZPATH];
+} fileHandleData_t;
+
+static fileHandleData_t fsh[MAX_FILE_HANDLES];
+
+// TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
+// wether we did a reorder on the current search path when joining the server
+static qboolean fs_reordered;
+
+// never load anything from pk3 files that are not present at the server when pure
+static int fs_numServerPaks;
+static int fs_serverPaks[MAX_SEARCH_PATHS]; // checksums
+static char *fs_serverPakNames[MAX_SEARCH_PATHS]; // pk3 names
+
+// only used for autodownload, to make sure the client has at least
+// all the pk3 files that are referenced at the server side
+static int fs_numServerReferencedPaks;
+static int fs_serverReferencedPaks[MAX_SEARCH_PATHS]; // checksums
+static char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS]; // pk3 names
+
+// last valid game folder used
+char lastValidBase[MAX_OSPATH];
+char lastValidGame[MAX_OSPATH];
+
+#ifdef FS_MISSING
+FILE* missingFiles = NULL;
+#endif
+
+/*
+==============
+FS_Initialized
+==============
+*/
+
+qboolean FS_Initialized( void ) {
+ return (fs_searchpaths != NULL);
+}
+
+/*
+=================
+FS_PakIsPure
+=================
+*/
+qboolean FS_PakIsPure( pack_t *pack ) {
+ int i;
+
+ if ( fs_numServerPaks ) {
+ for ( i = 0 ; i < fs_numServerPaks ; i++ ) {
+ // FIXME: also use hashed file names
+ // NOTE TTimo: a pk3 with same checksum but different name would be validated too
+ // I don't see this as allowing for any exploit, it would only happen if the client does manips of it's file names 'not a bug'
+ if ( pack->checksum == fs_serverPaks[i] ) {
+ return qtrue; // on the aproved list
+ }
+ }
+ return qfalse; // not on the pure server pak list
+ }
+ return qtrue;
+}
+
+
+/*
+=================
+FS_LoadStack
+return load stack
+=================
+*/
+int FS_LoadStack( void )
+{
+ return fs_loadStack;
+}
+
+/*
+================
+return a hash value for the filename
+================
+*/
+static long FS_HashFileName( const char *fname, int hashSize ) {
+ int i;
+ long hash;
+ char letter;
+
+ hash = 0;
+ i = 0;
+ while (fname[i] != '\0') {
+ letter = tolower(fname[i]);
+ if (letter =='.') break; // don't include extension
+ if (letter =='\\') letter = '/'; // damn path names
+ if (letter == PATH_SEP) letter = '/'; // damn path names
+ hash+=(long)(letter)*(i+119);
+ i++;
+ }
+ hash = (hash ^ (hash >> 10) ^ (hash >> 20));
+ hash &= (hashSize-1);
+ return hash;
+}
+
+static fileHandle_t FS_HandleForFile(void) {
+ int i;
+
+ for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) {
+ if ( fsh[i].handleFiles.file.o == NULL ) {
+ return i;
+ }
+ }
+ Com_Error( ERR_DROP, "FS_HandleForFile: none free" );
+ return 0;
+}
+
+static FILE *FS_FileForHandle( fileHandle_t f ) {
+ if ( f < 0 || f > MAX_FILE_HANDLES ) {
+ Com_Error( ERR_DROP, "FS_FileForHandle: out of reange" );
+ }
+ if (fsh[f].zipFile == qtrue) {
+ Com_Error( ERR_DROP, "FS_FileForHandle: can't get FILE on zip file" );
+ }
+ if ( ! fsh[f].handleFiles.file.o ) {
+ Com_Error( ERR_DROP, "FS_FileForHandle: NULL" );
+ }
+
+ return fsh[f].handleFiles.file.o;
+}
+
+void FS_ForceFlush( fileHandle_t f ) {
+ FILE *file;
+
+ file = FS_FileForHandle(f);
+ setvbuf( file, NULL, _IONBF, 0 );
+}
+
+/*
+================
+FS_filelength
+
+If this is called on a non-unique FILE (from a pak file),
+it will return the size of the pak file, not the expected
+size of the file.
+================
+*/
+int FS_filelength( fileHandle_t f ) {
+ int pos;
+ int end;
+ FILE* h;
+
+ h = FS_FileForHandle(f);
+ pos = ftell (h);
+ fseek (h, 0, SEEK_END);
+ end = ftell (h);
+ fseek (h, pos, SEEK_SET);
+
+ return end;
+}
+
+/*
+====================
+FS_ReplaceSeparators
+
+Fix things up differently for win/unix/mac
+====================
+*/
+static void FS_ReplaceSeparators( char *path ) {
+ char *s;
+
+ for ( s = path ; *s ; s++ ) {
+ if ( *s == '/' || *s == '\\' ) {
+ *s = PATH_SEP;
+ }
+ }
+}
+
+/*
+===================
+FS_BuildOSPath
+
+Qpath may have either forward or backwards slashes
+===================
+*/
+char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ) {
+ char temp[MAX_OSPATH];
+ static char ospath[2][MAX_OSPATH];
+ static int toggle;
+
+ toggle ^= 1; // flip-flop to allow two returns without clash
+
+ if( !game || !game[0] ) {
+ game = fs_gamedir;
+ }
+
+ Com_sprintf( temp, sizeof(temp), "/%s/%s", game, qpath );
+ FS_ReplaceSeparators( temp );
+ Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s", base, temp );
+
+ return ospath[toggle];
+}
+
+
+/*
+============
+FS_CreatePath
+
+Creates any directories needed to store the given filename
+============
+*/
+static qboolean FS_CreatePath (char *OSPath) {
+ char *ofs;
+
+ // make absolutely sure that it can't back up the path
+ // FIXME: is c: allowed???
+ if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) {
+ Com_Printf( "WARNING: refusing to create relative path \"%s\"\n", OSPath );
+ return qtrue;
+ }
+
+ for (ofs = OSPath+1 ; *ofs ; ofs++) {
+ if (*ofs == PATH_SEP) {
+ // create the directory
+ *ofs = 0;
+ Sys_Mkdir (OSPath);
+ *ofs = PATH_SEP;
+ }
+ }
+ return qfalse;
+}
+
+/*
+=================
+FS_CopyFile
+
+Copy a fully specified file from one place to another
+=================
+*/
+static void FS_CopyFile( char *fromOSPath, char *toOSPath ) {
+ FILE *f;
+ int len;
+ byte *buf;
+
+ Com_Printf( "copy %s to %s\n", fromOSPath, toOSPath );
+
+ if (strstr(fromOSPath, "journal.dat") || strstr(fromOSPath, "journaldata.dat")) {
+ Com_Printf( "Ignoring journal files\n");
+ return;
+ }
+
+ f = fopen( fromOSPath, "rb" );
+ if ( !f ) {
+ return;
+ }
+ fseek (f, 0, SEEK_END);
+ len = ftell (f);
+ fseek (f, 0, SEEK_SET);
+
+ // we are using direct malloc instead of Z_Malloc here, so it
+ // probably won't work on a mac... Its only for developers anyway...
+ buf = malloc( len );
+ if (fread( buf, 1, len, f ) != len)
+ Com_Error( ERR_FATAL, "Short read in FS_Copyfiles()\n" );
+ fclose( f );
+
+ if( FS_CreatePath( toOSPath ) ) {
+ return;
+ }
+
+ f = fopen( toOSPath, "wb" );
+ if ( !f ) {
+ return;
+ }
+ if (fwrite( buf, 1, len, f ) != len)
+ Com_Error( ERR_FATAL, "Short write in FS_Copyfiles()\n" );
+ fclose( f );
+ free( buf );
+}
+
+/*
+===========
+FS_Remove
+
+===========
+*/
+void FS_Remove( const char *osPath ) {
+ remove( osPath );
+}
+
+/*
+===========
+FS_HomeRemove
+
+===========
+*/
+void FS_HomeRemove( const char *homePath ) {
+ remove( FS_BuildOSPath( fs_homepath->string,
+ fs_gamedir, homePath ) );
+}
+
+/*
+================
+FS_FileExists
+
+Tests if the file exists in the current gamedir, this DOES NOT
+search the paths. This is to determine if opening a file to write
+(which always goes into the current gamedir) will cause any overwrites.
+NOTE TTimo: this goes with FS_FOpenFileWrite for opening the file afterwards
+================
+*/
+qboolean FS_FileExists( const char *file )
+{
+ FILE *f;
+ char *testpath;
+
+ testpath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, file );
+
+ f = fopen( testpath, "rb" );
+ if (f) {
+ fclose( f );
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+================
+FS_SV_FileExists
+
+Tests if the file exists
+================
+*/
+qboolean FS_SV_FileExists( const char *file )
+{
+ FILE *f;
+ char *testpath;
+
+ testpath = FS_BuildOSPath( fs_homepath->string, file, "");
+ testpath[strlen(testpath)-1] = '\0';
+
+ f = fopen( testpath, "rb" );
+ if (f) {
+ fclose( f );
+ return qtrue;
+ }
+ return qfalse;
+}
+
+
+/*
+===========
+FS_SV_FOpenFileWrite
+
+===========
+*/
+fileHandle_t FS_SV_FOpenFileWrite( const char *filename ) {
+ char *ospath;
+ fileHandle_t f;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ ospath = FS_BuildOSPath( fs_homepath->string, filename, "" );
+ ospath[strlen(ospath)-1] = '\0';
+
+ f = FS_HandleForFile();
+ fsh[f].zipFile = qfalse;
+
+ if ( fs_debug->integer ) {
+ Com_Printf( "FS_SV_FOpenFileWrite: %s\n", ospath );
+ }
+
+ if( FS_CreatePath( ospath ) ) {
+ return 0;
+ }
+
+ Com_DPrintf( "writing to: %s\n", ospath );
+ fsh[f].handleFiles.file.o = fopen( ospath, "wb" );
+
+ Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
+
+ fsh[f].handleSync = qfalse;
+ if (!fsh[f].handleFiles.file.o) {
+ f = 0;
+ }
+ return f;
+}
+
+/*
+===========
+FS_SV_FOpenFileRead
+search for a file somewhere below the home path, base path or cd path
+we search in that order, matching FS_SV_FOpenFileRead order
+===========
+*/
+int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ) {
+ char *ospath;
+ fileHandle_t f = 0;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ f = FS_HandleForFile();
+ fsh[f].zipFile = qfalse;
+
+ Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
+
+ // don't let sound stutter
+ S_ClearSoundBuffer();
+
+ // search homepath
+ ospath = FS_BuildOSPath( fs_homepath->string, filename, "" );
+ // remove trailing slash
+ ospath[strlen(ospath)-1] = '\0';
+
+ if ( fs_debug->integer ) {
+ Com_Printf( "FS_SV_FOpenFileRead (fs_homepath): %s\n", ospath );
+ }
+
+ fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
+ fsh[f].handleSync = qfalse;
+ if (!fsh[f].handleFiles.file.o)
+ {
+ // NOTE TTimo on non *nix systems, fs_homepath == fs_basepath, might want to avoid
+ if (Q_stricmp(fs_homepath->string,fs_basepath->string))
+ {
+ // search basepath
+ ospath = FS_BuildOSPath( fs_basepath->string, filename, "" );
+ ospath[strlen(ospath)-1] = '\0';
+
+ if ( fs_debug->integer )
+ {
+ Com_Printf( "FS_SV_FOpenFileRead (fs_basepath): %s\n", ospath );
+ }
+
+ fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
+ fsh[f].handleSync = qfalse;
+
+ if ( !fsh[f].handleFiles.file.o )
+ {
+ f = 0;
+ }
+ }
+ }
+
+ if (!fsh[f].handleFiles.file.o) {
+ // search cd path
+ ospath = FS_BuildOSPath( fs_cdpath->string, filename, "" );
+ ospath[strlen(ospath)-1] = '\0';
+
+ if (fs_debug->integer)
+ {
+ Com_Printf( "FS_SV_FOpenFileRead (fs_cdpath) : %s\n", ospath );
+ }
+
+ fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
+ fsh[f].handleSync = qfalse;
+
+ if( !fsh[f].handleFiles.file.o ) {
+ f = 0;
+ }
+ }
+
+ *fp = f;
+ if (f) {
+ return FS_filelength(f);
+ }
+ return 0;
+}
+
+
+/*
+===========
+FS_SV_Rename
+
+===========
+*/
+void FS_SV_Rename( const char *from, const char *to ) {
+ char *from_ospath, *to_ospath;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ // don't let sound stutter
+ S_ClearSoundBuffer();
+
+ from_ospath = FS_BuildOSPath( fs_homepath->string, from, "" );
+ to_ospath = FS_BuildOSPath( fs_homepath->string, to, "" );
+ from_ospath[strlen(from_ospath)-1] = '\0';
+ to_ospath[strlen(to_ospath)-1] = '\0';
+
+ if ( fs_debug->integer ) {
+ Com_Printf( "FS_SV_Rename: %s --> %s\n", from_ospath, to_ospath );
+ }
+
+ if (rename( from_ospath, to_ospath )) {
+ // Failed, try copying it and deleting the original
+ FS_CopyFile ( from_ospath, to_ospath );
+ FS_Remove ( from_ospath );
+ }
+}
+
+
+
+/*
+===========
+FS_Rename
+
+===========
+*/
+void FS_Rename( const char *from, const char *to ) {
+ char *from_ospath, *to_ospath;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ // don't let sound stutter
+ S_ClearSoundBuffer();
+
+ from_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, from );
+ to_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, to );
+
+ if ( fs_debug->integer ) {
+ Com_Printf( "FS_Rename: %s --> %s\n", from_ospath, to_ospath );
+ }
+
+ if (rename( from_ospath, to_ospath )) {
+ // Failed, try copying it and deleting the original
+ FS_CopyFile ( from_ospath, to_ospath );
+ FS_Remove ( from_ospath );
+ }
+}
+
+/*
+==============
+FS_FCloseFile
+
+If the FILE pointer is an open pak file, leave it open.
+
+For some reason, other dll's can't just cal fclose()
+on files returned by FS_FOpenFile...
+==============
+*/
+void FS_FCloseFile( fileHandle_t f ) {
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ if (fsh[f].streamed) {
+ Sys_EndStreamedFile(f);
+ }
+ if (fsh[f].zipFile == qtrue) {
+ unzCloseCurrentFile( fsh[f].handleFiles.file.z );
+ if ( fsh[f].handleFiles.unique ) {
+ unzClose( fsh[f].handleFiles.file.z );
+ }
+ Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) );
+ return;
+ }
+
+ // we didn't find it as a pak, so close it as a unique file
+ if (fsh[f].handleFiles.file.o) {
+ fclose (fsh[f].handleFiles.file.o);
+ }
+ Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) );
+}
+
+/*
+===========
+FS_FOpenFileWrite
+
+===========
+*/
+fileHandle_t FS_FOpenFileWrite( const char *filename ) {
+ char *ospath;
+ fileHandle_t f;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ f = FS_HandleForFile();
+ fsh[f].zipFile = qfalse;
+
+ ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename );
+
+ if ( fs_debug->integer ) {
+ Com_Printf( "FS_FOpenFileWrite: %s\n", ospath );
+ }
+
+ if( FS_CreatePath( ospath ) ) {
+ return 0;
+ }
+
+ // enabling the following line causes a recursive function call loop
+ // when running with +set logfile 1 +set developer 1
+ //Com_DPrintf( "writing to: %s\n", ospath );
+ fsh[f].handleFiles.file.o = fopen( ospath, "wb" );
+
+ Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
+
+ fsh[f].handleSync = qfalse;
+ if (!fsh[f].handleFiles.file.o) {
+ f = 0;
+ }
+ return f;
+}
+
+/*
+===========
+FS_FOpenFileAppend
+
+===========
+*/
+fileHandle_t FS_FOpenFileAppend( const char *filename ) {
+ char *ospath;
+ fileHandle_t f;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ f = FS_HandleForFile();
+ fsh[f].zipFile = qfalse;
+
+ Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
+
+ // don't let sound stutter
+ S_ClearSoundBuffer();
+
+ ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename );
+
+ if ( fs_debug->integer ) {
+ Com_Printf( "FS_FOpenFileAppend: %s\n", ospath );
+ }
+
+ if( FS_CreatePath( ospath ) ) {
+ return 0;
+ }
+
+ fsh[f].handleFiles.file.o = fopen( ospath, "ab" );
+ fsh[f].handleSync = qfalse;
+ if (!fsh[f].handleFiles.file.o) {
+ f = 0;
+ }
+ return f;
+}
+
+/*
+===========
+FS_FilenameCompare
+
+Ignore case and seprator char distinctions
+===========
+*/
+qboolean FS_FilenameCompare( const char *s1, const char *s2 ) {
+ int c1, c2;
+
+ do {
+ c1 = *s1++;
+ c2 = *s2++;
+
+ if (c1 >= 'a' && c1 <= 'z') {
+ c1 -= ('a' - 'A');
+ }
+ if (c2 >= 'a' && c2 <= 'z') {
+ c2 -= ('a' - 'A');
+ }
+
+ if ( c1 == '\\' || c1 == ':' ) {
+ c1 = '/';
+ }
+ if ( c2 == '\\' || c2 == ':' ) {
+ c2 = '/';
+ }
+
+ if (c1 != c2) {
+ return qtrue; // strings not equal
+ }
+ } while (c1);
+
+ return qfalse; // strings are equal
+}
+
+/*
+===========
+FS_ShiftedStrStr
+===========
+*/
+char *FS_ShiftedStrStr(const char *string, const char *substring, int shift) {
+ char buf[MAX_STRING_TOKENS];
+ int i;
+
+ for (i = 0; substring[i]; i++) {
+ buf[i] = substring[i] + shift;
+ }
+ buf[i] = '\0';
+ return strstr(string, buf);
+}
+
+/*
+===========
+FS_FOpenFileRead
+
+Finds the file in the search path.
+Returns filesize and an open FILE pointer.
+Used for streaming data out of either a
+separate file or a ZIP file.
+===========
+*/
+extern qboolean com_fullyInitialized;
+
+int FS_FOpenFileRead( const char *filename, fileHandle_t *file, qboolean uniqueFILE ) {
+ searchpath_t *search;
+ char *netpath;
+ pack_t *pak;
+ fileInPack_t *pakFile;
+ directory_t *dir;
+ long hash;
+ unz_s *zfi;
+ FILE *temp;
+ int l;
+ char demoExt[16];
+
+ hash = 0;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ if ( file == NULL ) {
+ // just wants to see if file is there
+ for ( search = fs_searchpaths ; search ; search = search->next ) {
+ //
+ if ( search->pack ) {
+ hash = FS_HashFileName(filename, search->pack->hashSize);
+ }
+ // is the element a pak file?
+ if ( search->pack && search->pack->hashTable[hash] ) {
+ // look through all the pak file elements
+ pak = search->pack;
+ pakFile = pak->hashTable[hash];
+ do {
+ // case and separator insensitive comparisons
+ if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
+ // found it!
+ return qtrue;
+ }
+ pakFile = pakFile->next;
+ } while(pakFile != NULL);
+ } else if ( search->dir ) {
+ dir = search->dir;
+
+ netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename );
+ temp = fopen (netpath, "rb");
+ if ( !temp ) {
+ continue;
+ }
+ fclose(temp);
+ return qtrue;
+ }
+ }
+ return qfalse;
+ }
+
+ if ( !filename ) {
+ Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" );
+ }
+
+ Com_sprintf (demoExt, sizeof(demoExt), ".dm_%d",PROTOCOL_VERSION );
+ // qpaths are not supposed to have a leading slash
+ if ( filename[0] == '/' || filename[0] == '\\' ) {
+ filename++;
+ }
+
+ // make absolutely sure that it can't back up the path.
+ // The searchpaths do guarantee that something will always
+ // be prepended, so we don't need to worry about "c:" or "//limbo"
+ if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) {
+ *file = 0;
+ return -1;
+ }
+
+ //
+ // search through the path, one element at a time
+ //
+
+ *file = FS_HandleForFile();
+ fsh[*file].handleFiles.unique = uniqueFILE;
+
+ for ( search = fs_searchpaths ; search ; search = search->next ) {
+ //
+ if ( search->pack ) {
+ hash = FS_HashFileName(filename, search->pack->hashSize);
+ }
+ // is the element a pak file?
+ if ( search->pack && search->pack->hashTable[hash] ) {
+ // disregard if it doesn't match one of the allowed pure pak files
+ if ( !FS_PakIsPure(search->pack) ) {
+ continue;
+ }
+
+ // look through all the pak file elements
+ pak = search->pack;
+ pakFile = pak->hashTable[hash];
+ do {
+ // case and separator insensitive comparisons
+ if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
+ // found it!
+
+ // mark the pak as having been referenced and mark specifics on cgame and ui
+ // shaders, txt, arena files by themselves do not count as a reference as
+ // these are loaded from all pk3s
+ // from every pk3 file..
+ l = strlen( filename );
+ if ( !(pak->referenced & FS_GENERAL_REF)) {
+ if ( Q_stricmp(filename + l - 7, ".shader") != 0 &&
+ Q_stricmp(filename + l - 4, ".txt") != 0 &&
+ Q_stricmp(filename + l - 4, ".cfg") != 0 &&
+ Q_stricmp(filename + l - 7, ".config") != 0 &&
+ strstr(filename, "levelshots") == NULL &&
+ Q_stricmp(filename + l - 4, ".bot") != 0 &&
+ Q_stricmp(filename + l - 6, ".arena") != 0 &&
+ Q_stricmp(filename + l - 5, ".menu") != 0) {
+ pak->referenced |= FS_GENERAL_REF;
+ }
+ }
+
+ // game.qvm - 13
+ // ZT`X!di`
+ if (!(pak->referenced & FS_QAGAME_REF) && FS_ShiftedStrStr(filename, "ZT`X!di`", 13)) {
+ pak->referenced |= FS_QAGAME_REF;
+ }
+ // cgame.qvm - 7
+ // \`Zf^'jof
+ if (!(pak->referenced & FS_CGAME_REF) && FS_ShiftedStrStr(filename , "\\`Zf^'jof", 7)) {
+ pak->referenced |= FS_CGAME_REF;
+ }
+ // ui.qvm - 5
+ // pd)lqh
+ if (!(pak->referenced & FS_UI_REF) && FS_ShiftedStrStr(filename , "pd)lqh", 5)) {
+ pak->referenced |= FS_UI_REF;
+ }
+
+ if ( uniqueFILE ) {
+ // open a new file on the pakfile
+ fsh[*file].handleFiles.file.z = unzReOpen (pak->pakFilename, pak->handle);
+ if (fsh[*file].handleFiles.file.z == NULL) {
+ Com_Error (ERR_FATAL, "Couldn't reopen %s", pak->pakFilename);
+ }
+ } else {
+ fsh[*file].handleFiles.file.z = pak->handle;
+ }
+ Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) );
+ fsh[*file].zipFile = qtrue;
+ zfi = (unz_s *)fsh[*file].handleFiles.file.z;
+ // in case the file was new
+ temp = zfi->file;
+ // set the file position in the zip file (also sets the current file info)
+ unzSetCurrentFileInfoPosition(pak->handle, pakFile->pos);
+ // copy the file info into the unzip structure
+ Com_Memcpy( zfi, pak->handle, sizeof(unz_s) );
+ // we copy this back into the structure
+ zfi->file = temp;
+ // open the file in the zip
+ unzOpenCurrentFile( fsh[*file].handleFiles.file.z );
+ fsh[*file].zipFilePos = pakFile->pos;
+
+ if ( fs_debug->integer ) {
+ Com_Printf( "FS_FOpenFileRead: %s (found in '%s')\n",
+ filename, pak->pakFilename );
+ }
+ return zfi->cur_file_info.uncompressed_size;
+ }
+ pakFile = pakFile->next;
+ } while(pakFile != NULL);
+ } else if ( search->dir ) {
+ // check a file in the directory tree
+
+ // if we are running restricted, the only files we
+ // will allow to come from the directory are .cfg files
+ l = strlen( filename );
+ // FIXME TTimo I'm not sure about the fs_numServerPaks test
+ // if you are using FS_ReadFile to find out if a file exists,
+ // this test can make the search fail although the file is in the directory
+ // I had the problem on https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=8
+ // turned out I used FS_FileExists instead
+ if ( fs_restrict->integer || fs_numServerPaks ) {
+
+ if ( Q_stricmp( filename + l - 4, ".cfg" ) // for config files
+ && Q_stricmp( filename + l - 5, ".menu" ) // menu files
+ && Q_stricmp( filename + l - 5, ".game" ) // menu files
+ && Q_stricmp( filename + l - strlen(demoExt), demoExt ) // menu files
+ && Q_stricmp( filename + l - 4, ".dat" ) ) { // for journal files
+ continue;
+ }
+ }
+
+ dir = search->dir;
+
+ netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename );
+ fsh[*file].handleFiles.file.o = fopen (netpath, "rb");
+ if ( !fsh[*file].handleFiles.file.o ) {
+ continue;
+ }
+
+ if ( Q_stricmp( filename + l - 4, ".cfg" ) // for config files
+ && Q_stricmp( filename + l - 5, ".menu" ) // menu files
+ && Q_stricmp( filename + l - 5, ".game" ) // menu files
+ && Q_stricmp( filename + l - strlen(demoExt), demoExt ) // menu files
+ && Q_stricmp( filename + l - 4, ".dat" ) ) { // for journal files
+ fs_fakeChkSum = random();
+ }
+
+ Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) );
+ fsh[*file].zipFile = qfalse;
+ if ( fs_debug->integer ) {
+ Com_Printf( "FS_FOpenFileRead: %s (found in '%s/%s')\n", filename,
+ dir->path, dir->gamedir );
+ }
+
+ // if we are getting it from the cdpath, optionally copy it
+ // to the basepath
+ if ( fs_copyfiles->integer && !Q_stricmp( dir->path, fs_cdpath->string ) ) {
+ char *copypath;
+
+ copypath = FS_BuildOSPath( fs_basepath->string, dir->gamedir, filename );
+ FS_CopyFile( netpath, copypath );
+ }
+
+ return FS_filelength (*file);
+ }
+ }
+
+ Com_DPrintf ("Can't find %s\n", filename);
+#ifdef FS_MISSING
+ if (missingFiles) {
+ fprintf(missingFiles, "%s\n", filename);
+ }
+#endif
+ *file = 0;
+ return -1;
+}
+
+
+/*
+=================
+FS_Read
+
+Properly handles partial reads
+=================
+*/
+int FS_Read2( void *buffer, int len, fileHandle_t f ) {
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ if ( !f ) {
+ return 0;
+ }
+ if (fsh[f].streamed) {
+ int r;
+ fsh[f].streamed = qfalse;
+ r = Sys_StreamedRead( buffer, len, 1, f);
+ fsh[f].streamed = qtrue;
+ return r;
+ } else {
+ return FS_Read( buffer, len, f);
+ }
+}
+
+int FS_Read( void *buffer, int len, fileHandle_t f ) {
+ int block, remaining;
+ int read;
+ byte *buf;
+ int tries;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ if ( !f ) {
+ return 0;
+ }
+
+ buf = (byte *)buffer;
+ fs_readCount += len;
+
+ if (fsh[f].zipFile == qfalse) {
+ remaining = len;
+ tries = 0;
+ while (remaining) {
+ block = remaining;
+ read = fread (buf, 1, block, fsh[f].handleFiles.file.o);
+ if (read == 0) {
+ // we might have been trying to read from a CD, which
+ // sometimes returns a 0 read on windows
+ if (!tries) {
+ tries = 1;
+ } else {
+ return len-remaining; //Com_Error (ERR_FATAL, "FS_Read: 0 bytes read");
+ }
+ }
+
+ if (read == -1) {
+ Com_Error (ERR_FATAL, "FS_Read: -1 bytes read");
+ }
+
+ remaining -= read;
+ buf += read;
+ }
+ return len;
+ } else {
+ return unzReadCurrentFile(fsh[f].handleFiles.file.z, buffer, len);
+ }
+}
+
+/*
+=================
+FS_Write
+
+Properly handles partial writes
+=================
+*/
+int FS_Write( const void *buffer, int len, fileHandle_t h ) {
+ int block, remaining;
+ int written;
+ byte *buf;
+ int tries;
+ FILE *f;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ if ( !h ) {
+ return 0;
+ }
+
+ f = FS_FileForHandle(h);
+ buf = (byte *)buffer;
+
+ remaining = len;
+ tries = 0;
+ while (remaining) {
+ block = remaining;
+ written = fwrite (buf, 1, block, f);
+ if (written == 0) {
+ if (!tries) {
+ tries = 1;
+ } else {
+ Com_Printf( "FS_Write: 0 bytes written\n" );
+ return 0;
+ }
+ }
+
+ if (written == -1) {
+ Com_Printf( "FS_Write: -1 bytes written\n" );
+ return 0;
+ }
+
+ remaining -= written;
+ buf += written;
+ }
+ if ( fsh[h].handleSync ) {
+ fflush( f );
+ }
+ return len;
+}
+
+void QDECL FS_Printf( fileHandle_t h, const char *fmt, ... ) {
+ va_list argptr;
+ char msg[MAXPRINTMSG];
+
+ va_start (argptr,fmt);
+ Q_vsnprintf (msg, sizeof(msg), fmt, argptr);
+ va_end (argptr);
+
+ FS_Write(msg, strlen(msg), h);
+}
+
+#define PK3_SEEK_BUFFER_SIZE 65536
+
+/*
+=================
+FS_Seek
+
+=================
+*/
+int FS_Seek( fileHandle_t f, long offset, int origin ) {
+ int _origin;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ return -1;
+ }
+
+ if (fsh[f].streamed) {
+ fsh[f].streamed = qfalse;
+ Sys_StreamSeek( f, offset, origin );
+ fsh[f].streamed = qtrue;
+ }
+
+ if (fsh[f].zipFile == qtrue) {
+ //FIXME: this is incomplete and really, really
+ //crappy (but better than what was here before)
+ byte buffer[PK3_SEEK_BUFFER_SIZE];
+ int remainder = offset;
+
+ if( offset < 0 || origin == FS_SEEK_END ) {
+ Com_Error( ERR_FATAL, "Negative offsets and FS_SEEK_END not implemented "
+ "for FS_Seek on pk3 file contents\n" );
+ return -1;
+ }
+
+ switch( origin ) {
+ case FS_SEEK_SET:
+ unzSetCurrentFileInfoPosition(fsh[f].handleFiles.file.z, fsh[f].zipFilePos);
+ unzOpenCurrentFile(fsh[f].handleFiles.file.z);
+ //fallthrough
+
+ case FS_SEEK_CUR:
+ while( remainder > PK3_SEEK_BUFFER_SIZE ) {
+ FS_Read( buffer, PK3_SEEK_BUFFER_SIZE, f );
+ remainder -= PK3_SEEK_BUFFER_SIZE;
+ }
+ FS_Read( buffer, remainder, f );
+ return offset;
+ break;
+
+ default:
+ Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" );
+ return -1;
+ break;
+ }
+ } else {
+ FILE *file;
+ file = FS_FileForHandle(f);
+ switch( origin ) {
+ case FS_SEEK_CUR:
+ _origin = SEEK_CUR;
+ break;
+ case FS_SEEK_END:
+ _origin = SEEK_END;
+ break;
+ case FS_SEEK_SET:
+ _origin = SEEK_SET;
+ break;
+ default:
+ _origin = SEEK_CUR;
+ Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" );
+ break;
+ }
+
+ return fseek( file, offset, _origin );
+ }
+}
+
+
+/*
+======================================================================================
+
+CONVENIENCE FUNCTIONS FOR ENTIRE FILES
+
+======================================================================================
+*/
+
+int FS_FileIsInPAK(const char *filename, int *pChecksum ) {
+ searchpath_t *search;
+ pack_t *pak;
+ fileInPack_t *pakFile;
+ long hash = 0;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ if ( !filename ) {
+ Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" );
+ }
+
+ // qpaths are not supposed to have a leading slash
+ if ( filename[0] == '/' || filename[0] == '\\' ) {
+ filename++;
+ }
+
+ // make absolutely sure that it can't back up the path.
+ // The searchpaths do guarantee that something will always
+ // be prepended, so we don't need to worry about "c:" or "//limbo"
+ if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) {
+ return -1;
+ }
+
+ //
+ // search through the path, one element at a time
+ //
+
+ for ( search = fs_searchpaths ; search ; search = search->next ) {
+ //
+ if (search->pack) {
+ hash = FS_HashFileName(filename, search->pack->hashSize);
+ }
+ // is the element a pak file?
+ if ( search->pack && search->pack->hashTable[hash] ) {
+ // disregard if it doesn't match one of the allowed pure pak files
+ if ( !FS_PakIsPure(search->pack) ) {
+ continue;
+ }
+
+ // look through all the pak file elements
+ pak = search->pack;
+ pakFile = pak->hashTable[hash];
+ do {
+ // case and separator insensitive comparisons
+ if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
+ if (pChecksum) {
+ *pChecksum = pak->pure_checksum;
+ }
+ return 1;
+ }
+ pakFile = pakFile->next;
+ } while(pakFile != NULL);
+ }
+ }
+ return -1;
+}
+
+/*
+============
+FS_ReadFile
+
+Filename are relative to the quake search path
+a null buffer will just return the file length without loading
+============
+*/
+int FS_ReadFile( const char *qpath, void **buffer ) {
+ fileHandle_t h;
+ byte* buf;
+ qboolean isConfig;
+ int len;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ if ( !qpath || !qpath[0] ) {
+ Com_Error( ERR_FATAL, "FS_ReadFile with empty name\n" );
+ }
+
+ buf = NULL; // quiet compiler warning
+
+ // if this is a .cfg file and we are playing back a journal, read
+ // it from the journal file
+ if ( strstr( qpath, ".cfg" ) ) {
+ isConfig = qtrue;
+ if ( com_journal && com_journal->integer == 2 ) {
+ int r;
+
+ Com_DPrintf( "Loading %s from journal file.\n", qpath );
+ r = FS_Read( &len, sizeof( len ), com_journalDataFile );
+ if ( r != sizeof( len ) ) {
+ if (buffer != NULL) *buffer = NULL;
+ return -1;
+ }
+ // if the file didn't exist when the journal was created
+ if (!len) {
+ if (buffer == NULL) {
+ return 1; // hack for old journal files
+ }
+ *buffer = NULL;
+ return -1;
+ }
+ if (buffer == NULL) {
+ return len;
+ }
+
+ buf = Hunk_AllocateTempMemory(len+1);
+ *buffer = buf;
+
+ r = FS_Read( buf, len, com_journalDataFile );
+ if ( r != len ) {
+ Com_Error( ERR_FATAL, "Read from journalDataFile failed" );
+ }
+
+ fs_loadCount++;
+ fs_loadStack++;
+
+ // guarantee that it will have a trailing 0 for string operations
+ buf[len] = 0;
+
+ return len;
+ }
+ } else {
+ isConfig = qfalse;
+ }
+
+ // look for it in the filesystem or pack files
+ len = FS_FOpenFileRead( qpath, &h, qfalse );
+ if ( h == 0 ) {
+ if ( buffer ) {
+ *buffer = NULL;
+ }
+ // if we are journalling and it is a config file, write a zero to the journal file
+ if ( isConfig && com_journal && com_journal->integer == 1 ) {
+ Com_DPrintf( "Writing zero for %s to journal file.\n", qpath );
+ len = 0;
+ FS_Write( &len, sizeof( len ), com_journalDataFile );
+ FS_Flush( com_journalDataFile );
+ }
+ return -1;
+ }
+
+ if ( !buffer ) {
+ if ( isConfig && com_journal && com_journal->integer == 1 ) {
+ Com_DPrintf( "Writing len for %s to journal file.\n", qpath );
+ FS_Write( &len, sizeof( len ), com_journalDataFile );
+ FS_Flush( com_journalDataFile );
+ }
+ FS_FCloseFile( h);
+ return len;
+ }
+
+ fs_loadCount++;
+ fs_loadStack++;
+
+ buf = Hunk_AllocateTempMemory(len+1);
+ *buffer = buf;
+
+ FS_Read (buf, len, h);
+
+ // guarantee that it will have a trailing 0 for string operations
+ buf[len] = 0;
+ FS_FCloseFile( h );
+
+ // if we are journalling and it is a config file, write it to the journal file
+ if ( isConfig && com_journal && com_journal->integer == 1 ) {
+ Com_DPrintf( "Writing %s to journal file.\n", qpath );
+ FS_Write( &len, sizeof( len ), com_journalDataFile );
+ FS_Write( buf, len, com_journalDataFile );
+ FS_Flush( com_journalDataFile );
+ }
+ return len;
+}
+
+/*
+=============
+FS_FreeFile
+=============
+*/
+void FS_FreeFile( void *buffer ) {
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+ if ( !buffer ) {
+ Com_Error( ERR_FATAL, "FS_FreeFile( NULL )" );
+ }
+ fs_loadStack--;
+
+ Hunk_FreeTempMemory( buffer );
+
+ // if all of our temp files are free, clear all of our space
+ if ( fs_loadStack == 0 ) {
+ Hunk_ClearTempMemory();
+ }
+}
+
+/*
+============
+FS_WriteFile
+
+Filename are reletive to the quake search path
+============
+*/
+void FS_WriteFile( const char *qpath, const void *buffer, int size ) {
+ fileHandle_t f;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ if ( !qpath || !buffer ) {
+ Com_Error( ERR_FATAL, "FS_WriteFile: NULL parameter" );
+ }
+
+ f = FS_FOpenFileWrite( qpath );
+ if ( !f ) {
+ Com_Printf( "Failed to open %s\n", qpath );
+ return;
+ }
+
+ FS_Write( buffer, size, f );
+
+ FS_FCloseFile( f );
+}
+
+
+
+/*
+==========================================================================
+
+ZIP FILE LOADING
+
+==========================================================================
+*/
+
+/*
+=================
+FS_LoadZipFile
+
+Creates a new pak_t in the search chain for the contents
+of a zip file.
+=================
+*/
+static pack_t *FS_LoadZipFile( char *zipfile, const char *basename )
+{
+ fileInPack_t *buildBuffer;
+ pack_t *pack;
+ unzFile uf;
+ int err;
+ unz_global_info gi;
+ char filename_inzip[MAX_ZPATH];
+ unz_file_info file_info;
+ int i, len;
+ long hash;
+ int fs_numHeaderLongs;
+ int *fs_headerLongs;
+ char *namePtr;
+
+ fs_numHeaderLongs = 0;
+
+ uf = unzOpen(zipfile);
+ err = unzGetGlobalInfo (uf,&gi);
+
+ if (err != UNZ_OK)
+ return NULL;
+
+ fs_packFiles += gi.number_entry;
+
+ len = 0;
+ unzGoToFirstFile(uf);
+ for (i = 0; i < gi.number_entry; i++)
+ {
+ err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
+ if (err != UNZ_OK) {
+ break;
+ }
+ len += strlen(filename_inzip) + 1;
+ unzGoToNextFile(uf);
+ }
+
+ buildBuffer = Z_Malloc( (gi.number_entry * sizeof( fileInPack_t )) + len );
+ namePtr = ((char *) buildBuffer) + gi.number_entry * sizeof( fileInPack_t );
+ fs_headerLongs = Z_Malloc( ( gi.number_entry + 1 ) * sizeof(int) );
+ fs_headerLongs[ fs_numHeaderLongs++ ] = LittleLong( fs_checksumFeed );
+
+ // get the hash table size from the number of files in the zip
+ // because lots of custom pk3 files have less than 32 or 64 files
+ for (i = 1; i <= MAX_FILEHASH_SIZE; i <<= 1) {
+ if (i > gi.number_entry) {
+ break;
+ }
+ }
+
+ pack = Z_Malloc( sizeof( pack_t ) + i * sizeof(fileInPack_t *) );
+ pack->hashSize = i;
+ pack->hashTable = (fileInPack_t **) (((char *) pack) + sizeof( pack_t ));
+ for(i = 0; i < pack->hashSize; i++) {
+ pack->hashTable[i] = NULL;
+ }
+
+ Q_strncpyz( pack->pakFilename, zipfile, sizeof( pack->pakFilename ) );
+ Q_strncpyz( pack->pakBasename, basename, sizeof( pack->pakBasename ) );
+
+ // strip .pk3 if needed
+ if ( strlen( pack->pakBasename ) > 4 && !Q_stricmp( pack->pakBasename + strlen( pack->pakBasename ) - 4, ".pk3" ) ) {
+ pack->pakBasename[strlen( pack->pakBasename ) - 4] = 0;
+ }
+
+ pack->handle = uf;
+ pack->numfiles = gi.number_entry;
+ unzGoToFirstFile(uf);
+
+ for (i = 0; i < gi.number_entry; i++)
+ {
+ err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
+ if (err != UNZ_OK) {
+ break;
+ }
+ if (file_info.uncompressed_size > 0) {
+ fs_headerLongs[fs_numHeaderLongs++] = LittleLong(file_info.crc);
+ }
+ Q_strlwr( filename_inzip );
+ hash = FS_HashFileName(filename_inzip, pack->hashSize);
+ buildBuffer[i].name = namePtr;
+ strcpy( buildBuffer[i].name, filename_inzip );
+ namePtr += strlen(filename_inzip) + 1;
+ // store the file position in the zip
+ unzGetCurrentFileInfoPosition(uf, &buildBuffer[i].pos);
+ //
+ buildBuffer[i].next = pack->hashTable[hash];
+ pack->hashTable[hash] = &buildBuffer[i];
+ unzGoToNextFile(uf);
+ }
+
+ pack->checksum = Com_BlockChecksum( &fs_headerLongs[ 1 ], 4 * ( fs_numHeaderLongs - 1 ) );
+ pack->pure_checksum = Com_BlockChecksum( fs_headerLongs, 4 * fs_numHeaderLongs );
+ pack->checksum = LittleLong( pack->checksum );
+ pack->pure_checksum = LittleLong( pack->pure_checksum );
+
+ Z_Free(fs_headerLongs);
+
+ pack->buildBuffer = buildBuffer;
+ return pack;
+}
+
+/*
+=================================================================================
+
+DIRECTORY SCANNING FUNCTIONS
+
+=================================================================================
+*/
+
+#define MAX_FOUND_FILES 0x1000
+
+static int FS_ReturnPath( const char *zname, char *zpath, int *depth ) {
+ int len, at, newdep;
+
+ newdep = 0;
+ zpath[0] = 0;
+ len = 0;
+ at = 0;
+
+ while(zname[at] != 0)
+ {
+ if (zname[at]=='/' || zname[at]=='\\') {
+ len = at;
+ newdep++;
+ }
+ at++;
+ }
+ strcpy(zpath, zname);
+ zpath[len] = 0;
+ *depth = newdep;
+
+ return len;
+}
+
+/*
+==================
+FS_AddFileToList
+==================
+*/
+static int FS_AddFileToList( char *name, char *list[MAX_FOUND_FILES], int nfiles ) {
+ int i;
+
+ if ( nfiles == MAX_FOUND_FILES - 1 ) {
+ return nfiles;
+ }
+ for ( i = 0 ; i < nfiles ; i++ ) {
+ if ( !Q_stricmp( name, list[i] ) ) {
+ return nfiles; // allready in list
+ }
+ }
+ list[nfiles] = CopyString( name );
+ nfiles++;
+
+ return nfiles;
+}
+
+/*
+===============
+FS_ListFilteredFiles
+
+Returns a uniqued list of files that match the given criteria
+from all search paths
+===============
+*/
+char **FS_ListFilteredFiles( const char *path, const char *extension, char *filter, int *numfiles ) {
+ int nfiles;
+ char **listCopy;
+ char *list[MAX_FOUND_FILES];
+ searchpath_t *search;
+ int i;
+ int pathLength;
+ int extensionLength;
+ int length, pathDepth, temp;
+ pack_t *pak;
+ fileInPack_t *buildBuffer;
+ char zpath[MAX_ZPATH];
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ if ( !path ) {
+ *numfiles = 0;
+ return NULL;
+ }
+ if ( !extension ) {
+ extension = "";
+ }
+
+ pathLength = strlen( path );
+ if ( path[pathLength-1] == '\\' || path[pathLength-1] == '/' ) {
+ pathLength--;
+ }
+ extensionLength = strlen( extension );
+ nfiles = 0;
+ FS_ReturnPath(path, zpath, &pathDepth);
+
+ //
+ // search through the path, one element at a time, adding to list
+ //
+ for (search = fs_searchpaths ; search ; search = search->next) {
+ // is the element a pak file?
+ if (search->pack) {
+
+ //ZOID: If we are pure, don't search for files on paks that
+ // aren't on the pure list
+ if ( !FS_PakIsPure(search->pack) ) {
+ continue;
+ }
+
+ // look through all the pak file elements
+ pak = search->pack;
+ buildBuffer = pak->buildBuffer;
+ for (i = 0; i < pak->numfiles; i++) {
+ char *name;
+ int zpathLen, depth;
+
+ // check for directory match
+ name = buildBuffer[i].name;
+ //
+ if (filter) {
+ // case insensitive
+ if (!Com_FilterPath( filter, name, qfalse ))
+ continue;
+ // unique the match
+ nfiles = FS_AddFileToList( name, list, nfiles );
+ }
+ else {
+
+ zpathLen = FS_ReturnPath(name, zpath, &depth);
+
+ if ( (depth-pathDepth)>2 || pathLength > zpathLen || Q_stricmpn( name, path, pathLength ) ) {
+ continue;
+ }
+
+ // check for extension match
+ length = strlen( name );
+ if ( length < extensionLength ) {
+ continue;
+ }
+
+ if ( Q_stricmp( name + length - extensionLength, extension ) ) {
+ continue;
+ }
+ // unique the match
+
+ temp = pathLength;
+ if (pathLength) {
+ temp++; // include the '/'
+ }
+ nfiles = FS_AddFileToList( name + temp, list, nfiles );
+ }
+ }
+ } else if (search->dir) { // scan for files in the filesystem
+ char *netpath;
+ int numSysFiles;
+ char **sysFiles;
+ char *name;
+
+ // don't scan directories for files if we are pure or restricted
+ if ( fs_restrict->integer || fs_numServerPaks ) {
+ continue;
+ } else {
+ netpath = FS_BuildOSPath( search->dir->path, search->dir->gamedir, path );
+ sysFiles = Sys_ListFiles( netpath, extension, filter, &numSysFiles, qfalse );
+ for ( i = 0 ; i < numSysFiles ; i++ ) {
+ // unique the match
+ name = sysFiles[i];
+ nfiles = FS_AddFileToList( name, list, nfiles );
+ }
+ Sys_FreeFileList( sysFiles );
+ }
+ }
+ }
+
+ // return a copy of the list
+ *numfiles = nfiles;
+
+ if ( !nfiles ) {
+ return NULL;
+ }
+
+ listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) );
+ for ( i = 0 ; i < nfiles ; i++ ) {
+ listCopy[i] = list[i];
+ }
+ listCopy[i] = NULL;
+
+ return listCopy;
+}
+
+/*
+=================
+FS_ListFiles
+=================
+*/
+char **FS_ListFiles( const char *path, const char *extension, int *numfiles ) {
+ return FS_ListFilteredFiles( path, extension, NULL, numfiles );
+}
+
+/*
+=================
+FS_FreeFileList
+=================
+*/
+void FS_FreeFileList( char **list ) {
+ int i;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ if ( !list ) {
+ return;
+ }
+
+ for ( i = 0 ; list[i] ; i++ ) {
+ Z_Free( list[i] );
+ }
+
+ Z_Free( list );
+}
+
+
+/*
+================
+FS_GetFileList
+================
+*/
+int FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) {
+ int nFiles, i, nTotal, nLen;
+ char **pFiles = NULL;
+
+ *listbuf = 0;
+ nFiles = 0;
+ nTotal = 0;
+
+ if (Q_stricmp(path, "$modlist") == 0) {
+ return FS_GetModList(listbuf, bufsize);
+ }
+
+ pFiles = FS_ListFiles(path, extension, &nFiles);
+
+ for (i =0; i < nFiles; i++) {
+ nLen = strlen(pFiles[i]) + 1;
+ if (nTotal + nLen + 1 < bufsize) {
+ strcpy(listbuf, pFiles[i]);
+ listbuf += nLen;
+ nTotal += nLen;
+ }
+ else {
+ nFiles = i;
+ break;
+ }
+ }
+
+ FS_FreeFileList(pFiles);
+
+ return nFiles;
+}
+
+/*
+=======================
+Sys_ConcatenateFileLists
+
+mkv: Naive implementation. Concatenates three lists into a
+ new list, and frees the old lists from the heap.
+bk001129 - from cvs1.17 (mkv)
+
+FIXME TTimo those two should move to common.c next to Sys_ListFiles
+=======================
+ */
+static unsigned int Sys_CountFileList(char **list)
+{
+ int i = 0;
+
+ if (list)
+ {
+ while (*list)
+ {
+ list++;
+ i++;
+ }
+ }
+ return i;
+}
+
+static char** Sys_ConcatenateFileLists( char **list0, char **list1, char **list2 )
+{
+ int totalLength = 0;
+ char** cat = NULL, **dst, **src;
+
+ totalLength += Sys_CountFileList(list0);
+ totalLength += Sys_CountFileList(list1);
+ totalLength += Sys_CountFileList(list2);
+
+ /* Create new list. */
+ dst = cat = Z_Malloc( ( totalLength + 1 ) * sizeof( char* ) );
+
+ /* Copy over lists. */
+ if (list0)
+ {
+ for (src = list0; *src; src++, dst++)
+ *dst = *src;
+ }
+ if (list1)
+ {
+ for (src = list1; *src; src++, dst++)
+ *dst = *src;
+ }
+ if (list2)
+ {
+ for (src = list2; *src; src++, dst++)
+ *dst = *src;
+ }
+
+ // Terminate the list
+ *dst = NULL;
+
+ // Free our old lists.
+ // NOTE: not freeing their content, it's been merged in dst and still being used
+ if (list0) Z_Free( list0 );
+ if (list1) Z_Free( list1 );
+ if (list2) Z_Free( list2 );
+
+ return cat;
+}
+
+/*
+================
+FS_GetModList
+
+Returns a list of mod directory names
+A mod directory is a peer to base with a pk3 in it
+The directories are searched in base path, cd path and home path
+================
+*/
+int FS_GetModList( char *listbuf, int bufsize ) {
+ int nMods, i, j, nTotal, nLen, nPaks, nPotential, nDescLen;
+ char **pFiles = NULL;
+ char **pPaks = NULL;
+ char *name, *path;
+ char descPath[MAX_OSPATH];
+ fileHandle_t descHandle;
+
+ int dummy;
+ char **pFiles0 = NULL;
+ char **pFiles1 = NULL;
+ char **pFiles2 = NULL;
+ qboolean bDrop = qfalse;
+
+ *listbuf = 0;
+ nMods = nPotential = nTotal = 0;
+
+ pFiles0 = Sys_ListFiles( fs_homepath->string, NULL, NULL, &dummy, qtrue );
+ pFiles1 = Sys_ListFiles( fs_basepath->string, NULL, NULL, &dummy, qtrue );
+ pFiles2 = Sys_ListFiles( fs_cdpath->string, NULL, NULL, &dummy, qtrue );
+ // we searched for mods in the three paths
+ // it is likely that we have duplicate names now, which we will cleanup below
+ pFiles = Sys_ConcatenateFileLists( pFiles0, pFiles1, pFiles2 );
+ nPotential = Sys_CountFileList(pFiles);
+
+ for ( i = 0 ; i < nPotential ; i++ ) {
+ name = pFiles[i];
+ // NOTE: cleaner would involve more changes
+ // ignore duplicate mod directories
+ if (i!=0) {
+ bDrop = qfalse;
+ for(j=0; j<i; j++)
+ {
+ if (Q_stricmp(pFiles[j],name)==0) {
+ // this one can be dropped
+ bDrop = qtrue;
+ break;
+ }
+ }
+ }
+ if (bDrop) {
+ continue;
+ }
+ // we drop BASEGAME "." and ".."
+ if (Q_stricmp(name, BASEGAME) && Q_stricmpn(name, ".", 1)) {
+ // now we need to find some .pk3 files to validate the mod
+ // NOTE TTimo: (actually I'm not sure why .. what if it's a mod under developement with no .pk3?)
+ // we didn't keep the information when we merged the directory names, as to what OS Path it was found under
+ // so it could be in base path, cd path or home path
+ // we will try each three of them here (yes, it's a bit messy)
+ path = FS_BuildOSPath( fs_basepath->string, name, "" );
+ nPaks = 0;
+ pPaks = Sys_ListFiles(path, ".pk3", NULL, &nPaks, qfalse);
+ Sys_FreeFileList( pPaks ); // we only use Sys_ListFiles to check wether .pk3 files are present
+
+ /* Try on cd path */
+ if( nPaks <= 0 ) {
+ path = FS_BuildOSPath( fs_cdpath->string, name, "" );
+ nPaks = 0;
+ pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse );
+ Sys_FreeFileList( pPaks );
+ }
+
+ /* try on home path */
+ if ( nPaks <= 0 )
+ {
+ path = FS_BuildOSPath( fs_homepath->string, name, "" );
+ nPaks = 0;
+ pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse );
+ Sys_FreeFileList( pPaks );
+ }
+
+ if (nPaks > 0) {
+ nLen = strlen(name) + 1;
+ // nLen is the length of the mod path
+ // we need to see if there is a description available
+ descPath[0] = '\0';
+ strcpy(descPath, name);
+ strcat(descPath, "/description.txt");
+ nDescLen = FS_SV_FOpenFileRead( descPath, &descHandle );
+ if ( nDescLen > 0 && descHandle) {
+ FILE *file;
+ file = FS_FileForHandle(descHandle);
+ Com_Memset( descPath, 0, sizeof( descPath ) );
+ nDescLen = fread(descPath, 1, 48, file);
+ if (nDescLen >= 0) {
+ descPath[nDescLen] = '\0';
+ }
+ FS_FCloseFile(descHandle);
+ } else {
+ strcpy(descPath, name);
+ }
+ nDescLen = strlen(descPath) + 1;
+
+ if (nTotal + nLen + 1 + nDescLen + 1 < bufsize) {
+ strcpy(listbuf, name);
+ listbuf += nLen;
+ strcpy(listbuf, descPath);
+ listbuf += nDescLen;
+ nTotal += nLen + nDescLen;
+ nMods++;
+ }
+ else {
+ break;
+ }
+ }
+ }
+ }
+ Sys_FreeFileList( pFiles );
+
+ return nMods;
+}
+
+
+
+
+//============================================================================
+
+/*
+================
+FS_Dir_f
+================
+*/
+void FS_Dir_f( void ) {
+ char *path;
+ char *extension;
+ char **dirnames;
+ int ndirs;
+ int i;
+
+ if ( Cmd_Argc() < 2 || Cmd_Argc() > 3 ) {
+ Com_Printf( "usage: dir <directory> [extension]\n" );
+ return;
+ }
+
+ if ( Cmd_Argc() == 2 ) {
+ path = Cmd_Argv( 1 );
+ extension = "";
+ } else {
+ path = Cmd_Argv( 1 );
+ extension = Cmd_Argv( 2 );
+ }
+
+ Com_Printf( "Directory of %s %s\n", path, extension );
+ Com_Printf( "---------------\n" );
+
+ dirnames = FS_ListFiles( path, extension, &ndirs );
+
+ for ( i = 0; i < ndirs; i++ ) {
+ Com_Printf( "%s\n", dirnames[i] );
+ }
+ FS_FreeFileList( dirnames );
+}
+
+/*
+===========
+FS_ConvertPath
+===========
+*/
+void FS_ConvertPath( char *s ) {
+ while (*s) {
+ if ( *s == '\\' || *s == ':' ) {
+ *s = '/';
+ }
+ s++;
+ }
+}
+
+/*
+===========
+FS_PathCmp
+
+Ignore case and seprator char distinctions
+===========
+*/
+int FS_PathCmp( const char *s1, const char *s2 ) {
+ int c1, c2;
+
+ do {
+ c1 = *s1++;
+ c2 = *s2++;
+
+ if (c1 >= 'a' && c1 <= 'z') {
+ c1 -= ('a' - 'A');
+ }
+ if (c2 >= 'a' && c2 <= 'z') {
+ c2 -= ('a' - 'A');
+ }
+
+ if ( c1 == '\\' || c1 == ':' ) {
+ c1 = '/';
+ }
+ if ( c2 == '\\' || c2 == ':' ) {
+ c2 = '/';
+ }
+
+ if (c1 < c2) {
+ return -1; // strings not equal
+ }
+ if (c1 > c2) {
+ return 1;
+ }
+ } while (c1);
+
+ return 0; // strings are equal
+}
+
+/*
+================
+FS_SortFileList
+================
+*/
+void FS_SortFileList(char **filelist, int numfiles) {
+ int i, j, k, numsortedfiles;
+ char **sortedlist;
+
+ sortedlist = Z_Malloc( ( numfiles + 1 ) * sizeof( *sortedlist ) );
+ sortedlist[0] = NULL;
+ numsortedfiles = 0;
+ for (i = 0; i < numfiles; i++) {
+ for (j = 0; j < numsortedfiles; j++) {
+ if (FS_PathCmp(filelist[i], sortedlist[j]) < 0) {
+ break;
+ }
+ }
+ for (k = numsortedfiles; k > j; k--) {
+ sortedlist[k] = sortedlist[k-1];
+ }
+ sortedlist[j] = filelist[i];
+ numsortedfiles++;
+ }
+ Com_Memcpy(filelist, sortedlist, numfiles * sizeof( *filelist ) );
+ Z_Free(sortedlist);
+}
+
+/*
+================
+FS_NewDir_f
+================
+*/
+void FS_NewDir_f( void ) {
+ char *filter;
+ char **dirnames;
+ int ndirs;
+ int i;
+
+ if ( Cmd_Argc() < 2 ) {
+ Com_Printf( "usage: fdir <filter>\n" );
+ Com_Printf( "example: fdir *q3dm*.bsp\n");
+ return;
+ }
+
+ filter = Cmd_Argv( 1 );
+
+ Com_Printf( "---------------\n" );
+
+ dirnames = FS_ListFilteredFiles( "", "", filter, &ndirs );
+
+ FS_SortFileList(dirnames, ndirs);
+
+ for ( i = 0; i < ndirs; i++ ) {
+ FS_ConvertPath(dirnames[i]);
+ Com_Printf( "%s\n", dirnames[i] );
+ }
+ Com_Printf( "%d files listed\n", ndirs );
+ FS_FreeFileList( dirnames );
+}
+
+/*
+============
+FS_Path_f
+
+============
+*/
+void FS_Path_f( void ) {
+ searchpath_t *s;
+ int i;
+
+ Com_Printf ("Current search path:\n");
+ for (s = fs_searchpaths; s; s = s->next) {
+ if (s->pack) {
+ Com_Printf ("%s (%i files)\n", s->pack->pakFilename, s->pack->numfiles);
+ if ( fs_numServerPaks ) {
+ if ( !FS_PakIsPure(s->pack) ) {
+ Com_Printf( " not on the pure list\n" );
+ } else {
+ Com_Printf( " on the pure list\n" );
+ }
+ }
+ } else {
+ Com_Printf ("%s/%s\n", s->dir->path, s->dir->gamedir );
+ }
+ }
+
+
+ Com_Printf( "\n" );
+ for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) {
+ if ( fsh[i].handleFiles.file.o ) {
+ Com_Printf( "handle %i: %s\n", i, fsh[i].name );
+ }
+ }
+}
+
+/*
+============
+FS_TouchFile_f
+
+The only purpose of this function is to allow game script files to copy
+arbitrary files furing an "fs_copyfiles 1" run.
+============
+*/
+void FS_TouchFile_f( void ) {
+ fileHandle_t f;
+
+ if ( Cmd_Argc() != 2 ) {
+ Com_Printf( "Usage: touchFile <file>\n" );
+ return;
+ }
+
+ FS_FOpenFileRead( Cmd_Argv( 1 ), &f, qfalse );
+ if ( f ) {
+ FS_FCloseFile( f );
+ }
+}
+
+//===========================================================================
+
+
+static int QDECL paksort( const void *a, const void *b ) {
+ char *aa, *bb;
+
+ aa = *(char **)a;
+ bb = *(char **)b;
+
+ return FS_PathCmp( aa, bb );
+}
+
+/*
+================
+FS_AddGameDirectory
+
+Sets fs_gamedir, adds the directory to the head of the path,
+then loads the zip headers
+================
+*/
+static void FS_AddGameDirectory( const char *path, const char *dir ) {
+ searchpath_t *sp;
+ int i;
+ searchpath_t *search;
+ pack_t *pak;
+ char *pakfile;
+ int numfiles;
+ char **pakfiles;
+
+ // this fixes the case where fs_basepath is the same as fs_cdpath
+ // which happens on full installs
+ for ( sp = fs_searchpaths ; sp ; sp = sp->next ) {
+ if ( sp->dir && !Q_stricmp(sp->dir->path, path) && !Q_stricmp(sp->dir->gamedir, dir)) {
+ return; // we've already got this one
+ }
+ }
+
+ Q_strncpyz( fs_gamedir, dir, sizeof( fs_gamedir ) );
+
+ //
+ // add the directory to the search path
+ //
+ search = Z_Malloc (sizeof(searchpath_t));
+ search->dir = Z_Malloc( sizeof( *search->dir ) );
+
+ Q_strncpyz( search->dir->path, path, sizeof( search->dir->path ) );
+ Q_strncpyz( search->dir->gamedir, dir, sizeof( search->dir->gamedir ) );
+ search->next = fs_searchpaths;
+ fs_searchpaths = search;
+
+ // find all pak files in this directory
+ pakfile = FS_BuildOSPath( path, dir, "" );
+ pakfile[ strlen(pakfile) - 1 ] = 0; // strip the trailing slash
+
+ pakfiles = Sys_ListFiles( pakfile, ".pk3", NULL, &numfiles, qfalse );
+
+ qsort( pakfiles, numfiles, sizeof(char*), paksort );
+
+ for ( i = 0 ; i < numfiles ; i++ ) {
+ pakfile = FS_BuildOSPath( path, dir, pakfiles[i] );
+ if ( ( pak = FS_LoadZipFile( pakfile, pakfiles[i] ) ) == 0 )
+ continue;
+ // store the game name for downloading
+ strcpy(pak->pakGamename, dir);
+
+ search = Z_Malloc (sizeof(searchpath_t));
+ search->pack = pak;
+ search->next = fs_searchpaths;
+ fs_searchpaths = search;
+ }
+
+ // done
+ Sys_FreeFileList( pakfiles );
+}
+
+/*
+================
+FS_idPak
+================
+*/
+qboolean FS_idPak( char *pak, char *base ) {
+ int i;
+
+ for (i = 0; i < NUM_ID_PAKS; i++) {
+ if ( !FS_FilenameCompare(pak, va("%s/pak%d", base, i)) ) {
+ break;
+ }
+ }
+ if (i < NUM_ID_PAKS) {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+================
+FS_idPak
+
+Check whether the string contains stuff like "../" to prevent directory traversal bugs
+and return qtrue if it does.
+================
+*/
+
+qboolean FS_CheckDirTraversal(const char *checkdir)
+{
+ if(strstr(checkdir, "../") || strstr(checkdir, "..\\"))
+ return qtrue;
+
+ return qfalse;
+}
+
+/*
+================
+FS_ComparePaks
+
+----------------
+dlstring == qtrue
+
+Returns a list of pak files that we should download from the server. They all get stored
+in the current gamedir and an FS_Restart will be fired up after we download them all.
+
+The string is the format:
+
+@remotename@localname [repeat]
+
+static int fs_numServerReferencedPaks;
+static int fs_serverReferencedPaks[MAX_SEARCH_PATHS];
+static char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS];
+
+----------------
+dlstring == qfalse
+
+we are not interested in a download string format, we want something human-readable
+(this is used for diagnostics while connecting to a pure server)
+
+================
+*/
+qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) {
+ searchpath_t *sp;
+ qboolean havepak, badchecksum;
+ char *origpos = neededpaks;
+ int i;
+
+ if (!fs_numServerReferencedPaks)
+ return qfalse; // Server didn't send any pack information along
+
+ *neededpaks = 0;
+
+ for ( i = 0 ; i < fs_numServerReferencedPaks ; i++ )
+ {
+ // Ok, see if we have this pak file
+ badchecksum = qfalse;
+ havepak = qfalse;
+
+ // never autodownload any of the tremulous paks
+ if ( FS_idPak(fs_serverReferencedPakNames[i], BASEGAME) ) {
+ continue;
+ }
+
+ // Make sure the server cannot make us write to non-quake3 directories.
+ if(FS_CheckDirTraversal(fs_serverReferencedPakNames[i]))
+ {
+ Com_Printf("WARNING: Invalid download name %s\n", fs_serverReferencedPakNames[i]);
+ continue;
+ }
+
+ for ( sp = fs_searchpaths ; sp ; sp = sp->next ) {
+ if ( sp->pack && sp->pack->checksum == fs_serverReferencedPaks[i] ) {
+ havepak = qtrue; // This is it!
+ break;
+ }
+ }
+
+ if ( !havepak && fs_serverReferencedPakNames[i] && *fs_serverReferencedPakNames[i] ) {
+ // Don't got it
+
+ if (dlstring)
+ {
+ // We need this to make sure we won't hit the end of the buffer or the server could
+ // overwrite non-pk3 files on clients by writing so much crap into neededpaks that
+ // Q_strcat cuts off the .pk3 extension.
+
+ origpos += strlen(origpos);
+
+ // Remote name
+ Q_strcat( neededpaks, len, "@");
+ Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
+ Q_strcat( neededpaks, len, ".pk3" );
+
+ // Local name
+ Q_strcat( neededpaks, len, "@");
+ // Do we have one with the same name?
+ if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) )
+ {
+ char st[MAX_ZPATH];
+ // We already have one called this, we need to download it to another name
+ // Make something up with the checksum in it
+ Com_sprintf( st, sizeof( st ), "%s.%08x.pk3", fs_serverReferencedPakNames[i], fs_serverReferencedPaks[i] );
+ Q_strcat( neededpaks, len, st );
+ } else
+ {
+ Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
+ Q_strcat( neededpaks, len, ".pk3" );
+ }
+
+ // Find out whether it might have overflowed the buffer and don't add this file to the
+ // list if that is the case.
+ if(strlen(origpos) + (origpos - neededpaks) >= len - 1)
+ {
+ *origpos = '\0';
+ break;
+ }
+ }
+ else
+ {
+ Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
+ Q_strcat( neededpaks, len, ".pk3" );
+ // Do we have one with the same name?
+ if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) )
+ {
+ Q_strcat( neededpaks, len, " (local file exists with wrong checksum)");
+ }
+ Q_strcat( neededpaks, len, "\n");
+ }
+ }
+ }
+
+ if ( *neededpaks ) {
+ return qtrue;
+ }
+
+ return qfalse; // We have them all
+}
+
+/*
+================
+FS_Shutdown
+
+Frees all resources.
+================
+*/
+void FS_Shutdown( qboolean closemfp ) {
+ searchpath_t *p, *next;
+ int i;
+
+ for(i = 0; i < MAX_FILE_HANDLES; i++) {
+ if (fsh[i].fileSize) {
+ FS_FCloseFile(i);
+ }
+ }
+
+ // free everything
+ for ( p = fs_searchpaths ; p ; p = next ) {
+ next = p->next;
+
+ if ( p->pack ) {
+ unzClose(p->pack->handle);
+ Z_Free( p->pack->buildBuffer );
+ Z_Free( p->pack );
+ }
+ if ( p->dir ) {
+ Z_Free( p->dir );
+ }
+ Z_Free( p );
+ }
+
+ // any FS_ calls will now be an error until reinitialized
+ fs_searchpaths = NULL;
+
+ Cmd_RemoveCommand( "path" );
+ Cmd_RemoveCommand( "dir" );
+ Cmd_RemoveCommand( "fdir" );
+ Cmd_RemoveCommand( "touchFile" );
+
+#ifdef FS_MISSING
+ if (closemfp) {
+ fclose(missingFiles);
+ }
+#endif
+}
+
+/*
+================
+FS_ReorderPurePaks
+NOTE TTimo: the reordering that happens here is not reflected in the cvars (\cvarlist *pak*)
+ this can lead to misleading situations, see https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
+================
+*/
+static void FS_ReorderPurePaks( void )
+{
+ searchpath_t *s;
+ int i;
+ searchpath_t **p_insert_index, // for linked list reordering
+ **p_previous; // when doing the scan
+
+ // only relevant when connected to pure server
+ if ( !fs_numServerPaks )
+ return;
+
+ fs_reordered = qfalse;
+
+ p_insert_index = &fs_searchpaths; // we insert in order at the beginning of the list
+ for ( i = 0 ; i < fs_numServerPaks ; i++ ) {
+ p_previous = p_insert_index; // track the pointer-to-current-item
+ for (s = *p_insert_index; s; s = s->next) {
+ // the part of the list before p_insert_index has been sorted already
+ if (s->pack && fs_serverPaks[i] == s->pack->checksum) {
+ fs_reordered = qtrue;
+ // move this element to the insert list
+ *p_previous = s->next;
+ s->next = *p_insert_index;
+ *p_insert_index = s;
+ // increment insert list
+ p_insert_index = &s->next;
+ break; // iterate to next server pack
+ }
+ p_previous = &s->next;
+ }
+ }
+}
+
+/*
+================
+FS_Startup
+================
+*/
+static void FS_Startup( const char *gameName ) {
+ const char *homePath;
+
+ Com_Printf( "----- FS_Startup -----\n" );
+
+ fs_debug = Cvar_Get( "fs_debug", "0", 0 );
+ fs_copyfiles = Cvar_Get( "fs_copyfiles", "0", CVAR_INIT );
+ fs_cdpath = Cvar_Get ("fs_cdpath", Sys_DefaultCDPath(), CVAR_INIT );
+ fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultInstallPath(), CVAR_INIT );
+ fs_basegame = Cvar_Get ("fs_basegame", "", CVAR_INIT );
+ homePath = Sys_DefaultHomePath();
+ if (!homePath || !homePath[0]) {
+ homePath = fs_basepath->string;
+ }
+ fs_homepath = Cvar_Get ("fs_homepath", homePath, CVAR_INIT );
+ fs_gamedirvar = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
+ fs_restrict = Cvar_Get ("fs_restrict", "", CVAR_INIT );
+
+ // add search path elements in reverse priority order
+ if (fs_cdpath->string[0]) {
+ FS_AddGameDirectory( fs_cdpath->string, gameName );
+ }
+ if (fs_basepath->string[0]) {
+ FS_AddGameDirectory( fs_basepath->string, gameName );
+ }
+ // fs_homepath is somewhat particular to *nix systems, only add if relevant
+ // NOTE: same filtering below for mods and basegame
+ if (fs_basepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
+ FS_AddGameDirectory ( fs_homepath->string, gameName );
+ }
+
+ // check for additional base game so mods can be based upon other mods
+ if ( fs_basegame->string[0] && !Q_stricmp( gameName, BASEGAME ) && Q_stricmp( fs_basegame->string, gameName ) ) {
+ if (fs_cdpath->string[0]) {
+ FS_AddGameDirectory(fs_cdpath->string, fs_basegame->string);
+ }
+ if (fs_basepath->string[0]) {
+ FS_AddGameDirectory(fs_basepath->string, fs_basegame->string);
+ }
+ if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
+ FS_AddGameDirectory(fs_homepath->string, fs_basegame->string);
+ }
+ }
+
+ // check for additional game folder for mods
+ if ( fs_gamedirvar->string[0] && !Q_stricmp( gameName, BASEGAME ) && Q_stricmp( fs_gamedirvar->string, gameName ) ) {
+ if (fs_cdpath->string[0]) {
+ FS_AddGameDirectory(fs_cdpath->string, fs_gamedirvar->string);
+ }
+ if (fs_basepath->string[0]) {
+ FS_AddGameDirectory(fs_basepath->string, fs_gamedirvar->string);
+ }
+ if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
+ FS_AddGameDirectory(fs_homepath->string, fs_gamedirvar->string);
+ }
+ }
+
+ // add our commands
+ Cmd_AddCommand ("path", FS_Path_f);
+ Cmd_AddCommand ("dir", FS_Dir_f );
+ Cmd_AddCommand ("fdir", FS_NewDir_f );
+ Cmd_AddCommand ("touchFile", FS_TouchFile_f );
+
+ // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=506
+ // reorder the pure pk3 files according to server order
+ FS_ReorderPurePaks();
+
+ // print the current search paths
+ FS_Path_f();
+
+ fs_gamedirvar->modified = qfalse; // We just loaded, it's not modified
+
+ Com_Printf( "----------------------\n" );
+
+#ifdef FS_MISSING
+ if (missingFiles == NULL) {
+ missingFiles = fopen( "\\missing.txt", "ab" );
+ }
+#endif
+ Com_Printf( "%d files in pk3 files\n", fs_packFiles );
+}
+
+/*
+=====================
+FS_GamePureChecksum
+
+Returns the checksum of the pk3 from which the server loaded the qagame.qvm
+=====================
+*/
+const char *FS_GamePureChecksum( void ) {
+ static char info[MAX_STRING_TOKENS];
+ searchpath_t *search;
+
+ info[0] = 0;
+
+ for ( search = fs_searchpaths ; search ; search = search->next ) {
+ // is the element a pak file?
+ if ( search->pack ) {
+ if (search->pack->referenced & FS_QAGAME_REF) {
+ Com_sprintf(info, sizeof(info), "%d", search->pack->checksum);
+ }
+ }
+ }
+
+ return info;
+}
+
+/*
+=====================
+FS_LoadedPakChecksums
+
+Returns a space separated string containing the checksums of all loaded pk3 files.
+Servers with sv_pure set will get this string and pass it to clients.
+=====================
+*/
+const char *FS_LoadedPakChecksums( void ) {
+ static char info[BIG_INFO_STRING];
+ searchpath_t *search;
+
+ info[0] = 0;
+
+ for ( search = fs_searchpaths ; search ; search = search->next ) {
+ // is the element a pak file?
+ if ( !search->pack ) {
+ continue;
+ }
+
+ Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) );
+ }
+
+ return info;
+}
+
+/*
+=====================
+FS_LoadedPakNames
+
+Returns a space separated string containing the names of all loaded pk3 files.
+Servers with sv_pure set will get this string and pass it to clients.
+=====================
+*/
+const char *FS_LoadedPakNames( void ) {
+ static char info[BIG_INFO_STRING];
+ searchpath_t *search;
+
+ info[0] = 0;
+
+ for ( search = fs_searchpaths ; search ; search = search->next ) {
+ // is the element a pak file?
+ if ( !search->pack ) {
+ continue;
+ }
+
+ if (*info) {
+ Q_strcat(info, sizeof( info ), " " );
+ }
+ Q_strcat( info, sizeof( info ), search->pack->pakBasename );
+ }
+
+ return info;
+}
+
+/*
+=====================
+FS_LoadedPakPureChecksums
+
+Returns a space separated string containing the pure checksums of all loaded pk3 files.
+Servers with sv_pure use these checksums to compare with the checksums the clients send
+back to the server.
+=====================
+*/
+const char *FS_LoadedPakPureChecksums( void ) {
+ static char info[BIG_INFO_STRING];
+ searchpath_t *search;
+
+ info[0] = 0;
+
+ for ( search = fs_searchpaths ; search ; search = search->next ) {
+ // is the element a pak file?
+ if ( !search->pack ) {
+ continue;
+ }
+
+ Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) );
+ }
+
+ return info;
+}
+
+/*
+=====================
+FS_ReferencedPakChecksums
+
+Returns a space separated string containing the checksums of all referenced pk3 files.
+The server will send this to the clients so they can check which files should be auto-downloaded.
+=====================
+*/
+const char *FS_ReferencedPakChecksums( void ) {
+ static char info[BIG_INFO_STRING];
+ searchpath_t *search;
+
+ info[0] = 0;
+
+
+ for ( search = fs_searchpaths ; search ; search = search->next ) {
+ // is the element a pak file?
+ if ( search->pack ) {
+ if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) {
+ Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) );
+ }
+ }
+ }
+
+ return info;
+}
+
+/*
+=====================
+FS_ReferencedPakPureChecksums
+
+Returns a space separated string containing the pure checksums of all referenced pk3 files.
+Servers with sv_pure set will get this string back from clients for pure validation
+
+The string has a specific order, "cgame ui @ ref1 ref2 ref3 ..."
+=====================
+*/
+const char *FS_ReferencedPakPureChecksums( void ) {
+ static char info[BIG_INFO_STRING];
+ searchpath_t *search;
+ int nFlags, numPaks, checksum;
+
+ info[0] = 0;
+
+ checksum = fs_checksumFeed;
+ numPaks = 0;
+ for (nFlags = FS_CGAME_REF; nFlags; nFlags = nFlags >> 1) {
+ if (nFlags & FS_GENERAL_REF) {
+ // add a delimter between must haves and general refs
+ //Q_strcat(info, sizeof(info), "@ ");
+ info[strlen(info)+1] = '\0';
+ info[strlen(info)+2] = '\0';
+ info[strlen(info)] = '@';
+ info[strlen(info)] = ' ';
+ }
+ for ( search = fs_searchpaths ; search ; search = search->next ) {
+ // is the element a pak file and has it been referenced based on flag?
+ if ( search->pack && (search->pack->referenced & nFlags)) {
+ Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) );
+ if (nFlags & (FS_CGAME_REF | FS_UI_REF)) {
+ break;
+ }
+ checksum ^= search->pack->pure_checksum;
+ numPaks++;
+ }
+ }
+ if (fs_fakeChkSum != 0) {
+ // only added if a non-pure file is referenced
+ Q_strcat( info, sizeof( info ), va("%i ", fs_fakeChkSum ) );
+ }
+ }
+ // last checksum is the encoded number of referenced pk3s
+ checksum ^= numPaks;
+ Q_strcat( info, sizeof( info ), va("%i ", checksum ) );
+
+ return info;
+}
+
+/*
+=====================
+FS_ReferencedPakNames
+
+Returns a space separated string containing the names of all referenced pk3 files.
+The server will send this to the clients so they can check which files should be auto-downloaded.
+=====================
+*/
+const char *FS_ReferencedPakNames( void ) {
+ static char info[BIG_INFO_STRING];
+ searchpath_t *search;
+
+ info[0] = 0;
+
+ // we want to return ALL pk3's from the fs_game path
+ // and referenced one's from base
+ for ( search = fs_searchpaths ; search ; search = search->next ) {
+ // is the element a pak file?
+ if ( search->pack ) {
+ if (*info) {
+ Q_strcat(info, sizeof( info ), " " );
+ }
+ if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) {
+ Q_strcat( info, sizeof( info ), search->pack->pakGamename );
+ Q_strcat( info, sizeof( info ), "/" );
+ Q_strcat( info, sizeof( info ), search->pack->pakBasename );
+ }
+ }
+ }
+
+ return info;
+}
+
+/*
+=====================
+FS_ClearPakReferences
+=====================
+*/
+void FS_ClearPakReferences( int flags ) {
+ searchpath_t *search;
+
+ if ( !flags ) {
+ flags = -1;
+ }
+ for ( search = fs_searchpaths; search; search = search->next ) {
+ // is the element a pak file and has it been referenced?
+ if ( search->pack ) {
+ search->pack->referenced &= ~flags;
+ }
+ }
+}
+
+
+/*
+=====================
+FS_PureServerSetLoadedPaks
+
+If the string is empty, all data sources will be allowed.
+If not empty, only pk3 files that match one of the space
+separated checksums will be checked for files, with the
+exception of .cfg and .dat files.
+=====================
+*/
+void FS_PureServerSetLoadedPaks( const char *pakSums, const char *pakNames ) {
+ int i, c, d;
+
+ Cmd_TokenizeString( pakSums );
+
+ c = Cmd_Argc();
+ if ( c > MAX_SEARCH_PATHS ) {
+ c = MAX_SEARCH_PATHS;
+ }
+
+ fs_numServerPaks = c;
+
+ for ( i = 0 ; i < c ; i++ ) {
+ fs_serverPaks[i] = atoi( Cmd_Argv( i ) );
+ }
+
+ if (fs_numServerPaks) {
+ Com_DPrintf( "Connected to a pure server.\n" );
+ }
+ else
+ {
+ if (fs_reordered)
+ {
+ // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
+ // force a restart to make sure the search order will be correct
+ Com_DPrintf( "FS search reorder is required\n" );
+ FS_Restart(fs_checksumFeed);
+ return;
+ }
+ }
+
+ for ( i = 0 ; i < c ; i++ ) {
+ if (fs_serverPakNames[i]) {
+ Z_Free(fs_serverPakNames[i]);
+ }
+ fs_serverPakNames[i] = NULL;
+ }
+ if ( pakNames && *pakNames ) {
+ Cmd_TokenizeString( pakNames );
+
+ d = Cmd_Argc();
+ if ( d > MAX_SEARCH_PATHS ) {
+ d = MAX_SEARCH_PATHS;
+ }
+
+ for ( i = 0 ; i < d ; i++ ) {
+ fs_serverPakNames[i] = CopyString( Cmd_Argv( i ) );
+ }
+ }
+}
+
+/*
+=====================
+FS_PureServerSetReferencedPaks
+
+The checksums and names of the pk3 files referenced at the server
+are sent to the client and stored here. The client will use these
+checksums to see if any pk3 files need to be auto-downloaded.
+=====================
+*/
+void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames ) {
+ int i, c, d = 0;
+
+ Cmd_TokenizeString( pakSums );
+
+ c = Cmd_Argc();
+ if ( c > MAX_SEARCH_PATHS ) {
+ c = MAX_SEARCH_PATHS;
+ }
+
+ for ( i = 0 ; i < c ; i++ ) {
+ fs_serverReferencedPaks[i] = atoi( Cmd_Argv( i ) );
+ }
+
+ for (i = 0 ; i < sizeof(fs_serverReferencedPakNames) / sizeof(*fs_serverReferencedPakNames); i++)
+ {
+ if(fs_serverReferencedPakNames[i])
+ Z_Free(fs_serverReferencedPakNames[i]);
+
+ fs_serverReferencedPakNames[i] = NULL;
+ }
+
+ if ( pakNames && *pakNames ) {
+ Cmd_TokenizeString( pakNames );
+
+ d = Cmd_Argc();
+
+ if(d > c)
+ d = c;
+
+ for ( i = 0 ; i < d ; i++ ) {
+ fs_serverReferencedPakNames[i] = CopyString( Cmd_Argv( i ) );
+ }
+ }
+
+ // ensure that there are as many checksums as there are pak names.
+ if(d < c)
+ c = d;
+
+ fs_numServerReferencedPaks = c;
+}
+
+/*
+================
+FS_InitFilesystem
+
+Called only at inital startup, not when the filesystem
+is resetting due to a game change
+================
+*/
+void FS_InitFilesystem( void ) {
+ // allow command line parms to override our defaults
+ // we have to specially handle this, because normal command
+ // line variable sets don't happen until after the filesystem
+ // has already been initialized
+ Com_StartupVariable( "fs_cdpath" );
+ Com_StartupVariable( "fs_basepath" );
+ Com_StartupVariable( "fs_homepath" );
+ Com_StartupVariable( "fs_game" );
+ Com_StartupVariable( "fs_copyfiles" );
+ Com_StartupVariable( "fs_restrict" );
+
+ // try to start up normally
+ FS_Startup( BASEGAME );
+
+ // if we can't find default.cfg, assume that the paths are
+ // busted and error out now, rather than getting an unreadable
+ // graphics screen when the font fails to load
+ if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) {
+ Com_Error( ERR_FATAL, "Couldn't load default.cfg" );
+ // bk001208 - SafeMode see below, FIXME?
+ }
+
+ Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase));
+ Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame));
+
+ // bk001208 - SafeMode see below, FIXME?
+}
+
+
+/*
+================
+FS_Restart
+================
+*/
+void FS_Restart( int checksumFeed ) {
+
+ // free anything we currently have loaded
+ FS_Shutdown(qfalse);
+
+ // set the checksum feed
+ fs_checksumFeed = checksumFeed;
+
+ // clear pak references
+ FS_ClearPakReferences(0);
+
+ // try to start up normally
+ FS_Startup( BASEGAME );
+
+ // if we can't find default.cfg, assume that the paths are
+ // busted and error out now, rather than getting an unreadable
+ // graphics screen when the font fails to load
+ if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) {
+ // this might happen when connecting to a pure server not using BASEGAME/pak0.pk3
+ // (for instance a TA demo server)
+ if (lastValidBase[0]) {
+ FS_PureServerSetLoadedPaks("", "");
+ Cvar_Set("fs_basepath", lastValidBase);
+ Cvar_Set("fs_gamedirvar", lastValidGame);
+ lastValidBase[0] = '\0';
+ lastValidGame[0] = '\0';
+ Cvar_Set( "fs_restrict", "0" );
+ FS_Restart(checksumFeed);
+ Com_Error( ERR_DROP, "Invalid game folder\n" );
+ return;
+ }
+ Com_Error( ERR_FATAL, "Couldn't load default.cfg" );
+ }
+
+ // bk010116 - new check before safeMode
+ if ( Q_stricmp(fs_gamedirvar->string, lastValidGame) ) {
+ // skip the autogen.cfg if "safe" is on the command line
+ if ( !Com_SafeMode() ) {
+ Cbuf_AddText ("exec autogen.cfg\n");
+ }
+ }
+
+ Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase));
+ Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame));
+
+}
+
+/*
+=================
+FS_ConditionalRestart
+restart if necessary
+=================
+*/
+qboolean FS_ConditionalRestart( int checksumFeed ) {
+ if( fs_gamedirvar->modified || checksumFeed != fs_checksumFeed ) {
+ FS_Restart( checksumFeed );
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+========================================================================================
+
+Handle based file calls for virtual machines
+
+========================================================================================
+*/
+
+int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) {
+ int r;
+ qboolean sync;
+
+ sync = qfalse;
+
+ switch( mode ) {
+ case FS_READ:
+ r = FS_FOpenFileRead( qpath, f, qtrue );
+ break;
+ case FS_WRITE:
+ *f = FS_FOpenFileWrite( qpath );
+ r = 0;
+ if (*f == 0) {
+ r = -1;
+ }
+ break;
+ case FS_APPEND_SYNC:
+ sync = qtrue;
+ case FS_APPEND:
+ *f = FS_FOpenFileAppend( qpath );
+ r = 0;
+ if (*f == 0) {
+ r = -1;
+ }
+ break;
+ default:
+ Com_Error( ERR_FATAL, "FSH_FOpenFile: bad mode" );
+ return -1;
+ }
+
+ if (!f) {
+ return r;
+ }
+
+ if ( *f ) {
+ if (fsh[*f].zipFile == qtrue) {
+ fsh[*f].baseOffset = unztell(fsh[*f].handleFiles.file.z);
+ } else {
+ fsh[*f].baseOffset = ftell(fsh[*f].handleFiles.file.o);
+ }
+ fsh[*f].fileSize = r;
+ fsh[*f].streamed = qfalse;
+
+ if (mode == FS_READ) {
+ Sys_BeginStreamedFile( *f, 0x4000 );
+ fsh[*f].streamed = qtrue;
+ }
+ }
+ fsh[*f].handleSync = sync;
+
+ return r;
+}
+
+int FS_FTell( fileHandle_t f ) {
+ int pos;
+ if (fsh[f].zipFile == qtrue) {
+ pos = unztell(fsh[f].handleFiles.file.z);
+ } else {
+ pos = ftell(fsh[f].handleFiles.file.o);
+ }
+ return pos;
+}
+
+void FS_Flush( fileHandle_t f ) {
+ fflush(fsh[f].handleFiles.file.o);
+}
+
+void FS_FilenameCompletion( const char *dir, const char *ext,
+ qboolean stripExt, void(*callback)(const char *s) ) {
+ char **filenames;
+ int nfiles;
+ int i;
+ char filename[ MAX_STRING_CHARS ];
+
+ filenames = FS_ListFilteredFiles( dir, ext, NULL, &nfiles );
+
+ FS_SortFileList( filenames, nfiles );
+
+ for( i = 0; i < nfiles; i++ ) {
+ FS_ConvertPath( filenames[ i ] );
+ Q_strncpyz( filename, filenames[ i ], MAX_STRING_CHARS );
+
+ if( stripExt ) {
+ COM_StripExtension(filename, filename, sizeof(filename));
+ }
+
+ callback( filename );
+ }
+ FS_FreeFileList( filenames );
+}
diff --git a/src/qcommon/huffman.c b/src/qcommon/huffman.c
new file mode 100644
index 0000000..a579944
--- /dev/null
+++ b/src/qcommon/huffman.c
@@ -0,0 +1,438 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/* This is based on the Adaptive Huffman algorithm described in Sayood's Data
+ * Compression book. The ranks are not actually stored, but implicitly defined
+ * by the location of a node within a doubly-linked list */
+
+#include "q_shared.h"
+#include "qcommon.h"
+
+static int bloc = 0;
+
+void Huff_putBit( int bit, byte *fout, int *offset) {
+ bloc = *offset;
+ if ((bloc&7) == 0) {
+ fout[(bloc>>3)] = 0;
+ }
+ fout[(bloc>>3)] |= bit << (bloc&7);
+ bloc++;
+ *offset = bloc;
+}
+
+int Huff_getBit( byte *fin, int *offset) {
+ int t;
+ bloc = *offset;
+ t = (fin[(bloc>>3)] >> (bloc&7)) & 0x1;
+ bloc++;
+ *offset = bloc;
+ return t;
+}
+
+/* Add a bit to the output file (buffered) */
+static void add_bit (char bit, byte *fout) {
+ if ((bloc&7) == 0) {
+ fout[(bloc>>3)] = 0;
+ }
+ fout[(bloc>>3)] |= bit << (bloc&7);
+ bloc++;
+}
+
+/* Receive one bit from the input file (buffered) */
+static int get_bit (byte *fin) {
+ int t;
+ t = (fin[(bloc>>3)] >> (bloc&7)) & 0x1;
+ bloc++;
+ return t;
+}
+
+static node_t **get_ppnode(huff_t* huff) {
+ node_t **tppnode;
+ if (!huff->freelist) {
+ return &(huff->nodePtrs[huff->blocPtrs++]);
+ } else {
+ tppnode = huff->freelist;
+ huff->freelist = (node_t **)*tppnode;
+ return tppnode;
+ }
+}
+
+static void free_ppnode(huff_t* huff, node_t **ppnode) {
+ *ppnode = (node_t *)huff->freelist;
+ huff->freelist = ppnode;
+}
+
+/* Swap the location of these two nodes in the tree */
+static void swap (huff_t* huff, node_t *node1, node_t *node2) {
+ node_t *par1, *par2;
+
+ par1 = node1->parent;
+ par2 = node2->parent;
+
+ if (par1) {
+ if (par1->left == node1) {
+ par1->left = node2;
+ } else {
+ par1->right = node2;
+ }
+ } else {
+ huff->tree = node2;
+ }
+
+ if (par2) {
+ if (par2->left == node2) {
+ par2->left = node1;
+ } else {
+ par2->right = node1;
+ }
+ } else {
+ huff->tree = node1;
+ }
+
+ node1->parent = par2;
+ node2->parent = par1;
+}
+
+/* Swap these two nodes in the linked list (update ranks) */
+static void swaplist(node_t *node1, node_t *node2) {
+ node_t *par1;
+
+ par1 = node1->next;
+ node1->next = node2->next;
+ node2->next = par1;
+
+ par1 = node1->prev;
+ node1->prev = node2->prev;
+ node2->prev = par1;
+
+ if (node1->next == node1) {
+ node1->next = node2;
+ }
+ if (node2->next == node2) {
+ node2->next = node1;
+ }
+ if (node1->next) {
+ node1->next->prev = node1;
+ }
+ if (node2->next) {
+ node2->next->prev = node2;
+ }
+ if (node1->prev) {
+ node1->prev->next = node1;
+ }
+ if (node2->prev) {
+ node2->prev->next = node2;
+ }
+}
+
+/* Do the increments */
+static void increment(huff_t* huff, node_t *node) {
+ node_t *lnode;
+
+ if (!node) {
+ return;
+ }
+
+ if (node->next != NULL && node->next->weight == node->weight) {
+ lnode = *node->head;
+ if (lnode != node->parent) {
+ swap(huff, lnode, node);
+ }
+ swaplist(lnode, node);
+ }
+ if (node->prev && node->prev->weight == node->weight) {
+ *node->head = node->prev;
+ } else {
+ *node->head = NULL;
+ free_ppnode(huff, node->head);
+ }
+ node->weight++;
+ if (node->next && node->next->weight == node->weight) {
+ node->head = node->next->head;
+ } else {
+ node->head = get_ppnode(huff);
+ *node->head = node;
+ }
+ if (node->parent) {
+ increment(huff, node->parent);
+ if (node->prev == node->parent) {
+ swaplist(node, node->parent);
+ if (*node->head == node) {
+ *node->head = node->parent;
+ }
+ }
+ }
+}
+
+void Huff_addRef(huff_t* huff, byte ch) {
+ node_t *tnode, *tnode2;
+ if (huff->loc[ch] == NULL) { /* if this is the first transmission of this node */
+ tnode = &(huff->nodeList[huff->blocNode++]);
+ tnode2 = &(huff->nodeList[huff->blocNode++]);
+
+ tnode2->symbol = INTERNAL_NODE;
+ tnode2->weight = 1;
+ tnode2->next = huff->lhead->next;
+ if (huff->lhead->next) {
+ huff->lhead->next->prev = tnode2;
+ if (huff->lhead->next->weight == 1) {
+ tnode2->head = huff->lhead->next->head;
+ } else {
+ tnode2->head = get_ppnode(huff);
+ *tnode2->head = tnode2;
+ }
+ } else {
+ tnode2->head = get_ppnode(huff);
+ *tnode2->head = tnode2;
+ }
+ huff->lhead->next = tnode2;
+ tnode2->prev = huff->lhead;
+
+ tnode->symbol = ch;
+ tnode->weight = 1;
+ tnode->next = huff->lhead->next;
+ if (huff->lhead->next) {
+ huff->lhead->next->prev = tnode;
+ if (huff->lhead->next->weight == 1) {
+ tnode->head = huff->lhead->next->head;
+ } else {
+ /* this should never happen */
+ tnode->head = get_ppnode(huff);
+ *tnode->head = tnode2;
+ }
+ } else {
+ /* this should never happen */
+ tnode->head = get_ppnode(huff);
+ *tnode->head = tnode;
+ }
+ huff->lhead->next = tnode;
+ tnode->prev = huff->lhead;
+ tnode->left = tnode->right = NULL;
+
+ if (huff->lhead->parent) {
+ if (huff->lhead->parent->left == huff->lhead) { /* lhead is guaranteed to by the NYT */
+ huff->lhead->parent->left = tnode2;
+ } else {
+ huff->lhead->parent->right = tnode2;
+ }
+ } else {
+ huff->tree = tnode2;
+ }
+
+ tnode2->right = tnode;
+ tnode2->left = huff->lhead;
+
+ tnode2->parent = huff->lhead->parent;
+ huff->lhead->parent = tnode->parent = tnode2;
+
+ huff->loc[ch] = tnode;
+
+ increment(huff, tnode2->parent);
+ } else {
+ increment(huff, huff->loc[ch]);
+ }
+}
+
+/* Get a symbol */
+int Huff_Receive (node_t *node, int *ch, byte *fin) {
+ while (node && node->symbol == INTERNAL_NODE) {
+ if (get_bit(fin)) {
+ node = node->right;
+ } else {
+ node = node->left;
+ }
+ }
+ if (!node) {
+ return 0;
+// Com_Error(ERR_DROP, "Illegal tree!\n");
+ }
+ return (*ch = node->symbol);
+}
+
+/* Get a symbol */
+void Huff_offsetReceive (node_t *node, int *ch, byte *fin, int *offset) {
+ bloc = *offset;
+ while (node && node->symbol == INTERNAL_NODE) {
+ if (get_bit(fin)) {
+ node = node->right;
+ } else {
+ node = node->left;
+ }
+ }
+ if (!node) {
+ *ch = 0;
+ return;
+// Com_Error(ERR_DROP, "Illegal tree!\n");
+ }
+ *ch = node->symbol;
+ *offset = bloc;
+}
+
+/* Send the prefix code for this node */
+static void send(node_t *node, node_t *child, byte *fout) {
+ if (node->parent) {
+ send(node->parent, node, fout);
+ }
+ if (child) {
+ if (node->right == child) {
+ add_bit(1, fout);
+ } else {
+ add_bit(0, fout);
+ }
+ }
+}
+
+/* Send a symbol */
+void Huff_transmit (huff_t *huff, int ch, byte *fout) {
+ int i;
+ if (huff->loc[ch] == NULL) {
+ /* node_t hasn't been transmitted, send a NYT, then the symbol */
+ Huff_transmit(huff, NYT, fout);
+ for (i = 7; i >= 0; i--) {
+ add_bit((char)((ch >> i) & 0x1), fout);
+ }
+ } else {
+ send(huff->loc[ch], NULL, fout);
+ }
+}
+
+void Huff_offsetTransmit (huff_t *huff, int ch, byte *fout, int *offset) {
+ bloc = *offset;
+ send(huff->loc[ch], NULL, fout);
+ *offset = bloc;
+}
+
+void Huff_Decompress(msg_t *mbuf, int offset) {
+ int ch, cch, i, j, size;
+ byte seq[65536];
+ byte* buffer;
+ huff_t huff;
+
+ size = mbuf->cursize - offset;
+ buffer = mbuf->data + offset;
+
+ if ( size <= 0 ) {
+ return;
+ }
+
+ Com_Memset(&huff, 0, sizeof(huff_t));
+ // Initialize the tree & list with the NYT node
+ huff.tree = huff.lhead = huff.ltail = huff.loc[NYT] = &(huff.nodeList[huff.blocNode++]);
+ huff.tree->symbol = NYT;
+ huff.tree->weight = 0;
+ huff.lhead->next = huff.lhead->prev = NULL;
+ huff.tree->parent = huff.tree->left = huff.tree->right = NULL;
+
+ cch = buffer[0]*256 + buffer[1];
+ // don't overflow with bad messages
+ if ( cch > mbuf->maxsize - offset ) {
+ cch = mbuf->maxsize - offset;
+ }
+ bloc = 16;
+
+ for ( j = 0; j < cch; j++ ) {
+ ch = 0;
+ // don't overflow reading from the messages
+ // FIXME: would it be better to have a overflow check in get_bit ?
+ if ( (bloc >> 3) > size ) {
+ seq[j] = 0;
+ break;
+ }
+ Huff_Receive(huff.tree, &ch, buffer); /* Get a character */
+ if ( ch == NYT ) { /* We got a NYT, get the symbol associated with it */
+ ch = 0;
+ for ( i = 0; i < 8; i++ ) {
+ ch = (ch<<1) + get_bit(buffer);
+ }
+ }
+
+ seq[j] = ch; /* Write symbol */
+
+ Huff_addRef(&huff, (byte)ch); /* Increment node */
+ }
+ mbuf->cursize = cch + offset;
+ Com_Memcpy(mbuf->data + offset, seq, cch);
+}
+
+extern int oldsize;
+
+void Huff_Compress(msg_t *mbuf, int offset) {
+ int i, ch, size;
+ byte seq[65536];
+ byte* buffer;
+ huff_t huff;
+
+ size = mbuf->cursize - offset;
+ buffer = mbuf->data+ + offset;
+
+ if (size<=0) {
+ return;
+ }
+
+ Com_Memset(&huff, 0, sizeof(huff_t));
+ // Add the NYT (not yet transmitted) node into the tree/list */
+ huff.tree = huff.lhead = huff.loc[NYT] = &(huff.nodeList[huff.blocNode++]);
+ huff.tree->symbol = NYT;
+ huff.tree->weight = 0;
+ huff.lhead->next = huff.lhead->prev = NULL;
+ huff.tree->parent = huff.tree->left = huff.tree->right = NULL;
+ huff.loc[NYT] = huff.tree;
+
+ seq[0] = (size>>8);
+ seq[1] = size&0xff;
+
+ bloc = 16;
+
+ for (i=0; i<size; i++ ) {
+ ch = buffer[i];
+ Huff_transmit(&huff, ch, seq); /* Transmit symbol */
+ Huff_addRef(&huff, (byte)ch); /* Do update */
+ }
+
+ bloc += 8; // next byte
+
+ mbuf->cursize = (bloc>>3) + offset;
+ Com_Memcpy(mbuf->data+offset, seq, (bloc>>3));
+}
+
+void Huff_Init(huffman_t *huff) {
+
+ Com_Memset(&huff->compressor, 0, sizeof(huff_t));
+ Com_Memset(&huff->decompressor, 0, sizeof(huff_t));
+
+ // Initialize the tree & list with the NYT node
+ huff->decompressor.tree = huff->decompressor.lhead = huff->decompressor.ltail = huff->decompressor.loc[NYT] = &(huff->decompressor.nodeList[huff->decompressor.blocNode++]);
+ huff->decompressor.tree->symbol = NYT;
+ huff->decompressor.tree->weight = 0;
+ huff->decompressor.lhead->next = huff->decompressor.lhead->prev = NULL;
+ huff->decompressor.tree->parent = huff->decompressor.tree->left = huff->decompressor.tree->right = NULL;
+
+ // Add the NYT (not yet transmitted) node into the tree/list */
+ huff->compressor.tree = huff->compressor.lhead = huff->compressor.loc[NYT] = &(huff->compressor.nodeList[huff->compressor.blocNode++]);
+ huff->compressor.tree->symbol = NYT;
+ huff->compressor.tree->weight = 0;
+ huff->compressor.lhead->next = huff->compressor.lhead->prev = NULL;
+ huff->compressor.tree->parent = huff->compressor.tree->left = huff->compressor.tree->right = NULL;
+ huff->compressor.loc[NYT] = huff->compressor.tree;
+}
+
diff --git a/src/qcommon/md4.c b/src/qcommon/md4.c
new file mode 100644
index 0000000..838b062
--- /dev/null
+++ b/src/qcommon/md4.c
@@ -0,0 +1,208 @@
+/*
+ mdfour.c
+
+ An implementation of MD4 designed for use in the samba SMB
+ authentication protocol
+
+ Copyright (C) 1997-1998 Andrew Tridgell
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to:
+
+ Free Software Foundation, Inc.
+ 59 Temple Place - Suite 330
+ Boston, MA 02111-1307, USA
+
+ $Id: mdfour.c,v 1.1 2002/08/23 22:03:27 abster Exp $
+*/
+
+#include "q_shared.h"
+#include "qcommon.h"
+
+struct mdfour {
+ uint32_t A, B, C, D;
+ uint32_t totalN;
+};
+
+
+/* NOTE: This code makes no attempt to be fast!
+
+ It assumes that a int is at least 32 bits long
+*/
+
+static struct mdfour *m;
+
+#define F(X,Y,Z) (((X)&(Y)) | ((~(X))&(Z)))
+#define G(X,Y,Z) (((X)&(Y)) | ((X)&(Z)) | ((Y)&(Z)))
+#define H(X,Y,Z) ((X)^(Y)^(Z))
+#define lshift(x,s) (((x)<<(s)) | ((x)>>(32-(s))))
+
+#define ROUND1(a,b,c,d,k,s) a = lshift(a + F(b,c,d) + X[k], s)
+#define ROUND2(a,b,c,d,k,s) a = lshift(a + G(b,c,d) + X[k] + 0x5A827999,s)
+#define ROUND3(a,b,c,d,k,s) a = lshift(a + H(b,c,d) + X[k] + 0x6ED9EBA1,s)
+
+/* this applies md4 to 64 byte chunks */
+static void mdfour64(uint32_t *M)
+{
+ int j;
+ uint32_t AA, BB, CC, DD;
+ uint32_t X[16];
+ uint32_t A,B,C,D;
+
+ for (j=0;j<16;j++)
+ X[j] = M[j];
+
+ A = m->A; B = m->B; C = m->C; D = m->D;
+ AA = A; BB = B; CC = C; DD = D;
+
+ ROUND1(A,B,C,D, 0, 3); ROUND1(D,A,B,C, 1, 7);
+ ROUND1(C,D,A,B, 2, 11); ROUND1(B,C,D,A, 3, 19);
+ ROUND1(A,B,C,D, 4, 3); ROUND1(D,A,B,C, 5, 7);
+ ROUND1(C,D,A,B, 6, 11); ROUND1(B,C,D,A, 7, 19);
+ ROUND1(A,B,C,D, 8, 3); ROUND1(D,A,B,C, 9, 7);
+ ROUND1(C,D,A,B, 10, 11); ROUND1(B,C,D,A, 11, 19);
+ ROUND1(A,B,C,D, 12, 3); ROUND1(D,A,B,C, 13, 7);
+ ROUND1(C,D,A,B, 14, 11); ROUND1(B,C,D,A, 15, 19);
+
+ ROUND2(A,B,C,D, 0, 3); ROUND2(D,A,B,C, 4, 5);
+ ROUND2(C,D,A,B, 8, 9); ROUND2(B,C,D,A, 12, 13);
+ ROUND2(A,B,C,D, 1, 3); ROUND2(D,A,B,C, 5, 5);
+ ROUND2(C,D,A,B, 9, 9); ROUND2(B,C,D,A, 13, 13);
+ ROUND2(A,B,C,D, 2, 3); ROUND2(D,A,B,C, 6, 5);
+ ROUND2(C,D,A,B, 10, 9); ROUND2(B,C,D,A, 14, 13);
+ ROUND2(A,B,C,D, 3, 3); ROUND2(D,A,B,C, 7, 5);
+ ROUND2(C,D,A,B, 11, 9); ROUND2(B,C,D,A, 15, 13);
+
+ ROUND3(A,B,C,D, 0, 3); ROUND3(D,A,B,C, 8, 9);
+ ROUND3(C,D,A,B, 4, 11); ROUND3(B,C,D,A, 12, 15);
+ ROUND3(A,B,C,D, 2, 3); ROUND3(D,A,B,C, 10, 9);
+ ROUND3(C,D,A,B, 6, 11); ROUND3(B,C,D,A, 14, 15);
+ ROUND3(A,B,C,D, 1, 3); ROUND3(D,A,B,C, 9, 9);
+ ROUND3(C,D,A,B, 5, 11); ROUND3(B,C,D,A, 13, 15);
+ ROUND3(A,B,C,D, 3, 3); ROUND3(D,A,B,C, 11, 9);
+ ROUND3(C,D,A,B, 7, 11); ROUND3(B,C,D,A, 15, 15);
+
+ A += AA; B += BB; C += CC; D += DD;
+
+ for (j=0;j<16;j++)
+ X[j] = 0;
+
+ m->A = A; m->B = B; m->C = C; m->D = D;
+}
+
+static void copy64(uint32_t *M, byte *in)
+{
+ int i;
+
+ for (i=0;i<16;i++)
+ M[i] = (in[i*4+3]<<24) | (in[i*4+2]<<16) |
+ (in[i*4+1]<<8) | (in[i*4+0]<<0);
+}
+
+static void copy4(byte *out,uint32_t x)
+{
+ out[0] = x&0xFF;
+ out[1] = (x>>8)&0xFF;
+ out[2] = (x>>16)&0xFF;
+ out[3] = (x>>24)&0xFF;
+}
+
+void mdfour_begin(struct mdfour *md)
+{
+ md->A = 0x67452301;
+ md->B = 0xefcdab89;
+ md->C = 0x98badcfe;
+ md->D = 0x10325476;
+ md->totalN = 0;
+}
+
+
+static void mdfour_tail(byte *in, int n)
+{
+ byte buf[128];
+ uint32_t M[16];
+ uint32_t b;
+
+ m->totalN += n;
+
+ b = m->totalN * 8;
+
+ Com_Memset(buf, 0, 128);
+ if (n) Com_Memcpy(buf, in, n);
+ buf[n] = 0x80;
+
+ if (n <= 55) {
+ copy4(buf+56, b);
+ copy64(M, buf);
+ mdfour64(M);
+ } else {
+ copy4(buf+120, b);
+ copy64(M, buf);
+ mdfour64(M);
+ copy64(M, buf+64);
+ mdfour64(M);
+ }
+}
+
+static void mdfour_update(struct mdfour *md, byte *in, int n)
+{
+ uint32_t M[16];
+
+ m = md;
+
+ if (n == 0) mdfour_tail(in, n);
+
+ while (n >= 64) {
+ copy64(M, in);
+ mdfour64(M);
+ in += 64;
+ n -= 64;
+ m->totalN += 64;
+ }
+
+ mdfour_tail(in, n);
+}
+
+
+static void mdfour_result(struct mdfour *md, byte *out)
+{
+ m = md;
+
+ copy4(out, m->A);
+ copy4(out+4, m->B);
+ copy4(out+8, m->C);
+ copy4(out+12, m->D);
+}
+
+static void mdfour(byte *out, byte *in, int n)
+{
+ struct mdfour md;
+ mdfour_begin(&md);
+ mdfour_update(&md, in, n);
+ mdfour_result(&md, out);
+}
+
+//===================================================================
+
+unsigned Com_BlockChecksum (const void *buffer, int length)
+{
+ int digest[4];
+ unsigned val;
+
+ mdfour( (byte *)digest, (byte *)buffer, length );
+
+ val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3];
+
+ return val;
+}
diff --git a/src/qcommon/md5.c b/src/qcommon/md5.c
new file mode 100644
index 0000000..5cf12bb
--- /dev/null
+++ b/src/qcommon/md5.c
@@ -0,0 +1,310 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+#include "q_shared.h"
+#include "qcommon.h"
+
+typedef struct MD5Context {
+ uint32_t buf[4];
+ uint32_t bits[2];
+ unsigned char in[64];
+} MD5_CTX;
+
+#ifndef Q3_BIG_ENDIAN
+ #define byteReverse(buf, len) /* Nothing */
+#else
+ static void byteReverse(unsigned char *buf, unsigned longs);
+
+ /*
+ * Note: this code is harmless on little-endian machines.
+ */
+ static void byteReverse(unsigned char *buf, unsigned longs)
+ {
+ uint32_t t;
+ do {
+ t = (uint32_t)
+ ((unsigned) buf[3] << 8 | buf[2]) << 16 |
+ ((unsigned) buf[1] << 8 | buf[0]);
+ *(uint32_t *) buf = t;
+ buf += 4;
+ } while (--longs);
+ }
+#endif // Q3_BIG_ENDIAN
+
+/*
+ * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+static void MD5Init(struct MD5Context *ctx)
+{
+ ctx->buf[0] = 0x67452301;
+ ctx->buf[1] = 0xefcdab89;
+ ctx->buf[2] = 0x98badcfe;
+ ctx->buf[3] = 0x10325476;
+
+ ctx->bits[0] = 0;
+ ctx->bits[1] = 0;
+}
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+ ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data. MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+static void MD5Transform(uint32_t buf[4],
+ uint32_t const in[16])
+{
+ register uint32_t a, b, c, d;
+
+ a = buf[0];
+ b = buf[1];
+ c = buf[2];
+ d = buf[3];
+
+ MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+ MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+ MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+ MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+ MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+ MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+ MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+ MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+ MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+ MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+ MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+ MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+ MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+ MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+ MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+ MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+ MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+ MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+ MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+ MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+ MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+ MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+ MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+ MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+ MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+ MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+ MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+ MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+ MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+ MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+ MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+ MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+ MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+ MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+ MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+ MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+ MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+ MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+ MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+ MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+ MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+ MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+ MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+ MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+ MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+ MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+ MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+ MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+ MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+ MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+ MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+ MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+ MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+ MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+ MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+ MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+ MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+ MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+ MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+ MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+ MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+ MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+ MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+ MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+static void MD5Update(struct MD5Context *ctx, unsigned char const *buf,
+ unsigned len)
+{
+ uint32_t t;
+
+ /* Update bitcount */
+
+ t = ctx->bits[0];
+ if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
+ ctx->bits[1]++; /* Carry from low to high */
+ ctx->bits[1] += len >> 29;
+
+ t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
+
+ /* Handle any leading odd-sized chunks */
+
+ if (t) {
+ unsigned char *p = (unsigned char *) ctx->in + t;
+
+ t = 64 - t;
+ if (len < t) {
+ memcpy(p, buf, len);
+ return;
+ }
+ memcpy(p, buf, t);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ buf += t;
+ len -= t;
+ }
+ /* Process data in 64-byte chunks */
+
+ while (len >= 64) {
+ memcpy(ctx->in, buf, 64);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ buf += 64;
+ len -= 64;
+ }
+
+ /* Handle any remaining bytes of data. */
+
+ memcpy(ctx->in, buf, len);
+}
+
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+static void MD5Final(struct MD5Context *ctx, unsigned char *digest)
+{
+ unsigned count;
+ unsigned char *p;
+
+ /* Compute number of bytes mod 64 */
+ count = (ctx->bits[0] >> 3) & 0x3F;
+
+ /* Set the first char of padding to 0x80. This is safe since there is
+ always at least one byte free */
+ p = ctx->in + count;
+ *p++ = 0x80;
+
+ /* Bytes of padding needed to make 64 bytes */
+ count = 64 - 1 - count;
+
+ /* Pad out to 56 mod 64 */
+ if (count < 8) {
+ /* Two lots of padding: Pad the first block to 64 bytes */
+ memset(p, 0, count);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+
+ /* Now fill the next block with 56 bytes */
+ memset(ctx->in, 0, 56);
+ } else {
+ /* Pad block to 56 bytes */
+ memset(p, 0, count - 8);
+ }
+ byteReverse(ctx->in, 14);
+
+ /* Append length in bits and transform */
+ ((uint32_t *) ctx->in)[14] = ctx->bits[0];
+ ((uint32_t *) ctx->in)[15] = ctx->bits[1];
+
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ byteReverse((unsigned char *) ctx->buf, 4);
+
+ if (digest!=NULL)
+ memcpy(digest, ctx->buf, 16);
+ memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */
+}
+
+
+char *Com_MD5File( const char *fn, int length, const char *prefix, int prefix_len )
+{
+ static char final[33] = {""};
+ unsigned char digest[16] = {""};
+ fileHandle_t f;
+ MD5_CTX md5;
+ byte buffer[2048];
+ int i;
+ int filelen = 0;
+ int r = 0;
+ int total = 0;
+
+ Q_strncpyz( final, "", sizeof( final ) );
+
+ filelen = FS_SV_FOpenFileRead( fn, &f );
+
+ if( !f ) {
+ return final;
+ }
+ if( filelen < 1 ) {
+ FS_FCloseFile( f );
+ return final;
+ }
+ if(filelen < length || !length) {
+ length = filelen;
+ }
+
+ MD5Init(&md5);
+
+ if( prefix_len && *prefix )
+ MD5Update(&md5 , (unsigned char *)prefix, prefix_len);
+
+ for(;;) {
+ r = FS_Read2(buffer, sizeof(buffer), f);
+ if(r < 1)
+ break;
+ if(r + total > length)
+ r = length - total;
+ total += r;
+ MD5Update(&md5 , buffer, r);
+ if(r < sizeof(buffer) || total >= length)
+ break;
+ }
+ FS_FCloseFile(f);
+ MD5Final(&md5, digest);
+ final[0] = '\0';
+ for(i = 0; i < 16; i++) {
+ Q_strcat(final, sizeof(final), va("%02X", digest[i]));
+ }
+ return final;
+}
diff --git a/src/qcommon/msg.c b/src/qcommon/msg.c
new file mode 100644
index 0000000..b30b2ba
--- /dev/null
+++ b/src/qcommon/msg.c
@@ -0,0 +1,1767 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+#include "q_shared.h"
+#include "qcommon.h"
+
+static huffman_t msgHuff;
+
+static qboolean msgInit = qfalse;
+
+int pcount[256];
+
+/*
+==============================================================================
+
+ MESSAGE IO FUNCTIONS
+
+Handles byte ordering and avoids alignment errors
+==============================================================================
+*/
+
+int oldsize = 0;
+
+void MSG_initHuffman( void );
+
+void MSG_Init( msg_t *buf, byte *data, int length ) {
+ if (!msgInit) {
+ MSG_initHuffman();
+ }
+ Com_Memset (buf, 0, sizeof(*buf));
+ buf->data = data;
+ buf->maxsize = length;
+}
+
+void MSG_InitOOB( msg_t *buf, byte *data, int length ) {
+ if (!msgInit) {
+ MSG_initHuffman();
+ }
+ Com_Memset (buf, 0, sizeof(*buf));
+ buf->data = data;
+ buf->maxsize = length;
+ buf->oob = qtrue;
+}
+
+void MSG_Clear( msg_t *buf ) {
+ buf->cursize = 0;
+ buf->overflowed = qfalse;
+ buf->bit = 0; //<- in bits
+}
+
+
+void MSG_Bitstream( msg_t *buf ) {
+ buf->oob = qfalse;
+}
+
+void MSG_BeginReading( msg_t *msg ) {
+ msg->readcount = 0;
+ msg->bit = 0;
+ msg->oob = qfalse;
+}
+
+void MSG_BeginReadingOOB( msg_t *msg ) {
+ msg->readcount = 0;
+ msg->bit = 0;
+ msg->oob = qtrue;
+}
+
+void MSG_Copy(msg_t *buf, byte *data, int length, msg_t *src)
+{
+ if (length<src->cursize) {
+ Com_Error( ERR_DROP, "MSG_Copy: can't copy into a smaller msg_t buffer");
+ }
+ Com_Memcpy(buf, src, sizeof(msg_t));
+ buf->data = data;
+ Com_Memcpy(buf->data, src->data, src->cursize);
+}
+
+/*
+=============================================================================
+
+bit functions
+
+=============================================================================
+*/
+
+int overflows;
+
+// negative bit values include signs
+void MSG_WriteBits( msg_t *msg, int value, int bits ) {
+ int i;
+// FILE* fp;
+
+ oldsize += bits;
+
+ // this isn't an exact overflow check, but close enough
+ if ( msg->maxsize - msg->cursize < 4 ) {
+ msg->overflowed = qtrue;
+ return;
+ }
+
+ if ( bits == 0 || bits < -31 || bits > 32 ) {
+ Com_Error( ERR_DROP, "MSG_WriteBits: bad bits %i", bits );
+ }
+
+ // check for overflows
+ if ( bits != 32 ) {
+ if ( bits > 0 ) {
+ if ( value > ( ( 1 << bits ) - 1 ) || value < 0 ) {
+ overflows++;
+ }
+ } else {
+ int r;
+
+ r = 1 << (bits-1);
+
+ if ( value > r - 1 || value < -r ) {
+ overflows++;
+ }
+ }
+ }
+ if ( bits < 0 ) {
+ bits = -bits;
+ }
+ if (msg->oob) {
+ if (bits==8) {
+ msg->data[msg->cursize] = value;
+ msg->cursize += 1;
+ msg->bit += 8;
+ } else if (bits==16) {
+ unsigned short *sp = (unsigned short *)&msg->data[msg->cursize];
+ *sp = LittleShort(value);
+ msg->cursize += 2;
+ msg->bit += 16;
+ } else if (bits==32) {
+ unsigned int *ip = (unsigned int *)&msg->data[msg->cursize];
+ *ip = LittleLong(value);
+ msg->cursize += 4;
+ msg->bit += 32;
+ } else {
+ Com_Error(ERR_DROP, "can't read %d bits\n", bits);
+ }
+ } else {
+// fp = fopen("c:\\netchan.bin", "a");
+ value &= (0xffffffff>>(32-bits));
+ if (bits&7) {
+ int nbits;
+ nbits = bits&7;
+ for(i=0;i<nbits;i++) {
+ Huff_putBit((value&1), msg->data, &msg->bit);
+ value = (value>>1);
+ }
+ bits = bits - nbits;
+ }
+ if (bits) {
+ for(i=0;i<bits;i+=8) {
+// fwrite(bp, 1, 1, fp);
+ Huff_offsetTransmit (&msgHuff.compressor, (value&0xff), msg->data, &msg->bit);
+ value = (value>>8);
+ }
+ }
+ msg->cursize = (msg->bit>>3)+1;
+// fclose(fp);
+ }
+}
+
+int MSG_ReadBits( msg_t *msg, int bits ) {
+ int value;
+ int get;
+ qboolean sgn;
+ int i, nbits;
+// FILE* fp;
+
+ value = 0;
+
+ if ( bits < 0 ) {
+ bits = -bits;
+ sgn = qtrue;
+ } else {
+ sgn = qfalse;
+ }
+
+ if (msg->oob) {
+ if (bits==8) {
+ value = msg->data[msg->readcount];
+ msg->readcount += 1;
+ msg->bit += 8;
+ } else if (bits==16) {
+ unsigned short *sp = (unsigned short *)&msg->data[msg->readcount];
+ value = LittleShort(*sp);
+ msg->readcount += 2;
+ msg->bit += 16;
+ } else if (bits==32) {
+ unsigned int *ip = (unsigned int *)&msg->data[msg->readcount];
+ value = LittleLong(*ip);
+ msg->readcount += 4;
+ msg->bit += 32;
+ } else {
+ Com_Error(ERR_DROP, "can't read %d bits\n", bits);
+ }
+ } else {
+ nbits = 0;
+ if (bits&7) {
+ nbits = bits&7;
+ for(i=0;i<nbits;i++) {
+ value |= (Huff_getBit(msg->data, &msg->bit)<<i);
+ }
+ bits = bits - nbits;
+ }
+ if (bits) {
+// fp = fopen("c:\\netchan.bin", "a");
+ for(i=0;i<bits;i+=8) {
+ Huff_offsetReceive (msgHuff.decompressor.tree, &get, msg->data, &msg->bit);
+// fwrite(&get, 1, 1, fp);
+ value |= (get<<(i+nbits));
+ }
+// fclose(fp);
+ }
+ msg->readcount = (msg->bit>>3)+1;
+ }
+ if ( sgn ) {
+ if ( value & ( 1 << ( bits - 1 ) ) ) {
+ value |= -1 ^ ( ( 1 << bits ) - 1 );
+ }
+ }
+
+ return value;
+}
+
+
+
+//================================================================================
+
+//
+// writing functions
+//
+
+void MSG_WriteChar( msg_t *sb, int c ) {
+#ifdef PARANOID
+ if (c < -128 || c > 127)
+ Com_Error (ERR_FATAL, "MSG_WriteChar: range error");
+#endif
+
+ MSG_WriteBits( sb, c, 8 );
+}
+
+void MSG_WriteByte( msg_t *sb, int c ) {
+#ifdef PARANOID
+ if (c < 0 || c > 255)
+ Com_Error (ERR_FATAL, "MSG_WriteByte: range error");
+#endif
+
+ MSG_WriteBits( sb, c, 8 );
+}
+
+void MSG_WriteData( msg_t *buf, const void *data, int length ) {
+ int i;
+ for(i=0;i<length;i++) {
+ MSG_WriteByte(buf, ((byte *)data)[i]);
+ }
+}
+
+void MSG_WriteShort( msg_t *sb, int c ) {
+#ifdef PARANOID
+ if (c < ((short)0x8000) || c > (short)0x7fff)
+ Com_Error (ERR_FATAL, "MSG_WriteShort: range error");
+#endif
+
+ MSG_WriteBits( sb, c, 16 );
+}
+
+void MSG_WriteLong( msg_t *sb, int c ) {
+ MSG_WriteBits( sb, c, 32 );
+}
+
+void MSG_WriteFloat( msg_t *sb, float f ) {
+ union {
+ float f;
+ int l;
+ } dat;
+
+ dat.f = f;
+ MSG_WriteBits( sb, dat.l, 32 );
+}
+
+void MSG_WriteString( msg_t *sb, const char *s ) {
+ if ( !s ) {
+ MSG_WriteData (sb, "", 1);
+ } else {
+ int l,i;
+ char string[MAX_STRING_CHARS];
+
+ l = strlen( s );
+ if ( l >= MAX_STRING_CHARS ) {
+ Com_Printf( "MSG_WriteString: MAX_STRING_CHARS" );
+ MSG_WriteData (sb, "", 1);
+ return;
+ }
+ Q_strncpyz( string, s, sizeof( string ) );
+
+ // get rid of 0xff chars, because old clients don't like them
+ for ( i = 0 ; i < l ; i++ ) {
+ if ( ((byte *)string)[i] > 127 ) {
+ string[i] = '.';
+ }
+ }
+
+ MSG_WriteData (sb, string, l+1);
+ }
+}
+
+void MSG_WriteBigString( msg_t *sb, const char *s ) {
+ if ( !s ) {
+ MSG_WriteData (sb, "", 1);
+ } else {
+ int l,i;
+ char string[BIG_INFO_STRING];
+
+ l = strlen( s );
+ if ( l >= BIG_INFO_STRING ) {
+ Com_Printf( "MSG_WriteString: BIG_INFO_STRING" );
+ MSG_WriteData (sb, "", 1);
+ return;
+ }
+ Q_strncpyz( string, s, sizeof( string ) );
+
+ // get rid of 0xff chars, because old clients don't like them
+ for ( i = 0 ; i < l ; i++ ) {
+ if ( ((byte *)string)[i] > 127 ) {
+ string[i] = '.';
+ }
+ }
+
+ MSG_WriteData (sb, string, l+1);
+ }
+}
+
+void MSG_WriteAngle( msg_t *sb, float f ) {
+ MSG_WriteByte (sb, (int)(f*256/360) & 255);
+}
+
+void MSG_WriteAngle16( msg_t *sb, float f ) {
+ MSG_WriteShort (sb, ANGLE2SHORT(f));
+}
+
+
+//============================================================
+
+//
+// reading functions
+//
+
+// returns -1 if no more characters are available
+int MSG_ReadChar (msg_t *msg ) {
+ int c;
+
+ c = (signed char)MSG_ReadBits( msg, 8 );
+ if ( msg->readcount > msg->cursize ) {
+ c = -1;
+ }
+
+ return c;
+}
+
+int MSG_ReadByte( msg_t *msg ) {
+ int c;
+
+ c = (unsigned char)MSG_ReadBits( msg, 8 );
+ if ( msg->readcount > msg->cursize ) {
+ c = -1;
+ }
+ return c;
+}
+
+int MSG_ReadShort( msg_t *msg ) {
+ int c;
+
+ c = (short)MSG_ReadBits( msg, 16 );
+ if ( msg->readcount > msg->cursize ) {
+ c = -1;
+ }
+
+ return c;
+}
+
+int MSG_ReadLong( msg_t *msg ) {
+ int c;
+
+ c = MSG_ReadBits( msg, 32 );
+ if ( msg->readcount > msg->cursize ) {
+ c = -1;
+ }
+
+ return c;
+}
+
+float MSG_ReadFloat( msg_t *msg ) {
+ union {
+ byte b[4];
+ float f;
+ int l;
+ } dat;
+
+ dat.l = MSG_ReadBits( msg, 32 );
+ if ( msg->readcount > msg->cursize ) {
+ dat.f = -1;
+ }
+
+ return dat.f;
+}
+
+char *MSG_ReadString( msg_t *msg ) {
+ static char string[MAX_STRING_CHARS];
+ int l,c;
+
+ l = 0;
+ do {
+ c = MSG_ReadByte(msg); // use ReadByte so -1 is out of bounds
+ if ( c == -1 || c == 0 ) {
+ break;
+ }
+ // translate all fmt spec to avoid crash bugs
+ if ( c == '%' ) {
+ c = '.';
+ }
+ // don't allow higher ascii values
+ if ( c > 127 ) {
+ c = '.';
+ }
+
+ string[l] = c;
+ l++;
+ } while (l < sizeof(string)-1);
+
+ string[l] = 0;
+
+ return string;
+}
+
+char *MSG_ReadBigString( msg_t *msg ) {
+ static char string[BIG_INFO_STRING];
+ int l,c;
+
+ l = 0;
+ do {
+ c = MSG_ReadByte(msg); // use ReadByte so -1 is out of bounds
+ if ( c == -1 || c == 0 ) {
+ break;
+ }
+ // translate all fmt spec to avoid crash bugs
+ if ( c == '%' ) {
+ c = '.';
+ }
+ // don't allow higher ascii values
+ if ( c > 127 ) {
+ c = '.';
+ }
+
+ string[l] = c;
+ l++;
+ } while (l < sizeof(string)-1);
+
+ string[l] = 0;
+
+ return string;
+}
+
+char *MSG_ReadStringLine( msg_t *msg ) {
+ static char string[MAX_STRING_CHARS];
+ int l,c;
+
+ l = 0;
+ do {
+ c = MSG_ReadByte(msg); // use ReadByte so -1 is out of bounds
+ if (c == -1 || c == 0 || c == '\n') {
+ break;
+ }
+ // translate all fmt spec to avoid crash bugs
+ if ( c == '%' ) {
+ c = '.';
+ }
+ // don't allow higher ascii values
+ if ( c > 127 ) {
+ c = '.';
+ }
+
+ string[l] = c;
+ l++;
+ } while (l < sizeof(string)-1);
+
+ string[l] = 0;
+
+ return string;
+}
+
+float MSG_ReadAngle16( msg_t *msg ) {
+ return SHORT2ANGLE(MSG_ReadShort(msg));
+}
+
+void MSG_ReadData( msg_t *msg, void *data, int len ) {
+ int i;
+
+ for (i=0 ; i<len ; i++) {
+ ((byte *)data)[i] = MSG_ReadByte (msg);
+ }
+}
+
+
+/*
+=============================================================================
+
+delta functions
+
+=============================================================================
+*/
+
+extern cvar_t *cl_shownet;
+
+#define LOG(x) if( cl_shownet->integer == 4 ) { Com_Printf("%s ", x ); };
+
+void MSG_WriteDelta( msg_t *msg, int oldV, int newV, int bits ) {
+ if ( oldV == newV ) {
+ MSG_WriteBits( msg, 0, 1 );
+ return;
+ }
+ MSG_WriteBits( msg, 1, 1 );
+ MSG_WriteBits( msg, newV, bits );
+}
+
+int MSG_ReadDelta( msg_t *msg, int oldV, int bits ) {
+ if ( MSG_ReadBits( msg, 1 ) ) {
+ return MSG_ReadBits( msg, bits );
+ }
+ return oldV;
+}
+
+void MSG_WriteDeltaFloat( msg_t *msg, float oldV, float newV ) {
+ if ( oldV == newV ) {
+ MSG_WriteBits( msg, 0, 1 );
+ return;
+ }
+ MSG_WriteBits( msg, 1, 1 );
+ MSG_WriteBits( msg, *(int *)&newV, 32 );
+}
+
+float MSG_ReadDeltaFloat( msg_t *msg, float oldV ) {
+ if ( MSG_ReadBits( msg, 1 ) ) {
+ float newV;
+
+ *(int *)&newV = MSG_ReadBits( msg, 32 );
+ return newV;
+ }
+ return oldV;
+}
+
+/*
+=============================================================================
+
+delta functions with keys
+
+=============================================================================
+*/
+
+int kbitmask[32] = {
+ 0x00000001, 0x00000003, 0x00000007, 0x0000000F,
+ 0x0000001F, 0x0000003F, 0x0000007F, 0x000000FF,
+ 0x000001FF, 0x000003FF, 0x000007FF, 0x00000FFF,
+ 0x00001FFF, 0x00003FFF, 0x00007FFF, 0x0000FFFF,
+ 0x0001FFFF, 0x0003FFFF, 0x0007FFFF, 0x000FFFFF,
+ 0x001FFFFf, 0x003FFFFF, 0x007FFFFF, 0x00FFFFFF,
+ 0x01FFFFFF, 0x03FFFFFF, 0x07FFFFFF, 0x0FFFFFFF,
+ 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF,
+};
+
+void MSG_WriteDeltaKey( msg_t *msg, int key, int oldV, int newV, int bits ) {
+ if ( oldV == newV ) {
+ MSG_WriteBits( msg, 0, 1 );
+ return;
+ }
+ MSG_WriteBits( msg, 1, 1 );
+ MSG_WriteBits( msg, newV ^ key, bits );
+}
+
+int MSG_ReadDeltaKey( msg_t *msg, int key, int oldV, int bits ) {
+ if ( MSG_ReadBits( msg, 1 ) ) {
+ return MSG_ReadBits( msg, bits ) ^ (key & kbitmask[bits]);
+ }
+ return oldV;
+}
+
+void MSG_WriteDeltaKeyFloat( msg_t *msg, int key, float oldV, float newV ) {
+ if ( oldV == newV ) {
+ MSG_WriteBits( msg, 0, 1 );
+ return;
+ }
+ MSG_WriteBits( msg, 1, 1 );
+ MSG_WriteBits( msg, (*(int *)&newV) ^ key, 32 );
+}
+
+float MSG_ReadDeltaKeyFloat( msg_t *msg, int key, float oldV ) {
+ if ( MSG_ReadBits( msg, 1 ) ) {
+ float newV;
+
+ *(int *)&newV = MSG_ReadBits( msg, 32 ) ^ key;
+ return newV;
+ }
+ return oldV;
+}
+
+
+/*
+============================================================================
+
+usercmd_t communication
+
+============================================================================
+*/
+
+// ms is allways sent, the others are optional
+#define CM_ANGLE1 (1<<0)
+#define CM_ANGLE2 (1<<1)
+#define CM_ANGLE3 (1<<2)
+#define CM_FORWARD (1<<3)
+#define CM_SIDE (1<<4)
+#define CM_UP (1<<5)
+#define CM_BUTTONS (1<<6)
+#define CM_WEAPON (1<<7)
+
+/*
+=====================
+MSG_WriteDeltaUsercmd
+=====================
+*/
+void MSG_WriteDeltaUsercmd( msg_t *msg, usercmd_t *from, usercmd_t *to ) {
+ if ( to->serverTime - from->serverTime < 256 ) {
+ MSG_WriteBits( msg, 1, 1 );
+ MSG_WriteBits( msg, to->serverTime - from->serverTime, 8 );
+ } else {
+ MSG_WriteBits( msg, 0, 1 );
+ MSG_WriteBits( msg, to->serverTime, 32 );
+ }
+ MSG_WriteDelta( msg, from->angles[0], to->angles[0], 16 );
+ MSG_WriteDelta( msg, from->angles[1], to->angles[1], 16 );
+ MSG_WriteDelta( msg, from->angles[2], to->angles[2], 16 );
+ MSG_WriteDelta( msg, from->forwardmove, to->forwardmove, 8 );
+ MSG_WriteDelta( msg, from->rightmove, to->rightmove, 8 );
+ MSG_WriteDelta( msg, from->upmove, to->upmove, 8 );
+ MSG_WriteDelta( msg, from->buttons, to->buttons, 16 );
+ MSG_WriteDelta( msg, from->weapon, to->weapon, 8 );
+}
+
+
+/*
+=====================
+MSG_ReadDeltaUsercmd
+=====================
+*/
+void MSG_ReadDeltaUsercmd( msg_t *msg, usercmd_t *from, usercmd_t *to ) {
+ if ( MSG_ReadBits( msg, 1 ) ) {
+ to->serverTime = from->serverTime + MSG_ReadBits( msg, 8 );
+ } else {
+ to->serverTime = MSG_ReadBits( msg, 32 );
+ }
+ to->angles[0] = MSG_ReadDelta( msg, from->angles[0], 16);
+ to->angles[1] = MSG_ReadDelta( msg, from->angles[1], 16);
+ to->angles[2] = MSG_ReadDelta( msg, from->angles[2], 16);
+ to->forwardmove = MSG_ReadDelta( msg, from->forwardmove, 8);
+ to->rightmove = MSG_ReadDelta( msg, from->rightmove, 8);
+ to->upmove = MSG_ReadDelta( msg, from->upmove, 8);
+ to->buttons = MSG_ReadDelta( msg, from->buttons, 16);
+ to->weapon = MSG_ReadDelta( msg, from->weapon, 8);
+}
+
+/*
+=====================
+MSG_WriteDeltaUsercmd
+=====================
+*/
+void MSG_WriteDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *to ) {
+ if ( to->serverTime - from->serverTime < 256 ) {
+ MSG_WriteBits( msg, 1, 1 );
+ MSG_WriteBits( msg, to->serverTime - from->serverTime, 8 );
+ } else {
+ MSG_WriteBits( msg, 0, 1 );
+ MSG_WriteBits( msg, to->serverTime, 32 );
+ }
+ if (from->angles[0] == to->angles[0] &&
+ from->angles[1] == to->angles[1] &&
+ from->angles[2] == to->angles[2] &&
+ from->forwardmove == to->forwardmove &&
+ from->rightmove == to->rightmove &&
+ from->upmove == to->upmove &&
+ from->buttons == to->buttons &&
+ from->weapon == to->weapon) {
+ MSG_WriteBits( msg, 0, 1 ); // no change
+ oldsize += 7;
+ return;
+ }
+ key ^= to->serverTime;
+ MSG_WriteBits( msg, 1, 1 );
+ MSG_WriteDeltaKey( msg, key, from->angles[0], to->angles[0], 16 );
+ MSG_WriteDeltaKey( msg, key, from->angles[1], to->angles[1], 16 );
+ MSG_WriteDeltaKey( msg, key, from->angles[2], to->angles[2], 16 );
+ MSG_WriteDeltaKey( msg, key, from->forwardmove, to->forwardmove, 8 );
+ MSG_WriteDeltaKey( msg, key, from->rightmove, to->rightmove, 8 );
+ MSG_WriteDeltaKey( msg, key, from->upmove, to->upmove, 8 );
+ MSG_WriteDeltaKey( msg, key, from->buttons, to->buttons, 16 );
+ MSG_WriteDeltaKey( msg, key, from->weapon, to->weapon, 8 );
+}
+
+
+/*
+=====================
+MSG_ReadDeltaUsercmd
+=====================
+*/
+void MSG_ReadDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *to ) {
+ if ( MSG_ReadBits( msg, 1 ) ) {
+ to->serverTime = from->serverTime + MSG_ReadBits( msg, 8 );
+ } else {
+ to->serverTime = MSG_ReadBits( msg, 32 );
+ }
+ if ( MSG_ReadBits( msg, 1 ) ) {
+ key ^= to->serverTime;
+ to->angles[0] = MSG_ReadDeltaKey( msg, key, from->angles[0], 16);
+ to->angles[1] = MSG_ReadDeltaKey( msg, key, from->angles[1], 16);
+ to->angles[2] = MSG_ReadDeltaKey( msg, key, from->angles[2], 16);
+ to->forwardmove = MSG_ReadDeltaKey( msg, key, from->forwardmove, 8);
+ to->rightmove = MSG_ReadDeltaKey( msg, key, from->rightmove, 8);
+ to->upmove = MSG_ReadDeltaKey( msg, key, from->upmove, 8);
+ to->buttons = MSG_ReadDeltaKey( msg, key, from->buttons, 16);
+ to->weapon = MSG_ReadDeltaKey( msg, key, from->weapon, 8);
+ } else {
+ to->angles[0] = from->angles[0];
+ to->angles[1] = from->angles[1];
+ to->angles[2] = from->angles[2];
+ to->forwardmove = from->forwardmove;
+ to->rightmove = from->rightmove;
+ to->upmove = from->upmove;
+ to->buttons = from->buttons;
+ to->weapon = from->weapon;
+ }
+}
+
+/*
+=============================================================================
+
+entityState_t communication
+
+=============================================================================
+*/
+
+/*
+=================
+MSG_ReportChangeVectors_f
+
+Prints out a table from the current statistics for copying to code
+=================
+*/
+void MSG_ReportChangeVectors_f( void ) {
+ int i;
+ for(i=0;i<256;i++) {
+ if (pcount[i]) {
+ Com_Printf("%d used %d\n", i, pcount[i]);
+ }
+ }
+}
+
+typedef struct {
+ char *name;
+ int offset;
+ int bits; // 0 = float
+} netField_t;
+
+// using the stringizing operator to save typing...
+#define NETF(x) #x,(int)&((entityState_t*)0)->x
+
+netField_t entityStateFields[] =
+{
+{ NETF(pos.trTime), 32 },
+{ NETF(pos.trBase[0]), 0 },
+{ NETF(pos.trBase[1]), 0 },
+{ NETF(pos.trDelta[0]), 0 },
+{ NETF(pos.trDelta[1]), 0 },
+{ NETF(pos.trBase[2]), 0 },
+{ NETF(apos.trBase[1]), 0 },
+{ NETF(pos.trDelta[2]), 0 },
+{ NETF(apos.trBase[0]), 0 },
+{ NETF(event), 10 },
+{ NETF(angles2[1]), 0 },
+{ NETF(eType), 8 },
+{ NETF(torsoAnim), 8 },
+{ NETF(eventParm), 8 },
+{ NETF(legsAnim), 8 },
+{ NETF(groundEntityNum), GENTITYNUM_BITS },
+{ NETF(pos.trType), 8 },
+{ NETF(eFlags), 19 },
+{ NETF(otherEntityNum), GENTITYNUM_BITS },
+{ NETF(weapon), 8 },
+{ NETF(clientNum), 8 },
+{ NETF(angles[1]), 0 },
+{ NETF(pos.trDuration), 32 },
+{ NETF(apos.trType), 8 },
+{ NETF(origin[0]), 0 },
+{ NETF(origin[1]), 0 },
+{ NETF(origin[2]), 0 },
+{ NETF(solid), 24 },
+{ NETF(powerups), MAX_POWERUPS },
+{ NETF(modelindex), 8 },
+{ NETF(otherEntityNum2), GENTITYNUM_BITS },
+{ NETF(loopSound), 8 },
+{ NETF(generic1), 8 },
+{ NETF(origin2[2]), 0 },
+{ NETF(origin2[0]), 0 },
+{ NETF(origin2[1]), 0 },
+{ NETF(modelindex2), 8 },
+{ NETF(angles[0]), 0 },
+{ NETF(time), 32 },
+{ NETF(apos.trTime), 32 },
+{ NETF(apos.trDuration), 32 },
+{ NETF(apos.trBase[2]), 0 },
+{ NETF(apos.trDelta[0]), 0 },
+{ NETF(apos.trDelta[1]), 0 },
+{ NETF(apos.trDelta[2]), 0 },
+{ NETF(time2), 32 },
+{ NETF(angles[2]), 0 },
+{ NETF(angles2[0]), 0 },
+{ NETF(angles2[2]), 0 },
+{ NETF(constantLight), 32 },
+{ NETF(frame), 16 }
+};
+
+
+// if (int)f == f and (int)f + ( 1<<(FLOAT_INT_BITS-1) ) < ( 1 << FLOAT_INT_BITS )
+// the float will be sent with FLOAT_INT_BITS, otherwise all 32 bits will be sent
+#define FLOAT_INT_BITS 13
+#define FLOAT_INT_BIAS (1<<(FLOAT_INT_BITS-1))
+
+/*
+==================
+MSG_WriteDeltaEntity
+
+Writes part of a packetentities message, including the entity number.
+Can delta from either a baseline or a previous packet_entity
+If to is NULL, a remove entity update will be sent
+If force is not set, then nothing at all will be generated if the entity is
+identical, under the assumption that the in-order delta code will catch it.
+==================
+*/
+void MSG_WriteDeltaEntity( msg_t *msg, struct entityState_s *from, struct entityState_s *to,
+ qboolean force ) {
+ int i, lc;
+ int numFields;
+ netField_t *field;
+ int trunc;
+ float fullFloat;
+ int *fromF, *toF;
+
+ numFields = sizeof(entityStateFields)/sizeof(entityStateFields[0]);
+
+ // all fields should be 32 bits to avoid any compiler packing issues
+ // the "number" field is not part of the field list
+ // if this assert fails, someone added a field to the entityState_t
+ // struct without updating the message fields
+ assert( numFields + 1 == sizeof( *from )/4 );
+
+ // a NULL to is a delta remove message
+ if ( to == NULL ) {
+ if ( from == NULL ) {
+ return;
+ }
+ MSG_WriteBits( msg, from->number, GENTITYNUM_BITS );
+ MSG_WriteBits( msg, 1, 1 );
+ return;
+ }
+
+ if ( to->number < 0 || to->number >= MAX_GENTITIES ) {
+ Com_Error (ERR_FATAL, "MSG_WriteDeltaEntity: Bad entity number: %i", to->number );
+ }
+
+ lc = 0;
+ // build the change vector as bytes so it is endien independent
+ for ( i = 0, field = entityStateFields ; i < numFields ; i++, field++ ) {
+ fromF = (int *)( (byte *)from + field->offset );
+ toF = (int *)( (byte *)to + field->offset );
+ if ( *fromF != *toF ) {
+ lc = i+1;
+ }
+ }
+
+ if ( lc == 0 ) {
+ // nothing at all changed
+ if ( !force ) {
+ return; // nothing at all
+ }
+ // write two bits for no change
+ MSG_WriteBits( msg, to->number, GENTITYNUM_BITS );
+ MSG_WriteBits( msg, 0, 1 ); // not removed
+ MSG_WriteBits( msg, 0, 1 ); // no delta
+ return;
+ }
+
+ MSG_WriteBits( msg, to->number, GENTITYNUM_BITS );
+ MSG_WriteBits( msg, 0, 1 ); // not removed
+ MSG_WriteBits( msg, 1, 1 ); // we have a delta
+
+ MSG_WriteByte( msg, lc ); // # of changes
+
+ oldsize += numFields;
+
+ for ( i = 0, field = entityStateFields ; i < lc ; i++, field++ ) {
+ fromF = (int *)( (byte *)from + field->offset );
+ toF = (int *)( (byte *)to + field->offset );
+
+ if ( *fromF == *toF ) {
+ MSG_WriteBits( msg, 0, 1 ); // no change
+ continue;
+ }
+
+ MSG_WriteBits( msg, 1, 1 ); // changed
+
+ if ( field->bits == 0 ) {
+ // float
+ fullFloat = *(float *)toF;
+ trunc = (int)fullFloat;
+
+ if (fullFloat == 0.0f) {
+ MSG_WriteBits( msg, 0, 1 );
+ oldsize += FLOAT_INT_BITS;
+ } else {
+ MSG_WriteBits( msg, 1, 1 );
+ if ( trunc == fullFloat && trunc + FLOAT_INT_BIAS >= 0 &&
+ trunc + FLOAT_INT_BIAS < ( 1 << FLOAT_INT_BITS ) ) {
+ // send as small integer
+ MSG_WriteBits( msg, 0, 1 );
+ MSG_WriteBits( msg, trunc + FLOAT_INT_BIAS, FLOAT_INT_BITS );
+ } else {
+ // send as full floating point value
+ MSG_WriteBits( msg, 1, 1 );
+ MSG_WriteBits( msg, *toF, 32 );
+ }
+ }
+ } else {
+ if (*toF == 0) {
+ MSG_WriteBits( msg, 0, 1 );
+ } else {
+ MSG_WriteBits( msg, 1, 1 );
+ // integer
+ MSG_WriteBits( msg, *toF, field->bits );
+ }
+ }
+ }
+}
+
+/*
+==================
+MSG_ReadDeltaEntity
+
+The entity number has already been read from the message, which
+is how the from state is identified.
+
+If the delta removes the entity, entityState_t->number will be set to MAX_GENTITIES-1
+
+Can go from either a baseline or a previous packet_entity
+==================
+*/
+extern cvar_t *cl_shownet;
+
+void MSG_ReadDeltaEntity( msg_t *msg, entityState_t *from, entityState_t *to,
+ int number) {
+ int i, lc;
+ int numFields;
+ netField_t *field;
+ int *fromF, *toF;
+ int print;
+ int trunc;
+ int startBit, endBit;
+
+ if ( number < 0 || number >= MAX_GENTITIES) {
+ Com_Error( ERR_DROP, "Bad delta entity number: %i", number );
+ }
+
+ if ( msg->bit == 0 ) {
+ startBit = msg->readcount * 8 - GENTITYNUM_BITS;
+ } else {
+ startBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS;
+ }
+
+ // check for a remove
+ if ( MSG_ReadBits( msg, 1 ) == 1 ) {
+ Com_Memset( to, 0, sizeof( *to ) );
+ to->number = MAX_GENTITIES - 1;
+ if ( cl_shownet->integer >= 2 || cl_shownet->integer == -1 ) {
+ Com_Printf( "%3i: #%-3i remove\n", msg->readcount, number );
+ }
+ return;
+ }
+
+ // check for no delta
+ if ( MSG_ReadBits( msg, 1 ) == 0 ) {
+ *to = *from;
+ to->number = number;
+ return;
+ }
+
+ numFields = sizeof(entityStateFields)/sizeof(entityStateFields[0]);
+ lc = MSG_ReadByte(msg);
+
+ // shownet 2/3 will interleave with other printed info, -1 will
+ // just print the delta records`
+ if ( cl_shownet->integer >= 2 || cl_shownet->integer == -1 ) {
+ print = 1;
+ Com_Printf( "%3i: #%-3i ", msg->readcount, to->number );
+ } else {
+ print = 0;
+ }
+
+ to->number = number;
+
+ for ( i = 0, field = entityStateFields ; i < lc ; i++, field++ ) {
+ fromF = (int *)( (byte *)from + field->offset );
+ toF = (int *)( (byte *)to + field->offset );
+
+ if ( ! MSG_ReadBits( msg, 1 ) ) {
+ // no change
+ *toF = *fromF;
+ } else {
+ if ( field->bits == 0 ) {
+ // float
+ if ( MSG_ReadBits( msg, 1 ) == 0 ) {
+ *(float *)toF = 0.0f;
+ } else {
+ if ( MSG_ReadBits( msg, 1 ) == 0 ) {
+ // integral float
+ trunc = MSG_ReadBits( msg, FLOAT_INT_BITS );
+ // bias to allow equal parts positive and negative
+ trunc -= FLOAT_INT_BIAS;
+ *(float *)toF = trunc;
+ if ( print ) {
+ Com_Printf( "%s:%i ", field->name, trunc );
+ }
+ } else {
+ // full floating point value
+ *toF = MSG_ReadBits( msg, 32 );
+ if ( print ) {
+ Com_Printf( "%s:%f ", field->name, *(float *)toF );
+ }
+ }
+ }
+ } else {
+ if ( MSG_ReadBits( msg, 1 ) == 0 ) {
+ *toF = 0;
+ } else {
+ // integer
+ *toF = MSG_ReadBits( msg, field->bits );
+ if ( print ) {
+ Com_Printf( "%s:%i ", field->name, *toF );
+ }
+ }
+ }
+// pcount[i]++;
+ }
+ }
+ for ( i = lc, field = &entityStateFields[lc] ; i < numFields ; i++, field++ ) {
+ fromF = (int *)( (byte *)from + field->offset );
+ toF = (int *)( (byte *)to + field->offset );
+ // no change
+ *toF = *fromF;
+ }
+
+ if ( print ) {
+ if ( msg->bit == 0 ) {
+ endBit = msg->readcount * 8 - GENTITYNUM_BITS;
+ } else {
+ endBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS;
+ }
+ Com_Printf( " (%i bits)\n", endBit - startBit );
+ }
+}
+
+
+/*
+============================================================================
+
+plyer_state_t communication
+
+============================================================================
+*/
+
+// using the stringizing operator to save typing...
+#define PSF(x) #x,(int)&((playerState_t*)0)->x
+
+netField_t playerStateFields[] =
+{
+{ PSF(commandTime), 32 },
+{ PSF(origin[0]), 0 },
+{ PSF(origin[1]), 0 },
+{ PSF(bobCycle), 8 },
+{ PSF(velocity[0]), 0 },
+{ PSF(velocity[1]), 0 },
+{ PSF(viewangles[1]), 0 },
+{ PSF(viewangles[0]), 0 },
+{ PSF(weaponTime), -16 },
+{ PSF(origin[2]), 0 },
+{ PSF(velocity[2]), 0 },
+{ PSF(legsTimer), 8 },
+{ PSF(pm_time), -16 },
+{ PSF(eventSequence), 16 },
+{ PSF(torsoAnim), 8 },
+{ PSF(movementDir), 4 },
+{ PSF(events[0]), 8 },
+{ PSF(legsAnim), 8 },
+{ PSF(events[1]), 8 },
+{ PSF(pm_flags), 16 },
+{ PSF(groundEntityNum), GENTITYNUM_BITS },
+{ PSF(weaponstate), 4 },
+{ PSF(eFlags), 16 },
+{ PSF(externalEvent), 10 },
+{ PSF(gravity), 16 },
+{ PSF(speed), 16 },
+{ PSF(delta_angles[1]), 16 },
+{ PSF(externalEventParm), 8 },
+{ PSF(viewheight), -8 },
+{ PSF(damageEvent), 8 },
+{ PSF(damageYaw), 8 },
+{ PSF(damagePitch), 8 },
+{ PSF(damageCount), 8 },
+{ PSF(generic1), 8 },
+{ PSF(pm_type), 8 },
+{ PSF(delta_angles[0]), 16 },
+{ PSF(delta_angles[2]), 16 },
+{ PSF(torsoTimer), 12 },
+{ PSF(eventParms[0]), 8 },
+{ PSF(eventParms[1]), 8 },
+{ PSF(clientNum), 8 },
+{ PSF(weapon), 5 },
+{ PSF(viewangles[2]), 0 },
+{ PSF(grapplePoint[0]), 0 },
+{ PSF(grapplePoint[1]), 0 },
+{ PSF(grapplePoint[2]), 0 },
+{ PSF(otherEntityNum), 10 },
+{ PSF(loopSound), 16 }
+};
+
+/*
+=============
+MSG_WriteDeltaPlayerstate
+
+=============
+*/
+void MSG_WriteDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to ) {
+ int i;
+ playerState_t dummy;
+ int statsbits;
+ int persistantbits;
+ int ammobits;
+ int powerupbits;
+ int numFields;
+ int c;
+ netField_t *field;
+ int *fromF, *toF;
+ float fullFloat;
+ int trunc, lc;
+
+ if (!from) {
+ from = &dummy;
+ Com_Memset (&dummy, 0, sizeof(dummy));
+ }
+
+ c = msg->cursize;
+
+ numFields = sizeof( playerStateFields ) / sizeof( playerStateFields[0] );
+
+ lc = 0;
+ for ( i = 0, field = playerStateFields ; i < numFields ; i++, field++ ) {
+ fromF = (int *)( (byte *)from + field->offset );
+ toF = (int *)( (byte *)to + field->offset );
+ if ( *fromF != *toF ) {
+ lc = i+1;
+ }
+ }
+
+ MSG_WriteByte( msg, lc ); // # of changes
+
+ oldsize += numFields - lc;
+
+ for ( i = 0, field = playerStateFields ; i < lc ; i++, field++ ) {
+ fromF = (int *)( (byte *)from + field->offset );
+ toF = (int *)( (byte *)to + field->offset );
+
+ if ( *fromF == *toF ) {
+ MSG_WriteBits( msg, 0, 1 ); // no change
+ continue;
+ }
+
+ MSG_WriteBits( msg, 1, 1 ); // changed
+// pcount[i]++;
+
+ if ( field->bits == 0 ) {
+ // float
+ fullFloat = *(float *)toF;
+ trunc = (int)fullFloat;
+
+ if ( trunc == fullFloat && trunc + FLOAT_INT_BIAS >= 0 &&
+ trunc + FLOAT_INT_BIAS < ( 1 << FLOAT_INT_BITS ) ) {
+ // send as small integer
+ MSG_WriteBits( msg, 0, 1 );
+ MSG_WriteBits( msg, trunc + FLOAT_INT_BIAS, FLOAT_INT_BITS );
+ } else {
+ // send as full floating point value
+ MSG_WriteBits( msg, 1, 1 );
+ MSG_WriteBits( msg, *toF, 32 );
+ }
+ } else {
+ // integer
+ MSG_WriteBits( msg, *toF, field->bits );
+ }
+ }
+ c = msg->cursize - c;
+
+
+ //
+ // send the arrays
+ //
+ statsbits = 0;
+ for (i=0 ; i<MAX_STATS ; i++) {
+ if (to->stats[i] != from->stats[i]) {
+ statsbits |= 1<<i;
+ }
+ }
+ persistantbits = 0;
+ for (i=0 ; i<MAX_PERSISTANT ; i++) {
+ if (to->persistant[i] != from->persistant[i]) {
+ persistantbits |= 1<<i;
+ }
+ }
+ ammobits = 0;
+ for (i=0 ; i<MAX_WEAPONS ; i++) {
+ if (to->ammo[i] != from->ammo[i]) {
+ ammobits |= 1<<i;
+ }
+ }
+ powerupbits = 0;
+ for (i=0 ; i<MAX_POWERUPS ; i++) {
+ if (to->powerups[i] != from->powerups[i]) {
+ powerupbits |= 1<<i;
+ }
+ }
+
+ if (!statsbits && !persistantbits && !ammobits && !powerupbits) {
+ MSG_WriteBits( msg, 0, 1 ); // no change
+ oldsize += 4;
+ return;
+ }
+ MSG_WriteBits( msg, 1, 1 ); // changed
+
+ if ( statsbits ) {
+ MSG_WriteBits( msg, 1, 1 ); // changed
+ MSG_WriteBits( msg, statsbits, MAX_STATS );
+ for (i=0 ; i<MAX_STATS ; i++)
+ if (statsbits & (1<<i) )
+ MSG_WriteShort (msg, to->stats[i]);
+ } else {
+ MSG_WriteBits( msg, 0, 1 ); // no change
+ }
+
+
+ if ( persistantbits ) {
+ MSG_WriteBits( msg, 1, 1 ); // changed
+ MSG_WriteBits( msg, persistantbits, MAX_PERSISTANT );
+ for (i=0 ; i<MAX_PERSISTANT ; i++)
+ if (persistantbits & (1<<i) )
+ MSG_WriteShort (msg, to->persistant[i]);
+ } else {
+ MSG_WriteBits( msg, 0, 1 ); // no change
+ }
+
+
+ if ( ammobits ) {
+ MSG_WriteBits( msg, 1, 1 ); // changed
+ MSG_WriteBits( msg, ammobits, MAX_WEAPONS );
+ for (i=0 ; i<MAX_WEAPONS ; i++)
+ if (ammobits & (1<<i) )
+ MSG_WriteShort (msg, to->ammo[i]);
+ } else {
+ MSG_WriteBits( msg, 0, 1 ); // no change
+ }
+
+
+ if ( powerupbits ) {
+ MSG_WriteBits( msg, 1, 1 ); // changed
+ MSG_WriteBits( msg, powerupbits, MAX_POWERUPS );
+ for (i=0 ; i<MAX_POWERUPS ; i++)
+ if (powerupbits & (1<<i) )
+ MSG_WriteLong( msg, to->powerups[i] );
+ } else {
+ MSG_WriteBits( msg, 0, 1 ); // no change
+ }
+}
+
+
+/*
+===================
+MSG_ReadDeltaPlayerstate
+===================
+*/
+void MSG_ReadDeltaPlayerstate (msg_t *msg, playerState_t *from, playerState_t *to ) {
+ int i, lc;
+ int bits;
+ netField_t *field;
+ int numFields;
+ int startBit, endBit;
+ int print;
+ int *fromF, *toF;
+ int trunc;
+ playerState_t dummy;
+
+ if ( !from ) {
+ from = &dummy;
+ Com_Memset( &dummy, 0, sizeof( dummy ) );
+ }
+ *to = *from;
+
+ if ( msg->bit == 0 ) {
+ startBit = msg->readcount * 8 - GENTITYNUM_BITS;
+ } else {
+ startBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS;
+ }
+
+ // shownet 2/3 will interleave with other printed info, -2 will
+ // just print the delta records
+ if ( cl_shownet->integer >= 2 || cl_shownet->integer == -2 ) {
+ print = 1;
+ Com_Printf( "%3i: playerstate ", msg->readcount );
+ } else {
+ print = 0;
+ }
+
+ numFields = sizeof( playerStateFields ) / sizeof( playerStateFields[0] );
+ lc = MSG_ReadByte(msg);
+
+ for ( i = 0, field = playerStateFields ; i < lc ; i++, field++ ) {
+ fromF = (int *)( (byte *)from + field->offset );
+ toF = (int *)( (byte *)to + field->offset );
+
+ if ( ! MSG_ReadBits( msg, 1 ) ) {
+ // no change
+ *toF = *fromF;
+ } else {
+ if ( field->bits == 0 ) {
+ // float
+ if ( MSG_ReadBits( msg, 1 ) == 0 ) {
+ // integral float
+ trunc = MSG_ReadBits( msg, FLOAT_INT_BITS );
+ // bias to allow equal parts positive and negative
+ trunc -= FLOAT_INT_BIAS;
+ *(float *)toF = trunc;
+ if ( print ) {
+ Com_Printf( "%s:%i ", field->name, trunc );
+ }
+ } else {
+ // full floating point value
+ *toF = MSG_ReadBits( msg, 32 );
+ if ( print ) {
+ Com_Printf( "%s:%f ", field->name, *(float *)toF );
+ }
+ }
+ } else {
+ // integer
+ *toF = MSG_ReadBits( msg, field->bits );
+ if ( print ) {
+ Com_Printf( "%s:%i ", field->name, *toF );
+ }
+ }
+ }
+ }
+ for ( i=lc,field = &playerStateFields[lc];i<numFields; i++, field++) {
+ fromF = (int *)( (byte *)from + field->offset );
+ toF = (int *)( (byte *)to + field->offset );
+ // no change
+ *toF = *fromF;
+ }
+
+
+ // read the arrays
+ if (MSG_ReadBits( msg, 1 ) ) {
+ // parse stats
+ if ( MSG_ReadBits( msg, 1 ) ) {
+ LOG("PS_STATS");
+ bits = MSG_ReadBits (msg, MAX_STATS);
+ for (i=0 ; i<MAX_STATS ; i++) {
+ if (bits & (1<<i) ) {
+ to->stats[i] = MSG_ReadShort(msg);
+ }
+ }
+ }
+
+ // parse persistant stats
+ if ( MSG_ReadBits( msg, 1 ) ) {
+ LOG("PS_PERSISTANT");
+ bits = MSG_ReadBits (msg, MAX_PERSISTANT);
+ for (i=0 ; i<MAX_PERSISTANT ; i++) {
+ if (bits & (1<<i) ) {
+ to->persistant[i] = MSG_ReadShort(msg);
+ }
+ }
+ }
+
+ // parse ammo
+ if ( MSG_ReadBits( msg, 1 ) ) {
+ LOG("PS_AMMO");
+ bits = MSG_ReadBits (msg, MAX_WEAPONS);
+ for (i=0 ; i<MAX_WEAPONS ; i++) {
+ if (bits & (1<<i) ) {
+ to->ammo[i] = MSG_ReadShort(msg);
+ }
+ }
+ }
+
+ // parse powerups
+ if ( MSG_ReadBits( msg, 1 ) ) {
+ LOG("PS_POWERUPS");
+ bits = MSG_ReadBits (msg, MAX_POWERUPS);
+ for (i=0 ; i<MAX_POWERUPS ; i++) {
+ if (bits & (1<<i) ) {
+ to->powerups[i] = MSG_ReadLong(msg);
+ }
+ }
+ }
+ }
+
+ if ( print ) {
+ if ( msg->bit == 0 ) {
+ endBit = msg->readcount * 8 - GENTITYNUM_BITS;
+ } else {
+ endBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS;
+ }
+ Com_Printf( " (%i bits)\n", endBit - startBit );
+ }
+}
+
+int msg_hData[256] = {
+250315, // 0
+41193, // 1
+6292, // 2
+7106, // 3
+3730, // 4
+3750, // 5
+6110, // 6
+23283, // 7
+33317, // 8
+6950, // 9
+7838, // 10
+9714, // 11
+9257, // 12
+17259, // 13
+3949, // 14
+1778, // 15
+8288, // 16
+1604, // 17
+1590, // 18
+1663, // 19
+1100, // 20
+1213, // 21
+1238, // 22
+1134, // 23
+1749, // 24
+1059, // 25
+1246, // 26
+1149, // 27
+1273, // 28
+4486, // 29
+2805, // 30
+3472, // 31
+21819, // 32
+1159, // 33
+1670, // 34
+1066, // 35
+1043, // 36
+1012, // 37
+1053, // 38
+1070, // 39
+1726, // 40
+888, // 41
+1180, // 42
+850, // 43
+960, // 44
+780, // 45
+1752, // 46
+3296, // 47
+10630, // 48
+4514, // 49
+5881, // 50
+2685, // 51
+4650, // 52
+3837, // 53
+2093, // 54
+1867, // 55
+2584, // 56
+1949, // 57
+1972, // 58
+940, // 59
+1134, // 60
+1788, // 61
+1670, // 62
+1206, // 63
+5719, // 64
+6128, // 65
+7222, // 66
+6654, // 67
+3710, // 68
+3795, // 69
+1492, // 70
+1524, // 71
+2215, // 72
+1140, // 73
+1355, // 74
+971, // 75
+2180, // 76
+1248, // 77
+1328, // 78
+1195, // 79
+1770, // 80
+1078, // 81
+1264, // 82
+1266, // 83
+1168, // 84
+965, // 85
+1155, // 86
+1186, // 87
+1347, // 88
+1228, // 89
+1529, // 90
+1600, // 91
+2617, // 92
+2048, // 93
+2546, // 94
+3275, // 95
+2410, // 96
+3585, // 97
+2504, // 98
+2800, // 99
+2675, // 100
+6146, // 101
+3663, // 102
+2840, // 103
+14253, // 104
+3164, // 105
+2221, // 106
+1687, // 107
+3208, // 108
+2739, // 109
+3512, // 110
+4796, // 111
+4091, // 112
+3515, // 113
+5288, // 114
+4016, // 115
+7937, // 116
+6031, // 117
+5360, // 118
+3924, // 119
+4892, // 120
+3743, // 121
+4566, // 122
+4807, // 123
+5852, // 124
+6400, // 125
+6225, // 126
+8291, // 127
+23243, // 128
+7838, // 129
+7073, // 130
+8935, // 131
+5437, // 132
+4483, // 133
+3641, // 134
+5256, // 135
+5312, // 136
+5328, // 137
+5370, // 138
+3492, // 139
+2458, // 140
+1694, // 141
+1821, // 142
+2121, // 143
+1916, // 144
+1149, // 145
+1516, // 146
+1367, // 147
+1236, // 148
+1029, // 149
+1258, // 150
+1104, // 151
+1245, // 152
+1006, // 153
+1149, // 154
+1025, // 155
+1241, // 156
+952, // 157
+1287, // 158
+997, // 159
+1713, // 160
+1009, // 161
+1187, // 162
+879, // 163
+1099, // 164
+929, // 165
+1078, // 166
+951, // 167
+1656, // 168
+930, // 169
+1153, // 170
+1030, // 171
+1262, // 172
+1062, // 173
+1214, // 174
+1060, // 175
+1621, // 176
+930, // 177
+1106, // 178
+912, // 179
+1034, // 180
+892, // 181
+1158, // 182
+990, // 183
+1175, // 184
+850, // 185
+1121, // 186
+903, // 187
+1087, // 188
+920, // 189
+1144, // 190
+1056, // 191
+3462, // 192
+2240, // 193
+4397, // 194
+12136, // 195
+7758, // 196
+1345, // 197
+1307, // 198
+3278, // 199
+1950, // 200
+886, // 201
+1023, // 202
+1112, // 203
+1077, // 204
+1042, // 205
+1061, // 206
+1071, // 207
+1484, // 208
+1001, // 209
+1096, // 210
+915, // 211
+1052, // 212
+995, // 213
+1070, // 214
+876, // 215
+1111, // 216
+851, // 217
+1059, // 218
+805, // 219
+1112, // 220
+923, // 221
+1103, // 222
+817, // 223
+1899, // 224
+1872, // 225
+976, // 226
+841, // 227
+1127, // 228
+956, // 229
+1159, // 230
+950, // 231
+7791, // 232
+954, // 233
+1289, // 234
+933, // 235
+1127, // 236
+3207, // 237
+1020, // 238
+927, // 239
+1355, // 240
+768, // 241
+1040, // 242
+745, // 243
+952, // 244
+805, // 245
+1073, // 246
+740, // 247
+1013, // 248
+805, // 249
+1008, // 250
+796, // 251
+996, // 252
+1057, // 253
+11457, // 254
+13504, // 255
+};
+
+void MSG_initHuffman( void ) {
+ int i,j;
+
+ msgInit = qtrue;
+ Huff_Init(&msgHuff);
+ for(i=0;i<256;i++) {
+ for (j=0;j<msg_hData[i];j++) {
+ Huff_addRef(&msgHuff.compressor, (byte)i); // Do update
+ Huff_addRef(&msgHuff.decompressor, (byte)i); // Do update
+ }
+ }
+}
+
+/*
+void MSG_NUinitHuffman() {
+ byte *data;
+ int size, i, ch;
+ int array[256];
+
+ msgInit = qtrue;
+
+ Huff_Init(&msgHuff);
+ // load it in
+ size = FS_ReadFile( "netchan/netchan.bin", (void **)&data );
+
+ for(i=0;i<256;i++) {
+ array[i] = 0;
+ }
+ for(i=0;i<size;i++) {
+ ch = data[i];
+ Huff_addRef(&msgHuff.compressor, ch); // Do update
+ Huff_addRef(&msgHuff.decompressor, ch); // Do update
+ array[ch]++;
+ }
+ Com_Printf("msg_hData {\n");
+ for(i=0;i<256;i++) {
+ if (array[i] == 0) {
+ Huff_addRef(&msgHuff.compressor, i); // Do update
+ Huff_addRef(&msgHuff.decompressor, i); // Do update
+ }
+ Com_Printf("%d, // %d\n", array[i], i);
+ }
+ Com_Printf("};\n");
+ FS_FreeFile( data );
+ Cbuf_AddText( "condump dump.txt\n" );
+}
+*/
+
+//===========================================================================
diff --git a/src/qcommon/net_chan.c b/src/qcommon/net_chan.c
new file mode 100644
index 0000000..869d87a
--- /dev/null
+++ b/src/qcommon/net_chan.c
@@ -0,0 +1,801 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "q_shared.h"
+#include "qcommon.h"
+
+/*
+
+packet header
+-------------
+4 outgoing sequence. high bit will be set if this is a fragmented message
+[2 qport (only for client to server)]
+[2 fragment start byte]
+[2 fragment length. if < FRAGMENT_SIZE, this is the last fragment]
+
+if the sequence number is -1, the packet should be handled as an out-of-band
+message instead of as part of a netcon.
+
+All fragments will have the same sequence numbers.
+
+The qport field is a workaround for bad address translating routers that
+sometimes remap the client's source port on a packet during gameplay.
+
+If the base part of the net address matches and the qport matches, then the
+channel matches even if the IP port differs. The IP port should be updated
+to the new value before sending out any replies.
+
+*/
+
+
+#define MAX_PACKETLEN 1400 // max size of a network packet
+
+#define FRAGMENT_SIZE (MAX_PACKETLEN - 100)
+#define PACKET_HEADER 10 // two ints and a short
+
+#define FRAGMENT_BIT (1<<31)
+
+cvar_t *showpackets;
+cvar_t *showdrop;
+cvar_t *qport;
+
+static char *netsrcString[2] = {
+ "client",
+ "server"
+};
+
+/*
+===============
+Netchan_Init
+
+===============
+*/
+void Netchan_Init( int port ) {
+ port &= 0xffff;
+ showpackets = Cvar_Get ("showpackets", "0", CVAR_TEMP );
+ showdrop = Cvar_Get ("showdrop", "0", CVAR_TEMP );
+ qport = Cvar_Get ("net_qport", va("%i", port), CVAR_INIT );
+}
+
+/*
+==============
+Netchan_Setup
+
+called to open a channel to a remote system
+==============
+*/
+void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport ) {
+ Com_Memset (chan, 0, sizeof(*chan));
+
+ chan->sock = sock;
+ chan->remoteAddress = adr;
+ chan->qport = qport;
+ chan->incomingSequence = 0;
+ chan->outgoingSequence = 1;
+}
+
+// TTimo: unused, commenting out to make gcc happy
+#if 0
+/*
+==============
+Netchan_ScramblePacket
+
+A probably futile attempt to make proxy hacking somewhat
+more difficult.
+==============
+*/
+#define SCRAMBLE_START 6
+static void Netchan_ScramblePacket( msg_t *buf ) {
+ unsigned seed;
+ int i, j, c, mask, temp;
+ int seq[MAX_PACKETLEN];
+
+ seed = ( LittleLong( *(unsigned *)buf->data ) * 3 ) ^ ( buf->cursize * 123 );
+ c = buf->cursize;
+ if ( c <= SCRAMBLE_START ) {
+ return;
+ }
+ if ( c > MAX_PACKETLEN ) {
+ Com_Error( ERR_DROP, "MAX_PACKETLEN" );
+ }
+
+ // generate a sequence of "random" numbers
+ for (i = 0 ; i < c ; i++) {
+ seed = (119 * seed + 1);
+ seq[i] = seed;
+ }
+
+ // transpose each character
+ for ( mask = 1 ; mask < c-SCRAMBLE_START ; mask = ( mask << 1 ) + 1 ) {
+ }
+ mask >>= 1;
+ for (i = SCRAMBLE_START ; i < c ; i++) {
+ j = SCRAMBLE_START + ( seq[i] & mask );
+ temp = buf->data[j];
+ buf->data[j] = buf->data[i];
+ buf->data[i] = temp;
+ }
+
+ // byte xor the data after the header
+ for (i = SCRAMBLE_START ; i < c ; i++) {
+ buf->data[i] ^= seq[i];
+ }
+}
+
+static void Netchan_UnScramblePacket( msg_t *buf ) {
+ unsigned seed;
+ int i, j, c, mask, temp;
+ int seq[MAX_PACKETLEN];
+
+ seed = ( LittleLong( *(unsigned *)buf->data ) * 3 ) ^ ( buf->cursize * 123 );
+ c = buf->cursize;
+ if ( c <= SCRAMBLE_START ) {
+ return;
+ }
+ if ( c > MAX_PACKETLEN ) {
+ Com_Error( ERR_DROP, "MAX_PACKETLEN" );
+ }
+
+ // generate a sequence of "random" numbers
+ for (i = 0 ; i < c ; i++) {
+ seed = (119 * seed + 1);
+ seq[i] = seed;
+ }
+
+ // byte xor the data after the header
+ for (i = SCRAMBLE_START ; i < c ; i++) {
+ buf->data[i] ^= seq[i];
+ }
+
+ // transpose each character in reverse order
+ for ( mask = 1 ; mask < c-SCRAMBLE_START ; mask = ( mask << 1 ) + 1 ) {
+ }
+ mask >>= 1;
+ for (i = c-1 ; i >= SCRAMBLE_START ; i--) {
+ j = SCRAMBLE_START + ( seq[i] & mask );
+ temp = buf->data[j];
+ buf->data[j] = buf->data[i];
+ buf->data[i] = temp;
+ }
+}
+#endif
+
+/*
+=================
+Netchan_TransmitNextFragment
+
+Send one fragment of the current message
+=================
+*/
+void Netchan_TransmitNextFragment( netchan_t *chan ) {
+ msg_t send;
+ byte send_buf[MAX_PACKETLEN];
+ int fragmentLength;
+
+ // write the packet header
+ MSG_InitOOB (&send, send_buf, sizeof(send_buf)); // <-- only do the oob here
+
+ MSG_WriteLong( &send, chan->outgoingSequence | FRAGMENT_BIT );
+
+ // send the qport if we are a client
+ if ( chan->sock == NS_CLIENT ) {
+ MSG_WriteShort( &send, qport->integer );
+ }
+
+ // copy the reliable message to the packet first
+ fragmentLength = FRAGMENT_SIZE;
+ if ( chan->unsentFragmentStart + fragmentLength > chan->unsentLength ) {
+ fragmentLength = chan->unsentLength - chan->unsentFragmentStart;
+ }
+
+ MSG_WriteShort( &send, chan->unsentFragmentStart );
+ MSG_WriteShort( &send, fragmentLength );
+ MSG_WriteData( &send, chan->unsentBuffer + chan->unsentFragmentStart, fragmentLength );
+
+ // send the datagram
+ NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress );
+
+ if ( showpackets->integer ) {
+ Com_Printf ("%s send %4i : s=%i fragment=%i,%i\n"
+ , netsrcString[ chan->sock ]
+ , send.cursize
+ , chan->outgoingSequence
+ , chan->unsentFragmentStart, fragmentLength);
+ }
+
+ chan->unsentFragmentStart += fragmentLength;
+
+ // this exit condition is a little tricky, because a packet
+ // that is exactly the fragment length still needs to send
+ // a second packet of zero length so that the other side
+ // can tell there aren't more to follow
+ if ( chan->unsentFragmentStart == chan->unsentLength && fragmentLength != FRAGMENT_SIZE ) {
+ chan->outgoingSequence++;
+ chan->unsentFragments = qfalse;
+ }
+}
+
+
+/*
+===============
+Netchan_Transmit
+
+Sends a message to a connection, fragmenting if necessary
+A 0 length will still generate a packet.
+================
+*/
+void Netchan_Transmit( netchan_t *chan, int length, const byte *data ) {
+ msg_t send;
+ byte send_buf[MAX_PACKETLEN];
+
+ if ( length > MAX_MSGLEN ) {
+ Com_Error( ERR_DROP, "Netchan_Transmit: length = %i", length );
+ }
+ chan->unsentFragmentStart = 0;
+
+ // fragment large reliable messages
+ if ( length >= FRAGMENT_SIZE ) {
+ chan->unsentFragments = qtrue;
+ chan->unsentLength = length;
+ Com_Memcpy( chan->unsentBuffer, data, length );
+
+ // only send the first fragment now
+ Netchan_TransmitNextFragment( chan );
+
+ return;
+ }
+
+ // write the packet header
+ MSG_InitOOB (&send, send_buf, sizeof(send_buf));
+
+ MSG_WriteLong( &send, chan->outgoingSequence );
+ chan->outgoingSequence++;
+
+ // send the qport if we are a client
+ if ( chan->sock == NS_CLIENT ) {
+ MSG_WriteShort( &send, qport->integer );
+ }
+
+ MSG_WriteData( &send, data, length );
+
+ // send the datagram
+ NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress );
+
+ if ( showpackets->integer ) {
+ Com_Printf( "%s send %4i : s=%i ack=%i\n"
+ , netsrcString[ chan->sock ]
+ , send.cursize
+ , chan->outgoingSequence - 1
+ , chan->incomingSequence );
+ }
+}
+
+/*
+=================
+Netchan_Process
+
+Returns qfalse if the message should not be processed due to being
+out of order or a fragment.
+
+Msg must be large enough to hold MAX_MSGLEN, because if this is the
+final fragment of a multi-part message, the entire thing will be
+copied out.
+=================
+*/
+qboolean Netchan_Process( netchan_t *chan, msg_t *msg ) {
+ int sequence;
+ int qport;
+ int fragmentStart, fragmentLength;
+ qboolean fragmented;
+
+ // XOR unscramble all data in the packet after the header
+// Netchan_UnScramblePacket( msg );
+
+ // get sequence numbers
+ MSG_BeginReadingOOB( msg );
+ sequence = MSG_ReadLong( msg );
+
+ // check for fragment information
+ if ( sequence & FRAGMENT_BIT ) {
+ sequence &= ~FRAGMENT_BIT;
+ fragmented = qtrue;
+ } else {
+ fragmented = qfalse;
+ }
+
+ // read the qport if we are a server
+ if ( chan->sock == NS_SERVER ) {
+ qport = MSG_ReadShort( msg );
+ }
+
+ // read the fragment information
+ if ( fragmented ) {
+ fragmentStart = MSG_ReadShort( msg );
+ fragmentLength = MSG_ReadShort( msg );
+ } else {
+ fragmentStart = 0; // stop warning message
+ fragmentLength = 0;
+ }
+
+ if ( showpackets->integer ) {
+ if ( fragmented ) {
+ Com_Printf( "%s recv %4i : s=%i fragment=%i,%i\n"
+ , netsrcString[ chan->sock ]
+ , msg->cursize
+ , sequence
+ , fragmentStart, fragmentLength );
+ } else {
+ Com_Printf( "%s recv %4i : s=%i\n"
+ , netsrcString[ chan->sock ]
+ , msg->cursize
+ , sequence );
+ }
+ }
+
+ //
+ // discard out of order or duplicated packets
+ //
+ if ( sequence <= chan->incomingSequence ) {
+ if ( showdrop->integer || showpackets->integer ) {
+ Com_Printf( "%s:Out of order packet %i at %i\n"
+ , NET_AdrToString( chan->remoteAddress )
+ , sequence
+ , chan->incomingSequence );
+ }
+ return qfalse;
+ }
+
+ //
+ // dropped packets don't keep the message from being used
+ //
+ chan->dropped = sequence - (chan->incomingSequence+1);
+ if ( chan->dropped > 0 ) {
+ if ( showdrop->integer || showpackets->integer ) {
+ Com_Printf( "%s:Dropped %i packets at %i\n"
+ , NET_AdrToString( chan->remoteAddress )
+ , chan->dropped
+ , sequence );
+ }
+ }
+
+
+ //
+ // if this is the final framgent of a reliable message,
+ // bump incoming_reliable_sequence
+ //
+ if ( fragmented ) {
+ // TTimo
+ // make sure we add the fragments in correct order
+ // either a packet was dropped, or we received this one too soon
+ // we don't reconstruct the fragments. we will wait till this fragment gets to us again
+ // (NOTE: we could probably try to rebuild by out of order chunks if needed)
+ if ( sequence != chan->fragmentSequence ) {
+ chan->fragmentSequence = sequence;
+ chan->fragmentLength = 0;
+ }
+
+ // if we missed a fragment, dump the message
+ if ( fragmentStart != chan->fragmentLength ) {
+ if ( showdrop->integer || showpackets->integer ) {
+ Com_Printf( "%s:Dropped a message fragment\n"
+ , NET_AdrToString( chan->remoteAddress ));
+ }
+ // we can still keep the part that we have so far,
+ // so we don't need to clear chan->fragmentLength
+ return qfalse;
+ }
+
+ // copy the fragment to the fragment buffer
+ if ( fragmentLength < 0 || msg->readcount + fragmentLength > msg->cursize ||
+ chan->fragmentLength + fragmentLength > sizeof( chan->fragmentBuffer ) ) {
+ if ( showdrop->integer || showpackets->integer ) {
+ Com_Printf ("%s:illegal fragment length\n"
+ , NET_AdrToString (chan->remoteAddress ) );
+ }
+ return qfalse;
+ }
+
+ Com_Memcpy( chan->fragmentBuffer + chan->fragmentLength,
+ msg->data + msg->readcount, fragmentLength );
+
+ chan->fragmentLength += fragmentLength;
+
+ // if this wasn't the last fragment, don't process anything
+ if ( fragmentLength == FRAGMENT_SIZE ) {
+ return qfalse;
+ }
+
+ if ( chan->fragmentLength > msg->maxsize ) {
+ Com_Printf( "%s:fragmentLength %i > msg->maxsize\n"
+ , NET_AdrToString (chan->remoteAddress ),
+ chan->fragmentLength );
+ return qfalse;
+ }
+
+ // copy the full message over the partial fragment
+
+ // make sure the sequence number is still there
+ *(int *)msg->data = LittleLong( sequence );
+
+ Com_Memcpy( msg->data + 4, chan->fragmentBuffer, chan->fragmentLength );
+ msg->cursize = chan->fragmentLength + 4;
+ chan->fragmentLength = 0;
+ msg->readcount = 4; // past the sequence number
+ msg->bit = 32; // past the sequence number
+
+ // TTimo
+ // clients were not acking fragmented messages
+ chan->incomingSequence = sequence;
+
+ return qtrue;
+ }
+
+ //
+ // the message can now be read from the current message pointer
+ //
+ chan->incomingSequence = sequence;
+
+ return qtrue;
+}
+
+
+//==============================================================================
+
+/*
+===================
+NET_CompareBaseAdr
+
+Compares without the port
+===================
+*/
+qboolean NET_CompareBaseAdr (netadr_t a, netadr_t b)
+{
+ if (a.type != b.type)
+ return qfalse;
+
+ if (a.type == NA_LOOPBACK)
+ return qtrue;
+
+ if (a.type == NA_IP)
+ {
+ if (a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3])
+ return qtrue;
+ return qfalse;
+ }
+
+ if (a.type == NA_IPX)
+ {
+ if ((memcmp(a.ipx, b.ipx, 10) == 0))
+ return qtrue;
+ return qfalse;
+ }
+
+
+ Com_Printf ("NET_CompareBaseAdr: bad address type\n");
+ return qfalse;
+}
+
+const char *NET_AdrToString (netadr_t a)
+{
+ static char s[64];
+
+ if (a.type == NA_LOOPBACK) {
+ Com_sprintf (s, sizeof(s), "loopback");
+ } else if (a.type == NA_IP) {
+ Com_sprintf (s, sizeof(s), "%i.%i.%i.%i:%hu",
+ a.ip[0], a.ip[1], a.ip[2], a.ip[3], BigShort(a.port));
+ } else {
+ Com_sprintf (s, sizeof(s), "%02x%02x%02x%02x.%02x%02x%02x%02x%02x%02x:%hu",
+ a.ipx[0], a.ipx[1], a.ipx[2], a.ipx[3], a.ipx[4], a.ipx[5], a.ipx[6], a.ipx[7], a.ipx[8], a.ipx[9],
+ BigShort(a.port));
+ }
+
+ return s;
+}
+
+
+qboolean NET_CompareAdr (netadr_t a, netadr_t b)
+{
+ if (a.type != b.type)
+ return qfalse;
+
+ if (a.type == NA_LOOPBACK)
+ return qtrue;
+
+ if (a.type == NA_IP)
+ {
+ if (a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3] && a.port == b.port)
+ return qtrue;
+ return qfalse;
+ }
+
+ if (a.type == NA_IPX)
+ {
+ if ((memcmp(a.ipx, b.ipx, 10) == 0) && a.port == b.port)
+ return qtrue;
+ return qfalse;
+ }
+
+ Com_Printf ("NET_CompareAdr: bad address type\n");
+ return qfalse;
+}
+
+
+qboolean NET_IsLocalAddress( netadr_t adr ) {
+ return adr.type == NA_LOOPBACK;
+}
+
+
+
+/*
+=============================================================================
+
+LOOPBACK BUFFERS FOR LOCAL PLAYER
+
+=============================================================================
+*/
+
+// there needs to be enough loopback messages to hold a complete
+// gamestate of maximum size
+#define MAX_LOOPBACK 16
+
+typedef struct {
+ byte data[MAX_PACKETLEN];
+ int datalen;
+} loopmsg_t;
+
+typedef struct {
+ loopmsg_t msgs[MAX_LOOPBACK];
+ int get, send;
+} loopback_t;
+
+loopback_t loopbacks[2];
+
+
+qboolean NET_GetLoopPacket (netsrc_t sock, netadr_t *net_from, msg_t *net_message)
+{
+ int i;
+ loopback_t *loop;
+
+ loop = &loopbacks[sock];
+
+ if (loop->send - loop->get > MAX_LOOPBACK)
+ loop->get = loop->send - MAX_LOOPBACK;
+
+ if (loop->get >= loop->send)
+ return qfalse;
+
+ i = loop->get & (MAX_LOOPBACK-1);
+ loop->get++;
+
+ Com_Memcpy (net_message->data, loop->msgs[i].data, loop->msgs[i].datalen);
+ net_message->cursize = loop->msgs[i].datalen;
+ Com_Memset (net_from, 0, sizeof(*net_from));
+ net_from->type = NA_LOOPBACK;
+ return qtrue;
+
+}
+
+
+void NET_SendLoopPacket (netsrc_t sock, int length, const void *data, netadr_t to)
+{
+ int i;
+ loopback_t *loop;
+
+ loop = &loopbacks[sock^1];
+
+ i = loop->send & (MAX_LOOPBACK-1);
+ loop->send++;
+
+ Com_Memcpy (loop->msgs[i].data, data, length);
+ loop->msgs[i].datalen = length;
+}
+
+//=============================================================================
+
+typedef struct packetQueue_s {
+ struct packetQueue_s *next;
+ int length;
+ byte *data;
+ netadr_t to;
+ int release;
+} packetQueue_t;
+
+packetQueue_t *packetQueue = NULL;
+
+static void NET_QueuePacket( int length, const void *data, netadr_t to,
+ int offset )
+{
+ packetQueue_t *new, *next = packetQueue;
+
+ if(offset > 999)
+ offset = 999;
+
+ new = S_Malloc(sizeof(packetQueue_t));
+ new->data = S_Malloc(length);
+ Com_Memcpy(new->data, data, length);
+ new->length = length;
+ new->to = to;
+ new->release = Sys_Milliseconds() + offset;
+ new->next = NULL;
+
+ if(!packetQueue) {
+ packetQueue = new;
+ return;
+ }
+ while(next) {
+ if(!next->next) {
+ next->next = new;
+ return;
+ }
+ next = next->next;
+ }
+}
+
+void NET_FlushPacketQueue(void)
+{
+ packetQueue_t *last;
+ int now;
+
+ while(packetQueue) {
+ now = Sys_Milliseconds();
+ if(packetQueue->release >= now)
+ break;
+ Sys_SendPacket(packetQueue->length, packetQueue->data,
+ packetQueue->to);
+ last = packetQueue;
+ packetQueue = packetQueue->next;
+ Z_Free(last->data);
+ Z_Free(last);
+ }
+}
+
+void NET_SendPacket( netsrc_t sock, int length, const void *data, netadr_t to ) {
+
+ // sequenced packets are shown in netchan, so just show oob
+ if ( showpackets->integer && *(int *)data == -1 ) {
+ Com_Printf ("send packet %4i\n", length);
+ }
+
+ if ( to.type == NA_LOOPBACK ) {
+ NET_SendLoopPacket (sock, length, data, to);
+ return;
+ }
+ if ( to.type == NA_BAD ) {
+ return;
+ }
+
+ if ( sock == NS_CLIENT && cl_packetdelay->integer > 0 ) {
+ NET_QueuePacket( length, data, to, cl_packetdelay->integer );
+ }
+ else if ( sock == NS_SERVER && sv_packetdelay->integer > 0 ) {
+ NET_QueuePacket( length, data, to, sv_packetdelay->integer );
+ }
+ else {
+ Sys_SendPacket( length, data, to );
+ }
+}
+
+/*
+===============
+NET_OutOfBandPrint
+
+Sends a text message in an out-of-band datagram
+================
+*/
+void QDECL NET_OutOfBandPrint( netsrc_t sock, netadr_t adr, const char *format, ... ) {
+ va_list argptr;
+ char string[MAX_MSGLEN];
+
+
+ // set the header
+ string[0] = -1;
+ string[1] = -1;
+ string[2] = -1;
+ string[3] = -1;
+
+ va_start( argptr, format );
+ Q_vsnprintf( string+4, sizeof(string)-4, format, argptr );
+ va_end( argptr );
+
+ // send the datagram
+ NET_SendPacket( sock, strlen( string ), string, adr );
+}
+
+/*
+===============
+NET_OutOfBandPrint
+
+Sends a data message in an out-of-band datagram (only used for "connect")
+================
+*/
+void QDECL NET_OutOfBandData( netsrc_t sock, netadr_t adr, byte *format, int len ) {
+ byte string[MAX_MSGLEN*2];
+ int i;
+ msg_t mbuf;
+
+ // set the header
+ string[0] = 0xff;
+ string[1] = 0xff;
+ string[2] = 0xff;
+ string[3] = 0xff;
+
+ for(i=0;i<len;i++) {
+ string[i+4] = format[i];
+ }
+
+ mbuf.data = string;
+ mbuf.cursize = len+4;
+ Huff_Compress( &mbuf, 12);
+ // send the datagram
+ NET_SendPacket( sock, mbuf.cursize, mbuf.data, adr );
+}
+
+/*
+=============
+NET_StringToAdr
+
+Traps "localhost" for loopback, passes everything else to system
+=============
+*/
+qboolean NET_StringToAdr( const char *s, netadr_t *a ) {
+ qboolean r;
+ char base[MAX_STRING_CHARS];
+ char *port;
+
+ if (!strcmp (s, "localhost")) {
+ Com_Memset (a, 0, sizeof(*a));
+ a->type = NA_LOOPBACK;
+ return qtrue;
+ }
+
+ // look for a port number
+ Q_strncpyz( base, s, sizeof( base ) );
+ port = strstr( base, ":" );
+ if ( port ) {
+ *port = 0;
+ port++;
+ }
+
+ r = Sys_StringToAdr( base, a );
+
+ if ( !r ) {
+ a->type = NA_BAD;
+ return qfalse;
+ }
+
+ // inet_addr returns this if out of range
+ if ( a->ip[0] == 255 && a->ip[1] == 255 && a->ip[2] == 255 && a->ip[3] == 255 ) {
+ a->type = NA_BAD;
+ return qfalse;
+ }
+
+ if ( port ) {
+ a->port = BigShort( (short)atoi( port ) );
+ } else {
+ a->port = BigShort( PORT_SERVER );
+ }
+
+ return qtrue;
+}
+
diff --git a/src/qcommon/parse.c b/src/qcommon/parse.c
new file mode 100644
index 0000000..136ba0b
--- /dev/null
+++ b/src/qcommon/parse.c
@@ -0,0 +1,3585 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "q_shared.h"
+#include "qcommon.h"
+
+//script flags
+#define SCFL_NOERRORS 0x0001
+#define SCFL_NOWARNINGS 0x0002
+#define SCFL_NOSTRINGWHITESPACES 0x0004
+#define SCFL_NOSTRINGESCAPECHARS 0x0008
+#define SCFL_PRIMITIVE 0x0010
+#define SCFL_NOBINARYNUMBERS 0x0020
+#define SCFL_NONUMBERVALUES 0x0040
+
+//token types
+#define TT_STRING 1 // string
+#define TT_LITERAL 2 // literal
+#define TT_NUMBER 3 // number
+#define TT_NAME 4 // name
+#define TT_PUNCTUATION 5 // punctuation
+
+//string sub type
+//---------------
+// the length of the string
+//literal sub type
+//----------------
+// the ASCII code of the literal
+//number sub type
+//---------------
+#define TT_DECIMAL 0x0008 // decimal number
+#define TT_HEX 0x0100 // hexadecimal number
+#define TT_OCTAL 0x0200 // octal number
+#define TT_BINARY 0x0400 // binary number
+#define TT_FLOAT 0x0800 // floating point number
+#define TT_INTEGER 0x1000 // integer number
+#define TT_LONG 0x2000 // long number
+#define TT_UNSIGNED 0x4000 // unsigned number
+//punctuation sub type
+//--------------------
+#define P_RSHIFT_ASSIGN 1
+#define P_LSHIFT_ASSIGN 2
+#define P_PARMS 3
+#define P_PRECOMPMERGE 4
+
+#define P_LOGIC_AND 5
+#define P_LOGIC_OR 6
+#define P_LOGIC_GEQ 7
+#define P_LOGIC_LEQ 8
+#define P_LOGIC_EQ 9
+#define P_LOGIC_UNEQ 10
+
+#define P_MUL_ASSIGN 11
+#define P_DIV_ASSIGN 12
+#define P_MOD_ASSIGN 13
+#define P_ADD_ASSIGN 14
+#define P_SUB_ASSIGN 15
+#define P_INC 16
+#define P_DEC 17
+
+#define P_BIN_AND_ASSIGN 18
+#define P_BIN_OR_ASSIGN 19
+#define P_BIN_XOR_ASSIGN 20
+#define P_RSHIFT 21
+#define P_LSHIFT 22
+
+#define P_POINTERREF 23
+#define P_CPP1 24
+#define P_CPP2 25
+#define P_MUL 26
+#define P_DIV 27
+#define P_MOD 28
+#define P_ADD 29
+#define P_SUB 30
+#define P_ASSIGN 31
+
+#define P_BIN_AND 32
+#define P_BIN_OR 33
+#define P_BIN_XOR 34
+#define P_BIN_NOT 35
+
+#define P_LOGIC_NOT 36
+#define P_LOGIC_GREATER 37
+#define P_LOGIC_LESS 38
+
+#define P_REF 39
+#define P_COMMA 40
+#define P_SEMICOLON 41
+#define P_COLON 42
+#define P_QUESTIONMARK 43
+
+#define P_PARENTHESESOPEN 44
+#define P_PARENTHESESCLOSE 45
+#define P_BRACEOPEN 46
+#define P_BRACECLOSE 47
+#define P_SQBRACKETOPEN 48
+#define P_SQBRACKETCLOSE 49
+#define P_BACKSLASH 50
+
+#define P_PRECOMP 51
+#define P_DOLLAR 52
+
+//name sub type
+//-------------
+// the length of the name
+
+//punctuation
+typedef struct punctuation_s
+{
+ char *p; //punctuation character(s)
+ int n; //punctuation indication
+ struct punctuation_s *next; //next punctuation
+} punctuation_t;
+
+//token
+typedef struct token_s
+{
+ char string[MAX_TOKEN_CHARS]; //available token
+ int type; //last read token type
+ int subtype; //last read token sub type
+ unsigned long int intvalue; //integer value
+ double floatvalue; //floating point value
+ char *whitespace_p; //start of white space before token
+ char *endwhitespace_p; //start of white space before token
+ int line; //line the token was on
+ int linescrossed; //lines crossed in white space
+ struct token_s *next; //next token in chain
+} token_t;
+
+//script file
+typedef struct script_s
+{
+ char filename[1024]; //file name of the script
+ char *buffer; //buffer containing the script
+ char *script_p; //current pointer in the script
+ char *end_p; //pointer to the end of the script
+ char *lastscript_p; //script pointer before reading token
+ char *whitespace_p; //begin of the white space
+ char *endwhitespace_p;
+ int length; //length of the script in bytes
+ int line; //current line in script
+ int lastline; //line before reading token
+ int tokenavailable; //set by UnreadLastToken
+ int flags; //several script flags
+ punctuation_t *punctuations; //the punctuations used in the script
+ punctuation_t **punctuationtable;
+ token_t token; //available token
+ struct script_s *next; //next script in a chain
+} script_t;
+
+
+#define DEFINE_FIXED 0x0001
+
+#define BUILTIN_LINE 1
+#define BUILTIN_FILE 2
+#define BUILTIN_DATE 3
+#define BUILTIN_TIME 4
+#define BUILTIN_STDC 5
+
+#define INDENT_IF 0x0001
+#define INDENT_ELSE 0x0002
+#define INDENT_ELIF 0x0004
+#define INDENT_IFDEF 0x0008
+#define INDENT_IFNDEF 0x0010
+
+//macro definitions
+typedef struct define_s
+{
+ char *name; //define name
+ int flags; //define flags
+ int builtin; // > 0 if builtin define
+ int numparms; //number of define parameters
+ token_t *parms; //define parameters
+ token_t *tokens; //macro tokens (possibly containing parm tokens)
+ struct define_s *next; //next defined macro in a list
+ struct define_s *hashnext; //next define in the hash chain
+} define_t;
+
+//indents
+//used for conditional compilation directives:
+//#if, #else, #elif, #ifdef, #ifndef
+typedef struct indent_s
+{
+ int type; //indent type
+ int skip; //true if skipping current indent
+ script_t *script; //script the indent was in
+ struct indent_s *next; //next indent on the indent stack
+} indent_t;
+
+//source file
+typedef struct source_s
+{
+ char filename[MAX_QPATH]; //file name of the script
+ char includepath[MAX_QPATH]; //path to include files
+ punctuation_t *punctuations; //punctuations to use
+ script_t *scriptstack; //stack with scripts of the source
+ token_t *tokens; //tokens to read first
+ define_t *defines; //list with macro definitions
+ define_t **definehash; //hash chain with defines
+ indent_t *indentstack; //stack with indents
+ int skip; // > 0 if skipping conditional code
+ token_t token; //last read token
+} source_t;
+
+#define MAX_DEFINEPARMS 128
+
+//directive name with parse function
+typedef struct directive_s
+{
+ char *name;
+ int (*func)(source_t *source);
+} directive_t;
+
+#define DEFINEHASHSIZE 1024
+
+static int Parse_ReadToken(source_t *source, token_t *token);
+
+int numtokens;
+
+//list with global defines added to every source loaded
+define_t *globaldefines;
+
+//longer punctuations first
+punctuation_t default_punctuations[] =
+{
+ //binary operators
+ {">>=",P_RSHIFT_ASSIGN, NULL},
+ {"<<=",P_LSHIFT_ASSIGN, NULL},
+ //
+ {"...",P_PARMS, NULL},
+ //define merge operator
+ {"##",P_PRECOMPMERGE, NULL},
+ //logic operators
+ {"&&",P_LOGIC_AND, NULL},
+ {"||",P_LOGIC_OR, NULL},
+ {">=",P_LOGIC_GEQ, NULL},
+ {"<=",P_LOGIC_LEQ, NULL},
+ {"==",P_LOGIC_EQ, NULL},
+ {"!=",P_LOGIC_UNEQ, NULL},
+ //arithmatic operators
+ {"*=",P_MUL_ASSIGN, NULL},
+ {"/=",P_DIV_ASSIGN, NULL},
+ {"%=",P_MOD_ASSIGN, NULL},
+ {"+=",P_ADD_ASSIGN, NULL},
+ {"-=",P_SUB_ASSIGN, NULL},
+ {"++",P_INC, NULL},
+ {"--",P_DEC, NULL},
+ //binary operators
+ {"&=",P_BIN_AND_ASSIGN, NULL},
+ {"|=",P_BIN_OR_ASSIGN, NULL},
+ {"^=",P_BIN_XOR_ASSIGN, NULL},
+ {">>",P_RSHIFT, NULL},
+ {"<<",P_LSHIFT, NULL},
+ //reference operators
+ {"->",P_POINTERREF, NULL},
+ //C++
+ {"::",P_CPP1, NULL},
+ {".*",P_CPP2, NULL},
+ //arithmatic operators
+ {"*",P_MUL, NULL},
+ {"/",P_DIV, NULL},
+ {"%",P_MOD, NULL},
+ {"+",P_ADD, NULL},
+ {"-",P_SUB, NULL},
+ {"=",P_ASSIGN, NULL},
+ //binary operators
+ {"&",P_BIN_AND, NULL},
+ {"|",P_BIN_OR, NULL},
+ {"^",P_BIN_XOR, NULL},
+ {"~",P_BIN_NOT, NULL},
+ //logic operators
+ {"!",P_LOGIC_NOT, NULL},
+ {">",P_LOGIC_GREATER, NULL},
+ {"<",P_LOGIC_LESS, NULL},
+ //reference operator
+ {".",P_REF, NULL},
+ //seperators
+ {",",P_COMMA, NULL},
+ {";",P_SEMICOLON, NULL},
+ //label indication
+ {":",P_COLON, NULL},
+ //if statement
+ {"?",P_QUESTIONMARK, NULL},
+ //embracements
+ {"(",P_PARENTHESESOPEN, NULL},
+ {")",P_PARENTHESESCLOSE, NULL},
+ {"{",P_BRACEOPEN, NULL},
+ {"}",P_BRACECLOSE, NULL},
+ {"[",P_SQBRACKETOPEN, NULL},
+ {"]",P_SQBRACKETCLOSE, NULL},
+ //
+ {"\\",P_BACKSLASH, NULL},
+ //precompiler operator
+ {"#",P_PRECOMP, NULL},
+ {"$",P_DOLLAR, NULL},
+ {NULL, 0}
+};
+
+char basefolder[MAX_QPATH];
+
+/*
+===============
+Parse_CreatePunctuationTable
+===============
+*/
+static void Parse_CreatePunctuationTable(script_t *script, punctuation_t *punctuations)
+{
+ int i;
+ punctuation_t *p, *lastp, *newp;
+
+ //get memory for the table
+ if (!script->punctuationtable) script->punctuationtable = (punctuation_t **)
+ Z_Malloc(256 * sizeof(punctuation_t *));
+ Com_Memset(script->punctuationtable, 0, 256 * sizeof(punctuation_t *));
+ //add the punctuations in the list to the punctuation table
+ for (i = 0; punctuations[i].p; i++)
+ {
+ newp = &punctuations[i];
+ lastp = NULL;
+ //sort the punctuations in this table entry on length (longer punctuations first)
+ for (p = script->punctuationtable[(unsigned int) newp->p[0]]; p; p = p->next)
+ {
+ if (strlen(p->p) < strlen(newp->p))
+ {
+ newp->next = p;
+ if (lastp) lastp->next = newp;
+ else script->punctuationtable[(unsigned int) newp->p[0]] = newp;
+ break;
+ }
+ lastp = p;
+ }
+ if (!p)
+ {
+ newp->next = NULL;
+ if (lastp) lastp->next = newp;
+ else script->punctuationtable[(unsigned int) newp->p[0]] = newp;
+ }
+ }
+}
+
+/*
+===============
+Parse_ScriptError
+===============
+*/
+static void QDECL Parse_ScriptError(script_t *script, char *str, ...)
+{
+ char text[1024];
+ va_list ap;
+
+ if (script->flags & SCFL_NOERRORS) return;
+
+ va_start(ap, str);
+ vsprintf(text, str, ap);
+ va_end(ap);
+ Com_Printf( "file %s, line %d: %s\n", script->filename, script->line, text);
+}
+
+/*
+===============
+Parse_ScriptWarning
+===============
+*/
+static void QDECL Parse_ScriptWarning(script_t *script, char *str, ...)
+{
+ char text[1024];
+ va_list ap;
+
+ if (script->flags & SCFL_NOWARNINGS) return;
+
+ va_start(ap, str);
+ vsprintf(text, str, ap);
+ va_end(ap);
+ Com_Printf( "file %s, line %d: %s\n", script->filename, script->line, text);
+}
+
+/*
+===============
+Parse_SetScriptPunctuations
+===============
+*/
+static void Parse_SetScriptPunctuations(script_t *script, punctuation_t *p)
+{
+ if (p) Parse_CreatePunctuationTable(script, p);
+ else Parse_CreatePunctuationTable(script, default_punctuations);
+ if (p) script->punctuations = p;
+ else script->punctuations = default_punctuations;
+}
+
+/*
+===============
+Parse_ReadWhiteSpace
+===============
+*/
+static int Parse_ReadWhiteSpace(script_t *script)
+{
+ while(1)
+ {
+ //skip white space
+ while(*script->script_p <= ' ')
+ {
+ if (!*script->script_p) return 0;
+ if (*script->script_p == '\n') script->line++;
+ script->script_p++;
+ }
+ //skip comments
+ if (*script->script_p == '/')
+ {
+ //comments //
+ if (*(script->script_p+1) == '/')
+ {
+ script->script_p++;
+ do
+ {
+ script->script_p++;
+ if (!*script->script_p) return 0;
+ }
+ while(*script->script_p != '\n');
+ script->line++;
+ script->script_p++;
+ if (!*script->script_p) return 0;
+ continue;
+ }
+ //comments /* */
+ else if (*(script->script_p+1) == '*')
+ {
+ script->script_p++;
+ do
+ {
+ script->script_p++;
+ if (!*script->script_p) return 0;
+ if (*script->script_p == '\n') script->line++;
+ }
+ while(!(*script->script_p == '*' && *(script->script_p+1) == '/'));
+ script->script_p++;
+ if (!*script->script_p) return 0;
+ script->script_p++;
+ if (!*script->script_p) return 0;
+ continue;
+ }
+ }
+ break;
+ }
+ return 1;
+}
+
+/*
+===============
+Parse_ReadEscapeCharacter
+===============
+*/
+static int Parse_ReadEscapeCharacter(script_t *script, char *ch)
+{
+ int c, val, i;
+
+ //step over the leading '\\'
+ script->script_p++;
+ //determine the escape character
+ switch(*script->script_p)
+ {
+ case '\\': c = '\\'; break;
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+ case 'v': c = '\v'; break;
+ case 'b': c = '\b'; break;
+ case 'f': c = '\f'; break;
+ case 'a': c = '\a'; break;
+ case '\'': c = '\''; break;
+ case '\"': c = '\"'; break;
+ case '\?': c = '\?'; break;
+ case 'x':
+ {
+ script->script_p++;
+ for (i = 0, val = 0; ; i++, script->script_p++)
+ {
+ c = *script->script_p;
+ if (c >= '0' && c <= '9') c = c - '0';
+ else if (c >= 'A' && c <= 'Z') c = c - 'A' + 10;
+ else if (c >= 'a' && c <= 'z') c = c - 'a' + 10;
+ else break;
+ val = (val << 4) + c;
+ }
+ script->script_p--;
+ if (val > 0xFF)
+ {
+ Parse_ScriptWarning(script, "too large value in escape character");
+ val = 0xFF;
+ }
+ c = val;
+ break;
+ }
+ default: //NOTE: decimal ASCII code, NOT octal
+ {
+ if (*script->script_p < '0' || *script->script_p > '9') Parse_ScriptError(script, "unknown escape char");
+ for (i = 0, val = 0; ; i++, script->script_p++)
+ {
+ c = *script->script_p;
+ if (c >= '0' && c <= '9') c = c - '0';
+ else break;
+ val = val * 10 + c;
+ }
+ script->script_p--;
+ if (val > 0xFF)
+ {
+ Parse_ScriptWarning(script, "too large value in escape character");
+ val = 0xFF;
+ }
+ c = val;
+ break;
+ }
+ }
+ //step over the escape character or the last digit of the number
+ script->script_p++;
+ //store the escape character
+ *ch = c;
+ //succesfully read escape character
+ return 1;
+}
+
+/*
+===============
+Parse_ReadString
+
+Reads C-like string. Escape characters are interpretted.
+Quotes are included with the string.
+Reads two strings with a white space between them as one string.
+===============
+*/
+static int Parse_ReadString(script_t *script, token_t *token, int quote)
+{
+ int len, tmpline;
+ char *tmpscript_p;
+
+ if (quote == '\"') token->type = TT_STRING;
+ else token->type = TT_LITERAL;
+
+ len = 0;
+ //leading quote
+ token->string[len++] = *script->script_p++;
+ //
+ while(1)
+ {
+ //minus 2 because trailing double quote and zero have to be appended
+ if (len >= MAX_TOKEN_CHARS - 2)
+ {
+ Parse_ScriptError(script, "string longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS);
+ return 0;
+ }
+ //if there is an escape character and
+ //if escape characters inside a string are allowed
+ if (*script->script_p == '\\' && !(script->flags & SCFL_NOSTRINGESCAPECHARS))
+ {
+ if (!Parse_ReadEscapeCharacter(script, &token->string[len]))
+ {
+ token->string[len] = 0;
+ return 0;
+ }
+ len++;
+ }
+ //if a trailing quote
+ else if (*script->script_p == quote)
+ {
+ //step over the double quote
+ script->script_p++;
+ //if white spaces in a string are not allowed
+ if (script->flags & SCFL_NOSTRINGWHITESPACES) break;
+ //
+ tmpscript_p = script->script_p;
+ tmpline = script->line;
+ //read unusefull stuff between possible two following strings
+ if (!Parse_ReadWhiteSpace(script))
+ {
+ script->script_p = tmpscript_p;
+ script->line = tmpline;
+ break;
+ }
+ //if there's no leading double qoute
+ if (*script->script_p != quote)
+ {
+ script->script_p = tmpscript_p;
+ script->line = tmpline;
+ break;
+ }
+ //step over the new leading double quote
+ script->script_p++;
+ }
+ else
+ {
+ if (*script->script_p == '\0')
+ {
+ token->string[len] = 0;
+ Parse_ScriptError(script, "missing trailing quote");
+ return 0;
+ }
+ if (*script->script_p == '\n')
+ {
+ token->string[len] = 0;
+ Parse_ScriptError(script, "newline inside string %s", token->string);
+ return 0;
+ }
+ token->string[len++] = *script->script_p++;
+ }
+ }
+ //trailing quote
+ token->string[len++] = quote;
+ //end string with a zero
+ token->string[len] = '\0';
+ //the sub type is the length of the string
+ token->subtype = len;
+ return 1;
+}
+
+/*
+===============
+Parse_ReadName
+===============
+*/
+static int Parse_ReadName(script_t *script, token_t *token)
+{
+ int len = 0;
+ char c;
+
+ token->type = TT_NAME;
+ do
+ {
+ token->string[len++] = *script->script_p++;
+ if (len >= MAX_TOKEN_CHARS)
+ {
+ Parse_ScriptError(script, "name longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS);
+ return 0;
+ }
+ c = *script->script_p;
+ } while ((c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') ||
+ c == '_');
+ token->string[len] = '\0';
+ //the sub type is the length of the name
+ token->subtype = len;
+ return 1;
+}
+
+/*
+===============
+Parse_NumberValue
+===============
+*/
+static void Parse_NumberValue(char *string, int subtype, unsigned long int *intvalue,
+ double *floatvalue)
+{
+ unsigned long int dotfound = 0;
+
+ *intvalue = 0;
+ *floatvalue = 0;
+ //floating point number
+ if (subtype & TT_FLOAT)
+ {
+ while(*string)
+ {
+ if (*string == '.')
+ {
+ if (dotfound) return;
+ dotfound = 10;
+ string++;
+ }
+ if (dotfound)
+ {
+ *floatvalue = *floatvalue + (double) (*string - '0') /
+ (double) dotfound;
+ dotfound *= 10;
+ }
+ else
+ {
+ *floatvalue = *floatvalue * 10.0 + (double) (*string - '0');
+ }
+ string++;
+ }
+ *intvalue = (unsigned long) *floatvalue;
+ }
+ else if (subtype & TT_DECIMAL)
+ {
+ while(*string) *intvalue = *intvalue * 10 + (*string++ - '0');
+ *floatvalue = *intvalue;
+ }
+ else if (subtype & TT_HEX)
+ {
+ //step over the leading 0x or 0X
+ string += 2;
+ while(*string)
+ {
+ *intvalue <<= 4;
+ if (*string >= 'a' && *string <= 'f') *intvalue += *string - 'a' + 10;
+ else if (*string >= 'A' && *string <= 'F') *intvalue += *string - 'A' + 10;
+ else *intvalue += *string - '0';
+ string++;
+ }
+ *floatvalue = *intvalue;
+ }
+ else if (subtype & TT_OCTAL)
+ {
+ //step over the first zero
+ string += 1;
+ while(*string) *intvalue = (*intvalue << 3) + (*string++ - '0');
+ *floatvalue = *intvalue;
+ }
+ else if (subtype & TT_BINARY)
+ {
+ //step over the leading 0b or 0B
+ string += 2;
+ while(*string) *intvalue = (*intvalue << 1) + (*string++ - '0');
+ *floatvalue = *intvalue;
+ }
+}
+
+/*
+===============
+Parse_ReadNumber
+===============
+*/
+static int Parse_ReadNumber(script_t *script, token_t *token)
+{
+ int len = 0, i;
+ int octal, dot;
+ char c;
+// unsigned long int intvalue = 0;
+// double floatvalue = 0;
+
+ token->type = TT_NUMBER;
+ //check for a hexadecimal number
+ if (*script->script_p == '0' &&
+ (*(script->script_p + 1) == 'x' ||
+ *(script->script_p + 1) == 'X'))
+ {
+ token->string[len++] = *script->script_p++;
+ token->string[len++] = *script->script_p++;
+ c = *script->script_p;
+ //hexadecimal
+ while((c >= '0' && c <= '9') ||
+ (c >= 'a' && c <= 'f') ||
+ (c >= 'A' && c <= 'A'))
+ {
+ token->string[len++] = *script->script_p++;
+ if (len >= MAX_TOKEN_CHARS)
+ {
+ Parse_ScriptError(script, "hexadecimal number longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS);
+ return 0;
+ }
+ c = *script->script_p;
+ }
+ token->subtype |= TT_HEX;
+ }
+#ifdef BINARYNUMBERS
+ //check for a binary number
+ else if (*script->script_p == '0' &&
+ (*(script->script_p + 1) == 'b' ||
+ *(script->script_p + 1) == 'B'))
+ {
+ token->string[len++] = *script->script_p++;
+ token->string[len++] = *script->script_p++;
+ c = *script->script_p;
+ //binary
+ while(c == '0' || c == '1')
+ {
+ token->string[len++] = *script->script_p++;
+ if (len >= MAX_TOKEN_CHARS)
+ {
+ Parse_ScriptError(script, "binary number longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS);
+ return 0;
+ }
+ c = *script->script_p;
+ }
+ token->subtype |= TT_BINARY;
+ }
+#endif //BINARYNUMBERS
+ else //decimal or octal integer or floating point number
+ {
+ octal = qfalse;
+ dot = qfalse;
+ if (*script->script_p == '0') octal = qtrue;
+ while(1)
+ {
+ c = *script->script_p;
+ if (c == '.') dot = qtrue;
+ else if (c == '8' || c == '9') octal = qfalse;
+ else if (c < '0' || c > '9') break;
+ token->string[len++] = *script->script_p++;
+ if (len >= MAX_TOKEN_CHARS - 1)
+ {
+ Parse_ScriptError(script, "number longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS);
+ return 0;
+ }
+ }
+ if (octal) token->subtype |= TT_OCTAL;
+ else token->subtype |= TT_DECIMAL;
+ if (dot) token->subtype |= TT_FLOAT;
+ }
+ for (i = 0; i < 2; i++)
+ {
+ c = *script->script_p;
+ //check for a LONG number
+ if ( (c == 'l' || c == 'L') // bk001204 - brackets
+ && !(token->subtype & TT_LONG))
+ {
+ script->script_p++;
+ token->subtype |= TT_LONG;
+ }
+ //check for an UNSIGNED number
+ else if ( (c == 'u' || c == 'U') // bk001204 - brackets
+ && !(token->subtype & (TT_UNSIGNED | TT_FLOAT)))
+ {
+ script->script_p++;
+ token->subtype |= TT_UNSIGNED;
+ }
+ }
+ token->string[len] = '\0';
+ Parse_NumberValue(token->string, token->subtype, &token->intvalue, &token->floatvalue);
+ if (!(token->subtype & TT_FLOAT)) token->subtype |= TT_INTEGER;
+ return 1;
+}
+
+/*
+===============
+Parse_ReadPunctuation
+===============
+*/
+static int Parse_ReadPunctuation(script_t *script, token_t *token)
+{
+ int len;
+ char *p;
+ punctuation_t *punc;
+
+ for (punc = script->punctuationtable[(unsigned int)*script->script_p]; punc; punc = punc->next)
+ {
+ p = punc->p;
+ len = strlen(p);
+ //if the script contains at least as much characters as the punctuation
+ if (script->script_p + len <= script->end_p)
+ {
+ //if the script contains the punctuation
+ if (!strncmp(script->script_p, p, len))
+ {
+ strncpy(token->string, p, MAX_TOKEN_CHARS);
+ script->script_p += len;
+ token->type = TT_PUNCTUATION;
+ //sub type is the number of the punctuation
+ token->subtype = punc->n;
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+===============
+Parse_ReadPrimitive
+===============
+*/
+static int Parse_ReadPrimitive(script_t *script, token_t *token)
+{
+ int len;
+
+ len = 0;
+ while(*script->script_p > ' ' && *script->script_p != ';')
+ {
+ if (len >= MAX_TOKEN_CHARS)
+ {
+ Parse_ScriptError(script, "primitive token longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS);
+ return 0;
+ }
+ token->string[len++] = *script->script_p++;
+ }
+ token->string[len] = 0;
+ //copy the token into the script structure
+ Com_Memcpy(&script->token, token, sizeof(token_t));
+ //primitive reading successfull
+ return 1;
+}
+
+/*
+===============
+Parse_ReadSciptToken
+===============
+*/
+static int Parse_ReadSciptToken(script_t *script, token_t *token)
+{
+ //if there is a token available (from UnreadToken)
+ if (script->tokenavailable)
+ {
+ script->tokenavailable = 0;
+ Com_Memcpy(token, &script->token, sizeof(token_t));
+ return 1;
+ }
+ //save script pointer
+ script->lastscript_p = script->script_p;
+ //save line counter
+ script->lastline = script->line;
+ //clear the token stuff
+ Com_Memset(token, 0, sizeof(token_t));
+ //start of the white space
+ script->whitespace_p = script->script_p;
+ token->whitespace_p = script->script_p;
+ //read unusefull stuff
+ if (!Parse_ReadWhiteSpace(script)) return 0;
+
+ script->endwhitespace_p = script->script_p;
+ token->endwhitespace_p = script->script_p;
+ //line the token is on
+ token->line = script->line;
+ //number of lines crossed before token
+ token->linescrossed = script->line - script->lastline;
+ //if there is a leading double quote
+ if (*script->script_p == '\"')
+ {
+ if (!Parse_ReadString(script, token, '\"')) return 0;
+ }
+ //if an literal
+ else if (*script->script_p == '\'')
+ {
+ //if (!Parse_ReadLiteral(script, token)) return 0;
+ if (!Parse_ReadString(script, token, '\'')) return 0;
+ }
+ //if there is a number
+ else if ((*script->script_p >= '0' && *script->script_p <= '9') ||
+ (*script->script_p == '.' &&
+ (*(script->script_p + 1) >= '0' && *(script->script_p + 1) <= '9')))
+ {
+ if (!Parse_ReadNumber(script, token)) return 0;
+ }
+ //if this is a primitive script
+ else if (script->flags & SCFL_PRIMITIVE)
+ {
+ return Parse_ReadPrimitive(script, token);
+ }
+ //if there is a name
+ else if ((*script->script_p >= 'a' && *script->script_p <= 'z') ||
+ (*script->script_p >= 'A' && *script->script_p <= 'Z') ||
+ *script->script_p == '_')
+ {
+ if (!Parse_ReadName(script, token)) return 0;
+ }
+ //check for punctuations
+ else if (!Parse_ReadPunctuation(script, token))
+ {
+ Parse_ScriptError(script, "can't read token");
+ return 0;
+ }
+ //copy the token into the script structure
+ Com_Memcpy(&script->token, token, sizeof(token_t));
+ //succesfully read a token
+ return 1;
+}
+
+/*
+===============
+Parse_StripDoubleQuotes
+===============
+*/
+static void Parse_StripDoubleQuotes(char *string)
+{
+ if (*string == '\"')
+ {
+ strcpy(string, string+1);
+ }
+ if (string[strlen(string)-1] == '\"')
+ {
+ string[strlen(string)-1] = '\0';
+ }
+}
+
+/*
+===============
+Parse_EndOfScript
+===============
+*/
+static int Parse_EndOfScript(script_t *script)
+{
+ return script->script_p >= script->end_p;
+}
+
+/*
+===============
+Parse_LoadScriptFile
+===============
+*/
+static script_t *Parse_LoadScriptFile(const char *filename)
+{
+ fileHandle_t fp;
+ char pathname[MAX_QPATH];
+ int length;
+ void *buffer;
+ script_t *script;
+
+ if (strlen(basefolder))
+ Com_sprintf(pathname, sizeof(pathname), "%s/%s", basefolder, filename);
+ else
+ Com_sprintf(pathname, sizeof(pathname), "%s", filename);
+ length = FS_FOpenFileRead( pathname, &fp, qfalse );
+ if (!fp) return NULL;
+
+ buffer = Z_Malloc(sizeof(script_t) + length + 1);
+ Com_Memset( buffer, 0, sizeof(script_t) + length + 1 );
+
+ script = (script_t *) buffer;
+ Com_Memset(script, 0, sizeof(script_t));
+ strcpy(script->filename, filename);
+ script->buffer = (char *) buffer + sizeof(script_t);
+ script->buffer[length] = 0;
+ script->length = length;
+ //pointer in script buffer
+ script->script_p = script->buffer;
+ //pointer in script buffer before reading token
+ script->lastscript_p = script->buffer;
+ //pointer to end of script buffer
+ script->end_p = &script->buffer[length];
+ //set if there's a token available in script->token
+ script->tokenavailable = 0;
+ //
+ script->line = 1;
+ script->lastline = 1;
+ //
+ Parse_SetScriptPunctuations(script, NULL);
+ //
+ FS_Read(script->buffer, length, fp);
+ FS_FCloseFile(fp);
+ //
+ script->length = COM_Compress(script->buffer);
+
+ return script;
+}
+
+/*
+===============
+Parse_LoadScriptMemory
+===============
+*/
+static script_t *Parse_LoadScriptMemory(char *ptr, int length, char *name)
+{
+ void *buffer;
+ script_t *script;
+
+ buffer = Z_Malloc(sizeof(script_t) + length + 1);
+ Com_Memset( buffer, 0, sizeof(script_t) + length + 1 );
+
+ script = (script_t *) buffer;
+ Com_Memset(script, 0, sizeof(script_t));
+ strcpy(script->filename, name);
+ script->buffer = (char *) buffer + sizeof(script_t);
+ script->buffer[length] = 0;
+ script->length = length;
+ //pointer in script buffer
+ script->script_p = script->buffer;
+ //pointer in script buffer before reading token
+ script->lastscript_p = script->buffer;
+ //pointer to end of script buffer
+ script->end_p = &script->buffer[length];
+ //set if there's a token available in script->token
+ script->tokenavailable = 0;
+ //
+ script->line = 1;
+ script->lastline = 1;
+ //
+ Parse_SetScriptPunctuations(script, NULL);
+ //
+ Com_Memcpy(script->buffer, ptr, length);
+ //
+ return script;
+}
+
+/*
+===============
+Parse_FreeScript
+===============
+*/
+static void Parse_FreeScript(script_t *script)
+{
+ if (script->punctuationtable) Z_Free(script->punctuationtable);
+ Z_Free(script);
+}
+
+/*
+===============
+Parse_SetBaseFolder
+===============
+*/
+static void Parse_SetBaseFolder(char *path)
+{
+ Com_sprintf(basefolder, sizeof(basefolder), path);
+}
+
+/*
+===============
+Parse_SourceError
+===============
+*/
+static void QDECL Parse_SourceError(source_t *source, char *str, ...)
+{
+ char text[1024];
+ va_list ap;
+
+ va_start(ap, str);
+ vsprintf(text, str, ap);
+ va_end(ap);
+ Com_Printf( "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text);
+}
+
+/*
+===============
+Parse_SourceWarning
+===============
+*/
+static void QDECL Parse_SourceWarning(source_t *source, char *str, ...)
+{
+ char text[1024];
+ va_list ap;
+
+ va_start(ap, str);
+ vsprintf(text, str, ap);
+ va_end(ap);
+ Com_Printf( "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text);
+}
+
+/*
+===============
+Parse_PushIndent
+===============
+*/
+static void Parse_PushIndent(source_t *source, int type, int skip)
+{
+ indent_t *indent;
+
+ indent = (indent_t *) Z_Malloc(sizeof(indent_t));
+ indent->type = type;
+ indent->script = source->scriptstack;
+ indent->skip = (skip != 0);
+ source->skip += indent->skip;
+ indent->next = source->indentstack;
+ source->indentstack = indent;
+}
+
+/*
+===============
+Parse_PopIndent
+===============
+*/
+static void Parse_PopIndent(source_t *source, int *type, int *skip)
+{
+ indent_t *indent;
+
+ *type = 0;
+ *skip = 0;
+
+ indent = source->indentstack;
+ if (!indent) return;
+
+ //must be an indent from the current script
+ if (source->indentstack->script != source->scriptstack) return;
+
+ *type = indent->type;
+ *skip = indent->skip;
+ source->indentstack = source->indentstack->next;
+ source->skip -= indent->skip;
+ Z_Free(indent);
+}
+
+/*
+===============
+Parse_PushScript
+===============
+*/
+static void Parse_PushScript(source_t *source, script_t *script)
+{
+ script_t *s;
+
+ for (s = source->scriptstack; s; s = s->next)
+ {
+ if (!Q_stricmp(s->filename, script->filename))
+ {
+ Parse_SourceError(source, "%s recursively included", script->filename);
+ return;
+ }
+ }
+ //push the script on the script stack
+ script->next = source->scriptstack;
+ source->scriptstack = script;
+}
+
+/*
+===============
+Parse_CopyToken
+===============
+*/
+static token_t *Parse_CopyToken(token_t *token)
+{
+ token_t *t;
+
+// t = (token_t *) malloc(sizeof(token_t));
+ t = (token_t *) Z_Malloc(sizeof(token_t));
+// t = freetokens;
+ if (!t)
+ {
+ Com_Error(ERR_FATAL, "out of token space\n");
+ return NULL;
+ }
+// freetokens = freetokens->next;
+ Com_Memcpy(t, token, sizeof(token_t));
+ t->next = NULL;
+ numtokens++;
+ return t;
+}
+
+/*
+===============
+Parse_FreeToken
+===============
+*/
+static void Parse_FreeToken(token_t *token)
+{
+ //free(token);
+ Z_Free(token);
+// token->next = freetokens;
+// freetokens = token;
+ numtokens--;
+}
+
+/*
+===============
+Parse_ReadSourceToken
+===============
+*/
+static int Parse_ReadSourceToken(source_t *source, token_t *token)
+{
+ token_t *t;
+ script_t *script;
+ int type, skip;
+
+ //if there's no token already available
+ while(!source->tokens)
+ {
+ //if there's a token to read from the script
+ if (Parse_ReadSciptToken(source->scriptstack, token)) return qtrue;
+ //if at the end of the script
+ if (Parse_EndOfScript(source->scriptstack))
+ {
+ //remove all indents of the script
+ while(source->indentstack &&
+ source->indentstack->script == source->scriptstack)
+ {
+ Parse_SourceWarning(source, "missing #endif");
+ Parse_PopIndent(source, &type, &skip);
+ }
+ }
+ //if this was the initial script
+ if (!source->scriptstack->next) return qfalse;
+ //remove the script and return to the last one
+ script = source->scriptstack;
+ source->scriptstack = source->scriptstack->next;
+ Parse_FreeScript(script);
+ }
+ //copy the already available token
+ Com_Memcpy(token, source->tokens, sizeof(token_t));
+ //free the read token
+ t = source->tokens;
+ source->tokens = source->tokens->next;
+ Parse_FreeToken(t);
+ return qtrue;
+}
+
+/*
+===============
+Parse_UnreadSourceToken
+===============
+*/
+static int Parse_UnreadSourceToken(source_t *source, token_t *token)
+{
+ token_t *t;
+
+ t = Parse_CopyToken(token);
+ t->next = source->tokens;
+ source->tokens = t;
+ return qtrue;
+}
+
+/*
+===============
+Parse_ReadDefineParms
+===============
+*/
+static int Parse_ReadDefineParms(source_t *source, define_t *define, token_t **parms, int maxparms)
+{
+ token_t token, *t, *last;
+ int i, done, lastcomma, numparms, indent;
+
+ if (!Parse_ReadSourceToken(source, &token))
+ {
+ Parse_SourceError(source, "define %s missing parms", define->name);
+ return qfalse;
+ }
+ //
+ if (define->numparms > maxparms)
+ {
+ Parse_SourceError(source, "define with more than %d parameters", maxparms);
+ return qfalse;
+ }
+ //
+ for (i = 0; i < define->numparms; i++) parms[i] = NULL;
+ //if no leading "("
+ if (strcmp(token.string, "("))
+ {
+ Parse_UnreadSourceToken(source, &token);
+ Parse_SourceError(source, "define %s missing parms", define->name);
+ return qfalse;
+ }
+ //read the define parameters
+ for (done = 0, numparms = 0, indent = 0; !done;)
+ {
+ if (numparms >= maxparms)
+ {
+ Parse_SourceError(source, "define %s with too many parms", define->name);
+ return qfalse;
+ }
+ if (numparms >= define->numparms)
+ {
+ Parse_SourceWarning(source, "define %s has too many parms", define->name);
+ return qfalse;
+ }
+ parms[numparms] = NULL;
+ lastcomma = 1;
+ last = NULL;
+ while(!done)
+ {
+ //
+ if (!Parse_ReadSourceToken(source, &token))
+ {
+ Parse_SourceError(source, "define %s incomplete", define->name);
+ return qfalse;
+ }
+ //
+ if (!strcmp(token.string, ","))
+ {
+ if (indent <= 0)
+ {
+ if (lastcomma) Parse_SourceWarning(source, "too many comma's");
+ lastcomma = 1;
+ break;
+ }
+ }
+ lastcomma = 0;
+ //
+ if (!strcmp(token.string, "("))
+ {
+ indent++;
+ continue;
+ }
+ else if (!strcmp(token.string, ")"))
+ {
+ if (--indent <= 0)
+ {
+ if (!parms[define->numparms-1])
+ {
+ Parse_SourceWarning(source, "too few define parms");
+ }
+ done = 1;
+ break;
+ }
+ }
+ //
+ if (numparms < define->numparms)
+ {
+ //
+ t = Parse_CopyToken(&token);
+ t->next = NULL;
+ if (last) last->next = t;
+ else parms[numparms] = t;
+ last = t;
+ }
+ }
+ numparms++;
+ }
+ return qtrue;
+}
+
+/*
+===============
+Parse_StringizeTokens
+===============
+*/
+static int Parse_StringizeTokens(token_t *tokens, token_t *token)
+{
+ token_t *t;
+
+ token->type = TT_STRING;
+ token->whitespace_p = NULL;
+ token->endwhitespace_p = NULL;
+ token->string[0] = '\0';
+ strcat(token->string, "\"");
+ for (t = tokens; t; t = t->next)
+ {
+ strncat(token->string, t->string, MAX_TOKEN_CHARS - strlen(token->string));
+ }
+ strncat(token->string, "\"", MAX_TOKEN_CHARS - strlen(token->string));
+ return qtrue;
+}
+
+/*
+===============
+Parse_MergeTokens
+===============
+*/
+static int Parse_MergeTokens(token_t *t1, token_t *t2)
+{
+ //merging of a name with a name or number
+ if (t1->type == TT_NAME && (t2->type == TT_NAME || t2->type == TT_NUMBER))
+ {
+ strcat(t1->string, t2->string);
+ return qtrue;
+ }
+ //merging of two strings
+ if (t1->type == TT_STRING && t2->type == TT_STRING)
+ {
+ //remove trailing double quote
+ t1->string[strlen(t1->string)-1] = '\0';
+ //concat without leading double quote
+ strcat(t1->string, &t2->string[1]);
+ return qtrue;
+ }
+ //FIXME: merging of two number of the same sub type
+ return qfalse;
+}
+
+/*
+===============
+Parse_NameHash
+===============
+*/
+//char primes[16] = {1, 3, 5, 7, 11, 13, 17, 19, 23, 27, 29, 31, 37, 41, 43, 47};
+static int Parse_NameHash(char *name)
+{
+ int register hash, i;
+
+ hash = 0;
+ for (i = 0; name[i] != '\0'; i++)
+ {
+ hash += name[i] * (119 + i);
+ //hash += (name[i] << 7) + i;
+ //hash += (name[i] << (i&15));
+ }
+ hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (DEFINEHASHSIZE-1);
+ return hash;
+}
+
+/*
+===============
+Parse_AddDefineToHash
+===============
+*/
+static void Parse_AddDefineToHash(define_t *define, define_t **definehash)
+{
+ int hash;
+
+ hash = Parse_NameHash(define->name);
+ define->hashnext = definehash[hash];
+ definehash[hash] = define;
+}
+
+/*
+===============
+Parse_FindHashedDefine
+===============
+*/
+static define_t *Parse_FindHashedDefine(define_t **definehash, char *name)
+{
+ define_t *d;
+ int hash;
+
+ hash = Parse_NameHash(name);
+ for (d = definehash[hash]; d; d = d->hashnext)
+ {
+ if (!strcmp(d->name, name)) return d;
+ }
+ return NULL;
+}
+
+/*
+===============
+Parse_FindDefineParm
+===============
+*/
+static int Parse_FindDefineParm(define_t *define, char *name)
+{
+ token_t *p;
+ int i;
+
+ i = 0;
+ for (p = define->parms; p; p = p->next)
+ {
+ if (!strcmp(p->string, name)) return i;
+ i++;
+ }
+ return -1;
+}
+
+/*
+===============
+Parse_FreeDefine
+===============
+*/
+static void Parse_FreeDefine(define_t *define)
+{
+ token_t *t, *next;
+
+ //free the define parameters
+ for (t = define->parms; t; t = next)
+ {
+ next = t->next;
+ Parse_FreeToken(t);
+ }
+ //free the define tokens
+ for (t = define->tokens; t; t = next)
+ {
+ next = t->next;
+ Parse_FreeToken(t);
+ }
+ //free the define
+ Z_Free(define);
+}
+
+/*
+===============
+Parse_ExpandBuiltinDefine
+===============
+*/
+static int Parse_ExpandBuiltinDefine(source_t *source, token_t *deftoken, define_t *define,
+ token_t **firsttoken, token_t **lasttoken)
+{
+ token_t *token;
+#ifdef _WIN32
+ unsigned long t; // time_t t; //to prevent LCC warning
+#else
+ time_t t;
+#endif
+
+ char *curtime;
+
+ token = Parse_CopyToken(deftoken);
+ switch(define->builtin)
+ {
+ case BUILTIN_LINE:
+ {
+ sprintf(token->string, "%d", deftoken->line);
+ token->intvalue = deftoken->line;
+ token->floatvalue = deftoken->line;
+ token->type = TT_NUMBER;
+ token->subtype = TT_DECIMAL | TT_INTEGER;
+ *firsttoken = token;
+ *lasttoken = token;
+ break;
+ }
+ case BUILTIN_FILE:
+ {
+ strcpy(token->string, source->scriptstack->filename);
+ token->type = TT_NAME;
+ token->subtype = strlen(token->string);
+ *firsttoken = token;
+ *lasttoken = token;
+ break;
+ }
+ case BUILTIN_DATE:
+ {
+ t = time(NULL);
+ curtime = ctime(&t);
+ strcpy(token->string, "\"");
+ strncat(token->string, curtime+4, 7);
+ strncat(token->string+7, curtime+20, 4);
+ strcat(token->string, "\"");
+ free(curtime);
+ token->type = TT_NAME;
+ token->subtype = strlen(token->string);
+ *firsttoken = token;
+ *lasttoken = token;
+ break;
+ }
+ case BUILTIN_TIME:
+ {
+ t = time(NULL);
+ curtime = ctime(&t);
+ strcpy(token->string, "\"");
+ strncat(token->string, curtime+11, 8);
+ strcat(token->string, "\"");
+ free(curtime);
+ token->type = TT_NAME;
+ token->subtype = strlen(token->string);
+ *firsttoken = token;
+ *lasttoken = token;
+ break;
+ }
+ case BUILTIN_STDC:
+ default:
+ {
+ *firsttoken = NULL;
+ *lasttoken = NULL;
+ break;
+ }
+ }
+ return qtrue;
+}
+
+/*
+===============
+Parse_ExpandDefine
+===============
+*/
+static int Parse_ExpandDefine(source_t *source, token_t *deftoken, define_t *define,
+ token_t **firsttoken, token_t **lasttoken)
+{
+ token_t *parms[MAX_DEFINEPARMS], *dt, *pt, *t;
+ token_t *t1, *t2, *first, *last, *nextpt, token;
+ int parmnum, i;
+
+ //if it is a builtin define
+ if (define->builtin)
+ {
+ return Parse_ExpandBuiltinDefine(source, deftoken, define, firsttoken, lasttoken);
+ }
+ //if the define has parameters
+ if (define->numparms)
+ {
+ if (!Parse_ReadDefineParms(source, define, parms, MAX_DEFINEPARMS)) return qfalse;
+ }
+ //empty list at first
+ first = NULL;
+ last = NULL;
+ //create a list with tokens of the expanded define
+ for (dt = define->tokens; dt; dt = dt->next)
+ {
+ parmnum = -1;
+ //if the token is a name, it could be a define parameter
+ if (dt->type == TT_NAME)
+ {
+ parmnum = Parse_FindDefineParm(define, dt->string);
+ }
+ //if it is a define parameter
+ if (parmnum >= 0)
+ {
+ for (pt = parms[parmnum]; pt; pt = pt->next)
+ {
+ t = Parse_CopyToken(pt);
+ //add the token to the list
+ t->next = NULL;
+ if (last) last->next = t;
+ else first = t;
+ last = t;
+ }
+ }
+ else
+ {
+ //if stringizing operator
+ if (dt->string[0] == '#' && dt->string[1] == '\0')
+ {
+ //the stringizing operator must be followed by a define parameter
+ if (dt->next) parmnum = Parse_FindDefineParm(define, dt->next->string);
+ else parmnum = -1;
+ //
+ if (parmnum >= 0)
+ {
+ //step over the stringizing operator
+ dt = dt->next;
+ //stringize the define parameter tokens
+ if (!Parse_StringizeTokens(parms[parmnum], &token))
+ {
+ Parse_SourceError(source, "can't stringize tokens");
+ return qfalse;
+ }
+ t = Parse_CopyToken(&token);
+ }
+ else
+ {
+ Parse_SourceWarning(source, "stringizing operator without define parameter");
+ continue;
+ }
+ }
+ else
+ {
+ t = Parse_CopyToken(dt);
+ }
+ //add the token to the list
+ t->next = NULL;
+ if (last) last->next = t;
+ else first = t;
+ last = t;
+ }
+ }
+ //check for the merging operator
+ for (t = first; t; )
+ {
+ if (t->next)
+ {
+ //if the merging operator
+ if (t->next->string[0] == '#' && t->next->string[1] == '#')
+ {
+ t1 = t;
+ t2 = t->next->next;
+ if (t2)
+ {
+ if (!Parse_MergeTokens(t1, t2))
+ {
+ Parse_SourceError(source, "can't merge %s with %s", t1->string, t2->string);
+ return qfalse;
+ }
+ Parse_FreeToken(t1->next);
+ t1->next = t2->next;
+ if (t2 == last) last = t1;
+ Parse_FreeToken(t2);
+ continue;
+ }
+ }
+ }
+ t = t->next;
+ }
+ //store the first and last token of the list
+ *firsttoken = first;
+ *lasttoken = last;
+ //free all the parameter tokens
+ for (i = 0; i < define->numparms; i++)
+ {
+ for (pt = parms[i]; pt; pt = nextpt)
+ {
+ nextpt = pt->next;
+ Parse_FreeToken(pt);
+ }
+ }
+ //
+ return qtrue;
+}
+
+/*
+===============
+Parse_ExpandDefineIntoSource
+===============
+*/
+static int Parse_ExpandDefineIntoSource(source_t *source, token_t *deftoken, define_t *define)
+{
+ token_t *firsttoken, *lasttoken;
+
+ if (!Parse_ExpandDefine(source, deftoken, define, &firsttoken, &lasttoken)) return qfalse;
+
+ if (firsttoken && lasttoken)
+ {
+ lasttoken->next = source->tokens;
+ source->tokens = firsttoken;
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+===============
+Parse_ConvertPath
+===============
+*/
+static void Parse_ConvertPath(char *path)
+{
+ char *ptr;
+
+ //remove double path seperators
+ for (ptr = path; *ptr;)
+ {
+ if ((*ptr == '\\' || *ptr == '/') &&
+ (*(ptr+1) == '\\' || *(ptr+1) == '/'))
+ {
+ strcpy(ptr, ptr+1);
+ }
+ else
+ {
+ ptr++;
+ }
+ }
+ //set OS dependent path seperators
+ for (ptr = path; *ptr;)
+ {
+ if (*ptr == '/' || *ptr == '\\') *ptr = PATH_SEP;
+ ptr++;
+ }
+}
+
+/*
+===============
+Parse_ReadLine
+
+reads a token from the current line, continues reading on the next
+line only if a backslash '\' is encountered.
+===============
+*/
+static int Parse_ReadLine(source_t *source, token_t *token)
+{
+ int crossline;
+
+ crossline = 0;
+ do
+ {
+ if (!Parse_ReadSourceToken(source, token)) return qfalse;
+
+ if (token->linescrossed > crossline)
+ {
+ Parse_UnreadSourceToken(source, token);
+ return qfalse;
+ }
+ crossline = 1;
+ } while(!strcmp(token->string, "\\"));
+ return qtrue;
+}
+
+/*
+===============
+Parse_OperatorPriority
+===============
+*/
+typedef struct operator_s
+{
+ int operator;
+ int priority;
+ int parentheses;
+ struct operator_s *prev, *next;
+} operator_t;
+
+typedef struct value_s
+{
+ signed long int intvalue;
+ double floatvalue;
+ int parentheses;
+ struct value_s *prev, *next;
+} value_t;
+
+static int Parse_OperatorPriority(int op)
+{
+ switch(op)
+ {
+ case P_MUL: return 15;
+ case P_DIV: return 15;
+ case P_MOD: return 15;
+ case P_ADD: return 14;
+ case P_SUB: return 14;
+
+ case P_LOGIC_AND: return 7;
+ case P_LOGIC_OR: return 6;
+ case P_LOGIC_GEQ: return 12;
+ case P_LOGIC_LEQ: return 12;
+ case P_LOGIC_EQ: return 11;
+ case P_LOGIC_UNEQ: return 11;
+
+ case P_LOGIC_NOT: return 16;
+ case P_LOGIC_GREATER: return 12;
+ case P_LOGIC_LESS: return 12;
+
+ case P_RSHIFT: return 13;
+ case P_LSHIFT: return 13;
+
+ case P_BIN_AND: return 10;
+ case P_BIN_OR: return 8;
+ case P_BIN_XOR: return 9;
+ case P_BIN_NOT: return 16;
+
+ case P_COLON: return 5;
+ case P_QUESTIONMARK: return 5;
+ }
+ return qfalse;
+}
+
+#define MAX_VALUES 64
+#define MAX_OPERATORS 64
+#define AllocValue(val) \
+ if (numvalues >= MAX_VALUES) { \
+ Parse_SourceError(source, "out of value space\n"); \
+ error = 1; \
+ break; \
+ } \
+ else \
+ val = &value_heap[numvalues++];
+#define FreeValue(val)
+//
+#define AllocOperator(op) \
+ if (numoperators >= MAX_OPERATORS) { \
+ Parse_SourceError(source, "out of operator space\n"); \
+ error = 1; \
+ break; \
+ } \
+ else \
+ op = &operator_heap[numoperators++];
+#define FreeOperator(op)
+
+/*
+===============
+Parse_EvaluateTokens
+===============
+*/
+static int Parse_EvaluateTokens(source_t *source, token_t *tokens, signed long int *intvalue,
+ double *floatvalue, int integer)
+{
+ operator_t *o, *firstoperator, *lastoperator;
+ value_t *v, *firstvalue, *lastvalue, *v1, *v2;
+ token_t *t;
+ int brace = 0;
+ int parentheses = 0;
+ int error = 0;
+ int lastwasvalue = 0;
+ int negativevalue = 0;
+ int questmarkintvalue = 0;
+ double questmarkfloatvalue = 0;
+ int gotquestmarkvalue = qfalse;
+ int lastoperatortype = 0;
+ //
+ operator_t operator_heap[MAX_OPERATORS];
+ int numoperators = 0;
+ value_t value_heap[MAX_VALUES];
+ int numvalues = 0;
+
+ firstoperator = lastoperator = NULL;
+ firstvalue = lastvalue = NULL;
+ if (intvalue) *intvalue = 0;
+ if (floatvalue) *floatvalue = 0;
+ for (t = tokens; t; t = t->next)
+ {
+ switch(t->type)
+ {
+ case TT_NAME:
+ {
+ if (lastwasvalue || negativevalue)
+ {
+ Parse_SourceError(source, "syntax error in #if/#elif");
+ error = 1;
+ break;
+ }
+ if (strcmp(t->string, "defined"))
+ {
+ Parse_SourceError(source, "undefined name %s in #if/#elif", t->string);
+ error = 1;
+ break;
+ }
+ t = t->next;
+ if (!strcmp(t->string, "("))
+ {
+ brace = qtrue;
+ t = t->next;
+ }
+ if (!t || t->type != TT_NAME)
+ {
+ Parse_SourceError(source, "defined without name in #if/#elif");
+ error = 1;
+ break;
+ }
+ //v = (value_t *) Z_Malloc(sizeof(value_t));
+ AllocValue(v);
+ if (Parse_FindHashedDefine(source->definehash, t->string))
+ {
+ v->intvalue = 1;
+ v->floatvalue = 1;
+ }
+ else
+ {
+ v->intvalue = 0;
+ v->floatvalue = 0;
+ }
+ v->parentheses = parentheses;
+ v->next = NULL;
+ v->prev = lastvalue;
+ if (lastvalue) lastvalue->next = v;
+ else firstvalue = v;
+ lastvalue = v;
+ if (brace)
+ {
+ t = t->next;
+ if (!t || strcmp(t->string, ")"))
+ {
+ Parse_SourceError(source, "defined without ) in #if/#elif");
+ error = 1;
+ break;
+ }
+ }
+ brace = qfalse;
+ // defined() creates a value
+ lastwasvalue = 1;
+ break;
+ }
+ case TT_NUMBER:
+ {
+ if (lastwasvalue)
+ {
+ Parse_SourceError(source, "syntax error in #if/#elif");
+ error = 1;
+ break;
+ }
+ //v = (value_t *) Z_Malloc(sizeof(value_t));
+ AllocValue(v);
+ if (negativevalue)
+ {
+ v->intvalue = - (signed int) t->intvalue;
+ v->floatvalue = - t->floatvalue;
+ }
+ else
+ {
+ v->intvalue = t->intvalue;
+ v->floatvalue = t->floatvalue;
+ }
+ v->parentheses = parentheses;
+ v->next = NULL;
+ v->prev = lastvalue;
+ if (lastvalue) lastvalue->next = v;
+ else firstvalue = v;
+ lastvalue = v;
+ //last token was a value
+ lastwasvalue = 1;
+ //
+ negativevalue = 0;
+ break;
+ }
+ case TT_PUNCTUATION:
+ {
+ if (negativevalue)
+ {
+ Parse_SourceError(source, "misplaced minus sign in #if/#elif");
+ error = 1;
+ break;
+ }
+ if (t->subtype == P_PARENTHESESOPEN)
+ {
+ parentheses++;
+ break;
+ }
+ else if (t->subtype == P_PARENTHESESCLOSE)
+ {
+ parentheses--;
+ if (parentheses < 0)
+ {
+ Parse_SourceError(source, "too many ) in #if/#elsif");
+ error = 1;
+ }
+ break;
+ }
+ //check for invalid operators on floating point values
+ if (!integer)
+ {
+ if (t->subtype == P_BIN_NOT || t->subtype == P_MOD ||
+ t->subtype == P_RSHIFT || t->subtype == P_LSHIFT ||
+ t->subtype == P_BIN_AND || t->subtype == P_BIN_OR ||
+ t->subtype == P_BIN_XOR)
+ {
+ Parse_SourceError(source, "illigal operator %s on floating point operands\n", t->string);
+ error = 1;
+ break;
+ }
+ }
+ switch(t->subtype)
+ {
+ case P_LOGIC_NOT:
+ case P_BIN_NOT:
+ {
+ if (lastwasvalue)
+ {
+ Parse_SourceError(source, "! or ~ after value in #if/#elif");
+ error = 1;
+ break;
+ }
+ break;
+ }
+ case P_INC:
+ case P_DEC:
+ {
+ Parse_SourceError(source, "++ or -- used in #if/#elif");
+ break;
+ }
+ case P_SUB:
+ {
+ if (!lastwasvalue)
+ {
+ negativevalue = 1;
+ break;
+ }
+ }
+
+ case P_MUL:
+ case P_DIV:
+ case P_MOD:
+ case P_ADD:
+
+ case P_LOGIC_AND:
+ case P_LOGIC_OR:
+ case P_LOGIC_GEQ:
+ case P_LOGIC_LEQ:
+ case P_LOGIC_EQ:
+ case P_LOGIC_UNEQ:
+
+ case P_LOGIC_GREATER:
+ case P_LOGIC_LESS:
+
+ case P_RSHIFT:
+ case P_LSHIFT:
+
+ case P_BIN_AND:
+ case P_BIN_OR:
+ case P_BIN_XOR:
+
+ case P_COLON:
+ case P_QUESTIONMARK:
+ {
+ if (!lastwasvalue)
+ {
+ Parse_SourceError(source, "operator %s after operator in #if/#elif", t->string);
+ error = 1;
+ break;
+ }
+ break;
+ }
+ default:
+ {
+ Parse_SourceError(source, "invalid operator %s in #if/#elif", t->string);
+ error = 1;
+ break;
+ }
+ }
+ if (!error && !negativevalue)
+ {
+ //o = (operator_t *) Z_Malloc(sizeof(operator_t));
+ AllocOperator(o);
+ o->operator = t->subtype;
+ o->priority = Parse_OperatorPriority(t->subtype);
+ o->parentheses = parentheses;
+ o->next = NULL;
+ o->prev = lastoperator;
+ if (lastoperator) lastoperator->next = o;
+ else firstoperator = o;
+ lastoperator = o;
+ lastwasvalue = 0;
+ }
+ break;
+ }
+ default:
+ {
+ Parse_SourceError(source, "unknown %s in #if/#elif", t->string);
+ error = 1;
+ break;
+ }
+ }
+ if (error) break;
+ }
+ if (!error)
+ {
+ if (!lastwasvalue)
+ {
+ Parse_SourceError(source, "trailing operator in #if/#elif");
+ error = 1;
+ }
+ else if (parentheses)
+ {
+ Parse_SourceError(source, "too many ( in #if/#elif");
+ error = 1;
+ }
+ }
+ //
+ gotquestmarkvalue = qfalse;
+ questmarkintvalue = 0;
+ questmarkfloatvalue = 0;
+ //while there are operators
+ while(!error && firstoperator)
+ {
+ v = firstvalue;
+ for (o = firstoperator; o->next; o = o->next)
+ {
+ //if the current operator is nested deeper in parentheses
+ //than the next operator
+ if (o->parentheses > o->next->parentheses) break;
+ //if the current and next operator are nested equally deep in parentheses
+ if (o->parentheses == o->next->parentheses)
+ {
+ //if the priority of the current operator is equal or higher
+ //than the priority of the next operator
+ if (o->priority >= o->next->priority) break;
+ }
+ //if the arity of the operator isn't equal to 1
+ if (o->operator != P_LOGIC_NOT
+ && o->operator != P_BIN_NOT) v = v->next;
+ //if there's no value or no next value
+ if (!v)
+ {
+ Parse_SourceError(source, "mising values in #if/#elif");
+ error = 1;
+ break;
+ }
+ }
+ if (error) break;
+ v1 = v;
+ v2 = v->next;
+ switch(o->operator)
+ {
+ case P_LOGIC_NOT: v1->intvalue = !v1->intvalue;
+ v1->floatvalue = !v1->floatvalue; break;
+ case P_BIN_NOT: v1->intvalue = ~v1->intvalue;
+ break;
+ case P_MUL: v1->intvalue *= v2->intvalue;
+ v1->floatvalue *= v2->floatvalue; break;
+ case P_DIV: if (!v2->intvalue || !v2->floatvalue)
+ {
+ Parse_SourceError(source, "divide by zero in #if/#elif\n");
+ error = 1;
+ break;
+ }
+ v1->intvalue /= v2->intvalue;
+ v1->floatvalue /= v2->floatvalue; break;
+ case P_MOD: if (!v2->intvalue)
+ {
+ Parse_SourceError(source, "divide by zero in #if/#elif\n");
+ error = 1;
+ break;
+ }
+ v1->intvalue %= v2->intvalue; break;
+ case P_ADD: v1->intvalue += v2->intvalue;
+ v1->floatvalue += v2->floatvalue; break;
+ case P_SUB: v1->intvalue -= v2->intvalue;
+ v1->floatvalue -= v2->floatvalue; break;
+ case P_LOGIC_AND: v1->intvalue = v1->intvalue && v2->intvalue;
+ v1->floatvalue = v1->floatvalue && v2->floatvalue; break;
+ case P_LOGIC_OR: v1->intvalue = v1->intvalue || v2->intvalue;
+ v1->floatvalue = v1->floatvalue || v2->floatvalue; break;
+ case P_LOGIC_GEQ: v1->intvalue = v1->intvalue >= v2->intvalue;
+ v1->floatvalue = v1->floatvalue >= v2->floatvalue; break;
+ case P_LOGIC_LEQ: v1->intvalue = v1->intvalue <= v2->intvalue;
+ v1->floatvalue = v1->floatvalue <= v2->floatvalue; break;
+ case P_LOGIC_EQ: v1->intvalue = v1->intvalue == v2->intvalue;
+ v1->floatvalue = v1->floatvalue == v2->floatvalue; break;
+ case P_LOGIC_UNEQ: v1->intvalue = v1->intvalue != v2->intvalue;
+ v1->floatvalue = v1->floatvalue != v2->floatvalue; break;
+ case P_LOGIC_GREATER: v1->intvalue = v1->intvalue > v2->intvalue;
+ v1->floatvalue = v1->floatvalue > v2->floatvalue; break;
+ case P_LOGIC_LESS: v1->intvalue = v1->intvalue < v2->intvalue;
+ v1->floatvalue = v1->floatvalue < v2->floatvalue; break;
+ case P_RSHIFT: v1->intvalue >>= v2->intvalue;
+ break;
+ case P_LSHIFT: v1->intvalue <<= v2->intvalue;
+ break;
+ case P_BIN_AND: v1->intvalue &= v2->intvalue;
+ break;
+ case P_BIN_OR: v1->intvalue |= v2->intvalue;
+ break;
+ case P_BIN_XOR: v1->intvalue ^= v2->intvalue;
+ break;
+ case P_COLON:
+ {
+ if (!gotquestmarkvalue)
+ {
+ Parse_SourceError(source, ": without ? in #if/#elif");
+ error = 1;
+ break;
+ }
+ if (integer)
+ {
+ if (!questmarkintvalue) v1->intvalue = v2->intvalue;
+ }
+ else
+ {
+ if (!questmarkfloatvalue) v1->floatvalue = v2->floatvalue;
+ }
+ gotquestmarkvalue = qfalse;
+ break;
+ }
+ case P_QUESTIONMARK:
+ {
+ if (gotquestmarkvalue)
+ {
+ Parse_SourceError(source, "? after ? in #if/#elif");
+ error = 1;
+ break;
+ }
+ questmarkintvalue = v1->intvalue;
+ questmarkfloatvalue = v1->floatvalue;
+ gotquestmarkvalue = qtrue;
+ break;
+ }
+ }
+ if (error) break;
+ lastoperatortype = o->operator;
+ //if not an operator with arity 1
+ if (o->operator != P_LOGIC_NOT
+ && o->operator != P_BIN_NOT)
+ {
+ //remove the second value if not question mark operator
+ if (o->operator != P_QUESTIONMARK) v = v->next;
+ //
+ if (v->prev) v->prev->next = v->next;
+ else firstvalue = v->next;
+ if (v->next) v->next->prev = v->prev;
+ else lastvalue = v->prev;
+ //Z_Free(v);
+ FreeValue(v);
+ }
+ //remove the operator
+ if (o->prev) o->prev->next = o->next;
+ else firstoperator = o->next;
+ if (o->next) o->next->prev = o->prev;
+ else lastoperator = o->prev;
+ //Z_Free(o);
+ FreeOperator(o);
+ }
+ if (firstvalue)
+ {
+ if (intvalue) *intvalue = firstvalue->intvalue;
+ if (floatvalue) *floatvalue = firstvalue->floatvalue;
+ }
+ for (o = firstoperator; o; o = lastoperator)
+ {
+ lastoperator = o->next;
+ //Z_Free(o);
+ FreeOperator(o);
+ }
+ for (v = firstvalue; v; v = lastvalue)
+ {
+ lastvalue = v->next;
+ //Z_Free(v);
+ FreeValue(v);
+ }
+ if (!error) return qtrue;
+ if (intvalue) *intvalue = 0;
+ if (floatvalue) *floatvalue = 0;
+ return qfalse;
+}
+
+/*
+===============
+Parse_Evaluate
+===============
+*/
+static int Parse_Evaluate(source_t *source, signed long int *intvalue,
+ double *floatvalue, int integer)
+{
+ token_t token, *firsttoken, *lasttoken;
+ token_t *t, *nexttoken;
+ define_t *define;
+ int defined = qfalse;
+
+ if (intvalue) *intvalue = 0;
+ if (floatvalue) *floatvalue = 0;
+ //
+ if (!Parse_ReadLine(source, &token))
+ {
+ Parse_SourceError(source, "no value after #if/#elif");
+ return qfalse;
+ }
+ firsttoken = NULL;
+ lasttoken = NULL;
+ do
+ {
+ //if the token is a name
+ if (token.type == TT_NAME)
+ {
+ if (defined)
+ {
+ defined = qfalse;
+ t = Parse_CopyToken(&token);
+ t->next = NULL;
+ if (lasttoken) lasttoken->next = t;
+ else firsttoken = t;
+ lasttoken = t;
+ }
+ else if (!strcmp(token.string, "defined"))
+ {
+ defined = qtrue;
+ t = Parse_CopyToken(&token);
+ t->next = NULL;
+ if (lasttoken) lasttoken->next = t;
+ else firsttoken = t;
+ lasttoken = t;
+ }
+ else
+ {
+ //then it must be a define
+ define = Parse_FindHashedDefine(source->definehash, token.string);
+ if (!define)
+ {
+ Parse_SourceError(source, "can't evaluate %s, not defined", token.string);
+ return qfalse;
+ }
+ if (!Parse_ExpandDefineIntoSource(source, &token, define)) return qfalse;
+ }
+ }
+ //if the token is a number or a punctuation
+ else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION)
+ {
+ t = Parse_CopyToken(&token);
+ t->next = NULL;
+ if (lasttoken) lasttoken->next = t;
+ else firsttoken = t;
+ lasttoken = t;
+ }
+ else //can't evaluate the token
+ {
+ Parse_SourceError(source, "can't evaluate %s", token.string);
+ return qfalse;
+ }
+ } while(Parse_ReadLine(source, &token));
+ //
+ if (!Parse_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return qfalse;
+ //
+ for (t = firsttoken; t; t = nexttoken)
+ {
+ nexttoken = t->next;
+ Parse_FreeToken(t);
+ }
+ //
+ return qtrue;
+}
+
+/*
+===============
+Parse_DollarEvaluate
+===============
+*/
+static int Parse_DollarEvaluate(source_t *source, signed long int *intvalue,
+ double *floatvalue, int integer)
+{
+ int indent, defined = qfalse;
+ token_t token, *firsttoken, *lasttoken;
+ token_t *t, *nexttoken;
+ define_t *define;
+
+ if (intvalue) *intvalue = 0;
+ if (floatvalue) *floatvalue = 0;
+ //
+ if (!Parse_ReadSourceToken(source, &token))
+ {
+ Parse_SourceError(source, "no leading ( after $evalint/$evalfloat");
+ return qfalse;
+ }
+ if (!Parse_ReadSourceToken(source, &token))
+ {
+ Parse_SourceError(source, "nothing to evaluate");
+ return qfalse;
+ }
+ indent = 1;
+ firsttoken = NULL;
+ lasttoken = NULL;
+ do
+ {
+ //if the token is a name
+ if (token.type == TT_NAME)
+ {
+ if (defined)
+ {
+ defined = qfalse;
+ t = Parse_CopyToken(&token);
+ t->next = NULL;
+ if (lasttoken) lasttoken->next = t;
+ else firsttoken = t;
+ lasttoken = t;
+ }
+ else if (!strcmp(token.string, "defined"))
+ {
+ defined = qtrue;
+ t = Parse_CopyToken(&token);
+ t->next = NULL;
+ if (lasttoken) lasttoken->next = t;
+ else firsttoken = t;
+ lasttoken = t;
+ }
+ else
+ {
+ //then it must be a define
+ define = Parse_FindHashedDefine(source->definehash, token.string);
+ if (!define)
+ {
+ Parse_SourceError(source, "can't evaluate %s, not defined", token.string);
+ return qfalse;
+ }
+ if (!Parse_ExpandDefineIntoSource(source, &token, define)) return qfalse;
+ }
+ }
+ //if the token is a number or a punctuation
+ else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION)
+ {
+ if (*token.string == '(') indent++;
+ else if (*token.string == ')') indent--;
+ if (indent <= 0) break;
+ t = Parse_CopyToken(&token);
+ t->next = NULL;
+ if (lasttoken) lasttoken->next = t;
+ else firsttoken = t;
+ lasttoken = t;
+ }
+ else //can't evaluate the token
+ {
+ Parse_SourceError(source, "can't evaluate %s", token.string);
+ return qfalse;
+ }
+ } while(Parse_ReadSourceToken(source, &token));
+ //
+ if (!Parse_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return qfalse;
+ //
+ for (t = firsttoken; t; t = nexttoken)
+ {
+ nexttoken = t->next;
+ Parse_FreeToken(t);
+ }
+ //
+ return qtrue;
+}
+
+/*
+===============
+Parse_Directive_include
+===============
+*/
+static int Parse_Directive_include(source_t *source)
+{
+ script_t *script;
+ token_t token;
+ char path[MAX_QPATH];
+
+ if (source->skip > 0) return qtrue;
+ //
+ if (!Parse_ReadSourceToken(source, &token))
+ {
+ Parse_SourceError(source, "#include without file name");
+ return qfalse;
+ }
+ if (token.linescrossed > 0)
+ {
+ Parse_SourceError(source, "#include without file name");
+ return qfalse;
+ }
+ if (token.type == TT_STRING)
+ {
+ Parse_StripDoubleQuotes(token.string);
+ Parse_ConvertPath(token.string);
+ script = Parse_LoadScriptFile(token.string);
+ if (!script)
+ {
+ strcpy(path, source->includepath);
+ strcat(path, token.string);
+ script = Parse_LoadScriptFile(path);
+ }
+ }
+ else if (token.type == TT_PUNCTUATION && *token.string == '<')
+ {
+ strcpy(path, source->includepath);
+ while(Parse_ReadSourceToken(source, &token))
+ {
+ if (token.linescrossed > 0)
+ {
+ Parse_UnreadSourceToken(source, &token);
+ break;
+ }
+ if (token.type == TT_PUNCTUATION && *token.string == '>') break;
+ strncat(path, token.string, MAX_QPATH);
+ }
+ if (*token.string != '>')
+ {
+ Parse_SourceWarning(source, "#include missing trailing >");
+ }
+ if (!strlen(path))
+ {
+ Parse_SourceError(source, "#include without file name between < >");
+ return qfalse;
+ }
+ Parse_ConvertPath(path);
+ script = Parse_LoadScriptFile(path);
+ }
+ else
+ {
+ Parse_SourceError(source, "#include without file name");
+ return qfalse;
+ }
+ if (!script)
+ {
+ Parse_SourceError(source, "file %s not found", path);
+ return qfalse;
+ }
+ Parse_PushScript(source, script);
+ return qtrue;
+}
+
+/*
+===============
+Parse_WhiteSpaceBeforeToken
+===============
+*/
+static int Parse_WhiteSpaceBeforeToken(token_t *token)
+{
+ return token->endwhitespace_p - token->whitespace_p > 0;
+}
+
+/*
+===============
+Parse_ClearTokenWhiteSpace
+===============
+*/
+static void Parse_ClearTokenWhiteSpace(token_t *token)
+{
+ token->whitespace_p = NULL;
+ token->endwhitespace_p = NULL;
+ token->linescrossed = 0;
+}
+
+/*
+===============
+Parse_Directive_undef
+===============
+*/
+static int Parse_Directive_undef(source_t *source)
+{
+ token_t token;
+ define_t *define, *lastdefine;
+ int hash;
+
+ if (source->skip > 0) return qtrue;
+ //
+ if (!Parse_ReadLine(source, &token))
+ {
+ Parse_SourceError(source, "undef without name");
+ return qfalse;
+ }
+ if (token.type != TT_NAME)
+ {
+ Parse_UnreadSourceToken(source, &token);
+ Parse_SourceError(source, "expected name, found %s", token.string);
+ return qfalse;
+ }
+
+ hash = Parse_NameHash(token.string);
+ for (lastdefine = NULL, define = source->definehash[hash]; define; define = define->hashnext)
+ {
+ if (!strcmp(define->name, token.string))
+ {
+ if (define->flags & DEFINE_FIXED)
+ {
+ Parse_SourceWarning(source, "can't undef %s", token.string);
+ }
+ else
+ {
+ if (lastdefine) lastdefine->hashnext = define->hashnext;
+ else source->definehash[hash] = define->hashnext;
+ Parse_FreeDefine(define);
+ }
+ break;
+ }
+ lastdefine = define;
+ }
+ return qtrue;
+}
+
+/*
+===============
+Parse_Directive_elif
+===============
+*/
+static int Parse_Directive_elif(source_t *source)
+{
+ signed long int value;
+ int type, skip;
+
+ Parse_PopIndent(source, &type, &skip);
+ if (!type || type == INDENT_ELSE)
+ {
+ Parse_SourceError(source, "misplaced #elif");
+ return qfalse;
+ }
+ if (!Parse_Evaluate(source, &value, NULL, qtrue)) return qfalse;
+ skip = (value == 0);
+ Parse_PushIndent(source, INDENT_ELIF, skip);
+ return qtrue;
+}
+
+/*
+===============
+Parse_Directive_if
+===============
+*/
+static int Parse_Directive_if(source_t *source)
+{
+ signed long int value;
+ int skip;
+
+ if (!Parse_Evaluate(source, &value, NULL, qtrue)) return qfalse;
+ skip = (value == 0);
+ Parse_PushIndent(source, INDENT_IF, skip);
+ return qtrue;
+}
+
+/*
+===============
+Parse_Directive_line
+===============
+*/
+static int Parse_Directive_line(source_t *source)
+{
+ Parse_SourceError(source, "#line directive not supported");
+ return qfalse;
+}
+
+/*
+===============
+Parse_Directive_error
+===============
+*/
+static int Parse_Directive_error(source_t *source)
+{
+ token_t token;
+
+ strcpy(token.string, "");
+ Parse_ReadSourceToken(source, &token);
+ Parse_SourceError(source, "#error directive: %s", token.string);
+ return qfalse;
+}
+
+/*
+===============
+Parse_Directive_pragma
+===============
+*/
+static int Parse_Directive_pragma(source_t *source)
+{
+ token_t token;
+
+ Parse_SourceWarning(source, "#pragma directive not supported");
+ while(Parse_ReadLine(source, &token)) ;
+ return qtrue;
+}
+
+/*
+===============
+Parse_UnreadSignToken
+===============
+*/
+static void Parse_UnreadSignToken(source_t *source)
+{
+ token_t token;
+
+ token.line = source->scriptstack->line;
+ token.whitespace_p = source->scriptstack->script_p;
+ token.endwhitespace_p = source->scriptstack->script_p;
+ token.linescrossed = 0;
+ strcpy(token.string, "-");
+ token.type = TT_PUNCTUATION;
+ token.subtype = P_SUB;
+ Parse_UnreadSourceToken(source, &token);
+}
+
+/*
+===============
+Parse_Directive_eval
+===============
+*/
+static int Parse_Directive_eval(source_t *source)
+{
+ signed long int value;
+ token_t token;
+
+ if (!Parse_Evaluate(source, &value, NULL, qtrue)) return qfalse;
+ //
+ token.line = source->scriptstack->line;
+ token.whitespace_p = source->scriptstack->script_p;
+ token.endwhitespace_p = source->scriptstack->script_p;
+ token.linescrossed = 0;
+ sprintf(token.string, "%d", abs(value));
+ token.type = TT_NUMBER;
+ token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL;
+ Parse_UnreadSourceToken(source, &token);
+ if (value < 0) Parse_UnreadSignToken(source);
+ return qtrue;
+}
+
+/*
+===============
+Parse_Directive_evalfloat
+===============
+*/
+static int Parse_Directive_evalfloat(source_t *source)
+{
+ double value;
+ token_t token;
+
+ if (!Parse_Evaluate(source, NULL, &value, qfalse)) return qfalse;
+ token.line = source->scriptstack->line;
+ token.whitespace_p = source->scriptstack->script_p;
+ token.endwhitespace_p = source->scriptstack->script_p;
+ token.linescrossed = 0;
+ sprintf(token.string, "%1.2f", fabs(value));
+ token.type = TT_NUMBER;
+ token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL;
+ Parse_UnreadSourceToken(source, &token);
+ if (value < 0) Parse_UnreadSignToken(source);
+ return qtrue;
+}
+
+/*
+===============
+Parse_DollarDirective_evalint
+===============
+*/
+static int Parse_DollarDirective_evalint(source_t *source)
+{
+ signed long int value;
+ token_t token;
+
+ if (!Parse_DollarEvaluate(source, &value, NULL, qtrue)) return qfalse;
+ //
+ token.line = source->scriptstack->line;
+ token.whitespace_p = source->scriptstack->script_p;
+ token.endwhitespace_p = source->scriptstack->script_p;
+ token.linescrossed = 0;
+ sprintf(token.string, "%d", abs(value));
+ token.type = TT_NUMBER;
+ token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL;
+ token.intvalue = value;
+ token.floatvalue = value;
+ Parse_UnreadSourceToken(source, &token);
+ if (value < 0) Parse_UnreadSignToken(source);
+ return qtrue;
+}
+
+/*
+===============
+Parse_DollarDirective_evalfloat
+===============
+*/
+static int Parse_DollarDirective_evalfloat(source_t *source)
+{
+ double value;
+ token_t token;
+
+ if (!Parse_DollarEvaluate(source, NULL, &value, qfalse)) return qfalse;
+ token.line = source->scriptstack->line;
+ token.whitespace_p = source->scriptstack->script_p;
+ token.endwhitespace_p = source->scriptstack->script_p;
+ token.linescrossed = 0;
+ sprintf(token.string, "%1.2f", fabs(value));
+ token.type = TT_NUMBER;
+ token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL;
+ token.intvalue = (unsigned long) value;
+ token.floatvalue = value;
+ Parse_UnreadSourceToken(source, &token);
+ if (value < 0) Parse_UnreadSignToken(source);
+ return qtrue;
+}
+
+/*
+===============
+Parse_ReadDollarDirective
+===============
+*/
+directive_t dollardirectives[20] =
+{
+ {"evalint", Parse_DollarDirective_evalint},
+ {"evalfloat", Parse_DollarDirective_evalfloat},
+ {NULL, NULL}
+};
+
+static int Parse_ReadDollarDirective(source_t *source)
+{
+ token_t token;
+ int i;
+
+ //read the directive name
+ if (!Parse_ReadSourceToken(source, &token))
+ {
+ Parse_SourceError(source, "found $ without name");
+ return qfalse;
+ }
+ //directive name must be on the same line
+ if (token.linescrossed > 0)
+ {
+ Parse_UnreadSourceToken(source, &token);
+ Parse_SourceError(source, "found $ at end of line");
+ return qfalse;
+ }
+ //if if is a name
+ if (token.type == TT_NAME)
+ {
+ //find the precompiler directive
+ for (i = 0; dollardirectives[i].name; i++)
+ {
+ if (!strcmp(dollardirectives[i].name, token.string))
+ {
+ return dollardirectives[i].func(source);
+ }
+ }
+ }
+ Parse_UnreadSourceToken(source, &token);
+ Parse_SourceError(source, "unknown precompiler directive %s", token.string);
+ return qfalse;
+}
+
+/*
+===============
+Parse_Directive_if_def
+===============
+*/
+static int Parse_Directive_if_def(source_t *source, int type)
+{
+ token_t token;
+ define_t *d;
+ int skip;
+
+ if (!Parse_ReadLine(source, &token))
+ {
+ Parse_SourceError(source, "#ifdef without name");
+ return qfalse;
+ }
+ if (token.type != TT_NAME)
+ {
+ Parse_UnreadSourceToken(source, &token);
+ Parse_SourceError(source, "expected name after #ifdef, found %s", token.string);
+ return qfalse;
+ }
+ d = Parse_FindHashedDefine(source->definehash, token.string);
+ skip = (type == INDENT_IFDEF) == (d == NULL);
+ Parse_PushIndent(source, type, skip);
+ return qtrue;
+}
+
+/*
+===============
+Parse_Directive_ifdef
+===============
+*/
+static int Parse_Directive_ifdef(source_t *source)
+{
+ return Parse_Directive_if_def(source, INDENT_IFDEF);
+}
+
+/*
+===============
+Parse_Directive_ifndef
+===============
+*/
+static int Parse_Directive_ifndef(source_t *source)
+{
+ return Parse_Directive_if_def(source, INDENT_IFNDEF);
+}
+
+/*
+===============
+Parse_Directive_else
+===============
+*/
+static int Parse_Directive_else(source_t *source)
+{
+ int type, skip;
+
+ Parse_PopIndent(source, &type, &skip);
+ if (!type)
+ {
+ Parse_SourceError(source, "misplaced #else");
+ return qfalse;
+ }
+ if (type == INDENT_ELSE)
+ {
+ Parse_SourceError(source, "#else after #else");
+ return qfalse;
+ }
+ Parse_PushIndent(source, INDENT_ELSE, !skip);
+ return qtrue;
+}
+
+/*
+===============
+Parse_Directive_endif
+===============
+*/
+static int Parse_Directive_endif(source_t *source)
+{
+ int type, skip;
+
+ Parse_PopIndent(source, &type, &skip);
+ if (!type)
+ {
+ Parse_SourceError(source, "misplaced #endif");
+ return qfalse;
+ }
+ return qtrue;
+}
+
+/*
+===============
+Parse_CheckTokenString
+===============
+*/
+static int Parse_CheckTokenString(source_t *source, char *string)
+{
+ token_t tok;
+
+ if (!Parse_ReadToken(source, &tok)) return qfalse;
+ //if the token is available
+ if (!strcmp(tok.string, string)) return qtrue;
+ //
+ Parse_UnreadSourceToken(source, &tok);
+ return qfalse;
+}
+
+/*
+===============
+Parse_Directive_define
+===============
+*/
+static int Parse_Directive_define(source_t *source)
+{
+ token_t token, *t, *last;
+ define_t *define;
+
+ if (source->skip > 0) return qtrue;
+ //
+ if (!Parse_ReadLine(source, &token))
+ {
+ Parse_SourceError(source, "#define without name");
+ return qfalse;
+ }
+ if (token.type != TT_NAME)
+ {
+ Parse_UnreadSourceToken(source, &token);
+ Parse_SourceError(source, "expected name after #define, found %s", token.string);
+ return qfalse;
+ }
+ //check if the define already exists
+ define = Parse_FindHashedDefine(source->definehash, token.string);
+ if (define)
+ {
+ if (define->flags & DEFINE_FIXED)
+ {
+ Parse_SourceError(source, "can't redefine %s", token.string);
+ return qfalse;
+ }
+ Parse_SourceWarning(source, "redefinition of %s", token.string);
+ //unread the define name before executing the #undef directive
+ Parse_UnreadSourceToken(source, &token);
+ if (!Parse_Directive_undef(source)) return qfalse;
+ //if the define was not removed (define->flags & DEFINE_FIXED)
+ define = Parse_FindHashedDefine(source->definehash, token.string);
+ }
+ //allocate define
+ define = (define_t *) Z_Malloc(sizeof(define_t) + strlen(token.string) + 1);
+ Com_Memset(define, 0, sizeof(define_t));
+ define->name = (char *) define + sizeof(define_t);
+ strcpy(define->name, token.string);
+ //add the define to the source
+ Parse_AddDefineToHash(define, source->definehash);
+ //if nothing is defined, just return
+ if (!Parse_ReadLine(source, &token)) return qtrue;
+ //if it is a define with parameters
+ if (!Parse_WhiteSpaceBeforeToken(&token) && !strcmp(token.string, "("))
+ {
+ //read the define parameters
+ last = NULL;
+ if (!Parse_CheckTokenString(source, ")"))
+ {
+ while(1)
+ {
+ if (!Parse_ReadLine(source, &token))
+ {
+ Parse_SourceError(source, "expected define parameter");
+ return qfalse;
+ }
+ //if it isn't a name
+ if (token.type != TT_NAME)
+ {
+ Parse_SourceError(source, "invalid define parameter");
+ return qfalse;
+ }
+ //
+ if (Parse_FindDefineParm(define, token.string) >= 0)
+ {
+ Parse_SourceError(source, "two the same define parameters");
+ return qfalse;
+ }
+ //add the define parm
+ t = Parse_CopyToken(&token);
+ Parse_ClearTokenWhiteSpace(t);
+ t->next = NULL;
+ if (last) last->next = t;
+ else define->parms = t;
+ last = t;
+ define->numparms++;
+ //read next token
+ if (!Parse_ReadLine(source, &token))
+ {
+ Parse_SourceError(source, "define parameters not terminated");
+ return qfalse;
+ }
+ //
+ if (!strcmp(token.string, ")")) break;
+ //then it must be a comma
+ if (strcmp(token.string, ","))
+ {
+ Parse_SourceError(source, "define not terminated");
+ return qfalse;
+ }
+ }
+ }
+ if (!Parse_ReadLine(source, &token)) return qtrue;
+ }
+ //read the defined stuff
+ last = NULL;
+ do
+ {
+ t = Parse_CopyToken(&token);
+ if (t->type == TT_NAME && !strcmp(t->string, define->name))
+ {
+ Parse_SourceError(source, "recursive define (removed recursion)");
+ continue;
+ }
+ Parse_ClearTokenWhiteSpace(t);
+ t->next = NULL;
+ if (last) last->next = t;
+ else define->tokens = t;
+ last = t;
+ } while(Parse_ReadLine(source, &token));
+ //
+ if (last)
+ {
+ //check for merge operators at the beginning or end
+ if (!strcmp(define->tokens->string, "##") ||
+ !strcmp(last->string, "##"))
+ {
+ Parse_SourceError(source, "define with misplaced ##");
+ return qfalse;
+ }
+ }
+ return qtrue;
+}
+
+/*
+===============
+Parse_ReadDirective
+===============
+*/
+directive_t directives[20] =
+{
+ {"if", Parse_Directive_if},
+ {"ifdef", Parse_Directive_ifdef},
+ {"ifndef", Parse_Directive_ifndef},
+ {"elif", Parse_Directive_elif},
+ {"else", Parse_Directive_else},
+ {"endif", Parse_Directive_endif},
+ {"include", Parse_Directive_include},
+ {"define", Parse_Directive_define},
+ {"undef", Parse_Directive_undef},
+ {"line", Parse_Directive_line},
+ {"error", Parse_Directive_error},
+ {"pragma", Parse_Directive_pragma},
+ {"eval", Parse_Directive_eval},
+ {"evalfloat", Parse_Directive_evalfloat},
+ {NULL, NULL}
+};
+
+static int Parse_ReadDirective(source_t *source)
+{
+ token_t token;
+ int i;
+
+ //read the directive name
+ if (!Parse_ReadSourceToken(source, &token))
+ {
+ Parse_SourceError(source, "found # without name");
+ return qfalse;
+ }
+ //directive name must be on the same line
+ if (token.linescrossed > 0)
+ {
+ Parse_UnreadSourceToken(source, &token);
+ Parse_SourceError(source, "found # at end of line");
+ return qfalse;
+ }
+ //if if is a name
+ if (token.type == TT_NAME)
+ {
+ //find the precompiler directive
+ for (i = 0; directives[i].name; i++)
+ {
+ if (!strcmp(directives[i].name, token.string))
+ {
+ return directives[i].func(source);
+ }
+ }
+ }
+ Parse_SourceError(source, "unknown precompiler directive %s", token.string);
+ return qfalse;
+}
+
+/*
+===============
+Parse_UnreadToken
+===============
+*/
+static void Parse_UnreadToken(source_t *source, token_t *token)
+{
+ Parse_UnreadSourceToken(source, token);
+}
+
+/*
+===============
+Parse_ReadToken
+===============
+*/
+static int Parse_ReadToken(source_t *source, token_t *token)
+{
+ define_t *define;
+
+ while(1)
+ {
+ if (!Parse_ReadSourceToken(source, token)) return qfalse;
+ //check for precompiler directives
+ if (token->type == TT_PUNCTUATION && *token->string == '#')
+ {
+ {
+ //read the precompiler directive
+ if (!Parse_ReadDirective(source)) return qfalse;
+ continue;
+ }
+ }
+ if (token->type == TT_PUNCTUATION && *token->string == '$')
+ {
+ {
+ //read the precompiler directive
+ if (!Parse_ReadDollarDirective(source)) return qfalse;
+ continue;
+ }
+ }
+ // recursively concatenate strings that are behind each other still resolving defines
+ if (token->type == TT_STRING)
+ {
+ token_t newtoken;
+ if (Parse_ReadToken(source, &newtoken))
+ {
+ if (newtoken.type == TT_STRING)
+ {
+ token->string[strlen(token->string)-1] = '\0';
+ if (strlen(token->string) + strlen(newtoken.string+1) + 1 >= MAX_TOKEN_CHARS)
+ {
+ Parse_SourceError(source, "string longer than MAX_TOKEN_CHARS %d\n", MAX_TOKEN_CHARS);
+ return qfalse;
+ }
+ strcat(token->string, newtoken.string+1);
+ }
+ else
+ {
+ Parse_UnreadToken(source, &newtoken);
+ }
+ }
+ }
+ //if skipping source because of conditional compilation
+ if (source->skip) continue;
+ //if the token is a name
+ if (token->type == TT_NAME)
+ {
+ //check if the name is a define macro
+ define = Parse_FindHashedDefine(source->definehash, token->string);
+ //if it is a define macro
+ if (define)
+ {
+ //expand the defined macro
+ if (!Parse_ExpandDefineIntoSource(source, token, define)) return qfalse;
+ continue;
+ }
+ }
+ //copy token for unreading
+ Com_Memcpy(&source->token, token, sizeof(token_t));
+ //found a token
+ return qtrue;
+ }
+}
+
+/*
+===============
+Parse_DefineFromString
+===============
+*/
+static define_t *Parse_DefineFromString(char *string)
+{
+ script_t *script;
+ source_t src;
+ token_t *t;
+ int res, i;
+ define_t *def;
+
+ script = Parse_LoadScriptMemory(string, strlen(string), "*extern");
+ //create a new source
+ Com_Memset(&src, 0, sizeof(source_t));
+ strncpy(src.filename, "*extern", MAX_QPATH);
+ src.scriptstack = script;
+ src.definehash = Z_Malloc(DEFINEHASHSIZE * sizeof(define_t *));
+ Com_Memset( src.definehash, 0, DEFINEHASHSIZE * sizeof(define_t *));
+ //create a define from the source
+ res = Parse_Directive_define(&src);
+ //free any tokens if left
+ for (t = src.tokens; t; t = src.tokens)
+ {
+ src.tokens = src.tokens->next;
+ Parse_FreeToken(t);
+ }
+ def = NULL;
+ for (i = 0; i < DEFINEHASHSIZE; i++)
+ {
+ if (src.definehash[i])
+ {
+ def = src.definehash[i];
+ break;
+ }
+ }
+ //
+ Z_Free(src.definehash);
+ //
+ Parse_FreeScript(script);
+ //if the define was created succesfully
+ if (res > 0) return def;
+ //free the define is created
+ if (src.defines) Parse_FreeDefine(def);
+ //
+ return NULL;
+}
+
+/*
+===============
+Parse_AddGlobalDefine
+
+add a globals define that will be added to all opened sources
+===============
+*/
+int Parse_AddGlobalDefine(char *string)
+{
+ define_t *define;
+
+ define = Parse_DefineFromString(string);
+ if (!define) return qfalse;
+ define->next = globaldefines;
+ globaldefines = define;
+ return qtrue;
+}
+
+/*
+===============
+Parse_CopyDefine
+===============
+*/
+static define_t *Parse_CopyDefine(source_t *source, define_t *define)
+{
+ define_t *newdefine;
+ token_t *token, *newtoken, *lasttoken;
+
+ newdefine = (define_t *) Z_Malloc(sizeof(define_t) + strlen(define->name) + 1);
+ //copy the define name
+ newdefine->name = (char *) newdefine + sizeof(define_t);
+ strcpy(newdefine->name, define->name);
+ newdefine->flags = define->flags;
+ newdefine->builtin = define->builtin;
+ newdefine->numparms = define->numparms;
+ //the define is not linked
+ newdefine->next = NULL;
+ newdefine->hashnext = NULL;
+ //copy the define tokens
+ newdefine->tokens = NULL;
+ for (lasttoken = NULL, token = define->tokens; token; token = token->next)
+ {
+ newtoken = Parse_CopyToken(token);
+ newtoken->next = NULL;
+ if (lasttoken) lasttoken->next = newtoken;
+ else newdefine->tokens = newtoken;
+ lasttoken = newtoken;
+ }
+ //copy the define parameters
+ newdefine->parms = NULL;
+ for (lasttoken = NULL, token = define->parms; token; token = token->next)
+ {
+ newtoken = Parse_CopyToken(token);
+ newtoken->next = NULL;
+ if (lasttoken) lasttoken->next = newtoken;
+ else newdefine->parms = newtoken;
+ lasttoken = newtoken;
+ }
+ return newdefine;
+}
+
+/*
+===============
+Parse_AddGlobalDefinesToSource
+===============
+*/
+static void Parse_AddGlobalDefinesToSource(source_t *source)
+{
+ define_t *define, *newdefine;
+
+ for (define = globaldefines; define; define = define->next)
+ {
+ newdefine = Parse_CopyDefine(source, define);
+ Parse_AddDefineToHash(newdefine, source->definehash);
+ }
+}
+
+/*
+===============
+Parse_LoadSourceFile
+===============
+*/
+static source_t *Parse_LoadSourceFile(const char *filename)
+{
+ source_t *source;
+ script_t *script;
+
+ script = Parse_LoadScriptFile(filename);
+ if (!script) return NULL;
+
+ script->next = NULL;
+
+ source = (source_t *) Z_Malloc(sizeof(source_t));
+ Com_Memset(source, 0, sizeof(source_t));
+
+ strncpy(source->filename, filename, MAX_QPATH);
+ source->scriptstack = script;
+ source->tokens = NULL;
+ source->defines = NULL;
+ source->indentstack = NULL;
+ source->skip = 0;
+
+ source->definehash = Z_Malloc(DEFINEHASHSIZE * sizeof(define_t *));
+ Com_Memset( source->definehash, 0, DEFINEHASHSIZE * sizeof(define_t *));
+ Parse_AddGlobalDefinesToSource(source);
+ return source;
+}
+
+/*
+===============
+Parse_FreeSource
+===============
+*/
+static void Parse_FreeSource(source_t *source)
+{
+ script_t *script;
+ token_t *token;
+ define_t *define;
+ indent_t *indent;
+ int i;
+
+ //Parse_PrintDefineHashTable(source->definehash);
+ //free all the scripts
+ while(source->scriptstack)
+ {
+ script = source->scriptstack;
+ source->scriptstack = source->scriptstack->next;
+ Parse_FreeScript(script);
+ }
+ //free all the tokens
+ while(source->tokens)
+ {
+ token = source->tokens;
+ source->tokens = source->tokens->next;
+ Parse_FreeToken(token);
+ }
+ for (i = 0; i < DEFINEHASHSIZE; i++)
+ {
+ while(source->definehash[i])
+ {
+ define = source->definehash[i];
+ source->definehash[i] = source->definehash[i]->hashnext;
+ Parse_FreeDefine(define);
+ }
+ }
+ //free all indents
+ while(source->indentstack)
+ {
+ indent = source->indentstack;
+ source->indentstack = source->indentstack->next;
+ Z_Free(indent);
+ }
+ //
+ if (source->definehash) Z_Free(source->definehash);
+ //free the source itself
+ Z_Free(source);
+}
+
+#define MAX_SOURCEFILES 64
+
+source_t *sourceFiles[MAX_SOURCEFILES];
+
+/*
+===============
+Parse_LoadSourceHandle
+===============
+*/
+int Parse_LoadSourceHandle(const char *filename)
+{
+ source_t *source;
+ int i;
+
+ for (i = 1; i < MAX_SOURCEFILES; i++)
+ {
+ if (!sourceFiles[i])
+ break;
+ }
+ if (i >= MAX_SOURCEFILES)
+ return 0;
+ Parse_SetBaseFolder("");
+ source = Parse_LoadSourceFile(filename);
+ if (!source)
+ return 0;
+ sourceFiles[i] = source;
+ return i;
+}
+
+/*
+===============
+Parse_FreeSourceHandle
+===============
+*/
+int Parse_FreeSourceHandle(int handle)
+{
+ if (handle < 1 || handle >= MAX_SOURCEFILES)
+ return qfalse;
+ if (!sourceFiles[handle])
+ return qfalse;
+
+ Parse_FreeSource(sourceFiles[handle]);
+ sourceFiles[handle] = NULL;
+ return qtrue;
+}
+
+/*
+===============
+Parse_ReadTokenHandle
+===============
+*/
+int Parse_ReadTokenHandle(int handle, pc_token_t *pc_token)
+{
+ token_t token;
+ int ret;
+
+ if (handle < 1 || handle >= MAX_SOURCEFILES)
+ return 0;
+ if (!sourceFiles[handle])
+ return 0;
+
+ ret = Parse_ReadToken(sourceFiles[handle], &token);
+ strcpy(pc_token->string, token.string);
+ pc_token->type = token.type;
+ pc_token->subtype = token.subtype;
+ pc_token->intvalue = token.intvalue;
+ pc_token->floatvalue = token.floatvalue;
+ if (pc_token->type == TT_STRING)
+ Parse_StripDoubleQuotes(pc_token->string);
+ return ret;
+}
+
+/*
+===============
+Parse_SourceFileAndLine
+===============
+*/
+int Parse_SourceFileAndLine(int handle, char *filename, int *line)
+{
+ if (handle < 1 || handle >= MAX_SOURCEFILES)
+ return qfalse;
+ if (!sourceFiles[handle])
+ return qfalse;
+
+ strcpy(filename, sourceFiles[handle]->filename);
+ if (sourceFiles[handle]->scriptstack)
+ *line = sourceFiles[handle]->scriptstack->line;
+ else
+ *line = 0;
+ return qtrue;
+}
diff --git a/src/qcommon/q_math.c b/src/qcommon/q_math.c
new file mode 100644
index 0000000..196d2f5
--- /dev/null
+++ b/src/qcommon/q_math.c
@@ -0,0 +1,1562 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+// q_math.c -- stateless support routines that are included in each code module
+
+// Some of the vector functions are static inline in q_shared.h. q3asm
+// doesn't understand static functions though, so we only want them in
+// one file. That's what this is about.
+#ifdef Q3_VM
+#define __Q3_VM_MATH
+#endif
+
+#include "q_shared.h"
+
+vec3_t vec3_origin = {0,0,0};
+vec3_t axisDefault[3] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } };
+
+
+vec4_t colorBlack = {0, 0, 0, 1};
+vec4_t colorRed = {1, 0, 0, 1};
+vec4_t colorGreen = {0, 1, 0, 1};
+vec4_t colorBlue = {0, 0, 1, 1};
+vec4_t colorYellow = {1, 1, 0, 1};
+vec4_t colorMagenta= {1, 0, 1, 1};
+vec4_t colorCyan = {0, 1, 1, 1};
+vec4_t colorWhite = {1, 1, 1, 1};
+vec4_t colorLtGrey = {0.75, 0.75, 0.75, 1};
+vec4_t colorMdGrey = {0.5, 0.5, 0.5, 1};
+vec4_t colorDkGrey = {0.25, 0.25, 0.25, 1};
+
+vec4_t g_color_table[8] =
+ {
+ {0.0, 0.0, 0.0, 1.0},
+ {1.0, 0.0, 0.0, 1.0},
+ {0.0, 1.0, 0.0, 1.0},
+ {1.0, 1.0, 0.0, 1.0},
+ {0.0, 0.0, 1.0, 1.0},
+ {0.0, 1.0, 1.0, 1.0},
+ {1.0, 0.0, 1.0, 1.0},
+ {1.0, 1.0, 1.0, 1.0},
+ };
+
+
+vec3_t bytedirs[NUMVERTEXNORMALS] =
+{
+{-0.525731f, 0.000000f, 0.850651f}, {-0.442863f, 0.238856f, 0.864188f},
+{-0.295242f, 0.000000f, 0.955423f}, {-0.309017f, 0.500000f, 0.809017f},
+{-0.162460f, 0.262866f, 0.951056f}, {0.000000f, 0.000000f, 1.000000f},
+{0.000000f, 0.850651f, 0.525731f}, {-0.147621f, 0.716567f, 0.681718f},
+{0.147621f, 0.716567f, 0.681718f}, {0.000000f, 0.525731f, 0.850651f},
+{0.309017f, 0.500000f, 0.809017f}, {0.525731f, 0.000000f, 0.850651f},
+{0.295242f, 0.000000f, 0.955423f}, {0.442863f, 0.238856f, 0.864188f},
+{0.162460f, 0.262866f, 0.951056f}, {-0.681718f, 0.147621f, 0.716567f},
+{-0.809017f, 0.309017f, 0.500000f},{-0.587785f, 0.425325f, 0.688191f},
+{-0.850651f, 0.525731f, 0.000000f},{-0.864188f, 0.442863f, 0.238856f},
+{-0.716567f, 0.681718f, 0.147621f},{-0.688191f, 0.587785f, 0.425325f},
+{-0.500000f, 0.809017f, 0.309017f}, {-0.238856f, 0.864188f, 0.442863f},
+{-0.425325f, 0.688191f, 0.587785f}, {-0.716567f, 0.681718f, -0.147621f},
+{-0.500000f, 0.809017f, -0.309017f}, {-0.525731f, 0.850651f, 0.000000f},
+{0.000000f, 0.850651f, -0.525731f}, {-0.238856f, 0.864188f, -0.442863f},
+{0.000000f, 0.955423f, -0.295242f}, {-0.262866f, 0.951056f, -0.162460f},
+{0.000000f, 1.000000f, 0.000000f}, {0.000000f, 0.955423f, 0.295242f},
+{-0.262866f, 0.951056f, 0.162460f}, {0.238856f, 0.864188f, 0.442863f},
+{0.262866f, 0.951056f, 0.162460f}, {0.500000f, 0.809017f, 0.309017f},
+{0.238856f, 0.864188f, -0.442863f},{0.262866f, 0.951056f, -0.162460f},
+{0.500000f, 0.809017f, -0.309017f},{0.850651f, 0.525731f, 0.000000f},
+{0.716567f, 0.681718f, 0.147621f}, {0.716567f, 0.681718f, -0.147621f},
+{0.525731f, 0.850651f, 0.000000f}, {0.425325f, 0.688191f, 0.587785f},
+{0.864188f, 0.442863f, 0.238856f}, {0.688191f, 0.587785f, 0.425325f},
+{0.809017f, 0.309017f, 0.500000f}, {0.681718f, 0.147621f, 0.716567f},
+{0.587785f, 0.425325f, 0.688191f}, {0.955423f, 0.295242f, 0.000000f},
+{1.000000f, 0.000000f, 0.000000f}, {0.951056f, 0.162460f, 0.262866f},
+{0.850651f, -0.525731f, 0.000000f},{0.955423f, -0.295242f, 0.000000f},
+{0.864188f, -0.442863f, 0.238856f}, {0.951056f, -0.162460f, 0.262866f},
+{0.809017f, -0.309017f, 0.500000f}, {0.681718f, -0.147621f, 0.716567f},
+{0.850651f, 0.000000f, 0.525731f}, {0.864188f, 0.442863f, -0.238856f},
+{0.809017f, 0.309017f, -0.500000f}, {0.951056f, 0.162460f, -0.262866f},
+{0.525731f, 0.000000f, -0.850651f}, {0.681718f, 0.147621f, -0.716567f},
+{0.681718f, -0.147621f, -0.716567f},{0.850651f, 0.000000f, -0.525731f},
+{0.809017f, -0.309017f, -0.500000f}, {0.864188f, -0.442863f, -0.238856f},
+{0.951056f, -0.162460f, -0.262866f}, {0.147621f, 0.716567f, -0.681718f},
+{0.309017f, 0.500000f, -0.809017f}, {0.425325f, 0.688191f, -0.587785f},
+{0.442863f, 0.238856f, -0.864188f}, {0.587785f, 0.425325f, -0.688191f},
+{0.688191f, 0.587785f, -0.425325f}, {-0.147621f, 0.716567f, -0.681718f},
+{-0.309017f, 0.500000f, -0.809017f}, {0.000000f, 0.525731f, -0.850651f},
+{-0.525731f, 0.000000f, -0.850651f}, {-0.442863f, 0.238856f, -0.864188f},
+{-0.295242f, 0.000000f, -0.955423f}, {-0.162460f, 0.262866f, -0.951056f},
+{0.000000f, 0.000000f, -1.000000f}, {0.295242f, 0.000000f, -0.955423f},
+{0.162460f, 0.262866f, -0.951056f}, {-0.442863f, -0.238856f, -0.864188f},
+{-0.309017f, -0.500000f, -0.809017f}, {-0.162460f, -0.262866f, -0.951056f},
+{0.000000f, -0.850651f, -0.525731f}, {-0.147621f, -0.716567f, -0.681718f},
+{0.147621f, -0.716567f, -0.681718f}, {0.000000f, -0.525731f, -0.850651f},
+{0.309017f, -0.500000f, -0.809017f}, {0.442863f, -0.238856f, -0.864188f},
+{0.162460f, -0.262866f, -0.951056f}, {0.238856f, -0.864188f, -0.442863f},
+{0.500000f, -0.809017f, -0.309017f}, {0.425325f, -0.688191f, -0.587785f},
+{0.716567f, -0.681718f, -0.147621f}, {0.688191f, -0.587785f, -0.425325f},
+{0.587785f, -0.425325f, -0.688191f}, {0.000000f, -0.955423f, -0.295242f},
+{0.000000f, -1.000000f, 0.000000f}, {0.262866f, -0.951056f, -0.162460f},
+{0.000000f, -0.850651f, 0.525731f}, {0.000000f, -0.955423f, 0.295242f},
+{0.238856f, -0.864188f, 0.442863f}, {0.262866f, -0.951056f, 0.162460f},
+{0.500000f, -0.809017f, 0.309017f}, {0.716567f, -0.681718f, 0.147621f},
+{0.525731f, -0.850651f, 0.000000f}, {-0.238856f, -0.864188f, -0.442863f},
+{-0.500000f, -0.809017f, -0.309017f}, {-0.262866f, -0.951056f, -0.162460f},
+{-0.850651f, -0.525731f, 0.000000f}, {-0.716567f, -0.681718f, -0.147621f},
+{-0.716567f, -0.681718f, 0.147621f}, {-0.525731f, -0.850651f, 0.000000f},
+{-0.500000f, -0.809017f, 0.309017f}, {-0.238856f, -0.864188f, 0.442863f},
+{-0.262866f, -0.951056f, 0.162460f}, {-0.864188f, -0.442863f, 0.238856f},
+{-0.809017f, -0.309017f, 0.500000f}, {-0.688191f, -0.587785f, 0.425325f},
+{-0.681718f, -0.147621f, 0.716567f}, {-0.442863f, -0.238856f, 0.864188f},
+{-0.587785f, -0.425325f, 0.688191f}, {-0.309017f, -0.500000f, 0.809017f},
+{-0.147621f, -0.716567f, 0.681718f}, {-0.425325f, -0.688191f, 0.587785f},
+{-0.162460f, -0.262866f, 0.951056f}, {0.442863f, -0.238856f, 0.864188f},
+{0.162460f, -0.262866f, 0.951056f}, {0.309017f, -0.500000f, 0.809017f},
+{0.147621f, -0.716567f, 0.681718f}, {0.000000f, -0.525731f, 0.850651f},
+{0.425325f, -0.688191f, 0.587785f}, {0.587785f, -0.425325f, 0.688191f},
+{0.688191f, -0.587785f, 0.425325f}, {-0.955423f, 0.295242f, 0.000000f},
+{-0.951056f, 0.162460f, 0.262866f}, {-1.000000f, 0.000000f, 0.000000f},
+{-0.850651f, 0.000000f, 0.525731f}, {-0.955423f, -0.295242f, 0.000000f},
+{-0.951056f, -0.162460f, 0.262866f}, {-0.864188f, 0.442863f, -0.238856f},
+{-0.951056f, 0.162460f, -0.262866f}, {-0.809017f, 0.309017f, -0.500000f},
+{-0.864188f, -0.442863f, -0.238856f}, {-0.951056f, -0.162460f, -0.262866f},
+{-0.809017f, -0.309017f, -0.500000f}, {-0.681718f, 0.147621f, -0.716567f},
+{-0.681718f, -0.147621f, -0.716567f}, {-0.850651f, 0.000000f, -0.525731f},
+{-0.688191f, 0.587785f, -0.425325f}, {-0.587785f, 0.425325f, -0.688191f},
+{-0.425325f, 0.688191f, -0.587785f}, {-0.425325f, -0.688191f, -0.587785f},
+{-0.587785f, -0.425325f, -0.688191f}, {-0.688191f, -0.587785f, -0.425325f}
+};
+
+//==============================================================
+
+int Q_rand( int *seed ) {
+ *seed = (69069 * *seed + 1);
+ return *seed;
+}
+
+float Q_random( int *seed ) {
+ return ( Q_rand( seed ) & 0xffff ) / (float)0x10000;
+}
+
+float Q_crandom( int *seed ) {
+ return 2.0 * ( Q_random( seed ) - 0.5 );
+}
+
+//=======================================================
+
+signed char ClampChar( int i ) {
+ if ( i < -128 ) {
+ return -128;
+ }
+ if ( i > 127 ) {
+ return 127;
+ }
+ return i;
+}
+
+signed short ClampShort( int i ) {
+ if ( i < -32768 ) {
+ return -32768;
+ }
+ if ( i > 0x7fff ) {
+ return 0x7fff;
+ }
+ return i;
+}
+
+
+// this isn't a real cheap function to call!
+int DirToByte( vec3_t dir ) {
+ int i, best;
+ float d, bestd;
+
+ if ( !dir ) {
+ return 0;
+ }
+
+ bestd = 0;
+ best = 0;
+ for (i=0 ; i<NUMVERTEXNORMALS ; i++)
+ {
+ d = DotProduct (dir, bytedirs[i]);
+ if (d > bestd)
+ {
+ bestd = d;
+ best = i;
+ }
+ }
+
+ return best;
+}
+
+void ByteToDir( int b, vec3_t dir ) {
+ if ( b < 0 || b >= NUMVERTEXNORMALS ) {
+ VectorCopy( vec3_origin, dir );
+ return;
+ }
+ VectorCopy (bytedirs[b], dir);
+}
+
+
+unsigned ColorBytes3 (float r, float g, float b) {
+ unsigned i;
+
+ ( (byte *)&i )[0] = r * 255;
+ ( (byte *)&i )[1] = g * 255;
+ ( (byte *)&i )[2] = b * 255;
+
+ return i;
+}
+
+unsigned ColorBytes4 (float r, float g, float b, float a) {
+ unsigned i;
+
+ ( (byte *)&i )[0] = r * 255;
+ ( (byte *)&i )[1] = g * 255;
+ ( (byte *)&i )[2] = b * 255;
+ ( (byte *)&i )[3] = a * 255;
+
+ return i;
+}
+
+float NormalizeColor( const vec3_t in, vec3_t out ) {
+ float max;
+
+ max = in[0];
+ if ( in[1] > max ) {
+ max = in[1];
+ }
+ if ( in[2] > max ) {
+ max = in[2];
+ }
+
+ if ( !max ) {
+ VectorClear( out );
+ } else {
+ out[0] = in[0] / max;
+ out[1] = in[1] / max;
+ out[2] = in[2] / max;
+ }
+ return max;
+}
+
+
+/*
+=====================
+PlaneFromPoints
+
+Returns false if the triangle is degenrate.
+The normal will point out of the clock for clockwise ordered points
+=====================
+*/
+qboolean PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c ) {
+ vec3_t d1, d2;
+
+ VectorSubtract( b, a, d1 );
+ VectorSubtract( c, a, d2 );
+ CrossProduct( d2, d1, plane );
+ if ( VectorNormalize( plane ) == 0 ) {
+ return qfalse;
+ }
+
+ plane[3] = DotProduct( a, plane );
+ return qtrue;
+}
+
+/*
+===============
+RotatePointAroundVector
+
+This is not implemented very well...
+===============
+*/
+void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point,
+ float degrees ) {
+ float sin_a;
+ float cos_a;
+ float cos_ia;
+ float i_i_ia;
+ float j_j_ia;
+ float k_k_ia;
+ float i_j_ia;
+ float i_k_ia;
+ float j_k_ia;
+ float a_sin;
+ float b_sin;
+ float c_sin;
+ float rot[3][3];
+
+ cos_ia = DEG2RAD(degrees);
+ sin_a = sin(cos_ia);
+ cos_a = cos(cos_ia);
+ cos_ia = 1.0F - cos_a;
+
+ i_i_ia = dir[0] * dir[0] * cos_ia;
+ j_j_ia = dir[1] * dir[1] * cos_ia;
+ k_k_ia = dir[2] * dir[2] * cos_ia;
+ i_j_ia = dir[0] * dir[1] * cos_ia;
+ i_k_ia = dir[0] * dir[2] * cos_ia;
+ j_k_ia = dir[1] * dir[2] * cos_ia;
+
+ a_sin = dir[0] * sin_a;
+ b_sin = dir[1] * sin_a;
+ c_sin = dir[2] * sin_a;
+
+ rot[0][0] = i_i_ia + cos_a;
+ rot[0][1] = i_j_ia - c_sin;
+ rot[0][2] = i_k_ia + b_sin;
+ rot[1][0] = i_j_ia + c_sin;
+ rot[1][1] = j_j_ia + cos_a;
+ rot[1][2] = j_k_ia - a_sin;
+ rot[2][0] = i_k_ia - b_sin;
+ rot[2][1] = j_k_ia + a_sin;
+ rot[2][2] = k_k_ia + cos_a;
+
+ dst[0] = point[0] * rot[0][0] + point[1] * rot[0][1] + point[2] * rot[0][2];
+ dst[1] = point[0] * rot[1][0] + point[1] * rot[1][1] + point[2] * rot[1][2];
+ dst[2] = point[0] * rot[2][0] + point[1] * rot[2][1] + point[2] * rot[2][2];
+}
+
+/*
+===============
+RotateAroundDirection
+===============
+*/
+void RotateAroundDirection( vec3_t axis[3], vec_t angle ) {
+ vec_t scale;
+
+ angle = DEG2RAD( angle );
+
+ // create an arbitrary axis[1]
+ PerpendicularVector( axis[ 1 ], axis[ 0 ] );
+
+ // cross to get axis[2]
+ CrossProduct( axis[ 0 ], axis[ 1 ], axis[ 2 ] );
+
+ // rotate
+ scale = cos( angle );
+ VectorScale( axis[ 1 ], scale, axis[ 1 ] );
+
+ scale = sin( angle );
+ VectorMA( axis[ 1 ], scale, axis[ 2 ], axis[ 1 ] );
+
+ // recalculate axis[2]
+ CrossProduct( axis[ 0 ], axis[ 1 ], axis[ 2 ] );
+}
+
+
+
+void vectoangles( const vec3_t value1, vec3_t angles ) {
+ float forward;
+ float yaw, pitch;
+
+ if ( value1[1] == 0 && value1[0] == 0 ) {
+ yaw = 0;
+ if ( value1[2] > 0 ) {
+ pitch = 90;
+ }
+ else {
+ pitch = 270;
+ }
+ }
+ else {
+ if ( value1[0] ) {
+ yaw = ( atan2 ( value1[1], value1[0] ) * 180 / M_PI );
+ }
+ else if ( value1[1] > 0 ) {
+ yaw = 90;
+ }
+ else {
+ yaw = 270;
+ }
+ if ( yaw < 0 ) {
+ yaw += 360;
+ }
+
+ forward = sqrt ( value1[0]*value1[0] + value1[1]*value1[1] );
+ pitch = ( atan2(value1[2], forward) * 180 / M_PI );
+ if ( pitch < 0 ) {
+ pitch += 360;
+ }
+ }
+
+ angles[PITCH] = -pitch;
+ angles[YAW] = yaw;
+ angles[ROLL] = 0;
+}
+
+
+/*
+=================
+AxisToAngles
+
+Takes an axis (forward + right + up)
+and returns angles -- including a roll
+=================
+*/
+void AxisToAngles( vec3_t axis[3], vec3_t angles ) {
+ float length1;
+ float yaw, pitch, roll = 0.0f;
+
+ if ( axis[0][1] == 0 && axis[0][0] == 0 ) {
+ yaw = 0;
+ if ( axis[0][2] > 0 ) {
+ pitch = 90;
+ }
+ else {
+ pitch = 270;
+ }
+ }
+ else {
+ if ( axis[0][0] ) {
+ yaw = ( atan2 ( axis[0][1], axis[0][0] ) * 180 / M_PI );
+ }
+ else if ( axis[0][1] > 0 ) {
+ yaw = 90;
+ }
+ else {
+ yaw = 270;
+ }
+ if ( yaw < 0 ) {
+ yaw += 360;
+ }
+
+ length1 = sqrt ( axis[0][0]*axis[0][0] + axis[0][1]*axis[0][1] );
+ pitch = ( atan2(axis[0][2], length1) * 180 / M_PI );
+ if ( pitch < 0 ) {
+ pitch += 360;
+ }
+
+ roll = ( atan2( axis[1][2], axis[2][2] ) * 180 / M_PI );
+ if ( roll < 0 ) {
+ roll += 360;
+ }
+ }
+
+ angles[PITCH] = -pitch;
+ angles[YAW] = yaw;
+ angles[ROLL] = roll;
+}
+
+/*
+=================
+AnglesToAxis
+=================
+*/
+void AnglesToAxis( const vec3_t angles, vec3_t axis[3] ) {
+ vec3_t right;
+
+ // angle vectors returns "right" instead of "y axis"
+ AngleVectors( angles, axis[0], right, axis[2] );
+ VectorSubtract( vec3_origin, right, axis[1] );
+}
+
+void AxisClear( vec3_t axis[3] ) {
+ axis[0][0] = 1;
+ axis[0][1] = 0;
+ axis[0][2] = 0;
+ axis[1][0] = 0;
+ axis[1][1] = 1;
+ axis[1][2] = 0;
+ axis[2][0] = 0;
+ axis[2][1] = 0;
+ axis[2][2] = 1;
+}
+
+void AxisCopy( vec3_t in[3], vec3_t out[3] ) {
+ VectorCopy( in[0], out[0] );
+ VectorCopy( in[1], out[1] );
+ VectorCopy( in[2], out[2] );
+}
+
+void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal )
+{
+ float d;
+ vec3_t n;
+ float inv_denom;
+
+ inv_denom = 1.0f / DotProduct( normal, normal );
+#ifndef Q3_VM
+ assert( Q_fabs(inv_denom) != 0.0f ); // bk010122 - zero vectors get here
+#endif
+ inv_denom = 1.0f / inv_denom;
+
+ d = DotProduct( normal, p ) * inv_denom;
+
+ n[0] = normal[0] * inv_denom;
+ n[1] = normal[1] * inv_denom;
+ n[2] = normal[2] * inv_denom;
+
+ dst[0] = p[0] - d * n[0];
+ dst[1] = p[1] - d * n[1];
+ dst[2] = p[2] - d * n[2];
+}
+
+/*
+================
+MakeNormalVectors
+
+Given a normalized forward vector, create two
+other perpendicular vectors
+================
+*/
+void MakeNormalVectors( const vec3_t forward, vec3_t right, vec3_t up) {
+ float d;
+
+ // this rotate and negate guarantees a vector
+ // not colinear with the original
+ right[1] = -forward[0];
+ right[2] = forward[1];
+ right[0] = forward[2];
+
+ d = DotProduct (right, forward);
+ VectorMA (right, -d, forward, right);
+ VectorNormalize (right);
+ CrossProduct (right, forward, up);
+}
+
+
+void VectorRotate( vec3_t in, vec3_t matrix[3], vec3_t out )
+{
+ out[0] = DotProduct( in, matrix[0] );
+ out[1] = DotProduct( in, matrix[1] );
+ out[2] = DotProduct( in, matrix[2] );
+}
+
+//============================================================================
+
+#if !idppc
+/*
+** float q_rsqrt( float number )
+*/
+float Q_rsqrt( float number )
+{
+ union {
+ float f;
+ int i;
+ } t;
+ float x2, y;
+ const float threehalfs = 1.5F;
+
+ x2 = number * 0.5F;
+ t.f = number;
+ t.i = 0x5f3759df - ( t.i >> 1 ); // what the fuck?
+ y = t.f;
+ y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
+// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed
+
+ //assert( !isnan(y) ); // bk010122 - FPE?
+ return y;
+}
+
+float Q_fabs( float f ) {
+ int tmp = * ( int * ) &f;
+ tmp &= 0x7FFFFFFF;
+ return * ( float * ) &tmp;
+}
+#endif
+
+//============================================================
+
+/*
+===============
+LerpAngle
+
+===============
+*/
+float LerpAngle (float from, float to, float frac) {
+ float a;
+
+ if ( to - from > 180 ) {
+ to -= 360;
+ }
+ if ( to - from < -180 ) {
+ to += 360;
+ }
+ a = from + frac * (to - from);
+
+ return a;
+}
+
+
+/*
+=================
+AngleSubtract
+
+Always returns a value from -180 to 180
+=================
+*/
+float AngleSubtract( float a1, float a2 ) {
+ float a;
+
+ a = a1 - a2;
+ while ( a > 180 ) {
+ a -= 360;
+ }
+ while ( a < -180 ) {
+ a += 360;
+ }
+ return a;
+}
+
+
+void AnglesSubtract( vec3_t v1, vec3_t v2, vec3_t v3 ) {
+ v3[0] = AngleSubtract( v1[0], v2[0] );
+ v3[1] = AngleSubtract( v1[1], v2[1] );
+ v3[2] = AngleSubtract( v1[2], v2[2] );
+}
+
+
+float AngleMod(float a) {
+ a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535);
+ return a;
+}
+
+
+/*
+=================
+AngleNormalize360
+
+returns angle normalized to the range [0 <= angle < 360]
+=================
+*/
+float AngleNormalize360 ( float angle ) {
+ return (360.0 / 65536) * ((int)(angle * (65536 / 360.0)) & 65535);
+}
+
+
+/*
+=================
+AngleNormalize180
+
+returns angle normalized to the range [-180 < angle <= 180]
+=================
+*/
+float AngleNormalize180 ( float angle ) {
+ angle = AngleNormalize360( angle );
+ if ( angle > 180.0 ) {
+ angle -= 360.0;
+ }
+ return angle;
+}
+
+
+/*
+=================
+AngleDelta
+
+returns the normalized delta from angle1 to angle2
+=================
+*/
+float AngleDelta ( float angle1, float angle2 ) {
+ return AngleNormalize180( angle1 - angle2 );
+}
+
+
+//============================================================
+
+
+/*
+=================
+SetPlaneSignbits
+=================
+*/
+void SetPlaneSignbits (cplane_t *out) {
+ int bits, j;
+
+ // for fast box on planeside test
+ bits = 0;
+ for (j=0 ; j<3 ; j++) {
+ if (out->normal[j] < 0) {
+ bits |= 1<<j;
+ }
+ }
+ out->signbits = bits;
+}
+
+
+/*
+==================
+BoxOnPlaneSide
+
+Returns 1, 2, or 1 + 2
+
+// this is the slow, general version
+int BoxOnPlaneSide2 (vec3_t emins, vec3_t emaxs, struct cplane_s *p)
+{
+ int i;
+ float dist1, dist2;
+ int sides;
+ vec3_t corners[2];
+
+ for (i=0 ; i<3 ; i++)
+ {
+ if (p->normal[i] < 0)
+ {
+ corners[0][i] = emins[i];
+ corners[1][i] = emaxs[i];
+ }
+ else
+ {
+ corners[1][i] = emins[i];
+ corners[0][i] = emaxs[i];
+ }
+ }
+ dist1 = DotProduct (p->normal, corners[0]) - p->dist;
+ dist2 = DotProduct (p->normal, corners[1]) - p->dist;
+ sides = 0;
+ if (dist1 >= 0)
+ sides = 1;
+ if (dist2 < 0)
+ sides |= 2;
+
+ return sides;
+}
+
+==================
+*/
+
+#if !id386
+
+int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p)
+{
+ float dist1, dist2;
+ int sides;
+
+// fast axial cases
+ if (p->type < 3)
+ {
+ if (p->dist <= emins[p->type])
+ return 1;
+ if (p->dist >= emaxs[p->type])
+ return 2;
+ return 3;
+ }
+
+// general case
+ switch (p->signbits)
+ {
+ case 0:
+ dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2];
+ dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2];
+ break;
+ case 1:
+ dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2];
+ dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2];
+ break;
+ case 2:
+ dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2];
+ dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2];
+ break;
+ case 3:
+ dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2];
+ dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2];
+ break;
+ case 4:
+ dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2];
+ dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2];
+ break;
+ case 5:
+ dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2];
+ dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2];
+ break;
+ case 6:
+ dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2];
+ dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2];
+ break;
+ case 7:
+ dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2];
+ dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2];
+ break;
+ default:
+ dist1 = dist2 = 0; // shut up compiler
+ break;
+ }
+
+ sides = 0;
+ if (dist1 >= p->dist)
+ sides = 1;
+ if (dist2 < p->dist)
+ sides |= 2;
+
+ return sides;
+}
+#elif __GNUC__
+// use matha.s
+#else
+#pragma warning( disable: 4035 )
+
+__declspec( naked ) int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p)
+{
+ static int bops_initialized;
+ static int Ljmptab[8];
+
+ __asm {
+
+ push ebx
+
+ cmp bops_initialized, 1
+ je initialized
+ mov bops_initialized, 1
+
+ mov Ljmptab[0*4], offset Lcase0
+ mov Ljmptab[1*4], offset Lcase1
+ mov Ljmptab[2*4], offset Lcase2
+ mov Ljmptab[3*4], offset Lcase3
+ mov Ljmptab[4*4], offset Lcase4
+ mov Ljmptab[5*4], offset Lcase5
+ mov Ljmptab[6*4], offset Lcase6
+ mov Ljmptab[7*4], offset Lcase7
+
+initialized:
+
+ mov edx,dword ptr[4+12+esp]
+ mov ecx,dword ptr[4+4+esp]
+ xor eax,eax
+ mov ebx,dword ptr[4+8+esp]
+ mov al,byte ptr[17+edx]
+ cmp al,8
+ jge Lerror
+ fld dword ptr[0+edx]
+ fld st(0)
+ jmp dword ptr[Ljmptab+eax*4]
+Lcase0:
+ fmul dword ptr[ebx]
+ fld dword ptr[0+4+edx]
+ fxch st(2)
+ fmul dword ptr[ecx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[4+ebx]
+ fld dword ptr[0+8+edx]
+ fxch st(2)
+ fmul dword ptr[4+ecx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[8+ebx]
+ fxch st(5)
+ faddp st(3),st(0)
+ fmul dword ptr[8+ecx]
+ fxch st(1)
+ faddp st(3),st(0)
+ fxch st(3)
+ faddp st(2),st(0)
+ jmp LSetSides
+Lcase1:
+ fmul dword ptr[ecx]
+ fld dword ptr[0+4+edx]
+ fxch st(2)
+ fmul dword ptr[ebx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[4+ebx]
+ fld dword ptr[0+8+edx]
+ fxch st(2)
+ fmul dword ptr[4+ecx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[8+ebx]
+ fxch st(5)
+ faddp st(3),st(0)
+ fmul dword ptr[8+ecx]
+ fxch st(1)
+ faddp st(3),st(0)
+ fxch st(3)
+ faddp st(2),st(0)
+ jmp LSetSides
+Lcase2:
+ fmul dword ptr[ebx]
+ fld dword ptr[0+4+edx]
+ fxch st(2)
+ fmul dword ptr[ecx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[4+ecx]
+ fld dword ptr[0+8+edx]
+ fxch st(2)
+ fmul dword ptr[4+ebx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[8+ebx]
+ fxch st(5)
+ faddp st(3),st(0)
+ fmul dword ptr[8+ecx]
+ fxch st(1)
+ faddp st(3),st(0)
+ fxch st(3)
+ faddp st(2),st(0)
+ jmp LSetSides
+Lcase3:
+ fmul dword ptr[ecx]
+ fld dword ptr[0+4+edx]
+ fxch st(2)
+ fmul dword ptr[ebx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[4+ecx]
+ fld dword ptr[0+8+edx]
+ fxch st(2)
+ fmul dword ptr[4+ebx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[8+ebx]
+ fxch st(5)
+ faddp st(3),st(0)
+ fmul dword ptr[8+ecx]
+ fxch st(1)
+ faddp st(3),st(0)
+ fxch st(3)
+ faddp st(2),st(0)
+ jmp LSetSides
+Lcase4:
+ fmul dword ptr[ebx]
+ fld dword ptr[0+4+edx]
+ fxch st(2)
+ fmul dword ptr[ecx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[4+ebx]
+ fld dword ptr[0+8+edx]
+ fxch st(2)
+ fmul dword ptr[4+ecx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[8+ecx]
+ fxch st(5)
+ faddp st(3),st(0)
+ fmul dword ptr[8+ebx]
+ fxch st(1)
+ faddp st(3),st(0)
+ fxch st(3)
+ faddp st(2),st(0)
+ jmp LSetSides
+Lcase5:
+ fmul dword ptr[ecx]
+ fld dword ptr[0+4+edx]
+ fxch st(2)
+ fmul dword ptr[ebx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[4+ebx]
+ fld dword ptr[0+8+edx]
+ fxch st(2)
+ fmul dword ptr[4+ecx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[8+ecx]
+ fxch st(5)
+ faddp st(3),st(0)
+ fmul dword ptr[8+ebx]
+ fxch st(1)
+ faddp st(3),st(0)
+ fxch st(3)
+ faddp st(2),st(0)
+ jmp LSetSides
+Lcase6:
+ fmul dword ptr[ebx]
+ fld dword ptr[0+4+edx]
+ fxch st(2)
+ fmul dword ptr[ecx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[4+ecx]
+ fld dword ptr[0+8+edx]
+ fxch st(2)
+ fmul dword ptr[4+ebx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[8+ecx]
+ fxch st(5)
+ faddp st(3),st(0)
+ fmul dword ptr[8+ebx]
+ fxch st(1)
+ faddp st(3),st(0)
+ fxch st(3)
+ faddp st(2),st(0)
+ jmp LSetSides
+Lcase7:
+ fmul dword ptr[ecx]
+ fld dword ptr[0+4+edx]
+ fxch st(2)
+ fmul dword ptr[ebx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[4+ecx]
+ fld dword ptr[0+8+edx]
+ fxch st(2)
+ fmul dword ptr[4+ebx]
+ fxch st(2)
+ fld st(0)
+ fmul dword ptr[8+ecx]
+ fxch st(5)
+ faddp st(3),st(0)
+ fmul dword ptr[8+ebx]
+ fxch st(1)
+ faddp st(3),st(0)
+ fxch st(3)
+ faddp st(2),st(0)
+LSetSides:
+ faddp st(2),st(0)
+ fcomp dword ptr[12+edx]
+ xor ecx,ecx
+ fnstsw ax
+ fcomp dword ptr[12+edx]
+ and ah,1
+ xor ah,1
+ add cl,ah
+ fnstsw ax
+ and ah,1
+ add ah,ah
+ add cl,ah
+ pop ebx
+ mov eax,ecx
+ ret
+Lerror:
+ int 3
+ }
+}
+#pragma warning( default: 4035 )
+
+#endif
+
+/*
+=================
+RadiusFromBounds
+=================
+*/
+float RadiusFromBounds( const vec3_t mins, const vec3_t maxs ) {
+ int i;
+ vec3_t corner;
+ float a, b;
+
+ for (i=0 ; i<3 ; i++) {
+ a = fabs( mins[i] );
+ b = fabs( maxs[i] );
+ corner[i] = a > b ? a : b;
+ }
+
+ return VectorLength (corner);
+}
+
+
+void ClearBounds( vec3_t mins, vec3_t maxs ) {
+ mins[0] = mins[1] = mins[2] = 99999;
+ maxs[0] = maxs[1] = maxs[2] = -99999;
+}
+
+void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ) {
+ if ( v[0] < mins[0] ) {
+ mins[0] = v[0];
+ }
+ if ( v[0] > maxs[0]) {
+ maxs[0] = v[0];
+ }
+
+ if ( v[1] < mins[1] ) {
+ mins[1] = v[1];
+ }
+ if ( v[1] > maxs[1]) {
+ maxs[1] = v[1];
+ }
+
+ if ( v[2] < mins[2] ) {
+ mins[2] = v[2];
+ }
+ if ( v[2] > maxs[2]) {
+ maxs[2] = v[2];
+ }
+}
+
+
+vec_t VectorNormalize( vec3_t v ) {
+ // NOTE: TTimo - Apple G4 altivec source uses double?
+ float length, ilength;
+
+ length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
+ length = sqrt (length);
+
+ if ( length ) {
+ ilength = 1/length;
+ v[0] *= ilength;
+ v[1] *= ilength;
+ v[2] *= ilength;
+ }
+
+ return length;
+}
+
+vec_t VectorNormalize2( const vec3_t v, vec3_t out) {
+ float length, ilength;
+
+ length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
+ length = sqrt (length);
+
+ if (length)
+ {
+#ifndef Q3_VM // bk0101022 - FPE related
+// assert( ((Q_fabs(v[0])!=0.0f) || (Q_fabs(v[1])!=0.0f) || (Q_fabs(v[2])!=0.0f)) );
+#endif
+ ilength = 1/length;
+ out[0] = v[0]*ilength;
+ out[1] = v[1]*ilength;
+ out[2] = v[2]*ilength;
+ } else {
+#ifndef Q3_VM // bk0101022 - FPE related
+// assert( ((Q_fabs(v[0])==0.0f) && (Q_fabs(v[1])==0.0f) && (Q_fabs(v[2])==0.0f)) );
+#endif
+ VectorClear( out );
+ }
+
+ return length;
+
+}
+
+void _VectorMA( const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc) {
+ vecc[0] = veca[0] + scale*vecb[0];
+ vecc[1] = veca[1] + scale*vecb[1];
+ vecc[2] = veca[2] + scale*vecb[2];
+}
+
+
+vec_t _DotProduct( const vec3_t v1, const vec3_t v2 ) {
+ return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
+}
+
+void _VectorSubtract( const vec3_t veca, const vec3_t vecb, vec3_t out ) {
+ out[0] = veca[0]-vecb[0];
+ out[1] = veca[1]-vecb[1];
+ out[2] = veca[2]-vecb[2];
+}
+
+void _VectorAdd( const vec3_t veca, const vec3_t vecb, vec3_t out ) {
+ out[0] = veca[0]+vecb[0];
+ out[1] = veca[1]+vecb[1];
+ out[2] = veca[2]+vecb[2];
+}
+
+void _VectorCopy( const vec3_t in, vec3_t out ) {
+ out[0] = in[0];
+ out[1] = in[1];
+ out[2] = in[2];
+}
+
+void _VectorScale( const vec3_t in, vec_t scale, vec3_t out ) {
+ out[0] = in[0]*scale;
+ out[1] = in[1]*scale;
+ out[2] = in[2]*scale;
+}
+
+void Vector4Scale( const vec4_t in, vec_t scale, vec4_t out ) {
+ out[0] = in[0]*scale;
+ out[1] = in[1]*scale;
+ out[2] = in[2]*scale;
+ out[3] = in[3]*scale;
+}
+
+
+int Q_log2( int val ) {
+ int answer;
+
+ answer = 0;
+ while ( ( val>>=1 ) != 0 ) {
+ answer++;
+ }
+ return answer;
+}
+
+
+
+/*
+=================
+PlaneTypeForNormal
+=================
+*/
+/*
+int PlaneTypeForNormal (vec3_t normal) {
+ if ( normal[0] == 1.0 )
+ return PLANE_X;
+ if ( normal[1] == 1.0 )
+ return PLANE_Y;
+ if ( normal[2] == 1.0 )
+ return PLANE_Z;
+
+ return PLANE_NON_AXIAL;
+}
+*/
+
+
+/*
+================
+MatrixMultiply
+================
+*/
+void MatrixMultiply(float in1[3][3], float in2[3][3], float out[3][3]) {
+ out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] +
+ in1[0][2] * in2[2][0];
+ out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] +
+ in1[0][2] * in2[2][1];
+ out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] +
+ in1[0][2] * in2[2][2];
+ out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] +
+ in1[1][2] * in2[2][0];
+ out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] +
+ in1[1][2] * in2[2][1];
+ out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] +
+ in1[1][2] * in2[2][2];
+ out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] +
+ in1[2][2] * in2[2][0];
+ out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] +
+ in1[2][2] * in2[2][1];
+ out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] +
+ in1[2][2] * in2[2][2];
+}
+
+/*
+================
+VectorMatrixMultiply
+================
+*/
+void VectorMatrixMultiply( const vec3_t p, vec3_t m[ 3 ], vec3_t out )
+{
+ out[ 0 ] = m[ 0 ][ 0 ] * p[ 0 ] + m[ 1 ][ 0 ] * p[ 1 ] + m[ 2 ][ 0 ] * p[ 2 ];
+ out[ 1 ] = m[ 0 ][ 1 ] * p[ 0 ] + m[ 1 ][ 1 ] * p[ 1 ] + m[ 2 ][ 1 ] * p[ 2 ];
+ out[ 2 ] = m[ 0 ][ 2 ] * p[ 0 ] + m[ 1 ][ 2 ] * p[ 1 ] + m[ 2 ][ 2 ] * p[ 2 ];
+}
+
+
+void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) {
+ float angle;
+ static float sr, sp, sy, cr, cp, cy;
+ // static to help MS compiler fp bugs
+
+ angle = angles[YAW] * (M_PI*2 / 360);
+ sy = sin(angle);
+ cy = cos(angle);
+ angle = angles[PITCH] * (M_PI*2 / 360);
+ sp = sin(angle);
+ cp = cos(angle);
+ angle = angles[ROLL] * (M_PI*2 / 360);
+ sr = sin(angle);
+ cr = cos(angle);
+
+ if (forward)
+ {
+ forward[0] = cp*cy;
+ forward[1] = cp*sy;
+ forward[2] = -sp;
+ }
+ if (right)
+ {
+ right[0] = (-1*sr*sp*cy+-1*cr*-sy);
+ right[1] = (-1*sr*sp*sy+-1*cr*cy);
+ right[2] = -1*sr*cp;
+ }
+ if (up)
+ {
+ up[0] = (cr*sp*cy+-sr*-sy);
+ up[1] = (cr*sp*sy+-sr*cy);
+ up[2] = cr*cp;
+ }
+}
+
+/*
+** assumes "src" is normalized
+*/
+void PerpendicularVector( vec3_t dst, const vec3_t src )
+{
+ int pos;
+ int i;
+ float minelem = 1.0F;
+ vec3_t tempvec;
+
+ /*
+ ** find the smallest magnitude axially aligned vector
+ */
+ for ( pos = 0, i = 0; i < 3; i++ )
+ {
+ if ( fabs( src[i] ) < minelem )
+ {
+ pos = i;
+ minelem = fabs( src[i] );
+ }
+ }
+ tempvec[0] = tempvec[1] = tempvec[2] = 0.0F;
+ tempvec[pos] = 1.0F;
+
+ /*
+ ** project the point onto the plane defined by src
+ */
+ ProjectPointOnPlane( dst, tempvec, src );
+
+ /*
+ ** normalize the result
+ */
+ VectorNormalize( dst );
+}
+
+/*
+=================
+pointToLineDistance
+
+Distance from a point to some line
+=================
+*/
+float pointToLineDistance( const vec3_t p0, const vec3_t p1, const vec3_t p2 )
+{
+ vec3_t v, w, y;
+ float c1, c2;
+
+ VectorSubtract( p2, p1, v );
+ VectorSubtract( p1, p0, w );
+
+ CrossProduct( w, v, y );
+ c1 = VectorLength( y );
+ c2 = VectorLength( v );
+
+ if( c2 == 0.0f )
+ return 0.0f;
+ else
+ return c1 / c2;
+}
+
+/*
+=================
+GetPerpendicularViewVector
+
+Used to find an "up" vector for drawing a sprite so that it always faces the view as best as possible
+=================
+*/
+void GetPerpendicularViewVector( const vec3_t point, const vec3_t p1, const vec3_t p2, vec3_t up )
+{
+ vec3_t v1, v2;
+
+ VectorSubtract( point, p1, v1 );
+ VectorNormalize( v1 );
+
+ VectorSubtract( point, p2, v2 );
+ VectorNormalize( v2 );
+
+ CrossProduct( v1, v2, up );
+ VectorNormalize( up );
+}
+
+/*
+================
+ProjectPointOntoVector
+================
+*/
+void ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj )
+{
+ vec3_t pVec, vec;
+
+ VectorSubtract( point, vStart, pVec );
+ VectorSubtract( vEnd, vStart, vec );
+ VectorNormalize( vec );
+ // project onto the directional vector for this segment
+ VectorMA( vStart, DotProduct( pVec, vec ), vec, vProj );
+}
+
+/*
+================
+VectorMaxComponent
+
+Return the biggest component of some vector
+================
+*/
+float VectorMaxComponent( vec3_t v )
+{
+ float biggest = v[ 0 ];
+
+ if( v[ 1 ] > biggest )
+ biggest = v[ 1 ];
+
+ if( v[ 2 ] > biggest )
+ biggest = v[ 2 ];
+
+ return biggest;
+}
+
+/*
+================
+VectorMinComponent
+
+Return the smallest component of some vector
+================
+*/
+float VectorMinComponent( vec3_t v )
+{
+ float smallest = v[ 0 ];
+
+ if( v[ 1 ] < smallest )
+ smallest = v[ 1 ];
+
+ if( v[ 2 ] < smallest )
+ smallest = v[ 2 ];
+
+ return smallest;
+}
+
+
+#define LINE_DISTANCE_EPSILON 1e-05f
+
+/*
+================
+DistanceBetweenLineSegmentsSquared
+
+Return the smallest distance between two line segments, squared
+================
+*/
+vec_t DistanceBetweenLineSegmentsSquared(
+ const vec3_t sP0, const vec3_t sP1,
+ const vec3_t tP0, const vec3_t tP1,
+ float *s, float *t )
+{
+ vec3_t sMag, tMag, diff;
+ float a, b, c, d, e;
+ float D;
+ float sN, sD;
+ float tN, tD;
+ vec3_t separation;
+
+ VectorSubtract( sP1, sP0, sMag );
+ VectorSubtract( tP1, tP0, tMag );
+ VectorSubtract( sP0, tP0, diff );
+ a = DotProduct( sMag, sMag );
+ b = DotProduct( sMag, tMag );
+ c = DotProduct( tMag, tMag );
+ d = DotProduct( sMag, diff );
+ e = DotProduct( tMag, diff );
+ sD = tD = D = a * c - b * b;
+
+ if( D < LINE_DISTANCE_EPSILON )
+ {
+ // the lines are almost parallel
+ sN = 0.0; // force using point P0 on segment S1
+ sD = 1.0; // to prevent possible division by 0.0 later
+ tN = e;
+ tD = c;
+ }
+ else
+ {
+ // get the closest points on the infinite lines
+ sN = ( b * e - c * d );
+ tN = ( a * e - b * d );
+
+ if( sN < 0.0 )
+ {
+ // sN < 0 => the s=0 edge is visible
+ sN = 0.0;
+ tN = e;
+ tD = c;
+ }
+ else if( sN > sD )
+ {
+ // sN > sD => the s=1 edge is visible
+ sN = sD;
+ tN = e + b;
+ tD = c;
+ }
+ }
+
+ if( tN < 0.0 )
+ {
+ // tN < 0 => the t=0 edge is visible
+ tN = 0.0;
+
+ // recompute sN for this edge
+ if( -d < 0.0 )
+ sN = 0.0;
+ else if( -d > a )
+ sN = sD;
+ else
+ {
+ sN = -d;
+ sD = a;
+ }
+ }
+ else if( tN > tD )
+ {
+ // tN > tD => the t=1 edge is visible
+ tN = tD;
+
+ // recompute sN for this edge
+ if( ( -d + b ) < 0.0 )
+ sN = 0;
+ else if( ( -d + b ) > a )
+ sN = sD;
+ else
+ {
+ sN = ( -d + b );
+ sD = a;
+ }
+ }
+
+ // finally do the division to get *s and *t
+ *s = ( fabs( sN ) < LINE_DISTANCE_EPSILON ? 0.0 : sN / sD );
+ *t = ( fabs( tN ) < LINE_DISTANCE_EPSILON ? 0.0 : tN / tD );
+
+ // get the difference of the two closest points
+ VectorScale( sMag, *s, sMag );
+ VectorScale( tMag, *t, tMag );
+ VectorAdd( diff, sMag, separation );
+ VectorSubtract( separation, tMag, separation );
+
+ return VectorLengthSquared( separation );
+}
+
+/*
+================
+DistanceBetweenLineSegments
+
+Return the smallest distance between two line segments
+================
+*/
+vec_t DistanceBetweenLineSegments(
+ const vec3_t sP0, const vec3_t sP1,
+ const vec3_t tP0, const vec3_t tP1,
+ float *s, float *t )
+{
+ return (vec_t)sqrt( DistanceBetweenLineSegmentsSquared(
+ sP0, sP1, tP0, tP1, s, t ) );
+}
+
+/*
+=================
+Q_isnan
+
+Don't pass doubles to this
+================
+*/
+int Q_isnan( float x )
+{
+ union
+ {
+ float f;
+ unsigned int i;
+ } t;
+
+ t.f = x;
+ t.i &= 0x7FFFFFFF;
+ t.i = 0x7F800000 - t.i;
+
+ return (int)( (unsigned int)t.i >> 31 );
+}
diff --git a/src/qcommon/q_platform.h b/src/qcommon/q_platform.h
new file mode 100644
index 0000000..ad9a938
--- /dev/null
+++ b/src/qcommon/q_platform.h
@@ -0,0 +1,335 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+#ifndef __Q_PLATFORM_H
+#define __Q_PLATFORM_H
+
+// this is for determining if we have an asm version of a C function
+#ifdef Q3_VM
+
+#define id386 0
+#define idppc 0
+#define idppc_altivec 0
+
+#else
+
+#if (defined _M_IX86 || defined __i386__) && !defined(C_ONLY)
+#define id386 1
+#else
+#define id386 0
+#endif
+
+#if (defined(powerc) || defined(powerpc) || defined(ppc) || \
+ defined(__ppc) || defined(__ppc__)) && !defined(C_ONLY)
+#define idppc 1
+#if defined(__VEC__)
+#define idppc_altivec 1
+#ifdef MACOS_X // Apple's GCC does this differently than the FSF.
+#define VECCONST_UINT8(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) \
+ (vector unsigned char) (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p)
+#else
+#define VECCONST_UINT8(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) \
+ (vector unsigned char) {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p}
+#endif
+#else
+#define idppc_altivec 0
+#endif
+#else
+#define idppc 0
+#define idppc_altivec 0
+#endif
+
+#endif
+
+#ifndef __ASM_I386__ // don't include the C bits if included from qasm.h
+
+// for windows fastcall option
+#define QDECL
+
+//================================================================= WIN32 ===
+
+#ifdef _WIN32
+
+#undef QDECL
+#define QDECL __cdecl
+
+#if defined( _MSC_VER )
+#define OS_STRING "win_msvc"
+#elif defined __MINGW32__
+#define OS_STRING "win_mingw"
+#endif
+
+#define ID_INLINE __inline
+#define PATH_SEP '\\'
+
+#if defined( _M_IX86 ) || defined( __i386__ )
+#define ARCH_STRING "x86"
+#elif defined _M_ALPHA
+#define ARCH_STRING "AXP"
+#endif
+
+#define Q3_LITTLE_ENDIAN
+
+#define DLL_EXT ".dll"
+
+#endif
+
+//============================================================== MAC OS X ===
+
+#if defined(MACOS_X) || defined(__APPLE_CC__)
+
+// make sure this is defined, just for sanity's sake...
+#ifndef MACOS_X
+#define MACOS_X
+#endif
+
+#define OS_STRING "macosx"
+#define ID_INLINE inline
+#define PATH_SEP '/'
+
+#ifdef __ppc__
+#define ARCH_STRING "ppc"
+#define Q3_BIG_ENDIAN
+#elif defined __i386__
+#define ARCH_STRING "x86"
+#define Q3_LITTLE_ENDIAN
+#endif
+
+#define DLL_EXT ".dylib"
+
+#endif
+
+//================================================================= LINUX ===
+
+#ifdef __linux__
+
+#define OS_STRING "linux"
+#define ID_INLINE inline
+#define PATH_SEP '/'
+
+#if defined __i386__
+#define ARCH_STRING "x86"
+#elif defined __x86_64__
+#define ARCH_STRING "x86_64"
+#elif defined __powerpc64__
+#define ARCH_STRING "ppc64"
+#elif defined __powerpc__
+#define ARCH_STRING "ppc"
+#elif defined __s390__
+#define ARCH_STRING "s390"
+#elif defined __s390x__
+#define ARCH_STRING "s390x"
+#elif defined __ia64__
+#define ARCH_STRING "ia64"
+#elif defined __alpha__
+#define ARCH_STRING "alpha"
+#elif defined __sparc__
+#define ARCH_STRING "sparc"
+#elif defined __arm__
+#define ARCH_STRING "arm"
+#elif defined __cris__
+#define ARCH_STRING "cris"
+#elif defined __hppa__
+#define ARCH_STRING "hppa"
+#elif defined __mips__
+#define ARCH_STRING "mips"
+#elif defined __sh__
+#define ARCH_STRING "sh"
+#endif
+
+#if __FLOAT_WORD_ORDER == __BIG_ENDIAN
+#define Q3_BIG_ENDIAN
+#else
+#define Q3_LITTLE_ENDIAN
+#endif
+
+#define DLL_EXT ".so"
+
+#endif
+
+//=============================================================== FreeBSD ===
+
+#ifdef __FreeBSD__ // rb010123
+
+#include <machine/endian.h>
+
+#define OS_STRING "freebsd"
+#define ID_INLINE inline
+#define PATH_SEP '/'
+
+#ifdef __i386__
+#define ARCH_STRING "x86"
+#elif defined __axp__
+#define ARCH_STRING "alpha"
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+#define Q3_BIG_ENDIAN
+#else
+#define Q3_LITTLE_ENDIAN
+#endif
+
+#define DLL_EXT ".so"
+
+#endif
+
+//================================================================ NetBSD ===
+
+// This is very much like the FreeBSD one and can probably be merged
+#ifdef __NetBSD__
+
+#include <machine/endian.h>
+
+#define OS_STRING "netbsd"
+#define ID_INLINE inline
+#define PATH_SEP '/'
+
+#ifdef __i386__
+#define ARCH_STRING "x86"
+// Netbsd has alot of platforms
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+#define Q3_BIG_ENDIAN
+#else
+#define Q3_LITTLE_ENDIAN
+#endif
+
+#define DLL_EXT ".so"
+
+#endif
+
+//================================================================= SUNOS ===
+
+#ifdef __sun
+
+#include <stdint.h>
+#include <sys/byteorder.h>
+
+#define OS_STRING "solaris"
+#define ID_INLINE inline
+#define PATH_SEP '/'
+
+#ifdef __i386__
+#define ARCH_STRING "x86"
+#elif defined __sparc
+#define ARCH_STRING "sparc"
+#endif
+
+#if defined( _BIG_ENDIAN )
+#define Q3_BIG_ENDIAN
+#elif defined( _LITTLE_ENDIAN )
+#define Q3_LITTLE_ENDIAN
+#endif
+
+#define DLL_EXT ".so"
+
+#endif
+
+//================================================================== Q3VM ===
+
+#ifdef Q3_VM
+
+#define OS_STRING "q3vm"
+#define ID_INLINE
+#define PATH_SEP '/'
+
+#define ARCH_STRING "bytecode"
+
+#define DLL_EXT ".qvm"
+
+#endif
+
+//===========================================================================
+
+//catch missing defines in above blocks
+#if !defined( OS_STRING )
+#error "Operating system not supported"
+#endif
+
+#if !defined( ARCH_STRING )
+#error "Architecture not supported"
+#endif
+
+#ifndef ID_INLINE
+#error "ID_INLINE not defined"
+#endif
+
+#ifndef PATH_SEP
+#error "PATH_SEP not defined"
+#endif
+
+#ifndef DLL_EXT
+#error "DLL_EXT not defined"
+#endif
+
+
+//endianness
+short ShortSwap (short l);
+int LongSwap (int l);
+float FloatSwap (const float *f);
+
+#if defined( Q3_BIG_ENDIAN ) && defined( Q3_LITTLE_ENDIAN )
+#error "Endianness defined as both big and little"
+#elif defined( Q3_BIG_ENDIAN )
+
+#define LittleShort(x) ShortSwap(x)
+#define LittleLong(x) LongSwap(x)
+#define LittleFloat(x) FloatSwap(&x)
+#define BigShort
+#define BigLong
+#define BigFloat
+
+#elif defined( Q3_LITTLE_ENDIAN )
+
+#define LittleShort
+#define LittleLong
+#define LittleFloat
+#define BigShort(x) ShortSwap(x)
+#define BigLong(x) LongSwap(x)
+#define BigFloat(x) FloatSwap(&x)
+
+#elif defined( Q3_VM )
+
+#define LittleShort
+#define LittleLong
+#define LittleFloat
+#define BigShort
+#define BigLong
+#define BigFloat
+
+#else
+#error "Endianness not defined"
+#endif
+
+
+//platform string
+#ifdef NDEBUG
+#define PLATFORM_STRING OS_STRING "-" ARCH_STRING
+#else
+#define PLATFORM_STRING OS_STRING "-" ARCH_STRING "-debug"
+#endif
+
+#endif
+
+#endif
diff --git a/src/qcommon/q_shared.c b/src/qcommon/q_shared.c
new file mode 100644
index 0000000..436a0ca
--- /dev/null
+++ b/src/qcommon/q_shared.c
@@ -0,0 +1,1368 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+// q_shared.c -- stateless support routines that are included in each code dll
+#include "q_shared.h"
+
+float Com_Clamp( float min, float max, float value ) {
+ if ( value < min ) {
+ return min;
+ }
+ if ( value > max ) {
+ return max;
+ }
+ return value;
+}
+
+
+/*
+============
+COM_SkipPath
+============
+*/
+char *COM_SkipPath (char *pathname)
+{
+ char *last;
+
+ last = pathname;
+ while (*pathname)
+ {
+ if (*pathname=='/')
+ last = pathname+1;
+ pathname++;
+ }
+ return last;
+}
+
+/*
+============
+COM_StripExtension
+============
+*/
+void COM_StripExtension( const char *in, char *out, int destsize ) {
+ int length;
+
+ Q_strncpyz(out, in, destsize);
+
+ length = strlen(out)-1;
+ while (length > 0 && out[length] != '.')
+ {
+ length--;
+ if (out[length] == '/')
+ return; // no extension
+ }
+ if (length)
+ out[length] = 0;
+}
+
+
+/*
+==================
+COM_DefaultExtension
+==================
+*/
+void COM_DefaultExtension (char *path, int maxSize, const char *extension ) {
+ char oldPath[MAX_QPATH];
+ char *src;
+
+//
+// if path doesn't have a .EXT, append extension
+// (extension should include the .)
+//
+ src = path + strlen(path) - 1;
+
+ while (*src != '/' && src != path) {
+ if ( *src == '.' ) {
+ return; // it has an extension
+ }
+ src--;
+ }
+
+ Q_strncpyz( oldPath, path, sizeof( oldPath ) );
+ Com_sprintf( path, maxSize, "%s%s", oldPath, extension );
+}
+
+/*
+============================================================================
+
+ BYTE ORDER FUNCTIONS
+
+============================================================================
+*/
+/*
+// can't just use function pointers, or dll linkage can
+// mess up when qcommon is included in multiple places
+static short (*_BigShort) (short l);
+static short (*_LittleShort) (short l);
+static int (*_BigLong) (int l);
+static int (*_LittleLong) (int l);
+static qint64 (*_BigLong64) (qint64 l);
+static qint64 (*_LittleLong64) (qint64 l);
+static float (*_BigFloat) (const float *l);
+static float (*_LittleFloat) (const float *l);
+
+short BigShort(short l){return _BigShort(l);}
+short LittleShort(short l) {return _LittleShort(l);}
+int BigLong (int l) {return _BigLong(l);}
+int LittleLong (int l) {return _LittleLong(l);}
+qint64 BigLong64 (qint64 l) {return _BigLong64(l);}
+qint64 LittleLong64 (qint64 l) {return _LittleLong64(l);}
+float BigFloat (const float *l) {return _BigFloat(l);}
+float LittleFloat (const float *l) {return _LittleFloat(l);}
+*/
+
+short ShortSwap (short l)
+{
+ byte b1,b2;
+
+ b1 = l&255;
+ b2 = (l>>8)&255;
+
+ return (b1<<8) + b2;
+}
+
+short ShortNoSwap (short l)
+{
+ return l;
+}
+
+int LongSwap (int l)
+{
+ byte b1,b2,b3,b4;
+
+ b1 = l&255;
+ b2 = (l>>8)&255;
+ b3 = (l>>16)&255;
+ b4 = (l>>24)&255;
+
+ return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4;
+}
+
+int LongNoSwap (int l)
+{
+ return l;
+}
+
+qint64 Long64Swap (qint64 ll)
+{
+ qint64 result;
+
+ result.b0 = ll.b7;
+ result.b1 = ll.b6;
+ result.b2 = ll.b5;
+ result.b3 = ll.b4;
+ result.b4 = ll.b3;
+ result.b5 = ll.b2;
+ result.b6 = ll.b1;
+ result.b7 = ll.b0;
+
+ return result;
+}
+
+qint64 Long64NoSwap (qint64 ll)
+{
+ return ll;
+}
+
+typedef union {
+ float f;
+ unsigned int i;
+} _FloatByteUnion;
+
+float FloatSwap (const float *f) {
+ _FloatByteUnion out;
+
+ out.f = *f;
+ out.i = LongSwap(out.i);
+
+ return out.f;
+}
+
+float FloatNoSwap (const float *f)
+{
+ return *f;
+}
+
+/*
+================
+Swap_Init
+================
+*/
+/*
+void Swap_Init (void)
+{
+ byte swaptest[2] = {1,0};
+
+// set the byte swapping variables in a portable manner
+ if ( *(short *)swaptest == 1)
+ {
+ _BigShort = ShortSwap;
+ _LittleShort = ShortNoSwap;
+ _BigLong = LongSwap;
+ _LittleLong = LongNoSwap;
+ _BigLong64 = Long64Swap;
+ _LittleLong64 = Long64NoSwap;
+ _BigFloat = FloatSwap;
+ _LittleFloat = FloatNoSwap;
+ }
+ else
+ {
+ _BigShort = ShortNoSwap;
+ _LittleShort = ShortSwap;
+ _BigLong = LongNoSwap;
+ _LittleLong = LongSwap;
+ _BigLong64 = Long64NoSwap;
+ _LittleLong64 = Long64Swap;
+ _BigFloat = FloatNoSwap;
+ _LittleFloat = FloatSwap;
+ }
+
+}
+*/
+
+/*
+============================================================================
+
+PARSING
+
+============================================================================
+*/
+
+static char com_token[MAX_TOKEN_CHARS];
+static char com_parsename[MAX_TOKEN_CHARS];
+static int com_lines;
+
+void COM_BeginParseSession( const char *name )
+{
+ com_lines = 0;
+ Com_sprintf(com_parsename, sizeof(com_parsename), "%s", name);
+}
+
+int COM_GetCurrentParseLine( void )
+{
+ return com_lines;
+}
+
+char *COM_Parse( char **data_p )
+{
+ return COM_ParseExt( data_p, qtrue );
+}
+
+void COM_ParseError( char *format, ... )
+{
+ va_list argptr;
+ static char string[4096];
+
+ va_start (argptr, format);
+ vsprintf (string, format, argptr);
+ va_end (argptr);
+
+ Com_Printf("ERROR: %s, line %d: %s\n", com_parsename, com_lines, string);
+}
+
+void COM_ParseWarning( char *format, ... )
+{
+ va_list argptr;
+ static char string[4096];
+
+ va_start (argptr, format);
+ vsprintf (string, format, argptr);
+ va_end (argptr);
+
+ Com_Printf("WARNING: %s, line %d: %s\n", com_parsename, com_lines, string);
+}
+
+/*
+==============
+COM_Parse
+
+Parse a token out of a string
+Will never return NULL, just empty strings
+
+If "allowLineBreaks" is qtrue then an empty
+string will be returned if the next token is
+a newline.
+==============
+*/
+static char *SkipWhitespace( char *data, qboolean *hasNewLines ) {
+ int c;
+
+ while( (c = *data) <= ' ') {
+ if( !c ) {
+ return NULL;
+ }
+ if( c == '\n' ) {
+ com_lines++;
+ *hasNewLines = qtrue;
+ }
+ data++;
+ }
+
+ return data;
+}
+
+int COM_Compress( char *data_p ) {
+ char *in, *out;
+ int c;
+ qboolean newline = qfalse, whitespace = qfalse;
+
+ in = out = data_p;
+ if (in) {
+ while ((c = *in) != 0) {
+ // skip double slash comments
+ if ( c == '/' && in[1] == '/' ) {
+ while (*in && *in != '\n') {
+ in++;
+ }
+ // skip /* */ comments
+ } else if ( c == '/' && in[1] == '*' ) {
+ while ( *in && ( *in != '*' || in[1] != '/' ) )
+ in++;
+ if ( *in )
+ in += 2;
+ // record when we hit a newline
+ } else if ( c == '\n' || c == '\r' ) {
+ newline = qtrue;
+ in++;
+ // record when we hit whitespace
+ } else if ( c == ' ' || c == '\t') {
+ whitespace = qtrue;
+ in++;
+ // an actual token
+ } else {
+ // if we have a pending newline, emit it (and it counts as whitespace)
+ if (newline) {
+ *out++ = '\n';
+ newline = qfalse;
+ whitespace = qfalse;
+ } if (whitespace) {
+ *out++ = ' ';
+ whitespace = qfalse;
+ }
+
+ // copy quoted strings unmolested
+ if (c == '"') {
+ *out++ = c;
+ in++;
+ while (1) {
+ c = *in;
+ if (c && c != '"') {
+ *out++ = c;
+ in++;
+ } else {
+ break;
+ }
+ }
+ if (c == '"') {
+ *out++ = c;
+ in++;
+ }
+ } else {
+ *out = c;
+ out++;
+ in++;
+ }
+ }
+ }
+ }
+ *out = 0;
+ return out - data_p;
+}
+
+char *COM_ParseExt( char **data_p, qboolean allowLineBreaks )
+{
+ int c = 0, len;
+ qboolean hasNewLines = qfalse;
+ char *data;
+
+ data = *data_p;
+ len = 0;
+ com_token[0] = 0;
+
+ // make sure incoming data is valid
+ if ( !data )
+ {
+ *data_p = NULL;
+ return com_token;
+ }
+
+ while ( 1 )
+ {
+ // skip whitespace
+ data = SkipWhitespace( data, &hasNewLines );
+ if ( !data )
+ {
+ *data_p = NULL;
+ return com_token;
+ }
+ if ( hasNewLines && !allowLineBreaks )
+ {
+ *data_p = data;
+ return com_token;
+ }
+
+ c = *data;
+
+ // skip double slash comments
+ if ( c == '/' && data[1] == '/' )
+ {
+ data += 2;
+ while (*data && *data != '\n') {
+ data++;
+ }
+ }
+ // skip /* */ comments
+ else if ( c=='/' && data[1] == '*' )
+ {
+ data += 2;
+ while ( *data && ( *data != '*' || data[1] != '/' ) )
+ {
+ data++;
+ }
+ if ( *data )
+ {
+ data += 2;
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ // handle quoted strings
+ if (c == '\"')
+ {
+ data++;
+ while (1)
+ {
+ c = *data++;
+ if (c=='\"' || !c)
+ {
+ com_token[len] = 0;
+ *data_p = ( char * ) data;
+ return com_token;
+ }
+ if (len < MAX_TOKEN_CHARS - 1)
+ {
+ com_token[len] = c;
+ len++;
+ }
+ }
+ }
+
+ // parse a regular word
+ do
+ {
+ if (len < MAX_TOKEN_CHARS - 1)
+ {
+ com_token[len] = c;
+ len++;
+ }
+ data++;
+ c = *data;
+ if ( c == '\n' )
+ com_lines++;
+ } while (c>32);
+
+ com_token[len] = 0;
+
+ *data_p = ( char * ) data;
+ return com_token;
+}
+
+
+#if 0
+// no longer used
+/*
+===============
+COM_ParseInfos
+===============
+*/
+int COM_ParseInfos( char *buf, int max, char infos[][MAX_INFO_STRING] ) {
+ char *token;
+ int count;
+ char key[MAX_TOKEN_CHARS];
+
+ count = 0;
+
+ while ( 1 ) {
+ token = COM_Parse( &buf );
+ if ( !token[0] ) {
+ break;
+ }
+ if ( strcmp( token, "{" ) ) {
+ Com_Printf( "Missing { in info file\n" );
+ break;
+ }
+
+ if ( count == max ) {
+ Com_Printf( "Max infos exceeded\n" );
+ break;
+ }
+
+ infos[count][0] = 0;
+ while ( 1 ) {
+ token = COM_ParseExt( &buf, qtrue );
+ if ( !token[0] ) {
+ Com_Printf( "Unexpected end of info file\n" );
+ break;
+ }
+ if ( !strcmp( token, "}" ) ) {
+ break;
+ }
+ Q_strncpyz( key, token, sizeof( key ) );
+
+ token = COM_ParseExt( &buf, qfalse );
+ if ( !token[0] ) {
+ strcpy( token, "<NULL>" );
+ }
+ Info_SetValueForKey( infos[count], key, token );
+ }
+ count++;
+ }
+
+ return count;
+}
+#endif
+
+
+/*
+==================
+COM_MatchToken
+==================
+*/
+void COM_MatchToken( char **buf_p, char *match ) {
+ char *token;
+
+ token = COM_Parse( buf_p );
+ if ( strcmp( token, match ) ) {
+ Com_Error( ERR_DROP, "MatchToken: %s != %s", token, match );
+ }
+}
+
+
+/*
+=================
+SkipBracedSection
+
+The next token should be an open brace.
+Skips until a matching close brace is found.
+Internal brace depths are properly skipped.
+=================
+*/
+void SkipBracedSection (char **program) {
+ char *token;
+ int depth;
+
+ depth = 0;
+ do {
+ token = COM_ParseExt( program, qtrue );
+ if( token[1] == 0 ) {
+ if( token[0] == '{' ) {
+ depth++;
+ }
+ else if( token[0] == '}' ) {
+ depth--;
+ }
+ }
+ } while( depth && *program );
+}
+
+/*
+=================
+SkipRestOfLine
+=================
+*/
+void SkipRestOfLine ( char **data ) {
+ char *p;
+ int c;
+
+ p = *data;
+ while ( (c = *p++) != 0 ) {
+ if ( c == '\n' ) {
+ com_lines++;
+ break;
+ }
+ }
+
+ *data = p;
+}
+
+
+void Parse1DMatrix (char **buf_p, int x, float *m) {
+ char *token;
+ int i;
+
+ COM_MatchToken( buf_p, "(" );
+
+ for (i = 0 ; i < x ; i++) {
+ token = COM_Parse(buf_p);
+ m[i] = atof(token);
+ }
+
+ COM_MatchToken( buf_p, ")" );
+}
+
+void Parse2DMatrix (char **buf_p, int y, int x, float *m) {
+ int i;
+
+ COM_MatchToken( buf_p, "(" );
+
+ for (i = 0 ; i < y ; i++) {
+ Parse1DMatrix (buf_p, x, m + i * x);
+ }
+
+ COM_MatchToken( buf_p, ")" );
+}
+
+void Parse3DMatrix (char **buf_p, int z, int y, int x, float *m) {
+ int i;
+
+ COM_MatchToken( buf_p, "(" );
+
+ for (i = 0 ; i < z ; i++) {
+ Parse2DMatrix (buf_p, y, x, m + i * x*y);
+ }
+
+ COM_MatchToken( buf_p, ")" );
+}
+
+
+/*
+============================================================================
+
+ LIBRARY REPLACEMENT FUNCTIONS
+
+============================================================================
+*/
+
+int Q_isprint( int c )
+{
+ if ( c >= 0x20 && c <= 0x7E )
+ return ( 1 );
+ return ( 0 );
+}
+
+int Q_islower( int c )
+{
+ if (c >= 'a' && c <= 'z')
+ return ( 1 );
+ return ( 0 );
+}
+
+int Q_isupper( int c )
+{
+ if (c >= 'A' && c <= 'Z')
+ return ( 1 );
+ return ( 0 );
+}
+
+int Q_isalpha( int c )
+{
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
+ return ( 1 );
+ return ( 0 );
+}
+
+int Q_isdigit( int c )
+{
+ if ((c >= '0' && c <= '9'))
+ return ( 1 );
+ return ( 0 );
+}
+
+char* Q_strrchr( const char* string, int c )
+{
+ char cc = c;
+ char *s;
+ char *sp=(char *)0;
+
+ s = (char*)string;
+
+ while (*s)
+ {
+ if (*s == cc)
+ sp = s;
+ s++;
+ }
+ if (cc == 0)
+ sp = s;
+
+ return sp;
+}
+
+/*
+=============
+Q_strncpyz
+
+Safe strncpy that ensures a trailing zero
+=============
+*/
+void Q_strncpyz( char *dest, const char *src, int destsize ) {
+ // bk001129 - also NULL dest
+ if ( !dest ) {
+ Com_Error( ERR_FATAL, "Q_strncpyz: NULL dest" );
+ }
+ if ( !src ) {
+ Com_Error( ERR_FATAL, "Q_strncpyz: NULL src" );
+ }
+ if ( destsize < 1 ) {
+ Com_Error(ERR_FATAL,"Q_strncpyz: destsize < 1" );
+ }
+
+ strncpy( dest, src, destsize-1 );
+ dest[destsize-1] = 0;
+}
+
+int Q_stricmpn (const char *s1, const char *s2, int n) {
+ int c1, c2;
+
+ // bk001129 - moved in 1.17 fix not in id codebase
+ if ( s1 == NULL ) {
+ if ( s2 == NULL )
+ return 0;
+ else
+ return -1;
+ }
+ else if ( s2==NULL )
+ return 1;
+
+
+
+ do {
+ c1 = *s1++;
+ c2 = *s2++;
+
+ if (!n--) {
+ return 0; // strings are equal until end point
+ }
+
+ if (c1 != c2) {
+ if (c1 >= 'a' && c1 <= 'z') {
+ c1 -= ('a' - 'A');
+ }
+ if (c2 >= 'a' && c2 <= 'z') {
+ c2 -= ('a' - 'A');
+ }
+ if (c1 != c2) {
+ return c1 < c2 ? -1 : 1;
+ }
+ }
+ } while (c1);
+
+ return 0; // strings are equal
+}
+
+int Q_strncmp (const char *s1, const char *s2, int n) {
+ int c1, c2;
+
+ do {
+ c1 = *s1++;
+ c2 = *s2++;
+
+ if (!n--) {
+ return 0; // strings are equal until end point
+ }
+
+ if (c1 != c2) {
+ return c1 < c2 ? -1 : 1;
+ }
+ } while (c1);
+
+ return 0; // strings are equal
+}
+
+int Q_stricmp (const char *s1, const char *s2) {
+ return (s1 && s2) ? Q_stricmpn (s1, s2, 99999) : -1;
+}
+
+
+char *Q_strlwr( char *s1 ) {
+ char *s;
+
+ s = s1;
+ while ( *s ) {
+ *s = tolower(*s);
+ s++;
+ }
+ return s1;
+}
+
+char *Q_strupr( char *s1 ) {
+ char *s;
+
+ s = s1;
+ while ( *s ) {
+ *s = toupper(*s);
+ s++;
+ }
+ return s1;
+}
+
+
+// never goes past bounds or leaves without a terminating 0
+void Q_strcat( char *dest, int size, const char *src ) {
+ int l1;
+
+ l1 = strlen( dest );
+ if ( l1 >= size ) {
+ Com_Error( ERR_FATAL, "Q_strcat: already overflowed" );
+ }
+ Q_strncpyz( dest + l1, src, size - l1 );
+}
+
+
+int Q_PrintStrlen( const char *string ) {
+ int len;
+ const char *p;
+
+ if( !string ) {
+ return 0;
+ }
+
+ len = 0;
+ p = string;
+ while( *p ) {
+ if( Q_IsColorString( p ) ) {
+ p += 2;
+ continue;
+ }
+ p++;
+ len++;
+ }
+
+ return len;
+}
+
+
+char *Q_CleanStr( char *string ) {
+ char* d;
+ char* s;
+ int c;
+
+ s = string;
+ d = string;
+ while ((c = *s) != 0 ) {
+ if ( Q_IsColorString( s ) ) {
+ s++;
+ }
+ else if ( c >= 0x20 && c <= 0x7E ) {
+ *d++ = c;
+ }
+ s++;
+ }
+ *d = '\0';
+
+ return string;
+}
+
+
+void QDECL Com_sprintf( char *dest, int size, const char *fmt, ...) {
+ int len;
+ va_list argptr;
+ char bigbuffer[32000]; // big, but small enough to fit in PPC stack
+
+ va_start (argptr,fmt);
+ len = vsprintf (bigbuffer,fmt,argptr);
+ va_end (argptr);
+ if ( len >= sizeof( bigbuffer ) ) {
+ Com_Error( ERR_FATAL, "Com_sprintf: overflowed bigbuffer" );
+ }
+ if (len >= size) {
+ Com_Printf ("Com_sprintf: overflow of %i in %i\n", len, size);
+#ifdef _DEBUG
+ __asm {
+ int 3;
+ }
+#endif
+ }
+ Q_strncpyz (dest, bigbuffer, size );
+}
+
+
+/*
+============
+va
+
+does a varargs printf into a temp buffer, so I don't need to have
+varargs versions of all text functions.
+FIXME: make this buffer size safe someday
+============
+*/
+char * QDECL va( char *format, ... ) {
+ va_list argptr;
+ static char string[2][32000]; // in case va is called by nested functions
+ static int index = 0;
+ char *buf;
+
+ buf = string[index & 1];
+ index++;
+
+ va_start (argptr, format);
+ vsprintf (buf, format,argptr);
+ va_end (argptr);
+
+ return buf;
+}
+
+/*
+============
+Com_TruncateLongString
+
+Assumes buffer is atleast TRUNCATE_LENGTH big
+============
+*/
+void Com_TruncateLongString( char *buffer, const char *s )
+{
+ int length = strlen( s );
+
+ if( length <= TRUNCATE_LENGTH )
+ Q_strncpyz( buffer, s, TRUNCATE_LENGTH );
+ else
+ {
+ Q_strncpyz( buffer, s, ( TRUNCATE_LENGTH / 2 ) - 3 );
+ Q_strcat( buffer, TRUNCATE_LENGTH, " ... " );
+ Q_strcat( buffer, TRUNCATE_LENGTH, s + length - ( TRUNCATE_LENGTH / 2 ) + 3 );
+ }
+}
+
+/*
+=====================================================================
+
+ INFO STRINGS
+
+=====================================================================
+*/
+
+/*
+===============
+Info_ValueForKey
+
+Searches the string for the given
+key and returns the associated value, or an empty string.
+FIXME: overflow check?
+===============
+*/
+char *Info_ValueForKey( const char *s, const char *key ) {
+ char pkey[BIG_INFO_KEY];
+ static char value[2][BIG_INFO_VALUE]; // use two buffers so compares
+ // work without stomping on each other
+ static int valueindex = 0;
+ char *o;
+
+ if ( !s || !key ) {
+ return "";
+ }
+
+ if ( strlen( s ) >= BIG_INFO_STRING ) {
+ Com_Error( ERR_DROP, "Info_ValueForKey: oversize infostring" );
+ }
+
+ valueindex ^= 1;
+ if (*s == '\\')
+ s++;
+ while (1)
+ {
+ o = pkey;
+ while (*s != '\\')
+ {
+ if (!*s)
+ return "";
+ *o++ = *s++;
+ }
+ *o = 0;
+ s++;
+
+ o = value[valueindex];
+
+ while (*s != '\\' && *s)
+ {
+ *o++ = *s++;
+ }
+ *o = 0;
+
+ if (!Q_stricmp (key, pkey) )
+ return value[valueindex];
+
+ if (!*s)
+ break;
+ s++;
+ }
+
+ return "";
+}
+
+
+/*
+===================
+Info_NextPair
+
+Used to itterate through all the key/value pairs in an info string
+===================
+*/
+void Info_NextPair( const char **head, char *key, char *value ) {
+ char *o;
+ const char *s;
+
+ s = *head;
+
+ if ( *s == '\\' ) {
+ s++;
+ }
+ key[0] = 0;
+ value[0] = 0;
+
+ o = key;
+ while ( *s != '\\' ) {
+ if ( !*s ) {
+ *o = 0;
+ *head = s;
+ return;
+ }
+ *o++ = *s++;
+ }
+ *o = 0;
+ s++;
+
+ o = value;
+ while ( *s != '\\' && *s ) {
+ *o++ = *s++;
+ }
+ *o = 0;
+
+ *head = s;
+}
+
+
+/*
+===================
+Info_RemoveKey
+===================
+*/
+void Info_RemoveKey( char *s, const char *key ) {
+ char *start;
+ char pkey[MAX_INFO_KEY];
+ char value[MAX_INFO_VALUE];
+ char *o;
+
+ if ( strlen( s ) >= MAX_INFO_STRING ) {
+ Com_Error( ERR_DROP, "Info_RemoveKey: oversize infostring" );
+ }
+
+ if (strchr (key, '\\')) {
+ return;
+ }
+
+ while (1)
+ {
+ start = s;
+ if (*s == '\\')
+ s++;
+ o = pkey;
+ while (*s != '\\')
+ {
+ if (!*s)
+ return;
+ *o++ = *s++;
+ }
+ *o = 0;
+ s++;
+
+ o = value;
+ while (*s != '\\' && *s)
+ {
+ if (!*s)
+ return;
+ *o++ = *s++;
+ }
+ *o = 0;
+
+ if (!strcmp (key, pkey) )
+ {
+ strcpy (start, s); // remove this part
+ return;
+ }
+
+ if (!*s)
+ return;
+ }
+
+}
+
+/*
+===================
+Info_RemoveKey_Big
+===================
+*/
+void Info_RemoveKey_Big( char *s, const char *key ) {
+ char *start;
+ char pkey[BIG_INFO_KEY];
+ char value[BIG_INFO_VALUE];
+ char *o;
+
+ if ( strlen( s ) >= BIG_INFO_STRING ) {
+ Com_Error( ERR_DROP, "Info_RemoveKey_Big: oversize infostring" );
+ }
+
+ if (strchr (key, '\\')) {
+ return;
+ }
+
+ while (1)
+ {
+ start = s;
+ if (*s == '\\')
+ s++;
+ o = pkey;
+ while (*s != '\\')
+ {
+ if (!*s)
+ return;
+ *o++ = *s++;
+ }
+ *o = 0;
+ s++;
+
+ o = value;
+ while (*s != '\\' && *s)
+ {
+ if (!*s)
+ return;
+ *o++ = *s++;
+ }
+ *o = 0;
+
+ if (!strcmp (key, pkey) )
+ {
+ strcpy (start, s); // remove this part
+ return;
+ }
+
+ if (!*s)
+ return;
+ }
+
+}
+
+
+
+
+/*
+==================
+Info_Validate
+
+Some characters are illegal in info strings because they
+can mess up the server's parsing
+==================
+*/
+qboolean Info_Validate( const char *s ) {
+ if ( strchr( s, '\"' ) ) {
+ return qfalse;
+ }
+ if ( strchr( s, ';' ) ) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+/*
+==================
+Info_SetValueForKey
+
+Changes or adds a key/value pair
+==================
+*/
+void Info_SetValueForKey( char *s, const char *key, const char *value ) {
+ char newi[MAX_INFO_STRING];
+ const char* blacklist = "\\;\"";
+
+ if ( strlen( s ) >= MAX_INFO_STRING ) {
+ Com_Error( ERR_DROP, "Info_SetValueForKey: oversize infostring" );
+ }
+
+ for(; *blacklist; ++blacklist)
+ {
+ if (strchr (key, *blacklist) || strchr (value, *blacklist))
+ {
+ Com_Printf (S_COLOR_YELLOW "Can't use keys or values with a '%c': %s = %s\n", *blacklist, key, value);
+ return;
+ }
+ }
+
+ Info_RemoveKey (s, key);
+ if (!value || !strlen(value))
+ return;
+
+ Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value);
+
+ if (strlen(newi) + strlen(s) >= MAX_INFO_STRING)
+ {
+ Com_Printf ("Info string length exceeded\n");
+ return;
+ }
+
+ strcat (newi, s);
+ strcpy (s, newi);
+}
+
+/*
+==================
+Info_SetValueForKey_Big
+
+Changes or adds a key/value pair
+==================
+*/
+void Info_SetValueForKey_Big( char *s, const char *key, const char *value ) {
+ char newi[BIG_INFO_STRING];
+ const char* blacklist = "\\;\"";
+
+ if ( strlen( s ) >= BIG_INFO_STRING ) {
+ Com_Error( ERR_DROP, "Info_SetValueForKey: oversize infostring" );
+ }
+
+ for(; *blacklist; ++blacklist)
+ {
+ if (strchr (key, *blacklist) || strchr (value, *blacklist))
+ {
+ Com_Printf (S_COLOR_YELLOW "Can't use keys or values with a '%c': %s = %s\n", *blacklist, key, value);
+ return;
+ }
+ }
+
+ Info_RemoveKey_Big (s, key);
+ if (!value || !strlen(value))
+ return;
+
+ Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value);
+
+ if (strlen(newi) + strlen(s) >= BIG_INFO_STRING)
+ {
+ Com_Printf ("BIG Info string length exceeded\n");
+ return;
+ }
+
+ strcat (s, newi);
+}
+
+
+
+
+//====================================================================
+
+/*
+==================
+Com_CharIsOneOfCharset
+==================
+*/
+static qboolean Com_CharIsOneOfCharset( char c, char *set )
+{
+ int i;
+
+ for( i = 0; i < strlen( set ); i++ )
+ {
+ if( set[ i ] == c )
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+/*
+==================
+Com_SkipCharset
+==================
+*/
+char *Com_SkipCharset( char *s, char *sep )
+{
+ char *p = s;
+
+ while( p )
+ {
+ if( Com_CharIsOneOfCharset( *p, sep ) )
+ p++;
+ else
+ break;
+ }
+
+ return p;
+}
+
+/*
+==================
+Com_SkipTokens
+==================
+*/
+char *Com_SkipTokens( char *s, int numTokens, char *sep )
+{
+ int sepCount = 0;
+ char *p = s;
+
+ while( sepCount < numTokens )
+ {
+ if( Com_CharIsOneOfCharset( *p++, sep ) )
+ {
+ sepCount++;
+ while( Com_CharIsOneOfCharset( *p, sep ) )
+ p++;
+ }
+ else if( *p == '\0' )
+ break;
+ }
+
+ if( sepCount == numTokens )
+ return p;
+ else
+ return s;
+}
+
+//ROTAX
+#define isvisiblechar(c) (isgraph(c) && c != '"')
+
+void fixLineSpaces( char *result, char *source )
+{
+ char *reader = source;
+ char *writer = result;
+
+ while( *reader )
+ {
+ //Copy all normal characters
+ while ( *reader && isvisiblechar(*reader))
+ {
+ *writer = *reader;
+ reader++;
+ writer++;
+ }
+ if (*reader && !isvisiblechar(*reader))
+ {
+ *writer = ' ';
+ writer++;
+ reader++;
+ }
+ while ( *reader && !isvisiblechar(*reader))
+ {
+ reader++;
+ }
+ }
+ *writer = 0; //End of the line
+}
diff --git a/src/qcommon/q_shared.h b/src/qcommon/q_shared.h
new file mode 100644
index 0000000..47aead8
--- /dev/null
+++ b/src/qcommon/q_shared.h
@@ -0,0 +1,1330 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+#ifndef __Q_SHARED_H
+#define __Q_SHARED_H
+
+// q_shared.h -- included first by ALL program modules.
+// A user mod should never modify this file
+
+#define VERSION_NUMBER "1.1.0"
+#define Q3_VERSION "tremulous " VERSION_NUMBER
+#ifndef SVN_VERSION
+#define SVN_VERSION Q3_VERSION
+#endif
+#define CLIENT_WINDOW_TITLE "Tremulous " VERSION_NUMBER
+#define CLIENT_WINDOW_ICON "Tremulous"
+#define CONSOLE_WINDOW_TITLE "Tremulous " VERSION_NUMBER " console"
+#define CONSOLE_WINDOW_ICON "Tremulous console"
+
+#define MAX_TEAMNAME 32
+
+#ifdef _MSC_VER
+
+#pragma warning(disable : 4018) // signed/unsigned mismatch
+#pragma warning(disable : 4032)
+#pragma warning(disable : 4051)
+#pragma warning(disable : 4057) // slightly different base types
+#pragma warning(disable : 4100) // unreferenced formal parameter
+#pragma warning(disable : 4115)
+#pragma warning(disable : 4125) // decimal digit terminates octal escape sequence
+#pragma warning(disable : 4127) // conditional expression is constant
+#pragma warning(disable : 4136)
+#pragma warning(disable : 4152) // nonstandard extension, function/data pointer conversion in expression
+//#pragma warning(disable : 4201)
+//#pragma warning(disable : 4214)
+#pragma warning(disable : 4244)
+#pragma warning(disable : 4142) // benign redefinition
+//#pragma warning(disable : 4305) // truncation from const double to float
+//#pragma warning(disable : 4310) // cast truncates constant value
+//#pragma warning(disable: 4505) // unreferenced local function has been removed
+#pragma warning(disable : 4514)
+#pragma warning(disable : 4702) // unreachable code
+#pragma warning(disable : 4711) // selected for automatic inline expansion
+#pragma warning(disable : 4220) // varargs matches remaining parameters
+//#pragma intrinsic( memset, memcpy )
+#endif
+
+//Ignore __attribute__ on non-gcc platforms
+#ifndef __GNUC__
+#ifndef __attribute__
+#define __attribute__(x)
+#endif
+#endif
+
+/**********************************************************************
+ VM Considerations
+
+ The VM can not use the standard system headers because we aren't really
+ using the compiler they were meant for. We use bg_lib.h which contains
+ prototypes for the functions we define for our own use in bg_lib.c.
+
+ When writing mods, please add needed headers HERE, do not start including
+ stuff like <stdio.h> in the various .c files that make up each of the VMs
+ since you will be including system headers files can will have issues.
+
+ Remember, if you use a C library function that is not defined in bg_lib.c,
+ you will have to add your own version for support in the VM.
+
+ **********************************************************************/
+
+#ifdef Q3_VM
+
+#include "../game/bg_lib.h"
+
+#else
+
+#include <assert.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <ctype.h>
+#include <limits.h>
+
+#endif
+
+#include "q_platform.h"
+
+//=============================================================
+
+#ifdef Q3_VM
+ typedef int intptr_t;
+#else
+ #ifndef _MSC_VER
+ #include <stdint.h>
+ #else
+ #include <io.h>
+ typedef __int64 int64_t;
+ typedef __int32 int32_t;
+ typedef __int16 int16_t;
+ typedef __int8 int8_t;
+ typedef unsigned __int64 uint64_t;
+ typedef unsigned __int32 uint32_t;
+ typedef unsigned __int16 uint16_t;
+ typedef unsigned __int8 uint8_t;
+ #endif
+#endif
+
+typedef unsigned char byte;
+
+typedef enum {qfalse, qtrue} qboolean;
+
+typedef int qhandle_t;
+typedef int sfxHandle_t;
+typedef int fileHandle_t;
+typedef int clipHandle_t;
+
+#define PAD(x,y) (((x)+(y)-1) & ~((y)-1))
+
+#ifdef __GNUC__
+#define ALIGN(x) __attribute__((aligned(x)))
+#else
+#define ALIGN(x)
+#endif
+
+#ifndef NULL
+#define NULL ((void *)0)
+#endif
+
+#define MAX_QINT 0x7fffffff
+#define MIN_QINT (-MAX_QINT-1)
+
+
+// angle indexes
+#define PITCH 0 // up / down
+#define YAW 1 // left / right
+#define ROLL 2 // fall over
+
+// the game guarantees that no string from the network will ever
+// exceed MAX_STRING_CHARS
+#define MAX_STRING_CHARS 1024 // max length of a string passed to Cmd_TokenizeString
+#define MAX_STRING_TOKENS 1024 // max tokens resulting from Cmd_TokenizeString
+#define MAX_TOKEN_CHARS 1024 // max length of an individual token
+
+#define MAX_INFO_STRING 1024
+#define MAX_INFO_KEY 1024
+#define MAX_INFO_VALUE 1024
+
+#define BIG_INFO_STRING 8192 // used for system info key only
+#define BIG_INFO_KEY 8192
+#define BIG_INFO_VALUE 8192
+
+
+#define MAX_QPATH 64 // max length of a quake game pathname
+#ifdef PATH_MAX
+#define MAX_OSPATH PATH_MAX
+#else
+#define MAX_OSPATH 256 // max length of a filesystem pathname
+#endif
+
+#define MAX_NAME_LENGTH 32 // max length of a client name
+#define MAX_HOSTNAME_LENGTH 80 // max length of a host name
+
+#define MAX_SAY_TEXT 150
+
+// paramters for command buffer stuffing
+typedef enum {
+ EXEC_NOW, // don't return until completed, a VM should NEVER use this,
+ // because some commands might cause the VM to be unloaded...
+ EXEC_INSERT, // insert at current position, but don't run yet
+ EXEC_APPEND // add to end of the command buffer (normal case)
+} cbufExec_t;
+
+
+//
+// these aren't needed by any of the VMs. put in another header?
+//
+#define MAX_MAP_AREA_BYTES 32 // bit vector of area visibility
+
+
+// print levels from renderer (FIXME: set up for game / cgame?)
+typedef enum {
+ PRINT_ALL,
+ PRINT_DEVELOPER, // only print when "developer 1"
+ PRINT_WARNING,
+ PRINT_ERROR
+} printParm_t;
+
+
+#ifdef ERR_FATAL
+#undef ERR_FATAL // this is be defined in malloc.h
+#endif
+
+// parameters to the main Error routine
+typedef enum {
+ ERR_FATAL, // exit the entire game with a popup window
+ ERR_DROP, // print to console and disconnect from game
+ ERR_SERVERDISCONNECT, // don't kill server
+ ERR_DISCONNECT, // client disconnected from the server
+ ERR_NEED_CD // pop up the need-cd dialog
+} errorParm_t;
+
+
+// font rendering values used by ui and cgame
+
+#define PROP_GAP_WIDTH 3
+#define PROP_SPACE_WIDTH 8
+#define PROP_HEIGHT 27
+#define PROP_SMALL_SIZE_SCALE 0.75
+
+#define BLINK_DIVISOR 200
+#define PULSE_DIVISOR 75
+
+#define UI_LEFT 0x00000000 // default
+#define UI_CENTER 0x00000001
+#define UI_RIGHT 0x00000002
+#define UI_FORMATMASK 0x00000007
+#define UI_SMALLFONT 0x00000010
+#define UI_BIGFONT 0x00000020 // default
+#define UI_GIANTFONT 0x00000040
+#define UI_DROPSHADOW 0x00000800
+#define UI_BLINK 0x00001000
+#define UI_INVERSE 0x00002000
+#define UI_PULSE 0x00004000
+
+#if defined(_DEBUG) && !defined(BSPC)
+ #define HUNK_DEBUG
+#endif
+
+typedef enum {
+ h_high,
+ h_low,
+ h_dontcare
+} ha_pref;
+
+#ifdef HUNK_DEBUG
+#define Hunk_Alloc( size, preference ) Hunk_AllocDebug(size, preference, #size, __FILE__, __LINE__)
+void *Hunk_AllocDebug( int size, ha_pref preference, char *label, char *file, int line );
+#else
+void *Hunk_Alloc( int size, ha_pref preference );
+#endif
+
+#if defined(__GNUC__) && !defined(__MINGW32__) && !defined(MACOS_X)
+// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=371
+// custom Snd_Memset implementation for glibc memset bug workaround
+void Snd_Memset (void* dest, const int val, const size_t count);
+#else
+#define Snd_Memset Com_Memset
+#endif
+
+#define Com_Memset memset
+#define Com_Memcpy memcpy
+
+#define CIN_system 1
+#define CIN_loop 2
+#define CIN_hold 4
+#define CIN_silent 8
+#define CIN_shader 16
+
+/*
+==============================================================
+
+MATHLIB
+
+==============================================================
+*/
+
+
+typedef float vec_t;
+typedef vec_t vec2_t[2];
+typedef vec_t vec3_t[3];
+typedef vec_t vec4_t[4];
+typedef vec_t vec5_t[5];
+
+typedef int fixed4_t;
+typedef int fixed8_t;
+typedef int fixed16_t;
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846f // matches value in gcc v2 math.h
+#endif
+
+#ifndef M_SQRT2
+#define M_SQRT2 1.414213562f
+#endif
+
+#ifndef M_ROOT3
+#define M_ROOT3 1.732050808f
+#endif
+
+#define NUMVERTEXNORMALS 162
+extern vec3_t bytedirs[NUMVERTEXNORMALS];
+
+// all drawing is done to a 640*480 virtual screen size
+// and will be automatically scaled to the real resolution
+#define SCREEN_WIDTH 640
+#define SCREEN_HEIGHT 480
+
+#define TINYCHAR_WIDTH (SMALLCHAR_WIDTH)
+#define TINYCHAR_HEIGHT (SMALLCHAR_HEIGHT/2)
+
+#define SMALLCHAR_WIDTH 8
+#define SMALLCHAR_HEIGHT 16
+
+#define BIGCHAR_WIDTH 16
+#define BIGCHAR_HEIGHT 16
+
+#define GIANTCHAR_WIDTH 32
+#define GIANTCHAR_HEIGHT 48
+
+extern vec4_t colorBlack;
+extern vec4_t colorRed;
+extern vec4_t colorGreen;
+extern vec4_t colorBlue;
+extern vec4_t colorYellow;
+extern vec4_t colorMagenta;
+extern vec4_t colorCyan;
+extern vec4_t colorWhite;
+extern vec4_t colorLtGrey;
+extern vec4_t colorMdGrey;
+extern vec4_t colorDkGrey;
+
+#define Q_COLOR_ESCAPE '^'
+#define Q_IsColorString(p) ( p && *(p) == Q_COLOR_ESCAPE && *((p)+1) && *((p)+1) != Q_COLOR_ESCAPE )
+
+#define COLOR_BLACK '0'
+#define COLOR_RED '1'
+#define COLOR_GREEN '2'
+#define COLOR_YELLOW '3'
+#define COLOR_BLUE '4'
+#define COLOR_CYAN '5'
+#define COLOR_MAGENTA '6'
+#define COLOR_WHITE '7'
+#define ColorIndex(c) ( ( (c) - '0' ) & 7 )
+
+#define S_COLOR_BLACK "^0"
+#define S_COLOR_RED "^1"
+#define S_COLOR_GREEN "^2"
+#define S_COLOR_YELLOW "^3"
+#define S_COLOR_BLUE "^4"
+#define S_COLOR_CYAN "^5"
+#define S_COLOR_MAGENTA "^6"
+#define S_COLOR_WHITE "^7"
+
+extern vec4_t g_color_table[8];
+
+#define MAKERGB( v, r, g, b ) v[0]=r;v[1]=g;v[2]=b
+#define MAKERGBA( v, r, g, b, a ) v[0]=r;v[1]=g;v[2]=b;v[3]=a
+
+#define DEG2RAD( a ) ( ( (a) * M_PI ) / 180.0F )
+#define RAD2DEG( a ) ( ( (a) * 180.0f ) / M_PI )
+
+struct cplane_s;
+
+extern vec3_t vec3_origin;
+extern vec3_t axisDefault[3];
+
+#define nanmask (255<<23)
+
+#define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask)
+
+#if idppc
+
+static ID_INLINE float Q_rsqrt( float number ) {
+ float x = 0.5f * number;
+ float y;
+#ifdef __GNUC__
+ asm("frsqrte %0,%1" : "=f" (y) : "f" (number));
+#else
+ y = __frsqrte( number );
+#endif
+ return y * (1.5f - (x * y * y));
+ }
+
+#ifdef __GNUC__
+static ID_INLINE float Q_fabs(float x) {
+ float abs_x;
+
+ asm("fabs %0,%1" : "=f" (abs_x) : "f" (x));
+ return abs_x;
+}
+#else
+#define Q_fabs __fabsf
+#endif
+
+#else
+float Q_fabs( float f );
+float Q_rsqrt( float f ); // reciprocal square root
+#endif
+
+#define SQRTFAST( x ) ( (x) * Q_rsqrt( x ) )
+
+signed char ClampChar( int i );
+signed short ClampShort( int i );
+
+// this isn't a real cheap function to call!
+int DirToByte( vec3_t dir );
+void ByteToDir( int b, vec3_t dir );
+
+#if 1
+
+#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2])
+#define VectorSubtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2])
+#define VectorAdd(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2])
+#define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2])
+#define VectorScale(v, s, o) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s))
+#define VectorMA(v, s, b, o) ((o)[0]=(v)[0]+(b)[0]*(s),(o)[1]=(v)[1]+(b)[1]*(s),(o)[2]=(v)[2]+(b)[2]*(s))
+#define VectorLerp( f, s, e, r ) ((r)[0]=(s)[0]+(f)*((e)[0]-(s)[0]),\
+ (r)[1]=(s)[1]+(f)*((e)[1]-(s)[1]),\
+ (r)[2]=(s)[2]+(f)*((e)[2]-(s)[2]))
+
+#else
+
+#define DotProduct(x,y) _DotProduct(x,y)
+#define VectorSubtract(a,b,c) _VectorSubtract(a,b,c)
+#define VectorAdd(a,b,c) _VectorAdd(a,b,c)
+#define VectorCopy(a,b) _VectorCopy(a,b)
+#define VectorScale(v, s, o) _VectorScale(v,s,o)
+#define VectorMA(v, s, b, o) _VectorMA(v,s,b,o)
+
+#endif
+
+#ifdef Q3_VM
+#ifdef VectorCopy
+#undef VectorCopy
+// this is a little hack to get more efficient copies in our interpreter
+typedef struct {
+ float v[3];
+} vec3struct_t;
+#define VectorCopy(a,b) (*(vec3struct_t *)b=*(vec3struct_t *)a)
+#endif
+#endif
+
+#define Vector2Set(v, x, y) ((v)[0]=(x), (v)[1]=(y))
+#define VectorClear(a) ((a)[0]=(a)[1]=(a)[2]=0)
+#define VectorNegate(a,b) ((b)[0]=-(a)[0],(b)[1]=-(a)[1],(b)[2]=-(a)[2])
+#define VectorSet(v, x, y, z) ((v)[0]=(x), (v)[1]=(y), (v)[2]=(z))
+#define Vector4Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3])
+#define Vector4Add(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2],(c)[3]=(a)[3]+(b)[3])
+
+#define SnapVector(v) {v[0]=((int)(v[0]));v[1]=((int)(v[1]));v[2]=((int)(v[2]));}
+// just in case you do't want to use the macros
+vec_t _DotProduct( const vec3_t v1, const vec3_t v2 );
+void _VectorSubtract( const vec3_t veca, const vec3_t vecb, vec3_t out );
+void _VectorAdd( const vec3_t veca, const vec3_t vecb, vec3_t out );
+void _VectorCopy( const vec3_t in, vec3_t out );
+void _VectorScale( const vec3_t in, float scale, vec3_t out );
+void _VectorMA( const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc );
+
+unsigned ColorBytes3 (float r, float g, float b);
+unsigned ColorBytes4 (float r, float g, float b, float a);
+
+float NormalizeColor( const vec3_t in, vec3_t out );
+
+float RadiusFromBounds( const vec3_t mins, const vec3_t maxs );
+void ClearBounds( vec3_t mins, vec3_t maxs );
+void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs );
+
+#if !defined( Q3_VM ) || ( defined( Q3_VM ) && defined( __Q3_VM_MATH ) )
+static ID_INLINE int VectorCompare( const vec3_t v1, const vec3_t v2 ) {
+ if (v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2]) {
+ return 0;
+ }
+ return 1;
+}
+
+static ID_INLINE int VectorCompareEpsilon(
+ const vec3_t v1, const vec3_t v2, float epsilon )
+{
+ vec3_t d;
+
+ VectorSubtract( v1, v2, d );
+ d[ 0 ] = fabs( d[ 0 ] );
+ d[ 1 ] = fabs( d[ 1 ] );
+ d[ 2 ] = fabs( d[ 2 ] );
+
+ if( d[ 0 ] > epsilon || d[ 1 ] > epsilon || d[ 2 ] > epsilon )
+ return 0;
+
+ return 1;
+}
+
+static ID_INLINE vec_t VectorLength( const vec3_t v ) {
+ return (vec_t)sqrt (v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
+}
+
+static ID_INLINE vec_t VectorLengthSquared( const vec3_t v ) {
+ return (v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
+}
+
+static ID_INLINE vec_t Distance( const vec3_t p1, const vec3_t p2 ) {
+ vec3_t v;
+
+ VectorSubtract (p2, p1, v);
+ return VectorLength( v );
+}
+
+static ID_INLINE vec_t DistanceSquared( const vec3_t p1, const vec3_t p2 ) {
+ vec3_t v;
+
+ VectorSubtract (p2, p1, v);
+ return v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
+}
+
+// fast vector normalize routine that does not check to make sure
+// that length != 0, nor does it return length, uses rsqrt approximation
+static ID_INLINE void VectorNormalizeFast( vec3_t v )
+{
+ float ilength;
+
+ ilength = Q_rsqrt( DotProduct( v, v ) );
+
+ v[0] *= ilength;
+ v[1] *= ilength;
+ v[2] *= ilength;
+}
+
+static ID_INLINE void VectorInverse( vec3_t v ){
+ v[0] = -v[0];
+ v[1] = -v[1];
+ v[2] = -v[2];
+}
+
+static ID_INLINE void CrossProduct( const vec3_t v1, const vec3_t v2, vec3_t cross ) {
+ cross[0] = v1[1]*v2[2] - v1[2]*v2[1];
+ cross[1] = v1[2]*v2[0] - v1[0]*v2[2];
+ cross[2] = v1[0]*v2[1] - v1[1]*v2[0];
+}
+
+#else
+int VectorCompare( const vec3_t v1, const vec3_t v2 );
+
+vec_t VectorLength( const vec3_t v );
+
+vec_t VectorLengthSquared( const vec3_t v );
+
+vec_t Distance( const vec3_t p1, const vec3_t p2 );
+
+vec_t DistanceSquared( const vec3_t p1, const vec3_t p2 );
+
+void VectorNormalizeFast( vec3_t v );
+
+void VectorInverse( vec3_t v );
+
+void CrossProduct( const vec3_t v1, const vec3_t v2, vec3_t cross );
+
+#endif
+
+vec_t VectorNormalize (vec3_t v); // returns vector length
+vec_t VectorNormalize2( const vec3_t v, vec3_t out );
+void Vector4Scale( const vec4_t in, vec_t scale, vec4_t out );
+void VectorRotate( vec3_t in, vec3_t matrix[3], vec3_t out );
+int Q_log2(int val);
+
+float Q_acos(float c);
+
+int Q_rand( int *seed );
+float Q_random( int *seed );
+float Q_crandom( int *seed );
+
+#define random() ((rand () & 0x7fff) / ((float)0x7fff))
+#define crandom() (2.0 * (random() - 0.5))
+
+void vectoangles( const vec3_t value1, vec3_t angles);
+void AnglesToAxis( const vec3_t angles, vec3_t axis[3] );
+void AxisToAngles( vec3_t axis[3], vec3_t angles );
+
+void AxisClear( vec3_t axis[3] );
+void AxisCopy( vec3_t in[3], vec3_t out[3] );
+
+void SetPlaneSignbits( struct cplane_s *out );
+int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *plane);
+
+float AngleMod(float a);
+float LerpAngle (float from, float to, float frac);
+float AngleSubtract( float a1, float a2 );
+void AnglesSubtract( vec3_t v1, vec3_t v2, vec3_t v3 );
+
+float AngleNormalize360 ( float angle );
+float AngleNormalize180 ( float angle );
+float AngleDelta ( float angle1, float angle2 );
+
+qboolean PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c );
+void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal );
+void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees );
+void RotateAroundDirection( vec3_t axis[3], vec_t angle );
+void MakeNormalVectors( const vec3_t forward, vec3_t right, vec3_t up );
+// perpendicular vector could be replaced by this
+
+//int PlaneTypeForNormal (vec3_t normal);
+
+void MatrixMultiply(float in1[3][3], float in2[3][3], float out[3][3]);
+void VectorMatrixMultiply( const vec3_t p, vec3_t m[ 3 ], vec3_t out );
+void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up);
+void PerpendicularVector( vec3_t dst, const vec3_t src );
+int Q_isnan( float x );
+
+void GetPerpendicularViewVector( const vec3_t point, const vec3_t p1,
+ const vec3_t p2, vec3_t up );
+void ProjectPointOntoVector( vec3_t point, vec3_t vStart,
+ vec3_t vEnd, vec3_t vProj );
+float VectorDistance( vec3_t v1, vec3_t v2 );
+
+float pointToLineDistance( const vec3_t point, const vec3_t p1, const vec3_t p2 );
+float VectorMinComponent( vec3_t v );
+float VectorMaxComponent( vec3_t v );
+
+vec_t DistanceBetweenLineSegmentsSquared(
+ const vec3_t sP0, const vec3_t sP1,
+ const vec3_t tP0, const vec3_t tP1,
+ float *s, float *t );
+vec_t DistanceBetweenLineSegments(
+ const vec3_t sP0, const vec3_t sP1,
+ const vec3_t tP0, const vec3_t tP1,
+ float *s, float *t );
+
+#ifndef MAX
+#define MAX(x,y) ((x)>(y)?(x):(y))
+#endif
+
+#ifndef MIN
+#define MIN(x,y) ((x)<(y)?(x):(y))
+#endif
+
+//=============================================
+
+float Com_Clamp( float min, float max, float value );
+
+char *COM_SkipPath( char *pathname );
+void COM_StripExtension(const char *in, char *out, int destsize);
+void COM_DefaultExtension( char *path, int maxSize, const char *extension );
+
+void COM_BeginParseSession( const char *name );
+int COM_GetCurrentParseLine( void );
+char *COM_Parse( char **data_p );
+char *COM_ParseExt( char **data_p, qboolean allowLineBreak );
+int COM_Compress( char *data_p );
+void COM_ParseError( char *format, ... ) __attribute__ ((format (printf, 1, 2)));
+void COM_ParseWarning( char *format, ... ) __attribute__ ((format (printf, 1, 2)));
+//int COM_ParseInfos( char *buf, int max, char infos[][MAX_INFO_STRING] );
+
+#define MAX_TOKENLENGTH 1024
+
+#ifndef TT_STRING
+//token types
+#define TT_STRING 1 // string
+#define TT_LITERAL 2 // literal
+#define TT_NUMBER 3 // number
+#define TT_NAME 4 // name
+#define TT_PUNCTUATION 5 // punctuation
+#endif
+
+typedef struct pc_token_s
+{
+ int type;
+ int subtype;
+ int intvalue;
+ float floatvalue;
+ char string[MAX_TOKENLENGTH];
+} pc_token_t;
+
+// data is an in/out parm, returns a parsed out token
+
+void COM_MatchToken( char**buf_p, char *match );
+
+void SkipBracedSection (char **program);
+void SkipRestOfLine ( char **data );
+
+void Parse1DMatrix (char **buf_p, int x, float *m);
+void Parse2DMatrix (char **buf_p, int y, int x, float *m);
+void Parse3DMatrix (char **buf_p, int z, int y, int x, float *m);
+
+void QDECL Com_sprintf (char *dest, int size, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
+
+char *Com_SkipTokens( char *s, int numTokens, char *sep );
+char *Com_SkipCharset( char *s, char *sep );
+
+void Com_RandomBytes( byte *string, int len );
+
+// mode parm for FS_FOpenFile
+typedef enum {
+ FS_READ,
+ FS_WRITE,
+ FS_APPEND,
+ FS_APPEND_SYNC
+} fsMode_t;
+
+typedef enum {
+ FS_SEEK_CUR,
+ FS_SEEK_END,
+ FS_SEEK_SET
+} fsOrigin_t;
+
+//=============================================
+
+int Q_isprint( int c );
+int Q_islower( int c );
+int Q_isupper( int c );
+int Q_isalpha( int c );
+int Q_isdigit( int c );
+
+// portable case insensitive compare
+int Q_stricmp (const char *s1, const char *s2);
+int Q_strncmp (const char *s1, const char *s2, int n);
+int Q_stricmpn (const char *s1, const char *s2, int n);
+char *Q_strlwr( char *s1 );
+char *Q_strupr( char *s1 );
+char *Q_strrchr( const char* string, int c );
+
+// buffer size safe library replacements
+void Q_strncpyz( char *dest, const char *src, int destsize );
+void Q_strcat( char *dest, int size, const char *src );
+
+// strlen that discounts Quake color sequences
+int Q_PrintStrlen( const char *string );
+// removes color sequences from string
+char *Q_CleanStr( char *string );
+
+//=============================================
+
+// 64-bit integers for global rankings interface
+// implemented as a struct for qvm compatibility
+typedef struct
+{
+ byte b0;
+ byte b1;
+ byte b2;
+ byte b3;
+ byte b4;
+ byte b5;
+ byte b6;
+ byte b7;
+} qint64;
+
+//=============================================
+/*
+short BigShort(short l);
+short LittleShort(short l);
+int BigLong (int l);
+int LittleLong (int l);
+qint64 BigLong64 (qint64 l);
+qint64 LittleLong64 (qint64 l);
+float BigFloat (const float *l);
+float LittleFloat (const float *l);
+
+void Swap_Init (void);
+*/
+char * QDECL va(char *format, ...) __attribute__ ((format (printf, 1, 2)));
+
+#define TRUNCATE_LENGTH 64
+void Com_TruncateLongString( char *buffer, const char *s );
+
+//=============================================
+
+//
+// key / value info strings
+//
+char *Info_ValueForKey( const char *s, const char *key );
+void Info_RemoveKey( char *s, const char *key );
+void Info_RemoveKey_big( char *s, const char *key );
+void Info_SetValueForKey( char *s, const char *key, const char *value );
+void Info_SetValueForKey_Big( char *s, const char *key, const char *value );
+qboolean Info_Validate( const char *s );
+void Info_NextPair( const char **s, char *key, char *value );
+
+// this is only here so the functions in q_shared.c and bg_*.c can link
+void QDECL Com_Error( int level, const char *error, ... ) __attribute__ ((format (printf, 2, 3)));
+void QDECL Com_Printf( const char *msg, ... ) __attribute__ ((format (printf, 1, 2)));
+
+
+/*
+==========================================================
+
+CVARS (console variables)
+
+Many variables can be used for cheating purposes, so when
+cheats is zero, force all unspecified variables to their
+default values.
+==========================================================
+*/
+
+#define CVAR_ARCHIVE 1 // set to cause it to be saved to vars.rc
+ // used for system variables, not for player
+ // specific configurations
+#define CVAR_USERINFO 2 // sent to server on connect or change
+#define CVAR_SERVERINFO 4 // sent in response to front end requests
+#define CVAR_SYSTEMINFO 8 // these cvars will be duplicated on all clients
+#define CVAR_INIT 16 // don't allow change from console at all,
+ // but can be set from the command line
+#define CVAR_LATCH 32 // will only change when C code next does
+ // a Cvar_Get(), so it can't be changed
+ // without proper initialization. modified
+ // will be set, even though the value hasn't
+ // changed yet
+#define CVAR_ROM 64 // display only, cannot be set by user at all
+#define CVAR_USER_CREATED 128 // created by a set command
+#define CVAR_TEMP 256 // can be set even when cheats are disabled, but is not archived
+#define CVAR_CHEAT 512 // can not be changed if cheats are disabled
+#define CVAR_NORESTART 1024 // do not clear when a cvar_restart is issued
+
+#define CVAR_SERVER_CREATED 2048 // cvar was created by a server the client connected to.
+#define CVAR_NONEXISTENT 0xFFFFFFFF // Cvar doesn't exist.
+
+// nothing outside the Cvar_*() functions should modify these fields!
+typedef struct cvar_s {
+ char *name;
+ char *string;
+ char *resetString; // cvar_restart will reset to this value
+ char *latchedString; // for CVAR_LATCH vars
+ int flags;
+ qboolean modified; // set each time the cvar is changed
+ int modificationCount; // incremented each time the cvar is changed
+ float value; // atof( string )
+ int integer; // atoi( string )
+ struct cvar_s *next;
+ struct cvar_s *hashNext;
+} cvar_t;
+
+#define MAX_CVAR_VALUE_STRING 256
+
+typedef int cvarHandle_t;
+
+// the modules that run in the virtual machine can't access the cvar_t directly,
+// so they must ask for structured updates
+typedef struct {
+ cvarHandle_t handle;
+ int modificationCount;
+ float value;
+ int integer;
+ char string[MAX_CVAR_VALUE_STRING];
+} vmCvar_t;
+
+/*
+==============================================================
+
+COLLISION DETECTION
+
+==============================================================
+*/
+
+#include "surfaceflags.h" // shared with the q3map utility
+
+// plane types are used to speed some tests
+// 0-2 are axial planes
+#define PLANE_X 0
+#define PLANE_Y 1
+#define PLANE_Z 2
+#define PLANE_NON_AXIAL 3
+
+
+/*
+=================
+PlaneTypeForNormal
+=================
+*/
+
+#define PlaneTypeForNormal(x) (x[0] == 1.0 ? PLANE_X : (x[1] == 1.0 ? PLANE_Y : (x[2] == 1.0 ? PLANE_Z : PLANE_NON_AXIAL) ) )
+
+// plane_t structure
+// !!! if this is changed, it must be changed in asm code too !!!
+typedef struct cplane_s {
+ vec3_t normal;
+ float dist;
+ byte type; // for fast side tests: 0,1,2 = axial, 3 = nonaxial
+ byte signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision
+ byte pad[2];
+} cplane_t;
+
+typedef enum {
+ TT_NONE,
+
+ TT_AABB,
+ TT_CAPSULE,
+ TT_BISPHERE,
+
+ TT_NUM_TRACE_TYPES
+} traceType_t;
+
+// a trace is returned when a box is swept through the world
+typedef struct {
+ qboolean allsolid; // if true, plane is not valid
+ qboolean startsolid; // if true, the initial point was in a solid area
+ float fraction; // time completed, 1.0 = didn't hit anything
+ vec3_t endpos; // final position
+ cplane_t plane; // surface normal at impact, transformed to world space
+ int surfaceFlags; // surface hit
+ int contents; // contents on other side of surface hit
+ int entityNum; // entity the contacted sirface is a part of
+ float lateralFraction; // fraction of collision tangetially to the trace direction
+} trace_t;
+
+// trace->entityNum can also be 0 to (MAX_GENTITIES-1)
+// or ENTITYNUM_NONE, ENTITYNUM_WORLD
+
+
+// markfragments are returned by CM_MarkFragments()
+typedef struct {
+ int firstPoint;
+ int numPoints;
+} markFragment_t;
+
+
+
+typedef struct {
+ vec3_t origin;
+ vec3_t axis[3];
+} orientation_t;
+
+//=====================================================================
+
+
+// in order from highest priority to lowest
+// if none of the catchers are active, bound key strings will be executed
+#define KEYCATCH_CONSOLE 0x0001
+#define KEYCATCH_UI 0x0002
+#define KEYCATCH_MESSAGE 0x0004
+#define KEYCATCH_CGAME 0x0008
+
+
+// sound channels
+// channel 0 never willingly overrides
+// other channels will allways override a playing sound on that channel
+typedef enum {
+ CHAN_AUTO,
+ CHAN_LOCAL, // menu sounds, etc
+ CHAN_WEAPON,
+ CHAN_VOICE,
+ CHAN_ITEM,
+ CHAN_BODY,
+ CHAN_LOCAL_SOUND, // chat messages, etc
+ CHAN_ANNOUNCER // announcer voices, etc
+} soundChannel_t;
+
+
+/*
+========================================================================
+
+ ELEMENTS COMMUNICATED ACROSS THE NET
+
+========================================================================
+*/
+
+#define ANGLE2SHORT(x) ((int)((x)*65536/360) & 65535)
+#define SHORT2ANGLE(x) ((x)*(360.0/65536))
+
+#define SNAPFLAG_RATE_DELAYED 1
+#define SNAPFLAG_NOT_ACTIVE 2 // snapshot used during connection and for zombies
+#define SNAPFLAG_SERVERCOUNT 4 // toggled every map_restart so transitions can be detected
+
+//
+// per-level limits
+//
+#define MAX_CLIENTS 64 // absolute limit
+#define MAX_LOCATIONS 64
+
+#define GENTITYNUM_BITS 10 // don't need to send any more
+#define MAX_GENTITIES (1<<GENTITYNUM_BITS)
+
+// entitynums are communicated with GENTITY_BITS, so any reserved
+// values that are going to be communcated over the net need to
+// also be in this range
+#define ENTITYNUM_NONE (MAX_GENTITIES-1)
+#define ENTITYNUM_WORLD (MAX_GENTITIES-2)
+#define ENTITYNUM_MAX_NORMAL (MAX_GENTITIES-2)
+
+
+#define MAX_MODELS 256 // these are sent over the net as 8 bits
+#define MAX_SOUNDS 256 // so they cannot be blindly increased
+#define MAX_GAME_SHADERS 64
+#define MAX_GAME_PARTICLE_SYSTEMS 64
+
+
+#define MAX_CONFIGSTRINGS 1024
+
+// these are the only configstrings that the system reserves, all the
+// other ones are strictly for servergame to clientgame communication
+#define CS_SERVERINFO 0 // an info string with all the serverinfo cvars
+#define CS_SYSTEMINFO 1 // an info string for server system to client system configuration (timescale, etc)
+
+#define RESERVED_CONFIGSTRINGS 2 // game can't modify below this, only the system can
+
+#define MAX_GAMESTATE_CHARS 16000
+typedef struct {
+ int stringOffsets[MAX_CONFIGSTRINGS];
+ char stringData[MAX_GAMESTATE_CHARS];
+ int dataCount;
+} gameState_t;
+
+//=========================================================
+
+// bit field limits
+#define MAX_STATS 16
+#define MAX_PERSISTANT 16
+#define MAX_POWERUPS 16
+#define MAX_WEAPONS 16
+
+#define MAX_PS_EVENTS 2
+
+#define PS_PMOVEFRAMECOUNTBITS 6
+
+// playerState_t is the information needed by both the client and server
+// to predict player motion and actions
+// nothing outside of pmove should modify these, or some degree of prediction error
+// will occur
+
+// you can't add anything to this without modifying the code in msg.c
+
+// playerState_t is a full superset of entityState_t as it is used by players,
+// so if a playerState_t is transmitted, the entityState_t can be fully derived
+// from it.
+typedef struct playerState_s {
+ int commandTime; // cmd->serverTime of last executed command
+ int pm_type;
+ int bobCycle; // for view bobbing and footstep generation
+ int pm_flags; // ducked, jump_held, etc
+ int pm_time;
+
+ vec3_t origin;
+ vec3_t velocity;
+ int weaponTime;
+ int gravity;
+ int speed;
+ int delta_angles[3]; // add to command angles to get view direction
+ // changed by spawns, rotating objects, and teleporters
+
+ int groundEntityNum;// ENTITYNUM_NONE = in air
+
+ int legsTimer; // don't change low priority animations until this runs out
+ int legsAnim; // mask off ANIM_TOGGLEBIT
+
+ int torsoTimer; // don't change low priority animations until this runs out
+ int torsoAnim; // mask off ANIM_TOGGLEBIT
+
+ int movementDir; // a number 0 to 7 that represents the reletive angle
+ // of movement to the view angle (axial and diagonals)
+ // when at rest, the value will remain unchanged
+ // used to twist the legs during strafing
+
+ vec3_t grapplePoint; // location of grapple to pull towards if PMF_GRAPPLE_PULL
+
+ int eFlags; // copied to entityState_t->eFlags
+
+ int eventSequence; // pmove generated events
+ int events[MAX_PS_EVENTS];
+ int eventParms[MAX_PS_EVENTS];
+
+ int externalEvent; // events set on player from another source
+ int externalEventParm;
+ int externalEventTime;
+
+ int clientNum; // ranges from 0 to MAX_CLIENTS-1
+ int weapon; // copied to entityState_t->weapon
+ int weaponstate;
+
+ vec3_t viewangles; // for fixed views
+ int viewheight;
+
+ // damage feedback
+ int damageEvent; // when it changes, latch the other parms
+ int damageYaw;
+ int damagePitch;
+ int damageCount;
+
+ int stats[MAX_STATS];
+ int persistant[MAX_PERSISTANT]; // stats that aren't cleared on death
+ int powerups[MAX_POWERUPS]; // level.time that the powerup runs out
+ int ammo[MAX_WEAPONS];
+
+ int generic1;
+ int loopSound;
+ int otherEntityNum;
+
+ // not communicated over the net at all
+ int ping; // server to game info for scoreboard
+ int pmove_framecount; // FIXME: don't transmit over the network
+ int jumppad_frame;
+ int entityEventSequence;
+} playerState_t;
+
+
+//====================================================================
+
+
+//
+// usercmd_t->button bits, many of which are generated by the client system,
+// so they aren't game/cgame only definitions
+//
+#define BUTTON_ATTACK 1
+#define BUTTON_TALK 2 // displays talk balloon and disables actions
+#define BUTTON_USE_HOLDABLE 4
+#define BUTTON_GESTURE 8
+#define BUTTON_WALKING 16 // walking can't just be infered from MOVE_RUN
+ // because a key pressed late in the frame will
+ // only generate a small move value for that frame
+ // walking will use different animations and
+ // won't generate footsteps
+#define BUTTON_ATTACK2 32
+#define BUTTON_NEGATIVE 64
+
+#define BUTTON_GETFLAG 128
+#define BUTTON_GUARDBASE 256
+#define BUTTON_PATROL 512
+#define BUTTON_FOLLOWME 1024
+
+#define BUTTON_ANY 2048 // any key whatsoever
+
+#define MOVE_RUN 120 // if forwardmove or rightmove are >= MOVE_RUN,
+ // then BUTTON_WALKING should be set
+
+// usercmd_t is sent to the server each client frame
+typedef struct usercmd_s {
+ int serverTime;
+ int angles[3];
+ int buttons;
+ byte weapon; // weapon
+ signed char forwardmove, rightmove, upmove;
+} usercmd_t;
+
+//===================================================================
+
+// if entityState->solid == SOLID_BMODEL, modelindex is an inline model number
+#define SOLID_BMODEL 0xffffff
+
+typedef enum {
+ TR_STATIONARY,
+ TR_INTERPOLATE, // non-parametric, but interpolate between snapshots
+ TR_LINEAR,
+ TR_LINEAR_STOP,
+ TR_SINE, // value = base + sin( time / duration ) * delta
+ TR_GRAVITY,
+ TR_BUOYANCY //TA: what the hell is this doing in here anyway?
+} trType_t;
+
+typedef struct {
+ trType_t trType;
+ int trTime;
+ int trDuration; // if non 0, trTime + trDuration = stop time
+ vec3_t trBase;
+ vec3_t trDelta; // velocity, etc
+} trajectory_t;
+
+// entityState_t is the information conveyed from the server
+// in an update message about entities that the client will
+// need to render in some way
+// Different eTypes may use the information in different ways
+// The messages are delta compressed, so it doesn't really matter if
+// the structure size is fairly large
+
+typedef struct entityState_s {
+ int number; // entity index
+ int eType; // entityType_t
+ int eFlags;
+
+ trajectory_t pos; // for calculating position
+ trajectory_t apos; // for calculating angles
+
+ int time;
+ int time2;
+
+ vec3_t origin;
+ vec3_t origin2;
+
+ vec3_t angles;
+ vec3_t angles2;
+
+ int otherEntityNum; // shotgun sources, etc
+ int otherEntityNum2;
+
+ int groundEntityNum; // -1 = in air
+
+ int constantLight; // r + (g<<8) + (b<<16) + (intensity<<24)
+ int loopSound; // constantly loop this sound
+
+ int modelindex;
+ int modelindex2;
+ int clientNum; // 0 to (MAX_CLIENTS - 1), for players and corpses
+ int frame;
+
+ int solid; // for client side prediction, trap_linkentity sets this properly
+
+ int event; // impulse events -- muzzle flashes, footsteps, etc
+ int eventParm;
+
+ // for players
+ int powerups; // bit flags
+ int weapon; // determines weapon and flash model, etc
+ int legsAnim; // mask off ANIM_TOGGLEBIT
+ int torsoAnim; // mask off ANIM_TOGGLEBIT
+
+ int generic1;
+} entityState_t;
+
+typedef enum {
+ CA_UNINITIALIZED,
+ CA_DISCONNECTED, // not talking to a server
+ CA_AUTHORIZING, // not used any more, was checking cd key
+ CA_CONNECTING, // sending request packets to the server
+ CA_CHALLENGING, // sending challenge packets to the server
+ CA_CONNECTED, // netchan_t established, getting gamestate
+ CA_LOADING, // only during cgame initialization, never during main loop
+ CA_PRIMED, // got gamestate, waiting for first frame
+ CA_ACTIVE, // game views should be displayed
+ CA_CINEMATIC // playing a cinematic or a static pic, not connected to a server
+} connstate_t;
+
+// font support
+
+#define GLYPH_START 0
+#define GLYPH_END 255
+#define GLYPH_CHARSTART 32
+#define GLYPH_CHAREND 127
+#define GLYPHS_PER_FONT GLYPH_END - GLYPH_START + 1
+typedef struct {
+ int height; // number of scan lines
+ int top; // top of glyph in buffer
+ int bottom; // bottom of glyph in buffer
+ int pitch; // width for copying
+ int xSkip; // x adjustment
+ int imageWidth; // width of actual image
+ int imageHeight; // height of actual image
+ float s; // x offset in image where glyph starts
+ float t; // y offset in image where glyph starts
+ float s2;
+ float t2;
+ qhandle_t glyph; // handle to the shader with the glyph
+ char shaderName[32];
+} glyphInfo_t;
+
+typedef struct {
+ glyphInfo_t glyphs [GLYPHS_PER_FONT];
+ float glyphScale;
+ char name[MAX_QPATH];
+} fontInfo_t;
+
+#define Square(x) ((x)*(x))
+
+// real time
+//=============================================
+
+
+typedef struct qtime_s {
+ int tm_sec; /* seconds after the minute - [0,59] */
+ int tm_min; /* minutes after the hour - [0,59] */
+ int tm_hour; /* hours since midnight - [0,23] */
+ int tm_mday; /* day of the month - [1,31] */
+ int tm_mon; /* months since January - [0,11] */
+ int tm_year; /* years since 1900 */
+ int tm_wday; /* days since Sunday - [0,6] */
+ int tm_yday; /* days since January 1 - [0,365] */
+ int tm_isdst; /* daylight savings time flag */
+} qtime_t;
+
+
+// server browser sources
+// TTimo: AS_MPLAYER is no longer used
+#define AS_GLOBAL 2
+#define AS_MPLAYER 1
+#define AS_LOCAL 0
+#define AS_FAVORITES 3
+
+
+// cinematic states
+typedef enum {
+ FMV_IDLE,
+ FMV_PLAY, // play
+ FMV_EOF, // all other conditions, i.e. stop/EOF/abort
+ FMV_ID_BLT,
+ FMV_ID_IDLE,
+ FMV_LOOPED,
+ FMV_ID_WAIT
+} e_status;
+
+typedef enum _flag_status {
+ FLAG_ATBASE = 0,
+ FLAG_TAKEN, // CTF
+ FLAG_TAKEN_RED, // One Flag CTF
+ FLAG_TAKEN_BLUE, // One Flag CTF
+ FLAG_DROPPED
+} flagStatus_t;
+
+typedef enum {
+ DS_NONE,
+
+ DS_PLAYBACK,
+ DS_RECORDING,
+
+ DS_NUM_DEMO_STATES
+} demoState_t;
+
+
+#define MAX_GLOBAL_SERVERS 4096
+#define MAX_OTHER_SERVERS 128
+#define MAX_PINGREQUESTS 32
+#define MAX_SERVERSTATUSREQUESTS 16
+
+#define SAY_ALL 0
+#define SAY_TEAM 1
+#define SAY_TELL 2
+#define SAY_ACTION 3
+#define SAY_ACTION_T 4
+#define SAY_ADMINS 5
+
+#endif // __Q_SHARED_H
+
+
+
+
+
diff --git a/src/qcommon/qcommon.h b/src/qcommon/qcommon.h
new file mode 100644
index 0000000..9dcb0ba
--- /dev/null
+++ b/src/qcommon/qcommon.h
@@ -0,0 +1,1106 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// qcommon.h -- definitions common between client and server, but not game.or ref modules
+#ifndef _QCOMMON_H_
+#define _QCOMMON_H_
+
+#include "../qcommon/cm_public.h"
+
+//Ignore __attribute__ on non-gcc platforms
+#ifndef __GNUC__
+#ifndef __attribute__
+#define __attribute__(x)
+#endif
+#endif
+
+//============================================================================
+
+//
+// msg.c
+//
+typedef struct {
+ qboolean allowoverflow; // if false, do a Com_Error
+ qboolean overflowed; // set to true if the buffer size failed (with allowoverflow set)
+ qboolean oob; // set to true if the buffer size failed (with allowoverflow set)
+ byte *data;
+ int maxsize;
+ int cursize;
+ int readcount;
+ int bit; // for bitwise reads and writes
+} msg_t;
+
+void MSG_Init (msg_t *buf, byte *data, int length);
+void MSG_InitOOB( msg_t *buf, byte *data, int length );
+void MSG_Clear (msg_t *buf);
+void MSG_WriteData (msg_t *buf, const void *data, int length);
+void MSG_Bitstream( msg_t *buf );
+
+// TTimo
+// copy a msg_t in case we need to store it as is for a bit
+// (as I needed this to keep an msg_t from a static var for later use)
+// sets data buffer as MSG_Init does prior to do the copy
+void MSG_Copy(msg_t *buf, byte *data, int length, msg_t *src);
+
+struct usercmd_s;
+struct entityState_s;
+struct playerState_s;
+
+void MSG_WriteBits( msg_t *msg, int value, int bits );
+
+void MSG_WriteChar (msg_t *sb, int c);
+void MSG_WriteByte (msg_t *sb, int c);
+void MSG_WriteShort (msg_t *sb, int c);
+void MSG_WriteLong (msg_t *sb, int c);
+void MSG_WriteFloat (msg_t *sb, float f);
+void MSG_WriteString (msg_t *sb, const char *s);
+void MSG_WriteBigString (msg_t *sb, const char *s);
+void MSG_WriteAngle16 (msg_t *sb, float f);
+
+void MSG_BeginReading (msg_t *sb);
+void MSG_BeginReadingOOB(msg_t *sb);
+
+int MSG_ReadBits( msg_t *msg, int bits );
+
+int MSG_ReadChar (msg_t *sb);
+int MSG_ReadByte (msg_t *sb);
+int MSG_ReadShort (msg_t *sb);
+int MSG_ReadLong (msg_t *sb);
+float MSG_ReadFloat (msg_t *sb);
+char *MSG_ReadString (msg_t *sb);
+char *MSG_ReadBigString (msg_t *sb);
+char *MSG_ReadStringLine (msg_t *sb);
+float MSG_ReadAngle16 (msg_t *sb);
+void MSG_ReadData (msg_t *sb, void *buffer, int size);
+
+
+void MSG_WriteDeltaUsercmd( msg_t *msg, struct usercmd_s *from, struct usercmd_s *to );
+void MSG_ReadDeltaUsercmd( msg_t *msg, struct usercmd_s *from, struct usercmd_s *to );
+
+void MSG_WriteDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *to );
+void MSG_ReadDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *to );
+
+void MSG_WriteDeltaEntity( msg_t *msg, struct entityState_s *from, struct entityState_s *to
+ , qboolean force );
+void MSG_ReadDeltaEntity( msg_t *msg, entityState_t *from, entityState_t *to,
+ int number );
+
+void MSG_WriteDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to );
+void MSG_ReadDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to );
+
+
+void MSG_ReportChangeVectors_f( void );
+
+//============================================================================
+
+/*
+==============================================================
+
+NET
+
+==============================================================
+*/
+
+#define PACKET_BACKUP 32 // number of old messages that must be kept on client and
+ // server for delta comrpession and ping estimation
+#define PACKET_MASK (PACKET_BACKUP-1)
+
+#define MAX_PACKET_USERCMDS 32 // max number of usercmd_t in a packet
+
+#define PORT_ANY -1
+
+#define MAX_RELIABLE_COMMANDS 128 // max string commands buffered for restransmit
+
+typedef enum {
+ NA_BAD, // an address lookup failed
+ NA_LOOPBACK,
+ NA_BROADCAST,
+ NA_IP,
+ NA_IPX,
+ NA_BROADCAST_IPX
+} netadrtype_t;
+
+typedef enum {
+ NS_CLIENT,
+ NS_SERVER
+} netsrc_t;
+
+typedef struct {
+ netadrtype_t type;
+
+ byte ip[4];
+ byte ipx[10];
+
+ unsigned short port;
+} netadr_t;
+
+void NET_Init( void );
+void NET_Shutdown( void );
+void NET_Restart( void );
+void NET_Config( qboolean enableNetworking );
+void NET_FlushPacketQueue(void);
+void NET_SendPacket (netsrc_t sock, int length, const void *data, netadr_t to);
+void QDECL NET_OutOfBandPrint( netsrc_t net_socket, netadr_t adr, const char *format, ...) __attribute__ ((format (printf, 3, 4)));
+void QDECL NET_OutOfBandData( netsrc_t sock, netadr_t adr, byte *format, int len );
+
+qboolean NET_CompareAdr (netadr_t a, netadr_t b);
+qboolean NET_CompareBaseAdr (netadr_t a, netadr_t b);
+qboolean NET_IsLocalAddress (netadr_t adr);
+const char *NET_AdrToString (netadr_t a);
+qboolean NET_StringToAdr ( const char *s, netadr_t *a);
+qboolean NET_GetLoopPacket (netsrc_t sock, netadr_t *net_from, msg_t *net_message);
+void NET_Sleep(int msec);
+
+
+#define MAX_MSGLEN 16384 // max length of a message, which may
+ // be fragmented into multiple packets
+
+#define MAX_DOWNLOAD_WINDOW 8 // max of eight download frames
+#define MAX_DOWNLOAD_BLKSIZE 2048 // 2048 byte block chunks
+
+
+/*
+Netchan handles packet fragmentation and out of order / duplicate suppression
+*/
+
+typedef struct {
+ netsrc_t sock;
+
+ int dropped; // between last packet and previous
+
+ netadr_t remoteAddress;
+ int qport; // qport value to write when transmitting
+
+ // sequencing variables
+ int incomingSequence;
+ int outgoingSequence;
+
+ // incoming fragment assembly buffer
+ int fragmentSequence;
+ int fragmentLength;
+ byte fragmentBuffer[MAX_MSGLEN];
+
+ // outgoing fragment buffer
+ // we need to space out the sending of large fragmented messages
+ qboolean unsentFragments;
+ int unsentFragmentStart;
+ int unsentLength;
+ byte unsentBuffer[MAX_MSGLEN];
+} netchan_t;
+
+void Netchan_Init( int qport );
+void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport );
+
+void Netchan_Transmit( netchan_t *chan, int length, const byte *data );
+void Netchan_TransmitNextFragment( netchan_t *chan );
+
+qboolean Netchan_Process( netchan_t *chan, msg_t *msg );
+
+
+/*
+==============================================================
+
+PROTOCOL
+
+==============================================================
+*/
+
+#define PROTOCOL_VERSION 69
+
+// maintain a list of compatible protocols for demo playing
+// NOTE: that stuff only works with two digits protocols
+extern int demo_protocols[];
+
+// override on command line, config files etc.
+#ifndef MASTER_SERVER_NAME
+#define MASTER_SERVER_NAME "master.tremulous.net"
+#endif
+
+#define PORT_MASTER 30710
+#define PORT_SERVER 30720
+#define NUM_SERVER_PORTS 4 // broadcast scan this many ports after
+ // PORT_SERVER so a single machine can
+ // run multiple servers
+
+
+// the svc_strings[] array in cl_parse.c should mirror this
+//
+// server to client
+//
+enum svc_ops_e {
+ svc_bad,
+ svc_nop,
+ svc_gamestate,
+ svc_configstring, // [short] [string] only in gamestate messages
+ svc_baseline, // only in gamestate messages
+ svc_serverCommand, // [string] to be executed by client game module
+ svc_download, // [short] size [size bytes]
+ svc_snapshot,
+ svc_EOF
+};
+
+
+//
+// client to server
+//
+enum clc_ops_e {
+ clc_bad,
+ clc_nop,
+ clc_move, // [[usercmd_t]
+ clc_moveNoDelta, // [[usercmd_t]
+ clc_clientCommand, // [string] message
+ clc_EOF
+};
+
+/*
+==============================================================
+
+VIRTUAL MACHINE
+
+==============================================================
+*/
+
+typedef struct vm_s vm_t;
+
+typedef enum {
+ VMI_NATIVE,
+ VMI_BYTECODE,
+ VMI_COMPILED
+} vmInterpret_t;
+
+typedef enum {
+ TRAP_MEMSET = 100,
+ TRAP_MEMCPY,
+ TRAP_STRNCPY,
+ TRAP_SIN,
+ TRAP_COS,
+ TRAP_ATAN2,
+ TRAP_SQRT,
+ TRAP_MATRIXMULTIPLY,
+ TRAP_ANGLEVECTORS,
+ TRAP_PERPENDICULARVECTOR,
+ TRAP_FLOOR,
+ TRAP_CEIL,
+
+ TRAP_TESTPRINTINT,
+ TRAP_TESTPRINTFLOAT
+} sharedTraps_t;
+
+void VM_Init( void );
+vm_t *VM_Create( const char *module, intptr_t (*systemCalls)(intptr_t *),
+ vmInterpret_t interpret );
+// module should be bare: "cgame", not "cgame.dll" or "vm/cgame.qvm"
+
+void VM_Free( vm_t *vm );
+void VM_Clear(void);
+vm_t *VM_Restart( vm_t *vm );
+
+intptr_t QDECL VM_Call( vm_t *vm, int callNum, ... );
+
+void VM_Debug( int level );
+
+void *VM_ArgPtr( intptr_t intValue );
+void *VM_ExplicitArgPtr( vm_t *vm, intptr_t intValue );
+
+#define VMA(x) VM_ArgPtr(args[x])
+static ID_INLINE float _vmf(intptr_t x)
+{
+ union {
+ intptr_t l;
+ float f;
+ } t;
+ t.l = x;
+ return t.f;
+}
+#define VMF(x) _vmf(args[x])
+
+
+/*
+==============================================================
+
+CMD
+
+Command text buffering and command execution
+
+==============================================================
+*/
+
+/*
+
+Any number of commands can be added in a frame, from several different sources.
+Most commands come from either keybindings or console line input, but entire text
+files can be execed.
+
+*/
+
+void Cbuf_Init (void);
+// allocates an initial text buffer that will grow as needed
+
+void Cbuf_AddText( const char *text );
+// Adds command text at the end of the buffer, does NOT add a final \n
+
+void Cbuf_ExecuteText( int exec_when, const char *text );
+// this can be used in place of either Cbuf_AddText or Cbuf_InsertText
+
+void Cbuf_Execute (void);
+// Pulls off \n terminated lines of text from the command buffer and sends
+// them through Cmd_ExecuteString. Stops when the buffer is empty.
+// Normally called once per frame, but may be explicitly invoked.
+// Do not call inside a command function, or current args will be destroyed.
+
+//===========================================================================
+
+/*
+
+Command execution takes a null terminated string, breaks it into tokens,
+then searches for a command or variable that matches the first token.
+
+*/
+
+typedef void (*xcommand_t) (void);
+
+void Cmd_Init (void);
+
+void Cmd_AddCommand( const char *cmd_name, xcommand_t function );
+// called by the init functions of other parts of the program to
+// register commands and functions to call for them.
+// The cmd_name is referenced later, so it should not be in temp memory
+// if function is NULL, the command will be forwarded to the server
+// as a clc_clientCommand instead of executed locally
+
+void Cmd_RemoveCommand( const char *cmd_name );
+
+void Cmd_CommandCompletion( void(*callback)(const char *s) );
+// callback with each valid string
+
+int Cmd_Argc (void);
+char *Cmd_Argv (int arg);
+void Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength );
+char *Cmd_Args (void);
+char *Cmd_ArgsFrom( int arg );
+void Cmd_ArgsBuffer( char *buffer, int bufferLength );
+void Cmd_LiteralArgsBuffer( char *buffer, int bufferLength );
+char *Cmd_Cmd (void);
+// The functions that execute commands get their parameters with these
+// functions. Cmd_Argv () will return an empty string, not a NULL
+// if arg > argc, so string operations are allways safe.
+
+void Cmd_TokenizeString( const char *text );
+void Cmd_TokenizeStringIgnoreQuotes( const char *text_in );
+// Takes a null terminated string. Does not need to be /n terminated.
+// breaks the string up into arg tokens.
+
+void Cmd_ExecuteString( const char *text );
+// Parses a single line of text into arguments and tries to execute it
+// as if it was typed at the console
+
+void Cmd_SaveCmdContext( void );
+void Cmd_RestoreCmdContext( void );
+
+/*
+==============================================================
+
+CVAR
+
+==============================================================
+*/
+
+/*
+
+cvar_t variables are used to hold scalar or string variables that can be changed
+or displayed at the console or prog code as well as accessed directly
+in C code.
+
+The user can access cvars from the console in three ways:
+r_draworder prints the current value
+r_draworder 0 sets the current value to 0
+set r_draworder 0 as above, but creates the cvar if not present
+
+Cvars are restricted from having the same names as commands to keep this
+interface from being ambiguous.
+
+The are also occasionally used to communicated information between different
+modules of the program.
+
+*/
+
+cvar_t *Cvar_Get( const char *var_name, const char *value, int flags );
+// creates the variable if it doesn't exist, or returns the existing one
+// if it exists, the value will not be changed, but flags will be ORed in
+// that allows variables to be unarchived without needing bitflags
+// if value is "", the value will not override a previously set value.
+
+void Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags );
+// basically a slightly modified Cvar_Get for the interpreted modules
+
+void Cvar_Update( vmCvar_t *vmCvar );
+// updates an interpreted modules' version of a cvar
+
+void Cvar_Set( const char *var_name, const char *value );
+// will create the variable with no flags if it doesn't exist
+
+void Cvar_SetLatched( const char *var_name, const char *value);
+// don't set the cvar immediately
+
+void Cvar_SetValue( const char *var_name, float value );
+// expands value to a string and calls Cvar_Set
+
+float Cvar_VariableValue( const char *var_name );
+int Cvar_VariableIntegerValue( const char *var_name );
+// returns 0 if not defined or non numeric
+
+char *Cvar_VariableString( const char *var_name );
+void Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize );
+// returns an empty string if not defined
+
+int Cvar_Flags(const char *var_name);
+// returns CVAR_NONEXISTENT if cvar doesn't exist or the flags of that particular CVAR.
+
+void Cvar_CommandCompletion( void(*callback)(const char *s) );
+// callback with each valid string
+
+void Cvar_Reset( const char *var_name );
+void Cvar_ForceReset(const char *var_name);
+
+void Cvar_SetCheatState( void );
+// reset all testing vars to a safe value
+
+qboolean Cvar_Command( void );
+// called by Cmd_ExecuteString when Cmd_Argv(0) doesn't match a known
+// command. Returns true if the command was a variable reference that
+// was handled. (print or change)
+
+void Cvar_WriteVariables( fileHandle_t f );
+// writes lines containing "set variable value" for all variables
+// with the archive flag set to true.
+
+void Cvar_Init( void );
+
+char *Cvar_InfoString( int bit );
+char *Cvar_InfoString_Big( int bit );
+// returns an info string containing all the cvars that have the given bit set
+// in their flags ( CVAR_USERINFO, CVAR_SERVERINFO, CVAR_SYSTEMINFO, etc )
+void Cvar_InfoStringBuffer( int bit, char *buff, int buffsize );
+
+void Cvar_Restart_f( void );
+
+extern int cvar_modifiedFlags;
+// whenever a cvar is modifed, its flags will be OR'd into this, so
+// a single check can determine if any CVAR_USERINFO, CVAR_SERVERINFO,
+// etc, variables have been modified since the last check. The bit
+// can then be cleared to allow another change detection.
+
+/*
+==============================================================
+
+FILESYSTEM
+
+No stdio calls should be used by any part of the game, because
+we need to deal with all sorts of directory and seperator char
+issues.
+==============================================================
+*/
+
+// referenced flags
+// these are in loop specific order so don't change the order
+#define FS_GENERAL_REF 0x01
+#define FS_UI_REF 0x02
+#define FS_CGAME_REF 0x04
+#define FS_QAGAME_REF 0x08
+// number of id paks that will never be autodownloaded from baseq3
+#define NUM_ID_PAKS 9
+
+#define MAX_FILE_HANDLES 64
+
+#define BASEGAME "base"
+
+qboolean FS_Initialized( void );
+
+void FS_InitFilesystem ( void );
+void FS_Shutdown( qboolean closemfp );
+
+qboolean FS_ConditionalRestart( int checksumFeed );
+void FS_Restart( int checksumFeed );
+// shutdown and restart the filesystem so changes to fs_gamedir can take effect
+
+char **FS_ListFiles( const char *directory, const char *extension, int *numfiles );
+// directory should not have either a leading or trailing /
+// if extension is "/", only subdirectories will be returned
+// the returned files will not include any directories or /
+
+void FS_FreeFileList( char **list );
+
+qboolean FS_FileExists( const char *file );
+
+char *FS_BuildOSPath( const char *base, const char *game, const char *qpath );
+
+int FS_LoadStack( void );
+
+int FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize );
+int FS_GetModList( char *listbuf, int bufsize );
+
+fileHandle_t FS_FOpenFileWrite( const char *qpath );
+// will properly create any needed paths and deal with seperater character issues
+
+int FS_filelength( fileHandle_t f );
+fileHandle_t FS_SV_FOpenFileWrite( const char *filename );
+int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp );
+void FS_SV_Rename( const char *from, const char *to );
+int FS_FOpenFileRead( const char *qpath, fileHandle_t *file, qboolean uniqueFILE );
+// if uniqueFILE is true, then a new FILE will be fopened even if the file
+// is found in an already open pak file. If uniqueFILE is false, you must call
+// FS_FCloseFile instead of fclose, otherwise the pak FILE would be improperly closed
+// It is generally safe to always set uniqueFILE to true, because the majority of
+// file IO goes through FS_ReadFile, which Does The Right Thing already.
+
+int FS_FileIsInPAK(const char *filename, int *pChecksum );
+// returns 1 if a file is in the PAK file, otherwise -1
+
+int FS_Write( const void *buffer, int len, fileHandle_t f );
+
+int FS_Read2( void *buffer, int len, fileHandle_t f );
+int FS_Read( void *buffer, int len, fileHandle_t f );
+// properly handles partial reads and reads from other dlls
+
+void FS_FCloseFile( fileHandle_t f );
+// note: you can't just fclose from another DLL, due to MS libc issues
+
+int FS_ReadFile( const char *qpath, void **buffer );
+// returns the length of the file
+// a null buffer will just return the file length without loading
+// as a quick check for existance. -1 length == not present
+// A 0 byte will always be appended at the end, so string ops are safe.
+// the buffer should be considered read-only, because it may be cached
+// for other uses.
+
+void FS_ForceFlush( fileHandle_t f );
+// forces flush on files we're writing to.
+
+void FS_FreeFile( void *buffer );
+// frees the memory returned by FS_ReadFile
+
+void FS_WriteFile( const char *qpath, const void *buffer, int size );
+// writes a complete file, creating any subdirectories needed
+
+int FS_filelength( fileHandle_t f );
+// doesn't work for files that are opened from a pack file
+
+int FS_FTell( fileHandle_t f );
+// where are we?
+
+void FS_Flush( fileHandle_t f );
+
+void QDECL FS_Printf( fileHandle_t f, const char *fmt, ... ) __attribute__ ((format (printf, 2, 3)));
+// like fprintf
+
+int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode );
+// opens a file for reading, writing, or appending depending on the value of mode
+
+int FS_Seek( fileHandle_t f, long offset, int origin );
+// seek on a file (doesn't work for zip files!!!!!!!!)
+
+qboolean FS_FilenameCompare( const char *s1, const char *s2 );
+
+const char *FS_GamePureChecksum( void );
+// Returns the checksum of the pk3 from which the server loaded the qagame.qvm
+
+const char *FS_LoadedPakNames( void );
+const char *FS_LoadedPakChecksums( void );
+const char *FS_LoadedPakPureChecksums( void );
+// Returns a space separated string containing the checksums of all loaded pk3 files.
+// Servers with sv_pure set will get this string and pass it to clients.
+
+const char *FS_ReferencedPakNames( void );
+const char *FS_ReferencedPakChecksums( void );
+const char *FS_ReferencedPakPureChecksums( void );
+// Returns a space separated string containing the checksums of all loaded
+// AND referenced pk3 files. Servers with sv_pure set will get this string
+// back from clients for pure validation
+
+void FS_ClearPakReferences( int flags );
+// clears referenced booleans on loaded pk3s
+
+void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames );
+void FS_PureServerSetLoadedPaks( const char *pakSums, const char *pakNames );
+// If the string is empty, all data sources will be allowed.
+// If not empty, only pk3 files that match one of the space
+// separated checksums will be checked for files, with the
+// sole exception of .cfg files.
+
+qboolean FS_CheckDirTraversal(const char *checkdir);
+qboolean FS_idPak( char *pak, char *base );
+qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring );
+
+void FS_Rename( const char *from, const char *to );
+
+void FS_Remove( const char *osPath );
+void FS_HomeRemove( const char *homePath );
+
+void FS_FilenameCompletion( const char *dir, const char *ext,
+ qboolean stripExt, void(*callback)(const char *s) );
+/*
+==============================================================
+
+Edit fields and command line history/completion
+
+==============================================================
+*/
+
+#define MAX_EDIT_LINE 256
+typedef struct {
+ int cursor;
+ int scroll;
+ int widthInChars;
+ char buffer[MAX_EDIT_LINE];
+} field_t;
+
+void Field_Clear( field_t *edit );
+void Field_AutoComplete( field_t *edit );
+
+/*
+==============================================================
+
+MISC
+
+==============================================================
+*/
+
+// TTimo
+// vsnprintf is ISO/IEC 9899:1999
+// abstracting this to make it portable
+#ifdef WIN32
+#define Q_vsnprintf _vsnprintf
+#else
+// TODO: do we need Mac define?
+#define Q_vsnprintf vsnprintf
+#endif
+
+// returnbed by Sys_GetProcessorId
+#define CPUID_GENERIC 0 // any unrecognized processor
+
+#define CPUID_AXP 0x10
+
+#define CPUID_INTEL_UNSUPPORTED 0x20 // Intel 386/486
+#define CPUID_INTEL_PENTIUM 0x21 // Intel Pentium or PPro
+#define CPUID_INTEL_MMX 0x22 // Intel Pentium/MMX or P2/MMX
+#define CPUID_INTEL_KATMAI 0x23 // Intel Katmai
+
+#define CPUID_AMD_3DNOW 0x30 // AMD K6 3DNOW!
+
+// TTimo
+// centralized and cleaned, that's the max string you can send to a Com_Printf / Com_DPrintf (above gets truncated)
+#define MAXPRINTMSG 4096
+
+char *CopyString( const char *in );
+void Info_Print( const char *s );
+
+void Com_BeginRedirect (char *buffer, int buffersize, void (*flush)(char *));
+void Com_EndRedirect( void );
+void QDECL Com_Printf( const char *fmt, ... ) __attribute__ ((format (printf, 1, 2)));
+void QDECL Com_DPrintf( const char *fmt, ... ) __attribute__ ((format (printf, 1, 2)));
+void QDECL Com_Error( int code, const char *fmt, ... ) __attribute__ ((format (printf, 2, 3)));
+void Com_Quit_f( void );
+int Com_EventLoop( void );
+int Com_Milliseconds( void ); // will be journaled properly
+unsigned Com_BlockChecksum( const void *buffer, int length );
+char *Com_MD5File(const char *filename, int length, const char *prefix, int prefix_len);
+int Com_HashKey(char *string, int maxlen);
+int Com_Filter(char *filter, char *name, int casesensitive);
+int Com_FilterPath(char *filter, char *name, int casesensitive);
+int Com_RealTime(qtime_t *qtime);
+qboolean Com_SafeMode( void );
+
+void Com_StartupVariable( const char *match );
+// checks for and removes command line "+set var arg" constructs
+// if match is NULL, all set commands will be executed, otherwise
+// only a set with the exact name. Only used during startup.
+
+
+extern cvar_t *com_developer;
+extern cvar_t *com_dedicated;
+extern cvar_t *com_speeds;
+extern cvar_t *com_timescale;
+extern cvar_t *com_sv_running;
+extern cvar_t *com_cl_running;
+extern cvar_t *com_viewlog; // 0 = hidden, 1 = visible, 2 = minimized
+extern cvar_t *com_version;
+extern cvar_t *com_blood;
+extern cvar_t *com_buildScript; // for building release pak files
+extern cvar_t *com_journal;
+extern cvar_t *com_cameraMode;
+extern cvar_t *com_altivec;
+
+// both client and server must agree to pause
+extern cvar_t *cl_paused;
+extern cvar_t *sv_paused;
+
+extern cvar_t *cl_packetdelay;
+extern cvar_t *sv_packetdelay;
+
+// com_speeds times
+extern int time_game;
+extern int time_frontend;
+extern int time_backend; // renderer backend time
+
+extern int com_frameTime;
+extern int com_frameMsec;
+
+extern qboolean com_errorEntered;
+
+extern fileHandle_t com_journalFile;
+extern fileHandle_t com_journalDataFile;
+
+typedef enum {
+ TAG_FREE,
+ TAG_GENERAL,
+ TAG_BOTLIB,
+ TAG_RENDERER,
+ TAG_SMALL,
+ TAG_STATIC
+} memtag_t;
+
+/*
+
+--- low memory ----
+server vm
+server clipmap
+---mark---
+renderer initialization (shaders, etc)
+UI vm
+cgame vm
+renderer map
+renderer models
+
+---free---
+
+temp file loading
+--- high memory ---
+
+*/
+
+#if defined(_DEBUG) && !defined(BSPC)
+ #define ZONE_DEBUG
+#endif
+
+#ifdef ZONE_DEBUG
+#define Z_TagMalloc(size, tag) Z_TagMallocDebug(size, tag, #size, __FILE__, __LINE__)
+#define Z_Malloc(size) Z_MallocDebug(size, #size, __FILE__, __LINE__)
+#define S_Malloc(size) S_MallocDebug(size, #size, __FILE__, __LINE__)
+void *Z_TagMallocDebug( int size, int tag, char *label, char *file, int line ); // NOT 0 filled memory
+void *Z_MallocDebug( int size, char *label, char *file, int line ); // returns 0 filled memory
+void *S_MallocDebug( int size, char *label, char *file, int line ); // returns 0 filled memory
+#else
+void *Z_TagMalloc( int size, int tag ); // NOT 0 filled memory
+void *Z_Malloc( int size ); // returns 0 filled memory
+void *S_Malloc( int size ); // NOT 0 filled memory only for small allocations
+#endif
+void Z_Free( void *ptr );
+void Z_FreeTags( int tag );
+int Z_AvailableMemory( void );
+void Z_LogHeap( void );
+
+void Hunk_Clear( void );
+void Hunk_ClearToMark( void );
+void Hunk_SetMark( void );
+qboolean Hunk_CheckMark( void );
+void Hunk_ClearTempMemory( void );
+void *Hunk_AllocateTempMemory( int size );
+void Hunk_FreeTempMemory( void *buf );
+int Hunk_MemoryRemaining( void );
+void Hunk_Log( void);
+void Hunk_Trash( void );
+
+void Com_TouchMemory( void );
+
+// commandLine should not include the executable name (argv[0])
+void Com_Init( char *commandLine );
+void Com_Frame( void );
+void Com_Shutdown( void );
+
+
+/*
+==============================================================
+
+CLIENT / SERVER SYSTEMS
+
+==============================================================
+*/
+
+//
+// client interface
+//
+void CL_InitKeyCommands( void );
+// the keyboard binding interface must be setup before execing
+// config files, but the rest of client startup will happen later
+
+void CL_Init( void );
+void CL_Disconnect( qboolean showMainMenu );
+void CL_Shutdown( void );
+void CL_Frame( int msec );
+qboolean CL_GameCommand( void );
+void CL_KeyEvent (int key, qboolean down, unsigned time);
+
+void CL_CharEvent( int key );
+// char events are for field typing, not game control
+
+void CL_MouseEvent( int dx, int dy, int time );
+
+void CL_JoystickEvent( int axis, int value, int time );
+
+void CL_PacketEvent( netadr_t from, msg_t *msg );
+
+void CL_ConsolePrint( char *text );
+
+void CL_MapLoading( void );
+// do a screen update before starting to load a map
+// when the server is going to load a new map, the entire hunk
+// will be cleared, so the client must shutdown cgame, ui, and
+// the renderer
+
+void CL_ForwardCommandToServer( const char *string );
+// adds the current command line as a clc_clientCommand to the client message.
+// things like godmode, noclip, etc, are commands directed to the server,
+// so when they are typed in at the console, they will need to be forwarded.
+
+void CL_CDDialog( void );
+// bring up the "need a cd to play" dialog
+
+void CL_ShutdownAll( void );
+// shutdown all the client stuff
+
+void CL_FlushMemory( void );
+// dump all memory on an error
+
+void CL_StartHunkUsers( void );
+// start all the client stuff using the hunk
+
+void Key_WriteBindings( fileHandle_t f );
+// for writing the config files
+
+void S_ClearSoundBuffer( void );
+// call before filesystem access
+
+void SCR_DebugGraph (float value, int color); // FIXME: move logging to common?
+
+//
+// server interface
+//
+void SV_Init( void );
+void SV_Shutdown( char *finalmsg );
+void SV_Frame( int msec );
+void SV_PacketEvent( netadr_t from, msg_t *msg );
+qboolean SV_GameCommand( void );
+
+
+//
+// UI interface
+//
+qboolean UI_GameCommand( void );
+
+/*
+==============================================================
+
+NON-PORTABLE SYSTEM SERVICES
+
+==============================================================
+*/
+
+typedef enum {
+ AXIS_SIDE,
+ AXIS_FORWARD,
+ AXIS_UP,
+ AXIS_ROLL,
+ AXIS_YAW,
+ AXIS_PITCH,
+ MAX_JOYSTICK_AXIS
+} joystickAxis_t;
+
+typedef enum {
+ // bk001129 - make sure SE_NONE is zero
+ SE_NONE = 0, // evTime is still valid
+ SE_KEY, // evValue is a key code, evValue2 is the down flag
+ SE_CHAR, // evValue is an ascii char
+ SE_MOUSE, // evValue and evValue2 are reletive signed x / y moves
+ SE_JOYSTICK_AXIS, // evValue is an axis number and evValue2 is the current state (-127 to 127)
+ SE_CONSOLE, // evPtr is a char*
+ SE_PACKET // evPtr is a netadr_t followed by data bytes to evPtrLength
+} sysEventType_t;
+
+typedef struct {
+ int evTime;
+ sysEventType_t evType;
+ int evValue, evValue2;
+ int evPtrLength; // bytes of data pointed to by evPtr, for journaling
+ void *evPtr; // this must be manually freed if not NULL
+} sysEvent_t;
+
+sysEvent_t Sys_GetEvent( void );
+
+void Sys_Init (void);
+
+// general development dll loading for virtual machine testing
+// fqpath param added 7/20/02 by T.Ray - Sys_LoadDll is only called in vm.c at this time
+void * QDECL Sys_LoadDll( const char *name, char *fqpath , intptr_t (QDECL **entryPoint)(int, ...),
+ intptr_t (QDECL *systemcalls)(intptr_t, ...) );
+void Sys_UnloadDll( void *dllHandle );
+
+void Sys_UnloadGame( void );
+void *Sys_GetGameAPI( void *parms );
+
+void Sys_UnloadCGame( void );
+void *Sys_GetCGameAPI( void );
+
+void Sys_UnloadUI( void );
+void *Sys_GetUIAPI( void );
+
+//bot libraries
+void Sys_UnloadBotLib( void );
+void *Sys_GetBotLibAPI( void *parms );
+
+char *Sys_GetCurrentUser( void );
+
+void QDECL Sys_Error( const char *error, ...) __attribute__ ((format (printf, 1, 2)));
+void Sys_Quit (void);
+char *Sys_GetClipboardData( void ); // note that this isn't journaled...
+
+void Sys_Print( const char *msg );
+
+// Sys_Milliseconds should only be used for profiling purposes,
+// any game related timing information should come from event timestamps
+int Sys_Milliseconds (void);
+
+void Sys_SnapVector( float *v );
+
+qboolean Sys_RandomBytes( byte *string, int len );
+
+// the system console is shown when a dedicated server is running
+void Sys_DisplaySystemConsole( qboolean show );
+
+int Sys_GetProcessorId( void );
+
+void Sys_BeginStreamedFile( fileHandle_t f, int readahead );
+void Sys_EndStreamedFile( fileHandle_t f );
+int Sys_StreamedRead( void *buffer, int size, int count, fileHandle_t f );
+void Sys_StreamSeek( fileHandle_t f, int offset, int origin );
+
+void Sys_ShowConsole( int level, qboolean quitOnClose );
+void Sys_SetErrorText( const char *text );
+
+void Sys_SendPacket( int length, const void *data, netadr_t to );
+
+qboolean Sys_StringToAdr( const char *s, netadr_t *a );
+//Does NOT parse port numbers, only base addresses.
+
+qboolean Sys_IsLANAddress (netadr_t adr);
+void Sys_ShowIP(void);
+
+qboolean Sys_CheckCD( void );
+
+void Sys_Mkdir( const char *path );
+char *Sys_Cwd( void );
+void Sys_SetDefaultCDPath(const char *path);
+char *Sys_DefaultCDPath(void);
+void Sys_SetDefaultInstallPath(const char *path);
+char *Sys_DefaultInstallPath(void);
+void Sys_SetDefaultHomePath(const char *path);
+char *Sys_DefaultHomePath(void);
+
+char **Sys_ListFiles( const char *directory, const char *extension, char *filter, int *numfiles, qboolean wantsubs );
+void Sys_FreeFileList( char **list );
+
+void Sys_BeginProfiling( void );
+void Sys_EndProfiling( void );
+
+qboolean Sys_LowPhysicalMemory( void );
+unsigned int Sys_ProcessorCount( void );
+
+int Sys_MonkeyShouldBeSpanked( void );
+
+qboolean Sys_DetectAltivec( void );
+
+/* This is based on the Adaptive Huffman algorithm described in Sayood's Data
+ * Compression book. The ranks are not actually stored, but implicitly defined
+ * by the location of a node within a doubly-linked list */
+
+#define NYT HMAX /* NYT = Not Yet Transmitted */
+#define INTERNAL_NODE (HMAX+1)
+
+typedef struct nodetype {
+ struct nodetype *left, *right, *parent; /* tree structure */
+ struct nodetype *next, *prev; /* doubly-linked list */
+ struct nodetype **head; /* highest ranked node in block */
+ int weight;
+ int symbol;
+} node_t;
+
+#define HMAX 256 /* Maximum symbol */
+
+typedef struct {
+ int blocNode;
+ int blocPtrs;
+
+ node_t* tree;
+ node_t* lhead;
+ node_t* ltail;
+ node_t* loc[HMAX+1];
+ node_t** freelist;
+
+ node_t nodeList[768];
+ node_t* nodePtrs[768];
+} huff_t;
+
+typedef struct {
+ huff_t compressor;
+ huff_t decompressor;
+} huffman_t;
+
+void Huff_Compress(msg_t *buf, int offset);
+void Huff_Decompress(msg_t *buf, int offset);
+void Huff_Init(huffman_t *huff);
+void Huff_addRef(huff_t* huff, byte ch);
+int Huff_Receive (node_t *node, int *ch, byte *fin);
+void Huff_transmit (huff_t *huff, int ch, byte *fout);
+void Huff_offsetReceive (node_t *node, int *ch, byte *fin, int *offset);
+void Huff_offsetTransmit (huff_t *huff, int ch, byte *fout, int *offset);
+void Huff_putBit( int bit, byte *fout, int *offset);
+int Huff_getBit( byte *fout, int *offset);
+
+int Parse_AddGlobalDefine(char *string);
+int Parse_LoadSourceHandle(const char *filename);
+int Parse_FreeSourceHandle(int handle);
+int Parse_ReadTokenHandle(int handle, pc_token_t *pc_token);
+int Parse_SourceFileAndLine(int handle, char *filename, int *line);
+
+extern huffman_t clientHuffTables;
+
+#define SV_ENCODE_START 4
+#define SV_DECODE_START 12
+#define CL_ENCODE_START 12
+#define CL_DECODE_START 4
+
+// flags for sv_allowDownload and cl_allowDownload
+#define DLF_ENABLE 1
+#define DLF_NO_REDIRECT 2
+#define DLF_NO_UDP 4
+#define DLF_NO_DISCONNECT 8
+
+#endif // _QCOMMON_H_
diff --git a/src/qcommon/qfiles.h b/src/qcommon/qfiles.h
new file mode 100644
index 0000000..7e901b7
--- /dev/null
+++ b/src/qcommon/qfiles.h
@@ -0,0 +1,626 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+#ifndef __QFILES_H__
+#define __QFILES_H__
+
+//
+// qfiles.h: quake file formats
+// This file must be identical in the quake and utils directories
+//
+
+//Ignore __attribute__ on non-gcc platforms
+#ifndef __GNUC__
+#ifndef __attribute__
+#define __attribute__(x)
+#endif
+#endif
+
+// surface geometry should not exceed these limits
+#define SHADER_MAX_VERTEXES 1000
+#define SHADER_MAX_INDEXES (6*SHADER_MAX_VERTEXES)
+
+
+// the maximum size of game relative pathnames
+#define MAX_QPATH 64
+
+/*
+========================================================================
+
+QVM files
+
+========================================================================
+*/
+
+#define VM_MAGIC 0x12721444
+#define VM_MAGIC_VER2 0x12721445
+typedef struct {
+ int vmMagic;
+
+ int instructionCount;
+
+ int codeOffset;
+ int codeLength;
+
+ int dataOffset;
+ int dataLength;
+ int litLength; // ( dataLength - litLength ) should be byteswapped on load
+ int bssLength; // zero filled memory appended to datalength
+
+ //!!! below here is VM_MAGIC_VER2 !!!
+ int jtrgLength; // number of jump table targets
+} vmHeader_t;
+
+
+/*
+========================================================================
+
+PCX files are used for 8 bit images
+
+========================================================================
+*/
+
+typedef struct {
+ char manufacturer;
+ char version;
+ char encoding;
+ char bits_per_pixel;
+ unsigned short xmin,ymin,xmax,ymax;
+ unsigned short hres,vres;
+ unsigned char palette[48];
+ char reserved;
+ char color_planes;
+ unsigned short bytes_per_line;
+ unsigned short palette_type;
+ char filler[58];
+ unsigned char data; // unbounded
+} pcx_t;
+
+
+/*
+========================================================================
+
+TGA files are used for 24/32 bit images
+
+========================================================================
+*/
+
+typedef struct _TargaHeader {
+ unsigned char id_length, colormap_type, image_type;
+ unsigned short colormap_index, colormap_length;
+ unsigned char colormap_size;
+ unsigned short x_origin, y_origin, width, height;
+ unsigned char pixel_size, attributes;
+} TargaHeader;
+
+
+
+/*
+========================================================================
+
+.MD3 triangle model file format
+
+========================================================================
+*/
+
+#define MD3_IDENT (('3'<<24)+('P'<<16)+('D'<<8)+'I')
+#define MD3_VERSION 15
+
+// limits
+#define MD3_MAX_LODS 3
+#define MD3_MAX_TRIANGLES 8192 // per surface
+#define MD3_MAX_VERTS 4096 // per surface
+#define MD3_MAX_SHADERS 256 // per surface
+#define MD3_MAX_FRAMES 1024 // per model
+#define MD3_MAX_SURFACES 32 // per model
+#define MD3_MAX_TAGS 16 // per frame
+
+// vertex scales
+#define MD3_XYZ_SCALE (1.0/64)
+
+typedef struct md3Frame_s {
+ vec3_t bounds[2];
+ vec3_t localOrigin;
+ float radius;
+ char name[16];
+} md3Frame_t;
+
+typedef struct md3Tag_s {
+ char name[MAX_QPATH]; // tag name
+ vec3_t origin;
+ vec3_t axis[3];
+} md3Tag_t;
+
+/*
+** md3Surface_t
+**
+** CHUNK SIZE
+** header sizeof( md3Surface_t )
+** shaders sizeof( md3Shader_t ) * numShaders
+** triangles[0] sizeof( md3Triangle_t ) * numTriangles
+** st sizeof( md3St_t ) * numVerts
+** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames
+*/
+typedef struct {
+ int ident; //
+
+ char name[MAX_QPATH]; // polyset name
+
+ int flags;
+ int numFrames; // all surfaces in a model should have the same
+
+ int numShaders; // all surfaces in a model should have the same
+ int numVerts;
+
+ int numTriangles;
+ int ofsTriangles;
+
+ int ofsShaders; // offset from start of md3Surface_t
+ int ofsSt; // texture coords are common for all frames
+ int ofsXyzNormals; // numVerts * numFrames
+
+ int ofsEnd; // next surface follows
+} md3Surface_t;
+
+typedef struct {
+ char name[MAX_QPATH];
+ int shaderIndex; // for in-game use
+} md3Shader_t;
+
+typedef struct {
+ int indexes[3];
+} md3Triangle_t;
+
+typedef struct {
+ float st[2];
+} md3St_t;
+
+typedef struct {
+ short xyz[3];
+ short normal;
+} md3XyzNormal_t;
+
+typedef struct {
+ int ident;
+ int version;
+
+ char name[MAX_QPATH]; // model name
+
+ int flags;
+
+ int numFrames;
+ int numTags;
+ int numSurfaces;
+
+ int numSkins;
+
+ int ofsFrames; // offset for first frame
+ int ofsTags; // numFrames * numTags
+ int ofsSurfaces; // first surface, others follow
+
+ int ofsEnd; // end of file
+} md3Header_t;
+
+/*
+==============================================================================
+
+MD4 file format
+
+==============================================================================
+*/
+
+#define MD4_IDENT (('4'<<24)+('P'<<16)+('D'<<8)+'I')
+#define MD4_VERSION 1
+#define MD4_MAX_BONES 128
+
+typedef struct {
+ int boneIndex; // these are indexes into the boneReferences,
+ float boneWeight; // not the global per-frame bone list
+ vec3_t offset;
+} md4Weight_t;
+
+typedef struct {
+ vec3_t normal;
+ vec2_t texCoords;
+ int numWeights;
+ md4Weight_t weights[1]; // variable sized
+} md4Vertex_t;
+
+typedef struct {
+ int indexes[3];
+} md4Triangle_t;
+
+typedef struct {
+ int ident;
+
+ char name[MAX_QPATH]; // polyset name
+ char shader[MAX_QPATH];
+ int shaderIndex; // for in-game use
+
+ int ofsHeader; // this will be a negative number
+
+ int numVerts;
+ int ofsVerts;
+
+ int numTriangles;
+ int ofsTriangles;
+
+ // Bone references are a set of ints representing all the bones
+ // present in any vertex weights for this surface. This is
+ // needed because a model may have surfaces that need to be
+ // drawn at different sort times, and we don't want to have
+ // to re-interpolate all the bones for each surface.
+ int numBoneReferences;
+ int ofsBoneReferences;
+
+ int ofsEnd; // next surface follows
+} md4Surface_t;
+
+typedef struct {
+ float matrix[3][4];
+} md4Bone_t;
+
+typedef struct {
+ vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame
+ vec3_t localOrigin; // midpoint of bounds, used for sphere cull
+ float radius; // dist from localOrigin to corner
+ md4Bone_t bones[1]; // [numBones]
+} md4Frame_t;
+
+typedef struct {
+ int numSurfaces;
+ int ofsSurfaces; // first surface, others follow
+ int ofsEnd; // next lod follows
+} md4LOD_t;
+
+typedef struct {
+ int ident;
+ int version;
+
+ char name[MAX_QPATH]; // model name
+
+ // frames and bones are shared by all levels of detail
+ int numFrames;
+ int numBones;
+ int ofsBoneNames; // char name[ MAX_QPATH ]
+ int ofsFrames; // md4Frame_t[numFrames]
+
+ // each level of detail has completely separate sets of surfaces
+ int numLODs;
+ int ofsLODs;
+
+ int ofsEnd; // end of file
+} md4Header_t;
+
+/*
+ * Here are the definitions for Ravensoft's model format of md4. Raven stores their
+ * playermodels in .mdr files, in some games, which are pretty much like the md4
+ * format implemented by ID soft. It seems like ID's original md4 stuff is not used at all.
+ * MDR is being used in EliteForce, JediKnight2 and Soldiers of Fortune2 (I think).
+ * So this comes in handy for anyone who wants to make it possible to load player
+ * models from these games.
+ * This format has bone tags, which is similar to the thing you have in md3 I suppose.
+ * Raven has released their version of md3view under GPL enabling me to add support
+ * to this codebase. Thanks to Steven Howes aka Skinner for helping with example
+ * source code.
+ *
+ * - Thilo Schulz (arny@ats.s.bawue.de)
+ */
+
+// If you want to enable support for Raven's .mdr / md4 format, uncomment the next
+// line.
+//#define RAVENMD4
+
+#ifdef RAVENMD4
+
+#define MDR_IDENT (('5'<<24)+('M'<<16)+('D'<<8)+'R')
+#define MDR_VERSION 2
+#define MDR_MAX_BONES 128
+
+typedef struct {
+ int boneIndex; // these are indexes into the boneReferences,
+ float boneWeight; // not the global per-frame bone list
+ vec3_t offset;
+} mdrWeight_t;
+
+typedef struct {
+ vec3_t normal;
+ vec2_t texCoords;
+ int numWeights;
+ mdrWeight_t weights[1]; // variable sized
+} mdrVertex_t;
+
+typedef struct {
+ int indexes[3];
+} mdrTriangle_t;
+
+typedef struct {
+ int ident;
+
+ char name[MAX_QPATH]; // polyset name
+ char shader[MAX_QPATH];
+ int shaderIndex; // for in-game use
+
+ int ofsHeader; // this will be a negative number
+
+ int numVerts;
+ int ofsVerts;
+
+ int numTriangles;
+ int ofsTriangles;
+
+ // Bone references are a set of ints representing all the bones
+ // present in any vertex weights for this surface. This is
+ // needed because a model may have surfaces that need to be
+ // drawn at different sort times, and we don't want to have
+ // to re-interpolate all the bones for each surface.
+ int numBoneReferences;
+ int ofsBoneReferences;
+
+ int ofsEnd; // next surface follows
+} mdrSurface_t;
+
+typedef struct {
+ float matrix[3][4];
+} mdrBone_t;
+
+typedef struct {
+ vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame
+ vec3_t localOrigin; // midpoint of bounds, used for sphere cull
+ float radius; // dist from localOrigin to corner
+ char name[16];
+ mdrBone_t bones[1]; // [numBones]
+} mdrFrame_t;
+
+typedef struct {
+ unsigned char Comp[24]; // MC_COMP_BYTES is in MatComp.h, but don't want to couple
+} mdrCompBone_t;
+
+typedef struct {
+ vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame
+ vec3_t localOrigin; // midpoint of bounds, used for sphere cull
+ float radius; // dist from localOrigin to corner
+ mdrCompBone_t bones[1]; // [numBones]
+} mdrCompFrame_t;
+
+typedef struct {
+ int numSurfaces;
+ int ofsSurfaces; // first surface, others follow
+ int ofsEnd; // next lod follows
+} mdrLOD_t;
+
+typedef struct {
+ int boneIndex;
+ char name[32];
+} mdrTag_t;
+
+typedef struct {
+ int ident;
+ int version;
+
+ char name[MAX_QPATH]; // model name
+
+ // frames and bones are shared by all levels of detail
+ int numFrames;
+ int numBones;
+ int ofsFrames; // mdrFrame_t[numFrames]
+
+ // each level of detail has completely separate sets of surfaces
+ int numLODs;
+ int ofsLODs;
+
+ int numTags;
+ int ofsTags;
+
+ int ofsEnd; // end of file
+} mdrHeader_t;
+
+#endif
+
+/*
+==============================================================================
+
+ .BSP file format
+
+==============================================================================
+*/
+
+
+#define BSP_IDENT (('P'<<24)+('S'<<16)+('B'<<8)+'I')
+ // little-endian "IBSP"
+
+#define BSP_VERSION 46
+
+
+// there shouldn't be any problem with increasing these values at the
+// expense of more memory allocation in the utilities
+#define MAX_MAP_MODELS 0x400
+#define MAX_MAP_BRUSHES 0x8000
+#define MAX_MAP_ENTITIES 0x800
+#define MAX_MAP_ENTSTRING 0x40000
+#define MAX_MAP_SHADERS 0x400
+
+#define MAX_MAP_AREAS 0x100 // MAX_MAP_AREA_BYTES in q_shared must match!
+#define MAX_MAP_FOGS 0x100
+#define MAX_MAP_PLANES 0x20000
+#define MAX_MAP_NODES 0x20000
+#define MAX_MAP_BRUSHSIDES 0x20000
+#define MAX_MAP_LEAFS 0x20000
+#define MAX_MAP_LEAFFACES 0x20000
+#define MAX_MAP_LEAFBRUSHES 0x40000
+#define MAX_MAP_PORTALS 0x20000
+#define MAX_MAP_LIGHTING 0x800000
+#define MAX_MAP_LIGHTGRID 0x800000
+#define MAX_MAP_VISIBILITY 0x200000
+
+#define MAX_MAP_DRAW_SURFS 0x20000
+#define MAX_MAP_DRAW_VERTS 0x80000
+#define MAX_MAP_DRAW_INDEXES 0x80000
+
+
+// key / value pair sizes in the entities lump
+#define MAX_KEY 32
+#define MAX_VALUE 1024
+
+// the editor uses these predefined yaw angles to orient entities up or down
+#define ANGLE_UP -1
+#define ANGLE_DOWN -2
+
+#define LIGHTMAP_WIDTH 128
+#define LIGHTMAP_HEIGHT 128
+
+#define MAX_WORLD_COORD ( 128*1024 )
+#define MIN_WORLD_COORD ( -128*1024 )
+#define WORLD_SIZE ( MAX_WORLD_COORD - MIN_WORLD_COORD )
+
+//=============================================================================
+
+
+typedef struct {
+ int fileofs, filelen;
+} lump_t;
+
+#define LUMP_ENTITIES 0
+#define LUMP_SHADERS 1
+#define LUMP_PLANES 2
+#define LUMP_NODES 3
+#define LUMP_LEAFS 4
+#define LUMP_LEAFSURFACES 5
+#define LUMP_LEAFBRUSHES 6
+#define LUMP_MODELS 7
+#define LUMP_BRUSHES 8
+#define LUMP_BRUSHSIDES 9
+#define LUMP_DRAWVERTS 10
+#define LUMP_DRAWINDEXES 11
+#define LUMP_FOGS 12
+#define LUMP_SURFACES 13
+#define LUMP_LIGHTMAPS 14
+#define LUMP_LIGHTGRID 15
+#define LUMP_VISIBILITY 16
+#define HEADER_LUMPS 17
+
+typedef struct {
+ int ident;
+ int version;
+
+ lump_t lumps[HEADER_LUMPS];
+} dheader_t;
+
+typedef struct {
+ float mins[3], maxs[3];
+ int firstSurface, numSurfaces;
+ int firstBrush, numBrushes;
+} dmodel_t;
+
+typedef struct {
+ char shader[MAX_QPATH];
+ int surfaceFlags;
+ int contentFlags;
+} dshader_t;
+
+// planes x^1 is allways the opposite of plane x
+
+typedef struct {
+ float normal[3];
+ float dist;
+} dplane_t;
+
+typedef struct {
+ int planeNum;
+ int children[2]; // negative numbers are -(leafs+1), not nodes
+ int mins[3]; // for frustom culling
+ int maxs[3];
+} dnode_t;
+
+typedef struct {
+ int cluster; // -1 = opaque cluster (do I still store these?)
+ int area;
+
+ int mins[3]; // for frustum culling
+ int maxs[3];
+
+ int firstLeafSurface;
+ int numLeafSurfaces;
+
+ int firstLeafBrush;
+ int numLeafBrushes;
+} dleaf_t;
+
+typedef struct {
+ int planeNum; // positive plane side faces out of the leaf
+ int shaderNum;
+} dbrushside_t;
+
+typedef struct {
+ int firstSide;
+ int numSides;
+ int shaderNum; // the shader that determines the contents flags
+} dbrush_t;
+
+typedef struct {
+ char shader[MAX_QPATH];
+ int brushNum;
+ int visibleSide; // the brush side that ray tests need to clip against (-1 == none)
+} dfog_t;
+
+typedef struct {
+ vec3_t xyz;
+ float st[2];
+ float lightmap[2];
+ vec3_t normal;
+ byte color[4];
+} drawVert_t;
+
+#define drawVert_t_cleared(x) drawVert_t (x) = {{0, 0, 0}, {0, 0}, {0, 0}, {0, 0, 0}, {0, 0, 0, 0}}
+
+typedef enum {
+ MST_BAD,
+ MST_PLANAR,
+ MST_PATCH,
+ MST_TRIANGLE_SOUP,
+ MST_FLARE
+} mapSurfaceType_t;
+
+typedef struct {
+ int shaderNum;
+ int fogNum;
+ int surfaceType;
+
+ int firstVert;
+ int numVerts;
+
+ int firstIndex;
+ int numIndexes;
+
+ int lightmapNum;
+ int lightmapX, lightmapY;
+ int lightmapWidth, lightmapHeight;
+
+ vec3_t lightmapOrigin;
+ vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds
+
+ int patchWidth;
+ int patchHeight;
+} dsurface_t;
+
+
+#endif
diff --git a/src/qcommon/surfaceflags.h b/src/qcommon/surfaceflags.h
new file mode 100644
index 0000000..31ece5c
--- /dev/null
+++ b/src/qcommon/surfaceflags.h
@@ -0,0 +1,91 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+// This file must be identical in the quake and utils directories
+
+// contents flags are seperate bits
+// a given brush can contribute multiple content bits
+
+// these definitions also need to be in q_shared.h!
+
+#define CONTENTS_SOLID 1 // an eye is never valid in a solid
+#define CONTENTS_LAVA 8
+#define CONTENTS_SLIME 16
+#define CONTENTS_WATER 32
+#define CONTENTS_FOG 64
+
+#define CONTENTS_NOTTEAM1 0x0080
+#define CONTENTS_NOTTEAM2 0x0100
+#define CONTENTS_NOBOTCLIP 0x0200
+
+#define CONTENTS_AREAPORTAL 0x8000
+
+#define CONTENTS_PLAYERCLIP 0x10000
+#define CONTENTS_MONSTERCLIP 0x20000
+//bot specific contents types
+#define CONTENTS_TELEPORTER 0x40000
+#define CONTENTS_JUMPPAD 0x80000
+#define CONTENTS_CLUSTERPORTAL 0x100000
+#define CONTENTS_DONOTENTER 0x200000
+#define CONTENTS_BOTCLIP 0x400000
+#define CONTENTS_MOVER 0x800000
+
+#define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity
+
+#define CONTENTS_BODY 0x2000000 // should never be on a brush, only in game
+#define CONTENTS_CORPSE 0x4000000
+#define CONTENTS_DETAIL 0x8000000 // brushes not used for the bsp
+#define CONTENTS_STRUCTURAL 0x10000000 // brushes used for the bsp
+#define CONTENTS_TRANSLUCENT 0x20000000 // don't consume surface fragments inside
+#define CONTENTS_TRIGGER 0x40000000
+#define CONTENTS_NODROP 0x80000000 // don't leave bodies or items (death fog, lava)
+
+//TA: custominfoparms below
+#define CONTENTS_NOALIENBUILD 0x1000 //disallow alien building
+#define CONTENTS_NOHUMANBUILD 0x2000 //disallow alien building
+#define CONTENTS_NOBUILD 0x4000 //disallow alien building
+
+#define SURF_NODAMAGE 0x1 // never give falling damage
+#define SURF_SLICK 0x2 // effects game physics
+#define SURF_SKY 0x4 // lighting from environment map
+#define SURF_LADDER 0x8
+#define SURF_NOIMPACT 0x10 // don't make missile explosions
+#define SURF_NOMARKS 0x20 // don't leave missile marks
+#define SURF_FLESH 0x40 // make flesh sounds and effects
+#define SURF_NODRAW 0x80 // don't generate a drawsurface at all
+#define SURF_HINT 0x100 // make a primary bsp splitter
+#define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes
+#define SURF_NOLIGHTMAP 0x400 // surface doesn't need a lightmap
+#define SURF_POINTLIGHT 0x800 // generate lighting info at vertexes
+#define SURF_METALSTEPS 0x1000 // clanking footsteps
+#define SURF_NOSTEPS 0x2000 // no footstep sounds
+#define SURF_NONSOLID 0x4000 // don't collide against curves with this set
+#define SURF_LIGHTFILTER 0x8000 // act as a light filter during q3map -light
+#define SURF_ALPHASHADOW 0x10000 // do per-pixel light shadow casting in q3map
+#define SURF_NODLIGHT 0x20000 // don't dlight even if solid (solid lava, skies)
+#define SURF_DUST 0x40000 // leave a dust trail when walking on this surface
+
+//TA: custominfoparms below
+#define SURF_NOALIENBUILD 0x80000 //disallow alien building
+#define SURF_NOHUMANBUILD 0x100000 //disallow alien building
+#define SURF_NOBUILD 0x200000 //disallow alien building
diff --git a/src/qcommon/unzip.c b/src/qcommon/unzip.c
new file mode 100644
index 0000000..ac95609
--- /dev/null
+++ b/src/qcommon/unzip.c
@@ -0,0 +1,4297 @@
+/*****************************************************************************
+ * name: unzip.c
+ *
+ * desc: IO on .zip files using portions of zlib
+ *
+ * $Archive: /MissionPack/code/qcommon/unzip.c $
+ *
+ *****************************************************************************/
+
+#include "../client/client.h"
+#include "unzip.h"
+
+/* unzip.h -- IO for uncompress .zip files using zlib
+ Version 0.15 beta, Mar 19th, 1998,
+
+ Copyright (C) 1998 Gilles Vollant
+
+ This unzip package allow extract file from .ZIP file, compatible with PKZip 2.04g
+ WinZip, InfoZip tools and compatible.
+ Encryption and multi volume ZipFile (span) are not supported.
+ Old compressions used by old PKZip 1.x are not supported
+
+ THIS IS AN ALPHA VERSION. AT THIS STAGE OF DEVELOPPEMENT, SOMES API OR STRUCTURE
+ CAN CHANGE IN FUTURE VERSION !!
+ I WAIT FEEDBACK at mail info@winimage.com
+ Visit also http://www.winimage.com/zLibDll/unzip.htm for evolution
+
+ Condition of use and distribution are the same than zlib :
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+
+*/
+/* for more info about .ZIP format, see
+ ftp://ftp.cdrom.com/pub/infozip/doc/appnote-970311-iz.zip
+ PkWare has also a specification at :
+ ftp://ftp.pkware.com/probdesc.zip */
+
+/* zlib.h -- interface of the 'zlib' general purpose compression library
+ version 1.1.3, July 9th, 1998
+
+ Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+ Jean-loup Gailly Mark Adler
+ jloup@gzip.org madler@alumni.caltech.edu
+
+
+ The data format used by the zlib library is described by RFCs (Request for
+ Comments) 1950 to 1952 in the files ftp://ds.internic.net/rfc/rfc1950.txt
+ (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format).
+*/
+
+/* zconf.h -- configuration of the zlib compression library
+ * Copyright (C) 1995-1998 Jean-loup Gailly.
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+
+#ifndef _ZCONF_H
+#define _ZCONF_H
+
+/* Maximum value for memLevel in deflateInit2 */
+#ifndef MAX_MEM_LEVEL
+# ifdef MAXSEG_64K
+# define MAX_MEM_LEVEL 8
+# else
+# define MAX_MEM_LEVEL 9
+# endif
+#endif
+
+/* Maximum value for windowBits in deflateInit2 and inflateInit2.
+ * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files
+ * created by gzip. (Files created by minigzip can still be extracted by
+ * gzip.)
+ */
+#ifndef MAX_WBITS
+# define MAX_WBITS 15 /* 32K LZ77 window */
+#endif
+
+/* The memory requirements for deflate are (in bytes):
+ (1 << (windowBits+2)) + (1 << (memLevel+9))
+ that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values)
+ plus a few kilobytes for small objects. For example, if you want to reduce
+ the default memory requirements from 256K to 128K, compile with
+ make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7"
+ Of course this will generally degrade compression (there's no free lunch).
+
+ The memory requirements for inflate are (in bytes) 1 << windowBits
+ that is, 32K for windowBits=15 (default value) plus a few kilobytes
+ for small objects.
+*/
+
+ /* Type declarations */
+
+#ifndef OF /* function prototypes */
+#define OF(args) args
+#endif
+
+typedef unsigned char Byte; /* 8 bits */
+typedef unsigned int uInt; /* 16 bits or more */
+typedef unsigned long uLong; /* 32 bits or more */
+typedef Byte *voidp;
+
+#ifndef SEEK_SET
+# define SEEK_SET 0 /* Seek from beginning of file. */
+# define SEEK_CUR 1 /* Seek from current position. */
+# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */
+#endif
+
+#endif /* _ZCONF_H */
+
+#define ZLIB_VERSION "1.1.3"
+
+/*
+ The 'zlib' compression library provides in-memory compression and
+ decompression functions, including integrity checks of the uncompressed
+ data. This version of the library supports only one compression method
+ (deflation) but other algorithms will be added later and will have the same
+ stream interface.
+
+ Compression can be done in a single step if the buffers are large
+ enough (for example if an input file is mmap'ed), or can be done by
+ repeated calls of the compression function. In the latter case, the
+ application must provide more input and/or consume the output
+ (providing more output space) before each call.
+
+ The library also supports reading and writing files in gzip (.gz) format
+ with an interface similar to that of stdio.
+
+ The library does not install any signal handler. The decoder checks
+ the consistency of the compressed data, so the library should never
+ crash even in case of corrupted input.
+*/
+
+/*
+ The application must update next_in and avail_in when avail_in has
+ dropped to zero. It must update next_out and avail_out when avail_out
+ has dropped to zero. The application must initialize zalloc, zfree and
+ opaque before calling the init function. All other fields are set by the
+ compression library and must not be updated by the application.
+
+ The opaque value provided by the application will be passed as the first
+ parameter for calls of zalloc and zfree. This can be useful for custom
+ memory management. The compression library attaches no meaning to the
+ opaque value.
+
+ zalloc must return Z_NULL if there is not enough memory for the object.
+ If zlib is used in a multi-threaded application, zalloc and zfree must be
+ thread safe.
+
+ On 16-bit systems, the functions zalloc and zfree must be able to allocate
+ exactly 65536 bytes, but will not be required to allocate more than this
+ if the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS,
+ pointers returned by zalloc for objects of exactly 65536 bytes *must*
+ have their offset normalized to zero. The default allocation function
+ provided by this library ensures this (see zutil.c). To reduce memory
+ requirements and avoid any allocation of 64K objects, at the expense of
+ compression ratio, compile the library with -DMAX_WBITS=14 (see zconf.h).
+
+ The fields total_in and total_out can be used for statistics or
+ progress reports. After compression, total_in holds the total size of
+ the uncompressed data and may be saved for use in the decompressor
+ (particularly if the decompressor wants to decompress everything in
+ a single step).
+*/
+
+ /* constants */
+
+#define Z_NO_FLUSH 0
+#define Z_PARTIAL_FLUSH 1 /* will be removed, use Z_SYNC_FLUSH instead */
+#define Z_SYNC_FLUSH 2
+#define Z_FULL_FLUSH 3
+#define Z_FINISH 4
+/* Allowed flush values; see deflate() below for details */
+
+#define Z_OK 0
+#define Z_STREAM_END 1
+#define Z_NEED_DICT 2
+#define Z_ERRNO (-1)
+#define Z_STREAM_ERROR (-2)
+#define Z_DATA_ERROR (-3)
+#define Z_MEM_ERROR (-4)
+#define Z_BUF_ERROR (-5)
+#define Z_VERSION_ERROR (-6)
+/* Return codes for the compression/decompression functions. Negative
+ * values are errors, positive values are used for special but normal events.
+ */
+
+#define Z_NO_COMPRESSION 0
+#define Z_BEST_SPEED 1
+#define Z_BEST_COMPRESSION 9
+#define Z_DEFAULT_COMPRESSION (-1)
+/* compression levels */
+
+#define Z_FILTERED 1
+#define Z_HUFFMAN_ONLY 2
+#define Z_DEFAULT_STRATEGY 0
+/* compression strategy; see deflateInit2() below for details */
+
+#define Z_BINARY 0
+#define Z_ASCII 1
+#define Z_UNKNOWN 2
+/* Possible values of the data_type field */
+
+#define Z_DEFLATED 8
+/* The deflate compression method (the only one supported in this version) */
+
+#define Z_NULL (void *)0 /* for initializing zalloc, zfree, opaque */
+
+#define zlib_version zlibVersion()
+/* for compatibility with versions < 1.0.2 */
+
+ /* basic functions */
+
+// static const char * zlibVersion OF((void));
+/* The application can compare zlibVersion and ZLIB_VERSION for consistency.
+ If the first character differs, the library code actually used is
+ not compatible with the zlib.h header file used by the application.
+ This check is automatically made by deflateInit and inflateInit.
+ */
+
+/*
+int deflateInit OF((z_streamp strm, int level));
+
+ Initializes the internal stream state for compression. The fields
+ zalloc, zfree and opaque must be initialized before by the caller.
+ If zalloc and zfree are set to Z_NULL, deflateInit updates them to
+ use default allocation functions.
+
+ The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9:
+ 1 gives best speed, 9 gives best compression, 0 gives no compression at
+ all (the input data is simply copied a block at a time).
+ Z_DEFAULT_COMPRESSION requests a default compromise between speed and
+ compression (currently equivalent to level 6).
+
+ deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not
+ enough memory, Z_STREAM_ERROR if level is not a valid compression level,
+ Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible
+ with the version assumed by the caller (ZLIB_VERSION).
+ msg is set to null if there is no error message. deflateInit does not
+ perform any compression: this will be done by deflate().
+*/
+
+
+// static int deflate OF((z_streamp strm, int flush));
+/*
+ deflate compresses as much data as possible, and stops when the input
+ buffer becomes empty or the output buffer becomes full. It may introduce some
+ output latency (reading input without producing any output) except when
+ forced to flush.
+
+ The detailed semantics are as follows. deflate performs one or both of the
+ following actions:
+
+ - Compress more input starting at next_in and update next_in and avail_in
+ accordingly. If not all input can be processed (because there is not
+ enough room in the output buffer), next_in and avail_in are updated and
+ processing will resume at this point for the next call of deflate().
+
+ - Provide more output starting at next_out and update next_out and avail_out
+ accordingly. This action is forced if the parameter flush is non zero.
+ Forcing flush frequently degrades the compression ratio, so this parameter
+ should be set only when necessary (in interactive applications).
+ Some output may be provided even if flush is not set.
+
+ Before the call of deflate(), the application should ensure that at least
+ one of the actions is possible, by providing more input and/or consuming
+ more output, and updating avail_in or avail_out accordingly; avail_out
+ should never be zero before the call. The application can consume the
+ compressed output when it wants, for example when the output buffer is full
+ (avail_out == 0), or after each call of deflate(). If deflate returns Z_OK
+ and with zero avail_out, it must be called again after making room in the
+ output buffer because there might be more output pending.
+
+ If the parameter flush is set to Z_SYNC_FLUSH, all pending output is
+ flushed to the output buffer and the output is aligned on a byte boundary, so
+ that the decompressor can get all input data available so far. (In particular
+ avail_in is zero after the call if enough output space has been provided
+ before the call.) Flushing may degrade compression for some compression
+ algorithms and so it should be used only when necessary.
+
+ If flush is set to Z_FULL_FLUSH, all output is flushed as with
+ Z_SYNC_FLUSH, and the compression state is reset so that decompression can
+ restart from this point if previous compressed data has been damaged or if
+ random access is desired. Using Z_FULL_FLUSH too often can seriously degrade
+ the compression.
+
+ If deflate returns with avail_out == 0, this function must be called again
+ with the same value of the flush parameter and more output space (updated
+ avail_out), until the flush is complete (deflate returns with non-zero
+ avail_out).
+
+ If the parameter flush is set to Z_FINISH, pending input is processed,
+ pending output is flushed and deflate returns with Z_STREAM_END if there
+ was enough output space; if deflate returns with Z_OK, this function must be
+ called again with Z_FINISH and more output space (updated avail_out) but no
+ more input data, until it returns with Z_STREAM_END or an error. After
+ deflate has returned Z_STREAM_END, the only possible operations on the
+ stream are deflateReset or deflateEnd.
+
+ Z_FINISH can be used immediately after deflateInit if all the compression
+ is to be done in a single step. In this case, avail_out must be at least
+ 0.1% larger than avail_in plus 12 bytes. If deflate does not return
+ Z_STREAM_END, then it must be called again as described above.
+
+ deflate() sets strm->adler to the adler32 checksum of all input read
+ so (that is, total_in bytes).
+
+ deflate() may update data_type if it can make a good guess about
+ the input data type (Z_ASCII or Z_BINARY). In doubt, the data is considered
+ binary. This field is only for information purposes and does not affect
+ the compression algorithm in any manner.
+
+ deflate() returns Z_OK if some progress has been made (more input
+ processed or more output produced), Z_STREAM_END if all input has been
+ consumed and all output has been produced (only when flush is set to
+ Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example
+ if next_in or next_out was NULL), Z_BUF_ERROR if no progress is possible
+ (for example avail_in or avail_out was zero).
+*/
+
+
+// static int deflateEnd OF((z_streamp strm));
+/*
+ All dynamically allocated data structures for this stream are freed.
+ This function discards any unprocessed input and does not flush any
+ pending output.
+
+ deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the
+ stream state was inconsistent, Z_DATA_ERROR if the stream was freed
+ prematurely (some input or output was discarded). In the error case,
+ msg may be set but then points to a static string (which must not be
+ deallocated).
+*/
+
+
+/*
+int inflateInit OF((z_streamp strm));
+
+ Initializes the internal stream state for decompression. The fields
+ next_in, avail_in, zalloc, zfree and opaque must be initialized before by
+ the caller. If next_in is not Z_NULL and avail_in is large enough (the exact
+ value depends on the compression method), inflateInit determines the
+ compression method from the zlib header and allocates all data structures
+ accordingly; otherwise the allocation will be deferred to the first call of
+ inflate. If zalloc and zfree are set to Z_NULL, inflateInit updates them to
+ use default allocation functions.
+
+ inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough
+ memory, Z_VERSION_ERROR if the zlib library version is incompatible with the
+ version assumed by the caller. msg is set to null if there is no error
+ message. inflateInit does not perform any decompression apart from reading
+ the zlib header if present: this will be done by inflate(). (So next_in and
+ avail_in may be modified, but next_out and avail_out are unchanged.)
+*/
+
+
+static int inflate OF((z_streamp strm, int flush));
+/*
+ inflate decompresses as much data as possible, and stops when the input
+ buffer becomes empty or the output buffer becomes full. It may some
+ introduce some output latency (reading input without producing any output)
+ except when forced to flush.
+
+ The detailed semantics are as follows. inflate performs one or both of the
+ following actions:
+
+ - Decompress more input starting at next_in and update next_in and avail_in
+ accordingly. If not all input can be processed (because there is not
+ enough room in the output buffer), next_in is updated and processing
+ will resume at this point for the next call of inflate().
+
+ - Provide more output starting at next_out and update next_out and avail_out
+ accordingly. inflate() provides as much output as possible, until there
+ is no more input data or no more space in the output buffer (see below
+ about the flush parameter).
+
+ Before the call of inflate(), the application should ensure that at least
+ one of the actions is possible, by providing more input and/or consuming
+ more output, and updating the next_* and avail_* values accordingly.
+ The application can consume the uncompressed output when it wants, for
+ example when the output buffer is full (avail_out == 0), or after each
+ call of inflate(). If inflate returns Z_OK and with zero avail_out, it
+ must be called again after making room in the output buffer because there
+ might be more output pending.
+
+ If the parameter flush is set to Z_SYNC_FLUSH, inflate flushes as much
+ output as possible to the output buffer. The flushing behavior of inflate is
+ not specified for values of the flush parameter other than Z_SYNC_FLUSH
+ and Z_FINISH, but the current implementation actually flushes as much output
+ as possible anyway.
+
+ inflate() should normally be called until it returns Z_STREAM_END or an
+ error. However if all decompression is to be performed in a single step
+ (a single call of inflate), the parameter flush should be set to
+ Z_FINISH. In this case all pending input is processed and all pending
+ output is flushed; avail_out must be large enough to hold all the
+ uncompressed data. (The size of the uncompressed data may have been saved
+ by the compressor for this purpose.) The next operation on this stream must
+ be inflateEnd to deallocate the decompression state. The use of Z_FINISH
+ is never required, but can be used to inform inflate that a faster routine
+ may be used for the single inflate() call.
+
+ If a preset dictionary is needed at this point (see inflateSetDictionary
+ below), inflate sets strm-adler to the adler32 checksum of the
+ dictionary chosen by the compressor and returns Z_NEED_DICT; otherwise
+ it sets strm->adler to the adler32 checksum of all output produced
+ so (that is, total_out bytes) and returns Z_OK, Z_STREAM_END or
+ an error code as described below. At the end of the stream, inflate()
+ checks that its computed adler32 checksum is equal to that saved by the
+ compressor and returns Z_STREAM_END only if the checksum is correct.
+
+ inflate() returns Z_OK if some progress has been made (more input processed
+ or more output produced), Z_STREAM_END if the end of the compressed data has
+ been reached and all uncompressed output has been produced, Z_NEED_DICT if a
+ preset dictionary is needed at this point, Z_DATA_ERROR if the input data was
+ corrupted (input stream not conforming to the zlib format or incorrect
+ adler32 checksum), Z_STREAM_ERROR if the stream structure was inconsistent
+ (for example if next_in or next_out was NULL), Z_MEM_ERROR if there was not
+ enough memory, Z_BUF_ERROR if no progress is possible or if there was not
+ enough room in the output buffer when Z_FINISH is used. In the Z_DATA_ERROR
+ case, the application may then call inflateSync to look for a good
+ compression block.
+*/
+
+
+static int inflateEnd OF((z_streamp strm));
+/*
+ All dynamically allocated data structures for this stream are freed.
+ This function discards any unprocessed input and does not flush any
+ pending output.
+
+ inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state
+ was inconsistent. In the error case, msg may be set but then points to a
+ static string (which must not be deallocated).
+*/
+
+ /* Advanced functions */
+
+/*
+ The following functions are needed only in some special applications.
+*/
+
+/*
+int deflateInit2 OF((z_streamp strm,
+ int level,
+ int method,
+ int windowBits,
+ int memLevel,
+ int strategy));
+
+ This is another version of deflateInit with more compression options. The
+ fields next_in, zalloc, zfree and opaque must be initialized before by
+ the caller.
+
+ The method parameter is the compression method. It must be Z_DEFLATED in
+ this version of the library.
+
+ The windowBits parameter is the base two logarithm of the window size
+ (the size of the history buffer). It should be in the range 8..15 for this
+ version of the library. Larger values of this parameter result in better
+ compression at the expense of memory usage. The default value is 15 if
+ deflateInit is used instead.
+
+ The memLevel parameter specifies how much memory should be allocated
+ for the internal compression state. memLevel=1 uses minimum memory but
+ is slow and reduces compression ratio; memLevel=9 uses maximum memory
+ for optimal speed. The default value is 8. See zconf.h for total memory
+ usage as a function of windowBits and memLevel.
+
+ The strategy parameter is used to tune the compression algorithm. Use the
+ value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a
+ filter (or predictor), or Z_HUFFMAN_ONLY to force Huffman encoding only (no
+ string match). Filtered data consists mostly of small values with a
+ somewhat random distribution. In this case, the compression algorithm is
+ tuned to compress them better. The effect of Z_FILTERED is to force more
+ Huffman coding and less string matching; it is somewhat intermediate
+ between Z_DEFAULT and Z_HUFFMAN_ONLY. The strategy parameter only affects
+ the compression ratio but not the correctness of the compressed output even
+ if it is not set appropriately.
+
+ deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+ memory, Z_STREAM_ERROR if a parameter is invalid (such as an invalid
+ method). msg is set to null if there is no error message. deflateInit2 does
+ not perform any compression: this will be done by deflate().
+*/
+
+/*
+static int deflateSetDictionary OF((z_streamp strm,
+ const Byte *dictionary,
+ uInt dictLength));
+*/
+/*
+ Initializes the compression dictionary from the given byte sequence
+ without producing any compressed output. This function must be called
+ immediately after deflateInit, deflateInit2 or deflateReset, before any
+ call of deflate. The compressor and decompressor must use exactly the same
+ dictionary (see inflateSetDictionary).
+
+ The dictionary should consist of strings (byte sequences) that are likely
+ to be encountered later in the data to be compressed, with the most commonly
+ used strings preferably put towards the end of the dictionary. Using a
+ dictionary is most useful when the data to be compressed is short and can be
+ predicted with good accuracy; the data can then be compressed better than
+ with the default empty dictionary.
+
+ Depending on the size of the compression data structures selected by
+ deflateInit or deflateInit2, a part of the dictionary may in effect be
+ discarded, for example if the dictionary is larger than the window size in
+ deflate or deflate2. Thus the strings most likely to be useful should be
+ put at the end of the dictionary, not at the front.
+
+ Upon return of this function, strm->adler is set to the Adler32 value
+ of the dictionary; the decompressor may later use this value to determine
+ which dictionary has been used by the compressor. (The Adler32 value
+ applies to the whole dictionary even if only a subset of the dictionary is
+ actually used by the compressor.)
+
+ deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a
+ parameter is invalid (such as NULL dictionary) or the stream state is
+ inconsistent (for example if deflate has already been called for this stream
+ or if the compression method is bsort). deflateSetDictionary does not
+ perform any compression: this will be done by deflate().
+*/
+
+/*
+static int deflateCopy OF((z_streamp dest,
+ z_streamp source));
+*/
+/*
+ Sets the destination stream as a complete copy of the source stream.
+
+ This function can be useful when several compression strategies will be
+ tried, for example when there are several ways of pre-processing the input
+ data with a filter. The streams that will be discarded should then be freed
+ by calling deflateEnd. Note that deflateCopy duplicates the internal
+ compression state which can be quite large, so this strategy is slow and
+ can consume lots of memory.
+
+ deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not
+ enough memory, Z_STREAM_ERROR if the source stream state was inconsistent
+ (such as zalloc being NULL). msg is left unchanged in both source and
+ destination.
+*/
+
+// static int deflateReset OF((z_streamp strm));
+/*
+ This function is equivalent to deflateEnd followed by deflateInit,
+ but does not free and reallocate all the internal compression state.
+ The stream will keep the same compression level and any other attributes
+ that may have been set by deflateInit2.
+
+ deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source
+ stream state was inconsistent (such as zalloc or state being NULL).
+*/
+
+/*
+static int deflateParams OF((z_streamp strm,
+ int level,
+ int strategy));
+*/
+/*
+ Dynamically update the compression level and compression strategy. The
+ interpretation of level and strategy is as in deflateInit2. This can be
+ used to switch between compression and straight copy of the input data, or
+ to switch to a different kind of input data requiring a different
+ strategy. If the compression level is changed, the input available so far
+ is compressed with the old level (and may be flushed); the new level will
+ take effect only at the next call of deflate().
+
+ Before the call of deflateParams, the stream state must be set as for
+ a call of deflate(), since the currently available input may have to
+ be compressed and flushed. In particular, strm->avail_out must be non-zero.
+
+ deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source
+ stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR
+ if strm->avail_out was zero.
+*/
+
+/*
+int inflateInit2 OF((z_streamp strm,
+ int windowBits));
+
+ This is another version of inflateInit with an extra parameter. The
+ fields next_in, avail_in, zalloc, zfree and opaque must be initialized
+ before by the caller.
+
+ The windowBits parameter is the base two logarithm of the maximum window
+ size (the size of the history buffer). It should be in the range 8..15 for
+ this version of the library. The default value is 15 if inflateInit is used
+ instead. If a compressed stream with a larger window size is given as
+ input, inflate() will return with the error code Z_DATA_ERROR instead of
+ trying to allocate a larger window.
+
+ inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+ memory, Z_STREAM_ERROR if a parameter is invalid (such as a negative
+ memLevel). msg is set to null if there is no error message. inflateInit2
+ does not perform any decompression apart from reading the zlib header if
+ present: this will be done by inflate(). (So next_in and avail_in may be
+ modified, but next_out and avail_out are unchanged.)
+*/
+
+/*
+static int inflateSetDictionary OF((z_streamp strm,
+ const Byte *dictionary,
+ uInt dictLength));
+*/
+/*
+ Initializes the decompression dictionary from the given uncompressed byte
+ sequence. This function must be called immediately after a call of inflate
+ if this call returned Z_NEED_DICT. The dictionary chosen by the compressor
+ can be determined from the Adler32 value returned by this call of
+ inflate. The compressor and decompressor must use exactly the same
+ dictionary (see deflateSetDictionary).
+
+ inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a
+ parameter is invalid (such as NULL dictionary) or the stream state is
+ inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the
+ expected one (incorrect Adler32 value). inflateSetDictionary does not
+ perform any decompression: this will be done by subsequent calls of
+ inflate().
+*/
+
+// static int inflateSync OF((z_streamp strm));
+/*
+ Skips invalid compressed data until a full flush point (see above the
+ description of deflate with Z_FULL_FLUSH) can be found, or until all
+ available input is skipped. No output is provided.
+
+ inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR
+ if no more input was provided, Z_DATA_ERROR if no flush point has been found,
+ or Z_STREAM_ERROR if the stream structure was inconsistent. In the success
+ case, the application may save the current current value of total_in which
+ indicates where valid compressed data was found. In the error case, the
+ application may repeatedly call inflateSync, providing more input each time,
+ until success or end of the input data.
+*/
+
+static int inflateReset OF((z_streamp strm));
+/*
+ This function is equivalent to inflateEnd followed by inflateInit,
+ but does not free and reallocate all the internal decompression state.
+ The stream will keep attributes that may have been set by inflateInit2.
+
+ inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source
+ stream state was inconsistent (such as zalloc or state being NULL).
+*/
+
+
+ /* utility functions */
+
+/*
+ The following utility functions are implemented on top of the
+ basic stream-oriented functions. To simplify the interface, some
+ default options are assumed (compression level and memory usage,
+ standard memory allocation functions). The source code of these
+ utility functions can easily be modified if you need special options.
+*/
+
+/*
+static int compress OF((Byte *dest, uLong *destLen,
+ const Byte *source, uLong sourceLen));
+*/
+/*
+ Compresses the source buffer into the destination buffer. sourceLen is
+ the byte length of the source buffer. Upon entry, destLen is the total
+ size of the destination buffer, which must be at least 0.1% larger than
+ sourceLen plus 12 bytes. Upon exit, destLen is the actual size of the
+ compressed buffer.
+ This function can be used to compress a whole file at once if the
+ input file is mmap'ed.
+ compress returns Z_OK if success, Z_MEM_ERROR if there was not
+ enough memory, Z_BUF_ERROR if there was not enough room in the output
+ buffer.
+*/
+
+/*
+static int compress2 OF((Byte *dest, uLong *destLen,
+ const Byte *source, uLong sourceLen,
+ int level));
+*/
+/*
+ Compresses the source buffer into the destination buffer. The level
+ parameter has the same meaning as in deflateInit. sourceLen is the byte
+ length of the source buffer. Upon entry, destLen is the total size of the
+ destination buffer, which must be at least 0.1% larger than sourceLen plus
+ 12 bytes. Upon exit, destLen is the actual size of the compressed buffer.
+
+ compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+ memory, Z_BUF_ERROR if there was not enough room in the output buffer,
+ Z_STREAM_ERROR if the level parameter is invalid.
+*/
+
+/*
+static int uncompress OF((Byte *dest, uLong *destLen,
+ const Byte *source, uLong sourceLen));
+*/
+/*
+ Decompresses the source buffer into the destination buffer. sourceLen is
+ the byte length of the source buffer. Upon entry, destLen is the total
+ size of the destination buffer, which must be large enough to hold the
+ entire uncompressed data. (The size of the uncompressed data must have
+ been saved previously by the compressor and transmitted to the decompressor
+ by some mechanism outside the scope of this compression library.)
+ Upon exit, destLen is the actual size of the compressed buffer.
+ This function can be used to decompress a whole file at once if the
+ input file is mmap'ed.
+
+ uncompress returns Z_OK if success, Z_MEM_ERROR if there was not
+ enough memory, Z_BUF_ERROR if there was not enough room in the output
+ buffer, or Z_DATA_ERROR if the input data was corrupted.
+*/
+
+
+typedef voidp gzFile;
+
+gzFile gzopen OF((const char *path, const char *mode));
+/*
+ Opens a gzip (.gz) file for reading or writing. The mode parameter
+ is as in fopen ("rb" or "wb") but can also include a compression level
+ ("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for
+ Huffman only compression as in "wb1h". (See the description
+ of deflateInit2 for more information about the strategy parameter.)
+
+ gzopen can be used to read a file which is not in gzip format; in this
+ case gzread will directly read from the file without decompression.
+
+ gzopen returns NULL if the file could not be opened or if there was
+ insufficient memory to allocate the (de)compression state; errno
+ can be checked to distinguish the two cases (if errno is zero, the
+ zlib error is Z_MEM_ERROR). */
+
+gzFile gzdopen OF((int fd, const char *mode));
+/*
+ gzdopen() associates a gzFile with the file descriptor fd. File
+ descriptors are obtained from calls like open, dup, creat, pipe or
+ fileno (in the file has been previously opened with fopen).
+ The mode parameter is as in gzopen.
+ The next call of gzclose on the returned gzFile will also close the
+ file descriptor fd, just like fclose(fdopen(fd), mode) closes the file
+ descriptor fd. If you want to keep fd open, use gzdopen(dup(fd), mode).
+ gzdopen returns NULL if there was insufficient memory to allocate
+ the (de)compression state.
+*/
+
+int gzsetparams OF((gzFile file, int level, int strategy));
+/*
+ Dynamically update the compression level or strategy. See the description
+ of deflateInit2 for the meaning of these parameters.
+ gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not
+ opened for writing.
+*/
+
+int gzread OF((gzFile file, voidp buf, unsigned len));
+/*
+ Reads the given number of uncompressed bytes from the compressed file.
+ If the input file was not in gzip format, gzread copies the given number
+ of bytes into the buffer.
+ gzread returns the number of uncompressed bytes actually read (0 for
+ end of file, -1 for error). */
+
+int gzwrite OF((gzFile file,
+ const voidp buf, unsigned len));
+/*
+ Writes the given number of uncompressed bytes into the compressed file.
+ gzwrite returns the number of uncompressed bytes actually written
+ (0 in case of error).
+*/
+
+int QDECL gzprintf OF((gzFile file, const char *format, ...));
+/*
+ Converts, formats, and writes the args to the compressed file under
+ control of the format string, as in fprintf. gzprintf returns the number of
+ uncompressed bytes actually written (0 in case of error).
+*/
+
+int gzputs OF((gzFile file, const char *s));
+/*
+ Writes the given null-terminated string to the compressed file, excluding
+ the terminating null character.
+ gzputs returns the number of characters written, or -1 in case of error.
+*/
+
+char * gzgets OF((gzFile file, char *buf, int len));
+/*
+ Reads bytes from the compressed file until len-1 characters are read, or
+ a newline character is read and transferred to buf, or an end-of-file
+ condition is encountered. The string is then terminated with a null
+ character.
+ gzgets returns buf, or Z_NULL in case of error.
+*/
+
+int gzputc OF((gzFile file, int c));
+/*
+ Writes c, converted to an unsigned char, into the compressed file.
+ gzputc returns the value that was written, or -1 in case of error.
+*/
+
+int gzgetc OF((gzFile file));
+/*
+ Reads one byte from the compressed file. gzgetc returns this byte
+ or -1 in case of end of file or error.
+*/
+
+int gzflush OF((gzFile file, int flush));
+/*
+ Flushes all pending output into the compressed file. The parameter
+ flush is as in the deflate() function. The return value is the zlib
+ error number (see function gzerror below). gzflush returns Z_OK if
+ the flush parameter is Z_FINISH and all output could be flushed.
+ gzflush should be called only when strictly necessary because it can
+ degrade compression.
+*/
+
+long gzseek OF((gzFile file,
+ long offset, int whence));
+/*
+ Sets the starting position for the next gzread or gzwrite on the
+ given compressed file. The offset represents a number of bytes in the
+ uncompressed data stream. The whence parameter is defined as in lseek(2);
+ the value SEEK_END is not supported.
+ If the file is opened for reading, this function is emulated but can be
+ extremely slow. If the file is opened for writing, only forward seeks are
+ supported; gzseek then compresses a sequence of zeroes up to the new
+ starting position.
+
+ gzseek returns the resulting offset location as measured in bytes from
+ the beginning of the uncompressed stream, or -1 in case of error, in
+ particular if the file is opened for writing and the new starting position
+ would be before the current position.
+*/
+
+int gzrewind OF((gzFile file));
+/*
+ Rewinds the given file. This function is supported only for reading.
+
+ gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET)
+*/
+
+long gztell OF((gzFile file));
+/*
+ Returns the starting position for the next gzread or gzwrite on the
+ given compressed file. This position represents a number of bytes in the
+ uncompressed data stream.
+
+ gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR)
+*/
+
+int gzeof OF((gzFile file));
+/*
+ Returns 1 when EOF has previously been detected reading the given
+ input stream, otherwise zero.
+*/
+
+int gzclose OF((gzFile file));
+/*
+ Flushes all pending output if necessary, closes the compressed file
+ and deallocates all the (de)compression state. The return value is the zlib
+ error number (see function gzerror below).
+*/
+
+// static const char * gzerror OF((gzFile file, int *errnum));
+/*
+ Returns the error message for the last error which occurred on the
+ given compressed file. errnum is set to zlib error number. If an
+ error occurred in the file system and not in the compression library,
+ errnum is set to Z_ERRNO and the application may consult errno
+ to get the exact error code.
+*/
+
+ /* checksum functions */
+
+/*
+ These functions are not related to compression but are exported
+ anyway because they might be useful in applications using the
+ compression library.
+*/
+
+static uLong adler32 OF((uLong adler, const Byte *buf, uInt len));
+
+/*
+ Update a running Adler-32 checksum with the bytes buf[0..len-1] and
+ return the updated checksum. If buf is NULL, this function returns
+ the required initial value for the checksum.
+ An Adler-32 checksum is almost as reliable as a CRC32 but can be computed
+ much faster. Usage example:
+
+ uLong adler = adler32(0L, Z_NULL, 0);
+
+ while (read_buffer(buffer, length) != EOF) {
+ adler = adler32(adler, buffer, length);
+ }
+ if (adler != original_adler) error();
+*/
+
+ /* various hacks, don't look :) */
+
+/* deflateInit and inflateInit are macros to allow checking the zlib version
+ * and the compiler's view of z_stream:
+ */
+/*
+static int deflateInit_ OF((z_streamp strm, int level,
+ const char *version, int stream_size));
+static int inflateInit_ OF((z_streamp strm,
+ const char *version, int stream_size));
+static int deflateInit2_ OF((z_streamp strm, int level, int method,
+ int windowBits, int memLevel,
+ int strategy, const char *version,
+ int stream_size));
+*/
+static int inflateInit2_ OF((z_streamp strm, int windowBits,
+ const char *version, int stream_size));
+
+#define deflateInit(strm, level) \
+ deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream))
+#define inflateInit(strm) \
+ inflateInit_((strm), ZLIB_VERSION, sizeof(z_stream))
+#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
+ deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\
+ (strategy), ZLIB_VERSION, sizeof(z_stream))
+#define inflateInit2(strm, windowBits) \
+ inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
+
+
+// static const char * zError OF((int err));
+// static int inflateSyncPoint OF((z_streamp z));
+// static const uLong * get_crc_table OF((void));
+
+typedef unsigned char uch;
+typedef unsigned short ush;
+typedef unsigned long ulg;
+
+// static const char *z_errmsg[10]; /* indexed by 2-zlib_error */
+/* (size given to avoid silly warnings with Visual C++) */
+
+#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)]
+
+#define ERR_RETURN(strm,err) \
+ return (strm->msg = (char*)ERR_MSG(err), (err))
+/* To be used only when the state is known to be valid */
+
+ /* common constants */
+
+#ifndef DEF_WBITS
+# define DEF_WBITS MAX_WBITS
+#endif
+/* default windowBits for decompression. MAX_WBITS is for compression only */
+
+#if MAX_MEM_LEVEL >= 8
+# define DEF_MEM_LEVEL 8
+#else
+# define DEF_MEM_LEVEL MAX_MEM_LEVEL
+#endif
+/* default memLevel */
+
+#define STORED_BLOCK 0
+#define STATIC_TREES 1
+#define DYN_TREES 2
+/* The three kinds of block type */
+
+#define MIN_MATCH 3
+#define MAX_MATCH 258
+/* The minimum and maximum match lengths */
+
+#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */
+
+ /* target dependencies */
+
+ /* Common defaults */
+
+#ifndef OS_CODE
+# define OS_CODE 0x03 /* assume Unix */
+#endif
+
+#ifndef F_OPEN
+# define F_OPEN(name, mode) fopen((name), (mode))
+#endif
+
+ /* functions */
+
+#ifdef HAVE_STRERROR
+ extern char *strerror OF((int));
+# define zstrerror(errnum) strerror(errnum)
+#else
+# define zstrerror(errnum) ""
+#endif
+
+#define zmemcpy Com_Memcpy
+#define zmemcmp memcmp
+#define zmemzero(dest, len) Com_Memset(dest, 0, len)
+
+/* Diagnostic functions */
+#ifdef _ZIP_DEBUG_
+ int z_verbose = 0;
+# define Assert(cond,msg) assert(cond);
+ //{if(!(cond)) Sys_Error(msg);}
+# define Trace(x) {if (z_verbose>=0) Sys_Error x ;}
+# define Tracev(x) {if (z_verbose>0) Sys_Error x ;}
+# define Tracevv(x) {if (z_verbose>1) Sys_Error x ;}
+# define Tracec(c,x) {if (z_verbose>0 && (c)) Sys_Error x ;}
+# define Tracecv(c,x) {if (z_verbose>1 && (c)) Sys_Error x ;}
+#else
+# define Assert(cond,msg)
+# define Trace(x)
+# define Tracev(x)
+# define Tracevv(x)
+# define Tracec(c,x)
+# define Tracecv(c,x)
+#endif
+
+
+typedef uLong (*check_func) OF((uLong check, const Byte *buf, uInt len));
+static voidp zcalloc OF((voidp opaque, unsigned items, unsigned size));
+static void zcfree OF((voidp opaque, voidp ptr));
+
+#define ZALLOC(strm, items, size) \
+ (*((strm)->zalloc))((strm)->opaque, (items), (size))
+#define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidp)(addr))
+#define TRY_FREE(s, p) {if (p) ZFREE(s, p);}
+
+
+#if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) && \
+ !defined(CASESENSITIVITYDEFAULT_NO)
+#define CASESENSITIVITYDEFAULT_NO
+#endif
+
+
+#ifndef UNZ_BUFSIZE
+#define UNZ_BUFSIZE (65536)
+#endif
+
+#ifndef UNZ_MAXFILENAMEINZIP
+#define UNZ_MAXFILENAMEINZIP (256)
+#endif
+
+#ifndef ALLOC
+# define ALLOC(size) (Z_Malloc(size))
+#endif
+#ifndef TRYFREE
+# define TRYFREE(p) {if (p) Z_Free(p);}
+#endif
+
+#define SIZECENTRALDIRITEM (0x2e)
+#define SIZEZIPLOCALHEADER (0x1e)
+
+
+
+/* ===========================================================================
+ Read a byte from a gz_stream; update next_in and avail_in. Return EOF
+ for end of file.
+ IN assertion: the stream s has been sucessfully opened for reading.
+*/
+
+/*
+static int unzlocal_getByte(FILE *fin,int *pi)
+{
+ unsigned char c;
+ int err = fread(&c, 1, 1, fin);
+ if (err==1)
+ {
+ *pi = (int)c;
+ return UNZ_OK;
+ }
+ else
+ {
+ if (ferror(fin))
+ return UNZ_ERRNO;
+ else
+ return UNZ_EOF;
+ }
+}
+*/
+
+/* ===========================================================================
+ Reads a long in LSB order from the given gz_stream. Sets
+*/
+static int unzlocal_getShort (FILE* fin, uLong *pX)
+{
+ short v;
+
+ fread( &v, sizeof(v), 1, fin );
+
+ *pX = LittleShort( v);
+ return UNZ_OK;
+
+/*
+ uLong x ;
+ int i;
+ int err;
+
+ err = unzlocal_getByte(fin,&i);
+ x = (uLong)i;
+
+ if (err==UNZ_OK)
+ err = unzlocal_getByte(fin,&i);
+ x += ((uLong)i)<<8;
+
+ if (err==UNZ_OK)
+ *pX = x;
+ else
+ *pX = 0;
+ return err;
+*/
+}
+
+static int unzlocal_getLong (FILE *fin, uLong *pX)
+{
+ int v;
+
+ fread( &v, sizeof(v), 1, fin );
+
+ *pX = LittleLong( v);
+ return UNZ_OK;
+
+/*
+ uLong x ;
+ int i;
+ int err;
+
+ err = unzlocal_getByte(fin,&i);
+ x = (uLong)i;
+
+ if (err==UNZ_OK)
+ err = unzlocal_getByte(fin,&i);
+ x += ((uLong)i)<<8;
+
+ if (err==UNZ_OK)
+ err = unzlocal_getByte(fin,&i);
+ x += ((uLong)i)<<16;
+
+ if (err==UNZ_OK)
+ err = unzlocal_getByte(fin,&i);
+ x += ((uLong)i)<<24;
+
+ if (err==UNZ_OK)
+ *pX = x;
+ else
+ *pX = 0;
+ return err;
+*/
+}
+
+
+/* My own strcmpi / strcasecmp */
+static int strcmpcasenosensitive_internal (const char* fileName1,const char* fileName2)
+{
+ for (;;)
+ {
+ char c1=*(fileName1++);
+ char c2=*(fileName2++);
+ if ((c1>='a') && (c1<='z'))
+ c1 -= 0x20;
+ if ((c2>='a') && (c2<='z'))
+ c2 -= 0x20;
+ if (c1=='\0')
+ return ((c2=='\0') ? 0 : -1);
+ if (c2=='\0')
+ return 1;
+ if (c1<c2)
+ return -1;
+ if (c1>c2)
+ return 1;
+ }
+}
+
+
+#ifdef CASESENSITIVITYDEFAULT_NO
+#define CASESENSITIVITYDEFAULTVALUE 2
+#else
+#define CASESENSITIVITYDEFAULTVALUE 1
+#endif
+
+#ifndef STRCMPCASENOSENTIVEFUNCTION
+#define STRCMPCASENOSENTIVEFUNCTION strcmpcasenosensitive_internal
+#endif
+
+/*
+ Compare two filename (fileName1,fileName2).
+ If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp)
+ If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi
+ or strcasecmp)
+ If iCaseSenisivity = 0, case sensitivity is defaut of your operating system
+ (like 1 on Unix, 2 on Windows)
+
+*/
+extern int unzStringFileNameCompare (const char* fileName1,const char* fileName2,int iCaseSensitivity)
+{
+ if (iCaseSensitivity==0)
+ iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE;
+
+ if (iCaseSensitivity==1)
+ return strcmp(fileName1,fileName2);
+
+ return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2);
+}
+
+#define BUFREADCOMMENT (0x400)
+
+/*
+ Locate the Central directory of a zipfile (at the end, just before
+ the global comment)
+*/
+extern uLong unzlocal_SearchCentralDir(FILE *fin)
+{
+ unsigned char* buf;
+ uLong uSizeFile;
+ uLong uBackRead;
+ uLong uMaxBack=0xffff; /* maximum size of global comment */
+ uLong uPosFound=0;
+
+ if (fseek(fin,0,SEEK_END) != 0)
+ return 0;
+
+
+ uSizeFile = ftell( fin );
+
+ if (uMaxBack>uSizeFile)
+ uMaxBack = uSizeFile;
+
+ buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4);
+ if (buf==NULL)
+ return 0;
+
+ uBackRead = 4;
+ while (uBackRead<uMaxBack)
+ {
+ uLong uReadSize,uReadPos ;
+ int i;
+ if (uBackRead+BUFREADCOMMENT>uMaxBack)
+ uBackRead = uMaxBack;
+ else
+ uBackRead+=BUFREADCOMMENT;
+ uReadPos = uSizeFile-uBackRead ;
+
+ uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ?
+ (BUFREADCOMMENT+4) : (uSizeFile-uReadPos);
+ if (fseek(fin,uReadPos,SEEK_SET)!=0)
+ break;
+
+ if (fread(buf,(uInt)uReadSize,1,fin)!=1)
+ break;
+
+ for (i=(int)uReadSize-3; (i--)>0;)
+ if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) &&
+ ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06))
+ {
+ uPosFound = uReadPos+i;
+ break;
+ }
+
+ if (uPosFound!=0)
+ break;
+ }
+ TRYFREE(buf);
+ return uPosFound;
+}
+
+extern unzFile unzReOpen (const char* path, unzFile file)
+{
+ unz_s *s;
+ FILE * fin;
+
+ fin=fopen(path,"rb");
+ if (fin==NULL)
+ return NULL;
+
+ s=(unz_s*)ALLOC(sizeof(unz_s));
+ Com_Memcpy(s, (unz_s*)file, sizeof(unz_s));
+
+ s->file = fin;
+ return (unzFile)s;
+}
+
+/*
+ Open a Zip file. path contain the full pathname (by example,
+ on a Windows NT computer "c:\\test\\zlib109.zip" or on an Unix computer
+ "zlib/zlib109.zip".
+ If the zipfile cannot be opened (file don't exist or in not valid), the
+ return value is NULL.
+ Else, the return value is a unzFile Handle, usable with other function
+ of this unzip package.
+*/
+extern unzFile unzOpen (const char* path)
+{
+ unz_s us;
+ unz_s *s;
+ uLong central_pos,uL;
+ FILE * fin ;
+
+ uLong number_disk; /* number of the current dist, used for
+ spaning ZIP, unsupported, always 0*/
+ uLong number_disk_with_CD; /* number the the disk with central dir, used
+ for spaning ZIP, unsupported, always 0*/
+ uLong number_entry_CD; /* total number of entries in
+ the central dir
+ (same than number_entry on nospan) */
+
+ int err=UNZ_OK;
+
+ fin=fopen(path,"rb");
+ if (fin==NULL)
+ return NULL;
+
+ central_pos = unzlocal_SearchCentralDir(fin);
+ if (central_pos==0)
+ err=UNZ_ERRNO;
+
+ if (fseek(fin,central_pos,SEEK_SET)!=0)
+ err=UNZ_ERRNO;
+
+ /* the signature, already checked */
+ if (unzlocal_getLong(fin,&uL)!=UNZ_OK)
+ err=UNZ_ERRNO;
+
+ /* number of this disk */
+ if (unzlocal_getShort(fin,&number_disk)!=UNZ_OK)
+ err=UNZ_ERRNO;
+
+ /* number of the disk with the start of the central directory */
+ if (unzlocal_getShort(fin,&number_disk_with_CD)!=UNZ_OK)
+ err=UNZ_ERRNO;
+
+ /* total number of entries in the central dir on this disk */
+ if (unzlocal_getShort(fin,&us.gi.number_entry)!=UNZ_OK)
+ err=UNZ_ERRNO;
+
+ /* total number of entries in the central dir */
+ if (unzlocal_getShort(fin,&number_entry_CD)!=UNZ_OK)
+ err=UNZ_ERRNO;
+
+ if ((number_entry_CD!=us.gi.number_entry) ||
+ (number_disk_with_CD!=0) ||
+ (number_disk!=0))
+ err=UNZ_BADZIPFILE;
+
+ /* size of the central directory */
+ if (unzlocal_getLong(fin,&us.size_central_dir)!=UNZ_OK)
+ err=UNZ_ERRNO;
+
+ /* offset of start of central directory with respect to the
+ starting disk number */
+ if (unzlocal_getLong(fin,&us.offset_central_dir)!=UNZ_OK)
+ err=UNZ_ERRNO;
+
+ /* zipfile comment length */
+ if (unzlocal_getShort(fin,&us.gi.size_comment)!=UNZ_OK)
+ err=UNZ_ERRNO;
+
+ if ((central_pos<us.offset_central_dir+us.size_central_dir) &&
+ (err==UNZ_OK))
+ err=UNZ_BADZIPFILE;
+
+ if (err!=UNZ_OK)
+ {
+ fclose(fin);
+ return NULL;
+ }
+
+ us.file=fin;
+ us.byte_before_the_zipfile = central_pos -
+ (us.offset_central_dir+us.size_central_dir);
+ us.central_pos = central_pos;
+ us.pfile_in_zip_read = NULL;
+
+
+ s=(unz_s*)ALLOC(sizeof(unz_s));
+ *s=us;
+// unzGoToFirstFile((unzFile)s);
+ return (unzFile)s;
+}
+
+
+/*
+ Close a ZipFile opened with unzipOpen.
+ If there is files inside the .Zip opened with unzipOpenCurrentFile (see later),
+ these files MUST be closed with unzipCloseCurrentFile before call unzipClose.
+ return UNZ_OK if there is no problem. */
+extern int unzClose (unzFile file)
+{
+ unz_s* s;
+ if (file==NULL)
+ return UNZ_PARAMERROR;
+ s=(unz_s*)file;
+
+ if (s->pfile_in_zip_read!=NULL)
+ unzCloseCurrentFile(file);
+
+ fclose(s->file);
+ TRYFREE(s);
+ return UNZ_OK;
+}
+
+
+/*
+ Write info about the ZipFile in the *pglobal_info structure.
+ No preparation of the structure is needed
+ return UNZ_OK if there is no problem. */
+extern int unzGetGlobalInfo (unzFile file,unz_global_info *pglobal_info)
+{
+ unz_s* s;
+ if (file==NULL)
+ return UNZ_PARAMERROR;
+ s=(unz_s*)file;
+ *pglobal_info=s->gi;
+ return UNZ_OK;
+}
+
+
+/*
+ Translate date/time from Dos format to tm_unz (readable more easilty)
+*/
+static void unzlocal_DosDateToTmuDate (uLong ulDosDate, tm_unz* ptm)
+{
+ uLong uDate;
+ uDate = (uLong)(ulDosDate>>16);
+ ptm->tm_mday = (uInt)(uDate&0x1f) ;
+ ptm->tm_mon = (uInt)((((uDate)&0x1E0)/0x20)-1) ;
+ ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980) ;
+
+ ptm->tm_hour = (uInt) ((ulDosDate &0xF800)/0x800);
+ ptm->tm_min = (uInt) ((ulDosDate&0x7E0)/0x20) ;
+ ptm->tm_sec = (uInt) (2*(ulDosDate&0x1f)) ;
+}
+
+/*
+ Get Info about the current file in the zipfile, with internal only info
+*/
+static int unzlocal_GetCurrentFileInfoInternal (unzFile file,
+ unz_file_info *pfile_info,
+ unz_file_info_internal
+ *pfile_info_internal,
+ char *szFileName,
+ uLong fileNameBufferSize,
+ void *extraField,
+ uLong extraFieldBufferSize,
+ char *szComment,
+ uLong commentBufferSize)
+{
+ unz_s* s;
+ unz_file_info file_info;
+ unz_file_info_internal file_info_internal;
+ int err=UNZ_OK;
+ uLong uMagic;
+ long lSeek=0;
+
+ if (file==NULL)
+ return UNZ_PARAMERROR;
+ s=(unz_s*)file;
+ if (fseek(s->file,s->pos_in_central_dir+s->byte_before_the_zipfile,SEEK_SET)!=0)
+ err=UNZ_ERRNO;
+
+
+ /* we check the magic */
+ if (err==UNZ_OK) {
+ if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK)
+ err=UNZ_ERRNO;
+ else if (uMagic!=0x02014b50)
+ err=UNZ_BADZIPFILE;
+ }
+ if (unzlocal_getShort(s->file,&file_info.version) != UNZ_OK)
+ err=UNZ_ERRNO;
+
+ if (unzlocal_getShort(s->file,&file_info.version_needed) != UNZ_OK)
+ err=UNZ_ERRNO;
+
+ if (unzlocal_getShort(s->file,&file_info.flag) != UNZ_OK)
+ err=UNZ_ERRNO;
+
+ if (unzlocal_getShort(s->file,&file_info.compression_method) != UNZ_OK)
+ err=UNZ_ERRNO;
+
+ if (unzlocal_getLong(s->file,&file_info.dosDate) != UNZ_OK)
+ err=UNZ_ERRNO;
+
+ unzlocal_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date);
+
+ if (unzlocal_getLong(s->file,&file_info.crc) != UNZ_OK)
+ err=UNZ_ERRNO;
+
+ if (unzlocal_getLong(s->file,&file_info.compressed_size) != UNZ_OK)
+ err=UNZ_ERRNO;
+
+ if (unzlocal_getLong(s->file,&file_info.uncompressed_size) != UNZ_OK)
+ err=UNZ_ERRNO;
+
+ if (unzlocal_getShort(s->file,&file_info.size_filename) != UNZ_OK)
+ err=UNZ_ERRNO;
+
+ if (unzlocal_getShort(s->file,&file_info.size_file_extra) != UNZ_OK)
+ err=UNZ_ERRNO;
+
+ if (unzlocal_getShort(s->file,&file_info.size_file_comment) != UNZ_OK)
+ err=UNZ_ERRNO;
+
+ if (unzlocal_getShort(s->file,&file_info.disk_num_start) != UNZ_OK)
+ err=UNZ_ERRNO;
+
+ if (unzlocal_getShort(s->file,&file_info.internal_fa) != UNZ_OK)
+ err=UNZ_ERRNO;
+
+ if (unzlocal_getLong(s->file,&file_info.external_fa) != UNZ_OK)
+ err=UNZ_ERRNO;
+
+ if (unzlocal_getLong(s->file,&file_info_internal.offset_curfile) != UNZ_OK)
+ err=UNZ_ERRNO;
+
+ lSeek+=file_info.size_filename;
+ if ((err==UNZ_OK) && (szFileName!=NULL))
+ {
+ uLong uSizeRead ;
+ if (file_info.size_filename<fileNameBufferSize)
+ {
+ *(szFileName+file_info.size_filename)='\0';
+ uSizeRead = file_info.size_filename;
+ }
+ else
+ uSizeRead = fileNameBufferSize;
+
+ if ((file_info.size_filename>0) && (fileNameBufferSize>0))
+ if (fread(szFileName,(uInt)uSizeRead,1,s->file)!=1)
+ err=UNZ_ERRNO;
+ lSeek -= uSizeRead;
+ }
+
+
+ if ((err==UNZ_OK) && (extraField!=NULL))
+ {
+ uLong uSizeRead ;
+ if (file_info.size_file_extra<extraFieldBufferSize)
+ uSizeRead = file_info.size_file_extra;
+ else
+ uSizeRead = extraFieldBufferSize;
+
+ if (lSeek!=0) {
+ if (fseek(s->file,lSeek,SEEK_CUR)==0)
+ lSeek=0;
+ else
+ err=UNZ_ERRNO;
+ }
+ if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0)) {
+ if (fread(extraField,(uInt)uSizeRead,1,s->file)!=1)
+ err=UNZ_ERRNO;
+ }
+ lSeek += file_info.size_file_extra - uSizeRead;
+ }
+ else
+ lSeek+=file_info.size_file_extra;
+
+
+ if ((err==UNZ_OK) && (szComment!=NULL))
+ {
+ uLong uSizeRead ;
+ if (file_info.size_file_comment<commentBufferSize)
+ {
+ *(szComment+file_info.size_file_comment)='\0';
+ uSizeRead = file_info.size_file_comment;
+ }
+ else
+ uSizeRead = commentBufferSize;
+
+ if (lSeek!=0) {
+ if (fseek(s->file,lSeek,SEEK_CUR)==0)
+ lSeek=0;
+ else
+ err=UNZ_ERRNO;
+ }
+ if ((file_info.size_file_comment>0) && (commentBufferSize>0)) {
+ if (fread(szComment,(uInt)uSizeRead,1,s->file)!=1)
+ err=UNZ_ERRNO;
+ }
+ lSeek+=file_info.size_file_comment - uSizeRead;
+ }
+ else
+ lSeek+=file_info.size_file_comment;
+
+ if ((err==UNZ_OK) && (pfile_info!=NULL))
+ *pfile_info=file_info;
+
+ if ((err==UNZ_OK) && (pfile_info_internal!=NULL))
+ *pfile_info_internal=file_info_internal;
+
+ return err;
+}
+
+
+
+/*
+ Write info about the ZipFile in the *pglobal_info structure.
+ No preparation of the structure is needed
+ return UNZ_OK if there is no problem.
+*/
+extern int unzGetCurrentFileInfo ( unzFile file, unz_file_info *pfile_info,
+ char *szFileName, uLong fileNameBufferSize,
+ void *extraField, uLong extraFieldBufferSize,
+ char *szComment, uLong commentBufferSize)
+{
+ return unzlocal_GetCurrentFileInfoInternal(file,pfile_info,NULL,
+ szFileName,fileNameBufferSize,
+ extraField,extraFieldBufferSize,
+ szComment,commentBufferSize);
+}
+
+/*
+ Set the current file of the zipfile to the first file.
+ return UNZ_OK if there is no problem
+*/
+extern int unzGoToFirstFile (unzFile file)
+{
+ int err=UNZ_OK;
+ unz_s* s;
+ if (file==NULL)
+ return UNZ_PARAMERROR;
+ s=(unz_s*)file;
+ s->pos_in_central_dir=s->offset_central_dir;
+ s->num_file=0;
+ err=unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info,
+ &s->cur_file_info_internal,
+ NULL,0,NULL,0,NULL,0);
+ s->current_file_ok = (err == UNZ_OK);
+ return err;
+}
+
+
+/*
+ Set the current file of the zipfile to the next file.
+ return UNZ_OK if there is no problem
+ return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest.
+*/
+extern int unzGoToNextFile (unzFile file)
+{
+ unz_s* s;
+ int err;
+
+ if (file==NULL)
+ return UNZ_PARAMERROR;
+ s=(unz_s*)file;
+ if (!s->current_file_ok)
+ return UNZ_END_OF_LIST_OF_FILE;
+ if (s->num_file+1==s->gi.number_entry)
+ return UNZ_END_OF_LIST_OF_FILE;
+
+ s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename +
+ s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment ;
+ s->num_file++;
+ err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info,
+ &s->cur_file_info_internal,
+ NULL,0,NULL,0,NULL,0);
+ s->current_file_ok = (err == UNZ_OK);
+ return err;
+}
+
+/*
+ Get the position of the info of the current file in the zip.
+ return UNZ_OK if there is no problem
+*/
+extern int unzGetCurrentFileInfoPosition (unzFile file, unsigned long *pos )
+{
+ unz_s* s;
+
+ if (file==NULL)
+ return UNZ_PARAMERROR;
+ s=(unz_s*)file;
+
+ *pos = s->pos_in_central_dir;
+ return UNZ_OK;
+}
+
+/*
+ Set the position of the info of the current file in the zip.
+ return UNZ_OK if there is no problem
+*/
+extern int unzSetCurrentFileInfoPosition (unzFile file, unsigned long pos )
+{
+ unz_s* s;
+ int err;
+
+ if (file==NULL)
+ return UNZ_PARAMERROR;
+ s=(unz_s*)file;
+
+ s->pos_in_central_dir = pos;
+ err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info,
+ &s->cur_file_info_internal,
+ NULL,0,NULL,0,NULL,0);
+ s->current_file_ok = (err == UNZ_OK);
+ return UNZ_OK;
+}
+
+/*
+ Try locate the file szFileName in the zipfile.
+ For the iCaseSensitivity signification, see unzipStringFileNameCompare
+
+ return value :
+ UNZ_OK if the file is found. It becomes the current file.
+ UNZ_END_OF_LIST_OF_FILE if the file is not found
+*/
+extern int unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity)
+{
+ unz_s* s;
+ int err;
+
+
+ uLong num_fileSaved;
+ uLong pos_in_central_dirSaved;
+
+
+ if (file==NULL)
+ return UNZ_PARAMERROR;
+
+ if (strlen(szFileName)>=UNZ_MAXFILENAMEINZIP)
+ return UNZ_PARAMERROR;
+
+ s=(unz_s*)file;
+ if (!s->current_file_ok)
+ return UNZ_END_OF_LIST_OF_FILE;
+
+ num_fileSaved = s->num_file;
+ pos_in_central_dirSaved = s->pos_in_central_dir;
+
+ err = unzGoToFirstFile(file);
+
+ while (err == UNZ_OK)
+ {
+ char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1];
+ unzGetCurrentFileInfo(file,NULL,
+ szCurrentFileName,sizeof(szCurrentFileName)-1,
+ NULL,0,NULL,0);
+ if (unzStringFileNameCompare(szCurrentFileName,
+ szFileName,iCaseSensitivity)==0)
+ return UNZ_OK;
+ err = unzGoToNextFile(file);
+ }
+
+ s->num_file = num_fileSaved ;
+ s->pos_in_central_dir = pos_in_central_dirSaved ;
+ return err;
+}
+
+
+/*
+ Read the static header of the current zipfile
+ Check the coherency of the static header and info in the end of central
+ directory about this file
+ store in *piSizeVar the size of extra info in static header
+ (filename and size of extra field data)
+*/
+static int unzlocal_CheckCurrentFileCoherencyHeader (unz_s* s, uInt* piSizeVar,
+ uLong *poffset_local_extrafield,
+ uInt *psize_local_extrafield)
+{
+ uLong uMagic,uData,uFlags;
+ uLong size_filename;
+ uLong size_extra_field;
+ int err=UNZ_OK;
+
+ *piSizeVar = 0;
+ *poffset_local_extrafield = 0;
+ *psize_local_extrafield = 0;
+
+ if (fseek(s->file,s->cur_file_info_internal.offset_curfile +
+ s->byte_before_the_zipfile,SEEK_SET)!=0)
+ return UNZ_ERRNO;
+
+
+ if (err==UNZ_OK) {
+ if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK)
+ err=UNZ_ERRNO;
+ else if (uMagic!=0x04034b50)
+ err=UNZ_BADZIPFILE;
+ }
+ if (unzlocal_getShort(s->file,&uData) != UNZ_OK)
+ err=UNZ_ERRNO;
+/*
+ else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion))
+ err=UNZ_BADZIPFILE;
+*/
+ if (unzlocal_getShort(s->file,&uFlags) != UNZ_OK)
+ err=UNZ_ERRNO;
+
+ if (unzlocal_getShort(s->file,&uData) != UNZ_OK)
+ err=UNZ_ERRNO;
+ else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compression_method))
+ err=UNZ_BADZIPFILE;
+
+ if ((err==UNZ_OK) && (s->cur_file_info.compression_method!=0) &&
+ (s->cur_file_info.compression_method!=Z_DEFLATED))
+ err=UNZ_BADZIPFILE;
+
+ if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* date/time */
+ err=UNZ_ERRNO;
+
+ if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* crc */
+ err=UNZ_ERRNO;
+ else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) &&
+ ((uFlags & 8)==0))
+ err=UNZ_BADZIPFILE;
+
+ if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* size compr */
+ err=UNZ_ERRNO;
+ else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compressed_size) &&
+ ((uFlags & 8)==0))
+ err=UNZ_BADZIPFILE;
+
+ if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* size uncompr */
+ err=UNZ_ERRNO;
+ else if ((err==UNZ_OK) && (uData!=s->cur_file_info.uncompressed_size) &&
+ ((uFlags & 8)==0))
+ err=UNZ_BADZIPFILE;
+
+
+ if (unzlocal_getShort(s->file,&size_filename) != UNZ_OK)
+ err=UNZ_ERRNO;
+ else if ((err==UNZ_OK) && (size_filename!=s->cur_file_info.size_filename))
+ err=UNZ_BADZIPFILE;
+
+ *piSizeVar += (uInt)size_filename;
+
+ if (unzlocal_getShort(s->file,&size_extra_field) != UNZ_OK)
+ err=UNZ_ERRNO;
+ *poffset_local_extrafield= s->cur_file_info_internal.offset_curfile +
+ SIZEZIPLOCALHEADER + size_filename;
+ *psize_local_extrafield = (uInt)size_extra_field;
+
+ *piSizeVar += (uInt)size_extra_field;
+
+ return err;
+}
+
+/*
+ Open for reading data the current file in the zipfile.
+ If there is no error and the file is opened, the return value is UNZ_OK.
+*/
+extern int unzOpenCurrentFile (unzFile file)
+{
+ int err=UNZ_OK;
+ int Store;
+ uInt iSizeVar;
+ unz_s* s;
+ file_in_zip_read_info_s* pfile_in_zip_read_info;
+ uLong offset_local_extrafield; /* offset of the static extra field */
+ uInt size_local_extrafield; /* size of the static extra field */
+
+ if (file==NULL)
+ return UNZ_PARAMERROR;
+ s=(unz_s*)file;
+ if (!s->current_file_ok)
+ return UNZ_PARAMERROR;
+
+ if (s->pfile_in_zip_read != NULL)
+ unzCloseCurrentFile(file);
+
+ if (unzlocal_CheckCurrentFileCoherencyHeader(s,&iSizeVar,
+ &offset_local_extrafield,&size_local_extrafield)!=UNZ_OK)
+ return UNZ_BADZIPFILE;
+
+ pfile_in_zip_read_info = (file_in_zip_read_info_s*)
+ ALLOC(sizeof(file_in_zip_read_info_s));
+ if (pfile_in_zip_read_info==NULL)
+ return UNZ_INTERNALERROR;
+
+ pfile_in_zip_read_info->read_buffer=(char*)ALLOC(UNZ_BUFSIZE);
+ pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield;
+ pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield;
+ pfile_in_zip_read_info->pos_local_extrafield=0;
+
+ if (pfile_in_zip_read_info->read_buffer==NULL)
+ {
+ TRYFREE(pfile_in_zip_read_info);
+ return UNZ_INTERNALERROR;
+ }
+
+ pfile_in_zip_read_info->stream_initialised=0;
+
+ if ((s->cur_file_info.compression_method!=0) &&
+ (s->cur_file_info.compression_method!=Z_DEFLATED))
+ err=UNZ_BADZIPFILE;
+ Store = s->cur_file_info.compression_method==0;
+
+ pfile_in_zip_read_info->crc32_wait=s->cur_file_info.crc;
+ pfile_in_zip_read_info->crc32=0;
+ pfile_in_zip_read_info->compression_method =
+ s->cur_file_info.compression_method;
+ pfile_in_zip_read_info->file=s->file;
+ pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile;
+
+ pfile_in_zip_read_info->stream.total_out = 0;
+
+ if (!Store)
+ {
+ pfile_in_zip_read_info->stream.zalloc = (alloc_func)0;
+ pfile_in_zip_read_info->stream.zfree = (free_func)0;
+ pfile_in_zip_read_info->stream.opaque = (voidp)0;
+
+ err=inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS);
+ if (err == Z_OK)
+ pfile_in_zip_read_info->stream_initialised=1;
+ /* windowBits is passed < 0 to tell that there is no zlib header.
+ * Note that in this case inflate *requires* an extra "dummy" byte
+ * after the compressed stream in order to complete decompression and
+ * return Z_STREAM_END.
+ * In unzip, i don't wait absolutely Z_STREAM_END because I known the
+ * size of both compressed and uncompressed data
+ */
+ }
+ pfile_in_zip_read_info->rest_read_compressed =
+ s->cur_file_info.compressed_size ;
+ pfile_in_zip_read_info->rest_read_uncompressed =
+ s->cur_file_info.uncompressed_size ;
+
+
+ pfile_in_zip_read_info->pos_in_zipfile =
+ s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER +
+ iSizeVar;
+
+ pfile_in_zip_read_info->stream.avail_in = (uInt)0;
+
+
+ s->pfile_in_zip_read = pfile_in_zip_read_info;
+ return UNZ_OK;
+}
+
+
+/*
+ Read bytes from the current file.
+ buf contain buffer where data must be copied
+ len the size of buf.
+
+ return the number of byte copied if somes bytes are copied
+ return 0 if the end of file was reached
+ return <0 with error code if there is an error
+ (UNZ_ERRNO for IO error, or zLib error for uncompress error)
+*/
+extern int unzReadCurrentFile (unzFile file, void *buf, unsigned len)
+{
+ int err=UNZ_OK;
+ uInt iRead = 0;
+ unz_s* s;
+ file_in_zip_read_info_s* pfile_in_zip_read_info;
+ if (file==NULL)
+ return UNZ_PARAMERROR;
+ s=(unz_s*)file;
+ pfile_in_zip_read_info=s->pfile_in_zip_read;
+
+ if (pfile_in_zip_read_info==NULL)
+ return UNZ_PARAMERROR;
+
+
+ if ((pfile_in_zip_read_info->read_buffer == NULL))
+ return UNZ_END_OF_LIST_OF_FILE;
+ if (len==0)
+ return 0;
+
+ pfile_in_zip_read_info->stream.next_out = (Byte*)buf;
+
+ pfile_in_zip_read_info->stream.avail_out = (uInt)len;
+
+ if (len>pfile_in_zip_read_info->rest_read_uncompressed)
+ pfile_in_zip_read_info->stream.avail_out =
+ (uInt)pfile_in_zip_read_info->rest_read_uncompressed;
+
+ while (pfile_in_zip_read_info->stream.avail_out>0)
+ {
+ if ((pfile_in_zip_read_info->stream.avail_in==0) &&
+ (pfile_in_zip_read_info->rest_read_compressed>0))
+ {
+ uInt uReadThis = UNZ_BUFSIZE;
+ if (pfile_in_zip_read_info->rest_read_compressed<uReadThis)
+ uReadThis = (uInt)pfile_in_zip_read_info->rest_read_compressed;
+ if (uReadThis == 0)
+ return UNZ_EOF;
+ if (s->cur_file_info.compressed_size == pfile_in_zip_read_info->rest_read_compressed)
+ if (fseek(pfile_in_zip_read_info->file,
+ pfile_in_zip_read_info->pos_in_zipfile +
+ pfile_in_zip_read_info->byte_before_the_zipfile,SEEK_SET)!=0)
+ return UNZ_ERRNO;
+ if (fread(pfile_in_zip_read_info->read_buffer,uReadThis,1,
+ pfile_in_zip_read_info->file)!=1)
+ return UNZ_ERRNO;
+ pfile_in_zip_read_info->pos_in_zipfile += uReadThis;
+
+ pfile_in_zip_read_info->rest_read_compressed-=uReadThis;
+
+ pfile_in_zip_read_info->stream.next_in =
+ (Byte*)pfile_in_zip_read_info->read_buffer;
+ pfile_in_zip_read_info->stream.avail_in = (uInt)uReadThis;
+ }
+
+ if (pfile_in_zip_read_info->compression_method==0)
+ {
+ uInt uDoCopy,i ;
+ if (pfile_in_zip_read_info->stream.avail_out <
+ pfile_in_zip_read_info->stream.avail_in)
+ uDoCopy = pfile_in_zip_read_info->stream.avail_out ;
+ else
+ uDoCopy = pfile_in_zip_read_info->stream.avail_in ;
+
+ for (i=0;i<uDoCopy;i++)
+ *(pfile_in_zip_read_info->stream.next_out+i) =
+ *(pfile_in_zip_read_info->stream.next_in+i);
+
+// pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32,
+// pfile_in_zip_read_info->stream.next_out,
+// uDoCopy);
+ pfile_in_zip_read_info->rest_read_uncompressed-=uDoCopy;
+ pfile_in_zip_read_info->stream.avail_in -= uDoCopy;
+ pfile_in_zip_read_info->stream.avail_out -= uDoCopy;
+ pfile_in_zip_read_info->stream.next_out += uDoCopy;
+ pfile_in_zip_read_info->stream.next_in += uDoCopy;
+ pfile_in_zip_read_info->stream.total_out += uDoCopy;
+ iRead += uDoCopy;
+ }
+ else
+ {
+ uLong uTotalOutBefore,uTotalOutAfter;
+ const Byte *bufBefore;
+ uLong uOutThis;
+ int flush=Z_SYNC_FLUSH;
+
+ uTotalOutBefore = pfile_in_zip_read_info->stream.total_out;
+ bufBefore = pfile_in_zip_read_info->stream.next_out;
+
+ /*
+ if ((pfile_in_zip_read_info->rest_read_uncompressed ==
+ pfile_in_zip_read_info->stream.avail_out) &&
+ (pfile_in_zip_read_info->rest_read_compressed == 0))
+ flush = Z_FINISH;
+ */
+ err=inflate(&pfile_in_zip_read_info->stream,flush);
+
+ uTotalOutAfter = pfile_in_zip_read_info->stream.total_out;
+ uOutThis = uTotalOutAfter-uTotalOutBefore;
+
+// pfile_in_zip_read_info->crc32 =
+// crc32(pfile_in_zip_read_info->crc32,bufBefore,
+// (uInt)(uOutThis));
+
+ pfile_in_zip_read_info->rest_read_uncompressed -=
+ uOutThis;
+
+ iRead += (uInt)(uTotalOutAfter - uTotalOutBefore);
+
+ if (err==Z_STREAM_END)
+ return (iRead==0) ? UNZ_EOF : iRead;
+ if (err!=Z_OK)
+ break;
+ }
+ }
+
+ if (err==Z_OK)
+ return iRead;
+ return err;
+}
+
+
+/*
+ Give the current position in uncompressed data
+*/
+extern long unztell (unzFile file)
+{
+ unz_s* s;
+ file_in_zip_read_info_s* pfile_in_zip_read_info;
+ if (file==NULL)
+ return UNZ_PARAMERROR;
+ s=(unz_s*)file;
+ pfile_in_zip_read_info=s->pfile_in_zip_read;
+
+ if (pfile_in_zip_read_info==NULL)
+ return UNZ_PARAMERROR;
+
+ return (long)pfile_in_zip_read_info->stream.total_out;
+}
+
+
+/*
+ return 1 if the end of file was reached, 0 elsewhere
+*/
+extern int unzeof (unzFile file)
+{
+ unz_s* s;
+ file_in_zip_read_info_s* pfile_in_zip_read_info;
+ if (file==NULL)
+ return UNZ_PARAMERROR;
+ s=(unz_s*)file;
+ pfile_in_zip_read_info=s->pfile_in_zip_read;
+
+ if (pfile_in_zip_read_info==NULL)
+ return UNZ_PARAMERROR;
+
+ if (pfile_in_zip_read_info->rest_read_uncompressed == 0)
+ return 1;
+ else
+ return 0;
+}
+
+
+
+/*
+ Read extra field from the current file (opened by unzOpenCurrentFile)
+ This is the static-header version of the extra field (sometimes, there is
+ more info in the static-header version than in the central-header)
+
+ if buf==NULL, it return the size of the static extra field that can be read
+
+ if buf!=NULL, len is the size of the buffer, the extra header is copied in
+ buf.
+ the return value is the number of bytes copied in buf, or (if <0)
+ the error code
+*/
+extern int unzGetLocalExtrafield (unzFile file,void *buf,unsigned len)
+{
+ unz_s* s;
+ file_in_zip_read_info_s* pfile_in_zip_read_info;
+ uInt read_now;
+ uLong size_to_read;
+
+ if (file==NULL)
+ return UNZ_PARAMERROR;
+ s=(unz_s*)file;
+ pfile_in_zip_read_info=s->pfile_in_zip_read;
+
+ if (pfile_in_zip_read_info==NULL)
+ return UNZ_PARAMERROR;
+
+ size_to_read = (pfile_in_zip_read_info->size_local_extrafield -
+ pfile_in_zip_read_info->pos_local_extrafield);
+
+ if (buf==NULL)
+ return (int)size_to_read;
+
+ if (len>size_to_read)
+ read_now = (uInt)size_to_read;
+ else
+ read_now = (uInt)len ;
+
+ if (read_now==0)
+ return 0;
+
+ if (fseek(pfile_in_zip_read_info->file,
+ pfile_in_zip_read_info->offset_local_extrafield +
+ pfile_in_zip_read_info->pos_local_extrafield,SEEK_SET)!=0)
+ return UNZ_ERRNO;
+
+ if (fread(buf,(uInt)size_to_read,1,pfile_in_zip_read_info->file)!=1)
+ return UNZ_ERRNO;
+
+ return (int)read_now;
+}
+
+/*
+ Close the file in zip opened with unzipOpenCurrentFile
+ Return UNZ_CRCERROR if all the file was read but the CRC is not good
+*/
+extern int unzCloseCurrentFile (unzFile file)
+{
+ int err=UNZ_OK;
+
+ unz_s* s;
+ file_in_zip_read_info_s* pfile_in_zip_read_info;
+ if (file==NULL)
+ return UNZ_PARAMERROR;
+ s=(unz_s*)file;
+ pfile_in_zip_read_info=s->pfile_in_zip_read;
+
+ if (pfile_in_zip_read_info==NULL)
+ return UNZ_PARAMERROR;
+
+/*
+ if (pfile_in_zip_read_info->rest_read_uncompressed == 0)
+ {
+ if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait)
+ err=UNZ_CRCERROR;
+ }
+*/
+
+ TRYFREE(pfile_in_zip_read_info->read_buffer);
+ pfile_in_zip_read_info->read_buffer = NULL;
+ if (pfile_in_zip_read_info->stream_initialised)
+ inflateEnd(&pfile_in_zip_read_info->stream);
+
+ pfile_in_zip_read_info->stream_initialised = 0;
+ TRYFREE(pfile_in_zip_read_info);
+
+ s->pfile_in_zip_read=NULL;
+
+ return err;
+}
+
+
+/*
+ Get the global comment string of the ZipFile, in the szComment buffer.
+ uSizeBuf is the size of the szComment buffer.
+ return the number of byte copied or an error code <0
+*/
+extern int unzGetGlobalComment (unzFile file, char *szComment, uLong uSizeBuf)
+{
+ unz_s* s;
+ uLong uReadThis ;
+ if (file==NULL)
+ return UNZ_PARAMERROR;
+ s=(unz_s*)file;
+
+ uReadThis = uSizeBuf;
+ if (uReadThis>s->gi.size_comment)
+ uReadThis = s->gi.size_comment;
+
+ if (fseek(s->file,s->central_pos+22,SEEK_SET)!=0)
+ return UNZ_ERRNO;
+
+ if (uReadThis>0)
+ {
+ *szComment='\0';
+ if (fread(szComment,(uInt)uReadThis,1,s->file)!=1)
+ return UNZ_ERRNO;
+ }
+
+ if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment))
+ *(szComment+s->gi.size_comment)='\0';
+ return (int)uReadThis;
+}
+
+/* infblock.h -- header to use infblock.c
+ * Copyright (C) 1995-1998 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+ part of the implementation of the compression library and is
+ subject to change. Applications should only use zlib.h.
+ */
+
+struct inflate_blocks_state;
+typedef struct inflate_blocks_state inflate_blocks_statef;
+
+static inflate_blocks_statef * inflate_blocks_new OF((
+ z_streamp z,
+ check_func c, /* check function */
+ uInt w)); /* window size */
+
+static int inflate_blocks OF((
+ inflate_blocks_statef *,
+ z_streamp ,
+ int)); /* initial return code */
+
+static void inflate_blocks_reset OF((
+ inflate_blocks_statef *,
+ z_streamp ,
+ uLong *)); /* check value on output */
+
+static int inflate_blocks_free OF((
+ inflate_blocks_statef *,
+ z_streamp));
+
+#if 0
+static void inflate_set_dictionary OF((
+ inflate_blocks_statef *s,
+ const Byte *d, /* dictionary */
+ uInt n)); /* dictionary length */
+
+static int inflate_blocks_sync_point OF((
+ inflate_blocks_statef *s));
+#endif
+
+/* simplify the use of the inflate_huft type with some defines */
+#define exop word.what.Exop
+#define bits word.what.Bits
+
+/* Table for deflate from PKZIP's appnote.txt. */
+static const uInt border[] = { /* Order of the bit length code lengths */
+ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+
+/* inftrees.h -- header to use inftrees.c
+ * Copyright (C) 1995-1998 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+ part of the implementation of the compression library and is
+ subject to change. Applications should only use zlib.h.
+ */
+
+/* Huffman code lookup table entry--this entry is four bytes for machines
+ that have 16-bit pointers (e.g. PC's in the small or medium model). */
+
+typedef struct inflate_huft_s inflate_huft;
+
+struct inflate_huft_s {
+ union {
+ struct {
+ Byte Exop; /* number of extra bits or operation */
+ Byte Bits; /* number of bits in this code or subcode */
+ } what;
+ uInt pad; /* pad structure to a power of 2 (4 bytes for */
+ } word; /* 16-bit, 8 bytes for 32-bit int's) */
+ uInt base; /* literal, length base, distance base,
+ or table offset */
+};
+
+/* Maximum size of dynamic tree. The maximum found in a long but non-
+ exhaustive search was 1004 huft structures (850 for length/literals
+ and 154 for distances, the latter actually the result of an
+ exhaustive search). The actual maximum is not known, but the
+ value below is more than safe. */
+#define MANY 1440
+
+static int inflate_trees_bits OF((
+ uInt *, /* 19 code lengths */
+ uInt *, /* bits tree desired/actual depth */
+ inflate_huft * *, /* bits tree result */
+ inflate_huft *, /* space for trees */
+ z_streamp)); /* for messages */
+
+static int inflate_trees_dynamic OF((
+ uInt, /* number of literal/length codes */
+ uInt, /* number of distance codes */
+ uInt *, /* that many (total) code lengths */
+ uInt *, /* literal desired/actual bit depth */
+ uInt *, /* distance desired/actual bit depth */
+ inflate_huft * *, /* literal/length tree result */
+ inflate_huft * *, /* distance tree result */
+ inflate_huft *, /* space for trees */
+ z_streamp)); /* for messages */
+
+static int inflate_trees_fixed OF((
+ uInt *, /* literal desired/actual bit depth */
+ uInt *, /* distance desired/actual bit depth */
+ inflate_huft * *, /* literal/length tree result */
+ inflate_huft * *, /* distance tree result */
+ z_streamp)); /* for memory allocation */
+
+
+/* infcodes.h -- header to use infcodes.c
+ * Copyright (C) 1995-1998 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+ part of the implementation of the compression library and is
+ subject to change. Applications should only use zlib.h.
+ */
+
+struct inflate_codes_state;
+typedef struct inflate_codes_state inflate_codes_statef;
+
+static inflate_codes_statef *inflate_codes_new OF((
+ uInt, uInt,
+ inflate_huft *, inflate_huft *,
+ z_streamp ));
+
+static int inflate_codes OF((
+ inflate_blocks_statef *,
+ z_streamp ,
+ int));
+
+static void inflate_codes_free OF((
+ inflate_codes_statef *,
+ z_streamp ));
+
+/* infutil.h -- types and macros common to blocks and codes
+ * Copyright (C) 1995-1998 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+ part of the implementation of the compression library and is
+ subject to change. Applications should only use zlib.h.
+ */
+
+#ifndef _INFUTIL_H
+#define _INFUTIL_H
+
+typedef enum {
+ TYPE, /* get type bits (3, including end bit) */
+ LENS, /* get lengths for stored */
+ STORED, /* processing stored block */
+ TABLE, /* get table lengths */
+ BTREE, /* get bit lengths tree for a dynamic block */
+ DTREE, /* get length, distance trees for a dynamic block */
+ CODES, /* processing fixed or dynamic block */
+ DRY, /* output remaining window bytes */
+ DONE, /* finished last block, done */
+ BAD} /* got a data error--stuck here */
+inflate_block_mode;
+
+/* inflate blocks semi-private state */
+struct inflate_blocks_state {
+
+ /* mode */
+ inflate_block_mode mode; /* current inflate_block mode */
+
+ /* mode dependent information */
+ union {
+ uInt left; /* if STORED, bytes left to copy */
+ struct {
+ uInt table; /* table lengths (14 bits) */
+ uInt index; /* index into blens (or border) */
+ uInt *blens; /* bit lengths of codes */
+ uInt bb; /* bit length tree depth */
+ inflate_huft *tb; /* bit length decoding tree */
+ } trees; /* if DTREE, decoding info for trees */
+ struct {
+ inflate_codes_statef
+ *codes;
+ } decode; /* if CODES, current state */
+ } sub; /* submode */
+ uInt last; /* true if this block is the last block */
+
+ /* mode independent information */
+ uInt bitk; /* bits in bit buffer */
+ uLong bitb; /* bit buffer */
+ inflate_huft *hufts; /* single malloc for tree space */
+ Byte *window; /* sliding window */
+ Byte *end; /* one byte after sliding window */
+ Byte *read; /* window read pointer */
+ Byte *write; /* window write pointer */
+ check_func checkfn; /* check function */
+ uLong check; /* check on output */
+
+};
+
+
+/* defines for inflate input/output */
+/* update pointers and return */
+#define UPDBITS {s->bitb=b;s->bitk=k;}
+#define UPDIN {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;}
+#define UPDOUT {s->write=q;}
+#define UPDATE {UPDBITS UPDIN UPDOUT}
+#define LEAVE {UPDATE return inflate_flush(s,z,r);}
+/* get bytes and bits */
+#define LOADIN {p=z->next_in;n=z->avail_in;b=s->bitb;k=s->bitk;}
+#define NEEDBYTE {if(n)r=Z_OK;else LEAVE}
+#define NEXTBYTE (n--,*p++)
+#define NEEDBITS(j) {while(k<(j)){NEEDBYTE;b|=((uLong)NEXTBYTE)<<k;k+=8;}}
+#define DUMPBITS(j) {b>>=(j);k-=(j);}
+/* output bytes */
+#define WAVAIL (uInt)(q<s->read?s->read-q-1:s->end-q)
+#define LOADOUT {q=s->write;m=(uInt)WAVAIL;}
+#define WRAP {if(q==s->end&&s->read!=s->window){q=s->window;m=(uInt)WAVAIL;}}
+#define FLUSH {UPDOUT r=inflate_flush(s,z,r); LOADOUT}
+#define NEEDOUT {if(m==0){WRAP if(m==0){FLUSH WRAP if(m==0) LEAVE}}r=Z_OK;}
+#define OUTBYTE(a) {*q++=(Byte)(a);m--;}
+/* load static pointers */
+#define LOAD {LOADIN LOADOUT}
+
+/* masks for lower bits (size given to avoid silly warnings with Visual C++) */
+static uInt inflate_mask[17];
+
+/* copy as much as possible from the sliding window to the output area */
+static int inflate_flush OF((
+ inflate_blocks_statef *,
+ z_streamp ,
+ int));
+
+#endif
+
+
+/*
+ Notes beyond the 1.93a appnote.txt:
+
+ 1. Distance pointers never point before the beginning of the output
+ stream.
+ 2. Distance pointers can point back across blocks, up to 32k away.
+ 3. There is an implied maximum of 7 bits for the bit length table and
+ 15 bits for the actual data.
+ 4. If only one code exists, then it is encoded using one bit. (Zero
+ would be more efficient, but perhaps a little confusing.) If two
+ codes exist, they are coded using one bit each (0 and 1).
+ 5. There is no way of sending zero distance codes--a dummy must be
+ sent if there are none. (History: a pre 2.0 version of PKZIP would
+ store blocks with no distance codes, but this was discovered to be
+ too harsh a criterion.) Valid only for 1.93a. 2.04c does allow
+ zero distance codes, which is sent as one code of zero bits in
+ length.
+ 6. There are up to 286 literal/length codes. Code 256 represents the
+ end-of-block. Note however that the static length tree defines
+ 288 codes just to fill out the Huffman codes. Codes 286 and 287
+ cannot be used though, since there is no length base or extra bits
+ defined for them. Similarily, there are up to 30 distance codes.
+ However, static trees define 32 codes (all 5 bits) to fill out the
+ Huffman codes, but the last two had better not show up in the data.
+ 7. Unzip can check dynamic Huffman blocks for complete code sets.
+ The exception is that a single code would not be complete (see #4).
+ 8. The five bits following the block type is really the number of
+ literal codes sent minus 257.
+ 9. Length codes 8,16,16 are interpreted as 13 length codes of 8 bits
+ (1+6+6). Therefore, to output three times the length, you output
+ three codes (1+1+1), whereas to output four times the same length,
+ you only need two codes (1+3). Hmm.
+ 10. In the tree reconstruction algorithm, Code = Code + Increment
+ only if BitLength(i) is not zero. (Pretty obvious.)
+ 11. Correction: 4 Bits: # of Bit Length codes - 4 (4 - 19)
+ 12. Note: length code 284 can represent 227-258, but length code 285
+ really is 258. The last length deserves its own, short code
+ since it gets used a lot in very redundant files. The length
+ 258 is special since 258 - 3 (the min match length) is 255.
+ 13. The literal/length and distance code bit lengths are read as a
+ single stream of lengths. It is possible (and advantageous) for
+ a repeat code (16, 17, or 18) to go across the boundary between
+ the two sets of lengths.
+ */
+
+
+void inflate_blocks_reset(inflate_blocks_statef *s, z_streamp z, uLong *c)
+{
+ if (c != Z_NULL)
+ *c = s->check;
+ if (s->mode == BTREE || s->mode == DTREE)
+ ZFREE(z, s->sub.trees.blens);
+ if (s->mode == CODES)
+ inflate_codes_free(s->sub.decode.codes, z);
+ s->mode = TYPE;
+ s->bitk = 0;
+ s->bitb = 0;
+ s->read = s->write = s->window;
+ if (s->checkfn != Z_NULL)
+ z->adler = s->check = (*s->checkfn)(0L, (const Byte *)Z_NULL, 0);
+ Tracev(("inflate: blocks reset\n"));
+}
+
+
+inflate_blocks_statef *inflate_blocks_new(z_streamp z, check_func c, uInt w)
+{
+ inflate_blocks_statef *s;
+
+ if ((s = (inflate_blocks_statef *)ZALLOC
+ (z,1,sizeof(struct inflate_blocks_state))) == Z_NULL)
+ return s;
+ if ((s->hufts =
+ (inflate_huft *)ZALLOC(z, sizeof(inflate_huft), MANY)) == Z_NULL)
+ {
+ ZFREE(z, s);
+ return Z_NULL;
+ }
+ if ((s->window = (Byte *)ZALLOC(z, 1, w)) == Z_NULL)
+ {
+ ZFREE(z, s->hufts);
+ ZFREE(z, s);
+ return Z_NULL;
+ }
+ s->end = s->window + w;
+ s->checkfn = c;
+ s->mode = TYPE;
+ Tracev(("inflate: blocks allocated\n"));
+ inflate_blocks_reset(s, z, Z_NULL);
+ return s;
+}
+
+
+int inflate_blocks(inflate_blocks_statef *s, z_streamp z, int r)
+{
+ uInt t; /* temporary storage */
+ uLong b; /* bit buffer */
+ uInt k; /* bits in bit buffer */
+ Byte *p; /* input data pointer */
+ uInt n; /* bytes available there */
+ Byte *q; /* output window write pointer */
+ uInt m; /* bytes to end of window or read pointer */
+
+ /* copy input/output information to locals (UPDATE macro restores) */
+ LOAD
+
+ /* process input based on current state */
+ while (1) switch (s->mode)
+ {
+ case TYPE:
+ NEEDBITS(3)
+ t = (uInt)b & 7;
+ s->last = t & 1;
+ switch (t >> 1)
+ {
+ case 0: /* stored */
+ Tracev(("inflate: stored block%s\n",
+ s->last ? " (last)" : ""));
+ DUMPBITS(3)
+ t = k & 7; /* go to byte boundary */
+ DUMPBITS(t)
+ s->mode = LENS; /* get length of stored block */
+ break;
+ case 1: /* fixed */
+ Tracev(("inflate: fixed codes block%s\n",
+ s->last ? " (last)" : ""));
+ {
+ uInt bl, bd;
+ inflate_huft *tl, *td;
+ inflate_trees_fixed(&bl, &bd, &tl, &td, z);
+ s->sub.decode.codes = inflate_codes_new(bl, bd, tl, td, z);
+ if (s->sub.decode.codes == Z_NULL)
+ {
+ r = Z_MEM_ERROR;
+ LEAVE
+ }
+ }
+ DUMPBITS(3)
+ s->mode = CODES;
+ break;
+ case 2: /* dynamic */
+ Tracev(("inflate: dynamic codes block%s\n",
+ s->last ? " (last)" : ""));
+ DUMPBITS(3)
+ s->mode = TABLE;
+ break;
+ case 3: /* illegal */
+ DUMPBITS(3)
+ s->mode = BAD;
+ z->msg = (char*)"invalid block type";
+ r = Z_DATA_ERROR;
+ LEAVE
+ }
+ break;
+ case LENS:
+ NEEDBITS(32)
+ if ((((~b) >> 16) & 0xffff) != (b & 0xffff))
+ {
+ s->mode = BAD;
+ z->msg = (char*)"invalid stored block lengths";
+ r = Z_DATA_ERROR;
+ LEAVE
+ }
+ s->sub.left = (uInt)b & 0xffff;
+ b = k = 0; /* dump bits */
+ Tracev(("inflate: stored length %u\n", s->sub.left));
+ s->mode = s->sub.left ? STORED : (s->last ? DRY : TYPE);
+ break;
+ case STORED:
+ if (n == 0)
+ LEAVE
+ NEEDOUT
+ t = s->sub.left;
+ if (t > n) t = n;
+ if (t > m) t = m;
+ zmemcpy(q, p, t);
+ p += t; n -= t;
+ q += t; m -= t;
+ if ((s->sub.left -= t) != 0)
+ break;
+ Tracev(("inflate: stored end, %lu total out\n",
+ z->total_out + (q >= s->read ? q - s->read :
+ (s->end - s->read) + (q - s->window))));
+ s->mode = s->last ? DRY : TYPE;
+ break;
+ case TABLE:
+ NEEDBITS(14)
+ s->sub.trees.table = t = (uInt)b & 0x3fff;
+#ifndef PKZIP_BUG_WORKAROUND
+ if ((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29)
+ {
+ s->mode = BAD;
+ z->msg = (char*)"too many length or distance symbols";
+ r = Z_DATA_ERROR;
+ LEAVE
+ }
+#endif
+ t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f);
+ if ((s->sub.trees.blens = (uInt*)ZALLOC(z, t, sizeof(uInt))) == Z_NULL)
+ {
+ r = Z_MEM_ERROR;
+ LEAVE
+ }
+ DUMPBITS(14)
+ s->sub.trees.index = 0;
+ Tracev(("inflate: table sizes ok\n"));
+ s->mode = BTREE;
+ case BTREE:
+ while (s->sub.trees.index < 4 + (s->sub.trees.table >> 10))
+ {
+ NEEDBITS(3)
+ s->sub.trees.blens[border[s->sub.trees.index++]] = (uInt)b & 7;
+ DUMPBITS(3)
+ }
+ while (s->sub.trees.index < 19)
+ s->sub.trees.blens[border[s->sub.trees.index++]] = 0;
+ s->sub.trees.bb = 7;
+ t = inflate_trees_bits(s->sub.trees.blens, &s->sub.trees.bb,
+ &s->sub.trees.tb, s->hufts, z);
+ if (t != Z_OK)
+ {
+ ZFREE(z, s->sub.trees.blens);
+ r = t;
+ if (r == Z_DATA_ERROR)
+ s->mode = BAD;
+ LEAVE
+ }
+ s->sub.trees.index = 0;
+ Tracev(("inflate: bits tree ok\n"));
+ s->mode = DTREE;
+ case DTREE:
+ while (t = s->sub.trees.table,
+ s->sub.trees.index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f))
+ {
+ inflate_huft *h;
+ uInt i, j, c;
+
+ t = s->sub.trees.bb;
+ NEEDBITS(t)
+ h = s->sub.trees.tb + ((uInt)b & inflate_mask[t]);
+ t = h->bits;
+ c = h->base;
+ if (c < 16)
+ {
+ DUMPBITS(t)
+ s->sub.trees.blens[s->sub.trees.index++] = c;
+ }
+ else /* c == 16..18 */
+ {
+ i = c == 18 ? 7 : c - 14;
+ j = c == 18 ? 11 : 3;
+ NEEDBITS(t + i)
+ DUMPBITS(t)
+ j += (uInt)b & inflate_mask[i];
+ DUMPBITS(i)
+ i = s->sub.trees.index;
+ t = s->sub.trees.table;
+ if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) ||
+ (c == 16 && i < 1))
+ {
+ ZFREE(z, s->sub.trees.blens);
+ s->mode = BAD;
+ z->msg = (char*)"invalid bit length repeat";
+ r = Z_DATA_ERROR;
+ LEAVE
+ }
+ c = c == 16 ? s->sub.trees.blens[i - 1] : 0;
+ do {
+ s->sub.trees.blens[i++] = c;
+ } while (--j);
+ s->sub.trees.index = i;
+ }
+ }
+ s->sub.trees.tb = Z_NULL;
+ {
+ uInt bl, bd;
+ inflate_huft *tl, *td;
+ inflate_codes_statef *c;
+
+ tl = NULL;
+ td = NULL;
+ bl = 9; /* must be <= 9 for lookahead assumptions */
+ bd = 6; /* must be <= 9 for lookahead assumptions */
+ t = s->sub.trees.table;
+ t = inflate_trees_dynamic(257 + (t & 0x1f), 1 + ((t >> 5) & 0x1f),
+ s->sub.trees.blens, &bl, &bd, &tl, &td,
+ s->hufts, z);
+ ZFREE(z, s->sub.trees.blens);
+ if (t != Z_OK)
+ {
+ if (t == (uInt)Z_DATA_ERROR)
+ s->mode = BAD;
+ r = t;
+ LEAVE
+ }
+ Tracev(("inflate: trees ok\n"));
+ if ((c = inflate_codes_new(bl, bd, tl, td, z)) == Z_NULL)
+ {
+ r = Z_MEM_ERROR;
+ LEAVE
+ }
+ s->sub.decode.codes = c;
+ }
+ s->mode = CODES;
+ case CODES:
+ UPDATE
+ if ((r = inflate_codes(s, z, r)) != Z_STREAM_END)
+ return inflate_flush(s, z, r);
+ r = Z_OK;
+ inflate_codes_free(s->sub.decode.codes, z);
+ LOAD
+ Tracev(("inflate: codes end, %lu total out\n",
+ z->total_out + (q >= s->read ? q - s->read :
+ (s->end - s->read) + (q - s->window))));
+ if (!s->last)
+ {
+ s->mode = TYPE;
+ break;
+ }
+ s->mode = DRY;
+ case DRY:
+ FLUSH
+ if (s->read != s->write)
+ LEAVE
+ s->mode = DONE;
+ case DONE:
+ r = Z_STREAM_END;
+ LEAVE
+ case BAD:
+ r = Z_DATA_ERROR;
+ LEAVE
+ default:
+ r = Z_STREAM_ERROR;
+ LEAVE
+ }
+}
+
+
+int inflate_blocks_free(inflate_blocks_statef *s, z_streamp z)
+{
+ inflate_blocks_reset(s, z, Z_NULL);
+ ZFREE(z, s->window);
+ ZFREE(z, s->hufts);
+ ZFREE(z, s);
+ Tracev(("inflate: blocks freed\n"));
+ return Z_OK;
+}
+
+#if 0
+void inflate_set_dictionary(inflate_blocks_statef *s, const Byte *d, uInt n)
+{
+ zmemcpy(s->window, d, n);
+ s->read = s->write = s->window + n;
+}
+
+/* Returns true if inflate is currently at the end of a block generated
+ * by Z_SYNC_FLUSH or Z_FULL_FLUSH.
+ * IN assertion: s != Z_NULL
+ */
+int inflate_blocks_sync_point(inflate_blocks_statef *s)
+{
+ return s->mode == LENS;
+}
+#endif
+
+
+/* And'ing with mask[n] masks the lower n bits */
+static uInt inflate_mask[17] = {
+ 0x0000,
+ 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff,
+ 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff
+};
+
+
+/* copy as much as possible from the sliding window to the output area */
+int inflate_flush(inflate_blocks_statef *s, z_streamp z, int r)
+{
+ uInt n;
+ Byte *p;
+ Byte *q;
+
+ /* static copies of source and destination pointers */
+ p = z->next_out;
+ q = s->read;
+
+ /* compute number of bytes to copy as as end of window */
+ n = (uInt)((q <= s->write ? s->write : s->end) - q);
+ if (n > z->avail_out) n = z->avail_out;
+ if (n && r == Z_BUF_ERROR) r = Z_OK;
+
+ /* update counters */
+ z->avail_out -= n;
+ z->total_out += n;
+
+ /* update check information */
+ if (s->checkfn != Z_NULL)
+ z->adler = s->check = (*s->checkfn)(s->check, q, n);
+
+ /* copy as as end of window */
+ zmemcpy(p, q, n);
+ p += n;
+ q += n;
+
+ /* see if more to copy at beginning of window */
+ if (q == s->end)
+ {
+ /* wrap pointers */
+ q = s->window;
+ if (s->write == s->end)
+ s->write = s->window;
+
+ /* compute bytes to copy */
+ n = (uInt)(s->write - q);
+ if (n > z->avail_out) n = z->avail_out;
+ if (n && r == Z_BUF_ERROR) r = Z_OK;
+
+ /* update counters */
+ z->avail_out -= n;
+ z->total_out += n;
+
+ /* update check information */
+ if (s->checkfn != Z_NULL)
+ z->adler = s->check = (*s->checkfn)(s->check, q, n);
+
+ /* copy */
+ zmemcpy(p, q, n);
+ p += n;
+ q += n;
+ }
+
+ /* update pointers */
+ z->next_out = p;
+ s->read = q;
+
+ /* done */
+ return r;
+}
+
+/* inftrees.c -- generate Huffman trees for efficient decoding
+ * Copyright (C) 1995-1998 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/*
+ If you use the zlib library in a product, an acknowledgment is welcome
+ in the documentation of your product. If for some reason you cannot
+ include such an acknowledgment, I would appreciate that you keep this
+ copyright string in the executable of your product.
+ */
+
+/* simplify the use of the inflate_huft type with some defines */
+#define exop word.what.Exop
+#define bits word.what.Bits
+
+
+static int huft_build OF((
+ uInt *, /* code lengths in bits */
+ uInt, /* number of codes */
+ uInt, /* number of "simple" codes */
+ const uInt *, /* list of base values for non-simple codes */
+ const uInt *, /* list of extra bits for non-simple codes */
+ inflate_huft **, /* result: starting table */
+ uInt *, /* maximum lookup bits (returns actual) */
+ inflate_huft *, /* space for trees */
+ uInt *, /* hufts used in space */
+ uInt * )); /* space for values */
+
+/* Tables for deflate from PKZIP's appnote.txt. */
+static const uInt cplens[31] = { /* Copy lengths for literal codes 257..285 */
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
+ 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0};
+ /* see note #13 above about 258 */
+static const uInt cplext[31] = { /* Extra bits for literal codes 257..285 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
+ 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112}; /* 112==invalid */
+static const uInt cpdist[30] = { /* Copy offsets for distance codes 0..29 */
+ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
+ 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
+ 8193, 12289, 16385, 24577};
+static const uInt cpdext[30] = { /* Extra bits for distance codes */
+ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
+ 7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
+ 12, 12, 13, 13};
+
+/*
+ Huffman code decoding is performed using a multi-level table lookup.
+ The fastest way to decode is to simply build a lookup table whose
+ size is determined by the longest code. However, the time it takes
+ to build this table can also be a factor if the data being decoded
+ is not very long. The most common codes are necessarily the
+ shortest codes, so those codes dominate the decoding time, and hence
+ the speed. The idea is you can have a shorter table that decodes the
+ shorter, more probable codes, and then point to subsidiary tables for
+ the longer codes. The time it costs to decode the longer codes is
+ then traded against the time it takes to make longer tables.
+
+ This results of this trade are in the variables lbits and dbits
+ below. lbits is the number of bits the first level table for literal/
+ length codes can decode in one step, and dbits is the same thing for
+ the distance codes. Subsequent tables are also less than or equal to
+ those sizes. These values may be adjusted either when all of the
+ codes are shorter than that, in which case the longest code length in
+ bits is used, or when the shortest code is *longer* than the requested
+ table size, in which case the length of the shortest code in bits is
+ used.
+
+ There are two different values for the two tables, since they code a
+ different number of possibilities each. The literal/length table
+ codes 286 possible values, or in a flat code, a little over eight
+ bits. The distance table codes 30 possible values, or a little less
+ than five bits, flat. The optimum values for speed end up being
+ about one bit more than those, so lbits is 8+1 and dbits is 5+1.
+ The optimum values may differ though from machine to machine, and
+ possibly even between compilers. Your mileage may vary.
+ */
+
+
+/* If BMAX needs to be larger than 16, then h and x[] should be uLong. */
+#define BMAX 15 /* maximum bit length of any code */
+
+static int huft_build(uInt *b, uInt n, uInt s, const uInt *d, const uInt *e, inflate_huft ** t, uInt *m, inflate_huft *hp, uInt *hn, uInt *v)
+//uInt *b; /* code lengths in bits (all assumed <= BMAX) */
+//uInt n; /* number of codes (assumed <= 288) */
+//uInt s; /* number of simple-valued codes (0..s-1) */
+//const uInt *d; /* list of base values for non-simple codes */
+//const uInt *e; /* list of extra bits for non-simple codes */
+//inflate_huft ** t; /* result: starting table */
+//uInt *m; /* maximum lookup bits, returns actual */
+//inflate_huft *hp; /* space for trees */
+//uInt *hn; /* hufts used in space */
+//uInt *v; /* working area: values in order of bit length */
+/* Given a list of code lengths and a maximum table size, make a set of
+ tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR
+ if the given code set is incomplete (the tables are still built in this
+ case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of
+ lengths), or Z_MEM_ERROR if not enough memory. */
+{
+
+ uInt a; /* counter for codes of length k */
+ uInt c[BMAX+1]; /* bit length count table */
+ uInt f; /* i repeats in table every f entries */
+ int g; /* maximum code length */
+ int h; /* table level */
+ register uInt i; /* counter, current code */
+ register uInt j; /* counter */
+ register int k; /* number of bits in current code */
+ int l; /* bits per table (returned in m) */
+ uInt mask; /* (1 << w) - 1, to avoid cc -O bug on HP */
+ register uInt *p; /* pointer into c[], b[], or v[] */
+ inflate_huft *q; /* points to current table */
+ struct inflate_huft_s r = {{{0, 0}}}; /* table entry for structure assignment */
+ inflate_huft *u[BMAX]; /* table stack */
+ register int w; /* bits before this table == (l * h) */
+ uInt x[BMAX+1]; /* bit offsets, then code stack */
+ uInt *xp; /* pointer into x */
+ int y; /* number of dummy codes added */
+ uInt z; /* number of entries in current table */
+
+
+ /* Generate counts for each bit length */
+ p = c;
+#define C0 *p++ = 0;
+#define C2 C0 C0 C0 C0
+#define C4 C2 C2 C2 C2
+ C4 /* clear c[]--assume BMAX+1 is 16 */
+ p = b; i = n;
+ do {
+ c[*p++]++; /* assume all entries <= BMAX */
+ } while (--i);
+ if (c[0] == n) /* null input--all zero length codes */
+ {
+ *t = (inflate_huft *)Z_NULL;
+ *m = 0;
+ return Z_OK;
+ }
+
+
+ /* Find minimum and maximum length, bound *m by those */
+ l = *m;
+ for (j = 1; j <= BMAX; j++)
+ if (c[j])
+ break;
+ k = j; /* minimum code length */
+ if ((uInt)l < j)
+ l = j;
+ for (i = BMAX; i; i--)
+ if (c[i])
+ break;
+ g = i; /* maximum code length */
+ if ((uInt)l > i)
+ l = i;
+ *m = l;
+
+
+ /* Adjust last length count to fill out codes, if needed */
+ for (y = 1 << j; j < i; j++, y <<= 1)
+ if ((y -= c[j]) < 0)
+ return Z_DATA_ERROR;
+ if ((y -= c[i]) < 0)
+ return Z_DATA_ERROR;
+ c[i] += y;
+
+
+ /* Generate starting offsets into the value table for each length */
+ x[1] = j = 0;
+ p = c + 1; xp = x + 2;
+ while (--i) { /* note that i == g from above */
+ *xp++ = (j += *p++);
+ }
+
+
+ /* Make a table of values in order of bit lengths */
+ p = b; i = 0;
+ do {
+ if ((j = *p++) != 0)
+ v[x[j]++] = i;
+ } while (++i < n);
+ n = x[g]; /* set n to length of v */
+
+
+ /* Generate the Huffman codes and for each, make the table entries */
+ x[0] = i = 0; /* first Huffman code is zero */
+ p = v; /* grab values in bit order */
+ h = -1; /* no tables yet--level -1 */
+ w = -l; /* bits decoded == (l * h) */
+ u[0] = (inflate_huft *)Z_NULL; /* just to keep compilers happy */
+ q = (inflate_huft *)Z_NULL; /* ditto */
+ z = 0; /* ditto */
+
+ /* go through the bit lengths (k already is bits in shortest code) */
+ for (; k <= g; k++)
+ {
+ a = c[k];
+ while (a--)
+ {
+ /* here i is the Huffman code of length k bits for value *p */
+ /* make tables up to required level */
+ while (k > w + l)
+ {
+ h++;
+ w += l; /* previous table always l bits */
+
+ /* compute minimum size table less than or equal to l bits */
+ z = g - w;
+ z = z > (uInt)l ? l : z; /* table size upper limit */
+ if ((f = 1 << (j = k - w)) > a + 1) /* try a k-w bit table */
+ { /* too few codes for k-w bit table */
+ f -= a + 1; /* deduct codes from patterns left */
+ xp = c + k;
+ if (j < z)
+ while (++j < z) /* try smaller tables up to z bits */
+ {
+ if ((f <<= 1) <= *++xp)
+ break; /* enough codes to use up j bits */
+ f -= *xp; /* else deduct codes from patterns */
+ }
+ }
+ z = 1 << j; /* table entries for j-bit table */
+
+ /* allocate new table */
+ if (*hn + z > MANY) /* (note: doesn't matter for fixed) */
+ return Z_MEM_ERROR; /* not enough memory */
+ u[h] = q = hp + *hn;
+ *hn += z;
+
+ /* connect to last table, if there is one */
+ if (h)
+ {
+ x[h] = i; /* save pattern for backing up */
+ r.bits = (Byte)l; /* bits to dump before this table */
+ r.exop = (Byte)j; /* bits in this table */
+ j = i >> (w - l);
+ r.base = (uInt)(q - u[h-1] - j); /* offset to this table */
+ u[h-1][j] = r; /* connect to last table */
+ }
+ else
+ *t = q; /* first table is returned result */
+ }
+
+ /* set up table entry in r */
+ r.bits = (Byte)(k - w);
+ if (p >= v + n)
+ r.exop = 128 + 64; /* out of values--invalid code */
+ else if (*p < s)
+ {
+ r.exop = (Byte)(*p < 256 ? 0 : 32 + 64); /* 256 is end-of-block */
+ r.base = *p++; /* simple code is just the value */
+ }
+ else
+ {
+ r.exop = (Byte)(e[*p - s] + 16 + 64);/* non-simple--look up in lists */
+ r.base = d[*p++ - s];
+ }
+
+ /* fill code-like entries with r */
+ f = 1 << (k - w);
+ for (j = i >> w; j < z; j += f)
+ q[j] = r;
+
+ /* backwards increment the k-bit code i */
+ for (j = 1 << (k - 1); i & j; j >>= 1)
+ i ^= j;
+ i ^= j;
+
+ /* backup over finished tables */
+ mask = (1 << w) - 1; /* needed on HP, cc -O bug */
+ while ((i & mask) != x[h])
+ {
+ h--; /* don't need to update q */
+ w -= l;
+ mask = (1 << w) - 1;
+ }
+ }
+ }
+
+
+ /* Return Z_BUF_ERROR if we were given an incomplete table */
+ return y != 0 && g != 1 ? Z_BUF_ERROR : Z_OK;
+}
+
+
+int inflate_trees_bits(uInt *c, uInt *bb, inflate_huft * *tb, inflate_huft *hp, z_streamp z)
+//uInt *c; /* 19 code lengths */
+//uInt *bb; /* bits tree desired/actual depth */
+//inflate_huft * *tb; /* bits tree result */
+//inflate_huft *hp; /* space for trees */
+//z_streamp z; /* for messages */
+{
+ int r;
+ uInt hn = 0; /* hufts used in space */
+ uInt *v; /* work area for huft_build */
+
+ if ((v = (uInt*)ZALLOC(z, 19, sizeof(uInt))) == Z_NULL)
+ return Z_MEM_ERROR;
+ r = huft_build(c, 19, 19, (uInt*)Z_NULL, (uInt*)Z_NULL,
+ tb, bb, hp, &hn, v);
+ if (r == Z_DATA_ERROR)
+ z->msg = (char*)"oversubscribed dynamic bit lengths tree";
+ else if (r == Z_BUF_ERROR || *bb == 0)
+ {
+ z->msg = (char*)"incomplete dynamic bit lengths tree";
+ r = Z_DATA_ERROR;
+ }
+ ZFREE(z, v);
+ return r;
+}
+
+
+int inflate_trees_dynamic(uInt nl, uInt nd, uInt *c, uInt *bl, uInt *bd, inflate_huft * *tl, inflate_huft * *td, inflate_huft *hp, z_streamp z)
+//uInt nl; /* number of literal/length codes */
+//uInt nd; /* number of distance codes */
+//uInt *c; /* that many (total) code lengths */
+//uInt *bl; /* literal desired/actual bit depth */
+//uInt *bd; /* distance desired/actual bit depth */
+//inflate_huft * *tl; /* literal/length tree result */
+//inflate_huft * *td; /* distance tree result */
+//inflate_huft *hp; /* space for trees */
+//z_streamp z; /* for messages */
+{
+ int r;
+ uInt hn = 0; /* hufts used in space */
+ uInt *v; /* work area for huft_build */
+
+ /* allocate work area */
+ if ((v = (uInt*)ZALLOC(z, 288, sizeof(uInt))) == Z_NULL)
+ return Z_MEM_ERROR;
+
+ /* build literal/length tree */
+ r = huft_build(c, nl, 257, cplens, cplext, tl, bl, hp, &hn, v);
+ if (r != Z_OK || *bl == 0)
+ {
+ if (r == Z_DATA_ERROR)
+ z->msg = (char*)"oversubscribed literal/length tree";
+ else if (r != Z_MEM_ERROR)
+ {
+ z->msg = (char*)"incomplete literal/length tree";
+ r = Z_DATA_ERROR;
+ }
+ ZFREE(z, v);
+ return r;
+ }
+
+ /* build distance tree */
+ r = huft_build(c + nl, nd, 0, cpdist, cpdext, td, bd, hp, &hn, v);
+ if (r != Z_OK || (*bd == 0 && nl > 257))
+ {
+ if (r == Z_DATA_ERROR)
+ z->msg = (char*)"oversubscribed distance tree";
+ else if (r == Z_BUF_ERROR) {
+#ifdef PKZIP_BUG_WORKAROUND
+ r = Z_OK;
+ }
+#else
+ z->msg = (char*)"incomplete distance tree";
+ r = Z_DATA_ERROR;
+ }
+ else if (r != Z_MEM_ERROR)
+ {
+ z->msg = (char*)"empty distance tree with lengths";
+ r = Z_DATA_ERROR;
+ }
+ ZFREE(z, v);
+ return r;
+#endif
+ }
+
+ /* done */
+ ZFREE(z, v);
+ return Z_OK;
+}
+
+/* inffixed.h -- table for decoding fixed codes
+ * Generated automatically by the maketree.c program
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+ part of the implementation of the compression library and is
+ subject to change. Applications should only use zlib.h.
+ */
+
+static uInt fixed_bl = 9;
+static uInt fixed_bd = 5;
+static inflate_huft fixed_tl[] = {
+ {{{96,7}},256}, {{{0,8}},80}, {{{0,8}},16}, {{{84,8}},115},
+ {{{82,7}},31}, {{{0,8}},112}, {{{0,8}},48}, {{{0,9}},192},
+ {{{80,7}},10}, {{{0,8}},96}, {{{0,8}},32}, {{{0,9}},160},
+ {{{0,8}},0}, {{{0,8}},128}, {{{0,8}},64}, {{{0,9}},224},
+ {{{80,7}},6}, {{{0,8}},88}, {{{0,8}},24}, {{{0,9}},144},
+ {{{83,7}},59}, {{{0,8}},120}, {{{0,8}},56}, {{{0,9}},208},
+ {{{81,7}},17}, {{{0,8}},104}, {{{0,8}},40}, {{{0,9}},176},
+ {{{0,8}},8}, {{{0,8}},136}, {{{0,8}},72}, {{{0,9}},240},
+ {{{80,7}},4}, {{{0,8}},84}, {{{0,8}},20}, {{{85,8}},227},
+ {{{83,7}},43}, {{{0,8}},116}, {{{0,8}},52}, {{{0,9}},200},
+ {{{81,7}},13}, {{{0,8}},100}, {{{0,8}},36}, {{{0,9}},168},
+ {{{0,8}},4}, {{{0,8}},132}, {{{0,8}},68}, {{{0,9}},232},
+ {{{80,7}},8}, {{{0,8}},92}, {{{0,8}},28}, {{{0,9}},152},
+ {{{84,7}},83}, {{{0,8}},124}, {{{0,8}},60}, {{{0,9}},216},
+ {{{82,7}},23}, {{{0,8}},108}, {{{0,8}},44}, {{{0,9}},184},
+ {{{0,8}},12}, {{{0,8}},140}, {{{0,8}},76}, {{{0,9}},248},
+ {{{80,7}},3}, {{{0,8}},82}, {{{0,8}},18}, {{{85,8}},163},
+ {{{83,7}},35}, {{{0,8}},114}, {{{0,8}},50}, {{{0,9}},196},
+ {{{81,7}},11}, {{{0,8}},98}, {{{0,8}},34}, {{{0,9}},164},
+ {{{0,8}},2}, {{{0,8}},130}, {{{0,8}},66}, {{{0,9}},228},
+ {{{80,7}},7}, {{{0,8}},90}, {{{0,8}},26}, {{{0,9}},148},
+ {{{84,7}},67}, {{{0,8}},122}, {{{0,8}},58}, {{{0,9}},212},
+ {{{82,7}},19}, {{{0,8}},106}, {{{0,8}},42}, {{{0,9}},180},
+ {{{0,8}},10}, {{{0,8}},138}, {{{0,8}},74}, {{{0,9}},244},
+ {{{80,7}},5}, {{{0,8}},86}, {{{0,8}},22}, {{{192,8}},0},
+ {{{83,7}},51}, {{{0,8}},118}, {{{0,8}},54}, {{{0,9}},204},
+ {{{81,7}},15}, {{{0,8}},102}, {{{0,8}},38}, {{{0,9}},172},
+ {{{0,8}},6}, {{{0,8}},134}, {{{0,8}},70}, {{{0,9}},236},
+ {{{80,7}},9}, {{{0,8}},94}, {{{0,8}},30}, {{{0,9}},156},
+ {{{84,7}},99}, {{{0,8}},126}, {{{0,8}},62}, {{{0,9}},220},
+ {{{82,7}},27}, {{{0,8}},110}, {{{0,8}},46}, {{{0,9}},188},
+ {{{0,8}},14}, {{{0,8}},142}, {{{0,8}},78}, {{{0,9}},252},
+ {{{96,7}},256}, {{{0,8}},81}, {{{0,8}},17}, {{{85,8}},131},
+ {{{82,7}},31}, {{{0,8}},113}, {{{0,8}},49}, {{{0,9}},194},
+ {{{80,7}},10}, {{{0,8}},97}, {{{0,8}},33}, {{{0,9}},162},
+ {{{0,8}},1}, {{{0,8}},129}, {{{0,8}},65}, {{{0,9}},226},
+ {{{80,7}},6}, {{{0,8}},89}, {{{0,8}},25}, {{{0,9}},146},
+ {{{83,7}},59}, {{{0,8}},121}, {{{0,8}},57}, {{{0,9}},210},
+ {{{81,7}},17}, {{{0,8}},105}, {{{0,8}},41}, {{{0,9}},178},
+ {{{0,8}},9}, {{{0,8}},137}, {{{0,8}},73}, {{{0,9}},242},
+ {{{80,7}},4}, {{{0,8}},85}, {{{0,8}},21}, {{{80,8}},258},
+ {{{83,7}},43}, {{{0,8}},117}, {{{0,8}},53}, {{{0,9}},202},
+ {{{81,7}},13}, {{{0,8}},101}, {{{0,8}},37}, {{{0,9}},170},
+ {{{0,8}},5}, {{{0,8}},133}, {{{0,8}},69}, {{{0,9}},234},
+ {{{80,7}},8}, {{{0,8}},93}, {{{0,8}},29}, {{{0,9}},154},
+ {{{84,7}},83}, {{{0,8}},125}, {{{0,8}},61}, {{{0,9}},218},
+ {{{82,7}},23}, {{{0,8}},109}, {{{0,8}},45}, {{{0,9}},186},
+ {{{0,8}},13}, {{{0,8}},141}, {{{0,8}},77}, {{{0,9}},250},
+ {{{80,7}},3}, {{{0,8}},83}, {{{0,8}},19}, {{{85,8}},195},
+ {{{83,7}},35}, {{{0,8}},115}, {{{0,8}},51}, {{{0,9}},198},
+ {{{81,7}},11}, {{{0,8}},99}, {{{0,8}},35}, {{{0,9}},166},
+ {{{0,8}},3}, {{{0,8}},131}, {{{0,8}},67}, {{{0,9}},230},
+ {{{80,7}},7}, {{{0,8}},91}, {{{0,8}},27}, {{{0,9}},150},
+ {{{84,7}},67}, {{{0,8}},123}, {{{0,8}},59}, {{{0,9}},214},
+ {{{82,7}},19}, {{{0,8}},107}, {{{0,8}},43}, {{{0,9}},182},
+ {{{0,8}},11}, {{{0,8}},139}, {{{0,8}},75}, {{{0,9}},246},
+ {{{80,7}},5}, {{{0,8}},87}, {{{0,8}},23}, {{{192,8}},0},
+ {{{83,7}},51}, {{{0,8}},119}, {{{0,8}},55}, {{{0,9}},206},
+ {{{81,7}},15}, {{{0,8}},103}, {{{0,8}},39}, {{{0,9}},174},
+ {{{0,8}},7}, {{{0,8}},135}, {{{0,8}},71}, {{{0,9}},238},
+ {{{80,7}},9}, {{{0,8}},95}, {{{0,8}},31}, {{{0,9}},158},
+ {{{84,7}},99}, {{{0,8}},127}, {{{0,8}},63}, {{{0,9}},222},
+ {{{82,7}},27}, {{{0,8}},111}, {{{0,8}},47}, {{{0,9}},190},
+ {{{0,8}},15}, {{{0,8}},143}, {{{0,8}},79}, {{{0,9}},254},
+ {{{96,7}},256}, {{{0,8}},80}, {{{0,8}},16}, {{{84,8}},115},
+ {{{82,7}},31}, {{{0,8}},112}, {{{0,8}},48}, {{{0,9}},193},
+ {{{80,7}},10}, {{{0,8}},96}, {{{0,8}},32}, {{{0,9}},161},
+ {{{0,8}},0}, {{{0,8}},128}, {{{0,8}},64}, {{{0,9}},225},
+ {{{80,7}},6}, {{{0,8}},88}, {{{0,8}},24}, {{{0,9}},145},
+ {{{83,7}},59}, {{{0,8}},120}, {{{0,8}},56}, {{{0,9}},209},
+ {{{81,7}},17}, {{{0,8}},104}, {{{0,8}},40}, {{{0,9}},177},
+ {{{0,8}},8}, {{{0,8}},136}, {{{0,8}},72}, {{{0,9}},241},
+ {{{80,7}},4}, {{{0,8}},84}, {{{0,8}},20}, {{{85,8}},227},
+ {{{83,7}},43}, {{{0,8}},116}, {{{0,8}},52}, {{{0,9}},201},
+ {{{81,7}},13}, {{{0,8}},100}, {{{0,8}},36}, {{{0,9}},169},
+ {{{0,8}},4}, {{{0,8}},132}, {{{0,8}},68}, {{{0,9}},233},
+ {{{80,7}},8}, {{{0,8}},92}, {{{0,8}},28}, {{{0,9}},153},
+ {{{84,7}},83}, {{{0,8}},124}, {{{0,8}},60}, {{{0,9}},217},
+ {{{82,7}},23}, {{{0,8}},108}, {{{0,8}},44}, {{{0,9}},185},
+ {{{0,8}},12}, {{{0,8}},140}, {{{0,8}},76}, {{{0,9}},249},
+ {{{80,7}},3}, {{{0,8}},82}, {{{0,8}},18}, {{{85,8}},163},
+ {{{83,7}},35}, {{{0,8}},114}, {{{0,8}},50}, {{{0,9}},197},
+ {{{81,7}},11}, {{{0,8}},98}, {{{0,8}},34}, {{{0,9}},165},
+ {{{0,8}},2}, {{{0,8}},130}, {{{0,8}},66}, {{{0,9}},229},
+ {{{80,7}},7}, {{{0,8}},90}, {{{0,8}},26}, {{{0,9}},149},
+ {{{84,7}},67}, {{{0,8}},122}, {{{0,8}},58}, {{{0,9}},213},
+ {{{82,7}},19}, {{{0,8}},106}, {{{0,8}},42}, {{{0,9}},181},
+ {{{0,8}},10}, {{{0,8}},138}, {{{0,8}},74}, {{{0,9}},245},
+ {{{80,7}},5}, {{{0,8}},86}, {{{0,8}},22}, {{{192,8}},0},
+ {{{83,7}},51}, {{{0,8}},118}, {{{0,8}},54}, {{{0,9}},205},
+ {{{81,7}},15}, {{{0,8}},102}, {{{0,8}},38}, {{{0,9}},173},
+ {{{0,8}},6}, {{{0,8}},134}, {{{0,8}},70}, {{{0,9}},237},
+ {{{80,7}},9}, {{{0,8}},94}, {{{0,8}},30}, {{{0,9}},157},
+ {{{84,7}},99}, {{{0,8}},126}, {{{0,8}},62}, {{{0,9}},221},
+ {{{82,7}},27}, {{{0,8}},110}, {{{0,8}},46}, {{{0,9}},189},
+ {{{0,8}},14}, {{{0,8}},142}, {{{0,8}},78}, {{{0,9}},253},
+ {{{96,7}},256}, {{{0,8}},81}, {{{0,8}},17}, {{{85,8}},131},
+ {{{82,7}},31}, {{{0,8}},113}, {{{0,8}},49}, {{{0,9}},195},
+ {{{80,7}},10}, {{{0,8}},97}, {{{0,8}},33}, {{{0,9}},163},
+ {{{0,8}},1}, {{{0,8}},129}, {{{0,8}},65}, {{{0,9}},227},
+ {{{80,7}},6}, {{{0,8}},89}, {{{0,8}},25}, {{{0,9}},147},
+ {{{83,7}},59}, {{{0,8}},121}, {{{0,8}},57}, {{{0,9}},211},
+ {{{81,7}},17}, {{{0,8}},105}, {{{0,8}},41}, {{{0,9}},179},
+ {{{0,8}},9}, {{{0,8}},137}, {{{0,8}},73}, {{{0,9}},243},
+ {{{80,7}},4}, {{{0,8}},85}, {{{0,8}},21}, {{{80,8}},258},
+ {{{83,7}},43}, {{{0,8}},117}, {{{0,8}},53}, {{{0,9}},203},
+ {{{81,7}},13}, {{{0,8}},101}, {{{0,8}},37}, {{{0,9}},171},
+ {{{0,8}},5}, {{{0,8}},133}, {{{0,8}},69}, {{{0,9}},235},
+ {{{80,7}},8}, {{{0,8}},93}, {{{0,8}},29}, {{{0,9}},155},
+ {{{84,7}},83}, {{{0,8}},125}, {{{0,8}},61}, {{{0,9}},219},
+ {{{82,7}},23}, {{{0,8}},109}, {{{0,8}},45}, {{{0,9}},187},
+ {{{0,8}},13}, {{{0,8}},141}, {{{0,8}},77}, {{{0,9}},251},
+ {{{80,7}},3}, {{{0,8}},83}, {{{0,8}},19}, {{{85,8}},195},
+ {{{83,7}},35}, {{{0,8}},115}, {{{0,8}},51}, {{{0,9}},199},
+ {{{81,7}},11}, {{{0,8}},99}, {{{0,8}},35}, {{{0,9}},167},
+ {{{0,8}},3}, {{{0,8}},131}, {{{0,8}},67}, {{{0,9}},231},
+ {{{80,7}},7}, {{{0,8}},91}, {{{0,8}},27}, {{{0,9}},151},
+ {{{84,7}},67}, {{{0,8}},123}, {{{0,8}},59}, {{{0,9}},215},
+ {{{82,7}},19}, {{{0,8}},107}, {{{0,8}},43}, {{{0,9}},183},
+ {{{0,8}},11}, {{{0,8}},139}, {{{0,8}},75}, {{{0,9}},247},
+ {{{80,7}},5}, {{{0,8}},87}, {{{0,8}},23}, {{{192,8}},0},
+ {{{83,7}},51}, {{{0,8}},119}, {{{0,8}},55}, {{{0,9}},207},
+ {{{81,7}},15}, {{{0,8}},103}, {{{0,8}},39}, {{{0,9}},175},
+ {{{0,8}},7}, {{{0,8}},135}, {{{0,8}},71}, {{{0,9}},239},
+ {{{80,7}},9}, {{{0,8}},95}, {{{0,8}},31}, {{{0,9}},159},
+ {{{84,7}},99}, {{{0,8}},127}, {{{0,8}},63}, {{{0,9}},223},
+ {{{82,7}},27}, {{{0,8}},111}, {{{0,8}},47}, {{{0,9}},191},
+ {{{0,8}},15}, {{{0,8}},143}, {{{0,8}},79}, {{{0,9}},255}
+ };
+static inflate_huft fixed_td[] = {
+ {{{80,5}},1}, {{{87,5}},257}, {{{83,5}},17}, {{{91,5}},4097},
+ {{{81,5}},5}, {{{89,5}},1025}, {{{85,5}},65}, {{{93,5}},16385},
+ {{{80,5}},3}, {{{88,5}},513}, {{{84,5}},33}, {{{92,5}},8193},
+ {{{82,5}},9}, {{{90,5}},2049}, {{{86,5}},129}, {{{192,5}},24577},
+ {{{80,5}},2}, {{{87,5}},385}, {{{83,5}},25}, {{{91,5}},6145},
+ {{{81,5}},7}, {{{89,5}},1537}, {{{85,5}},97}, {{{93,5}},24577},
+ {{{80,5}},4}, {{{88,5}},769}, {{{84,5}},49}, {{{92,5}},12289},
+ {{{82,5}},13}, {{{90,5}},3073}, {{{86,5}},193}, {{{192,5}},24577}
+ };
+
+int inflate_trees_fixed(uInt *bl, uInt *bd, inflate_huft * *tl, inflate_huft * *td, z_streamp z)
+//uInt *bl; /* literal desired/actual bit depth */
+//uInt *bd; /* distance desired/actual bit depth */
+//inflate_huft * *tl; /* literal/length tree result */
+//inflate_huft * *td; /* distance tree result */
+//z_streamp z; /* for memory allocation */
+{
+ *bl = fixed_bl;
+ *bd = fixed_bd;
+ *tl = fixed_tl;
+ *td = fixed_td;
+ return Z_OK;
+}
+
+/* simplify the use of the inflate_huft type with some defines */
+#define exop word.what.Exop
+#define bits word.what.Bits
+
+/* macros for bit input with no checking and for returning unused bytes */
+#define GRABBITS(j) {while(k<(j)){b|=((uLong)NEXTBYTE)<<k;k+=8;}}
+#define UNGRAB {c=z->avail_in-n;c=(k>>3)<c?k>>3:c;n+=c;p-=c;k-=c<<3;}
+
+/* Called with number of bytes left to write in window at least 258
+ (the maximum string length) and number of input bytes available
+ at least ten. The ten bytes are six bytes for the longest length/
+ distance pair plus four bytes for overloading the bit buffer. */
+
+static int inflate_fast(uInt bl, uInt bd, inflate_huft *tl, inflate_huft *td, inflate_blocks_statef *s, z_streamp z)
+{
+ inflate_huft *t; /* temporary pointer */
+ uInt e; /* extra bits or operation */
+ uLong b; /* bit buffer */
+ uInt k; /* bits in bit buffer */
+ Byte *p; /* input data pointer */
+ uInt n; /* bytes available there */
+ Byte *q; /* output window write pointer */
+ uInt m; /* bytes to end of window or read pointer */
+ uInt ml; /* mask for literal/length tree */
+ uInt md; /* mask for distance tree */
+ uInt c; /* bytes to copy */
+ uInt d; /* distance back to copy from */
+ Byte *r; /* copy source pointer */
+
+ /* load input, output, bit values */
+ LOAD
+
+ /* initialize masks */
+ ml = inflate_mask[bl];
+ md = inflate_mask[bd];
+
+ /* do until not enough input or output space for fast loop */
+ do { /* assume called with m >= 258 && n >= 10 */
+ /* get literal/length code */
+ GRABBITS(20) /* max bits for literal/length code */
+ if ((e = (t = tl + ((uInt)b & ml))->exop) == 0)
+ {
+ DUMPBITS(t->bits)
+ Tracevv((t->base >= 0x20 && t->base < 0x7f ?
+ "inflate: * literal '%c'\n" :
+ "inflate: * literal 0x%02x\n", t->base));
+ *q++ = (Byte)t->base;
+ m--;
+ continue;
+ }
+ do {
+ DUMPBITS(t->bits)
+ if (e & 16)
+ {
+ /* get extra bits for length */
+ e &= 15;
+ c = t->base + ((uInt)b & inflate_mask[e]);
+ DUMPBITS(e)
+ Tracevv(("inflate: * length %u\n", c));
+
+ /* decode distance base of block to copy */
+ GRABBITS(15); /* max bits for distance code */
+ e = (t = td + ((uInt)b & md))->exop;
+ do {
+ DUMPBITS(t->bits)
+ if (e & 16)
+ {
+ /* get extra bits to add to distance base */
+ e &= 15;
+ GRABBITS(e) /* get extra bits (up to 13) */
+ d = t->base + ((uInt)b & inflate_mask[e]);
+ DUMPBITS(e)
+ Tracevv(("inflate: * distance %u\n", d));
+
+ /* do the copy */
+ m -= c;
+ if ((uInt)(q - s->window) >= d) /* offset before dest */
+ { /* just copy */
+ r = q - d;
+ *q++ = *r++; c--; /* minimum count is three, */
+ *q++ = *r++; c--; /* so unroll loop a little */
+ }
+ else /* else offset after destination */
+ {
+ e = d - (uInt)(q - s->window); /* bytes from offset to end */
+ r = s->end - e; /* pointer to offset */
+ if (c > e) /* if source crosses, */
+ {
+ c -= e; /* copy to end of window */
+ do {
+ *q++ = *r++;
+ } while (--e);
+ r = s->window; /* copy rest from start of window */
+ }
+ }
+ do { /* copy all or what's left */
+ *q++ = *r++;
+ } while (--c);
+ break;
+ }
+ else if ((e & 64) == 0)
+ {
+ t += t->base;
+ e = (t += ((uInt)b & inflate_mask[e]))->exop;
+ }
+ else
+ {
+ z->msg = (char*)"invalid distance code";
+ UNGRAB
+ UPDATE
+ return Z_DATA_ERROR;
+ }
+ } while (1);
+ break;
+ }
+ if ((e & 64) == 0)
+ {
+ t += t->base;
+ if ((e = (t += ((uInt)b & inflate_mask[e]))->exop) == 0)
+ {
+ DUMPBITS(t->bits)
+ Tracevv((t->base >= 0x20 && t->base < 0x7f ?
+ "inflate: * literal '%c'\n" :
+ "inflate: * literal 0x%02x\n", t->base));
+ *q++ = (Byte)t->base;
+ m--;
+ break;
+ }
+ }
+ else if (e & 32)
+ {
+ Tracevv(("inflate: * end of block\n"));
+ UNGRAB
+ UPDATE
+ return Z_STREAM_END;
+ }
+ else
+ {
+ z->msg = (char*)"invalid literal/length code";
+ UNGRAB
+ UPDATE
+ return Z_DATA_ERROR;
+ }
+ } while (1);
+ } while (m >= 258 && n >= 10);
+
+ /* not enough input or output--restore pointers and return */
+ UNGRAB
+ UPDATE
+ return Z_OK;
+}
+
+/* infcodes.c -- process literals and length/distance pairs
+ * Copyright (C) 1995-1998 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* simplify the use of the inflate_huft type with some defines */
+#define exop word.what.Exop
+#define bits word.what.Bits
+
+typedef enum { /* waiting for "i:"=input, "o:"=output, "x:"=nothing */
+ START, /* x: set up for LEN */
+ LEN, /* i: get length/literal/eob next */
+ LENEXT, /* i: getting length extra (have base) */
+ DIST, /* i: get distance next */
+ DISTEXT, /* i: getting distance extra */
+ COPY, /* o: copying bytes in window, waiting for space */
+ LIT, /* o: got literal, waiting for output space */
+ WASH, /* o: got eob, possibly still output waiting */
+ END, /* x: got eob and all data flushed */
+ BADCODE} /* x: got error */
+inflate_codes_mode;
+
+/* inflate codes private state */
+struct inflate_codes_state {
+
+ /* mode */
+ inflate_codes_mode mode; /* current inflate_codes mode */
+
+ /* mode dependent information */
+ uInt len;
+ union {
+ struct {
+ inflate_huft *tree; /* pointer into tree */
+ uInt need; /* bits needed */
+ } code; /* if LEN or DIST, where in tree */
+ uInt lit; /* if LIT, literal */
+ struct {
+ uInt get; /* bits to get for extra */
+ uInt dist; /* distance back to copy from */
+ } copy; /* if EXT or COPY, where and how much */
+ } sub; /* submode */
+
+ /* mode independent information */
+ Byte lbits; /* ltree bits decoded per branch */
+ Byte dbits; /* dtree bits decoder per branch */
+ inflate_huft *ltree; /* literal/length/eob tree */
+ inflate_huft *dtree; /* distance tree */
+
+};
+
+
+inflate_codes_statef *inflate_codes_new(uInt bl, uInt bd, inflate_huft *tl, inflate_huft *td, z_streamp z)
+{
+ inflate_codes_statef *c;
+
+ if ((c = (inflate_codes_statef *)
+ ZALLOC(z,1,sizeof(struct inflate_codes_state))) != Z_NULL)
+ {
+ c->mode = START;
+ c->lbits = (Byte)bl;
+ c->dbits = (Byte)bd;
+ c->ltree = tl;
+ c->dtree = td;
+ Tracev(("inflate: codes new\n"));
+ }
+ return c;
+}
+
+
+int inflate_codes(inflate_blocks_statef *s, z_streamp z, int r)
+{
+ uInt j; /* temporary storage */
+ inflate_huft *t; /* temporary pointer */
+ uInt e; /* extra bits or operation */
+ uLong b; /* bit buffer */
+ uInt k; /* bits in bit buffer */
+ Byte *p; /* input data pointer */
+ uInt n; /* bytes available there */
+ Byte *q; /* output window write pointer */
+ uInt m; /* bytes to end of window or read pointer */
+ Byte *f; /* pointer to copy strings from */
+ inflate_codes_statef *c = s->sub.decode.codes; /* codes state */
+
+ /* copy input/output information to locals (UPDATE macro restores) */
+ LOAD
+
+ /* process input and output based on current state */
+ while (1) switch (c->mode)
+ { /* waiting for "i:"=input, "o:"=output, "x:"=nothing */
+ case START: /* x: set up for LEN */
+#ifndef SLOW
+ if (m >= 258 && n >= 10)
+ {
+ UPDATE
+ r = inflate_fast(c->lbits, c->dbits, c->ltree, c->dtree, s, z);
+ LOAD
+ if (r != Z_OK)
+ {
+ c->mode = r == Z_STREAM_END ? WASH : BADCODE;
+ break;
+ }
+ }
+#endif /* !SLOW */
+ c->sub.code.need = c->lbits;
+ c->sub.code.tree = c->ltree;
+ c->mode = LEN;
+ case LEN: /* i: get length/literal/eob next */
+ j = c->sub.code.need;
+ NEEDBITS(j)
+ t = c->sub.code.tree + ((uInt)b & inflate_mask[j]);
+ DUMPBITS(t->bits)
+ e = (uInt)(t->exop);
+ if (e == 0) /* literal */
+ {
+ c->sub.lit = t->base;
+ Tracevv((t->base >= 0x20 && t->base < 0x7f ?
+ "inflate: literal '%c'\n" :
+ "inflate: literal 0x%02x\n", t->base));
+ c->mode = LIT;
+ break;
+ }
+ if (e & 16) /* length */
+ {
+ c->sub.copy.get = e & 15;
+ c->len = t->base;
+ c->mode = LENEXT;
+ break;
+ }
+ if ((e & 64) == 0) /* next table */
+ {
+ c->sub.code.need = e;
+ c->sub.code.tree = t + t->base;
+ break;
+ }
+ if (e & 32) /* end of block */
+ {
+ Tracevv(("inflate: end of block\n"));
+ c->mode = WASH;
+ break;
+ }
+ c->mode = BADCODE; /* invalid code */
+ z->msg = (char*)"invalid literal/length code";
+ r = Z_DATA_ERROR;
+ LEAVE
+ case LENEXT: /* i: getting length extra (have base) */
+ j = c->sub.copy.get;
+ NEEDBITS(j)
+ c->len += (uInt)b & inflate_mask[j];
+ DUMPBITS(j)
+ c->sub.code.need = c->dbits;
+ c->sub.code.tree = c->dtree;
+ Tracevv(("inflate: length %u\n", c->len));
+ c->mode = DIST;
+ case DIST: /* i: get distance next */
+ j = c->sub.code.need;
+ NEEDBITS(j)
+ t = c->sub.code.tree + ((uInt)b & inflate_mask[j]);
+ DUMPBITS(t->bits)
+ e = (uInt)(t->exop);
+ if (e & 16) /* distance */
+ {
+ c->sub.copy.get = e & 15;
+ c->sub.copy.dist = t->base;
+ c->mode = DISTEXT;
+ break;
+ }
+ if ((e & 64) == 0) /* next table */
+ {
+ c->sub.code.need = e;
+ c->sub.code.tree = t + t->base;
+ break;
+ }
+ c->mode = BADCODE; /* invalid code */
+ z->msg = (char*)"invalid distance code";
+ r = Z_DATA_ERROR;
+ LEAVE
+ case DISTEXT: /* i: getting distance extra */
+ j = c->sub.copy.get;
+ NEEDBITS(j)
+ c->sub.copy.dist += (uInt)b & inflate_mask[j];
+ DUMPBITS(j)
+ Tracevv(("inflate: distance %u\n", c->sub.copy.dist));
+ c->mode = COPY;
+ case COPY: /* o: copying bytes in window, waiting for space */
+#ifndef __TURBOC__ /* Turbo C bug for following expression */
+ f = (uInt)(q - s->window) < c->sub.copy.dist ?
+ s->end - (c->sub.copy.dist - (q - s->window)) :
+ q - c->sub.copy.dist;
+#else
+ f = q - c->sub.copy.dist;
+ if ((uInt)(q - s->window) < c->sub.copy.dist)
+ f = s->end - (c->sub.copy.dist - (uInt)(q - s->window));
+#endif
+ while (c->len)
+ {
+ NEEDOUT
+ OUTBYTE(*f++)
+ if (f == s->end)
+ f = s->window;
+ c->len--;
+ }
+ c->mode = START;
+ break;
+ case LIT: /* o: got literal, waiting for output space */
+ NEEDOUT
+ OUTBYTE(c->sub.lit)
+ c->mode = START;
+ break;
+ case WASH: /* o: got eob, possibly more output */
+ if (k > 7) /* return unused byte, if any */
+ {
+ Assert(k < 16, "inflate_codes grabbed too many bytes")
+ k -= 8;
+ n++;
+ p--; /* can always return one */
+ }
+ FLUSH
+ if (s->read != s->write)
+ LEAVE
+ c->mode = END;
+ case END:
+ r = Z_STREAM_END;
+ LEAVE
+ case BADCODE: /* x: got error */
+ r = Z_DATA_ERROR;
+ LEAVE
+ default:
+ r = Z_STREAM_ERROR;
+ LEAVE
+ }
+#ifdef NEED_DUMMY_RETURN
+ return Z_STREAM_ERROR; /* Some dumb compilers complain without this */
+#endif
+}
+
+
+void inflate_codes_free(inflate_codes_statef *c, z_streamp z)
+{
+ ZFREE(z, c);
+ Tracev(("inflate: codes free\n"));
+}
+
+/* adler32.c -- compute the Adler-32 checksum of a data stream
+ * Copyright (C) 1995-1998 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#define BASE 65521L /* largest prime smaller than 65536 */
+#define NMAX 5552
+/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */
+
+#undef DO1
+#undef DO2
+#undef DO4
+#undef DO8
+
+#define DO1(buf,i) {s1 += buf[i]; s2 += s1;}
+#define DO2(buf,i) DO1(buf,i); DO1(buf,i+1);
+#define DO4(buf,i) DO2(buf,i); DO2(buf,i+2);
+#define DO8(buf,i) DO4(buf,i); DO4(buf,i+4);
+#define DO16(buf) DO8(buf,0); DO8(buf,8);
+
+/* ========================================================================= */
+static uLong adler32(uLong adler, const Byte *buf, uInt len)
+{
+ unsigned long s1 = adler & 0xffff;
+ unsigned long s2 = (adler >> 16) & 0xffff;
+ int k;
+
+ if (buf == Z_NULL) return 1L;
+
+ while (len > 0) {
+ k = len < NMAX ? len : NMAX;
+ len -= k;
+ while (k >= 16) {
+ DO16(buf);
+ buf += 16;
+ k -= 16;
+ }
+ if (k != 0) do {
+ s1 += *buf++;
+ s2 += s1;
+ } while (--k);
+ s1 %= BASE;
+ s2 %= BASE;
+ }
+ return (s2 << 16) | s1;
+}
+
+
+/* infblock.h -- header to use infblock.c
+ * Copyright (C) 1995-1998 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+ part of the implementation of the compression library and is
+ subject to change. Applications should only use zlib.h.
+ */
+
+static inflate_blocks_statef * inflate_blocks_new OF((
+ z_streamp z,
+ check_func c, /* check function */
+ uInt w)); /* window size */
+
+static int inflate_blocks OF((
+ inflate_blocks_statef *,
+ z_streamp ,
+ int)); /* initial return code */
+
+static void inflate_blocks_reset OF((
+ inflate_blocks_statef *,
+ z_streamp ,
+ uLong *)); /* check value on output */
+
+static int inflate_blocks_free OF((
+ inflate_blocks_statef *,
+ z_streamp));
+
+#if 0
+static void inflate_set_dictionary OF((
+ inflate_blocks_statef *s,
+ const Byte *d, /* dictionary */
+ uInt n)); /* dictionary length */
+
+static int inflate_blocks_sync_point OF((
+ inflate_blocks_statef *s));
+#endif
+
+typedef enum {
+ imMETHOD, /* waiting for method byte */
+ imFLAG, /* waiting for flag byte */
+ imDICT4, /* four dictionary check bytes to go */
+ imDICT3, /* three dictionary check bytes to go */
+ imDICT2, /* two dictionary check bytes to go */
+ imDICT1, /* one dictionary check byte to go */
+ imDICT0, /* waiting for inflateSetDictionary */
+ imBLOCKS, /* decompressing blocks */
+ imCHECK4, /* four check bytes to go */
+ imCHECK3, /* three check bytes to go */
+ imCHECK2, /* two check bytes to go */
+ imCHECK1, /* one check byte to go */
+ imDONE, /* finished check, done */
+ imBAD} /* got an error--stay here */
+inflate_mode;
+
+/* inflate private state */
+struct internal_state {
+
+ /* mode */
+ inflate_mode mode; /* current inflate mode */
+
+ /* mode dependent information */
+ union {
+ uInt method; /* if FLAGS, method byte */
+ struct {
+ uLong was; /* computed check value */
+ uLong need; /* stream check value */
+ } check; /* if CHECK, check values to compare */
+ uInt marker; /* if BAD, inflateSync's marker bytes count */
+ } sub; /* submode */
+
+ /* mode independent information */
+ int nowrap; /* flag for no wrapper */
+ uInt wbits; /* log2(window size) (8..15, defaults to 15) */
+ inflate_blocks_statef
+ *blocks; /* current inflate_blocks state */
+
+};
+
+
+int inflateReset(z_streamp z)
+{
+ if (z == Z_NULL || z->state == Z_NULL)
+ return Z_STREAM_ERROR;
+ z->total_in = z->total_out = 0;
+ z->msg = Z_NULL;
+ z->state->mode = z->state->nowrap ? imBLOCKS : imMETHOD;
+ inflate_blocks_reset(z->state->blocks, z, Z_NULL);
+ Tracev(("inflate: reset\n"));
+ return Z_OK;
+}
+
+
+int inflateEnd(z_streamp z)
+{
+ if (z == Z_NULL || z->state == Z_NULL || z->zfree == Z_NULL)
+ return Z_STREAM_ERROR;
+ if (z->state->blocks != Z_NULL)
+ inflate_blocks_free(z->state->blocks, z);
+ ZFREE(z, z->state);
+ z->state = Z_NULL;
+ Tracev(("inflate: end\n"));
+ return Z_OK;
+}
+
+
+
+int inflateInit2_(z_streamp z, int w, const char *version, int stream_size)
+{
+ if (version == Z_NULL || version[0] != ZLIB_VERSION[0] ||
+ stream_size != sizeof(z_stream))
+ return Z_VERSION_ERROR;
+
+ /* initialize state */
+ if (z == Z_NULL)
+ return Z_STREAM_ERROR;
+ z->msg = Z_NULL;
+ if (z->zalloc == Z_NULL)
+ {
+ z->zalloc = (void *(*)(void *, unsigned, unsigned))zcalloc;
+ z->opaque = (voidp)0;
+ }
+ if (z->zfree == Z_NULL) z->zfree = (void (*)(void *, void *))zcfree;
+ if ((z->state = (struct internal_state *)
+ ZALLOC(z,1,sizeof(struct internal_state))) == Z_NULL)
+ return Z_MEM_ERROR;
+ z->state->blocks = Z_NULL;
+
+ /* handle undocumented nowrap option (no zlib header or check) */
+ z->state->nowrap = 0;
+ if (w < 0)
+ {
+ w = - w;
+ z->state->nowrap = 1;
+ }
+
+ /* set window size */
+ if (w < 8 || w > 15)
+ {
+ inflateEnd(z);
+ return Z_STREAM_ERROR;
+ }
+ z->state->wbits = (uInt)w;
+
+ /* create inflate_blocks state */
+ if ((z->state->blocks =
+ inflate_blocks_new(z, z->state->nowrap ? Z_NULL : adler32, (uInt)1 << w))
+ == Z_NULL)
+ {
+ inflateEnd(z);
+ return Z_MEM_ERROR;
+ }
+ Tracev(("inflate: allocated\n"));
+
+ /* reset state */
+ inflateReset(z);
+ return Z_OK;
+}
+
+#if 0
+int inflateInit_(z_streamp z, const char *version, int stream_size)
+{
+ return inflateInit2_(z, DEF_WBITS, version, stream_size);
+}
+#endif
+
+#define iNEEDBYTE {if(z->avail_in==0)return r;r=f;}
+#define iNEXTBYTE (z->avail_in--,z->total_in++,*z->next_in++)
+
+int inflate(z_streamp z, int f)
+{
+ int r;
+ uInt b;
+
+ if (z == Z_NULL || z->state == Z_NULL || z->next_in == Z_NULL)
+ return Z_STREAM_ERROR;
+ f = f == Z_FINISH ? Z_BUF_ERROR : Z_OK;
+ r = Z_BUF_ERROR;
+ while (1) switch (z->state->mode)
+ {
+ case imMETHOD:
+ iNEEDBYTE
+ if (((z->state->sub.method = iNEXTBYTE) & 0xf) != Z_DEFLATED)
+ {
+ z->state->mode = imBAD;
+ z->msg = (char*)"unknown compression method";
+ z->state->sub.marker = 5; /* can't try inflateSync */
+ break;
+ }
+ if ((z->state->sub.method >> 4) + 8 > z->state->wbits)
+ {
+ z->state->mode = imBAD;
+ z->msg = (char*)"invalid window size";
+ z->state->sub.marker = 5; /* can't try inflateSync */
+ break;
+ }
+ z->state->mode = imFLAG;
+ case imFLAG:
+ iNEEDBYTE
+ b = iNEXTBYTE;
+ if (((z->state->sub.method << 8) + b) % 31)
+ {
+ z->state->mode = imBAD;
+ z->msg = (char*)"incorrect header check";
+ z->state->sub.marker = 5; /* can't try inflateSync */
+ break;
+ }
+ Tracev(("inflate: zlib header ok\n"));
+ if (!(b & PRESET_DICT))
+ {
+ z->state->mode = imBLOCKS;
+ break;
+ }
+ z->state->mode = imDICT4;
+ case imDICT4:
+ iNEEDBYTE
+ z->state->sub.check.need = (uLong)iNEXTBYTE << 24;
+ z->state->mode = imDICT3;
+ case imDICT3:
+ iNEEDBYTE
+ z->state->sub.check.need += (uLong)iNEXTBYTE << 16;
+ z->state->mode = imDICT2;
+ case imDICT2:
+ iNEEDBYTE
+ z->state->sub.check.need += (uLong)iNEXTBYTE << 8;
+ z->state->mode = imDICT1;
+ case imDICT1:
+ iNEEDBYTE
+ z->state->sub.check.need += (uLong)iNEXTBYTE;
+ z->adler = z->state->sub.check.need;
+ z->state->mode = imDICT0;
+ return Z_NEED_DICT;
+ case imDICT0:
+ z->state->mode = imBAD;
+ z->msg = (char*)"need dictionary";
+ z->state->sub.marker = 0; /* can try inflateSync */
+ return Z_STREAM_ERROR;
+ case imBLOCKS:
+ r = inflate_blocks(z->state->blocks, z, r);
+ if (r == Z_DATA_ERROR)
+ {
+ z->state->mode = imBAD;
+ z->state->sub.marker = 0; /* can try inflateSync */
+ break;
+ }
+ if (r == Z_OK)
+ r = f;
+ if (r != Z_STREAM_END)
+ return r;
+ r = f;
+ inflate_blocks_reset(z->state->blocks, z, &z->state->sub.check.was);
+ if (z->state->nowrap)
+ {
+ z->state->mode = imDONE;
+ break;
+ }
+ z->state->mode = imCHECK4;
+ case imCHECK4:
+ iNEEDBYTE
+ z->state->sub.check.need = (uLong)iNEXTBYTE << 24;
+ z->state->mode = imCHECK3;
+ case imCHECK3:
+ iNEEDBYTE
+ z->state->sub.check.need += (uLong)iNEXTBYTE << 16;
+ z->state->mode = imCHECK2;
+ case imCHECK2:
+ iNEEDBYTE
+ z->state->sub.check.need += (uLong)iNEXTBYTE << 8;
+ z->state->mode = imCHECK1;
+ case imCHECK1:
+ iNEEDBYTE
+ z->state->sub.check.need += (uLong)iNEXTBYTE;
+
+ if (z->state->sub.check.was != z->state->sub.check.need)
+ {
+ z->state->mode = imBAD;
+ z->msg = (char*)"incorrect data check";
+ z->state->sub.marker = 5; /* can't try inflateSync */
+ break;
+ }
+ Tracev(("inflate: zlib check ok\n"));
+ z->state->mode = imDONE;
+ case imDONE:
+ return Z_STREAM_END;
+ case imBAD:
+ return Z_DATA_ERROR;
+ default:
+ return Z_STREAM_ERROR;
+ }
+#ifdef NEED_DUMMY_RETURN
+ return Z_STREAM_ERROR; /* Some dumb compilers complain without this */
+#endif
+}
+
+// defined but not used
+#if 0
+int inflateSetDictionary(z_streamp z, const Byte *dictionary, uInt dictLength)
+{
+ uInt length = dictLength;
+
+ if (z == Z_NULL || z->state == Z_NULL || z->state->mode != imDICT0)
+ return Z_STREAM_ERROR;
+
+ if (adler32(1L, dictionary, dictLength) != z->adler) return Z_DATA_ERROR;
+ z->adler = 1L;
+
+ if (length >= ((uInt)1<<z->state->wbits))
+ {
+ length = (1<<z->state->wbits)-1;
+ dictionary += dictLength - length;
+ }
+ inflate_set_dictionary(z->state->blocks, dictionary, length);
+ z->state->mode = imBLOCKS;
+ return Z_OK;
+}
+
+int inflateSync(z_streamp z)
+{
+ uInt n; /* number of bytes to look at */
+ Byte *p; /* pointer to bytes */
+ uInt m; /* number of marker bytes found in a row */
+ uLong r, w; /* temporaries to save total_in and total_out */
+
+ /* set up */
+ if (z == Z_NULL || z->state == Z_NULL)
+ return Z_STREAM_ERROR;
+ if (z->state->mode != imBAD)
+ {
+ z->state->mode = imBAD;
+ z->state->sub.marker = 0;
+ }
+ if ((n = z->avail_in) == 0)
+ return Z_BUF_ERROR;
+ p = z->next_in;
+ m = z->state->sub.marker;
+
+ /* search */
+ while (n && m < 4)
+ {
+ static const Byte mark[4] = {0, 0, 0xff, 0xff};
+ if (*p == mark[m])
+ m++;
+ else if (*p)
+ m = 0;
+ else
+ m = 4 - m;
+ p++, n--;
+ }
+
+ /* restore */
+ z->total_in += p - z->next_in;
+ z->next_in = p;
+ z->avail_in = n;
+ z->state->sub.marker = m;
+
+ /* return no joy or set up to restart on a new block */
+ if (m != 4)
+ return Z_DATA_ERROR;
+ r = z->total_in; w = z->total_out;
+ inflateReset(z);
+ z->total_in = r; z->total_out = w;
+ z->state->mode = imBLOCKS;
+ return Z_OK;
+}
+
+/* Returns true if inflate is currently at the end of a block generated
+ * by Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP
+ * implementation to provide an additional safety check. PPP uses Z_SYNC_FLUSH
+ * but removes the length bytes of the resulting empty stored block. When
+ * decompressing, PPP checks that at the end of input packet, inflate is
+ * waiting for these length bytes.
+ */
+int inflateSyncPoint(z_streamp z)
+{
+ if (z == Z_NULL || z->state == Z_NULL || z->state->blocks == Z_NULL)
+ return Z_STREAM_ERROR;
+ return inflate_blocks_sync_point(z->state->blocks);
+}
+#endif
+
+voidp zcalloc (voidp opaque, unsigned items, unsigned size)
+{
+ if (opaque) items += size - size; /* make compiler happy */
+ return (voidp)Z_Malloc(items*size);
+}
+
+void zcfree (voidp opaque, voidp ptr)
+{
+ Z_Free(ptr);
+ if (opaque) return; /* make compiler happy */
+}
+
+
diff --git a/src/qcommon/unzip.h b/src/qcommon/unzip.h
new file mode 100644
index 0000000..18eb31e
--- /dev/null
+++ b/src/qcommon/unzip.h
@@ -0,0 +1,337 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP)
+/* like the STRICT of WIN32, we define a pointer that cannot be converted
+ from (void*) without cast */
+typedef struct TagunzFile__ { int unused; } unzFile__;
+typedef unzFile__ *unzFile;
+#else
+typedef void* unzFile;
+#endif
+
+/* tm_unz contain date/time info */
+typedef struct tm_unz_s
+{
+ unsigned int tm_sec; /* seconds after the minute - [0,59] */
+ unsigned int tm_min; /* minutes after the hour - [0,59] */
+ unsigned int tm_hour; /* hours since midnight - [0,23] */
+ unsigned int tm_mday; /* day of the month - [1,31] */
+ unsigned int tm_mon; /* months since January - [0,11] */
+ unsigned int tm_year; /* years - [1980..2044] */
+} tm_unz;
+
+/* unz_global_info structure contain global data about the ZIPfile
+ These data comes from the end of central dir */
+typedef struct unz_global_info_s
+{
+ unsigned long number_entry; /* total number of entries in the central dir on this disk */
+ unsigned long size_comment; /* size of the global comment of the zipfile */
+} unz_global_info;
+
+
+/* unz_file_info contain information about a file in the zipfile */
+typedef struct unz_file_info_s
+{
+ unsigned long version; /* version made by 2 unsigned chars */
+ unsigned long version_needed; /* version needed to extract 2 unsigned chars */
+ unsigned long flag; /* general purpose bit flag 2 unsigned chars */
+ unsigned long compression_method; /* compression method 2 unsigned chars */
+ unsigned long dosDate; /* last mod file date in Dos fmt 4 unsigned chars */
+ unsigned long crc; /* crc-32 4 unsigned chars */
+ unsigned long compressed_size; /* compressed size 4 unsigned chars */
+ unsigned long uncompressed_size; /* uncompressed size 4 unsigned chars */
+ unsigned long size_filename; /* filename length 2 unsigned chars */
+ unsigned long size_file_extra; /* extra field length 2 unsigned chars */
+ unsigned long size_file_comment; /* file comment length 2 unsigned chars */
+
+ unsigned long disk_num_start; /* disk number start 2 unsigned chars */
+ unsigned long internal_fa; /* internal file attributes 2 unsigned chars */
+ unsigned long external_fa; /* external file attributes 4 unsigned chars */
+
+ tm_unz tmu_date;
+} unz_file_info;
+
+/* unz_file_info_interntal contain internal info about a file in zipfile*/
+typedef struct unz_file_info_internal_s
+{
+ unsigned long offset_curfile;/* relative offset of static header 4 unsigned chars */
+} unz_file_info_internal;
+
+typedef void* (*alloc_func) (void* opaque, unsigned int items, unsigned int size);
+typedef void (*free_func) (void* opaque, void* address);
+
+struct internal_state;
+
+typedef struct z_stream_s {
+ unsigned char *next_in; /* next input unsigned char */
+ unsigned int avail_in; /* number of unsigned chars available at next_in */
+ unsigned long total_in; /* total nb of input unsigned chars read so */
+
+ unsigned char *next_out; /* next output unsigned char should be put there */
+ unsigned int avail_out; /* remaining free space at next_out */
+ unsigned long total_out; /* total nb of unsigned chars output so */
+
+ char *msg; /* last error message, NULL if no error */
+ struct internal_state *state; /* not visible by applications */
+
+ alloc_func zalloc; /* used to allocate the internal state */
+ free_func zfree; /* used to free the internal state */
+ unsigned char* opaque; /* private data object passed to zalloc and zfree */
+
+ int data_type; /* best guess about the data type: ascii or binary */
+ unsigned long adler; /* adler32 value of the uncompressed data */
+ unsigned long reserved; /* reserved for future use */
+} z_stream;
+
+typedef z_stream *z_streamp;
+
+
+/* file_in_zip_read_info_s contain internal information about a file in zipfile,
+ when reading and decompress it */
+typedef struct
+{
+ char *read_buffer; /* internal buffer for compressed data */
+ z_stream stream; /* zLib stream structure for inflate */
+
+ unsigned long pos_in_zipfile; /* position in unsigned char on the zipfile, for fseek*/
+ unsigned long stream_initialised; /* flag set if stream structure is initialised*/
+
+ unsigned long offset_local_extrafield;/* offset of the static extra field */
+ unsigned int size_local_extrafield;/* size of the static extra field */
+ unsigned long pos_local_extrafield; /* position in the static extra field in read*/
+
+ unsigned long crc32; /* crc32 of all data uncompressed */
+ unsigned long crc32_wait; /* crc32 we must obtain after decompress all */
+ unsigned long rest_read_compressed; /* number of unsigned char to be decompressed */
+ unsigned long rest_read_uncompressed;/*number of unsigned char to be obtained after decomp*/
+ FILE* file; /* io structore of the zipfile */
+ unsigned long compression_method; /* compression method (0==store) */
+ unsigned long byte_before_the_zipfile;/* unsigned char before the zipfile, (>0 for sfx)*/
+} file_in_zip_read_info_s;
+
+
+/* unz_s contain internal information about the zipfile
+*/
+typedef struct
+{
+ FILE* file; /* io structore of the zipfile */
+ unz_global_info gi; /* public global information */
+ unsigned long byte_before_the_zipfile;/* unsigned char before the zipfile, (>0 for sfx)*/
+ unsigned long num_file; /* number of the current file in the zipfile*/
+ unsigned long pos_in_central_dir; /* pos of the current file in the central dir*/
+ unsigned long current_file_ok; /* flag about the usability of the current file*/
+ unsigned long central_pos; /* position of the beginning of the central dir*/
+
+ unsigned long size_central_dir; /* size of the central directory */
+ unsigned long offset_central_dir; /* offset of start of central directory with
+ respect to the starting disk number */
+
+ unz_file_info cur_file_info; /* public info about the current file in zip*/
+ unz_file_info_internal cur_file_info_internal; /* private info about it*/
+ file_in_zip_read_info_s* pfile_in_zip_read; /* structure about the current
+ file if we are decompressing it */
+ unsigned char* tmpFile;
+ int tmpPos,tmpSize;
+} unz_s;
+
+#define UNZ_OK (0)
+#define UNZ_END_OF_LIST_OF_FILE (-100)
+#define UNZ_ERRNO (Z_ERRNO)
+#define UNZ_EOF (0)
+#define UNZ_PARAMERROR (-102)
+#define UNZ_BADZIPFILE (-103)
+#define UNZ_INTERNALERROR (-104)
+#define UNZ_CRCERROR (-105)
+
+#define UNZ_CASESENSITIVE 1
+#define UNZ_NOTCASESENSITIVE 2
+#define UNZ_OSDEFAULTCASE 0
+
+extern int unzStringFileNameCompare (const char* fileName1, const char* fileName2, int iCaseSensitivity);
+
+/*
+ Compare two filename (fileName1,fileName2).
+ If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp)
+ If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi
+ or strcasecmp)
+ If iCaseSenisivity = 0, case sensitivity is defaut of your operating system
+ (like 1 on Unix, 2 on Windows)
+*/
+
+extern unzFile unzOpen (const char *path);
+extern unzFile unzReOpen (const char* path, unzFile file);
+
+/*
+ Open a Zip file. path contain the full pathname (by example,
+ on a Windows NT computer "c:\\zlib\\zlib111.zip" or on an Unix computer
+ "zlib/zlib111.zip".
+ If the zipfile cannot be opened (file don't exist or in not valid), the
+ return value is NULL.
+ Else, the return value is a unzFile Handle, usable with other function
+ of this unzip package.
+*/
+
+extern int unzClose (unzFile file);
+
+/*
+ Close a ZipFile opened with unzipOpen.
+ If there is files inside the .Zip opened with unzOpenCurrentFile (see later),
+ these files MUST be closed with unzipCloseCurrentFile before call unzipClose.
+ return UNZ_OK if there is no problem. */
+
+extern int unzGetGlobalInfo (unzFile file, unz_global_info *pglobal_info);
+
+/*
+ Write info about the ZipFile in the *pglobal_info structure.
+ No preparation of the structure is needed
+ return UNZ_OK if there is no problem. */
+
+
+extern int unzGetGlobalComment (unzFile file, char *szComment, unsigned long uSizeBuf);
+
+/*
+ Get the global comment string of the ZipFile, in the szComment buffer.
+ uSizeBuf is the size of the szComment buffer.
+ return the number of unsigned char copied or an error code <0
+*/
+
+
+/***************************************************************************/
+/* Unzip package allow you browse the directory of the zipfile */
+
+extern int unzGoToFirstFile (unzFile file);
+
+/*
+ Set the current file of the zipfile to the first file.
+ return UNZ_OK if there is no problem
+*/
+
+extern int unzGoToNextFile (unzFile file);
+
+/*
+ Set the current file of the zipfile to the next file.
+ return UNZ_OK if there is no problem
+ return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest.
+*/
+
+extern int unzGetCurrentFileInfoPosition (unzFile file, unsigned long *pos );
+
+/*
+ Get the position of the info of the current file in the zip.
+ return UNZ_OK if there is no problem
+*/
+
+extern int unzSetCurrentFileInfoPosition (unzFile file, unsigned long pos );
+
+/*
+ Set the position of the info of the current file in the zip.
+ return UNZ_OK if there is no problem
+*/
+
+extern int unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity);
+
+/*
+ Try locate the file szFileName in the zipfile.
+ For the iCaseSensitivity signification, see unzStringFileNameCompare
+
+ return value :
+ UNZ_OK if the file is found. It becomes the current file.
+ UNZ_END_OF_LIST_OF_FILE if the file is not found
+*/
+
+
+extern int unzGetCurrentFileInfo (unzFile file, unz_file_info *pfile_info, char *szFileName, unsigned long fileNameBufferSize, void *extraField, unsigned long extraFieldBufferSize, char *szComment, unsigned long commentBufferSize);
+
+/*
+ Get Info about the current file
+ if pfile_info!=NULL, the *pfile_info structure will contain somes info about
+ the current file
+ if szFileName!=NULL, the filemane string will be copied in szFileName
+ (fileNameBufferSize is the size of the buffer)
+ if extraField!=NULL, the extra field information will be copied in extraField
+ (extraFieldBufferSize is the size of the buffer).
+ This is the Central-header version of the extra field
+ if szComment!=NULL, the comment string of the file will be copied in szComment
+ (commentBufferSize is the size of the buffer)
+*/
+
+/***************************************************************************/
+/* for reading the content of the current zipfile, you can open it, read data
+ from it, and close it (you can close it before reading all the file)
+ */
+
+extern int unzOpenCurrentFile (unzFile file);
+
+/*
+ Open for reading data the current file in the zipfile.
+ If there is no error, the return value is UNZ_OK.
+*/
+
+extern int unzCloseCurrentFile (unzFile file);
+
+/*
+ Close the file in zip opened with unzOpenCurrentFile
+ Return UNZ_CRCERROR if all the file was read but the CRC is not good
+*/
+
+
+extern int unzReadCurrentFile (unzFile file, void* buf, unsigned len);
+
+/*
+ Read unsigned chars from the current file (opened by unzOpenCurrentFile)
+ buf contain buffer where data must be copied
+ len the size of buf.
+
+ return the number of unsigned char copied if somes unsigned chars are copied
+ return 0 if the end of file was reached
+ return <0 with error code if there is an error
+ (UNZ_ERRNO for IO error, or zLib error for uncompress error)
+*/
+
+extern long unztell(unzFile file);
+
+/*
+ Give the current position in uncompressed data
+*/
+
+extern int unzeof (unzFile file);
+
+/*
+ return 1 if the end of file was reached, 0 elsewhere
+*/
+
+extern int unzGetLocalExtrafield (unzFile file, void* buf, unsigned len);
+
+/*
+ Read extra field from the current file (opened by unzOpenCurrentFile)
+ This is the local-header version of the extra field (sometimes, there is
+ more info in the local-header version than in the central-header)
+
+ if buf==NULL, it return the size of the local extra field
+
+ if buf!=NULL, len is the size of the buffer, the extra header is copied in
+ buf.
+ the return value is the number of unsigned chars copied in buf, or (if <0)
+ the error code
+*/
diff --git a/src/qcommon/vm.c b/src/qcommon/vm.c
new file mode 100644
index 0000000..0017b41
--- /dev/null
+++ b/src/qcommon/vm.c
@@ -0,0 +1,895 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// vm.c -- virtual machine
+
+/*
+
+
+intermix code and data
+symbol table
+
+a dll has one imported function: VM_SystemCall
+and one exported function: Perform
+
+
+*/
+
+#include "vm_local.h"
+
+
+vm_t *currentVM = NULL; // bk001212
+vm_t *lastVM = NULL; // bk001212
+int vm_debugLevel;
+
+#define MAX_VM 3
+vm_t vmTable[MAX_VM];
+
+
+void VM_VmInfo_f( void );
+void VM_VmProfile_f( void );
+
+
+
+#if 0 // 64bit!
+// converts a VM pointer to a C pointer and
+// checks to make sure that the range is acceptable
+void *VM_VM2C( vmptr_t p, int length ) {
+ return (void *)p;
+}
+#endif
+
+void VM_Debug( int level ) {
+ vm_debugLevel = level;
+}
+
+/*
+==============
+VM_Init
+==============
+*/
+void VM_Init( void ) {
+ Cvar_Get( "vm_cgame", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2
+ Cvar_Get( "vm_game", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2
+ Cvar_Get( "vm_ui", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2
+
+ Cmd_AddCommand ("vmprofile", VM_VmProfile_f );
+ Cmd_AddCommand ("vminfo", VM_VmInfo_f );
+
+ Com_Memset( vmTable, 0, sizeof( vmTable ) );
+}
+
+
+/*
+===============
+VM_ValueToSymbol
+
+Assumes a program counter value
+===============
+*/
+const char *VM_ValueToSymbol( vm_t *vm, int value ) {
+ vmSymbol_t *sym;
+ static char text[MAX_TOKEN_CHARS];
+
+ sym = vm->symbols;
+ if ( !sym ) {
+ return "NO SYMBOLS";
+ }
+
+ // find the symbol
+ while ( sym->next && sym->next->symValue <= value ) {
+ sym = sym->next;
+ }
+
+ if ( value == sym->symValue ) {
+ return sym->symName;
+ }
+
+ Com_sprintf( text, sizeof( text ), "%s+%i", sym->symName, value - sym->symValue );
+
+ return text;
+}
+
+/*
+===============
+VM_ValueToFunctionSymbol
+
+For profiling, find the symbol behind this value
+===============
+*/
+vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ) {
+ vmSymbol_t *sym;
+ static vmSymbol_t nullSym;
+
+ sym = vm->symbols;
+ if ( !sym ) {
+ return &nullSym;
+ }
+
+ while ( sym->next && sym->next->symValue <= value ) {
+ sym = sym->next;
+ }
+
+ return sym;
+}
+
+
+/*
+===============
+VM_SymbolToValue
+===============
+*/
+int VM_SymbolToValue( vm_t *vm, const char *symbol ) {
+ vmSymbol_t *sym;
+
+ for ( sym = vm->symbols ; sym ; sym = sym->next ) {
+ if ( !strcmp( symbol, sym->symName ) ) {
+ return sym->symValue;
+ }
+ }
+ return 0;
+}
+
+
+/*
+=====================
+VM_SymbolForCompiledPointer
+=====================
+*/
+#if 0 // 64bit!
+const char *VM_SymbolForCompiledPointer( vm_t *vm, void *code ) {
+ int i;
+
+ if ( code < (void *)vm->codeBase ) {
+ return "Before code block";
+ }
+ if ( code >= (void *)(vm->codeBase + vm->codeLength) ) {
+ return "After code block";
+ }
+
+ // find which original instruction it is after
+ for ( i = 0 ; i < vm->codeLength ; i++ ) {
+ if ( (void *)vm->instructionPointers[i] > code ) {
+ break;
+ }
+ }
+ i--;
+
+ // now look up the bytecode instruction pointer
+ return VM_ValueToSymbol( vm, i );
+}
+#endif
+
+
+
+/*
+===============
+ParseHex
+===============
+*/
+int ParseHex( const char *text ) {
+ int value;
+ int c;
+
+ value = 0;
+ while ( ( c = *text++ ) != 0 ) {
+ if ( c >= '0' && c <= '9' ) {
+ value = value * 16 + c - '0';
+ continue;
+ }
+ if ( c >= 'a' && c <= 'f' ) {
+ value = value * 16 + 10 + c - 'a';
+ continue;
+ }
+ if ( c >= 'A' && c <= 'F' ) {
+ value = value * 16 + 10 + c - 'A';
+ continue;
+ }
+ }
+
+ return value;
+}
+
+/*
+===============
+VM_LoadSymbols
+===============
+*/
+void VM_LoadSymbols( vm_t *vm ) {
+ int len;
+ char *mapfile, *text_p, *token;
+ char name[MAX_QPATH];
+ char symbols[MAX_QPATH];
+ vmSymbol_t **prev, *sym;
+ int count;
+ int value;
+ int chars;
+ int segment;
+ int numInstructions;
+
+ // don't load symbols if not developer
+ if ( !com_developer->integer ) {
+ return;
+ }
+
+ COM_StripExtension(vm->name, name, sizeof(name));
+ Com_sprintf( symbols, sizeof( symbols ), "vm/%s.map", name );
+ len = FS_ReadFile( symbols, (void **)&mapfile );
+ if ( !mapfile ) {
+ Com_Printf( "Couldn't load symbol file: %s\n", symbols );
+ return;
+ }
+
+ numInstructions = vm->instructionPointersLength >> 2;
+
+ // parse the symbols
+ text_p = mapfile;
+ prev = &vm->symbols;
+ count = 0;
+
+ while ( 1 ) {
+ token = COM_Parse( &text_p );
+ if ( !token[0] ) {
+ break;
+ }
+ segment = ParseHex( token );
+ if ( segment ) {
+ COM_Parse( &text_p );
+ COM_Parse( &text_p );
+ continue; // only load code segment values
+ }
+
+ token = COM_Parse( &text_p );
+ if ( !token[0] ) {
+ Com_Printf( "WARNING: incomplete line at end of file\n" );
+ break;
+ }
+ value = ParseHex( token );
+
+ token = COM_Parse( &text_p );
+ if ( !token[0] ) {
+ Com_Printf( "WARNING: incomplete line at end of file\n" );
+ break;
+ }
+ chars = strlen( token );
+ sym = Hunk_Alloc( sizeof( *sym ) + chars, h_high );
+ *prev = sym;
+ prev = &sym->next;
+ sym->next = NULL;
+
+ // convert value from an instruction number to a code offset
+ if ( value >= 0 && value < numInstructions ) {
+ value = vm->instructionPointers[value];
+ }
+
+ sym->symValue = value;
+ Q_strncpyz( sym->symName, token, chars + 1 );
+
+ count++;
+ }
+
+ vm->numSymbols = count;
+ Com_Printf( "%i symbols parsed from %s\n", count, symbols );
+ FS_FreeFile( mapfile );
+}
+
+/*
+============
+VM_DllSyscall
+
+Dlls will call this directly
+
+ rcg010206 The horror; the horror.
+
+ The syscall mechanism relies on stack manipulation to get it's args.
+ This is likely due to C's inability to pass "..." parameters to
+ a function in one clean chunk. On PowerPC Linux, these parameters
+ are not necessarily passed on the stack, so while (&arg[0] == arg)
+ is true, (&arg[1] == 2nd function parameter) is not necessarily
+ accurate, as arg's value might have been stored to the stack or
+ other piece of scratch memory to give it a valid address, but the
+ next parameter might still be sitting in a register.
+
+ Quake's syscall system also assumes that the stack grows downward,
+ and that any needed types can be squeezed, safely, into a signed int.
+
+ This hack below copies all needed values for an argument to a
+ array in memory, so that Quake can get the correct values. This can
+ also be used on systems where the stack grows upwards, as the
+ presumably standard and safe stdargs.h macros are used.
+
+ As for having enough space in a signed int for your datatypes, well,
+ it might be better to wait for DOOM 3 before you start porting. :)
+
+ The original code, while probably still inherently dangerous, seems
+ to work well enough for the platforms it already works on. Rather
+ than add the performance hit for those platforms, the original code
+ is still in use there.
+
+ For speed, we just grab 15 arguments, and don't worry about exactly
+ how many the syscall actually needs; the extra is thrown away.
+
+============
+*/
+intptr_t QDECL VM_DllSyscall( intptr_t arg, ... ) {
+#if !id386
+ // rcg010206 - see commentary above
+ intptr_t args[16];
+ int i;
+ va_list ap;
+
+ args[0] = arg;
+
+ va_start(ap, arg);
+ for (i = 1; i < sizeof (args) / sizeof (args[i]); i++)
+ args[i] = va_arg(ap, intptr_t);
+ va_end(ap);
+
+ return currentVM->systemCall( args );
+#else // original id code
+ return currentVM->systemCall( &arg );
+#endif
+}
+
+/*
+=================
+VM_LoadQVM
+
+Load a .qvm file
+=================
+*/
+vmHeader_t *VM_LoadQVM( vm_t *vm, qboolean alloc ) {
+ int length;
+ int dataLength;
+ int i;
+ char filename[MAX_QPATH];
+ vmHeader_t *header;
+
+ // load the image
+ Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", vm->name );
+ Com_Printf( "Loading vm file %s...\n", filename );
+ length = FS_ReadFile( filename, (void **)&header );
+ if ( !header ) {
+ Com_Printf( "Failed.\n" );
+ VM_Free( vm );
+ return NULL;
+ }
+
+ if( LittleLong( header->vmMagic ) == VM_MAGIC_VER2 ) {
+ Com_Printf( "...which has vmMagic VM_MAGIC_VER2\n" );
+
+ // byte swap the header
+ for ( i = 0 ; i < sizeof( vmHeader_t ) / 4 ; i++ ) {
+ ((int *)header)[i] = LittleLong( ((int *)header)[i] );
+ }
+
+ // validate
+ if ( header->jtrgLength < 0
+ || header->bssLength < 0
+ || header->dataLength < 0
+ || header->litLength < 0
+ || header->codeLength <= 0 ) {
+ VM_Free( vm );
+ Com_Error( ERR_FATAL, "%s has bad header", filename );
+ }
+ } else if( LittleLong( header->vmMagic ) == VM_MAGIC ) {
+ // byte swap the header
+ // sizeof( vmHeader_t ) - sizeof( int ) is the 1.32b vm header size
+ for ( i = 0 ; i < ( sizeof( vmHeader_t ) - sizeof( int ) ) / 4 ; i++ ) {
+ ((int *)header)[i] = LittleLong( ((int *)header)[i] );
+ }
+
+ // validate
+ if ( header->bssLength < 0
+ || header->dataLength < 0
+ || header->litLength < 0
+ || header->codeLength <= 0 ) {
+ VM_Free( vm );
+ Com_Error( ERR_FATAL, "%s has bad header", filename );
+ }
+ } else {
+ VM_Free( vm );
+ Com_Error( ERR_FATAL, "%s does not have a recognisable "
+ "magic number in its header", filename );
+ }
+
+ // round up to next power of 2 so all data operations can
+ // be mask protected
+ dataLength = header->dataLength + header->litLength + header->bssLength;
+ for ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) {
+ }
+ dataLength = 1 << i;
+
+ if( alloc ) {
+ // allocate zero filled space for initialized and uninitialized data
+ vm->dataBase = Hunk_Alloc( dataLength, h_high );
+ vm->dataMask = dataLength - 1;
+ } else {
+ // clear the data
+ Com_Memset( vm->dataBase, 0, dataLength );
+ }
+
+ // copy the intialized data
+ Com_Memcpy( vm->dataBase, (byte *)header + header->dataOffset, header->dataLength + header->litLength );
+
+ // byte swap the longs
+ for ( i = 0 ; i < header->dataLength ; i += 4 ) {
+ *(int *)(vm->dataBase + i) = LittleLong( *(int *)(vm->dataBase + i ) );
+ }
+
+ if( header->vmMagic == VM_MAGIC_VER2 ) {
+ vm->numJumpTableTargets = header->jtrgLength >> 2;
+ Com_Printf( "Loading %d jump table targets\n", vm->numJumpTableTargets );
+
+ if( alloc ) {
+ vm->jumpTableTargets = Hunk_Alloc( header->jtrgLength, h_high );
+ } else {
+ Com_Memset( vm->jumpTableTargets, 0, header->jtrgLength );
+ }
+
+ Com_Memcpy( vm->jumpTableTargets, (byte *)header + header->dataOffset +
+ header->dataLength + header->litLength, header->jtrgLength );
+
+ // byte swap the longs
+ for ( i = 0 ; i < header->jtrgLength ; i += 4 ) {
+ *(int *)(vm->jumpTableTargets + i) = LittleLong( *(int *)(vm->jumpTableTargets + i ) );
+ }
+ }
+
+ return header;
+}
+
+/*
+=================
+VM_Restart
+
+Reload the data, but leave everything else in place
+This allows a server to do a map_restart without changing memory allocation
+=================
+*/
+vm_t *VM_Restart( vm_t *vm ) {
+ vmHeader_t *header;
+
+ // DLL's can't be restarted in place
+ if ( vm->dllHandle ) {
+ char name[MAX_QPATH];
+ intptr_t (*systemCall)( intptr_t *parms );
+
+ systemCall = vm->systemCall;
+ Q_strncpyz( name, vm->name, sizeof( name ) );
+
+ VM_Free( vm );
+
+ vm = VM_Create( name, systemCall, VMI_NATIVE );
+ return vm;
+ }
+
+ // load the image
+ Com_Printf( "VM_Restart()\n" );
+
+ if( !( header = VM_LoadQVM( vm, qfalse ) ) ) {
+ Com_Error( ERR_DROP, "VM_Restart failed.\n" );
+ return NULL;
+ }
+
+ // free the original file
+ FS_FreeFile( header );
+
+ return vm;
+}
+
+/*
+================
+VM_Create
+
+If image ends in .qvm it will be interpreted, otherwise
+it will attempt to load as a system dll
+================
+*/
+
+#define STACK_SIZE 0x20000
+
+vm_t *VM_Create( const char *module, intptr_t (*systemCalls)(intptr_t *),
+ vmInterpret_t interpret ) {
+ vm_t *vm;
+ vmHeader_t *header;
+ int i, remaining;
+
+ if ( !module || !module[0] || !systemCalls ) {
+ Com_Error( ERR_FATAL, "VM_Create: bad parms" );
+ }
+
+ remaining = Hunk_MemoryRemaining();
+
+ // see if we already have the VM
+ for ( i = 0 ; i < MAX_VM ; i++ ) {
+ if (!Q_stricmp(vmTable[i].name, module)) {
+ vm = &vmTable[i];
+ return vm;
+ }
+ }
+
+ // find a free vm
+ for ( i = 0 ; i < MAX_VM ; i++ ) {
+ if ( !vmTable[i].name[0] ) {
+ break;
+ }
+ }
+
+ if ( i == MAX_VM ) {
+ Com_Error( ERR_FATAL, "VM_Create: no free vm_t" );
+ }
+
+ vm = &vmTable[i];
+
+ Q_strncpyz( vm->name, module, sizeof( vm->name ) );
+ vm->systemCall = systemCalls;
+
+ // never allow dll loading with a demo
+ if ( interpret == VMI_NATIVE ) {
+ if ( Cvar_VariableValue( "fs_restrict" ) ) {
+ interpret = VMI_COMPILED;
+ }
+ }
+
+ if ( interpret == VMI_NATIVE ) {
+ // try to load as a system dll
+ Com_Printf( "Loading dll file %s.\n", vm->name );
+ vm->dllHandle = Sys_LoadDll( module, vm->fqpath , &vm->entryPoint, VM_DllSyscall );
+ if ( vm->dllHandle ) {
+ return vm;
+ }
+
+ Com_Printf( "Failed to load dll, looking for qvm.\n" );
+ interpret = VMI_COMPILED;
+ }
+
+ // load the image
+ if( !( header = VM_LoadQVM( vm, qtrue ) ) ) {
+ return NULL;
+ }
+
+ // allocate space for the jump targets, which will be filled in by the compile/prep functions
+ vm->instructionPointersLength = header->instructionCount * 4;
+ vm->instructionPointers = Hunk_Alloc( vm->instructionPointersLength, h_high );
+
+ // copy or compile the instructions
+ vm->codeLength = header->codeLength;
+
+ vm->compiled = qfalse;
+
+#ifdef NO_VM_COMPILED
+ if(interpret >= VMI_COMPILED) {
+ Com_Printf("Architecture doesn't have a bytecode compiler, using interpreter\n");
+ interpret = VMI_BYTECODE;
+ }
+#else
+ if ( interpret >= VMI_COMPILED ) {
+ vm->compiled = qtrue;
+ VM_Compile( vm, header );
+ }
+#endif
+ // VM_Compile may have reset vm->compiled if compilation failed
+ if (!vm->compiled)
+ {
+ VM_PrepareInterpreter( vm, header );
+ }
+
+ // free the original file
+ FS_FreeFile( header );
+
+ // load the map file
+ VM_LoadSymbols( vm );
+
+ // the stack is implicitly at the end of the image
+ vm->programStack = vm->dataMask + 1;
+ vm->stackBottom = vm->programStack - STACK_SIZE;
+
+ Com_Printf("%s loaded in %d bytes on the hunk\n", module, remaining - Hunk_MemoryRemaining());
+
+ return vm;
+}
+
+/*
+==============
+VM_Free
+==============
+*/
+void VM_Free( vm_t *vm ) {
+
+ if(vm->destroy)
+ vm->destroy(vm);
+
+ if ( vm->dllHandle ) {
+ Sys_UnloadDll( vm->dllHandle );
+ Com_Memset( vm, 0, sizeof( *vm ) );
+ }
+#if 0 // now automatically freed by hunk
+ if ( vm->codeBase ) {
+ Z_Free( vm->codeBase );
+ }
+ if ( vm->dataBase ) {
+ Z_Free( vm->dataBase );
+ }
+ if ( vm->instructionPointers ) {
+ Z_Free( vm->instructionPointers );
+ }
+#endif
+ Com_Memset( vm, 0, sizeof( *vm ) );
+
+ currentVM = NULL;
+ lastVM = NULL;
+}
+
+void VM_Clear(void) {
+ int i;
+ for (i=0;i<MAX_VM; i++) {
+ if ( vmTable[i].dllHandle ) {
+ Sys_UnloadDll( vmTable[i].dllHandle );
+ }
+ Com_Memset( &vmTable[i], 0, sizeof( vm_t ) );
+ }
+ currentVM = NULL;
+ lastVM = NULL;
+}
+
+void *VM_ArgPtr( intptr_t intValue ) {
+ if ( !intValue ) {
+ return NULL;
+ }
+ // bk001220 - currentVM is missing on reconnect
+ if ( currentVM==NULL )
+ return NULL;
+
+ if ( currentVM->entryPoint ) {
+ return (void *)(currentVM->dataBase + intValue);
+ }
+ else {
+ return (void *)(currentVM->dataBase + (intValue & currentVM->dataMask));
+ }
+}
+
+void *VM_ExplicitArgPtr( vm_t *vm, intptr_t intValue ) {
+ if ( !intValue ) {
+ return NULL;
+ }
+
+ // bk010124 - currentVM is missing on reconnect here as well?
+ if ( currentVM==NULL )
+ return NULL;
+
+ //
+ if ( vm->entryPoint ) {
+ return (void *)(vm->dataBase + intValue);
+ }
+ else {
+ return (void *)(vm->dataBase + (intValue & vm->dataMask));
+ }
+}
+
+
+/*
+==============
+VM_Call
+
+
+Upon a system call, the stack will look like:
+
+sp+32 parm1
+sp+28 parm0
+sp+24 return value
+sp+20 return address
+sp+16 local1
+sp+14 local0
+sp+12 arg1
+sp+8 arg0
+sp+4 return stack
+sp return address
+
+An interpreted function will immediately execute
+an OP_ENTER instruction, which will subtract space for
+locals from sp
+==============
+*/
+#define MAX_STACK 256
+#define STACK_MASK (MAX_STACK-1)
+
+intptr_t QDECL VM_Call( vm_t *vm, int callnum, ... ) {
+ vm_t *oldVM;
+ intptr_t r;
+ int i;
+
+ if ( !vm ) {
+ Com_Error( ERR_FATAL, "VM_Call with NULL vm" );
+ }
+
+ oldVM = currentVM;
+ currentVM = vm;
+ lastVM = vm;
+
+ if ( vm_debugLevel ) {
+ Com_Printf( "VM_Call( %d )\n", callnum );
+ }
+
+ // if we have a dll loaded, call it directly
+ if ( vm->entryPoint ) {
+ //rcg010207 - see dissertation at top of VM_DllSyscall() in this file.
+ int args[10];
+ va_list ap;
+ va_start(ap, callnum);
+ for (i = 0; i < sizeof (args) / sizeof (args[i]); i++) {
+ args[i] = va_arg(ap, int);
+ }
+ va_end(ap);
+
+ r = vm->entryPoint( callnum, args[0], args[1], args[2], args[3],
+ args[4], args[5], args[6], args[7],
+ args[8], args[9]);
+ } else {
+#if id386 // i386 calling convention doesn't need conversion
+#ifndef NO_VM_COMPILED
+ if ( vm->compiled )
+ r = VM_CallCompiled( vm, (int*)&callnum );
+ else
+#endif
+ r = VM_CallInterpreted( vm, (int*)&callnum );
+#else
+ struct {
+ int callnum;
+ int args[10];
+ } a;
+ va_list ap;
+
+ a.callnum = callnum;
+ va_start(ap, callnum);
+ for (i = 0; i < sizeof (a.args) / sizeof (a.args[0]); i++) {
+ a.args[i] = va_arg(ap, int);
+ }
+ va_end(ap);
+#ifndef NO_VM_COMPILED
+ if ( vm->compiled )
+ r = VM_CallCompiled( vm, &a.callnum );
+ else
+#endif
+ r = VM_CallInterpreted( vm, &a.callnum );
+#endif
+ }
+
+ if ( oldVM != NULL ) // bk001220 - assert(currentVM!=NULL) for oldVM==NULL
+ currentVM = oldVM;
+ return r;
+}
+
+//=================================================================
+
+static int QDECL VM_ProfileSort( const void *a, const void *b ) {
+ vmSymbol_t *sa, *sb;
+
+ sa = *(vmSymbol_t **)a;
+ sb = *(vmSymbol_t **)b;
+
+ if ( sa->profileCount < sb->profileCount ) {
+ return -1;
+ }
+ if ( sa->profileCount > sb->profileCount ) {
+ return 1;
+ }
+ return 0;
+}
+
+/*
+==============
+VM_VmProfile_f
+
+==============
+*/
+void VM_VmProfile_f( void ) {
+ vm_t *vm;
+ vmSymbol_t **sorted, *sym;
+ int i;
+ double total;
+
+ if ( !lastVM ) {
+ return;
+ }
+
+ vm = lastVM;
+
+ if ( !vm->numSymbols ) {
+ return;
+ }
+
+ sorted = Z_Malloc( vm->numSymbols * sizeof( *sorted ) );
+ sorted[0] = vm->symbols;
+ total = sorted[0]->profileCount;
+ for ( i = 1 ; i < vm->numSymbols ; i++ ) {
+ sorted[i] = sorted[i-1]->next;
+ total += sorted[i]->profileCount;
+ }
+
+ qsort( sorted, vm->numSymbols, sizeof( *sorted ), VM_ProfileSort );
+
+ for ( i = 0 ; i < vm->numSymbols ; i++ ) {
+ int perc;
+
+ sym = sorted[i];
+
+ perc = 100 * (float) sym->profileCount / total;
+ Com_Printf( "%2i%% %9i %s\n", perc, sym->profileCount, sym->symName );
+ sym->profileCount = 0;
+ }
+
+ Com_Printf(" %9.0f total\n", total );
+
+ Z_Free( sorted );
+}
+
+/*
+==============
+VM_VmInfo_f
+
+==============
+*/
+void VM_VmInfo_f( void ) {
+ vm_t *vm;
+ int i;
+
+ Com_Printf( "Registered virtual machines:\n" );
+ for ( i = 0 ; i < MAX_VM ; i++ ) {
+ vm = &vmTable[i];
+ if ( !vm->name[0] ) {
+ break;
+ }
+ Com_Printf( "%s : ", vm->name );
+ if ( vm->dllHandle ) {
+ Com_Printf( "native\n" );
+ continue;
+ }
+ if ( vm->compiled ) {
+ Com_Printf( "compiled on load\n" );
+ } else {
+ Com_Printf( "interpreted\n" );
+ }
+ Com_Printf( " code length : %7i\n", vm->codeLength );
+ Com_Printf( " table length: %7i\n", vm->instructionPointersLength );
+ Com_Printf( " data length : %7i\n", vm->dataMask + 1 );
+ }
+}
+
+/*
+===============
+VM_LogSyscalls
+
+Insert calls to this while debugging the vm compiler
+===============
+*/
+void VM_LogSyscalls( int *args ) {
+ static int callnum;
+ static FILE *f;
+
+ if ( !f ) {
+ f = fopen("syscalls.log", "w" );
+ }
+ callnum++;
+ fprintf(f, "%i: %p (%i) = %i %i %i %i\n", callnum, (void*)(args - (int *)currentVM->dataBase),
+ args[0], args[1], args[2], args[3], args[4] );
+}
diff --git a/src/qcommon/vm_interpreted.c b/src/qcommon/vm_interpreted.c
new file mode 100644
index 0000000..35096e5
--- /dev/null
+++ b/src/qcommon/vm_interpreted.c
@@ -0,0 +1,922 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+#include "vm_local.h"
+
+//#define DEBUG_VM
+#ifdef DEBUG_VM // bk001204
+static char *opnames[256] = {
+ "OP_UNDEF",
+
+ "OP_IGNORE",
+
+ "OP_BREAK",
+
+ "OP_ENTER",
+ "OP_LEAVE",
+ "OP_CALL",
+ "OP_PUSH",
+ "OP_POP",
+
+ "OP_CONST",
+
+ "OP_LOCAL",
+
+ "OP_JUMP",
+
+ //-------------------
+
+ "OP_EQ",
+ "OP_NE",
+
+ "OP_LTI",
+ "OP_LEI",
+ "OP_GTI",
+ "OP_GEI",
+
+ "OP_LTU",
+ "OP_LEU",
+ "OP_GTU",
+ "OP_GEU",
+
+ "OP_EQF",
+ "OP_NEF",
+
+ "OP_LTF",
+ "OP_LEF",
+ "OP_GTF",
+ "OP_GEF",
+
+ //-------------------
+
+ "OP_LOAD1",
+ "OP_LOAD2",
+ "OP_LOAD4",
+ "OP_STORE1",
+ "OP_STORE2",
+ "OP_STORE4",
+ "OP_ARG",
+
+ "OP_BLOCK_COPY",
+
+ //-------------------
+
+ "OP_SEX8",
+ "OP_SEX16",
+
+ "OP_NEGI",
+ "OP_ADD",
+ "OP_SUB",
+ "OP_DIVI",
+ "OP_DIVU",
+ "OP_MODI",
+ "OP_MODU",
+ "OP_MULI",
+ "OP_MULU",
+
+ "OP_BAND",
+ "OP_BOR",
+ "OP_BXOR",
+ "OP_BCOM",
+
+ "OP_LSH",
+ "OP_RSHI",
+ "OP_RSHU",
+
+ "OP_NEGF",
+ "OP_ADDF",
+ "OP_SUBF",
+ "OP_DIVF",
+ "OP_MULF",
+
+ "OP_CVIF",
+ "OP_CVFI"
+};
+#endif
+
+#if idppc
+
+//FIXME: these, um... look the same to me
+#if defined(__GNUC__)
+static ID_INLINE unsigned int loadWord(void *addr) {
+ unsigned int word;
+
+ asm("lwbrx %0,0,%1" : "=r" (word) : "r" (addr));
+ return word;
+}
+#else
+static ID_INLINE unsigned int __lwbrx(register void *addr,
+ register int offset) {
+ register unsigned int word;
+
+ asm("lwbrx %0,%2,%1" : "=r" (word) : "r" (addr), "b" (offset));
+ return word;
+}
+#define loadWord(addr) __lwbrx(addr,0)
+#endif
+
+#else
+ static ID_INLINE int loadWord(void *addr) {
+ int word;
+ memcpy(&word, addr, 4);
+ return LittleLong(word);
+ }
+#endif
+
+char *VM_Indent( vm_t *vm ) {
+ static char *string = " ";
+ if ( vm->callLevel > 20 ) {
+ return string;
+ }
+ return string + 2 * ( 20 - vm->callLevel );
+}
+
+void VM_StackTrace( vm_t *vm, int programCounter, int programStack ) {
+ int count;
+
+ count = 0;
+ do {
+ Com_Printf( "%s\n", VM_ValueToSymbol( vm, programCounter ) );
+ programStack = *(int *)&vm->dataBase[programStack+4];
+ programCounter = *(int *)&vm->dataBase[programStack];
+ } while ( programCounter != -1 && ++count < 32 );
+
+}
+
+
+/*
+====================
+VM_PrepareInterpreter
+====================
+*/
+void VM_PrepareInterpreter( vm_t *vm, vmHeader_t *header ) {
+ int op;
+ int pc;
+ byte *code;
+ int instruction;
+ int *codeBase;
+
+ vm->codeBase = Hunk_Alloc( vm->codeLength*4, h_high ); // we're now int aligned
+// memcpy( vm->codeBase, (byte *)header + header->codeOffset, vm->codeLength );
+
+ // we don't need to translate the instructions, but we still need
+ // to find each instructions starting point for jumps
+ pc = 0;
+ instruction = 0;
+ code = (byte *)header + header->codeOffset;
+ codeBase = (int *)vm->codeBase;
+
+ while ( instruction < header->instructionCount ) {
+ vm->instructionPointers[ instruction ] = pc;
+ instruction++;
+
+ op = code[ pc ];
+ codeBase[pc] = op;
+ if ( pc > header->codeLength ) {
+ Com_Error( ERR_FATAL, "VM_PrepareInterpreter: pc > header->codeLength" );
+ }
+
+ pc++;
+
+ // these are the only opcodes that aren't a single byte
+ switch ( op ) {
+ case OP_ENTER:
+ case OP_CONST:
+ case OP_LOCAL:
+ case OP_LEAVE:
+ case OP_EQ:
+ case OP_NE:
+ case OP_LTI:
+ case OP_LEI:
+ case OP_GTI:
+ case OP_GEI:
+ case OP_LTU:
+ case OP_LEU:
+ case OP_GTU:
+ case OP_GEU:
+ case OP_EQF:
+ case OP_NEF:
+ case OP_LTF:
+ case OP_LEF:
+ case OP_GTF:
+ case OP_GEF:
+ case OP_BLOCK_COPY:
+ codeBase[pc+0] = loadWord(&code[pc]);
+ pc += 4;
+ break;
+ case OP_ARG:
+ codeBase[pc+0] = code[pc];
+ pc += 1;
+ break;
+ default:
+ break;
+ }
+
+ }
+ pc = 0;
+ instruction = 0;
+ code = (byte *)header + header->codeOffset;
+ codeBase = (int *)vm->codeBase;
+
+ while ( instruction < header->instructionCount ) {
+ op = code[ pc ];
+ instruction++;
+ pc++;
+ switch ( op ) {
+ case OP_ENTER:
+ case OP_CONST:
+ case OP_LOCAL:
+ case OP_LEAVE:
+ case OP_EQ:
+ case OP_NE:
+ case OP_LTI:
+ case OP_LEI:
+ case OP_GTI:
+ case OP_GEI:
+ case OP_LTU:
+ case OP_LEU:
+ case OP_GTU:
+ case OP_GEU:
+ case OP_EQF:
+ case OP_NEF:
+ case OP_LTF:
+ case OP_LEF:
+ case OP_GTF:
+ case OP_GEF:
+ case OP_BLOCK_COPY:
+ switch(op) {
+ case OP_EQ:
+ case OP_NE:
+ case OP_LTI:
+ case OP_LEI:
+ case OP_GTI:
+ case OP_GEI:
+ case OP_LTU:
+ case OP_LEU:
+ case OP_GTU:
+ case OP_GEU:
+ case OP_EQF:
+ case OP_NEF:
+ case OP_LTF:
+ case OP_LEF:
+ case OP_GTF:
+ case OP_GEF:
+ codeBase[pc] = vm->instructionPointers[codeBase[pc]];
+ break;
+ default:
+ break;
+ }
+ pc += 4;
+ break;
+ case OP_ARG:
+ pc += 1;
+ break;
+ default:
+ break;
+ }
+
+ }
+}
+
+/*
+==============
+VM_Call
+
+
+Upon a system call, the stack will look like:
+
+sp+32 parm1
+sp+28 parm0
+sp+24 return stack
+sp+20 return address
+sp+16 local1
+sp+14 local0
+sp+12 arg1
+sp+8 arg0
+sp+4 return stack
+sp return address
+
+An interpreted function will immediately execute
+an OP_ENTER instruction, which will subtract space for
+locals from sp
+==============
+*/
+#define MAX_STACK 256
+#define STACK_MASK (MAX_STACK-1)
+
+#define DEBUGSTR va("%s%i", VM_Indent(vm), opStack-stack )
+
+int VM_CallInterpreted( vm_t *vm, int *args ) {
+ int stack[MAX_STACK];
+ int *opStack;
+ int programCounter;
+ int programStack;
+ int stackOnEntry;
+ byte *image;
+ int *codeImage;
+ int v1;
+ int dataMask;
+#ifdef DEBUG_VM
+ vmSymbol_t *profileSymbol;
+#endif
+
+ // interpret the code
+ vm->currentlyInterpreting = qtrue;
+
+ // we might be called recursively, so this might not be the very top
+ programStack = stackOnEntry = vm->programStack;
+
+#ifdef DEBUG_VM
+ profileSymbol = VM_ValueToFunctionSymbol( vm, 0 );
+ // uncomment this for debugging breakpoints
+ vm->breakFunction = 0;
+#endif
+ // set up the stack frame
+
+ image = vm->dataBase;
+ codeImage = (int *)vm->codeBase;
+ dataMask = vm->dataMask;
+
+ // leave a free spot at start of stack so
+ // that as long as opStack is valid, opStack-1 will
+ // not corrupt anything
+ opStack = stack;
+ programCounter = 0;
+
+ programStack -= 48;
+
+ *(int *)&image[ programStack + 44] = args[9];
+ *(int *)&image[ programStack + 40] = args[8];
+ *(int *)&image[ programStack + 36] = args[7];
+ *(int *)&image[ programStack + 32] = args[6];
+ *(int *)&image[ programStack + 28] = args[5];
+ *(int *)&image[ programStack + 24] = args[4];
+ *(int *)&image[ programStack + 20] = args[3];
+ *(int *)&image[ programStack + 16] = args[2];
+ *(int *)&image[ programStack + 12] = args[1];
+ *(int *)&image[ programStack + 8 ] = args[0];
+ *(int *)&image[ programStack + 4 ] = 0; // return stack
+ *(int *)&image[ programStack ] = -1; // will terminate the loop on return
+
+ vm->callLevel = 0;
+
+ VM_Debug(0);
+
+// vm_debugLevel=2;
+ // main interpreter loop, will exit when a LEAVE instruction
+ // grabs the -1 program counter
+
+#define r2 codeImage[programCounter]
+
+ while ( 1 ) {
+ int opcode, r0, r1;
+// unsigned int r2;
+
+nextInstruction:
+ r0 = ((int *)opStack)[0];
+ r1 = ((int *)opStack)[-1];
+nextInstruction2:
+#ifdef DEBUG_VM
+ if ( (unsigned)programCounter >= vm->codeLength ) {
+ Com_Error( ERR_DROP, "VM pc out of range" );
+ }
+
+ if ( opStack < stack ) {
+ Com_Error( ERR_DROP, "VM opStack underflow" );
+ }
+ if ( opStack >= stack+MAX_STACK ) {
+ Com_Error( ERR_DROP, "VM opStack overflow" );
+ }
+
+ if ( programStack <= vm->stackBottom ) {
+ Com_Error( ERR_DROP, "VM stack overflow" );
+ }
+
+ if ( programStack & 3 ) {
+ Com_Error( ERR_DROP, "VM program stack misaligned" );
+ }
+
+ if ( vm_debugLevel > 1 ) {
+ Com_Printf( "%s %s\n", DEBUGSTR, opnames[opcode] );
+ }
+ profileSymbol->profileCount++;
+#endif
+ opcode = codeImage[ programCounter++ ];
+
+ switch ( opcode ) {
+#ifdef DEBUG_VM
+ default:
+ Com_Error( ERR_DROP, "Bad VM instruction" ); // this should be scanned on load!
+#endif
+ case OP_BREAK:
+ vm->breakCount++;
+ goto nextInstruction2;
+ case OP_CONST:
+ opStack++;
+ r1 = r0;
+ r0 = *opStack = r2;
+
+ programCounter += 4;
+ goto nextInstruction2;
+ case OP_LOCAL:
+ opStack++;
+ r1 = r0;
+ r0 = *opStack = r2+programStack;
+
+ programCounter += 4;
+ goto nextInstruction2;
+
+ case OP_LOAD4:
+#ifdef DEBUG_VM
+ if ( *opStack & 3 ) {
+ Com_Error( ERR_DROP, "OP_LOAD4 misaligned" );
+ }
+#endif
+ r0 = *opStack = *(int *)&image[ r0&dataMask ];
+ goto nextInstruction2;
+ case OP_LOAD2:
+ r0 = *opStack = *(unsigned short *)&image[ r0&dataMask ];
+ goto nextInstruction2;
+ case OP_LOAD1:
+ r0 = *opStack = image[ r0&dataMask ];
+ goto nextInstruction2;
+
+ case OP_STORE4:
+ *(int *)&image[ r1&(dataMask & ~3) ] = r0;
+ opStack -= 2;
+ goto nextInstruction;
+ case OP_STORE2:
+ *(short *)&image[ r1&(dataMask & ~1) ] = r0;
+ opStack -= 2;
+ goto nextInstruction;
+ case OP_STORE1:
+ image[ r1&dataMask ] = r0;
+ opStack -= 2;
+ goto nextInstruction;
+
+ case OP_ARG:
+ // single byte offset from programStack
+ *(int *)&image[ codeImage[programCounter] + programStack ] = r0;
+ opStack--;
+ programCounter += 1;
+ goto nextInstruction;
+
+ case OP_BLOCK_COPY:
+ {
+ int *src, *dest;
+ int i, count, srci, desti;
+
+ count = r2;
+ // MrE: copy range check
+ srci = r0 & dataMask;
+ desti = r1 & dataMask;
+ count = ((srci + count) & dataMask) - srci;
+ count = ((desti + count) & dataMask) - desti;
+
+ src = (int *)&image[ r0&dataMask ];
+ dest = (int *)&image[ r1&dataMask ];
+ if ( ( (intptr_t)src | (intptr_t)dest | count ) & 3 ) {
+ // happens in westernq3
+ Com_Printf( S_COLOR_YELLOW "Warning: OP_BLOCK_COPY not dword aligned\n");
+ }
+ count >>= 2;
+ for ( i = count-1 ; i>= 0 ; i-- ) {
+ dest[i] = src[i];
+ }
+ programCounter += 4;
+ opStack -= 2;
+ }
+ goto nextInstruction;
+
+ case OP_CALL:
+ // save current program counter
+ *(int *)&image[ programStack ] = programCounter;
+
+ // jump to the location on the stack
+ programCounter = r0;
+ opStack--;
+ if ( programCounter < 0 ) {
+ // system call
+ int r;
+ int temp;
+#ifdef DEBUG_VM
+ int stomped;
+
+ if ( vm_debugLevel ) {
+ Com_Printf( "%s---> systemcall(%i)\n", DEBUGSTR, -1 - programCounter );
+ }
+#endif
+ // save the stack to allow recursive VM entry
+ temp = vm->callLevel;
+ vm->programStack = programStack - 4;
+#ifdef DEBUG_VM
+ stomped = *(int *)&image[ programStack + 4 ];
+#endif
+ *(int *)&image[ programStack + 4 ] = -1 - programCounter;
+
+//VM_LogSyscalls( (int *)&image[ programStack + 4 ] );
+ {
+ intptr_t* argptr = (intptr_t *)&image[ programStack + 4 ];
+ #if __WORDSIZE == 64
+ // the vm has ints on the stack, we expect
+ // longs so we have to convert it
+ intptr_t argarr[16];
+ int i;
+ for (i = 0; i < 16; ++i) {
+ argarr[i] = *(int*)&image[ programStack + 4 + 4*i ];
+ }
+ argptr = argarr;
+ #endif
+ r = vm->systemCall( argptr );
+ }
+
+#ifdef DEBUG_VM
+ // this is just our stack frame pointer, only needed
+ // for debugging
+ *(int *)&image[ programStack + 4 ] = stomped;
+#endif
+
+ // save return value
+ opStack++;
+ *opStack = r;
+ programCounter = *(int *)&image[ programStack ];
+ vm->callLevel = temp;
+#ifdef DEBUG_VM
+ if ( vm_debugLevel ) {
+ Com_Printf( "%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter ) );
+ }
+#endif
+ } else if ( (unsigned)programCounter >= vm->codeLength ) {
+ Com_Error( ERR_DROP, "VM program counter out of range in OP_CALL" );
+ } else {
+ programCounter = vm->instructionPointers[ programCounter ];
+ }
+ goto nextInstruction;
+
+ // push and pop are only needed for discarded or bad function return values
+ case OP_PUSH:
+ opStack++;
+ goto nextInstruction;
+ case OP_POP:
+ opStack--;
+ goto nextInstruction;
+
+ case OP_ENTER:
+#ifdef DEBUG_VM
+ profileSymbol = VM_ValueToFunctionSymbol( vm, programCounter );
+#endif
+ // get size of stack frame
+ v1 = r2;
+
+ programCounter += 4;
+ programStack -= v1;
+#ifdef DEBUG_VM
+ // save old stack frame for debugging traces
+ *(int *)&image[programStack+4] = programStack + v1;
+ if ( vm_debugLevel ) {
+ Com_Printf( "%s---> %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter - 5 ) );
+ if ( vm->breakFunction && programCounter - 5 == vm->breakFunction ) {
+ // this is to allow setting breakpoints here in the debugger
+ vm->breakCount++;
+// vm_debugLevel = 2;
+// VM_StackTrace( vm, programCounter, programStack );
+ }
+ vm->callLevel++;
+ }
+#endif
+ goto nextInstruction;
+ case OP_LEAVE:
+ // remove our stack frame
+ v1 = r2;
+
+ programStack += v1;
+
+ // grab the saved program counter
+ programCounter = *(int *)&image[ programStack ];
+#ifdef DEBUG_VM
+ profileSymbol = VM_ValueToFunctionSymbol( vm, programCounter );
+ if ( vm_debugLevel ) {
+ vm->callLevel--;
+ Com_Printf( "%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter ) );
+ }
+#endif
+ // check for leaving the VM
+ if ( programCounter == -1 ) {
+ goto done;
+ } else if ( (unsigned)programCounter >= vm->codeLength ) {
+ Com_Error( ERR_DROP, "VM program counter out of range in OP_LEAVE" );
+ }
+ goto nextInstruction;
+
+ /*
+ ===================================================================
+ BRANCHES
+ ===================================================================
+ */
+
+ case OP_JUMP:
+ programCounter = r0;
+ programCounter = vm->instructionPointers[ programCounter ];
+ opStack--;
+ goto nextInstruction;
+
+ case OP_EQ:
+ opStack -= 2;
+ if ( r1 == r0 ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 4;
+ goto nextInstruction;
+ }
+
+ case OP_NE:
+ opStack -= 2;
+ if ( r1 != r0 ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 4;
+ goto nextInstruction;
+ }
+
+ case OP_LTI:
+ opStack -= 2;
+ if ( r1 < r0 ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 4;
+ goto nextInstruction;
+ }
+
+ case OP_LEI:
+ opStack -= 2;
+ if ( r1 <= r0 ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 4;
+ goto nextInstruction;
+ }
+
+ case OP_GTI:
+ opStack -= 2;
+ if ( r1 > r0 ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 4;
+ goto nextInstruction;
+ }
+
+ case OP_GEI:
+ opStack -= 2;
+ if ( r1 >= r0 ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 4;
+ goto nextInstruction;
+ }
+
+ case OP_LTU:
+ opStack -= 2;
+ if ( ((unsigned)r1) < ((unsigned)r0) ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 4;
+ goto nextInstruction;
+ }
+
+ case OP_LEU:
+ opStack -= 2;
+ if ( ((unsigned)r1) <= ((unsigned)r0) ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 4;
+ goto nextInstruction;
+ }
+
+ case OP_GTU:
+ opStack -= 2;
+ if ( ((unsigned)r1) > ((unsigned)r0) ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 4;
+ goto nextInstruction;
+ }
+
+ case OP_GEU:
+ opStack -= 2;
+ if ( ((unsigned)r1) >= ((unsigned)r0) ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 4;
+ goto nextInstruction;
+ }
+
+ case OP_EQF:
+ if ( ((float *)opStack)[-1] == *(float *)opStack ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ opStack -= 2;
+ goto nextInstruction;
+ } else {
+ programCounter += 4;
+ opStack -= 2;
+ goto nextInstruction;
+ }
+
+ case OP_NEF:
+ if ( ((float *)opStack)[-1] != *(float *)opStack ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ opStack -= 2;
+ goto nextInstruction;
+ } else {
+ programCounter += 4;
+ opStack -= 2;
+ goto nextInstruction;
+ }
+
+ case OP_LTF:
+ if ( ((float *)opStack)[-1] < *(float *)opStack ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ opStack -= 2;
+ goto nextInstruction;
+ } else {
+ programCounter += 4;
+ opStack -= 2;
+ goto nextInstruction;
+ }
+
+ case OP_LEF:
+ if ( ((float *)opStack)[-1] <= *(float *)opStack ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ opStack -= 2;
+ goto nextInstruction;
+ } else {
+ programCounter += 4;
+ opStack -= 2;
+ goto nextInstruction;
+ }
+
+ case OP_GTF:
+ if ( ((float *)opStack)[-1] > *(float *)opStack ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ opStack -= 2;
+ goto nextInstruction;
+ } else {
+ programCounter += 4;
+ opStack -= 2;
+ goto nextInstruction;
+ }
+
+ case OP_GEF:
+ if ( ((float *)opStack)[-1] >= *(float *)opStack ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ opStack -= 2;
+ goto nextInstruction;
+ } else {
+ programCounter += 4;
+ opStack -= 2;
+ goto nextInstruction;
+ }
+
+
+ //===================================================================
+
+ case OP_NEGI:
+ *opStack = -r0;
+ goto nextInstruction;
+ case OP_ADD:
+ opStack[-1] = r1 + r0;
+ opStack--;
+ goto nextInstruction;
+ case OP_SUB:
+ opStack[-1] = r1 - r0;
+ opStack--;
+ goto nextInstruction;
+ case OP_DIVI:
+ opStack[-1] = r1 / r0;
+ opStack--;
+ goto nextInstruction;
+ case OP_DIVU:
+ opStack[-1] = ((unsigned)r1) / ((unsigned)r0);
+ opStack--;
+ goto nextInstruction;
+ case OP_MODI:
+ opStack[-1] = r1 % r0;
+ opStack--;
+ goto nextInstruction;
+ case OP_MODU:
+ opStack[-1] = ((unsigned)r1) % (unsigned)r0;
+ opStack--;
+ goto nextInstruction;
+ case OP_MULI:
+ opStack[-1] = r1 * r0;
+ opStack--;
+ goto nextInstruction;
+ case OP_MULU:
+ opStack[-1] = ((unsigned)r1) * ((unsigned)r0);
+ opStack--;
+ goto nextInstruction;
+
+ case OP_BAND:
+ opStack[-1] = ((unsigned)r1) & ((unsigned)r0);
+ opStack--;
+ goto nextInstruction;
+ case OP_BOR:
+ opStack[-1] = ((unsigned)r1) | ((unsigned)r0);
+ opStack--;
+ goto nextInstruction;
+ case OP_BXOR:
+ opStack[-1] = ((unsigned)r1) ^ ((unsigned)r0);
+ opStack--;
+ goto nextInstruction;
+ case OP_BCOM:
+ *opStack = ~ ((unsigned)r0);
+ goto nextInstruction;
+
+ case OP_LSH:
+ opStack[-1] = r1 << r0;
+ opStack--;
+ goto nextInstruction;
+ case OP_RSHI:
+ opStack[-1] = r1 >> r0;
+ opStack--;
+ goto nextInstruction;
+ case OP_RSHU:
+ opStack[-1] = ((unsigned)r1) >> r0;
+ opStack--;
+ goto nextInstruction;
+
+ case OP_NEGF:
+ *(float *)opStack = -*(float *)opStack;
+ goto nextInstruction;
+ case OP_ADDF:
+ *(float *)(opStack-1) = *(float *)(opStack-1) + *(float *)opStack;
+ opStack--;
+ goto nextInstruction;
+ case OP_SUBF:
+ *(float *)(opStack-1) = *(float *)(opStack-1) - *(float *)opStack;
+ opStack--;
+ goto nextInstruction;
+ case OP_DIVF:
+ *(float *)(opStack-1) = *(float *)(opStack-1) / *(float *)opStack;
+ opStack--;
+ goto nextInstruction;
+ case OP_MULF:
+ *(float *)(opStack-1) = *(float *)(opStack-1) * *(float *)opStack;
+ opStack--;
+ goto nextInstruction;
+
+ case OP_CVIF:
+ *(float *)opStack = (float)*opStack;
+ goto nextInstruction;
+ case OP_CVFI:
+ *opStack = (int) *(float *)opStack;
+ goto nextInstruction;
+ case OP_SEX8:
+ *opStack = (signed char)*opStack;
+ goto nextInstruction;
+ case OP_SEX16:
+ *opStack = (short)*opStack;
+ goto nextInstruction;
+ }
+ }
+
+done:
+ vm->currentlyInterpreting = qfalse;
+
+ if ( opStack != &stack[1] ) {
+ Com_Error( ERR_DROP, "Interpreter error: opStack = %ld", (long int) (opStack - stack) );
+ }
+
+ vm->programStack = stackOnEntry;
+
+ // return the result
+ return *opStack;
+}
diff --git a/src/qcommon/vm_local.h b/src/qcommon/vm_local.h
new file mode 100644
index 0000000..27b5226
--- /dev/null
+++ b/src/qcommon/vm_local.h
@@ -0,0 +1,185 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+#include "q_shared.h"
+#include "qcommon.h"
+
+typedef enum {
+ OP_UNDEF,
+
+ OP_IGNORE,
+
+ OP_BREAK,
+
+ OP_ENTER,
+ OP_LEAVE,
+ OP_CALL,
+ OP_PUSH,
+ OP_POP,
+
+ OP_CONST,
+ OP_LOCAL,
+
+ OP_JUMP,
+
+ //-------------------
+
+ OP_EQ,
+ OP_NE,
+
+ OP_LTI,
+ OP_LEI,
+ OP_GTI,
+ OP_GEI,
+
+ OP_LTU,
+ OP_LEU,
+ OP_GTU,
+ OP_GEU,
+
+ OP_EQF,
+ OP_NEF,
+
+ OP_LTF,
+ OP_LEF,
+ OP_GTF,
+ OP_GEF,
+
+ //-------------------
+
+ OP_LOAD1,
+ OP_LOAD2,
+ OP_LOAD4,
+ OP_STORE1,
+ OP_STORE2,
+ OP_STORE4, // *(stack[top-1]) = stack[top]
+ OP_ARG,
+
+ OP_BLOCK_COPY,
+
+ //-------------------
+
+ OP_SEX8,
+ OP_SEX16,
+
+ OP_NEGI,
+ OP_ADD,
+ OP_SUB,
+ OP_DIVI,
+ OP_DIVU,
+ OP_MODI,
+ OP_MODU,
+ OP_MULI,
+ OP_MULU,
+
+ OP_BAND,
+ OP_BOR,
+ OP_BXOR,
+ OP_BCOM,
+
+ OP_LSH,
+ OP_RSHI,
+ OP_RSHU,
+
+ OP_NEGF,
+ OP_ADDF,
+ OP_SUBF,
+ OP_DIVF,
+ OP_MULF,
+
+ OP_CVIF,
+ OP_CVFI
+} opcode_t;
+
+
+
+typedef int vmptr_t;
+
+typedef struct vmSymbol_s {
+ struct vmSymbol_s *next;
+ int symValue;
+ int profileCount;
+ char symName[1]; // variable sized
+} vmSymbol_t;
+
+#define VM_OFFSET_PROGRAM_STACK 0
+#define VM_OFFSET_SYSTEM_CALL 4
+
+struct vm_s {
+ // DO NOT MOVE OR CHANGE THESE WITHOUT CHANGING THE VM_OFFSET_* DEFINES
+ // USED BY THE ASM CODE
+ int programStack; // the vm may be recursively entered
+ intptr_t (*systemCall)( intptr_t *parms );
+
+ //------------------------------------
+
+ char name[MAX_QPATH];
+
+ // for dynamic linked modules
+ void *dllHandle;
+ intptr_t (QDECL *entryPoint)( int callNum, ... );
+ void (*destroy)(vm_t* self);
+
+ // for interpreted modules
+ qboolean currentlyInterpreting;
+
+ qboolean compiled;
+ byte *codeBase;
+ int codeLength;
+
+ int *instructionPointers;
+ int instructionPointersLength;
+
+ byte *dataBase;
+ int dataMask;
+
+ int stackBottom; // if programStack < stackBottom, error
+
+ int numSymbols;
+ struct vmSymbol_s *symbols;
+
+ int callLevel; // for debug indenting
+ int breakFunction; // increment breakCount on function entry to this
+ int breakCount;
+
+// fqpath member added 7/20/02 by T.Ray
+ char fqpath[MAX_QPATH+1] ;
+
+ byte *jumpTableTargets;
+ int numJumpTableTargets;
+};
+
+
+extern vm_t *currentVM;
+extern int vm_debugLevel;
+
+void VM_Compile( vm_t *vm, vmHeader_t *header );
+int VM_CallCompiled( vm_t *vm, int *args );
+
+void VM_PrepareInterpreter( vm_t *vm, vmHeader_t *header );
+int VM_CallInterpreted( vm_t *vm, int *args );
+
+vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value );
+int VM_SymbolToValue( vm_t *vm, const char *symbol );
+const char *VM_ValueToSymbol( vm_t *vm, int value );
+void VM_LogSyscalls( int *args );
+
diff --git a/src/qcommon/vm_none.c b/src/qcommon/vm_none.c
new file mode 100644
index 0000000..c7e5ba0
--- /dev/null
+++ b/src/qcommon/vm_none.c
@@ -0,0 +1,10 @@
+#include "vm_local.h"
+
+int VM_CallCompiled( vm_t *vm, int *args ) {
+ exit(99);
+ return 0;
+}
+
+void VM_Compile( vm_t *vm, vmHeader_t *header ) {
+ exit(99);
+}
diff --git a/src/qcommon/vm_ppc.c b/src/qcommon/vm_ppc.c
new file mode 100644
index 0000000..460fe42
--- /dev/null
+++ b/src/qcommon/vm_ppc.c
@@ -0,0 +1,2065 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// vm_ppc.c
+// ppc dynamic compiler
+
+#include "vm_local.h"
+#include <sys/mman.h>
+
+#define DEBUG_VM 0
+
+#if DEBUG_VM
+static char *opnames[256] = {
+ "OP_UNDEF",
+
+ "OP_IGNORE",
+
+ "OP_BREAK",
+
+ "OP_ENTER",
+ "OP_LEAVE",
+ "OP_CALL",
+ "OP_PUSH",
+ "OP_POP",
+
+ "OP_CONST",
+
+ "OP_LOCAL",
+
+ "OP_JUMP",
+
+ //-------------------
+
+ "OP_EQ",
+ "OP_NE",
+
+ "OP_LTI",
+ "OP_LEI",
+ "OP_GTI",
+ "OP_GEI",
+
+ "OP_LTU",
+ "OP_LEU",
+ "OP_GTU",
+ "OP_GEU",
+
+ "OP_EQF",
+ "OP_NEF",
+
+ "OP_LTF",
+ "OP_LEF",
+ "OP_GTF",
+ "OP_GEF",
+
+ //-------------------
+
+ "OP_LOAD1",
+ "OP_LOAD2",
+ "OP_LOAD4",
+ "OP_STORE1",
+ "OP_STORE2",
+ "OP_STORE4",
+ "OP_ARG",
+
+ "OP_BLOCK_COPY",
+
+ //-------------------
+
+ "OP_SEX8",
+ "OP_SEX16",
+
+ "OP_NEGI",
+ "OP_ADD",
+ "OP_SUB",
+ "OP_DIVI",
+ "OP_DIVU",
+ "OP_MODI",
+ "OP_MODU",
+ "OP_MULI",
+ "OP_MULU",
+
+ "OP_BAND",
+ "OP_BOR",
+ "OP_BXOR",
+ "OP_BCOM",
+
+ "OP_LSH",
+ "OP_RSHI",
+ "OP_RSHU",
+
+ "OP_NEGF",
+ "OP_ADDF",
+ "OP_SUBF",
+ "OP_DIVF",
+ "OP_MULF",
+
+ "OP_CVIF",
+ "OP_CVFI"
+};
+#endif
+
+typedef enum {
+ R_REAL_STACK = 1,
+ // registers 3-11 are the parameter passing registers
+
+ // state
+ R_STACK = 3, // local
+ R_OPSTACK, // global
+
+ // constants
+ R_MEMBASE, // global
+ R_MEMMASK,
+ R_ASMCALL, // global
+ R_INSTRUCTIONS, // global
+ R_NUM_INSTRUCTIONS, // global
+ R_CVM, // currentVM
+
+ // temps
+ R_TOP = 11,
+ R_SECOND = 12,
+ R_EA = 2 // effective address calculation
+
+} regNums_t;
+
+#define RG_REAL_STACK r1
+#define RG_STACK r3
+#define RG_OPSTACK r4
+#define RG_MEMBASE r5
+#define RG_MEMMASK r6
+#define RG_ASMCALL r7
+#define RG_INSTRUCTIONS r8
+#define RG_NUM_INSTRUCTIONS r9
+#define RG_CVM r10
+#define RG_TOP r12
+#define RG_SECOND r13
+#define RG_EA r14
+
+// The deepest value I saw in the Quake3 games was 9.
+#define OP_STACK_MAX_DEPTH 16
+
+// These are all volatile and thus must be saved upon entry to the VM code.
+// NOTE: These are General Purpose Registers (GPR) numbers like the
+// R_ definitions in the regNums_t enum above (31 is the max)
+static int opStackIntRegisters[OP_STACK_MAX_DEPTH] =
+{
+ 16, 17, 18, 19,
+ 20, 21, 22, 23,
+ 24, 25, 26, 27,
+ 28, 29, 30, 31
+};
+
+static unsigned int *opStackLoadInstructionAddr[OP_STACK_MAX_DEPTH];
+
+// We use different registers for the floating point
+// operand stack (these are volatile in the PPC ABI)
+// NOTE: these are Floating Point Register (FPR) numbers, not
+// General Purpose Register (GPR) numbers
+static int opStackFloatRegisters[OP_STACK_MAX_DEPTH] =
+{
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 12, 13, 14, 15
+};
+
+static int opStackRegType[OP_STACK_MAX_DEPTH] =
+{
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0
+};
+
+// this doesn't have the low order bits set for instructions i'm not using...
+typedef enum {
+ PPC_TDI = 0x08000000,
+ PPC_TWI = 0x0c000000,
+ PPC_MULLI = 0x1c000000,
+ PPC_SUBFIC = 0x20000000,
+ PPC_CMPI = 0x28000000,
+ PPC_CMPLI = 0x2c000000,
+ PPC_ADDIC = 0x30000000,
+ PPC_ADDIC_ = 0x34000000,
+ PPC_ADDI = 0x38000000,
+ PPC_ADDIS = 0x3c000000,
+ PPC_BC = 0x40000000,
+ PPC_SC = 0x44000000,
+ PPC_B = 0x48000000,
+
+ PPC_MCRF = 0x4c000000,
+ PPC_BCLR = 0x4c000020,
+ PPC_RFID = 0x4c000000,
+ PPC_CRNOR = 0x4c000000,
+ PPC_RFI = 0x4c000000,
+ PPC_CRANDC = 0x4c000000,
+ PPC_ISYNC = 0x4c000000,
+ PPC_CRXOR = 0x4c000000,
+ PPC_CRNAND = 0x4c000000,
+ PPC_CREQV = 0x4c000000,
+ PPC_CRORC = 0x4c000000,
+ PPC_CROR = 0x4c000000,
+//------------
+ PPC_BCCTR = 0x4c000420,
+ PPC_RLWIMI = 0x50000000,
+ PPC_RLWINM = 0x54000000,
+ PPC_RLWNM = 0x5c000000,
+ PPC_ORI = 0x60000000,
+ PPC_ORIS = 0x64000000,
+ PPC_XORI = 0x68000000,
+ PPC_XORIS = 0x6c000000,
+ PPC_ANDI_ = 0x70000000,
+ PPC_ANDIS_ = 0x74000000,
+ PPC_RLDICL = 0x78000000,
+ PPC_RLDICR = 0x78000000,
+ PPC_RLDIC = 0x78000000,
+ PPC_RLDIMI = 0x78000000,
+ PPC_RLDCL = 0x78000000,
+ PPC_RLDCR = 0x78000000,
+ PPC_CMP = 0x7c000000,
+ PPC_TW = 0x7c000000,
+ PPC_SUBFC = 0x7c000010,
+ PPC_MULHDU = 0x7c000000,
+ PPC_ADDC = 0x7c000014,
+ PPC_MULHWU = 0x7c000000,
+ PPC_MFCR = 0x7c000000,
+ PPC_LWAR = 0x7c000000,
+ PPC_LDX = 0x7c000000,
+ PPC_LWZX = 0x7c00002e,
+ PPC_SLW = 0x7c000030,
+ PPC_CNTLZW = 0x7c000000,
+ PPC_SLD = 0x7c000000,
+ PPC_AND = 0x7c000038,
+ PPC_CMPL = 0x7c000040,
+ PPC_SUBF = 0x7c000050,
+ PPC_LDUX = 0x7c000000,
+//------------
+ PPC_DCBST = 0x7c000000,
+ PPC_LWZUX = 0x7c00006c,
+ PPC_CNTLZD = 0x7c000000,
+ PPC_ANDC = 0x7c000000,
+ PPC_TD = 0x7c000000,
+ PPC_MULHD = 0x7c000000,
+ PPC_MULHW = 0x7c000000,
+ PPC_MTSRD = 0x7c000000,
+ PPC_MFMSR = 0x7c000000,
+ PPC_LDARX = 0x7c000000,
+ PPC_DCBF = 0x7c000000,
+ PPC_LBZX = 0x7c0000ae,
+ PPC_NEG = 0x7c000000,
+ PPC_MTSRDIN = 0x7c000000,
+ PPC_LBZUX = 0x7c000000,
+ PPC_NOR = 0x7c0000f8,
+ PPC_SUBFE = 0x7c000000,
+ PPC_ADDE = 0x7c000000,
+ PPC_MTCRF = 0x7c000000,
+ PPC_MTMSR = 0x7c000000,
+ PPC_STDX = 0x7c000000,
+ PPC_STWCX_ = 0x7c000000,
+ PPC_STWX = 0x7c00012e,
+ PPC_MTMSRD = 0x7c000000,
+ PPC_STDUX = 0x7c000000,
+ PPC_STWUX = 0x7c00016e,
+ PPC_SUBFZE = 0x7c000000,
+ PPC_ADDZE = 0x7c000000,
+ PPC_MTSR = 0x7c000000,
+ PPC_STDCX_ = 0x7c000000,
+ PPC_STBX = 0x7c0001ae,
+ PPC_SUBFME = 0x7c000000,
+ PPC_MULLD = 0x7c000000,
+//------------
+ PPC_ADDME = 0x7c000000,
+ PPC_MULLW = 0x7c0001d6,
+ PPC_MTSRIN = 0x7c000000,
+ PPC_DCBTST = 0x7c000000,
+ PPC_STBUX = 0x7c000000,
+ PPC_ADD = 0x7c000214,
+ PPC_DCBT = 0x7c000000,
+ PPC_LHZX = 0x7c00022e,
+ PPC_EQV = 0x7c000000,
+ PPC_TLBIE = 0x7c000000,
+ PPC_ECIWX = 0x7c000000,
+ PPC_LHZUX = 0x7c000000,
+ PPC_XOR = 0x7c000278,
+ PPC_MFSPR = 0x7c0002a6,
+ PPC_LWAX = 0x7c000000,
+ PPC_LHAX = 0x7c000000,
+ PPC_TLBIA = 0x7c000000,
+ PPC_MFTB = 0x7c000000,
+ PPC_LWAUX = 0x7c000000,
+ PPC_LHAUX = 0x7c000000,
+ PPC_STHX = 0x7c00032e,
+ PPC_ORC = 0x7c000338,
+ PPC_SRADI = 0x7c000000,
+ PPC_SLBIE = 0x7c000000,
+ PPC_ECOWX = 0x7c000000,
+ PPC_STHUX = 0x7c000000,
+ PPC_OR = 0x7c000378,
+ PPC_DIVDU = 0x7c000000,
+ PPC_DIVWU = 0x7c000396,
+ PPC_MTSPR = 0x7c0003a6,
+ PPC_DCBI = 0x7c000000,
+ PPC_NAND = 0x7c000000,
+ PPC_DIVD = 0x7c000000,
+//------------
+ PPC_DIVW = 0x7c0003d6,
+ PPC_SLBIA = 0x7c000000,
+ PPC_MCRXR = 0x7c000000,
+ PPC_LSWX = 0x7c000000,
+ PPC_LWBRX = 0x7c000000,
+ PPC_LFSX = 0x7c00042e,
+ PPC_SRW = 0x7c000430,
+ PPC_SRD = 0x7c000000,
+ PPC_TLBSYNC = 0x7c000000,
+ PPC_LFSUX = 0x7c000000,
+ PPC_MFSR = 0x7c000000,
+ PPC_LSWI = 0x7c000000,
+ PPC_SYNC = 0x7c000000,
+ PPC_LFDX = 0x7c000000,
+ PPC_LFDUX = 0x7c000000,
+ PPC_MFSRIN = 0x7c000000,
+ PPC_STSWX = 0x7c000000,
+ PPC_STWBRX = 0x7c000000,
+ PPC_STFSX = 0x7c00052e,
+ PPC_STFSUX = 0x7c000000,
+ PPC_STSWI = 0x7c000000,
+ PPC_STFDX = 0x7c000000,
+ PPC_DCBA = 0x7c000000,
+ PPC_STFDUX = 0x7c000000,
+ PPC_LHBRX = 0x7c000000,
+ PPC_SRAW = 0x7c000630,
+ PPC_SRAD = 0x7c000000,
+ PPC_SRAWI = 0x7c000000,
+ PPC_EIEIO = 0x7c000000,
+ PPC_STHBRX = 0x7c000000,
+ PPC_EXTSH = 0x7c000734,
+ PPC_EXTSB = 0x7c000774,
+ PPC_ICBI = 0x7c000000,
+//------------
+ PPC_STFIWX = 0x7c0007ae,
+ PPC_EXTSW = 0x7c000000,
+ PPC_DCBZ = 0x7c000000,
+ PPC_LWZ = 0x80000000,
+ PPC_LWZU = 0x84000000,
+ PPC_LBZ = 0x88000000,
+ PPC_LBZU = 0x8c000000,
+ PPC_STW = 0x90000000,
+ PPC_STWU = 0x94000000,
+ PPC_STB = 0x98000000,
+ PPC_STBU = 0x9c000000,
+ PPC_LHZ = 0xa0000000,
+ PPC_LHZU = 0xa4000000,
+ PPC_LHA = 0xa8000000,
+ PPC_LHAU = 0xac000000,
+ PPC_STH = 0xb0000000,
+ PPC_STHU = 0xb4000000,
+ PPC_LMW = 0xb8000000,
+ PPC_STMW = 0xbc000000,
+ PPC_LFS = 0xc0000000,
+ PPC_LFSU = 0xc4000000,
+ PPC_LFD = 0xc8000000,
+ PPC_LFDU = 0xcc000000,
+ PPC_STFS = 0xd0000000,
+ PPC_STFSU = 0xd4000000,
+ PPC_STFD = 0xd8000000,
+ PPC_STFDU = 0xdc000000,
+ PPC_LD = 0xe8000000,
+ PPC_LDU = 0xe8000001,
+ PPC_LWA = 0xe8000002,
+ PPC_FDIVS = 0xec000024,
+ PPC_FSUBS = 0xec000028,
+ PPC_FADDS = 0xec00002a,
+//------------
+ PPC_FSQRTS = 0xec000000,
+ PPC_FRES = 0xec000000,
+ PPC_FMULS = 0xec000032,
+ PPC_FMSUBS = 0xec000000,
+ PPC_FMADDS = 0xec000000,
+ PPC_FNMSUBS = 0xec000000,
+ PPC_FNMADDS = 0xec000000,
+ PPC_STD = 0xf8000000,
+ PPC_STDU = 0xf8000001,
+ PPC_FCMPU = 0xfc000000,
+ PPC_FRSP = 0xfc000018,
+ PPC_FCTIW = 0xfc000000,
+ PPC_FCTIWZ = 0xfc00001e,
+ PPC_FDIV = 0xfc000000,
+ PPC_FSUB = 0xfc000028,
+ PPC_FADD = 0xfc000000,
+ PPC_FSQRT = 0xfc000000,
+ PPC_FSEL = 0xfc000000,
+ PPC_FMUL = 0xfc000000,
+ PPC_FRSQRTE = 0xfc000000,
+ PPC_FMSUB = 0xfc000000,
+ PPC_FMADD = 0xfc000000,
+ PPC_FNMSUB = 0xfc000000,
+ PPC_FNMADD = 0xfc000000,
+ PPC_FCMPO = 0xfc000000,
+ PPC_MTFSB1 = 0xfc000000,
+ PPC_FNEG = 0xfc000050,
+ PPC_MCRFS = 0xfc000000,
+ PPC_MTFSB0 = 0xfc000000,
+ PPC_FMR = 0xfc000000,
+ PPC_MTFSFI = 0xfc000000,
+ PPC_FNABS = 0xfc000000,
+ PPC_FABS = 0xfc000000,
+//------------
+ PPC_MFFS = 0xfc000000,
+ PPC_MTFSF = 0xfc000000,
+ PPC_FCTID = 0xfc000000,
+ PPC_FCTIDZ = 0xfc000000,
+ PPC_FCFID = 0xfc000000
+
+} ppcOpcodes_t;
+
+
+// the newly generated code
+static unsigned *buf;
+static int compiledOfs; // in dwords
+static int pass;
+
+// fromt the original bytecode
+static byte *code;
+static int pc;
+
+void AsmCall( void );
+
+double itofConvert[2];
+
+static int Constant4( void ) {
+ int v;
+
+ v = code[pc] | (code[pc+1]<<8) | (code[pc+2]<<16) | (code[pc+3]<<24);
+ pc += 4;
+ return v;
+}
+
+static int Constant1( void ) {
+ int v;
+
+ v = code[pc];
+ pc += 1;
+ return v;
+}
+
+static void Emit4( char *opname, int i ) {
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("\t\t\t%p %s\t%08lx\n",&buf[compiledOfs],opname,i&0x3ffffff);
+ #endif
+ buf[ compiledOfs ] = i;
+ compiledOfs++;
+}
+
+static void Inst( char *opname, int opcode, int destReg, int aReg, int bReg ) {
+ unsigned r;
+
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("\t\t\t%p %s\tr%d,r%d,r%d\n",&buf[compiledOfs],opname,destReg,aReg,bReg);
+ #endif
+ r = opcode | ( destReg << 21 ) | ( aReg << 16 ) | ( bReg << 11 ) ;
+ buf[ compiledOfs ] = r;
+ compiledOfs++;
+}
+
+static void Inst4( char *opname, int opcode, int destReg, int aReg, int bReg, int cReg ) {
+ unsigned r;
+
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("\t\t\t%p %s\tr%d,r%d,r%d,r%d\n",&buf[compiledOfs],opname,destReg,aReg,bReg,cReg);
+ #endif
+ r = opcode | ( destReg << 21 ) | ( aReg << 16 ) | ( bReg << 11 ) | ( cReg << 6 );
+ buf[ compiledOfs ] = r;
+ compiledOfs++;
+}
+
+static void InstImm( char *opname, int opcode, int destReg, int aReg, int immediate ) {
+ unsigned r;
+
+ if ( immediate > 32767 || immediate < -32768 ) {
+ Com_Error( ERR_FATAL, "VM_Compile: immediate value %i out of range, opcode %x,%d,%d", immediate, opcode, destReg, aReg );
+ }
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("\t\t\t%p %s\tr%d,r%d,0x%x\n",&buf[compiledOfs],opname,destReg,aReg,immediate);
+ #endif
+ r = opcode | ( destReg << 21 ) | ( aReg << 16 ) | ( immediate & 0xffff );
+ buf[ compiledOfs ] = r;
+ compiledOfs++;
+}
+
+static void InstImmU( char *opname, int opcode, int destReg, int aReg, int immediate ) {
+ unsigned r;
+
+ if ( immediate > 0xffff || immediate < 0 ) {
+ Com_Error( ERR_FATAL, "VM_Compile: immediate value %i out of range", immediate );
+ }
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("\t\t\t%p %s\tr%d,r%d,0x%x\n",&buf[compiledOfs],opname,destReg,aReg,immediate);
+ #endif
+ r = opcode | ( destReg << 21 ) | ( aReg << 16 ) | ( immediate & 0xffff );
+ buf[ compiledOfs ] = r;
+ compiledOfs++;
+}
+
+static int pop0, pop1, oc0, oc1;
+static vm_t *tvm;
+static int instruction;
+static byte *jused;
+
+static void spillOpStack(int depth)
+{
+ // Store out each register on the operand stack to it's correct location.
+ int i;
+
+ for(i = 0; i < depth; i++)
+ {
+ assert(opStackRegType[i]);
+ assert(opStackRegType[i] == 1);
+ switch(opStackRegType[i])
+ {
+ case 1: // Integer register
+ InstImm( "stw", PPC_STW, opStackIntRegisters[i], R_OPSTACK, i*4+4);
+ break;
+ case 2: // Float register
+ InstImm( "stfs", PPC_STFS, opStackFloatRegisters[i], R_OPSTACK, i*4+4);
+ break;
+ }
+ opStackRegType[i] = 0;
+ }
+}
+
+static void loadOpStack(int depth)
+{
+ // Back off operand stack pointer and reload all operands.
+// InstImm( "addi", PPC_ADDI, R_OPSTACK, R_OPSTACK, -(depth)*4 );
+
+ int i;
+
+ for(i = 0; i < depth; i++)
+ {
+ assert(opStackRegType[i] == 0);
+ // For now we're stuck reloading everything as an integer.
+ opStackLoadInstructionAddr[i] = &buf[compiledOfs];
+ InstImm( "lwz", PPC_LWZ, opStackIntRegisters[i], R_OPSTACK, i*4+4);
+ opStackRegType[i] = 1;
+ }
+}
+
+static void makeFloat(int depth)
+{
+ //assert(opStackRegType[depth] == 1);
+ if(opStackRegType[depth] == 1)
+ {
+ unsigned instruction;
+ unsigned destReg, aReg, bReg, imm;
+
+ if(opStackLoadInstructionAddr[depth])
+ {
+ // Repatch load instruction to use LFS instead of LWZ
+ instruction = *opStackLoadInstructionAddr[depth];
+ // Figure out if it's LWZ or LWZX
+ if((instruction & 0xfc000000) == PPC_LWZ)
+ {
+ //printf("patching LWZ at %p to LFS at depth %ld\n",opStackLoadInstructionAddr[depth],depth);
+ //printf("old instruction: %08lx\n",instruction);
+ // Extract registers
+ destReg = (instruction >> 21) & 31;
+ aReg = (instruction >> 16) & 31;
+ imm = instruction & 0xffff;
+
+ // Calculate correct FP register to use.
+ // THIS ASSUMES REGISTER USAGE FOR THE STACK IS n, n+1, n+2, etc!
+ //printf("old dest: %ld\n",destReg);
+ destReg = (destReg - opStackIntRegisters[0]) + opStackFloatRegisters[0];
+ instruction = PPC_LFS | ( destReg << 21 ) | ( aReg << 16 ) | imm ;
+ //printf("new dest: %ld\n",destReg);
+ //printf("new instruction: %08lx\n",instruction);
+ }
+ else
+ {
+ //printf("patching LWZX at %p to LFSX at depth %ld\n",opStackLoadInstructionAddr[depth],depth);
+ //printf("old instruction: %08lx\n",instruction);
+ // Extract registers
+ destReg = (instruction >> 21) & 31;
+ aReg = (instruction >> 16) & 31;
+ bReg = (instruction >> 11) & 31;
+ // Calculate correct FP register to use.
+ // THIS ASSUMES REGISTER USAGE FOR THE STACK IS n, n+1, n+2, etc!
+ //printf("old dest: %ld\n",destReg);
+ destReg = (destReg - opStackIntRegisters[0]) + opStackFloatRegisters[0];
+ instruction = PPC_LFSX | ( destReg << 21 ) | ( aReg << 16 ) | ( bReg << 11 ) ;
+ //printf("new dest: %ld\n",destReg);
+ //printf("new instruction: %08lx\n",instruction);
+ }
+ *opStackLoadInstructionAddr[depth] = instruction;
+ opStackLoadInstructionAddr[depth] = 0;
+ }
+ else
+ {
+ //printf("doing float constant load at %p for depth %ld\n",&buf[compiledOfs],depth);
+ // It was likely loaded as a constant so we have to save/load it. A more
+ // interesting implementation might be to generate code to do a "PC relative"
+ // load from the VM code region.
+ InstImm( "stw", PPC_STW, opStackIntRegisters[depth], R_OPSTACK, depth*4+4);
+ // For XXX make sure we force enough NOPs to get the load into
+ // another dispatch group to avoid pipeline flush.
+ Inst( "ori", PPC_ORI, 0, 0, 0 );
+ Inst( "ori", PPC_ORI, 0, 0, 0 );
+ Inst( "ori", PPC_ORI, 0, 0, 0 );
+ Inst( "ori", PPC_ORI, 0, 0, 0 );
+ InstImm( "lfs", PPC_LFS, opStackFloatRegisters[depth], R_OPSTACK, depth*4+4);
+ }
+ opStackRegType[depth] = 2;
+ }
+}
+
+// TJW: Unused
+#if 0
+static void fltop() {
+ if (rtopped == qfalse) {
+ InstImm( PPC_LFS, R_TOP, R_OPSTACK, 0 ); // get value from opstack
+ }
+}
+#endif
+
+#if 0
+static void fltopandsecond() {
+ InstImm( PPC_LFS, R_TOP, R_OPSTACK, 0 ); // get value from opstack
+ InstImm( PPC_LFS, R_SECOND, R_OPSTACK, -4 ); // get value from opstack
+ InstImm( PPC_ADDI, R_OPSTACK, R_OPSTACK, -8 );
+ rtopped = qfalse;
+ return;
+}
+#endif
+
+#define assertInteger(depth) assert(opStackRegType[depth] == 1)
+
+/*
+=================
+VM_Compile
+=================
+*/
+void VM_Compile( vm_t *vm, vmHeader_t *header ) {
+ int op;
+ int maxLength;
+ int v;
+ int i;
+ int opStackDepth;
+
+ int mainFunction;
+
+ // set up the into-to-float variables
+ ((int *)itofConvert)[0] = 0x43300000;
+ ((int *)itofConvert)[1] = 0x80000000;
+ ((int *)itofConvert)[2] = 0x43300000;
+
+ // allocate a very large temp buffer, we will shrink it later
+ maxLength = header->codeLength * 8;
+ buf = Z_Malloc( maxLength );
+ jused = Z_Malloc(header->instructionCount + 2);
+ Com_Memset(jused, 0, header->instructionCount+2);
+
+ // compile everything twice, so the second pass will have valid instruction
+ // pointers for branches
+ for ( pass = -1 ; pass < 2 ; pass++ ) {
+
+ // translate all instructions
+ pc = 0;
+ mainFunction = 0;
+ opStackDepth = 0;
+
+ pop0 = 343545;
+ pop1 = 2443545;
+ oc0 = -2343535;
+ oc1 = 24353454;
+ tvm = vm;
+ code = (byte *)header + header->codeOffset;
+ compiledOfs = 0;
+#ifndef __GNUC__
+ // metrowerks seems to require this header in front of functions
+ Emit4( (int)(buf+2) );
+ Emit4( 0 );
+#endif
+
+ for ( instruction = 0 ; instruction < header->instructionCount ; instruction++ ) {
+ if ( compiledOfs*4 > maxLength - 16 ) {
+ Com_Error( ERR_DROP, "VM_Compile: maxLength exceeded" );
+ }
+
+ op = code[ pc ];
+ if ( !pass ) {
+ vm->instructionPointers[ instruction ] = compiledOfs * 4;
+ }
+ pc++;
+ switch ( op ) {
+ case 0:
+ break;
+ case OP_BREAK:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08lx BREAK\n",instruction);
+ #endif
+ InstImmU( "addi", PPC_ADDI, R_TOP, 0, 0 );
+ InstImm( "lwz", PPC_LWZ, R_TOP, R_TOP, 0 ); // *(int *)0 to crash to debugger
+ break;
+ case OP_ENTER:
+ opStackDepth = 0;
+ v = Constant4();
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x ENTER\t%04x\n",instruction,v);
+ #endif
+ opStackRegType[opStackDepth] = 0;
+ mainFunction++;
+ if(mainFunction == 1)
+ {
+ // Main VM entry point is the first thing we compile, so save off operand stack
+ // registers here. This avoids issues with trying to trick the native compiler
+ // into doing it, and properly matches the PowerPC ABI
+ InstImm( "addi", PPC_ADDI, R_REAL_STACK, R_REAL_STACK, -OP_STACK_MAX_DEPTH*4 ); // sub R_STACK, R_STACK, imm
+ for(i = 0; i < OP_STACK_MAX_DEPTH; i++)
+ InstImm( "stw", PPC_STW, opStackIntRegisters[i], R_REAL_STACK, i*4);
+ }
+ InstImm( "addi", PPC_ADDI, R_STACK, R_STACK, -v ); // sub R_STACK, R_STACK, imm
+ break;
+ case OP_CONST:
+ v = Constant4();
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x CONST\t%08x\n",instruction,v);
+ #endif
+ opStackLoadInstructionAddr[opStackDepth] = 0;
+ if ( v < 32768 && v >= -32768 ) {
+ InstImmU( "addi", PPC_ADDI, opStackIntRegisters[opStackDepth], 0, v & 0xffff );
+ } else {
+ InstImmU( "addis", PPC_ADDIS, opStackIntRegisters[opStackDepth], 0, (v >> 16)&0xffff );
+ if ( v & 0xffff ) {
+ InstImmU( "ori", PPC_ORI, opStackIntRegisters[opStackDepth], opStackIntRegisters[opStackDepth], v & 0xffff );
+ }
+ }
+ opStackRegType[opStackDepth] = 1;
+ opStackDepth += 1;
+ if (code[pc] == OP_JUMP) {
+ jused[v] = 1;
+ }
+ break;
+ case OP_LOCAL:
+ oc1 = Constant4();
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x LOCAL\t%08x\n",instruction,oc1);
+ #endif
+ if (code[pc] == OP_LOAD4 || code[pc] == OP_LOAD2 || code[pc] == OP_LOAD1) {
+ oc1 &= vm->dataMask;
+ }
+ InstImm( "addi", PPC_ADDI, opStackIntRegisters[opStackDepth], R_STACK, oc1 );
+ opStackRegType[opStackDepth] = 1;
+ opStackLoadInstructionAddr[opStackDepth] = 0;
+ opStackDepth += 1;
+ break;
+ case OP_ARG:
+ v = Constant1();
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x ARG \t%08x\n",instruction,v);
+ #endif
+ InstImm( "addi", PPC_ADDI, R_EA, R_STACK, v ); // location to put it
+ if(opStackRegType[opStackDepth-1] == 1)
+ Inst( "stwx", PPC_STWX, opStackIntRegisters[opStackDepth-1], R_EA, R_MEMBASE );
+ else
+ Inst( "stfsx", PPC_STFSX, opStackFloatRegisters[opStackDepth-1], R_EA, R_MEMBASE );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackDepth -= 1;
+
+ break;
+ case OP_CALL:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x CALL\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assert(opStackDepth > 0);
+ Inst( "mflr", PPC_MFSPR, R_SECOND, 8, 0 ); // move from link register
+ InstImm( "stwu", PPC_STWU, R_SECOND, R_REAL_STACK, -16 ); // save off the old return address
+
+ // Spill operand stack registers.
+ spillOpStack(opStackDepth);
+
+ // We need to leave R_OPSTACK pointing to the top entry on the stack, which is the call address.
+ // It will be consumed (and R4 decremented) by the AsmCall code.
+ InstImm( "addi", PPC_ADDI, R_OPSTACK, R_OPSTACK, opStackDepth*4);
+
+ Inst( "mtctr", PPC_MTSPR, R_ASMCALL, 9, 0 ); // move to count register
+ Inst( "bctrl", PPC_BCCTR | 1, 20, 0, 0 ); // jump and link to the count register
+
+ // R4 now points to the top of the operand stack, which has the return value in it. We want to
+ // back off the pointer to point to the base of our local operand stack and then reload the stack.
+
+ InstImm("addi", PPC_ADDI, R_OPSTACK, R_OPSTACK, -opStackDepth*4);
+
+ // Reload operand stack.
+ loadOpStack(opStackDepth);
+
+ InstImm( "lwz", PPC_LWZ, R_SECOND, R_REAL_STACK, 0 ); // fetch the old return address
+ InstImm( "addi", PPC_ADDI, R_REAL_STACK, R_REAL_STACK, 16 );
+ Inst( "mtlr", PPC_MTSPR, R_SECOND, 8, 0 ); // move to link register
+ break;
+ case OP_PUSH:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x PUSH\n",instruction);
+ #endif
+ opStackRegType[opStackDepth] = 1; // Garbage int value.
+ opStackDepth += 1;
+ break;
+ case OP_POP:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x POP\n",instruction);
+ #endif
+ opStackDepth -= 1;
+ opStackRegType[opStackDepth] = 0; // ??
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ break;
+ case OP_LEAVE:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x LEAVE\n",instruction);
+ #endif
+ assert(opStackDepth == 1);
+ assert(opStackRegType[0] != 0);
+ // Save return value onto top of op stack. We also have to increment R_OPSTACK
+ switch(opStackRegType[0])
+ {
+ case 1: // Integer register
+ InstImm( "stw", PPC_STWU, opStackIntRegisters[0], R_OPSTACK, 4);
+ break;
+ case 2: // Float register
+ InstImm( "stfs", PPC_STFSU, opStackFloatRegisters[0], R_OPSTACK, 4);
+ break;
+ }
+ InstImm( "addi", PPC_ADDI, R_STACK, R_STACK, Constant4() ); // add R_STACK, R_STACK, imm
+ if(mainFunction == 1)
+ {
+ for(i = 0; i < OP_STACK_MAX_DEPTH; i++)
+ InstImm( "lwz", PPC_LWZ, opStackIntRegisters[i], R_REAL_STACK, i*4);
+ InstImm( "addi", PPC_ADDI, R_REAL_STACK, R_REAL_STACK, OP_STACK_MAX_DEPTH*4 );
+ }
+ opStackDepth--;
+ opStackRegType[opStackDepth] = 0;
+ opStackLoadInstructionAddr[opStackDepth] = 0;
+ Inst( "blr", PPC_BCLR, 20, 0, 0 ); // branch unconditionally to link register
+ break;
+ case OP_LOAD4:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x LOAD4\n",instruction);
+ #endif
+ // We should try to figure out whether to use LWZX or LFSX based
+ // on some kind of code analysis after subsequent passes. I think what
+ // we could do is store the compiled load instruction address along with
+ // the register type. When we hit the first mismatched operator, we go back
+ // and patch the load. Since LCC's operand stack should be at 0 depth by the
+ // time we hit a branch, this should work fairly well. FIXME FIXME FIXME.
+ assertInteger(opStackDepth-1);
+ opStackLoadInstructionAddr[opStackDepth-1] = &buf[ compiledOfs ];
+ Inst( "lwzx", PPC_LWZX, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], R_MEMBASE );// load from memory base
+ opStackRegType[opStackDepth-1] = 1;
+ break;
+ case OP_LOAD2:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x LOAD2\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ Inst( "lhzx", PPC_LHZX, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], R_MEMBASE );// load from memory base
+ opStackRegType[opStackDepth-1] = 1;
+ break;
+ case OP_LOAD1:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x LOAD1\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ Inst( "lbzx", PPC_LBZX, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], R_MEMBASE );// load from memory base
+ opStackRegType[opStackDepth-1] = 1;
+ break;
+ case OP_STORE4:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x STORE4\n",instruction);
+ #endif
+ assertInteger(opStackDepth-2);
+ if(opStackRegType[opStackDepth-1] == 1)
+ Inst( "stwx", PPC_STWX, opStackIntRegisters[opStackDepth-1],
+ opStackIntRegisters[opStackDepth-2], R_MEMBASE ); // store from memory base
+ else
+ Inst( "stfsx", PPC_STFSX, opStackFloatRegisters[opStackDepth-1],
+ opStackIntRegisters[opStackDepth-2], R_MEMBASE ); // store from memory base
+ opStackRegType[opStackDepth-1] = 0;
+ opStackRegType[opStackDepth-2] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-2] = 0;
+ opStackDepth -= 2;
+ break;
+ case OP_STORE2:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x STORE2\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "sthx", PPC_STHX, opStackIntRegisters[opStackDepth-1],
+ opStackIntRegisters[opStackDepth-2], R_MEMBASE ); // store from memory base
+ opStackRegType[opStackDepth-1] = 0;
+ opStackRegType[opStackDepth-2] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-2] = 0;
+ opStackDepth -= 2;
+ break;
+ case OP_STORE1:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x STORE1\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "stbx", PPC_STBX, opStackIntRegisters[opStackDepth-1],
+ opStackIntRegisters[opStackDepth-2], R_MEMBASE ); // store from memory base
+ opStackRegType[opStackDepth-1] = 0;
+ opStackRegType[opStackDepth-2] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-2] = 0;
+ opStackDepth -= 2;
+ break;
+
+ case OP_EQ:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x EQ\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "cmp", PPC_CMP, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackRegType[opStackDepth-2] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-2] = 0;
+ opStackDepth -= 2;
+ i = Constant4();
+ jused[i] = 1;
+ InstImm( "bc", PPC_BC, 4, 2, 8 );
+ if ( pass==1 ) {
+ v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs];
+ } else {
+ v = 0;
+ }
+ Emit4("b", PPC_B | (v&0x3ffffff) );
+ break;
+ case OP_NE:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x NE\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "cmp", PPC_CMP, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackRegType[opStackDepth-2] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-2] = 0;
+ opStackDepth -= 2;
+ i = Constant4();
+ jused[i] = 1;
+ InstImm( "bc", PPC_BC, 12, 2, 8 );
+ if ( pass==1 ) {
+ v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs];
+ } else {
+ v = 0;
+ }
+ Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) );
+// InstImm( "bc", PPC_BC, 4, 2, v );
+
+ break;
+ case OP_LTI:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x LTI\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "cmp", PPC_CMP, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackRegType[opStackDepth-2] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-2] = 0;
+ opStackDepth -= 2;
+ i = Constant4();
+ jused[i] = 1;
+ InstImm( "bc", PPC_BC, 4, 0, 8 );
+ if ( pass==1 ) {
+ v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs];
+ } else {
+ v = 0;
+ }
+ Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) );
+// InstImm( "bc", PPC_BC, 12, 0, v );
+ break;
+ case OP_LEI:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x LEI\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "cmp", PPC_CMP, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackRegType[opStackDepth-2] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-2] = 0;
+ opStackDepth -= 2;
+ i = Constant4();
+ jused[i] = 1;
+ InstImm( "bc", PPC_BC, 12, 1, 8 );
+ if ( pass==1 ) {
+ v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs];
+ } else {
+ v = 0;
+ }
+ Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) );
+// InstImm( "bc", PPC_BC, 4, 1, v );
+ break;
+ case OP_GTI:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x GTI\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "cmp", PPC_CMP, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackRegType[opStackDepth-2] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-2] = 0;
+ opStackDepth -= 2;
+ i = Constant4();
+ jused[i] = 1;
+ InstImm( "bc", PPC_BC, 4, 1, 8 );
+ if ( pass==1 ) {
+ v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs];
+ } else {
+ v = 0;
+ }
+ Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) );
+// InstImm( "bc", PPC_BC, 12, 1, v );
+ break;
+ case OP_GEI:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x GEI\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "cmp", PPC_CMP, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackRegType[opStackDepth-2] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-2] = 0;
+ opStackDepth -= 2;
+ i = Constant4();
+ jused[i] = 1;
+ InstImm( "bc", PPC_BC, 12, 0, 8 );
+ if ( pass==1 ) {
+ v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs];
+ } else {
+ v = 0;
+ }
+ Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) );
+// InstImm( "bc", PPC_BC, 4, 0, v );
+ break;
+ case OP_LTU:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x LTU\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "cmpl", PPC_CMPL, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackRegType[opStackDepth-2] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-2] = 0;
+ opStackDepth -= 2;
+ i = Constant4();
+ jused[i] = 1;
+ InstImm( "bc", PPC_BC, 4, 0, 8 );
+ if ( pass==1 ) {
+ v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs];
+ } else {
+ v = 0;
+ }
+ Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) );
+// InstImm( "bc", PPC_BC, 12, 0, v );
+ break;
+ case OP_LEU:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x LEU\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "cmpl", PPC_CMPL, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackRegType[opStackDepth-2] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-2] = 0;
+ opStackDepth -= 2;
+ i = Constant4();
+ jused[i] = 1;
+ InstImm( "bc", PPC_BC, 12, 1, 8 );
+ if ( pass==1 ) {
+ v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs];
+ } else {
+ v = 0;
+ }
+ Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) );
+// InstImm( "bc", PPC_BC, 4, 1, v );
+ break;
+ case OP_GTU:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x GTU\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "cmpl", PPC_CMPL, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackRegType[opStackDepth-2] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-2] = 0;
+ opStackDepth -= 2;
+ i = Constant4();
+ jused[i] = 1;
+ InstImm( "bc", PPC_BC, 4, 1, 8 );
+ if ( pass==1 ) {
+ v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs];
+ } else {
+ v = 0;
+ }
+ Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) );
+// InstImm( "bc", PPC_BC, 12, 1, v );
+ break;
+ case OP_GEU:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x GEU\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "cmpl", PPC_CMPL, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackRegType[opStackDepth-2] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-2] = 0;
+ opStackDepth -= 2;
+ i = Constant4();
+ jused[i] = 1;
+ InstImm( "bc", PPC_BC, 12, 0, 8 );
+ if ( pass==1 ) {
+ v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs];
+ } else {
+ v = 0;
+ }
+ Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) );
+// InstImm( "bc", PPC_BC, 4, 0, v );
+ break;
+
+ case OP_EQF:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x EQF\n",instruction);
+ #endif
+ makeFloat(opStackDepth-1);
+ makeFloat(opStackDepth-2);
+ Inst( "fcmpu", PPC_FCMPU, 0, opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackRegType[opStackDepth-2] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-2] = 0;
+ opStackDepth -= 2;
+ i = Constant4();
+ jused[i] = 1;
+ InstImm( "bc", PPC_BC, 4, 2, 8 );
+ if ( pass==1 ) {
+ v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs];
+ } else {
+ v = 0;
+ }
+ Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) );
+// InstImm( "bc", PPC_BC, 12, 2, v );
+ break;
+ case OP_NEF:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x NEF\n",instruction);
+ #endif
+ makeFloat(opStackDepth-1);
+ makeFloat(opStackDepth-2);
+ Inst( "fcmpu", PPC_FCMPU, 0, opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackRegType[opStackDepth-2] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-2] = 0;
+ opStackDepth -= 2;
+ i = Constant4();
+ jused[i] = 1;
+ InstImm( "bc", PPC_BC, 12, 2, 8 );
+ if ( pass==1 ) {
+ v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs];
+ } else {
+ v = 0;
+ }
+ Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) );
+// InstImm( "bc", PPC_BC, 4, 2, v );
+ break;
+ case OP_LTF:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x LTF\n",instruction);
+ #endif
+ makeFloat(opStackDepth-1);
+ makeFloat(opStackDepth-2);
+ Inst( "fcmpu", PPC_FCMPU, 0, opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackRegType[opStackDepth-2] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-2] = 0;
+ opStackDepth -= 2;
+ i = Constant4();
+ jused[i] = 1;
+ InstImm( "bc", PPC_BC, 4, 0, 8 );
+ if ( pass==1 ) {
+ v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs];
+ } else {
+ v = 0;
+ }
+ Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) );
+// InstImm( "bc", PPC_BC, 12, 0, v );
+ break;
+ case OP_LEF:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x LEF\n",instruction);
+ #endif
+ makeFloat(opStackDepth-1);
+ makeFloat(opStackDepth-2);
+ Inst( "fcmpu", PPC_FCMPU, 0, opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackRegType[opStackDepth-2] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-2] = 0;
+ opStackDepth -= 2;
+ i = Constant4();
+ jused[i] = 1;
+ InstImm( "bc", PPC_BC, 12, 1, 8 );
+ if ( pass==1 ) {
+ v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs];
+ } else {
+ v = 0;
+ }
+ Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) );
+// InstImm( "bc", PPC_BC, 4, 1, v );
+ break;
+ case OP_GTF:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x GTF\n",instruction);
+ #endif
+ makeFloat(opStackDepth-1);
+ makeFloat(opStackDepth-2);
+ Inst( "fcmpu", PPC_FCMPU, 0, opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackRegType[opStackDepth-2] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-2] = 0;
+ opStackDepth -= 2;
+ i = Constant4();
+ jused[i] = 1;
+ InstImm( "bc", PPC_BC, 4, 1, 8 );
+ if ( pass==1 ) {
+ v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs];
+ } else {
+ v = 0;
+ }
+ Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) );
+// InstImm( "bc", PPC_BC, 12, 1, v );
+ break;
+ case OP_GEF:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x GEF\n",instruction);
+ #endif
+ makeFloat(opStackDepth-1);
+ makeFloat(opStackDepth-2);
+ Inst( "fcmpu", PPC_FCMPU, 0, opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackRegType[opStackDepth-2] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-2] = 0;
+ opStackDepth -= 2;
+ i = Constant4();
+ jused[i] = 1;
+ InstImm( "bc", PPC_BC, 12, 0, 8 );
+ if ( pass==1 ) {
+ v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs];
+ } else {
+ v = 0;
+ }
+ Emit4("b", PPC_B | (unsigned int)(v&0x3ffffff) );
+// InstImm( "bc", PPC_BC, 4, 0, v );
+ break;
+
+ case OP_NEGI:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x NEGI\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ InstImm( "subfic", PPC_SUBFIC, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], 0 );
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ break;
+ case OP_ADD:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x ADD\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "add", PPC_ADD, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-2] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackDepth -= 1;
+ break;
+ case OP_SUB:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x SUB\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "subf", PPC_SUBF, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-2] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackDepth -= 1;
+ break;
+ case OP_DIVI:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x DIVI\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "divw", PPC_DIVW, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackDepth -= 1;
+ break;
+ case OP_DIVU:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x DIVU\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "divwu", PPC_DIVWU, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackDepth -= 1;
+ break;
+ case OP_MODI:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x MODI\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "divw", PPC_DIVW, R_EA, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+ Inst( "mullw", PPC_MULLW, R_EA, opStackIntRegisters[opStackDepth-1], R_EA );
+ Inst( "subf", PPC_SUBF, opStackIntRegisters[opStackDepth-2], R_EA, opStackIntRegisters[opStackDepth-2] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackDepth -= 1;
+ break;
+ case OP_MODU:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x MODU\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "divwu", PPC_DIVWU, R_EA, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+ Inst( "mullw", PPC_MULLW, R_EA, opStackIntRegisters[opStackDepth-1], R_EA );
+ Inst( "subf", PPC_SUBF, opStackIntRegisters[opStackDepth-2], R_EA, opStackIntRegisters[opStackDepth-2] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackDepth -= 1;
+ break;
+ case OP_MULI:
+ case OP_MULU:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x MULI\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "mullw", PPC_MULLW, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-2] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackDepth -= 1;
+ break;
+ case OP_BAND:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x BAND\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "and", PPC_AND, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackDepth -= 1;
+ break;
+ case OP_BOR:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x BOR\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "or", PPC_OR, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackDepth -= 1;
+ break;
+ case OP_BXOR:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x BXOR\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "xor", PPC_XOR, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackDepth -= 1;
+ break;
+ case OP_BCOM:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x BCOM\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ Inst( "nor", PPC_NOR, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1] );
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ break;
+ case OP_LSH:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x LSH\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "slw", PPC_SLW, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackDepth -= 1;
+ break;
+ case OP_RSHI:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x RSHI\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "sraw", PPC_SRAW, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackDepth -= 1;
+ break;
+ case OP_RSHU:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x RSHU\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ Inst( "srw", PPC_SRW, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackDepth -= 1;
+ break;
+
+ case OP_NEGF:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x NEGF\n",instruction);
+ #endif
+ makeFloat(opStackDepth-1);
+ Inst( "fneg", PPC_FNEG, opStackFloatRegisters[opStackDepth-1], 0, opStackFloatRegisters[opStackDepth-1] );
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ break;
+ case OP_ADDF:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x ADDF\n",instruction);
+ #endif
+ makeFloat(opStackDepth-1);
+ makeFloat(opStackDepth-2);
+ Inst( "fadds", PPC_FADDS, opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackDepth -= 1;
+ break;
+ case OP_SUBF:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x SUBF\n",instruction);
+ #endif
+ makeFloat(opStackDepth-1);
+ makeFloat(opStackDepth-2);
+ Inst( "fsubs", PPC_FSUBS, opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackDepth -= 1;
+ break;
+ case OP_DIVF:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x DIVF\n",instruction);
+ #endif
+ makeFloat(opStackDepth-1);
+ makeFloat(opStackDepth-2);
+ Inst( "fdivs", PPC_FDIVS, opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackDepth -= 1;
+ break;
+ case OP_MULF:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x MULF\n",instruction);
+ #endif
+ makeFloat(opStackDepth-1);
+ makeFloat(opStackDepth-2);
+ Inst4( "fmuls", PPC_FMULS, opStackFloatRegisters[opStackDepth-2], opStackFloatRegisters[opStackDepth-2], 0, opStackFloatRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackDepth -= 1;
+ break;
+
+ case OP_CVIF:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x CVIF\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ //makeInteger(opStackDepth-1);
+ v = (int)&itofConvert;
+ InstImmU( "addis", PPC_ADDIS, R_EA, 0, (v >> 16)&0xffff );
+ InstImmU( "ori", PPC_ORI, R_EA, R_EA, v & 0xffff );
+ InstImmU( "xoris", PPC_XORIS, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], 0x8000 );
+ InstImm( "stw", PPC_STW, opStackIntRegisters[opStackDepth-1], R_EA, 12 );
+ InstImm( "lfd", PPC_LFD, opStackFloatRegisters[opStackDepth-1], R_EA, 0 );
+ Inst( "ori", PPC_ORI, 0, 0, 0);
+ Inst( "ori", PPC_ORI, 0, 0, 0);
+ Inst( "ori", PPC_ORI, 0, 0, 0);
+ InstImm( "lfd", PPC_LFD, 13, R_EA, 8 );
+ Inst( "fsub", PPC_FSUB, opStackFloatRegisters[opStackDepth-1], 13, opStackFloatRegisters[opStackDepth-1] );
+ opStackRegType[opStackDepth-1] = 2;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ // Inst( PPC_FRSP, R_TOP, 0, R_TOP );
+ break;
+ case OP_CVFI:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x CVFI\n",instruction);
+ #endif
+ makeFloat(opStackDepth-1);
+
+ InstImm( "addi", PPC_ADDI, R_OPSTACK, R_OPSTACK, opStackDepth*4);
+
+ Inst( "fctiwz", PPC_FCTIWZ, opStackFloatRegisters[opStackDepth-1], 0, opStackFloatRegisters[opStackDepth-1] );
+ Inst( "stfiwx", PPC_STFIWX, opStackFloatRegisters[opStackDepth-1], 0, R_OPSTACK ); // save value to opstack (dummy area now)
+ Inst( "ori", PPC_ORI, 0, 0, 0);
+ Inst( "ori", PPC_ORI, 0, 0, 0);
+ Inst( "ori", PPC_ORI, 0, 0, 0);
+ Inst( "ori", PPC_ORI, 0, 0, 0);
+ InstImm( "lwz", PPC_LWZ, opStackIntRegisters[opStackDepth-1], R_OPSTACK, 0 );
+
+ InstImm( "addi", PPC_ADDI, R_OPSTACK, R_OPSTACK, -opStackDepth*4);
+
+ opStackRegType[opStackDepth-1] = 1;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ break;
+ case OP_SEX8:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x SEX8\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ Inst( "extsb", PPC_EXTSB, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], 0 );
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ break;
+ case OP_SEX16:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x SEX16\n",instruction);
+ #endif
+ assertInteger(opStackDepth-1);
+ Inst( "extsh", PPC_EXTSH, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], 0 );
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ break;
+
+ case OP_BLOCK_COPY:
+ v = Constant4() >> 2;
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x BLOCK_COPY\t%08lx\n",instruction,v<<2);
+ #endif
+ assert(opStackDepth >= 2);
+ assertInteger(opStackDepth-1);
+ assertInteger(opStackDepth-2);
+ InstImmU( "addi", PPC_ADDI, R_EA, 0, v ); // count
+ // FIXME: range check
+ Inst( "mtctr", PPC_MTSPR, R_EA, 9, 0 ); // move to count register
+
+ Inst( "add", PPC_ADD, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], R_MEMBASE );
+ InstImm( "addi", PPC_ADDI, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], -4 );
+ Inst( "add", PPC_ADD, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-2], R_MEMBASE );
+ InstImm( "addi", PPC_ADDI, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-2], -4 );
+
+ InstImm( "lwzu", PPC_LWZU, R_EA, opStackIntRegisters[opStackDepth-1], 4 ); // source
+ InstImm( "stwu", PPC_STWU, R_EA, opStackIntRegisters[opStackDepth-2], 4 ); // dest
+ Inst( "b", PPC_BC | 0xfff8 , 16, 0, 0 ); // loop
+ opStackRegType[opStackDepth-1] = 0;
+ opStackRegType[opStackDepth-2] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-2] = 0;
+ opStackDepth -= 2;
+ break;
+
+ case OP_JUMP:
+ #if DEBUG_VM
+ if(pass == 1)
+ printf("%08x JUMP\n",instruction);
+ #endif
+ assert(opStackDepth == 1);
+ assertInteger(opStackDepth-1);
+
+ Inst( "rlwinm", PPC_RLWINM | ( 29 << 1 ), opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], 2 );
+ // FIXME: range check
+ Inst( "lwzx", PPC_LWZX, opStackIntRegisters[opStackDepth-1], opStackIntRegisters[opStackDepth-1], R_INSTRUCTIONS );
+ Inst( "mtctr", PPC_MTSPR, opStackIntRegisters[opStackDepth-1], 9, 0 ); // move to count register
+ Inst( "bctr", PPC_BCCTR, 20, 0, 0 ); // jump to the count register
+ opStackRegType[opStackDepth-1] = 0;
+ opStackLoadInstructionAddr[opStackDepth-1] = 0;
+ opStackDepth -= 1;
+ break;
+ default:
+ Com_Error( ERR_DROP, "VM_CompilePPC: bad opcode %i at instruction %i, offset %i", op, instruction, pc );
+ }
+ pop0 = pop1;
+ pop1 = op;
+ assert(opStackDepth >= 0);
+ assert(opStackDepth < OP_STACK_MAX_DEPTH);
+
+ //printf("%4d\t%s\n",opStackDepth,opnames[op]);
+ }
+
+ Com_Printf( "VM file %s pass %d compiled to %i bytes of code\n", vm->name, (pass+1), compiledOfs*4 );
+
+ if ( pass == 0 ) {
+ // copy to an exact size buffer on the hunk
+ vm->codeLength = compiledOfs * 4;
+ vm->codeBase = Hunk_Alloc( vm->codeLength, h_low );
+ Com_Memcpy( vm->codeBase, buf, vm->codeLength );
+
+ //printf("codeBase: %p\n",vm->codeBase);
+
+ Z_Free( buf );
+
+ // offset all the instruction pointers for the new location
+ for ( i = 0 ; i < header->instructionCount ; i++ ) {
+ vm->instructionPointers[i] += (int)vm->codeBase;
+ //printf("%08x %08lx\n",i,vm->instructionPointers[i]);
+ }
+
+ // go back over it in place now to fixup reletive jump targets
+ buf = (unsigned *)vm->codeBase;
+ } else if ( pass == 1 ) {
+ // clear the instruction cache for generated code
+ msync(vm->codeBase, vm->codeLength, MS_INVALIDATE);
+ }
+ }
+ if(0)
+ {
+ char buf[256];
+ printf("wait..\n");
+ gets(buf);
+ }
+ Z_Free( jused );
+}
+
+/*
+==============
+VM_CallCompiled
+
+This function is called directly by the generated code
+==============
+*/
+int VM_CallCompiled( vm_t *vm, int *args ) {
+ int stack[1024];
+ int programStack;
+ int stackOnEntry;
+ byte *image;
+
+ currentVM = vm;
+
+ //printf("VM_CallCompiled: %p %08lx %08lx %08lx\n",
+ // vm, args[0],args[1],args[2]);
+
+ // interpret the code
+ vm->currentlyInterpreting = qtrue;
+
+ // we might be called recursively, so this might not be the very top
+ programStack = vm->programStack;
+ stackOnEntry = programStack;
+ image = vm->dataBase;
+
+ // set up the stack frame
+ programStack -= 48;
+
+ *(int *)&image[ programStack + 44] = args[9];
+ *(int *)&image[ programStack + 40] = args[8];
+ *(int *)&image[ programStack + 36] = args[7];
+ *(int *)&image[ programStack + 32] = args[6];
+ *(int *)&image[ programStack + 28] = args[5];
+ *(int *)&image[ programStack + 24] = args[4];
+ *(int *)&image[ programStack + 20] = args[3];
+ *(int *)&image[ programStack + 16] = args[2];
+ *(int *)&image[ programStack + 12] = args[1];
+ *(int *)&image[ programStack + 8 ] = args[0];
+ *(int *)&image[ programStack + 4 ] = 0; // return stack
+ *(int *)&image[ programStack ] = -1; // will terminate the loop on return
+
+ // Cheesy... manually save registers used by VM call...
+ // off we go into generated code...
+ // the PPC calling standard says the parms will all go into R3 - R11, so
+ // no special asm code is needed here
+#ifdef __GNUC__
+ ((void(*)(int, int, int, int, int, int, int, int))(vm->codeBase))(
+ programStack, (int)&stack,
+ (int)image, vm->dataMask, (int)&AsmCall,
+ (int)vm->instructionPointers, vm->instructionPointersLength,
+ (int)vm );
+#else
+ ((void(*)(int, int, int, int, int, int, int, int))(vm->codeBase))(
+ programStack, (int)&stack,
+ (int)image, vm->dataMask, *(int *)&AsmCall /* skip function pointer header */,
+ (int)vm->instructionPointers, vm->instructionPointersLength,
+ (int)vm );
+#endif
+ vm->programStack = stackOnEntry;
+
+ vm->currentlyInterpreting = qfalse;
+
+ return stack[1];
+}
+
+
+/*
+==================
+AsmCall
+
+Put this at end of file because gcc messes up debug line numbers
+==================
+*/
+#ifdef __GNUC__
+
+void AsmCall( void ) {
+asm (
+ // pop off the destination instruction
+" lwz r12,0(r4) \n" // RG_TOP, 0(RG_OPSTACK)
+" addi r4,r4,-4 \n" // RG_OPSTACK, RG_OPSTACK, -4 \n"
+
+ // see if it is a system trap
+" cmpwi r12,0 \n" // RG_TOP, 0 \n"
+" bc 12,0, systemTrap \n"
+
+ // calling another VM function, so lookup in instructionPointers
+" slwi r12,r12,2 \n" // RG_TOP,RG_TOP,2
+ // FIXME: range check
+" lwzx r12, r8, r12 \n" // RG_TOP, RG_INSTRUCTIONS(RG_TOP)
+" mtctr r12 \n" // RG_TOP
+);
+
+#if defined(MACOS_X) && defined(__OPTIMIZE__)
+ // On Mac OS X, gcc doesn't push a frame when we are optimized, so trying to tear it down results in grave disorder.
+//#warning Mac OS X optimization on, not popping GCC AsmCall frame
+#else
+ // Mac OS X Server and unoptimized compiles include a GCC AsmCall frame
+ asm (
+" lwz r1,0(r1) \n" // pop off the GCC AsmCall frame
+" lmw r30,-8(r1) \n"
+);
+#endif
+
+asm (
+" bcctr 20,0 \n" // when it hits a leave, it will branch to the current link register
+
+ // calling a system trap
+"systemTrap: \n"
+ // convert to positive system call number
+" subfic r12,r12,-1 \n"
+
+ // save all our registers, including the current link register
+" mflr r13 \n" // RG_SECOND // copy off our link register
+" addi r1,r1,-92 \n" // required 24 byets of linkage, 32 bytes of parameter, plus our saves
+" stw r3,56(r1) \n" // RG_STACK, -36(REAL_STACK)
+" stw r4,60(r1) \n" // RG_OPSTACK, 4(RG_REAL_STACK)
+" stw r5,64(r1) \n" // RG_MEMBASE, 8(RG_REAL_STACK)
+" stw r6,68(r1) \n" // RG_MEMMASK, 12(RG_REAL_STACK)
+" stw r7,72(r1) \n" // RG_ASMCALL, 16(RG_REAL_STACK)
+" stw r8,76(r1) \n" // RG_INSTRUCTIONS, 20(RG_REAL_STACK)
+" stw r9,80(r1) \n" // RG_NUM_INSTRUCTIONS, 24(RG_REAL_STACK)
+" stw r10,84(r1) \n" // RG_VM, 28(RG_REAL_STACK)
+" stw r13,88(r1) \n" // RG_SECOND, 32(RG_REAL_STACK) // link register
+
+ // save the vm stack position to allow recursive VM entry
+" addi r13,r3,-4 \n" // RG_TOP, RG_STACK, -4
+" stw r13,0(r10) \n" //RG_TOP, VM_OFFSET_PROGRAM_STACK(RG_VM)
+
+ // save the system call number as the 0th parameter
+" add r3,r3,r5 \n" // r3, RG_STACK, RG_MEMBASE // r3 is the first parameter to vm->systemCalls
+" stwu r12,4(r3) \n" // RG_TOP, 4(r3)
+
+ // make the system call with the address of all the VM parms as a parameter
+ // vm->systemCalls( &parms )
+" lwz r12,4(r10) \n" // RG_TOP, VM_OFFSET_SYSTEM_CALL(RG_VM)
+" mtctr r12 \n" // RG_TOP
+" bcctrl 20,0 \n"
+" mr r12,r3 \n" // RG_TOP, r3
+
+ // pop our saved registers
+" lwz r3,56(r1) \n" // RG_STACK, 0(RG_REAL_STACK)
+" lwz r4,60(r1) \n" // RG_OPSTACK, 4(RG_REAL_STACK)
+" lwz r5,64(r1) \n" // RG_MEMBASE, 8(RG_REAL_STACK)
+" lwz r6,68(r1) \n" // RG_MEMMASK, 12(RG_REAL_STACK)
+" lwz r7,72(r1) \n" // RG_ASMCALL, 16(RG_REAL_STACK)
+" lwz r8,76(r1) \n" // RG_INSTRUCTIONS, 20(RG_REAL_STACK)
+" lwz r9,80(r1) \n" // RG_NUM_INSTRUCTIONS, 24(RG_REAL_STACK)
+" lwz r10,84(r1) \n" // RG_VM, 28(RG_REAL_STACK)
+" lwz r13,88(r1) \n" // RG_SECOND, 32(RG_REAL_STACK)
+" addi r1,r1,92 \n" // RG_REAL_STACK, RG_REAL_STACK, 36
+
+ // restore the old link register
+" mtlr r13 \n" // RG_SECOND
+
+ // save off the return value
+" stwu r12,4(r4) \n" // RG_TOP, 0(RG_OPSTACK)
+
+ // GCC adds its own prolog / epliog code
+ );
+}
+#else
+
+// codewarrior version
+
+void asm AsmCall( void ) {
+
+ // pop off the destination instruction
+
+ lwz r12,0(r4) // RG_TOP, 0(RG_OPSTACK)
+
+ addi r4,r4,-4 // RG_OPSTACK, RG_OPSTACK, -4
+
+
+
+ // see if it is a system trap
+
+ cmpwi r12,0 // RG_TOP, 0
+
+ bc 12,0, systemTrap
+
+
+
+ // calling another VM function, so lookup in instructionPointers
+
+ slwi r12,r12,2 // RG_TOP,RG_TOP,2
+
+ // FIXME: range check
+
+ lwzx r12, r8, r12 // RG_TOP, RG_INSTRUCTIONS(RG_TOP)
+
+ mtctr r12 // RG_TOP
+
+
+
+ bcctr 20,0 // when it hits a leave, it will branch to the current link register
+
+
+
+ // calling a system trap
+
+systemTrap:
+
+ // convert to positive system call number
+
+ subfic r12,r12,-1
+
+
+
+ // save all our registers, including the current link register
+
+ mflr r13 // RG_SECOND // copy off our link register
+
+ addi r1,r1,-92 // required 24 byets of linkage, 32 bytes of parameter, plus our saves
+
+ stw r3,56(r1) // RG_STACK, -36(REAL_STACK)
+
+ stw r4,60(r1) // RG_OPSTACK, 4(RG_REAL_STACK)
+
+ stw r5,64(r1) // RG_MEMBASE, 8(RG_REAL_STACK)
+
+ stw r6,68(r1) // RG_MEMMASK, 12(RG_REAL_STACK)
+
+ stw r7,72(r1) // RG_ASMCALL, 16(RG_REAL_STACK)
+
+ stw r8,76(r1) // RG_INSTRUCTIONS, 20(RG_REAL_STACK)
+
+ stw r9,80(r1) // RG_NUM_INSTRUCTIONS, 24(RG_REAL_STACK)
+
+ stw r10,84(r1) // RG_VM, 28(RG_REAL_STACK)
+
+ stw r13,88(r1) // RG_SECOND, 32(RG_REAL_STACK) // link register
+
+
+
+ // save the vm stack position to allow recursive VM entry
+
+ addi r13,r3,-4 // RG_TOP, RG_STACK, -4
+
+ stw r13,0(r10) //RG_TOP, VM_OFFSET_PROGRAM_STACK(RG_VM)
+
+
+
+ // save the system call number as the 0th parameter
+
+ add r3,r3,r5 // r3, RG_STACK, RG_MEMBASE // r3 is the first parameter to vm->systemCalls
+
+ stwu r12,4(r3) // RG_TOP, 4(r3)
+
+
+
+ // make the system call with the address of all the VM parms as a parameter
+
+ // vm->systemCalls( &parms )
+
+ lwz r12,4(r10) // RG_TOP, VM_OFFSET_SYSTEM_CALL(RG_VM)
+
+
+
+ // perform macos cross fragment fixup crap
+
+ lwz r9,0(r12)
+
+ stw r2,52(r1) // save old TOC
+
+ lwz r2,4(r12)
+
+
+
+ mtctr r9 // RG_TOP
+
+ bcctrl 20,0
+
+
+
+ lwz r2,52(r1) // restore TOC
+
+
+
+ mr r12,r3 // RG_TOP, r3
+
+
+
+ // pop our saved registers
+
+ lwz r3,56(r1) // RG_STACK, 0(RG_REAL_STACK)
+
+ lwz r4,60(r1) // RG_OPSTACK, 4(RG_REAL_STACK)
+
+ lwz r5,64(r1) // RG_MEMBASE, 8(RG_REAL_STACK)
+
+ lwz r6,68(r1) // RG_MEMMASK, 12(RG_REAL_STACK)
+
+ lwz r7,72(r1) // RG_ASMCALL, 16(RG_REAL_STACK)
+
+ lwz r8,76(r1) // RG_INSTRUCTIONS, 20(RG_REAL_STACK)
+
+ lwz r9,80(r1) // RG_NUM_INSTRUCTIONS, 24(RG_REAL_STACK)
+
+ lwz r10,84(r1) // RG_VM, 28(RG_REAL_STACK)
+
+ lwz r13,88(r1) // RG_SECOND, 32(RG_REAL_STACK)
+
+ addi r1,r1,92 // RG_REAL_STACK, RG_REAL_STACK, 36
+
+
+
+ // restore the old link register
+
+ mtlr r13 // RG_SECOND
+
+
+
+ // save off the return value
+
+ stwu r12,4(r4) // RG_TOP, 0(RG_OPSTACK)
+
+
+
+ blr
+
+}
+
+
+
+
+#endif
diff --git a/src/qcommon/vm_x86.c b/src/qcommon/vm_x86.c
new file mode 100644
index 0000000..c0a703b
--- /dev/null
+++ b/src/qcommon/vm_x86.c
@@ -0,0 +1,1240 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// vm_x86.c -- load time compiler and execution environment for x86
+
+#include "vm_local.h"
+#ifdef _WIN32
+#include <windows.h>
+#endif
+
+#ifdef __FreeBSD__ // rb0101023
+#include <sys/types.h>
+#endif
+
+#ifndef _WIN32
+#include <sys/mman.h> // for PROT_ stuff
+#endif
+
+/* need this on NX enabled systems (i386 with PAE kernel or
+ * noexec32=on x86_64) */
+#ifdef __linux__
+#define VM_X86_MMAP
+#endif
+
+static void VM_Destroy_Compiled(vm_t* self);
+
+/*
+
+ eax scratch
+ ebx scratch
+ ecx scratch (required for shifts)
+ edx scratch (required for divisions)
+ esi program stack
+ edi opstack
+
+*/
+
+// TTimo: initialised the statics, this fixes a crash when entering a compiled VM
+static byte *buf = NULL;
+static byte *jused = NULL;
+static int compiledOfs = 0;
+static byte *code = NULL;
+static int pc = 0;
+
+static int *instructionPointers = NULL;
+
+#define FTOL_PTR
+
+#ifdef _MSC_VER
+
+#if defined( FTOL_PTR )
+int _ftol( float );
+static int ftolPtr = (int)_ftol;
+#endif
+
+void AsmCall( void );
+static int asmCallPtr = (int)AsmCall;
+
+#else // _MSC_VER
+
+#if defined( FTOL_PTR )
+// bk001213 - BEWARE: does not work! UI menu etc. broken - stack!
+// bk001119 - added: int gftol( float x ) { return (int)x; }
+
+int qftol( void ); // bk001213 - label, see unix/ftol.nasm
+int qftol027F( void ); // bk001215 - fixed FPU control variants
+int qftol037F( void );
+int qftol0E7F( void ); // bk010102 - fixed bogus bits (duh)
+int qftol0F7F( void );
+
+
+static int ftolPtr = (int)qftol0F7F;
+#endif // FTOL_PTR
+
+void doAsmCall( void );
+static int asmCallPtr = (int)doAsmCall;
+#endif
+
+
+static int callMask = 0; // bk001213 - init
+
+static int instruction, pass;
+static int lastConst = 0;
+static int oc0, oc1, pop0, pop1;
+
+typedef enum
+{
+ LAST_COMMAND_NONE = 0,
+ LAST_COMMAND_MOV_EDI_EAX,
+ LAST_COMMAND_SUB_DI_4,
+ LAST_COMMAND_SUB_DI_8,
+} ELastCommand;
+
+static ELastCommand LastCommand;
+
+/*
+=================
+AsmCall
+=================
+*/
+#ifdef _MSC_VER
+__declspec( naked ) void AsmCall( void ) {
+int programStack;
+int *opStack;
+int syscallNum;
+vm_t* savedVM;
+
+__asm {
+ mov eax, dword ptr [edi]
+ sub edi, 4
+ or eax,eax
+ jl systemCall
+ // calling another vm function
+ shl eax,2
+ add eax, dword ptr [instructionPointers]
+ call dword ptr [eax]
+ mov eax, dword ptr [edi]
+ and eax, [callMask]
+ ret
+systemCall:
+
+ // convert negative num to system call number
+ // and store right before the first arg
+ neg eax
+ dec eax
+
+ push ebp
+ mov ebp, esp
+ sub esp, __LOCAL_SIZE
+
+ mov dword ptr syscallNum, eax // so C code can get at it
+ mov dword ptr programStack, esi // so C code can get at it
+ mov dword ptr opStack, edi
+
+ push ecx
+ push esi // we may call recursively, so the
+ push edi // statics aren't guaranteed to be around
+}
+
+ savedVM = currentVM;
+
+ // save the stack to allow recursive VM entry
+ currentVM->programStack = programStack - 4;
+ *(int *)((byte *)currentVM->dataBase + programStack + 4) = syscallNum;
+//VM_LogSyscalls( (int *)((byte *)currentVM->dataBase + programStack + 4) );
+ *(opStack+1) = currentVM->systemCall( (int *)((byte *)currentVM->dataBase + programStack + 4) );
+
+ currentVM = savedVM;
+
+_asm {
+ pop edi
+ pop esi
+ pop ecx
+ add edi, 4 // we added the return value
+
+ mov esp, ebp
+ pop ebp
+
+ ret
+}
+
+}
+
+#else //!_MSC_VER
+
+#if defined(__MINGW32__) || defined(MACOS_X) // _ is prepended to compiled symbols
+#define CMANG(sym) "_"#sym
+#else
+#define CMANG(sym) #sym
+#endif
+
+static int callProgramStack;
+static int *callOpStack;
+static int callSyscallNum;
+
+void callAsmCall(void)
+{
+ vm_t *savedVM;
+ int *callOpStack2;
+
+ savedVM = currentVM;
+ callOpStack2 = callOpStack;
+
+ // save the stack to allow recursive VM entry
+ currentVM->programStack = callProgramStack - 4;
+ *(int *)((byte *)currentVM->dataBase + callProgramStack + 4) = callSyscallNum;
+ //VM_LogSyscalls((int *)((byte *)currentVM->dataBase + callProgramStack + 4) );
+ *(callOpStack2+1) = currentVM->systemCall( (intptr_t *)((byte *)currentVM->dataBase + callProgramStack + 4) );
+
+ currentVM = savedVM;
+}
+
+// Note the C space function AsmCall is never actually called, and is in fact
+// arbitrarily named (though this is not true for the MSC version). When a vm
+// makes a system call, control jumps straight to the doAsmCall label.
+void AsmCall( void ) {
+ asm( CMANG(doAsmCall) ": \n\t" \
+ " movl (%%edi),%%eax \n\t" \
+ " subl $4,%%edi \n\t" \
+ " orl %%eax,%%eax \n\t" \
+ " jl systemCall \n\t" \
+ " shll $2,%%eax \n\t" \
+ " addl %3,%%eax \n\t" \
+ " call *(%%eax) \n\t" \
+ " movl (%%edi),%%eax \n\t" \
+ " andl " CMANG(callMask) ", %%eax \n\t" \
+ " jmp doret \n\t" \
+ "systemCall: \n\t" \
+ " negl %%eax \n\t" \
+ " decl %%eax \n\t" \
+ " movl %%eax,%0 \n\t" \
+ " movl %%esi,%1 \n\t" \
+ " movl %%edi,%2 \n\t" \
+ " pushl %%ecx \n\t" \
+ " pushl %%esi \n\t" \
+ " pushl %%edi \n\t" \
+ " call " CMANG(callAsmCall) " \n\t" \
+ " popl %%edi \n\t" \
+ " popl %%esi \n\t" \
+ " popl %%ecx \n\t" \
+ " addl $4,%%edi \n\t" \
+ "doret: \n\t" \
+ " ret \n\t" \
+ : "=rm" (callSyscallNum), "=rm" (callProgramStack), "=rm" (callOpStack) \
+ : "m" (instructionPointers) \
+ : "ax", "di", "si", "cx" \
+ );
+}
+#endif
+
+static int Constant4( void ) {
+ int v;
+
+ v = code[pc] | (code[pc+1]<<8) | (code[pc+2]<<16) | (code[pc+3]<<24);
+ pc += 4;
+ return v;
+}
+
+static int Constant1( void ) {
+ int v;
+
+ v = code[pc];
+ pc += 1;
+ return v;
+}
+
+static void Emit1( int v )
+{
+ buf[ compiledOfs ] = v;
+ compiledOfs++;
+
+ LastCommand = LAST_COMMAND_NONE;
+}
+
+#if 0
+static void Emit2( int v ) {
+ Emit1( v & 255 );
+ Emit1( ( v >> 8 ) & 255 );
+}
+#endif
+
+static void Emit4( int v ) {
+ Emit1( v & 255 );
+ Emit1( ( v >> 8 ) & 255 );
+ Emit1( ( v >> 16 ) & 255 );
+ Emit1( ( v >> 24 ) & 255 );
+}
+
+static int Hex( int c ) {
+ if ( c >= 'a' && c <= 'f' ) {
+ return 10 + c - 'a';
+ }
+ if ( c >= 'A' && c <= 'F' ) {
+ return 10 + c - 'A';
+ }
+ if ( c >= '0' && c <= '9' ) {
+ return c - '0';
+ }
+
+ Com_Error( ERR_DROP, "Hex: bad char '%c'", c );
+
+ return 0;
+}
+static void EmitString( const char *string ) {
+ int c1, c2;
+ int v;
+
+ while ( 1 ) {
+ c1 = string[0];
+ c2 = string[1];
+
+ v = ( Hex( c1 ) << 4 ) | Hex( c2 );
+ Emit1( v );
+
+ if ( !string[2] ) {
+ break;
+ }
+ string += 3;
+ }
+}
+
+
+
+static void EmitCommand(ELastCommand command)
+{
+ switch(command)
+ {
+ case LAST_COMMAND_MOV_EDI_EAX:
+ EmitString( "89 07" ); // mov dword ptr [edi], eax
+ break;
+
+ case LAST_COMMAND_SUB_DI_4:
+ EmitString( "83 EF 04" ); // sub edi, 4
+ break;
+
+ case LAST_COMMAND_SUB_DI_8:
+ EmitString( "83 EF 08" ); // sub edi, 8
+ break;
+ default:
+ break;
+ }
+ LastCommand = command;
+}
+
+static void EmitAddEDI4(vm_t *vm) {
+ if (LastCommand == LAST_COMMAND_SUB_DI_4 && jused[instruction-1] == 0)
+ { // sub di,4
+ compiledOfs -= 3;
+ vm->instructionPointers[ instruction-1 ] = compiledOfs;
+ return;
+ }
+ if (LastCommand == LAST_COMMAND_SUB_DI_8 && jused[instruction-1] == 0)
+ { // sub di,8
+ compiledOfs -= 3;
+ vm->instructionPointers[ instruction-1 ] = compiledOfs;
+ EmitString( "83 EF 04" ); // sub edi,4
+ return;
+ }
+ EmitString( "83 C7 04" ); // add edi,4
+}
+
+static void EmitMovEAXEDI(vm_t *vm) {
+ if (LastCommand == LAST_COMMAND_MOV_EDI_EAX)
+ { // mov [edi], eax
+ compiledOfs -= 2;
+ vm->instructionPointers[ instruction-1 ] = compiledOfs;
+ return;
+ }
+ if (pop1 == OP_DIVI || pop1 == OP_DIVU || pop1 == OP_MULI || pop1 == OP_MULU ||
+ pop1 == OP_STORE4 || pop1 == OP_STORE2 || pop1 == OP_STORE1 )
+ {
+ return;
+ }
+ if (pop1 == OP_CONST && buf[compiledOfs-6] == 0xC7 && buf[compiledOfs-5] == 0x07 )
+ { // mov edi, 0x123456
+ compiledOfs -= 6;
+ vm->instructionPointers[ instruction-1 ] = compiledOfs;
+ EmitString( "B8" ); // mov eax, 0x12345678
+ Emit4( lastConst );
+ return;
+ }
+ EmitString( "8B 07" ); // mov eax, dword ptr [edi]
+}
+
+qboolean EmitMovEBXEDI(vm_t *vm, int andit) {
+ if (LastCommand == LAST_COMMAND_MOV_EDI_EAX)
+ { // mov [edi], eax
+ compiledOfs -= 2;
+ vm->instructionPointers[ instruction-1 ] = compiledOfs;
+ EmitString( "8B D8"); // mov bx, eax
+ return qfalse;
+ }
+ if (pop1 == OP_DIVI || pop1 == OP_DIVU || pop1 == OP_MULI || pop1 == OP_MULU ||
+ pop1 == OP_STORE4 || pop1 == OP_STORE2 || pop1 == OP_STORE1 )
+ {
+ EmitString( "8B D8"); // mov bx, eax
+ return qfalse;
+ }
+ if (pop1 == OP_CONST && buf[compiledOfs-6] == 0xC7 && buf[compiledOfs-5] == 0x07 )
+ { // mov edi, 0x123456
+ compiledOfs -= 6;
+ vm->instructionPointers[ instruction-1 ] = compiledOfs;
+ EmitString( "BB" ); // mov ebx, 0x12345678
+ if (andit) {
+ Emit4( lastConst & andit );
+ } else {
+ Emit4( lastConst );
+ }
+ return qtrue;
+ }
+
+ EmitString( "8B 1F" ); // mov ebx, dword ptr [edi]
+ return qfalse;
+}
+
+/*
+=================
+VM_Compile
+=================
+*/
+void VM_Compile( vm_t *vm, vmHeader_t *header ) {
+ int op;
+ int maxLength;
+ int v;
+ int i;
+ qboolean opt;
+
+ // allocate a very large temp buffer, we will shrink it later
+ maxLength = header->codeLength * 8;
+ buf = Z_Malloc( maxLength );
+ jused = Z_Malloc(header->instructionCount + 2 );
+
+ Com_Memset(jused, 0, header->instructionCount+2);
+
+ // ensure that the optimisation pass knows about all the jump
+ // table targets
+ for( i = 0; i < vm->numJumpTableTargets; i++ ) {
+ jused[ *(int *)(vm->jumpTableTargets + ( i * sizeof( int ) ) ) ] = 1;
+ }
+
+ for(pass=0;pass<2;pass++) {
+ oc0 = -23423;
+ oc1 = -234354;
+ pop0 = -43435;
+ pop1 = -545455;
+
+ // translate all instructions
+ pc = 0;
+ instruction = 0;
+ code = (byte *)header + header->codeOffset;
+ compiledOfs = 0;
+
+ LastCommand = LAST_COMMAND_NONE;
+
+ while ( instruction < header->instructionCount ) {
+ if ( compiledOfs > maxLength - 16 ) {
+ Com_Error( ERR_FATAL, "VM_CompileX86: maxLength exceeded" );
+ }
+
+ vm->instructionPointers[ instruction ] = compiledOfs;
+ instruction++;
+
+ if ( pc > header->codeLength ) {
+ Com_Error( ERR_FATAL, "VM_CompileX86: pc > header->codeLength" );
+ }
+
+ op = code[ pc ];
+ pc++;
+ switch ( op ) {
+ case 0:
+ break;
+ case OP_BREAK:
+ EmitString( "CC" ); // int 3
+ break;
+ case OP_ENTER:
+ EmitString( "81 EE" ); // sub esi, 0x12345678
+ Emit4( Constant4() );
+ break;
+ case OP_CONST:
+ if (code[pc+4] == OP_LOAD4) {
+ EmitAddEDI4(vm);
+ EmitString( "BB" ); // mov ebx, 0x12345678
+ Emit4( (Constant4()&vm->dataMask) + (int)vm->dataBase);
+ EmitString( "8B 03" ); // mov eax, dword ptr [ebx]
+ EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax
+ pc++; // OP_LOAD4
+ instruction += 1;
+ break;
+ }
+ if (code[pc+4] == OP_LOAD2) {
+ EmitAddEDI4(vm);
+ EmitString( "BB" ); // mov ebx, 0x12345678
+ Emit4( (Constant4()&vm->dataMask) + (int)vm->dataBase);
+ EmitString( "0F B7 03" ); // movzx eax, word ptr [ebx]
+ EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax
+ pc++; // OP_LOAD4
+ instruction += 1;
+ break;
+ }
+ if (code[pc+4] == OP_LOAD1) {
+ EmitAddEDI4(vm);
+ EmitString( "BB" ); // mov ebx, 0x12345678
+ Emit4( (Constant4()&vm->dataMask) + (int)vm->dataBase);
+ EmitString( "0F B6 03" ); // movzx eax, byte ptr [ebx]
+ EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax
+ pc++; // OP_LOAD4
+ instruction += 1;
+ break;
+ }
+ if (code[pc+4] == OP_STORE4) {
+ opt = EmitMovEBXEDI(vm, (vm->dataMask & ~3));
+ EmitString( "B8" ); // mov eax, 0x12345678
+ Emit4( Constant4() );
+// if (!opt) {
+// EmitString( "81 E3" ); // and ebx, 0x12345678
+// Emit4( vm->dataMask & ~3 );
+// }
+ EmitString( "89 83" ); // mov dword ptr [ebx+0x12345678], eax
+ Emit4( (int)vm->dataBase );
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ pc++; // OP_STORE4
+ instruction += 1;
+ break;
+ }
+ if (code[pc+4] == OP_STORE2) {
+ opt = EmitMovEBXEDI(vm, (vm->dataMask & ~1));
+ EmitString( "B8" ); // mov eax, 0x12345678
+ Emit4( Constant4() );
+// if (!opt) {
+// EmitString( "81 E3" ); // and ebx, 0x12345678
+// Emit4( vm->dataMask & ~1 );
+// }
+ EmitString( "66 89 83" ); // mov word ptr [ebx+0x12345678], eax
+ Emit4( (int)vm->dataBase );
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ pc++; // OP_STORE4
+ instruction += 1;
+ break;
+ }
+ if (code[pc+4] == OP_STORE1) {
+ opt = EmitMovEBXEDI(vm, vm->dataMask);
+ EmitString( "B8" ); // mov eax, 0x12345678
+ Emit4( Constant4() );
+// if (!opt) {
+// EmitString( "81 E3" ); // and ebx, 0x12345678
+// Emit4( vm->dataMask );
+// }
+ EmitString( "88 83" ); // mov byte ptr [ebx+0x12345678], eax
+ Emit4( (int)vm->dataBase );
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ pc++; // OP_STORE4
+ instruction += 1;
+ break;
+ }
+ if (code[pc+4] == OP_ADD) {
+ EmitString( "81 07" ); // add dword ptr [edi], 0x1234567
+ Emit4( Constant4() );
+ pc++; // OP_ADD
+ instruction += 1;
+ break;
+ }
+ if (code[pc+4] == OP_SUB) {
+ EmitString( "81 2F" ); // sub dword ptr [edi], 0x1234567
+ Emit4( Constant4() );
+ pc++; // OP_ADD
+ instruction += 1;
+ break;
+ }
+ EmitAddEDI4(vm);
+ EmitString( "C7 07" ); // mov dword ptr [edi], 0x12345678
+ lastConst = Constant4();
+ Emit4( lastConst );
+ if (code[pc] == OP_JUMP) {
+ jused[lastConst] = 1;
+ }
+ break;
+ case OP_LOCAL:
+ EmitAddEDI4(vm);
+ EmitString( "8D 86" ); // lea eax, [0x12345678 + esi]
+ oc0 = oc1;
+ oc1 = Constant4();
+ Emit4( oc1 );
+ EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax
+ break;
+ case OP_ARG:
+ EmitMovEAXEDI(vm); // mov eax,dword ptr [edi]
+ EmitString( "89 86" ); // mov dword ptr [esi+database],eax
+ // FIXME: range check
+ Emit4( Constant1() + (int)vm->dataBase );
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ break;
+ case OP_CALL:
+ EmitString( "C7 86" ); // mov dword ptr [esi+database],0x12345678
+ Emit4( (int)vm->dataBase );
+ Emit4( pc );
+ EmitString( "FF 15" ); // call asmCallPtr
+ Emit4( (int)&asmCallPtr );
+ break;
+ case OP_PUSH:
+ EmitAddEDI4(vm);
+ break;
+ case OP_POP:
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ break;
+ case OP_LEAVE:
+ v = Constant4();
+ EmitString( "81 C6" ); // add esi, 0x12345678
+ Emit4( v );
+ EmitString( "C3" ); // ret
+ break;
+ case OP_LOAD4:
+ if (code[pc] == OP_CONST && code[pc+5] == OP_ADD && code[pc+6] == OP_STORE4) {
+ if (oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) {
+ compiledOfs -= 11;
+ vm->instructionPointers[ instruction-1 ] = compiledOfs;
+ }
+ pc++; // OP_CONST
+ v = Constant4();
+ EmitMovEBXEDI(vm, vm->dataMask);
+ if (v == 1 && oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) {
+ EmitString( "FF 83"); // inc dword ptr [ebx + 0x12345678]
+ Emit4( (int)vm->dataBase );
+ } else {
+ EmitString( "8B 83" ); // mov eax, dword ptr [ebx + 0x12345678]
+ Emit4( (int)vm->dataBase );
+ EmitString( "05" ); // add eax, const
+ Emit4( v );
+ if (oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) {
+ EmitString( "89 83" ); // mov dword ptr [ebx+0x12345678], eax
+ Emit4( (int)vm->dataBase );
+ } else {
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ EmitString( "8B 1F" ); // mov ebx, dword ptr [edi]
+ EmitString( "89 83" ); // mov dword ptr [ebx+0x12345678], eax
+ Emit4( (int)vm->dataBase );
+ }
+ }
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ pc++; // OP_ADD
+ pc++; // OP_STORE
+ instruction += 3;
+ break;
+ }
+
+ if (code[pc] == OP_CONST && code[pc+5] == OP_SUB && code[pc+6] == OP_STORE4) {
+ if (oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) {
+ compiledOfs -= 11;
+ vm->instructionPointers[ instruction-1 ] = compiledOfs;
+ }
+ EmitMovEBXEDI(vm, vm->dataMask);
+ EmitString( "8B 83" ); // mov eax, dword ptr [ebx + 0x12345678]
+ Emit4( (int)vm->dataBase );
+ pc++; // OP_CONST
+ v = Constant4();
+ if (v == 1 && oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) {
+ EmitString( "FF 8B"); // dec dword ptr [ebx + 0x12345678]
+ Emit4( (int)vm->dataBase );
+ } else {
+ EmitString( "2D" ); // sub eax, const
+ Emit4( v );
+ if (oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) {
+ EmitString( "89 83" ); // mov dword ptr [ebx+0x12345678], eax
+ Emit4( (int)vm->dataBase );
+ } else {
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ EmitString( "8B 1F" ); // mov ebx, dword ptr [edi]
+ EmitString( "89 83" ); // mov dword ptr [ebx+0x12345678], eax
+ Emit4( (int)vm->dataBase );
+ }
+ }
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ pc++; // OP_SUB
+ pc++; // OP_STORE
+ instruction += 3;
+ break;
+ }
+
+ if (buf[compiledOfs-2] == 0x89 && buf[compiledOfs-1] == 0x07) {
+ compiledOfs -= 2;
+ vm->instructionPointers[ instruction-1 ] = compiledOfs;
+ EmitString( "8B 80"); // mov eax, dword ptr [eax + 0x1234567]
+ Emit4( (int)vm->dataBase );
+ EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax
+ break;
+ }
+ EmitMovEBXEDI(vm, vm->dataMask);
+ EmitString( "8B 83" ); // mov eax, dword ptr [ebx + 0x12345678]
+ Emit4( (int)vm->dataBase );
+ EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax
+ break;
+ case OP_LOAD2:
+ EmitMovEBXEDI(vm, vm->dataMask);
+ EmitString( "0F B7 83" ); // movzx eax, word ptr [ebx + 0x12345678]
+ Emit4( (int)vm->dataBase );
+ EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax
+ break;
+ case OP_LOAD1:
+ EmitMovEBXEDI(vm, vm->dataMask);
+ EmitString( "0F B6 83" ); // movzx eax, byte ptr [ebx + 0x12345678]
+ Emit4( (int)vm->dataBase );
+ EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax
+ break;
+ case OP_STORE4:
+ EmitMovEAXEDI(vm);
+ EmitString( "8B 5F FC" ); // mov ebx, dword ptr [edi-4]
+// if (pop1 != OP_CALL) {
+// EmitString( "81 E3" ); // and ebx, 0x12345678
+// Emit4( vm->dataMask & ~3 );
+// }
+ EmitString( "89 83" ); // mov dword ptr [ebx+0x12345678], eax
+ Emit4( (int)vm->dataBase );
+ EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8
+ break;
+ case OP_STORE2:
+ EmitMovEAXEDI(vm);
+ EmitString( "8B 5F FC" ); // mov ebx, dword ptr [edi-4]
+// EmitString( "81 E3" ); // and ebx, 0x12345678
+// Emit4( vm->dataMask & ~1 );
+ EmitString( "66 89 83" ); // mov word ptr [ebx+0x12345678], eax
+ Emit4( (int)vm->dataBase );
+ EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8
+ break;
+ case OP_STORE1:
+ EmitMovEAXEDI(vm);
+ EmitString( "8B 5F FC" ); // mov ebx, dword ptr [edi-4]
+// EmitString( "81 E3" ); // and ebx, 0x12345678
+// Emit4( vm->dataMask );
+ EmitString( "88 83" ); // mov byte ptr [ebx+0x12345678], eax
+ Emit4( (int)vm->dataBase );
+ EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8
+ break;
+
+ case OP_EQ:
+ EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8
+ EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4]
+ EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8]
+ EmitString( "75 06" ); // jne +6
+ EmitString( "FF 25" ); // jmp [0x12345678]
+ v = Constant4();
+ jused[v] = 1;
+ Emit4( (int)vm->instructionPointers + v*4 );
+ break;
+ case OP_NE:
+ EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8
+ EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4]
+ EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8]
+ EmitString( "74 06" ); // je +6
+ EmitString( "FF 25" ); // jmp [0x12345678]
+ v = Constant4();
+ jused[v] = 1;
+ Emit4( (int)vm->instructionPointers + v*4 );
+ break;
+ case OP_LTI:
+ EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8
+ EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4]
+ EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8]
+ EmitString( "7D 06" ); // jnl +6
+ EmitString( "FF 25" ); // jmp [0x12345678]
+ v = Constant4();
+ jused[v] = 1;
+ Emit4( (int)vm->instructionPointers + v*4 );
+ break;
+ case OP_LEI:
+ EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8
+ EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4]
+ EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8]
+ EmitString( "7F 06" ); // jnle +6
+ EmitString( "FF 25" ); // jmp [0x12345678]
+ v = Constant4();
+ jused[v] = 1;
+ Emit4( (int)vm->instructionPointers + v*4 );
+ break;
+ case OP_GTI:
+ EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8
+ EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4]
+ EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8]
+ EmitString( "7E 06" ); // jng +6
+ EmitString( "FF 25" ); // jmp [0x12345678]
+ v = Constant4();
+ jused[v] = 1;
+ Emit4( (int)vm->instructionPointers + v*4 );
+ break;
+ case OP_GEI:
+ EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8
+ EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4]
+ EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8]
+ EmitString( "7C 06" ); // jnge +6
+ EmitString( "FF 25" ); // jmp [0x12345678]
+ v = Constant4();
+ jused[v] = 1;
+ Emit4( (int)vm->instructionPointers + v*4 );
+ break;
+ case OP_LTU:
+ EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8
+ EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4]
+ EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8]
+ EmitString( "73 06" ); // jnb +6
+ EmitString( "FF 25" ); // jmp [0x12345678]
+ v = Constant4();
+ jused[v] = 1;
+ Emit4( (int)vm->instructionPointers + v*4 );
+ break;
+ case OP_LEU:
+ EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8
+ EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4]
+ EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8]
+ EmitString( "77 06" ); // jnbe +6
+ EmitString( "FF 25" ); // jmp [0x12345678]
+ v = Constant4();
+ jused[v] = 1;
+ Emit4( (int)vm->instructionPointers + v*4 );
+ break;
+ case OP_GTU:
+ EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8
+ EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4]
+ EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8]
+ EmitString( "76 06" ); // jna +6
+ EmitString( "FF 25" ); // jmp [0x12345678]
+ v = Constant4();
+ jused[v] = 1;
+ Emit4( (int)vm->instructionPointers + v*4 );
+ break;
+ case OP_GEU:
+ EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8
+ EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4]
+ EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8]
+ EmitString( "72 06" ); // jnae +6
+ EmitString( "FF 25" ); // jmp [0x12345678]
+ v = Constant4();
+ jused[v] = 1;
+ Emit4( (int)vm->instructionPointers + v*4 );
+ break;
+ case OP_EQF:
+ EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8
+ EmitString( "D9 47 04" ); // fld dword ptr [edi+4]
+ EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8]
+ EmitString( "DF E0" ); // fnstsw ax
+ EmitString( "F6 C4 40" ); // test ah,0x40
+ EmitString( "74 06" ); // je +6
+ EmitString( "FF 25" ); // jmp [0x12345678]
+ v = Constant4();
+ jused[v] = 1;
+ Emit4( (int)vm->instructionPointers + v*4 );
+ break;
+ case OP_NEF:
+ EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8
+ EmitString( "D9 47 04" ); // fld dword ptr [edi+4]
+ EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8]
+ EmitString( "DF E0" ); // fnstsw ax
+ EmitString( "F6 C4 40" ); // test ah,0x40
+ EmitString( "75 06" ); // jne +6
+ EmitString( "FF 25" ); // jmp [0x12345678]
+ v = Constant4();
+ jused[v] = 1;
+ Emit4( (int)vm->instructionPointers + v*4 );
+ break;
+ case OP_LTF:
+ EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8
+ EmitString( "D9 47 04" ); // fld dword ptr [edi+4]
+ EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8]
+ EmitString( "DF E0" ); // fnstsw ax
+ EmitString( "F6 C4 01" ); // test ah,0x01
+ EmitString( "74 06" ); // je +6
+ EmitString( "FF 25" ); // jmp [0x12345678]
+ v = Constant4();
+ jused[v] = 1;
+ Emit4( (int)vm->instructionPointers + v*4 );
+ break;
+ case OP_LEF:
+ EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8
+ EmitString( "D9 47 04" ); // fld dword ptr [edi+4]
+ EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8]
+ EmitString( "DF E0" ); // fnstsw ax
+ EmitString( "F6 C4 41" ); // test ah,0x41
+ EmitString( "74 06" ); // je +6
+ EmitString( "FF 25" ); // jmp [0x12345678]
+ v = Constant4();
+ jused[v] = 1;
+ Emit4( (int)vm->instructionPointers + v*4 );
+ break;
+ case OP_GTF:
+ EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8
+ EmitString( "D9 47 04" ); // fld dword ptr [edi+4]
+ EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8]
+ EmitString( "DF E0" ); // fnstsw ax
+ EmitString( "F6 C4 41" ); // test ah,0x41
+ EmitString( "75 06" ); // jne +6
+ EmitString( "FF 25" ); // jmp [0x12345678]
+ v = Constant4();
+ jused[v] = 1;
+ Emit4( (int)vm->instructionPointers + v*4 );
+ break;
+ case OP_GEF:
+ EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8
+ EmitString( "D9 47 04" ); // fld dword ptr [edi+4]
+ EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8]
+ EmitString( "DF E0" ); // fnstsw ax
+ EmitString( "F6 C4 01" ); // test ah,0x01
+ EmitString( "75 06" ); // jne +6
+ EmitString( "FF 25" ); // jmp [0x12345678]
+ v = Constant4();
+ jused[v] = 1;
+ Emit4( (int)vm->instructionPointers + v*4 );
+ break;
+ case OP_NEGI:
+ EmitString( "F7 1F" ); // neg dword ptr [edi]
+ break;
+ case OP_ADD:
+ EmitMovEAXEDI(vm); // mov eax, dword ptr [edi]
+ EmitString( "01 47 FC" ); // add dword ptr [edi-4],eax
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ break;
+ case OP_SUB:
+ EmitMovEAXEDI(vm); // mov eax, dword ptr [edi]
+ EmitString( "29 47 FC" ); // sub dword ptr [edi-4],eax
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ break;
+ case OP_DIVI:
+ EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4]
+ EmitString( "99" ); // cdq
+ EmitString( "F7 3F" ); // idiv dword ptr [edi]
+ EmitString( "89 47 FC" ); // mov dword ptr [edi-4],eax
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ break;
+ case OP_DIVU:
+ EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4]
+ EmitString( "33 D2" ); // xor edx, edx
+ EmitString( "F7 37" ); // div dword ptr [edi]
+ EmitString( "89 47 FC" ); // mov dword ptr [edi-4],eax
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ break;
+ case OP_MODI:
+ EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4]
+ EmitString( "99" ); // cdq
+ EmitString( "F7 3F" ); // idiv dword ptr [edi]
+ EmitString( "89 57 FC" ); // mov dword ptr [edi-4],edx
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ break;
+ case OP_MODU:
+ EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4]
+ EmitString( "33 D2" ); // xor edx, edx
+ EmitString( "F7 37" ); // div dword ptr [edi]
+ EmitString( "89 57 FC" ); // mov dword ptr [edi-4],edx
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ break;
+ case OP_MULI:
+ EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4]
+ EmitString( "F7 2F" ); // imul dword ptr [edi]
+ EmitString( "89 47 FC" ); // mov dword ptr [edi-4],eax
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ break;
+ case OP_MULU:
+ EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4]
+ EmitString( "F7 27" ); // mul dword ptr [edi]
+ EmitString( "89 47 FC" ); // mov dword ptr [edi-4],eax
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ break;
+ case OP_BAND:
+ EmitMovEAXEDI(vm); // mov eax, dword ptr [edi]
+ EmitString( "21 47 FC" ); // and dword ptr [edi-4],eax
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ break;
+ case OP_BOR:
+ EmitMovEAXEDI(vm); // mov eax, dword ptr [edi]
+ EmitString( "09 47 FC" ); // or dword ptr [edi-4],eax
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ break;
+ case OP_BXOR:
+ EmitMovEAXEDI(vm); // mov eax, dword ptr [edi]
+ EmitString( "31 47 FC" ); // xor dword ptr [edi-4],eax
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ break;
+ case OP_BCOM:
+ EmitString( "F7 17" ); // not dword ptr [edi]
+ break;
+ case OP_LSH:
+ EmitString( "8B 0F" ); // mov ecx, dword ptr [edi]
+ EmitString( "D3 67 FC" ); // shl dword ptr [edi-4], cl
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ break;
+ case OP_RSHI:
+ EmitString( "8B 0F" ); // mov ecx, dword ptr [edi]
+ EmitString( "D3 7F FC" ); // sar dword ptr [edi-4], cl
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ break;
+ case OP_RSHU:
+ EmitString( "8B 0F" ); // mov ecx, dword ptr [edi]
+ EmitString( "D3 6F FC" ); // shr dword ptr [edi-4], cl
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ break;
+ case OP_NEGF:
+ EmitString( "D9 07" ); // fld dword ptr [edi]
+ EmitString( "D9 E0" ); // fchs
+ EmitString( "D9 1F" ); // fstp dword ptr [edi]
+ break;
+ case OP_ADDF:
+ EmitString( "D9 47 FC" ); // fld dword ptr [edi-4]
+ EmitString( "D8 07" ); // fadd dword ptr [edi]
+ EmitString( "D9 5F FC" ); // fstp dword ptr [edi-4]
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ break;
+ case OP_SUBF:
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ EmitString( "D9 07" ); // fld dword ptr [edi]
+ EmitString( "D8 67 04" ); // fsub dword ptr [edi+4]
+ EmitString( "D9 1F" ); // fstp dword ptr [edi]
+ break;
+ case OP_DIVF:
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ EmitString( "D9 07" ); // fld dword ptr [edi]
+ EmitString( "D8 77 04" ); // fdiv dword ptr [edi+4]
+ EmitString( "D9 1F" ); // fstp dword ptr [edi]
+ break;
+ case OP_MULF:
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ EmitString( "D9 07" ); // fld dword ptr [edi]
+ EmitString( "D8 4f 04" ); // fmul dword ptr [edi+4]
+ EmitString( "D9 1F" ); // fstp dword ptr [edi]
+ break;
+ case OP_CVIF:
+ EmitString( "DB 07" ); // fild dword ptr [edi]
+ EmitString( "D9 1F" ); // fstp dword ptr [edi]
+ break;
+ case OP_CVFI:
+#ifndef FTOL_PTR // WHENHELLISFROZENOVER // bk001213 - was used in 1.17
+ // not IEEE complient, but simple and fast
+ EmitString( "D9 07" ); // fld dword ptr [edi]
+ EmitString( "DB 1F" ); // fistp dword ptr [edi]
+#else // FTOL_PTR
+ // call the library conversion function
+ EmitString( "D9 07" ); // fld dword ptr [edi]
+ EmitString( "FF 15" ); // call ftolPtr
+ Emit4( (int)&ftolPtr );
+ EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax
+#endif
+ break;
+ case OP_SEX8:
+ EmitString( "0F BE 07" ); // movsx eax, byte ptr [edi]
+ EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax
+ break;
+ case OP_SEX16:
+ EmitString( "0F BF 07" ); // movsx eax, word ptr [edi]
+ EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax
+ break;
+
+ case OP_BLOCK_COPY:
+ // FIXME: range check
+ EmitString( "56" ); // push esi
+ EmitString( "57" ); // push edi
+ EmitString( "8B 37" ); // mov esi,[edi]
+ EmitString( "8B 7F FC" ); // mov edi,[edi-4]
+ EmitString( "B9" ); // mov ecx,0x12345678
+ Emit4( Constant4() >> 2 );
+ EmitString( "B8" ); // mov eax, datamask
+ Emit4( vm->dataMask );
+ EmitString( "BB" ); // mov ebx, database
+ Emit4( (int)vm->dataBase );
+ EmitString( "23 F0" ); // and esi, eax
+ EmitString( "03 F3" ); // add esi, ebx
+ EmitString( "23 F8" ); // and edi, eax
+ EmitString( "03 FB" ); // add edi, ebx
+ EmitString( "F3 A5" ); // rep movsd
+ EmitString( "5F" ); // pop edi
+ EmitString( "5E" ); // pop esi
+ EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8
+ break;
+
+ case OP_JUMP:
+ EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4
+ EmitString( "8B 47 04" ); // mov eax,dword ptr [edi+4]
+ // FIXME: range check
+ EmitString( "FF 24 85" ); // jmp dword ptr [instructionPointers + eax * 4]
+ Emit4( (int)vm->instructionPointers );
+ break;
+ default:
+ Com_Error( ERR_DROP, "VM_CompileX86: bad opcode %i at offset %i", op, pc );
+ }
+ pop0 = pop1;
+ pop1 = op;
+ }
+ }
+
+ // copy to an exact size buffer on the hunk
+ vm->codeLength = compiledOfs;
+#ifdef VM_X86_MMAP
+ vm->codeBase = mmap(NULL, compiledOfs, PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
+ if(vm->codeBase == (void*)-1)
+ Com_Error(ERR_DROP, "VM_CompileX86: can't mmap memory");
+#elif _WIN32
+ // allocate memory with EXECUTE permissions under windows.
+ vm->codeBase = VirtualAlloc(NULL, compiledOfs, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
+ if(!vm->codeBase)
+ Com_Error(ERR_DROP, "VM_CompileX86: VirtualAlloc failed");
+#else
+ vm->codeBase = malloc(compiledOfs);
+#endif
+
+ Com_Memcpy( vm->codeBase, buf, compiledOfs );
+
+#ifdef VM_X86_MMAP
+ if(mprotect(vm->codeBase, compiledOfs, PROT_READ|PROT_EXEC))
+ Com_Error(ERR_DROP, "VM_CompileX86: mprotect failed");
+#elif _WIN32
+ {
+ DWORD oldProtect = 0;
+
+ // remove write permissions.
+ if(!VirtualProtect(vm->codeBase, compiledOfs, PAGE_EXECUTE_READ, &oldProtect))
+ Com_Error(ERR_DROP, "VM_CompileX86: VirtualProtect failed");
+ }
+#endif
+
+ Z_Free( buf );
+ Z_Free( jused );
+ Com_Printf( "VM file %s compiled to %i bytes of code\n", vm->name, compiledOfs );
+
+ vm->destroy = VM_Destroy_Compiled;
+
+ // offset all the instruction pointers for the new location
+ for ( i = 0 ; i < header->instructionCount ; i++ ) {
+ vm->instructionPointers[i] += (int)vm->codeBase;
+ }
+}
+
+void VM_Destroy_Compiled(vm_t* self)
+{
+#ifdef VM_X86_MMAP
+ munmap(self->codeBase, self->codeLength);
+#elif _WIN32
+ VirtualFree(self->codeBase, self->codeLength, MEM_RELEASE);
+#else
+ free(self->codeBase);
+#endif
+}
+
+/*
+==============
+VM_CallCompiled
+
+This function is called directly by the generated code
+==============
+*/
+int VM_CallCompiled( vm_t *vm, int *args ) {
+ int stack[1024];
+ int programCounter;
+ int programStack;
+ int stackOnEntry;
+ byte *image;
+ void *entryPoint;
+ void *opStack;
+ int *oldInstructionPointers;
+
+ oldInstructionPointers = instructionPointers;
+
+ currentVM = vm;
+ instructionPointers = vm->instructionPointers;
+
+ // interpret the code
+ vm->currentlyInterpreting = qtrue;
+
+ callMask = vm->dataMask;
+
+ // we might be called recursively, so this might not be the very top
+ programStack = vm->programStack;
+ stackOnEntry = programStack;
+
+ // set up the stack frame
+ image = vm->dataBase;
+
+ programCounter = 0;
+
+ programStack -= 48;
+
+ *(int *)&image[ programStack + 44] = args[9];
+ *(int *)&image[ programStack + 40] = args[8];
+ *(int *)&image[ programStack + 36] = args[7];
+ *(int *)&image[ programStack + 32] = args[6];
+ *(int *)&image[ programStack + 28] = args[5];
+ *(int *)&image[ programStack + 24] = args[4];
+ *(int *)&image[ programStack + 20] = args[3];
+ *(int *)&image[ programStack + 16] = args[2];
+ *(int *)&image[ programStack + 12] = args[1];
+ *(int *)&image[ programStack + 8 ] = args[0];
+ *(int *)&image[ programStack + 4 ] = 0; // return stack
+ *(int *)&image[ programStack ] = -1; // will terminate the loop on return
+
+ // off we go into generated code...
+ entryPoint = vm->codeBase;
+ opStack = &stack;
+
+#ifdef _MSC_VER
+ __asm {
+ pushad
+ mov esi, programStack;
+ mov edi, opStack
+ call entryPoint
+ mov programStack, esi
+ mov opStack, edi
+ popad
+ }
+#else
+ {
+ static int memProgramStack;
+ static void *memOpStack;
+ static void *memEntryPoint;
+
+ memProgramStack = programStack;
+ memOpStack = opStack;
+ memEntryPoint = entryPoint;
+
+ __asm__(" pushal \n" \
+ " movl %0,%%esi \n" \
+ " movl %1,%%edi \n" \
+ " call *%2 \n" \
+ " movl %%esi,%0 \n" \
+ " movl %%edi,%1 \n" \
+ " popal \n" \
+ : "=m" (memProgramStack), "=m" (memOpStack) \
+ : "m" (memEntryPoint), "m" (memProgramStack), "m" (memOpStack) \
+ : "si", "di" \
+ );
+
+ programStack = memProgramStack;
+ opStack = memOpStack;
+ }
+#endif
+
+ if ( opStack != &stack[1] ) {
+ Com_Error( ERR_DROP, "opStack corrupted in compiled code" );
+ }
+ if ( programStack != stackOnEntry - 48 ) {
+ Com_Error( ERR_DROP, "programStack corrupted in compiled code" );
+ }
+
+ vm->programStack = stackOnEntry;
+
+ // in case we were recursively called by another vm
+ instructionPointers = oldInstructionPointers;
+
+ return *(int *)opStack;
+}
diff --git a/src/qcommon/vm_x86_64.c b/src/qcommon/vm_x86_64.c
new file mode 100644
index 0000000..e8e827e
--- /dev/null
+++ b/src/qcommon/vm_x86_64.c
@@ -0,0 +1,1023 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+Copyright (C) 2005 Ludwig Nussel <ludwig.nussel@web.de>
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// vm_x86_64.c -- load time compiler and execution environment for x86-64
+
+#include "vm_local.h"
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+
+#ifdef DEBUG_VM
+#define Dfprintf(fd, args...) fprintf(fd, ##args)
+static FILE* qdasmout;
+#else
+#define Dfprintf(args...)
+#endif
+
+static void VM_Destroy_Compiled(vm_t* self);
+
+/*
+
+ |=====================|
+ ^ dataMask ^- programStack rdi
+ |
+ +- r8
+
+ eax scratch
+ ebx scratch
+ ecx scratch (required for shifts)
+ edx scratch (required for divisions)
+ rsi stack pointer (opStack)
+ rdi program frame pointer (programStack)
+ r8 pointer data (vm->dataBase)
+ r10 start of generated code
+*/
+
+
+static long callAsmCall(long callProgramStack, long callSyscallNum)
+{
+ vm_t *savedVM;
+ long ret = 0x77;
+ long args[11];
+// int iargs[11];
+ int i;
+
+// Dfprintf(stderr, "callAsmCall(%ld, %ld)\n", callProgramStack, callSyscallNum);
+// Com_Printf("-> callAsmCall %s, level %d, num %ld\n", currentVM->name, currentVM->callLevel, callSyscallNum);
+
+ savedVM = currentVM;
+
+ // save the stack to allow recursive VM entry
+ currentVM->programStack = callProgramStack - 4;
+
+ args[0] = callSyscallNum;
+// iargs[0] = callSyscallNum;
+ for(i = 0; i < 10; ++i)
+ {
+// iargs[i+1] = *(int *)((byte *)currentVM->dataBase + callProgramStack + 8 + 4*i);
+ args[i+1] = *(int *)((byte *)currentVM->dataBase + callProgramStack + 8 + 4*i);
+ }
+ ret = currentVM->systemCall(args);
+
+ currentVM = savedVM;
+// Com_Printf("<- callAsmCall %s, level %d, num %ld\n", currentVM->name, currentVM->callLevel, callSyscallNum);
+
+ return ret;
+}
+
+#ifdef DEBUG_VM // bk001204
+static char *opnames[256] = {
+ "OP_UNDEF",
+
+ "OP_IGNORE",
+
+ "OP_BREAK",
+
+ "OP_ENTER",
+ "OP_LEAVE",
+ "OP_CALL",
+ "OP_PUSH",
+ "OP_POP",
+
+ "OP_CONST",
+
+ "OP_LOCAL",
+
+ "OP_JUMP",
+
+ //-------------------
+
+ "OP_EQ",
+ "OP_NE",
+
+ "OP_LTI",
+ "OP_LEI",
+ "OP_GTI",
+ "OP_GEI",
+
+ "OP_LTU",
+ "OP_LEU",
+ "OP_GTU",
+ "OP_GEU",
+
+ "OP_EQF",
+ "OP_NEF",
+
+ "OP_LTF",
+ "OP_LEF",
+ "OP_GTF",
+ "OP_GEF",
+
+ //-------------------
+
+ "OP_LOAD1",
+ "OP_LOAD2",
+ "OP_LOAD4",
+ "OP_STORE1",
+ "OP_STORE2",
+ "OP_STORE4",
+ "OP_ARG",
+
+ "OP_BLOCK_COPY",
+
+ //-------------------
+
+ "OP_SEX8",
+ "OP_SEX16",
+
+ "OP_NEGI",
+ "OP_ADD",
+ "OP_SUB",
+ "OP_DIVI",
+ "OP_DIVU",
+ "OP_MODI",
+ "OP_MODU",
+ "OP_MULI",
+ "OP_MULU",
+
+ "OP_BAND",
+ "OP_BOR",
+ "OP_BXOR",
+ "OP_BCOM",
+
+ "OP_LSH",
+ "OP_RSHI",
+ "OP_RSHU",
+
+ "OP_NEGF",
+ "OP_ADDF",
+ "OP_SUBF",
+ "OP_DIVF",
+ "OP_MULF",
+
+ "OP_CVIF",
+ "OP_CVFI"
+};
+#endif // DEBUG_VM
+
+static unsigned char op_argsize[256] =
+{
+ [OP_ENTER] = 4,
+ [OP_LEAVE] = 4,
+ [OP_CONST] = 4,
+ [OP_LOCAL] = 4,
+ [OP_EQ] = 4,
+ [OP_NE] = 4,
+ [OP_LTI] = 4,
+ [OP_LEI] = 4,
+ [OP_GTI] = 4,
+ [OP_GEI] = 4,
+ [OP_LTU] = 4,
+ [OP_LEU] = 4,
+ [OP_GTU] = 4,
+ [OP_GEU] = 4,
+ [OP_EQF] = 4,
+ [OP_NEF] = 4,
+ [OP_LTF] = 4,
+ [OP_LEF] = 4,
+ [OP_GTF] = 4,
+ [OP_GEF] = 4,
+ [OP_ARG] = 1,
+ [OP_BLOCK_COPY] = 4,
+};
+
+#define emit(x...) \
+ do { fprintf(fh_s, ##x); fputc('\n', fh_s); } while(0)
+
+// integer compare and jump
+#define IJ(op) \
+ emit("subq $8, %%rsi"); \
+ emit("movl 4(%%rsi), %%eax"); \
+ emit("cmpl 8(%%rsi), %%eax"); \
+ emit(op " i_%08x", instruction+1); \
+ emit("jmp i_%08x", iarg);
+
+#ifdef USE_X87
+#define FJ(bits, op) \
+ emit("subq $8, %%rsi");\
+ emit("flds 4(%%rsi)");\
+ emit("fcomps 8(%%rsi)");\
+ emit("fnstsw %%ax");\
+ emit("testb $" #bits ", %%ah");\
+ emit(op " i_%08x", instruction+1);\
+ emit("jmp i_%08x", iarg);
+#define XJ(x)
+#else
+#define FJ(x, y)
+#define XJ(op) \
+ emit("subq $8, %%rsi");\
+ emit("movss 4(%%rsi), %%xmm0");\
+ emit("ucomiss 8(%%rsi), %%xmm0");\
+ emit("jp i_%08x", instruction+1);\
+ emit(op " i_%08x", instruction+1);\
+ emit("jmp i_%08x", iarg);
+#endif
+
+#define SIMPLE(op) \
+ emit("subq $4, %%rsi"); \
+ emit("movl 4(%%rsi), %%eax"); \
+ emit(op " %%eax, 0(%%rsi)");
+
+#ifdef USE_X87
+#define FSIMPLE(op) \
+ emit("subq $4, %%rsi"); \
+ emit("flds 0(%%rsi)"); \
+ emit(op " 4(%%rsi)"); \
+ emit("fstps 0(%%rsi)");
+#define XSIMPLE(op)
+#else
+#define FSIMPLE(op)
+#define XSIMPLE(op) \
+ emit("subq $4, %%rsi"); \
+ emit("movss 0(%%rsi), %%xmm0"); \
+ emit(op " 4(%%rsi), %%xmm0"); \
+ emit("movss %%xmm0, 0(%%rsi)");
+#endif
+
+#define SHIFT(op) \
+ emit("subq $4, %%rsi"); \
+ emit("movl 4(%%rsi), %%ecx"); \
+ emit("movl 0(%%rsi), %%eax"); \
+ emit(op " %%cl, %%eax"); \
+ emit("movl %%eax, 0(%%rsi)");
+
+#if 1
+#define RANGECHECK(reg) \
+ emit("andl $0x%x, %%" #reg, vm->dataMask);
+#elif 0
+#define RANGECHECK(reg) \
+ emit("pushl %%" #reg); \
+ emit("andl $0x%x, %%" #reg, ~vm->dataMask); \
+ emit("jz rangecheck_ok_i_%08x", instruction); \
+ emit("int3"); \
+ emit("rangecheck_ok_i_%08x:", instruction); \
+ emit("popl %%" #reg); \
+ emit("andl $0x%x, %%" #reg, vm->dataMask);
+#else
+#define RANGECHECK(reg)
+#endif
+
+#ifdef DEBUG_VM
+#define NOTIMPL(x) \
+ do { Com_Error(ERR_DROP, "instruction not implemented: %s\n", opnames[x]); } while(0)
+#else
+#define NOTIMPL(x) \
+ do { Com_Printf(S_COLOR_RED "instruction not implemented: %x\n", x); vm->compiled = qfalse; return; } while(0)
+#endif
+
+static void* getentrypoint(vm_t* vm)
+{
+ return vm->codeBase+64; // skip ELF header
+}
+
+char* mmapfile(const char* fn, size_t* size)
+{
+ int fd = -1;
+ char* mem = NULL;
+ struct stat stb;
+
+ fd = open(fn, O_RDONLY);
+ if(fd == -1)
+ goto out;
+
+ if(fstat(fd, &stb) == -1)
+ goto out;
+
+ *size = stb.st_size;
+
+ mem = mmap(NULL, stb.st_size, PROT_READ|PROT_EXEC, MAP_SHARED, fd, 0);
+ if(mem == (void*)-1)
+ mem = NULL;
+
+out:
+ if(fd != -1)
+ close(fd);
+
+ return mem;
+}
+
+static int doas(char* in, char* out, unsigned char** compiledcode)
+{
+ unsigned char* mem;
+ size_t size = -1;
+ pid_t pid;
+
+ Com_Printf("running assembler < %s > %s\n", in, out);
+ pid = fork();
+ if(pid == -1)
+ {
+ Com_Printf(S_COLOR_RED "can't fork\n");
+ return -1;
+ }
+
+ if(!pid)
+ {
+ char* const argv[] = {
+ "as",
+ "-o",
+ out,
+ in,
+ NULL
+ };
+
+ execvp(argv[0], argv);
+ _exit(-1);
+ }
+ else
+ {
+ int status;
+ if(waitpid(pid, &status, 0) == -1)
+ {
+ Com_Printf(S_COLOR_RED "can't wait for as: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if(!WIFEXITED(status))
+ {
+ Com_Printf(S_COLOR_RED "as died\n");
+ return -1;
+ }
+ if(WEXITSTATUS(status))
+ {
+ Com_Printf(S_COLOR_RED "as failed with status %d\n", WEXITSTATUS(status));
+ return -1;
+ }
+ }
+
+ Com_Printf("done\n");
+
+ mem = (unsigned char*)mmapfile(out, &size);
+ if(!mem)
+ {
+ Com_Printf(S_COLOR_RED "can't mmap object file %s: %s\n", out, strerror(errno));
+ return -1;
+ }
+
+ *compiledcode = mem;
+
+ return size;
+}
+
+static void block_copy_vm(unsigned dest, unsigned src, unsigned count)
+{
+ unsigned dataMask = currentVM->dataMask;
+
+ if ((dest & dataMask) != dest
+ || (src & dataMask) != src
+ || ((dest+count) & dataMask) != dest + count
+ || ((src+count) & dataMask) != src + count)
+ {
+ Com_Error(ERR_DROP, "OP_BLOCK_COPY out of range!\n");
+ }
+
+ memcpy(currentVM->dataBase+dest, currentVM->dataBase+src, count);
+}
+
+/*
+=================
+VM_Compile
+=================
+*/
+void VM_Compile( vm_t *vm, vmHeader_t *header ) {
+ unsigned char op;
+ int pc;
+ unsigned instruction;
+ char* code;
+ unsigned iarg = 0;
+ unsigned char barg = 0;
+ void* entryPoint;
+
+ char fn_s[2*MAX_QPATH]; // output file for assembler code
+ char fn_o[2*MAX_QPATH]; // file written by as
+#ifdef DEBUG_VM
+ char fn_d[MAX_QPATH]; // disassembled
+#endif
+ FILE* fh_s;
+ int fd_s, fd_o;
+ byte* compiledcode;
+ int compiledsize;
+
+ Com_Printf("compiling %s\n", vm->name);
+
+#ifdef DEBUG_VM
+ snprintf(fn_s, sizeof(fn_s), "%.63s.s", vm->name);
+ snprintf(fn_o, sizeof(fn_o), "%.63s.o", vm->name);
+ fd_s = open(fn_s, O_CREAT|O_WRONLY, 0644);
+ fd_o = open(fn_o, O_CREAT|O_WRONLY, 0644);
+#else
+ snprintf(fn_s, sizeof(fn_s), "/tmp/%.63s.s_XXXXXX", vm->name);
+ snprintf(fn_o, sizeof(fn_o), "/tmp/%.63s.o_XXXXXX", vm->name);
+ fd_s = mkstemp(fn_s);
+ fd_o = mkstemp(fn_o);
+#endif
+ if(fd_s == -1 || fd_o == -1)
+ {
+ if(fd_s != -1) close(fd_s);
+ if(fd_o != -1) close(fd_o);
+ unlink(fn_s);
+ unlink(fn_o);
+
+ Com_Printf(S_COLOR_RED "can't create temporary file %s for vm\n", fn_s);
+ vm->compiled = qfalse;
+ return;
+ }
+
+#ifdef DEBUG_VM
+ strcpy(fn_d,vm->name);
+ strcat(fn_d, ".qdasm");
+
+ qdasmout = fopen(fn_d, "w");
+#endif
+
+ fh_s = fdopen(fd_s, "wb");
+ if(!fh_s)
+ {
+ Com_Printf(S_COLOR_RED "can't write %s\n", fn_s);
+ vm->compiled = qfalse;
+ return;
+ }
+
+ // translate all instructions
+ pc = 0;
+ code = (char *)header + header->codeOffset;
+
+ emit("start:");
+ emit("or %%r8, %%r8"); // check whether to set up instruction pointers
+ emit("jnz main");
+ emit("jmp setupinstructionpointers");
+
+ emit("main:");
+
+ for ( instruction = 0; instruction < header->instructionCount; ++instruction )
+ {
+ op = code[ pc ];
+ ++pc;
+
+ vm->instructionPointers[instruction] = pc;
+
+#if 0
+ emit("nop");
+ emit("movq $%d, %%r15", instruction);
+ emit("nop");
+#endif
+
+ if(op_argsize[op] == 4)
+ {
+ iarg = *(int*)(code+pc);
+ pc += 4;
+ Dfprintf(qdasmout, "%s %8u\n", opnames[op], iarg);
+ }
+ else if(op_argsize[op] == 1)
+ {
+ barg = code[pc++];
+ Dfprintf(qdasmout, "%s %8hhu\n", opnames[op], barg);
+ }
+ else
+ {
+ Dfprintf(qdasmout, "%s\n", opnames[op]);
+ }
+ emit("i_%08x:", instruction);
+ switch ( op )
+ {
+ case OP_UNDEF:
+ NOTIMPL(op);
+ break;
+ case OP_IGNORE:
+ emit("nop");
+ break;
+ case OP_BREAK:
+ emit("int3");
+ break;
+ case OP_ENTER:
+ emit("subl $%d, %%edi", iarg);
+ RANGECHECK(edi);
+ break;
+ case OP_LEAVE:
+ emit("addl $%d, %%edi", iarg); // get rid of stack frame
+ emit("ret");
+ break;
+ case OP_CALL:
+ emit("movl 0(%%rsi), %%eax"); // get instr from stack
+ emit("subq $4, %%rsi");
+ emit("movl $%d, 0(%%r8, %%rdi, 1)", instruction+1); // save next instruction
+ emit("orl %%eax, %%eax");
+ emit("jl callSyscall%d", instruction);
+ emit("movq $%lu, %%rbx", (unsigned long)vm->instructionPointers);
+ emit("movl (%%rbx, %%rax, 4), %%eax"); // load new relative jump address
+ emit("addq %%r10, %%rax");
+ emit("callq *%%rax");
+ emit("jmp i_%08x", instruction+1);
+ emit("callSyscall%d:", instruction);
+// emit("fnsave 4(%%rsi)");
+ emit("push %%rsi");
+ emit("push %%rdi");
+ emit("push %%r8");
+ emit("push %%r9");
+ emit("push %%r10");
+ emit("movq %%rsp, %%rbx"); // we need to align the stack pointer
+ emit("subq $8, %%rbx"); // |
+ emit("andq $127, %%rbx"); // |
+ emit("subq %%rbx, %%rsp"); // <-+
+ emit("push %%rbx");
+ emit("negl %%eax"); // convert to actual number
+ emit("decl %%eax");
+ // first argument already in rdi
+ emit("movq %%rax, %%rsi"); // second argument in rsi
+ emit("movq $%lu, %%rax", (unsigned long)callAsmCall);
+ emit("callq *%%rax");
+ emit("pop %%rbx");
+ emit("addq %%rbx, %%rsp");
+ emit("pop %%r10");
+ emit("pop %%r9");
+ emit("pop %%r8");
+ emit("pop %%rdi");
+ emit("pop %%rsi");
+// emit("frstor 4(%%rsi)");
+ emit("addq $4, %%rsi");
+ emit("movl %%eax, (%%rsi)"); // store return value
+ break;
+ case OP_PUSH:
+ emit("addq $4, %%rsi");
+ break;
+ case OP_POP:
+ emit("subq $4, %%rsi");
+ break;
+ case OP_CONST:
+ emit("addq $4, %%rsi");
+ emit("movl $%d, 0(%%rsi)", iarg);
+ break;
+ case OP_LOCAL:
+ emit("movl %%edi, %%ebx");
+ emit("addl $%d,%%ebx", iarg);
+ emit("addq $4, %%rsi");
+ emit("movl %%ebx, 0(%%rsi)");
+ break;
+ case OP_JUMP:
+ emit("movl 0(%%rsi), %%eax"); // get instr from stack
+ emit("subq $4, %%rsi");
+ emit("movq $%lu, %%rbx", (unsigned long)vm->instructionPointers);
+ emit("movl (%%rbx, %%rax, 4), %%eax"); // load new relative jump address
+ emit("addq %%r10, %%rax");
+ emit("jmp *%%rax");
+ break;
+ case OP_EQ:
+ IJ("jne");
+ break;
+ case OP_NE:
+ IJ("je");
+ break;
+ case OP_LTI:
+ IJ("jnl");
+ break;
+ case OP_LEI:
+ IJ("jnle");
+ break;
+ case OP_GTI:
+ IJ("jng");
+ break;
+ case OP_GEI:
+ IJ("jnge");
+ break;
+ case OP_LTU:
+ IJ("jnb");
+ break;
+ case OP_LEU:
+ IJ("jnbe");
+ break;
+ case OP_GTU:
+ IJ("jna");
+ break;
+ case OP_GEU:
+ IJ("jnae");
+ break;
+ case OP_EQF:
+ FJ(0x40, "jz");
+ XJ("jnz");
+ break;
+ case OP_NEF:
+ FJ(0x40, "jnz");
+#ifndef USE_X87
+ emit("subq $8, %%rsi");
+ emit("movss 4(%%rsi), %%xmm0");
+ emit("ucomiss 8(%%rsi), %%xmm0");
+ emit("jp dojump_i_%08x", instruction);
+ emit("jz i_%08x", instruction+1);
+ emit("dojump_i_%08x:", instruction);
+ emit("jmp i_%08x", iarg);
+#endif
+ break;
+ case OP_LTF:
+ FJ(0x01, "jz");
+ XJ("jnc");
+ break;
+ case OP_LEF:
+ FJ(0x41, "jz");
+ XJ("ja");
+ break;
+ case OP_GTF:
+ FJ(0x41, "jnz");
+ XJ("jbe");
+ break;
+ case OP_GEF:
+ FJ(0x01, "jnz");
+ XJ("jb");
+ break;
+ case OP_LOAD1:
+ emit("movl 0(%%rsi), %%eax"); // get value from stack
+ RANGECHECK(eax);
+ emit("movb 0(%%r8, %%rax, 1), %%al"); // deref into eax
+ emit("andq $255, %%rax");
+ emit("movl %%eax, 0(%%rsi)"); // store on stack
+ break;
+ case OP_LOAD2:
+ emit("movl 0(%%rsi), %%eax"); // get value from stack
+ RANGECHECK(eax);
+ emit("movw 0(%%r8, %%rax, 1), %%ax"); // deref into eax
+ emit("movl %%eax, 0(%%rsi)"); // store on stack
+ break;
+ case OP_LOAD4:
+ emit("movl 0(%%rsi), %%eax"); // get value from stack
+ RANGECHECK(eax); // not a pointer!?
+ emit("movl 0(%%r8, %%rax, 1), %%eax"); // deref into eax
+ emit("movl %%eax, 0(%%rsi)"); // store on stack
+ break;
+ case OP_STORE1:
+ emit("movl 0(%%rsi), %%eax"); // get value from stack
+ emit("andq $255, %%rax");
+ emit("movl -4(%%rsi), %%ebx"); // get pointer from stack
+ RANGECHECK(ebx);
+ emit("movb %%al, 0(%%r8, %%rbx, 1)"); // store in memory
+ emit("subq $8, %%rsi");
+ break;
+ case OP_STORE2:
+ emit("movl 0(%%rsi), %%eax"); // get value from stack
+ emit("movl -4(%%rsi), %%ebx"); // get pointer from stack
+ RANGECHECK(ebx);
+ emit("movw %%ax, 0(%%r8, %%rbx, 1)"); // store in memory
+ emit("subq $8, %%rsi");
+ break;
+ case OP_STORE4:
+ emit("movl -4(%%rsi), %%ebx"); // get pointer from stack
+ RANGECHECK(ebx);
+ emit("movl 0(%%rsi), %%ecx"); // get value from stack
+ emit("movl %%ecx, 0(%%r8, %%rbx, 1)"); // store in memory
+ emit("subq $8, %%rsi");
+ break;
+ case OP_ARG:
+ emit("subq $4, %%rsi");
+ emit("movl 4(%%rsi), %%eax"); // get value from stack
+ emit("movl $0x%hhx, %%ebx", barg);
+ emit("addl %%edi, %%ebx");
+ RANGECHECK(ebx);
+ emit("movl %%eax, 0(%%r8,%%rbx, 1)"); // store in args space
+ break;
+ case OP_BLOCK_COPY:
+
+ emit("subq $8, %%rsi");
+ emit("push %%rsi");
+ emit("push %%rdi");
+ emit("push %%r8");
+ emit("push %%r9");
+ emit("push %%r10");
+ emit("movl 4(%%rsi), %%edi"); // 1st argument dest
+ emit("movl 8(%%rsi), %%esi"); // 2nd argument src
+ emit("movl $%d, %%edx", iarg); // 3rd argument count
+ emit("movq $%lu, %%rax", (unsigned long)block_copy_vm);
+ emit("callq *%%rax");
+ emit("pop %%r10");
+ emit("pop %%r9");
+ emit("pop %%r8");
+ emit("pop %%rdi");
+ emit("pop %%rsi");
+
+ break;
+ case OP_SEX8:
+ emit("movw 0(%%rsi), %%ax");
+ emit("andq $255, %%rax");
+ emit("cbw");
+ emit("cwde");
+ emit("movl %%eax, 0(%%rsi)");
+ break;
+ case OP_SEX16:
+ emit("movw 0(%%rsi), %%ax");
+ emit("cwde");
+ emit("movl %%eax, 0(%%rsi)");
+ break;
+ case OP_NEGI:
+ emit("negl 0(%%rsi)");
+ break;
+ case OP_ADD:
+ SIMPLE("addl");
+ break;
+ case OP_SUB:
+ SIMPLE("subl");
+ break;
+ case OP_DIVI:
+ emit("subq $4, %%rsi");
+ emit("movl 0(%%rsi), %%eax");
+ emit("cdq");
+ emit("idivl 4(%%rsi)");
+ emit("movl %%eax, 0(%%rsi)");
+ break;
+ case OP_DIVU:
+ emit("subq $4, %%rsi");
+ emit("movl 0(%%rsi), %%eax");
+ emit("xorq %%rdx, %%rdx");
+ emit("divl 4(%%rsi)");
+ emit("movl %%eax, 0(%%rsi)");
+ break;
+ case OP_MODI:
+ emit("subq $4, %%rsi");
+ emit("movl 0(%%rsi), %%eax");
+ emit("xorl %%edx, %%edx");
+ emit("cdq");
+ emit("idivl 4(%%rsi)");
+ emit("movl %%edx, 0(%%rsi)");
+ break;
+ case OP_MODU:
+ emit("subq $4, %%rsi");
+ emit("movl 0(%%rsi), %%eax");
+ emit("xorl %%edx, %%edx");
+ emit("divl 4(%%rsi)");
+ emit("movl %%edx, 0(%%rsi)");
+ break;
+ case OP_MULI:
+ emit("subq $4, %%rsi");
+ emit("movl 0(%%rsi), %%eax");
+ emit("imull 4(%%rsi)");
+ emit("movl %%eax, 0(%%rsi)");
+ break;
+ case OP_MULU:
+ emit("subq $4, %%rsi");
+ emit("movl 0(%%rsi), %%eax");
+ emit("mull 4(%%rsi)");
+ emit("movl %%eax, 0(%%rsi)");
+ break;
+ case OP_BAND:
+ SIMPLE("andl");
+ break;
+ case OP_BOR:
+ SIMPLE("orl");
+ break;
+ case OP_BXOR:
+ SIMPLE("xorl");
+ break;
+ case OP_BCOM:
+ emit("notl 0(%%rsi)");
+ break;
+ case OP_LSH:
+ SHIFT("shl");
+ break;
+ case OP_RSHI:
+ SHIFT("sarl");
+ break;
+ case OP_RSHU:
+ SHIFT("shrl");
+ break;
+ case OP_NEGF:
+#ifdef USE_X87
+ emit("flds 0(%%rsi)");
+ emit("fchs");
+ emit("fstps 0(%%rsi)");
+#else
+ emit("movl $0x80000000, %%eax");
+ emit("xorl %%eax, 0(%%rsi)");
+#endif
+ break;
+ case OP_ADDF:
+ FSIMPLE("fadds");
+ XSIMPLE("addss");
+ break;
+ case OP_SUBF:
+ FSIMPLE("fsubs");
+ XSIMPLE("subss");
+ break;
+ case OP_DIVF:
+ FSIMPLE("fdivs");
+ XSIMPLE("divss");
+ break;
+ case OP_MULF:
+ FSIMPLE("fmuls");
+ XSIMPLE("mulss");
+ break;
+ case OP_CVIF:
+#ifdef USE_X87
+ emit("filds 0(%%rsi)");
+ emit("fstps 0(%%rsi)");
+#else
+ emit("movl 0(%%rsi), %%eax");
+ emit("cvtsi2ss %%eax, %%xmm0");
+ emit("movss %%xmm0, 0(%%rsi)");
+#endif
+ break;
+ case OP_CVFI:
+#ifdef USE_X87
+ emit("flds 0(%%rsi)");
+ emit("fnstcw 4(%%rsi)");
+ emit("movw $0x0F7F, 8(%%rsi)"); // round toward zero
+ emit("fldcw 8(%%rsi)");
+ emit("fistpl 0(%%rsi)");
+ emit("fldcw 4(%%rsi)");
+#else
+ emit("movss 0(%%rsi), %%xmm0");
+ emit("cvttss2si %%xmm0, %%eax");
+ emit("movl %%eax, 0(%%rsi)");
+#endif
+ break;
+ default:
+ NOTIMPL(op);
+ break;
+ }
+ }
+
+
+ emit("setupinstructionpointers:");
+ emit("movq $%lu, %%rax", (unsigned long)vm->instructionPointers);
+ for ( instruction = 0; instruction < header->instructionCount; ++instruction )
+ {
+ emit("movl $i_%08x-start, %d(%%rax)", instruction, instruction*4);
+ }
+ emit("ret");
+
+ emit("debugger:");
+ if(1);
+ {
+ int i = 6;
+ while(i--)
+ {
+ emit("nop");
+ emit("int3");
+ }
+ }
+
+ fflush(fh_s);
+ fclose(fh_s);
+
+ compiledsize = doas(fn_s, fn_o, &compiledcode);
+ if(compiledsize == -1)
+ {
+ vm->compiled = qfalse;
+ goto out;
+ }
+
+ vm->codeBase = compiledcode; // remember to skip ELF header!
+ vm->codeLength = compiledsize;
+
+ vm->destroy = VM_Destroy_Compiled;
+
+ entryPoint = getentrypoint(vm);
+
+// __asm__ __volatile__ ("int3");
+ Com_Printf("computing jump table\n");
+
+ // call code with r8 set to zero to set up instruction pointers
+ __asm__ __volatile__ (
+ " xorq %%r8,%%r8 \r\n" \
+ " movq %0,%%r10 \r\n" \
+ " callq *%%r10 \r\n" \
+ :
+ : "m" (entryPoint)
+ : "%r8", "%r10", "%rax"
+ );
+
+#ifdef DEBUG_VM
+ fflush(qdasmout);
+ fclose(qdasmout);
+#endif
+
+ Com_Printf( "VM file %s compiled to %i bytes of code (%p - %p)\n", vm->name, vm->codeLength, vm->codeBase, vm->codeBase+vm->codeLength );
+
+out:
+ close(fd_o);
+
+#ifndef DEBUG_VM
+ if(!com_developer->integer)
+ {
+ unlink(fn_o);
+ unlink(fn_s);
+ }
+#endif
+}
+
+
+void VM_Destroy_Compiled(vm_t* self)
+{
+ munmap(self->codeBase, self->codeLength);
+}
+
+/*
+==============
+VM_CallCompiled
+
+This function is called directly by the generated code
+==============
+*/
+
+#ifdef DEBUG_VM
+static char* memData;
+#endif
+
+int VM_CallCompiled( vm_t *vm, int *args ) {
+ int programCounter;
+ int programStack;
+ int stackOnEntry;
+ byte *image;
+ void *entryPoint;
+ void *opStack;
+ int stack[1024] = { 0xDEADBEEF };
+
+ currentVM = vm;
+
+ ++vm->callLevel;
+// Com_Printf("entering %s level %d, call %d, arg1 = 0x%x\n", vm->name, vm->callLevel, args[0], args[1]);
+
+ // interpret the code
+ vm->currentlyInterpreting = qtrue;
+
+// callMask = vm->dataMask;
+
+ // we might be called recursively, so this might not be the very top
+ programStack = vm->programStack;
+ stackOnEntry = programStack;
+
+ // set up the stack frame
+ image = vm->dataBase;
+#ifdef DEBUG_VM
+ memData = (char*)image;
+#endif
+
+ programCounter = 0;
+
+ programStack -= 48;
+
+ *(int *)&image[ programStack + 44] = args[9];
+ *(int *)&image[ programStack + 40] = args[8];
+ *(int *)&image[ programStack + 36] = args[7];
+ *(int *)&image[ programStack + 32] = args[6];
+ *(int *)&image[ programStack + 28] = args[5];
+ *(int *)&image[ programStack + 24] = args[4];
+ *(int *)&image[ programStack + 20] = args[3];
+ *(int *)&image[ programStack + 16] = args[2];
+ *(int *)&image[ programStack + 12] = args[1];
+ *(int *)&image[ programStack + 8 ] = args[0];
+ *(int *)&image[ programStack + 4 ] = 0x77777777; // return stack
+ *(int *)&image[ programStack ] = -1; // will terminate the loop on return
+
+ // off we go into generated code...
+ entryPoint = getentrypoint(vm);
+ opStack = &stack;
+
+ __asm__ __volatile__ (
+ " movq %5,%%rsi \r\n" \
+ " movl %4,%%edi \r\n" \
+ " movq %2,%%r10 \r\n" \
+ " movq %3,%%r8 \r\n" \
+ " subq $24, %%rsp # fix alignment as call pushes one value \r\n" \
+ " callq *%%r10 \r\n" \
+ " addq $24, %%rsp \r\n" \
+ " movl %%edi, %0 \r\n" \
+ " movq %%rsi, %1 \r\n" \
+ : "=m" (programStack), "=m" (opStack)
+ : "m" (entryPoint), "m" (vm->dataBase), "m" (programStack), "m" (opStack)
+ : "%rsi", "%rdi", "%rax", "%rbx", "%rcx", "%rdx", "%r8", "%r10", "%r15", "%xmm0"
+ );
+
+ if ( opStack != &stack[1] ) {
+ Com_Error( ERR_DROP, "opStack corrupted in compiled code (offset %ld)\n", (long int) ((void *) &stack[1] - opStack));
+ }
+ if ( programStack != stackOnEntry - 48 ) {
+ Com_Error( ERR_DROP, "programStack corrupted in compiled code\n" );
+ }
+
+// Com_Printf("exiting %s level %d\n", vm->name, vm->callLevel);
+ --vm->callLevel;
+ vm->programStack = stackOnEntry;
+
+ return *(int *)opStack;
+}