summaryrefslogtreecommitdiff
path: root/src/qcommon
diff options
context:
space:
mode:
Diffstat (limited to 'src/qcommon')
-rw-r--r--src/qcommon/CMakeLists.txt54
-rw-r--r--src/qcommon/alternatePlayerstate.h75
-rw-r--r--src/qcommon/cdefs.h79
-rw-r--r--src/qcommon/cm_load.cpp1022
-rw-r--r--src/qcommon/cm_local.h225
-rw-r--r--src/qcommon/cm_patch.cpp1801
-rw-r--r--src/qcommon/cm_patch.h105
-rw-r--r--src/qcommon/cm_polylib.cpp737
-rw-r--r--src/qcommon/cm_polylib.h74
-rw-r--r--src/qcommon/cm_public.h79
-rw-r--r--src/qcommon/cm_test.cpp526
-rw-r--r--src/qcommon/cm_trace.cpp1801
-rw-r--r--src/qcommon/cmd.cpp941
-rw-r--r--src/qcommon/cmd.h115
-rw-r--r--src/qcommon/common.cpp3662
-rw-r--r--src/qcommon/crypto.cpp92
-rw-r--r--src/qcommon/crypto.h45
-rw-r--r--src/qcommon/cvar.cpp1498
-rw-r--r--src/qcommon/cvar.h199
-rw-r--r--src/qcommon/files.cpp3986
-rw-r--r--src/qcommon/files.h286
-rw-r--r--src/qcommon/huffman.cpp558
-rw-r--r--src/qcommon/huffman.h59
-rw-r--r--src/qcommon/ioapi.cpp373
-rw-r--r--src/qcommon/ioapi.h173
-rw-r--r--src/qcommon/json.h353
-rw-r--r--src/qcommon/md4.cpp202
-rw-r--r--src/qcommon/md4.h6
-rw-r--r--src/qcommon/md5.cpp312
-rw-r--r--src/qcommon/msg.cpp2248
-rw-r--r--src/qcommon/msg.h79
-rw-r--r--src/qcommon/net.h145
-rw-r--r--src/qcommon/net_chan.cpp697
-rw-r--r--src/qcommon/net_ip.cpp1842
-rw-r--r--src/qcommon/parse.cpp3725
-rw-r--r--src/qcommon/puff.cpp759
-rw-r--r--src/qcommon/puff.h43
-rw-r--r--src/qcommon/q3_lauxlib.cpp46
-rw-r--r--src/qcommon/q3_lauxlib.h46
-rw-r--r--src/qcommon/q_math.c610
-rw-r--r--src/qcommon/q_platform.h112
-rw-r--r--src/qcommon/q_shared.c634
-rw-r--r--src/qcommon/q_shared.h761
-rw-r--r--src/qcommon/qcommon.h413
-rw-r--r--src/qcommon/qfiles.h634
-rw-r--r--src/qcommon/surfaceflags.h21
-rw-r--r--src/qcommon/unzip.cpp1951
-rw-r--r--src/qcommon/unzip.h321
-rw-r--r--src/qcommon/vm.cpp1020
-rw-r--r--src/qcommon/vm.h67
-rw-r--r--src/qcommon/vm_interpreted.cpp904
-rw-r--r--src/qcommon/vm_local.h204
-rw-r--r--src/qcommon/vm_x86.cpp1840
53 files changed, 36996 insertions, 1564 deletions
diff --git a/src/qcommon/CMakeLists.txt b/src/qcommon/CMakeLists.txt
new file mode 100644
index 0000000..493dfa4
--- /dev/null
+++ b/src/qcommon/CMakeLists.txt
@@ -0,0 +1,54 @@
+add_library (
+ common STATIC
+ cm_load.c
+ cm_local.h
+ cm_patch.c
+ cm_patch.h
+ cm_polylib.c
+ cm_polylib.h
+ cm_public.h
+ cm_test.c
+ cm_trace.c
+ cmd.cpp
+ common.c
+ cvar.cpp
+ dialog.h
+ files.c
+ huffman.c
+ ioapi.c
+ ioapi.h
+ json.h
+ md4.c
+ md5.c
+ msg.c
+ net_chan.c
+ net_ip.c
+ parse.c
+ puff.c
+ puff.h
+ q3_lauxlib.cpp
+ q3_lauxlib.h
+ q_math.c
+ q_platform.h
+ q_shared.c
+ q_shared.h
+ qcommon.h
+ qfiles.h
+ surfaceflags.h
+ unzip.c
+ unzip.h
+ vm.c
+ vm_interpreted.c
+ vm_local.h
+ vm_none.c
+ vm_powerpc.c
+ vm_powerpc_asm.c
+ vm_powerpc_asm.h
+ vm_sparc.c
+ vm_sparc.h
+ vm_x86.c
+)
+
+include_directories( ${RESTCLIENT_INCLUDES_DIR} )
+
+set ( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14" )
diff --git a/src/qcommon/alternatePlayerstate.h b/src/qcommon/alternatePlayerstate.h
new file mode 100644
index 0000000..a35778b
--- /dev/null
+++ b/src/qcommon/alternatePlayerstate.h
@@ -0,0 +1,75 @@
+#ifndef QCOMMON_ALTERNATEPLAYERSTATE_H
+#define QCOMMON_ALTERNATEPLAYERSTATE_H 1
+
+#include "q_shared.h"
+
+struct alternatePlayerState_t {
+ 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 relative angle
+ // of movement to the view angle (axial and diagonals)
+ // when at rest, the value will remain unchanged
+ // used to twist the legs during strafing
+
+ vec3_t grapplePoint; // location of grapple to pull towards if PMF_GRAPPLE_PULL
+
+ int eFlags; // copied to entityState_t->eFlags
+
+ int eventSequence; // pmove generated events
+ int events[MAX_PS_EVENTS];
+ int eventParms[MAX_PS_EVENTS];
+
+ int externalEvent; // events set on player from another source
+ int externalEventParm;
+ int externalEventTime;
+
+ int clientNum; // ranges from 0 to MAX_CLIENTS-1
+ int weapon; // copied to entityState_t->weapon
+ int weaponstate;
+
+ vec3_t viewangles; // for fixed views
+ int viewheight;
+
+ // damage feedback
+ int damageEvent; // when it changes, latch the other parms
+ int damageYaw;
+ int damagePitch;
+ int damageCount;
+
+ int stats[MAX_STATS];
+ int persistant[MAX_PERSISTANT]; // stats that aren't cleared on death
+ int misc[MAX_MISC]; // misc data
+ int ammo[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;
+ int jumppad_frame;
+ int entityEventSequence;
+};
+
+#endif
diff --git a/src/qcommon/cdefs.h b/src/qcommon/cdefs.h
new file mode 100644
index 0000000..327c244
--- /dev/null
+++ b/src/qcommon/cdefs.h
@@ -0,0 +1,79 @@
+/* Copyright (c) 2010-2012, Victor J. Roemer. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef __CDEFS_H__
+#define __CDEFS_H__
+
+#define UNUSED __attribute__((unused))
+
+#define NORETURN __attribute__((noreturn))
+
+/* Support for flexible arrays, stolen from dnet */
+#undef __flexarr
+#if defined(__GNUC__) && ((__GNUC__ > 2) || \
+ (__GNUC__ == 2 && __GNUC_MINOR__ >= 97))
+
+/* GCC 2.97 supports C99 flexible array members. */
+# define __flexarr []
+#else
+# ifdef __GNUC__
+# define __flexarr [0]
+# else
+# if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+# define __flexarr []
+# elif defined(_WIN32)
+/* MS VC++ */
+# define __flexarr []
+# else
+/* Some other non-C99 compiler. Approximate with [1]. */
+# define __flexarr [1]
+# endif
+# endif
+#endif
+
+#ifndef SO_PUBLIC
+#if defined _WIN32 || defined __CYGWIN__
+# ifdef __GNUC__
+# define SO_PUBLIC __attribute__((dllimport))
+# else
+# define SO_PUBLIC __declspec(dllimport)
+# endif
+# define DLL_LOCAL
+#else
+# ifdef HAVE_VISIBILITY
+# define SO_PUBLIC __attribute__ ((visibility("default")))
+# define SO_PRIVATE __attribute__ ((visibility("hidden")))
+# else
+# define SO_PUBLIC
+# define SO_PRIVATE
+# endif
+#endif
+#endif
+
+#endif
diff --git a/src/qcommon/cm_load.cpp b/src/qcommon/cm_load.cpp
new file mode 100644
index 0000000..0ce0606
--- /dev/null
+++ b/src/qcommon/cm_load.cpp
@@ -0,0 +1,1022 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// cmodel.c -- model loading
+
+#include "cm_local.h"
+#include "files.h"
+#include "md4.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);
+
+
+/*
+===============================================================================
+
+ MAP LOADING
+
+===============================================================================
+*/
+
+/*
+=================
+CMod_LoadShaders
+=================
+*/
+void CMod_LoadShaders( lump_t *l ) {
+ dshader_t *in, *out;
+ int i, count;
+
+ in = (dshader_t *)(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 = (dshader_t*)Hunk_Alloc( count * sizeof( *cm.shaders ), h_high );
+ cm.numShaders = count;
+
+ ::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 = (dmodel_t *)(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 = (cmodel_t*)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 = &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 = (int*)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 = (int*)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 count;
+
+ in = (dnode_t *)(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 = (cNode_t*)Hunk_Alloc( count * sizeof( *cm.nodes ), h_high );
+ cm.numNodes = count;
+
+ out = cm.nodes;
+
+ for (int i=0 ; i<count ; i++, out++, in++)
+ {
+ out->plane = cm.planes + LittleLong( in->planeNum );
+ for (int 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 count;
+
+ in = (dbrush_t *)(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 = (cbrush_t*)Hunk_Alloc( ( BOX_BRUSHES + count ) * sizeof( *cm.brushes ), h_high );
+ cm.numBrushes = count;
+
+ out = cm.brushes;
+
+ for ( int 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)
+{
+ cLeaf_t *out;
+ dleaf_t *in;
+ int count;
+
+ in = (dleaf_t *)(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 = (cLeaf_t*)Hunk_Alloc( ( BOX_LEAFS + count ) * sizeof( *cm.leafs ), h_high );
+ cm.numLeafs = count;
+
+ out = cm.leafs;
+ for ( int 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 = (cArea_t*)Hunk_Alloc( cm.numAreas * sizeof( *cm.areas ), h_high );
+ cm.areaPortals = (int*)Hunk_Alloc( cm.numAreas * cm.numAreas * sizeof( *cm.areaPortals ), h_high );
+}
+
+/*
+=================
+CMod_LoadPlanes
+=================
+*/
+void CMod_LoadPlanes (lump_t *l)
+{
+ cplane_t *out;
+ dplane_t *in;
+ int count;
+ int bits;
+
+ in = (dplane_t*)(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 = (cplane_t*)Hunk_Alloc( ( BOX_PLANES + count ) * sizeof( *cm.planes ), h_high );
+ cm.numPlanes = count;
+
+ out = cm.planes;
+
+ for ( int i = 0 ; i<count ; i++, in++, out++)
+ {
+ bits = 0;
+ for (int 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 *out;
+ int *in;
+ int count;
+
+ in = (int*)(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 = (int*)Hunk_Alloc( (count + BOX_BRUSHES) * sizeof( *cm.leafbrushes ), h_high );
+ cm.numLeafBrushes = count;
+
+ out = cm.leafbrushes;
+
+ for ( int i=0 ; i<count ; i++, in++, out++)
+ {
+ *out = LittleLong (*in);
+ }
+}
+
+/*
+=================
+CMod_LoadLeafSurfaces
+=================
+*/
+void CMod_LoadLeafSurfaces( lump_t *l )
+{
+ int *out;
+ int *in;
+ int count;
+
+ in = (int*)(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 = (int*)Hunk_Alloc( count * sizeof( *cm.leafsurfaces ), h_high );
+ cm.numLeafSurfaces = count;
+
+ out = cm.leafsurfaces;
+
+ for ( int 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 = (dbrushside_t *)(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 = (cbrushside_t*)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 bool 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 true;
+
+ if( VectorCompareEpsilon( p1, q0, CM_EDGE_VERTEX_EPSILON ) &&
+ VectorCompareEpsilon( p0, q1, CM_EDGE_VERTEX_EPSILON ) )
+ return true;
+
+ return false;
+}
+
+/*
+=================
+CMod_AddEdgeToBrush
+=================
+*/
+static bool CMod_AddEdgeToBrush( const vec3_t p0, const vec3_t p1,
+ cbrushedge_t *edges, int *numEdges )
+{
+ int i;
+
+ if( !edges || !numEdges )
+ return false;
+
+ for( i = 0; i < *numEdges; i++ )
+ {
+ if( CMod_BrushEdgesAreTheSame( p0, p1,
+ edges[ i ].p0, edges[ i ].p1 ) )
+ return false;
+ }
+
+ VectorCopy( p0, edges[ *numEdges ].p0 );
+ VectorCopy( p1, edges[ *numEdges ].p1 );
+ (*numEdges)++;
+
+ return true;
+}
+
+/*
+=================
+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
+ ::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 = (char*)Hunk_Alloc( l->filelen, h_high );
+ cm.numEntityChars = l->filelen;
+ ::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 = (byte*)Hunk_Alloc( cm.clusterBytes, h_high );
+ ::memset( cm.visibility, 255, cm.clusterBytes );
+ return;
+ }
+ buf = cmod_base + l->fileofs;
+
+ cm.vised = true;
+ cm.visibility = (byte*)Hunk_Alloc( len, h_high );
+ cm.numClusters = LittleLong( ((int *)buf)[0] );
+ cm.clusterBytes = LittleLong( ((int *)buf)[1] );
+ ::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 = (dsurface_t *)(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 = (cPatch_t**)Hunk_Alloc( cm.numSurfaces * sizeof( cm.surfaces[0] ), h_high );
+
+ dv = (drawVert_t *)(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 = (cPatch_t*)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, bool clientload, int *checksum ) {
+ union {
+ int *i;
+ void *v;
+ } buf;
+ 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
+ ::memset( &cm, 0, sizeof( cm ) );
+ CM_ClearLevelPatches();
+
+ if ( !name[0] ) {
+ cm.numLeafs = 1;
+ cm.numClusters = 1;
+ cm.numAreas = 1;
+ cm.cmodels = (cmodel_t*)Hunk_Alloc( sizeof( *cm.cmodels ), h_high );
+ *checksum = 0;
+ return;
+ }
+
+ //
+ // load the file
+ //
+#ifndef BSPC
+ length = FS_ReadFile( name, &buf.v );
+#else
+ length = LoadQuakeFile((quakefile_t *) name, &buf.v);
+#endif
+
+ if ( !buf.i ) {
+ Com_Error (ERR_DROP, "Couldn't load %s", name);
+ }
+
+ last_checksum = LittleLong (Com_BlockChecksum (buf.i, length));
+ *checksum = last_checksum;
+
+ header = *(dheader_t *)buf.i;
+ for (size_t i = 0 ; i<sizeof(dheader_t)/4 ; i++) {
+ ((int *)&header)[i] = LittleLong ( ((int *)&header)[i]);
+ }
+
+ if ( header.version != BSP_VERSION ) {
+ Com_Error (ERR_DROP, "CM_LoadMap: %s has wrong version number (%i should be %i)"
+ , name, header.version, BSP_VERSION );
+ }
+
+ cmod_base = (byte *)buf.i;
+
+ // load into heap
+ CMod_LoadShaders( &header.lumps[LUMP_SHADERS] );
+ CMod_LoadLeafs (&header.lumps[LUMP_LEAFS]);
+ CMod_LoadLeafBrushes (&header.lumps[LUMP_LEAFBRUSHES]);
+ CMod_LoadLeafSurfaces (&header.lumps[LUMP_LEAFSURFACES]);
+ CMod_LoadPlanes (&header.lumps[LUMP_PLANES]);
+ CMod_LoadBrushSides (&header.lumps[LUMP_BRUSHSIDES]);
+ CMod_LoadBrushes (&header.lumps[LUMP_BRUSHES]);
+ CMod_LoadSubmodels (&header.lumps[LUMP_MODELS]);
+ CMod_LoadNodes (&header.lumps[LUMP_NODES]);
+ CMod_LoadEntityString (&header.lumps[LUMP_ENTITIES]);
+ CMod_LoadVisibility( &header.lumps[LUMP_VISIBILITY] );
+ CMod_LoadPatches( &header.lumps[LUMP_SURFACES], &header.lumps[LUMP_DRAWVERTS] );
+
+ CMod_CreateBrushSideWindings( );
+
+ // we are NOT freeing the file, because it is cached for the ref
+ FS_FreeFile (buf.v);
+
+ CM_InitBoxHull ();
+
+ CM_FloodAreaConnections ();
+
+ // allow this to be cached if it is loaded by the server
+ if ( !clientload ) {
+ Q_strncpyz( cm.name, name, sizeof( cm.name ) );
+ }
+}
+
+/*
+==================
+CM_ClearMap
+==================
+*/
+void CM_ClearMap( void ) {
+ ::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..54e62eb
--- /dev/null
+++ b/src/qcommon/cm_local.h
@@ -0,0 +1,225 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#ifndef CM_LOCAL_H
+#define CM_LOCAL_H 1
+
+#include "cvar.h"
+#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
+ bool 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;
+ bool 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
+ bool isPoint; // optimized case
+ trace_t trace; // returned from trace call
+ sphere_t sphere; // sphere for oriendted capsule collision
+ biSphere_t biSphere;
+ bool testLateralCollision; // whether or not to test for lateral collision
+} traceWork_t;
+
+typedef struct leafList_s {
+ int count;
+ int maxcount;
+ bool 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 );
+bool CM_BoundsIntersect( const vec3_t mins, const vec3_t maxs, const vec3_t mins2, const vec3_t maxs2 );
+bool CM_BoundsIntersectPoint( const vec3_t mins, const vec3_t maxs, const vec3_t point );
+
+// cm_patch.c
+
+struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points );
+void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc );
+bool CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc );
+void CM_ClearLevelPatches( void );
+
+// cm_test.c
+void CM_FloodAreaConnections (void);
+
+#endif
diff --git a/src/qcommon/cm_patch.cpp b/src/qcommon/cm_patch.cpp
new file mode 100644
index 0000000..274d7c6
--- /dev/null
+++ b/src/qcommon/cm_patch.cpp
@@ -0,0 +1,1801 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#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 );
+bool 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];
+ bool 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;
+ bool wrapWidth;
+ bool 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 bool 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 bool 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 false;
+ }
+
+ plane[3] = DotProduct( a, plane );
+ return true;
+}
+
+
+/*
+================================================================================
+
+GRID SUBDIVISION
+
+================================================================================
+*/
+
+/*
+=================
+CM_NeedsSubdivision
+
+Returns true if the given quadratic curve is not flat enough for our
+collision detection purposes
+=================
+*/
+static bool 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 (bool)(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;
+ bool 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 true
+===================
+*/
+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 = true;
+ } else {
+ grid->wrapWidth = false;
+ }
+}
+
+/*
+=================
+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 bool CM_ComparePoints( float *a, float *b ) {
+ float d;
+
+ d = a[0] - b[0];
+ if ( d < -POINT_EPSILON || d > POINT_EPSILON ) {
+ return false;
+ }
+ d = a[1] - b[1];
+ if ( d < -POINT_EPSILON || d > POINT_EPSILON ) {
+ return false;
+ }
+ d = a[2] - b[2];
+ if ( d < -POINT_EPSILON || d > POINT_EPSILON ) {
+ return false;
+ }
+ return true;
+}
+
+/*
+=================
+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_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 = false;
+ return true;
+ }
+
+ 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 = true;
+ return true;
+ }
+
+ return false;
+}
+
+/*
+==================
+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 = false;
+
+ 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 );
+ if ( p == -1 ) {
+ return -1;
+ }
+ 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 );
+ if ( p == -1 ) {
+ return -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 );
+ if ( p == -1 ) {
+ return -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 );
+ if ( p == -1 ) {
+ return -1;
+ }
+ 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 );
+ if ( p == -1 ) {
+ return -1;
+ }
+ 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 );
+ if ( p == -1 ) {
+ return -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] = true;
+ } else if ( back && !front ) {
+ facet->borderInward[k] = false;
+ } 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] = false;
+ if ( !debugBlock ) {
+ debugBlock = true;
+ 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 bool CM_ValidateFacet( facet_t *facet ) {
+ float plane[4];
+ int j;
+ winding_t *w;
+ vec3_t bounds[2];
+
+ if ( facet->surfacePlane == -1 ) {
+ return false;
+ }
+
+ 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 false;
+ }
+ 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 false; // 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 false; // we must be missing a plane
+ }
+ if ( bounds[0][j] >= MAX_MAP_BOUNDS ) {
+ return false;
+ }
+ if ( bounds[1][j] <= -MAX_MAP_BOUNDS ) {
+ return false;
+ }
+ }
+ return true; // winding is fine
+}
+
+/*
+==================
+CM_AddFacetBevels
+==================
+*/
+void CM_AddFacetBevels( facet_t *facet ) {
+
+ int i, j, k, l;
+ int axis, dir, 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
+ for ( axis = 0 ; axis < 3 ; axis++ )
+ {
+ for ( dir = -1 ; dir <= 1 ; dir += 2 )
+ {
+ 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" );
+ continue;
+ }
+ facet->borderPlanes[facet->numBorders] = CM_FindPlane2(plane, &flipped);
+ facet->borderNoAdjust[facet->numBorders] = false;
+ 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" );
+ continue;
+ }
+ 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] = false;
+ 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
+ if ( facet->numBorders >= 4 + 6 + 16 ) {
+ Com_Printf( "ERROR: too many bevels\n" );
+ return;
+ }
+ facet->borderPlanes[facet->numBorders] = facet->surfacePlane;
+ facet->borderNoAdjust[facet->numBorders] = false;
+ facet->borderInward[facet->numBorders] = true;
+ 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];
+ ::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] = (bool)noAdjust[EN_TOP];
+ facet->borderPlanes[1] = borders[EN_RIGHT];
+ facet->borderNoAdjust[1] = (bool)noAdjust[EN_RIGHT];
+ facet->borderPlanes[2] = borders[EN_BOTTOM];
+ facet->borderNoAdjust[2] = (bool)noAdjust[EN_BOTTOM];
+ facet->borderPlanes[3] = borders[EN_LEFT];
+ facet->borderNoAdjust[3] = (bool)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] = (bool)noAdjust[EN_TOP];
+ facet->borderPlanes[1] = borders[EN_RIGHT];
+ facet->borderNoAdjust[1] = (bool)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];
+ ::memset( facet, 0, sizeof( *facet ) );
+
+ facet->surfacePlane = gridPlanes[i][j][1];
+ facet->numBorders = 3;
+ facet->borderPlanes[0] = borders[EN_BOTTOM];
+ facet->borderNoAdjust[0] = (bool)noAdjust[EN_BOTTOM];
+ facet->borderPlanes[1] = borders[EN_LEFT];
+ facet->borderNoAdjust[1] = (bool)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 = (facet_t*)Hunk_Alloc( numFacets * sizeof( *pf->facets ), h_high );
+ ::memcpy( pf->facets, facets, numFacets * sizeof( *pf->facets ) );
+ pf->planes = (patchPlane_t*)Hunk_Alloc( numPlanes * sizeof( *pf->planes ), h_high );
+ ::memcpy( pf->planes, planes, numPlanes * sizeof( *pf->planes ) );
+}
+
+
+/*
+===================
+CM_GeneratePatchCollide
+
+Creates an internal structure that will be used to perform
+collision detection with a patch mesh.
+
+Points is packed as concatenated rows.
+===================
+*/
+struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points ) {
+ patchCollide_t *pf;
+ cGrid_t grid;
+ int i, j;
+
+ if ( width <= 2 || height <= 2 || !points ) {
+ Com_Error( ERR_DROP, "CM_GeneratePatchFacets: bad parameters: (%i, %i, %p)",
+ width, height, (void *)points );
+ }
+
+ if ( !(width & 1) || !(height & 1) ) {
+ Com_Error( ERR_DROP, "CM_GeneratePatchFacets: even sizes are invalid for quadratic meshes" );
+ }
+
+ if ( width > MAX_GRID_SIZE || height > MAX_GRID_SIZE ) {
+ Com_Error( ERR_DROP, "CM_GeneratePatchFacets: source is > MAX_GRID_SIZE" );
+ }
+
+ // build a grid
+ grid.width = width;
+ grid.height = height;
+ grid.wrapWidth = false;
+ grid.wrapHeight = false;
+ 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 = (patchCollide_t*)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 ) {
+ bool 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] = false;
+ } else {
+ frontFacing[i] = true;
+ }
+ 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 = false;
+
+ 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 false;
+ }
+
+ // if it doesn't cross the plane, the plane isn't relevent
+ if (d1 <= 0 && d2 <= 0 ) {
+ return true;
+ }
+
+ // 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 = true;
+ }
+ } else { // leave
+ f = (d1+SURFACE_CLIP_EPSILON) / (d1-d2);
+ if ( f > 1 ) {
+ f = 1;
+ }
+ if (f < *leaveFrac) {
+ *leaveFrac = f;
+ }
+ }
+ return true;
+}
+
+/*
+====================
+CM_TraceThroughPatchCollide
+====================
+*/
+void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) {
+ int i, j, hit, hitnum;
+ float offset, enterFrac, leaveFrac, t;
+ patchPlane_t *planes;
+ facet_t *facet;
+ float plane[4] = {0, 0, 0, 0}, bestplane[4] = {0, 0, 0, 0};
+ vec3_t startp, endp;
+#ifndef BSPC
+ static cvar_t *cv;
+#endif //BSPC
+
+ if ( !CM_BoundsIntersect( tw->bounds[0], tw->bounds[1],
+ pc->bounds[0], pc->bounds[1] ) ) {
+ return;
+ }
+
+ if (tw->isPoint) {
+ CM_TracePointThroughPatchCollide( tw, pc );
+ return;
+ }
+
+ facet = pc->facets;
+ for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) {
+ enterFrac = -1.0;
+ leaveFrac = 1.0;
+ hitnum = -1;
+ //
+ planes = &pc->planes[ facet->surfacePlane ];
+ VectorCopy(planes->plane, plane);
+ plane[3] = planes->plane[3];
+ if ( tw->type == TT_CAPSULE ) {
+ // adjust the plane distance apropriately for radius
+ plane[3] += tw->sphere.radius;
+
+ // find the closest point on the capsule to the plane
+ t = DotProduct( plane, tw->sphere.offset );
+ if ( t > 0.0f ) {
+ VectorSubtract( tw->start, tw->sphere.offset, startp );
+ VectorSubtract( tw->end, tw->sphere.offset, endp );
+ }
+ else {
+ VectorAdd( tw->start, tw->sphere.offset, startp );
+ VectorAdd( tw->end, tw->sphere.offset, endp );
+ }
+ }
+ else {
+ offset = DotProduct( tw->offsets[ planes->signbits ], plane);
+ plane[3] -= offset;
+ VectorCopy( tw->start, startp );
+ VectorCopy( tw->end, endp );
+ }
+
+ if (!CM_CheckFacetPlane(plane, startp, endp, &enterFrac, &leaveFrac, &hit)) {
+ continue;
+ }
+ if (hit) {
+ Vector4Copy(plane, bestplane);
+ }
+
+ for ( j = 0; j < facet->numBorders; j++ ) {
+ planes = &pc->planes[ facet->borderPlanes[j] ];
+ if (facet->borderInward[j]) {
+ VectorNegate(planes->plane, plane);
+ plane[3] = -planes->plane[3];
+ }
+ else {
+ VectorCopy(planes->plane, plane);
+ plane[3] = planes->plane[3];
+ }
+ if ( tw->type == TT_CAPSULE ) {
+ // adjust the plane distance apropriately for radius
+ plane[3] += tw->sphere.radius;
+
+ // find the closest point on the capsule to the plane
+ t = DotProduct( plane, tw->sphere.offset );
+ if ( t > 0.0f ) {
+ VectorSubtract( tw->start, tw->sphere.offset, startp );
+ VectorSubtract( tw->end, tw->sphere.offset, endp );
+ }
+ else {
+ VectorAdd( tw->start, tw->sphere.offset, startp );
+ VectorAdd( tw->end, tw->sphere.offset, endp );
+ }
+ }
+ else {
+ // NOTE: this works even though the plane might be flipped because the bbox is centered
+ offset = DotProduct( tw->offsets[ planes->signbits ], plane);
+ plane[3] += fabs(offset);
+ VectorCopy( tw->start, startp );
+ VectorCopy( tw->end, endp );
+ }
+
+ if (!CM_CheckFacetPlane(plane, startp, endp, &enterFrac, &leaveFrac, &hit)) {
+ break;
+ }
+ if (hit) {
+ hitnum = j;
+ Vector4Copy(plane, bestplane);
+ }
+ }
+ if (j < facet->numBorders) continue;
+ //never clip against the back side
+ if (hitnum == facet->numBorders - 1) continue;
+
+ if (enterFrac < leaveFrac && enterFrac >= 0) {
+ if (enterFrac < tw->trace.fraction) {
+ if (enterFrac < 0) {
+ enterFrac = 0;
+ }
+#ifndef BSPC
+ if (!cv) {
+ cv = Cvar_Get( "r_debugSurfaceUpdate", "1", 0 );
+ }
+ if (cv && cv->integer) {
+ debugPatchCollide = pc;
+ debugFacet = facet;
+ }
+#endif //BSPC
+
+ tw->trace.fraction = enterFrac;
+ VectorCopy( bestplane, tw->trace.plane.normal );
+ tw->trace.plane.dist = bestplane[3];
+ }
+ }
+ }
+}
+
+
+/*
+=======================================================================
+
+POSITION TEST
+
+=======================================================================
+*/
+
+/*
+====================
+CM_PositionTestInPatchCollide
+====================
+*/
+bool 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 false;
+ }
+ //
+ 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 true;
+ }
+ return false;
+}
+
+/*
+=======================================================================
+
+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 = false;
+ //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 = false;
+ //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..50e10af
--- /dev/null
+++ b/src/qcommon/cm_patch.h
@@ -0,0 +1,105 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+//#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 );
+bool 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];
+ bool 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;
+ bool wrapWidth;
+ bool 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.cpp b/src/qcommon/cm_polylib.cpp
new file mode 100644
index 0000000..ca1c94c
--- /dev/null
+++ b/src/qcommon/cm_polylib.cpp
@@ -0,0 +1,737 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+// 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 = (winding_t*)Z_Malloc (s);
+ ::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;
+ ::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)
+{
+ intptr_t size;
+ winding_t *c;
+
+ c = AllocWinding (w->numpoints);
+ size = (intptr_t) ((winding_t *)0)->p[w->numpoints];
+ ::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] = { 0 };
+ int sides[MAX_POINTS_ON_WINDING+4] = { 0 };
+ 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] = { 0 };
+ int sides[MAX_POINTS_ON_WINDING+4] = { 0 };
+ 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)
+{
+ bool front, back;
+ int i;
+ vec_t d;
+
+ front = false;
+ back = false;
+ for (i=0 ; i<w->numpoints ; i++)
+ {
+ d = DotProduct (w->p[i], normal) - dist;
+ if (d < -ON_EPSILON)
+ {
+ if (front)
+ return SIDE_CROSS;
+ back = true;
+ continue;
+ }
+ if (d > ON_EPSILON)
+ {
+ if (back)
+ return SIDE_CROSS;
+ front = true;
+ 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];
+ bool hullSide[MAX_HULL_POINTS];
+ bool outside;
+
+ if ( !*hull ) {
+ *hull = CopyWinding( w );
+ return;
+ }
+
+ numHullPoints = (*hull)->numpoints;
+ ::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 = false;
+ for ( j = 0 ; j < numHullPoints ; j++ ) {
+ VectorSubtract( p, hullPoints[j], dir );
+ d = DotProduct( dir, hullDirs[j] );
+ if ( d >= ON_EPSILON ) {
+ outside = true;
+ }
+ if ( d >= -ON_EPSILON ) {
+ hullSide[j] = true;
+ } else {
+ hullSide[j] = false;
+ }
+ }
+
+ // 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;
+ ::memcpy( hullPoints, newHullPoints, numHullPoints * sizeof(vec3_t) );
+ }
+
+ FreeWinding( *hull );
+ w = AllocWinding( numHullPoints );
+ w->numpoints = numHullPoints;
+ *hull = w;
+ ::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..0a04bd6
--- /dev/null
+++ b/src/qcommon/cm_polylib.h
@@ -0,0 +1,74 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+// this is only used for visualization tools in cm_ debug functions
+
+#ifndef CM_POLYLIB_H
+#define CM_POLYLIB_H 1
+
+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);
+
+#endif
diff --git a/src/qcommon/cm_public.h b/src/qcommon/cm_public.h
new file mode 100644
index 0000000..5b3a46f
--- /dev/null
+++ b/src/qcommon/cm_public.h
@@ -0,0 +1,79 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+#ifndef _CM_PUBLIC_H_
+#define _CM_PUBLIC_H_
+
+#include "qfiles.h"
+
+void CM_LoadMap( const char *name, bool 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, bool open );
+bool CM_AreasConnected( int area1, int area2 );
+
+int CM_WriteAreaBits( byte *buffer, int area );
+
+// cm_patch.c
+void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, float *points) );
+
+#endif
diff --git a/src/qcommon/cm_test.cpp b/src/qcommon/cm_test.cpp
new file mode 100644
index 0000000..b7f8b8f
--- /dev/null
+++ b/src/qcommon/cm_test.cpp
@@ -0,0 +1,526 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#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 = true;
+ 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 = true;
+ 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 = false;
+
+ 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 = (int *)list;
+ ll.storeLeafs = CM_StoreBrushes;
+ ll.lastLeaf = 0;
+ ll.overflowed = false;
+
+ CM_BoxLeafnums_r( &ll, 0 );
+
+ return ll.count;
+}
+
+
+//====================================================================
+
+
+/*
+==================
+CM_PointContents
+
+==================
+*/
+int CM_PointContents( const vec3_t p, clipHandle_t model ) {
+ int leafnum;
+ int i, k;
+ int brushnum;
+ cLeaf_t *leaf;
+ cbrush_t *b;
+ int contents;
+ float d;
+ cmodel_t *clipm;
+
+ if (!cm.numNodes) { // map not loaded
+ return 0;
+ }
+
+ if ( model ) {
+ clipm = CM_ClipHandleToModel( model );
+ leaf = &clipm->leaf;
+ } else {
+ leafnum = CM_PointLeafnum_r (p, 0);
+ leaf = &cm.leafs[leafnum];
+ }
+
+ if(leaf->area == -1)
+ return CONTENTS_SOLID;
+
+ contents = 0;
+ for (k=0 ; k<leaf->numLeafBrushes ; k++) {
+ brushnum = cm.leafbrushes[leaf->firstLeafBrush+k];
+ b = &cm.brushes[brushnum];
+
+ if ( !CM_BoundsIntersectPoint( b->bounds[0], b->bounds[1], p ) ) {
+ continue;
+ }
+
+ // see if the point is in the brush
+ for ( i = 0 ; i < b->numsides ; i++ ) {
+ d = DotProduct( p, b->sides[i].plane->normal );
+// FIXME test for Cash
+// if ( d >= b->sides[i].plane->dist ) {
+ if ( d > b->sides[i].plane->dist ) {
+ break;
+ }
+ }
+
+ if ( i == b->numsides ) {
+ contents |= b->contents;
+ }
+ }
+
+ return contents;
+}
+
+/*
+==================
+CM_TransformedPointContents
+
+Handles offseting and rotation of the end points for moving and
+rotating entities
+==================
+*/
+int CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles) {
+ vec3_t p_l;
+ vec3_t temp;
+ vec3_t forward, right, up;
+
+ // subtract origin offset
+ VectorSubtract (p, origin, p_l);
+
+ // rotate start and end into the models frame of reference
+ if ( model != BOX_MODEL_HANDLE &&
+ (angles[0] || angles[1] || angles[2]) )
+ {
+ AngleVectors (angles, forward, right, up);
+
+ VectorCopy (p_l, temp);
+ p_l[0] = DotProduct (temp, forward);
+ p_l[1] = -DotProduct (temp, right);
+ p_l[2] = DotProduct (temp, up);
+ }
+
+ return CM_PointContents( p_l, model );
+}
+
+
+
+/*
+===============================================================================
+
+PVS
+
+===============================================================================
+*/
+
+byte *CM_ClusterPVS (int cluster) {
+ if (cluster < 0 || cluster >= cm.numClusters || !cm.vised ) {
+ return cm.visibility;
+ }
+
+ return cm.visibility + cluster * cm.clusterBytes;
+}
+
+
+
+/*
+===============================================================================
+
+AREAPORTALS
+
+===============================================================================
+*/
+
+void CM_FloodArea_r( int areaNum, int floodnum) {
+ int i;
+ cArea_t *area;
+ int *con;
+
+ area = &cm.areas[ areaNum ];
+
+ if ( area->floodvalid == cm.floodvalid ) {
+ if (area->floodnum == floodnum)
+ return;
+ Com_Error (ERR_DROP, "FloodArea_r: reflooded");
+ }
+
+ area->floodnum = floodnum;
+ area->floodvalid = cm.floodvalid;
+ con = cm.areaPortals + areaNum * cm.numAreas;
+ for ( i=0 ; i < cm.numAreas ; i++ ) {
+ if ( con[i] > 0 ) {
+ CM_FloodArea_r( i, floodnum );
+ }
+ }
+}
+
+/*
+====================
+CM_FloodAreaConnections
+
+====================
+*/
+void CM_FloodAreaConnections( void ) {
+ int i;
+ cArea_t *area;
+ int floodnum;
+
+ // all current floods are now invalid
+ cm.floodvalid++;
+ floodnum = 0;
+
+ for (i = 0 ; i < cm.numAreas ; i++) {
+ area = &cm.areas[i];
+ if (area->floodvalid == cm.floodvalid) {
+ continue; // already flooded into
+ }
+ floodnum++;
+ CM_FloodArea_r (i, floodnum);
+ }
+
+}
+
+/*
+====================
+CM_AdjustAreaPortalState
+
+====================
+*/
+void CM_AdjustAreaPortalState( int area1, int area2, bool 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
+
+====================
+*/
+bool CM_AreasConnected( int area1, int area2 ) {
+#ifndef BSPC
+ if ( cm_noAreas->integer ) {
+ return true;
+ }
+#endif
+
+ if ( area1 < 0 || area2 < 0 ) {
+ return false;
+ }
+
+ if (area1 >= cm.numAreas || area2 >= cm.numAreas) {
+ Com_Error (ERR_DROP, "area >= cm.numAreas");
+ }
+
+ if (cm.areas[area1].floodnum == cm.areas[area2].floodnum) {
+ return true;
+ }
+ return false;
+}
+
+
+/*
+=================
+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
+ ::memset (buffer, 255, bytes);
+ }
+ else
+ {
+ floodnum = cm.areas[area].floodnum;
+ for (i=0 ; i<cm.numAreas ; i++)
+ {
+ if (cm.areas[i].floodnum == floodnum || area == -1)
+ buffer[i>>3] |= 1<<(i&7);
+ }
+ }
+
+ return bytes;
+}
+
+/*
+====================
+CM_BoundsIntersect
+====================
+*/
+bool CM_BoundsIntersect( const vec3_t mins, const vec3_t maxs, const vec3_t mins2, const vec3_t maxs2 )
+{
+ if (maxs[0] < mins2[0] - SURFACE_CLIP_EPSILON ||
+ maxs[1] < mins2[1] - SURFACE_CLIP_EPSILON ||
+ maxs[2] < mins2[2] - SURFACE_CLIP_EPSILON ||
+ mins[0] > maxs2[0] + SURFACE_CLIP_EPSILON ||
+ mins[1] > maxs2[1] + SURFACE_CLIP_EPSILON ||
+ mins[2] > maxs2[2] + SURFACE_CLIP_EPSILON)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+/*
+====================
+CM_BoundsIntersectPoint
+====================
+*/
+bool CM_BoundsIntersectPoint( const vec3_t mins, const vec3_t maxs, const vec3_t point )
+{
+ if (maxs[0] < point[0] - SURFACE_CLIP_EPSILON ||
+ maxs[1] < point[1] - SURFACE_CLIP_EPSILON ||
+ maxs[2] < point[2] - SURFACE_CLIP_EPSILON ||
+ mins[0] > point[0] + SURFACE_CLIP_EPSILON ||
+ mins[1] > point[1] + SURFACE_CLIP_EPSILON ||
+ mins[2] > point[2] + SURFACE_CLIP_EPSILON)
+ {
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/qcommon/cm_trace.cpp b/src/qcommon/cm_trace.cpp
new file mode 100644
index 0000000..b773f2a
--- /dev/null
+++ b/src/qcommon/cm_trace.cpp
@@ -0,0 +1,1801 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "cm_local.h"
+
+// always use bbox vs. bbox collision and never capsule vs. bbox or vice versa
+//#define ALWAYS_BBOX_VS_BBOX
+// always use capsule vs. capsule collision and never capsule vs. bbox or vice versa
+//#define ALWAYS_CAPSULE_VS_CAPSULE
+
+//#define CAPSULE_DEBUG
+
+/*
+===============================================================================
+
+BASIC MATH
+
+===============================================================================
+*/
+
+/*
+================
+RotatePoint
+================
+*/
+void RotatePoint(vec3_t point, /*const*/ vec3_t matrix[3]) { // FIXME
+ vec3_t tvec;
+
+ VectorCopy(point, tvec);
+ point[0] = DotProduct(matrix[0], tvec);
+ point[1] = DotProduct(matrix[1], tvec);
+ point[2] = DotProduct(matrix[2], tvec);
+}
+
+/*
+================
+TransposeMatrix
+================
+*/
+void TransposeMatrix(/*const*/ vec3_t matrix[3], vec3_t transpose[3]) { // FIXME
+ int i, j;
+ for (i = 0; i < 3; i++) {
+ for (j = 0; j < 3; j++) {
+ transpose[i][j] = matrix[j][i];
+ }
+ }
+}
+
+/*
+================
+CreateRotationMatrix
+================
+*/
+void CreateRotationMatrix(const vec3_t angles, vec3_t matrix[3]) {
+ AngleVectors(angles, matrix[0], matrix[1], matrix[2]);
+ VectorInverse(matrix[1]);
+}
+
+/*
+================
+CM_ProjectPointOntoVector
+================
+*/
+void CM_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vDir, vec3_t vProj )
+{
+ vec3_t pVec;
+
+ VectorSubtract( point, vStart, pVec );
+ // project onto the directional vector for this segment
+ VectorMA( vStart, DotProduct( pVec, vDir ), vDir, vProj );
+}
+
+/*
+================
+CM_DistanceFromLineSquared
+================
+*/
+float CM_DistanceFromLineSquared(vec3_t p, vec3_t lp1, vec3_t lp2, vec3_t dir) {
+ vec3_t proj, t;
+ int j;
+
+ CM_ProjectPointOntoVector(p, lp1, dir, proj);
+ for (j = 0; j < 3; j++)
+ if ((proj[j] > lp1[j] && proj[j] > lp2[j]) ||
+ (proj[j] < lp1[j] && proj[j] < lp2[j]))
+ break;
+ if (j < 3) {
+ if (fabs(proj[j] - lp1[j]) < fabs(proj[j] - lp2[j]))
+ VectorSubtract(p, lp1, t);
+ else
+ VectorSubtract(p, lp2, t);
+ return VectorLengthSquared(t);
+ }
+ VectorSubtract(p, proj, t);
+ return VectorLengthSquared(t);
+}
+
+/*
+================
+CM_VectorDistanceSquared
+================
+*/
+float CM_VectorDistanceSquared(vec3_t p1, vec3_t p2) {
+ vec3_t dir;
+
+ VectorSubtract(p2, p1, dir);
+ return VectorLengthSquared(dir);
+}
+
+/*
+================
+SquareRootFloat
+================
+*/
+float SquareRootFloat(float number) {
+ floatint_t t;
+ float x, y;
+ const float f = 1.5F;
+
+ x = number * 0.5F;
+ t.f = number;
+ t.i = 0x5f3759df - ( t.i >> 1 );
+ y = t.f;
+ y = y * ( f - ( x * y * y ) );
+ y = y * ( f - ( x * y * y ) );
+ return number * y;
+}
+
+
+/*
+===============================================================================
+
+POSITION TESTING
+
+===============================================================================
+*/
+
+/*
+================
+CM_TestBoxInBrush
+================
+*/
+void CM_TestBoxInBrush( traceWork_t *tw, cbrush_t *brush ) {
+ int i;
+ cplane_t *plane;
+ float dist;
+ float d1;
+ cbrushside_t *side;
+ float t;
+ vec3_t startp;
+
+ if (!brush->numsides) {
+ return;
+ }
+
+ // special test for axial
+ if ( tw->bounds[0][0] > brush->bounds[1][0]
+ || tw->bounds[0][1] > brush->bounds[1][1]
+ || tw->bounds[0][2] > brush->bounds[1][2]
+ || tw->bounds[1][0] < brush->bounds[0][0]
+ || tw->bounds[1][1] < brush->bounds[0][1]
+ || tw->bounds[1][2] < brush->bounds[0][2]
+ ) {
+ return;
+ }
+
+ if ( tw->type == TT_CAPSULE ) {
+ // the first six planes are the axial planes, so we only
+ // need to test the remainder
+ for ( i = 6 ; i < brush->numsides ; i++ ) {
+ side = brush->sides + i;
+ plane = side->plane;
+
+ // adjust the plane distance apropriately for radius
+ dist = plane->dist + tw->sphere.radius;
+ // find the closest point on the capsule to the plane
+ t = DotProduct( plane->normal, tw->sphere.offset );
+ if ( t > 0 )
+ {
+ VectorSubtract( tw->start, tw->sphere.offset, startp );
+ }
+ else
+ {
+ VectorAdd( tw->start, tw->sphere.offset, startp );
+ }
+ d1 = DotProduct( startp, plane->normal ) - dist;
+ // if completely in front of face, no intersection
+ if ( d1 > 0 ) {
+ return;
+ }
+ }
+ } else {
+ // the first six planes are the axial planes, so we only
+ // need to test the remainder
+ for ( i = 6 ; i < brush->numsides ; i++ ) {
+ side = brush->sides + i;
+ plane = side->plane;
+
+ // adjust the plane distance apropriately for mins/maxs
+ dist = plane->dist - DotProduct( tw->offsets[ plane->signbits ], plane->normal );
+
+ d1 = DotProduct( tw->start, plane->normal ) - dist;
+
+ // if completely in front of face, no intersection
+ if ( d1 > 0 ) {
+ return;
+ }
+ }
+ }
+
+ // inside this brush
+ tw->trace.startsolid = tw->trace.allsolid = qtrue;
+ tw->trace.fraction = 0;
+ tw->trace.contents = brush->contents;
+}
+
+
+
+/*
+================
+CM_TestInLeaf
+================
+*/
+void CM_TestInLeaf( traceWork_t *tw, cLeaf_t *leaf ) {
+ int k;
+ int brushnum;
+ cbrush_t *b;
+ cPatch_t *patch;
+
+ // test box position against all brushes in the leaf
+ for (k=0 ; k<leaf->numLeafBrushes ; k++) {
+ brushnum = cm.leafbrushes[leaf->firstLeafBrush+k];
+ b = &cm.brushes[brushnum];
+ if (b->checkcount == cm.checkcount) {
+ continue; // already checked this brush in another leaf
+ }
+ b->checkcount = cm.checkcount;
+
+ if ( !(b->contents & tw->contents)) {
+ continue;
+ }
+
+ CM_TestBoxInBrush( tw, b );
+ if ( tw->trace.allsolid ) {
+ return;
+ }
+ }
+
+ // test against all patches
+#ifdef BSPC
+ if (1) {
+#else
+ if ( !cm_noCurves->integer ) {
+#endif //BSPC
+ for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) {
+ patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ];
+ if ( !patch ) {
+ continue;
+ }
+ if ( patch->checkcount == cm.checkcount ) {
+ continue; // already checked this brush in another leaf
+ }
+ patch->checkcount = cm.checkcount;
+
+ if ( !(patch->contents & tw->contents)) {
+ continue;
+ }
+
+ if ( CM_PositionTestInPatchCollide( tw, patch->pc ) ) {
+ tw->trace.startsolid = tw->trace.allsolid = qtrue;
+ tw->trace.fraction = 0;
+ tw->trace.contents = patch->contents;
+ return;
+ }
+ }
+ }
+}
+
+/*
+==================
+CM_TestCapsuleInCapsule
+
+capsule inside capsule check
+==================
+*/
+void CM_TestCapsuleInCapsule( traceWork_t *tw, clipHandle_t model ) {
+ int i;
+ vec3_t mins, maxs;
+ vec3_t top, bottom;
+ vec3_t p1, p2, tmp;
+ vec3_t offset, symetricSize[2];
+ float radius, halfwidth, halfheight, offs, r;
+
+ CM_ModelBounds(model, mins, maxs);
+
+ VectorAdd(tw->start, tw->sphere.offset, top);
+ VectorSubtract(tw->start, tw->sphere.offset, bottom);
+ for ( i = 0 ; i < 3 ; i++ ) {
+ offset[i] = ( mins[i] + maxs[i] ) * 0.5;
+ symetricSize[0][i] = mins[i] - offset[i];
+ symetricSize[1][i] = maxs[i] - offset[i];
+ }
+ halfwidth = symetricSize[ 1 ][ 0 ];
+ halfheight = symetricSize[ 1 ][ 2 ];
+ radius = ( halfwidth > halfheight ) ? halfheight : halfwidth;
+ offs = halfheight - radius;
+
+ r = Square(tw->sphere.radius + radius);
+ // check if any of the spheres overlap
+ VectorCopy(offset, p1);
+ p1[2] += offs;
+ VectorSubtract(p1, top, tmp);
+ if ( VectorLengthSquared(tmp) < r ) {
+ tw->trace.startsolid = tw->trace.allsolid = qtrue;
+ tw->trace.fraction = 0;
+ }
+ VectorSubtract(p1, bottom, tmp);
+ if ( VectorLengthSquared(tmp) < r ) {
+ tw->trace.startsolid = tw->trace.allsolid = qtrue;
+ tw->trace.fraction = 0;
+ }
+ VectorCopy(offset, p2);
+ p2[2] -= offs;
+ VectorSubtract(p2, top, tmp);
+ if ( VectorLengthSquared(tmp) < r ) {
+ tw->trace.startsolid = tw->trace.allsolid = qtrue;
+ tw->trace.fraction = 0;
+ }
+ VectorSubtract(p2, bottom, tmp);
+ if ( VectorLengthSquared(tmp) < r ) {
+ tw->trace.startsolid = tw->trace.allsolid = qtrue;
+ tw->trace.fraction = 0;
+ }
+ // if between cylinder up and lower bounds
+ if ( (top[2] >= p1[2] && top[2] <= p2[2]) ||
+ (bottom[2] >= p1[2] && bottom[2] <= p2[2]) ) {
+ // 2d coordinates
+ top[2] = p1[2] = 0;
+ // if the cylinders overlap
+ VectorSubtract(top, p1, tmp);
+ if ( VectorLengthSquared(tmp) < r ) {
+ tw->trace.startsolid = tw->trace.allsolid = qtrue;
+ tw->trace.fraction = 0;
+ }
+ }
+}
+
+/*
+==================
+CM_TestBoundingBoxInCapsule
+
+bounding box inside capsule check
+==================
+*/
+void CM_TestBoundingBoxInCapsule( traceWork_t *tw, clipHandle_t model ) {
+ vec3_t mins, maxs, offset, size[2];
+ clipHandle_t h;
+ cmodel_t *cmod;
+ int i;
+
+ // mins maxs of the capsule
+ CM_ModelBounds(model, mins, maxs);
+
+ // offset for capsule center
+ for ( i = 0 ; i < 3 ; i++ ) {
+ offset[i] = ( mins[i] + maxs[i] ) * 0.5;
+ size[0][i] = mins[i] - offset[i];
+ size[1][i] = maxs[i] - offset[i];
+ tw->start[i] -= offset[i];
+ tw->end[i] -= offset[i];
+ }
+
+ // replace the bounding box with the capsule
+ tw->type = TT_CAPSULE;
+ tw->sphere.radius = ( size[1][0] > size[1][2] ) ? size[1][2]: size[1][0];
+ tw->sphere.halfheight = size[1][2];
+ VectorSet( tw->sphere.offset, 0, 0, size[1][2] - tw->sphere.radius );
+
+ // replace the capsule with the bounding box
+ h = CM_TempBoxModel(tw->size[0], tw->size[1], false);
+ // 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 = false;
+
+ 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;
+ bool 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 = false;
+ startout = false;
+
+ 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 = true; // endpoint is not in solid
+
+ if( d1 > 0 )
+ startout = true;
+
+ // 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 = true;
+
+ // 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 = true; // endpoint is not in solid
+ }
+ if (d1 > 0) {
+ startout = true;
+ }
+
+ // 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 = true;
+
+ // 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 = true; // endpoint is not in solid
+ }
+ if (d1 > 0) {
+ startout = true;
+ }
+
+ // 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 = true;
+
+ // 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;
+ if (clipplane != NULL) {
+ tw->trace.plane = *clipplane;
+ }
+ if (leadside != NULL) {
+ 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
+ ::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
+ ::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 = false;
+
+ if ( !CM_BoundsIntersect( tw->bounds[0], tw->bounds[1],
+ b->bounds[0], b->bounds[1] ) ) {
+ continue;
+ }
+
+ CM_TraceThroughBrush( tw, b );
+ if ( !tw->trace.fraction ) {
+ tw->trace.lateralFraction = 0.0f;
+ return;
+ }
+ }
+
+ // trace line against all patches in the leaf
+#ifdef BSPC
+ if (1) {
+#else
+ if ( !cm_noCurves->integer ) {
+#endif
+ for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) {
+ patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ];
+ if ( !patch ) {
+ continue;
+ }
+ if ( patch->checkcount == cm.checkcount ) {
+ continue; // already checked this patch in another leaf
+ }
+ patch->checkcount = cm.checkcount;
+
+ if ( !(patch->contents & tw->contents) ) {
+ continue;
+ }
+
+ CM_TraceThroughPatch( tw, patch );
+
+ if ( !tw->trace.fraction ) {
+ tw->trace.lateralFraction = 0.0f;
+ return;
+ }
+ }
+ }
+
+ if( tw->testLateralCollision && tw->trace.fraction < 1.0f )
+ {
+ for( k = 0; k < leaf->numLeafBrushes; k++ )
+ {
+ brushnum = cm.leafbrushes[ leaf->firstLeafBrush + k ];
+
+ b = &cm.brushes[ brushnum ];
+
+ // This brush never collided, so don't bother
+ if( !b->collided )
+ continue;
+
+ if( !( b->contents & tw->contents ) )
+ continue;
+
+ CM_ProximityToBrush( tw, b );
+
+ if( !tw->trace.lateralFraction )
+ return;
+ }
+
+ for( k = 0; k < leaf->numLeafSurfaces; k++ )
+ {
+ patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ];
+ if( !patch )
+ continue;
+
+ if( !( patch->contents & tw->contents ) )
+ continue;
+
+ CM_ProximityToPatch( tw, patch );
+
+ if( !tw->trace.lateralFraction )
+ return;
+ }
+ }
+}
+
+#define RADIUS_EPSILON 1.0f
+
+/*
+================
+CM_TraceThroughSphere
+
+get the first intersection of the ray with the sphere
+================
+*/
+void CM_TraceThroughSphere( traceWork_t *tw, vec3_t origin, float radius, vec3_t start, vec3_t end ) {
+ float l1, l2, length, scale, fraction;
+ //float a;
+ float 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;
+ float 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], false);
+ // calculate collision
+ cmod = CM_ClipHandleToModel( h );
+ CM_TraceThroughLeaf( tw, &cmod->leaf );
+}
+
+//=========================================================================================
+
+/*
+==================
+CM_TraceThroughTree
+
+Traverse all the contacted leafs from the start to the end position.
+If the trace is a point, they will be exactly in order, but for larger
+trace volumes it is possible to hit something in a later leaf with
+a smaller intercept fraction.
+==================
+*/
+void CM_TraceThroughTree( traceWork_t *tw, int num, float p1f, float p2f, vec3_t p1, vec3_t p2) {
+ cNode_t *node;
+ cplane_t *plane;
+ float t1, t2, offset;
+ float frac, frac2;
+ float idist;
+ vec3_t mid;
+ int side;
+ float midf;
+
+ if (tw->trace.fraction <= p1f) {
+ return; // already hit something nearer
+ }
+
+ // if < 0, we are in a leaf node
+ if (num < 0) {
+ CM_TraceThroughLeaf( tw, &cm.leafs[-1-num] );
+ return;
+ }
+
+ //
+ // find the point distances to the seperating plane
+ // and the offset for the size of the box
+ //
+ node = cm.nodes + num;
+ plane = node->plane;
+
+ // adjust the plane distance apropriately for mins/maxs
+ if ( plane->type < 3 ) {
+ t1 = p1[plane->type] - plane->dist;
+ t2 = p2[plane->type] - plane->dist;
+ offset = tw->extents[plane->type];
+ } else {
+ t1 = DotProduct (plane->normal, p1) - plane->dist;
+ t2 = DotProduct (plane->normal, p2) - plane->dist;
+ if ( tw->isPoint ) {
+ offset = 0;
+ } else {
+ // this is silly
+ offset = 2048;
+ }
+ }
+
+ // see which sides we need to consider
+ if ( t1 >= offset + 1 && t2 >= offset + 1 ) {
+ CM_TraceThroughTree( tw, node->children[0], p1f, p2f, p1, p2 );
+ return;
+ }
+ if ( t1 < -offset - 1 && t2 < -offset - 1 ) {
+ CM_TraceThroughTree( tw, node->children[1], p1f, p2f, p1, p2 );
+ return;
+ }
+
+ // put the crosspoint SURFACE_CLIP_EPSILON pixels on the near side
+ if ( t1 < t2 ) {
+ idist = 1.0/(t1-t2);
+ side = 1;
+ frac2 = (t1 + offset + SURFACE_CLIP_EPSILON)*idist;
+ frac = (t1 - offset + SURFACE_CLIP_EPSILON)*idist;
+ } else if (t1 > t2) {
+ idist = 1.0/(t1-t2);
+ side = 0;
+ frac2 = (t1 - offset - SURFACE_CLIP_EPSILON)*idist;
+ frac = (t1 + offset + SURFACE_CLIP_EPSILON)*idist;
+ } else {
+ side = 0;
+ frac = 1;
+ frac2 = 0;
+ }
+
+ // move up to the node
+ if ( frac < 0 ) {
+ frac = 0;
+ }
+ if ( frac > 1 ) {
+ frac = 1;
+ }
+
+ midf = p1f + (p2f - p1f)*frac;
+
+ mid[0] = p1[0] + frac*(p2[0] - p1[0]);
+ mid[1] = p1[1] + frac*(p2[1] - p1[1]);
+ mid[2] = p1[2] + frac*(p2[2] - p1[2]);
+
+ CM_TraceThroughTree( tw, node->children[side], p1f, midf, p1, mid );
+
+
+ // go past the node
+ if ( frac2 < 0 ) {
+ frac2 = 0;
+ }
+ if ( frac2 > 1 ) {
+ frac2 = 1;
+ }
+
+ midf = p1f + (p2f - p1f)*frac2;
+
+ mid[0] = p1[0] + frac2*(p2[0] - p1[0]);
+ mid[1] = p1[1] + frac2*(p2[1] - p1[1]);
+ mid[2] = p1[2] + frac2*(p2[2] - p1[2]);
+
+ CM_TraceThroughTree( tw, node->children[side^1], midf, p2f, mid, p2 );
+}
+
+//======================================================================
+
+
+/*
+==================
+CM_Trace
+==================
+*/
+void CM_Trace( trace_t *results, const vec3_t start,
+ const vec3_t end, vec3_t mins, vec3_t maxs,
+ clipHandle_t model, const vec3_t origin, int brushmask,
+ traceType_t type, sphere_t *sphere ) {
+ int i;
+ traceWork_t tw;
+ vec3_t offset;
+ cmodel_t *cmod;
+
+ cmod = CM_ClipHandleToModel( model );
+
+ cm.checkcount++; // for multi-check avoidance
+
+ c_traces++; // for statistics, may be zeroed
+
+ // fill in a default trace
+ ::memset( &tw, 0, sizeof(tw) );
+ tw.trace.fraction = 1; // assume it goes the entire distance until shown otherwise
+ VectorCopy(origin, tw.modelOrigin);
+ tw.type = type;
+
+ if (!cm.numNodes) {
+ *results = tw.trace;
+
+ return; // map not loaded, shouldn't happen
+ }
+
+ // allow NULL to be passed in for 0,0,0
+ if ( !mins ) {
+ mins = vec3_origin;
+ }
+ if ( !maxs ) {
+ maxs = vec3_origin;
+ }
+
+ // set basic parms
+ tw.contents = brushmask;
+
+ // adjust so that mins and maxs are always symetric, which
+ // avoids some complications with plane expanding of rotated
+ // bmodels
+ for ( i = 0 ; i < 3 ; i++ ) {
+ offset[i] = ( mins[i] + maxs[i] ) * 0.5;
+ tw.size[0][i] = mins[i] - offset[i];
+ tw.size[1][i] = maxs[i] - offset[i];
+ tw.start[i] = start[i] + offset[i];
+ tw.end[i] = end[i] + offset[i];
+ }
+
+ // if a sphere is already specified
+ if ( sphere ) {
+ tw.sphere = *sphere;
+ }
+ else {
+ tw.sphere.radius = ( tw.size[1][0] > tw.size[1][2] ) ? tw.size[1][2]: tw.size[1][0];
+ tw.sphere.halfheight = tw.size[1][2];
+ VectorSet( tw.sphere.offset, 0, 0, tw.size[1][2] - tw.sphere.radius );
+ }
+
+ tw.maxOffset = tw.size[1][0] + tw.size[1][1] + tw.size[1][2];
+
+ // tw.offsets[signbits] = vector to apropriate corner from origin
+ tw.offsets[0][0] = tw.size[0][0];
+ tw.offsets[0][1] = tw.size[0][1];
+ tw.offsets[0][2] = tw.size[0][2];
+
+ tw.offsets[1][0] = tw.size[1][0];
+ tw.offsets[1][1] = tw.size[0][1];
+ tw.offsets[1][2] = tw.size[0][2];
+
+ tw.offsets[2][0] = tw.size[0][0];
+ tw.offsets[2][1] = tw.size[1][1];
+ tw.offsets[2][2] = tw.size[0][2];
+
+ tw.offsets[3][0] = tw.size[1][0];
+ tw.offsets[3][1] = tw.size[1][1];
+ tw.offsets[3][2] = tw.size[0][2];
+
+ tw.offsets[4][0] = tw.size[0][0];
+ tw.offsets[4][1] = tw.size[0][1];
+ tw.offsets[4][2] = tw.size[1][2];
+
+ tw.offsets[5][0] = tw.size[1][0];
+ tw.offsets[5][1] = tw.size[0][1];
+ tw.offsets[5][2] = tw.size[1][2];
+
+ tw.offsets[6][0] = tw.size[0][0];
+ tw.offsets[6][1] = tw.size[1][1];
+ tw.offsets[6][2] = tw.size[1][2];
+
+ tw.offsets[7][0] = tw.size[1][0];
+ tw.offsets[7][1] = tw.size[1][1];
+ tw.offsets[7][2] = tw.size[1][2];
+
+ //
+ // calculate bounds
+ //
+ if ( tw.type == TT_CAPSULE ) {
+ for ( i = 0 ; i < 3 ; i++ ) {
+ if ( tw.start[i] < tw.end[i] ) {
+ tw.bounds[0][i] = tw.start[i] - fabs(tw.sphere.offset[i]) - tw.sphere.radius;
+ tw.bounds[1][i] = tw.end[i] + fabs(tw.sphere.offset[i]) + tw.sphere.radius;
+ } else {
+ tw.bounds[0][i] = tw.end[i] - fabs(tw.sphere.offset[i]) - tw.sphere.radius;
+ tw.bounds[1][i] = tw.start[i] + fabs(tw.sphere.offset[i]) + tw.sphere.radius;
+ }
+ }
+ }
+ else {
+ for ( i = 0 ; i < 3 ; i++ ) {
+ if ( tw.start[i] < tw.end[i] ) {
+ tw.bounds[0][i] = tw.start[i] + tw.size[0][i];
+ tw.bounds[1][i] = tw.end[i] + tw.size[1][i];
+ } else {
+ tw.bounds[0][i] = tw.end[i] + tw.size[0][i];
+ tw.bounds[1][i] = tw.start[i] + tw.size[1][i];
+ }
+ }
+ }
+
+ //
+ // check for position test special case
+ //
+ if (start[0] == end[0] && start[1] == end[1] && start[2] == end[2]) {
+ if ( model ) {
+#ifdef ALWAYS_BBOX_VS_BBOX // FIXME - compile time flag?
+ if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) {
+ tw.type = TT_AABB;
+ CM_TestInLeaf( &tw, &cmod->leaf );
+ }
+ else
+#elif defined(ALWAYS_CAPSULE_VS_CAPSULE)
+ if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) {
+ CM_TestCapsuleInCapsule( &tw, model );
+ }
+ else
+#endif
+ if ( model == CAPSULE_MODEL_HANDLE ) {
+ if ( tw.type == TT_CAPSULE ) {
+ CM_TestCapsuleInCapsule( &tw, model );
+ }
+ else {
+ CM_TestBoundingBoxInCapsule( &tw, model );
+ }
+ }
+ else {
+ CM_TestInLeaf( &tw, &cmod->leaf );
+ }
+ } else {
+ CM_PositionTest( &tw );
+ }
+ } else {
+ //
+ // check for point special case
+ //
+ if ( tw.size[0][0] == 0 && tw.size[0][1] == 0 && tw.size[0][2] == 0 ) {
+ tw.isPoint = true;
+ VectorClear( tw.extents );
+ } else {
+ tw.isPoint = false;
+ 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;
+ bool 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 = true;
+ } else {
+ rotated = false;
+ }
+
+ 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
+ ::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 = true;
+ 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 = false;
+ 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.cpp b/src/qcommon/cmd.cpp
new file mode 100644
index 0000000..97b1226
--- /dev/null
+++ b/src/qcommon/cmd.cpp
@@ -0,0 +1,941 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// cmd.c -- Quake script command processing module
+
+#include "cmd.h"
+
+#include "cvar.h"
+#include "files.h"
+#include "q_shared.h"
+#include "qcommon.h"
+
+#ifndef DEDICATED
+#include "client/client.h"
+#endif
+
+#define MAX_CMD_BUFFER 128*1024
+#define MAX_CMD_LINE 1024
+
+typedef struct {
+ byte *data;
+ int maxsize;
+ int cursize;
+} cmd_t;
+
+int cmd_wait;
+cmd_t cmd_text;
+byte cmd_text_buf[MAX_CMD_BUFFER];
+
+
+//=============================================================================
+
+/*
+============
+Cmd_Wait_f
+
+Causes execution of the remainder of the command buffer to be delayed until
+next frame. This allows commands like:
+bind g "cmd use rocket ; +attack ; wait ; -attack ; cmd use blaster"
+============
+*/
+void Cmd_Wait_f( void ) {
+ if ( Cmd_Argc() == 2 ) {
+ cmd_wait = atoi( Cmd_Argv( 1 ) );
+ if ( cmd_wait < 0 )
+ cmd_wait = 1; // ignore the argument
+ } else {
+ cmd_wait = 1;
+ }
+}
+
+
+/*
+=============================================================================
+
+ COMMAND BUFFER
+
+=============================================================================
+*/
+
+/*
+============
+Cbuf_Init
+============
+*/
+void Cbuf_Init (void)
+{
+ cmd_text.data = cmd_text_buf;
+ cmd_text.maxsize = MAX_CMD_BUFFER;
+ cmd_text.cursize = 0;
+}
+
+/*
+============
+Cbuf_AddText
+
+Adds command text at the end of the buffer, does NOT add a final \n
+============
+*/
+void Cbuf_AddText( const char *text ) {
+ int l;
+
+ l = strlen (text);
+
+ if (cmd_text.cursize + l >= cmd_text.maxsize)
+ {
+ Com_Printf ("Cbuf_AddText: overflow\n");
+ return;
+ }
+ ::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_InsertFmtText( const char *fmt, ... ) {
+ int len;
+ int i;
+
+ char text[MAXPRINTMSG];
+
+ va_list args;
+ va_start(args, fmt);
+ Q_vsnprintf(text, sizeof(text), fmt, args);
+ va_end(args);
+
+ 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
+ ::memcpy( cmd_text.data, text, len - 1 );
+
+ // add a \n
+ cmd_text.data[ len - 1 ] = '\n';
+
+ cmd_text.cursize += len;
+}
+
+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
+ ::memcpy( cmd_text.data, text, len - 1 );
+
+ // add a \n
+ cmd_text.data[ len - 1 ] = '\n';
+
+ cmd_text.cursize += len;
+}
+
+/*
+============
+Cbuf_ExecuteText
+============
+*/
+void Cbuf_ExecuteText (int exec_when, const char *text)
+{
+ switch (exec_when)
+ {
+ case EXEC_NOW:
+ if (text && strlen(text) > 0) {
+ Com_DPrintf(S_COLOR_YELLOW "EXEC_NOW %s\n", text);
+ Cmd_ExecuteString (text);
+ } else {
+ Cbuf_Execute();
+ Com_DPrintf(S_COLOR_YELLOW "EXEC_NOW %s\n", cmd_text.data);
+ }
+ break;
+ case EXEC_INSERT:
+ Cbuf_InsertText (text);
+ break;
+ case EXEC_APPEND:
+ Cbuf_AddText (text);
+ break;
+ default:
+ Com_Error (ERR_FATAL, "Cbuf_ExecuteText: bad exec_when");
+ }
+}
+
+/*
+============
+Cbuf_Execute
+============
+*/
+void Cbuf_Execute (void)
+{
+ int i;
+ char *text;
+ char line[MAX_CMD_LINE];
+ int quotes;
+
+ // This will keep // style comments all on one line by not breaking on
+ // a semicolon. It will keep /* ... */ style comments all on one line by not
+ // breaking it for semicolon or newline.
+ bool in_star_comment = false;
+ bool in_slash_comment = false;
+ while (cmd_text.cursize)
+ {
+ if ( cmd_wait > 0 ) {
+ // skip out while text still remains in buffer, leaving it
+ // for next frame
+ cmd_wait--;
+ break;
+ }
+
+ // find a \n or ; line break or comment: // or /* */
+ text = (char *)cmd_text.data;
+
+ quotes = 0;
+ for (i=0 ; i< cmd_text.cursize ; i++)
+ {
+ if (text[i] == '"')
+ quotes++;
+
+ if ( !(quotes&1)) {
+ if (i < cmd_text.cursize - 1) {
+ if (! in_star_comment && text[i] == '/' && text[i+1] == '/')
+ in_slash_comment = true;
+ else if (! in_slash_comment && text[i] == '/' && text[i+1] == '*')
+ in_star_comment = true;
+ else if (in_star_comment && text[i] == '*' && text[i+1] == '/') {
+ in_star_comment = false;
+ // If we are in a star comment, then the part after it is valid
+ // Note: This will cause it to NUL out the terminating '/'
+ // but ExecuteString doesn't require it anyway.
+ i++;
+ break;
+ }
+ }
+ if (! in_slash_comment && ! in_star_comment && text[i] == ';')
+ break;
+ }
+ if (! in_star_comment && (text[i] == '\n' || text[i] == '\r')) {
+ in_slash_comment = false;
+ break;
+ }
+ }
+
+ if( i >= (MAX_CMD_LINE - 1)) {
+ i = MAX_CMD_LINE - 1;
+ }
+
+ ::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 ) {
+ bool quiet;
+ union {
+ char *c;
+ void *v;
+ } f;
+ char filename[MAX_QPATH];
+
+ quiet = !Q_stricmp(Cmd_Argv(0), "execq");
+
+ if (Cmd_Argc () != 2) {
+ Com_Printf ("exec%s <filename> : execute a script file%s\n",
+ quiet ? "q" : "", quiet ? " without notification" : "");
+ return;
+ }
+
+ Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) );
+ COM_DefaultExtension( filename, sizeof( filename ), ".cfg" );
+ FS_ReadFile( filename, &f.v);
+ if (!f.c) {
+ Com_Printf ("couldn't exec %s\n", filename);
+ return;
+ }
+ if (!quiet)
+ Com_Printf ("execing %s\n", filename);
+
+ Cbuf_InsertText (f.c);
+
+ FS_FreeFile (f.v);
+}
+
+
+/*
+===============
+Cmd_Vstr_f
+
+Inserts the current value of a variable as command text
+===============
+*/
+void Cmd_Vstr_f( void ) {
+ if (Cmd_Argc() != 2) {
+ Com_Printf ("vstr <variablename> : execute a variable command\n");
+ return;
+ }
+
+ const char* v = Cvar_VariableString( Cmd_Argv( 1 ) );
+ Cbuf_InsertFmtText( "%s\n", v );
+}
+
+
+/*
+===============
+Cmd_Echo_f
+
+Just prints the rest of the line to the console
+===============
+*/
+void Cmd_Echo_f (void)
+{
+ Com_Printf ("%s\n", Cmd_Args());
+}
+
+
+/*
+=============================================================================
+
+ COMMAND EXECUTION
+
+=============================================================================
+*/
+
+struct cmd_function_t
+{
+ cmd_function_t *next;
+ char *name;
+ xcommand_t function;
+ completionFunc_t complete;
+};
+
+
+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 )
+{
+ ::memcpy( &savedCmd, &cmd, sizeof( cmdContext_t ) );
+}
+
+/*
+============
+Cmd_RestoreCmdContext
+============
+*/
+void Cmd_RestoreCmdContext( void )
+{
+ ::memcpy( &cmd, &savedCmd, sizeof( cmdContext_t ) );
+}
+
+/*
+============
+Cmd_Argc
+============
+*/
+int Cmd_Argc( void ) {
+ return cmd.argc;
+}
+
+/*
+============
+Cmd_Argv
+============
+*/
+char* Cmd_Argv( int arg ) {
+ if ( arg >= cmd.argc ) {
+ return (char*)"\0";
+ }
+ 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, bool 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, false );
+}
+
+/*
+============
+Cmd_TokenizeStringIgnoreQuotes
+============
+*/
+void Cmd_TokenizeStringIgnoreQuotes( const char *text_in ) {
+ Cmd_TokenizeString2( text_in, true );
+}
+
+/*
+============
+Cmd_FindCommand
+============
+*/
+cmd_function_t *Cmd_FindCommand( const char *cmd_name )
+{
+ cmd_function_t *cmd;
+ for( cmd = cmd_functions; cmd; cmd = cmd->next )
+ if( !Q_stricmp( cmd_name, cmd->name ) )
+ return cmd;
+ return nullptr;
+}
+
+/*
+============
+Cmd_FindCommand
+============
+*/
+bool Cmd_CommadExists( const char *cmd_name )
+{
+ return Cmd_FindCommand( cmd_name ) ? true : false;
+}
+
+/*
+============
+Cmd_AddCommand
+============
+*/
+void Cmd_AddCommand( const char *cmd_name, xcommand_t function ) {
+ cmd_function_t *cmd;
+
+ // fail if the command already exists
+ if( Cmd_FindCommand( cmd_name ) )
+ {
+ // allow completion-only commands to be silently doubled
+ if( function != nullptr )
+ Com_Printf( "Cmd_AddCommand: %s already defined\n", cmd_name );
+ return;
+ }
+
+ // use a small malloc to avoid zone fragmentation
+ cmd = new cmd_function_t;
+ cmd->name = CopyString( cmd_name );
+ cmd->function = function;
+ cmd->complete = nullptr;
+ cmd->next = cmd_functions;
+ cmd_functions = cmd;
+}
+
+/*
+============
+Cmd_SetCommandCompletionFunc
+============
+*/
+void Cmd_SetCommandCompletionFunc( const char *command, completionFunc_t complete ) {
+ cmd_function_t *cmd;
+
+ for( cmd = cmd_functions; cmd; cmd = cmd->next ) {
+ if( !Q_stricmp( command, cmd->name ) ) {
+ cmd->complete = complete;
+ return;
+ }
+ }
+}
+
+/*
+============
+Cmd_RemoveCommand
+============
+*/
+void Cmd_RemoveCommand( const char *cmd_name )
+{
+ cmd_function_t *cmd, **back;
+
+ back = &cmd_functions;
+ for ( ;; )
+ {
+ 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);
+ }
+ delete cmd;
+ return;
+ }
+ back = &cmd->next;
+ }
+}
+
+/*
+============
+Cmd_RemoveCommandSafe
+
+Only remove commands with no associated function
+============
+*/
+void Cmd_RemoveCommandSafe( const char *cmd_name )
+{
+ cmd_function_t *cmd = Cmd_FindCommand( cmd_name );
+
+ if( !cmd )
+ return;
+
+ if( cmd->function )
+ {
+ Com_Error( ERR_DROP, "Restricted source tried to remove system command \"%s\"",
+ cmd_name );
+ return;
+ }
+
+ Cmd_RemoveCommand( cmd_name );
+}
+
+/*
+============
+Cmd_CommandCompletion
+============
+*/
+void Cmd_CommandCompletion( void(*callback)(const char *s) ) {
+ cmd_function_t *cmd;
+
+ for (cmd=cmd_functions ; cmd ; cmd=cmd->next) {
+ callback( cmd->name );
+ }
+}
+
+/*
+============
+Cmd_CompleteArgument
+============
+*/
+void Cmd_CompleteArgument( const char *command, char *args, int argNum )
+{
+ cmd_function_t *cmd;
+
+ // FIXIT-H: There needs to be a way to toggle this functionality at runtime
+ // rather than just crashing when a cgame doesn't provide support. #45
+ // https://github.com/GrangerHub/tremulous/issues/45
+#if 0
+#ifndef DEDICATED
+ // Forward command argument completion to CGAME VM
+ if( cls.cgame && !VM_Call( cls.cgame, CG_CONSOLE_COMPLETARGUMENT, argNum ) )
+#endif
+#endif
+ // Call local completion if VM doesn't pick up
+ for( cmd = cmd_functions; cmd; cmd = cmd->next )
+ if( !Q_stricmp( command, cmd->name ) && cmd->complete )
+ cmd->complete( args, argNum );
+}
+
+
+/*
+============
+Cmd_ExecuteString
+
+A complete command line has been parsed, so try to execute it
+============
+*/
+void Cmd_ExecuteString( const char *text ) {
+ cmd_function_t *cmdFunc, **prev;
+
+ // execute the command line
+ Cmd_TokenizeString( text );
+ if ( !Cmd_Argc() ) {
+ return; // no tokens
+ }
+
+ // check registered command functions
+ for ( prev = &cmd_functions ; *prev ; prev = &cmdFunc->next ) {
+ cmdFunc = *prev;
+ if ( !Q_stricmp( cmd.argv[0], cmdFunc->name ) ) {
+ // rearrange the links so that the command will be
+ // near the head of the list next time it is used
+ *prev = cmdFunc->next;
+ cmdFunc->next = cmd_functions;
+ cmd_functions = cmdFunc;
+
+ // perform the action
+ if ( !cmdFunc->function ) {
+ // let the cgame or game handle it
+ break;
+ } else {
+ cmdFunc->function ();
+ }
+ return;
+ }
+ }
+
+ // check cvars
+ if ( Cvar_Command() ) {
+ return;
+ }
+
+ // check client game commands
+ if ( com_cl_running && com_cl_running->integer && CL_GameCommand() ) {
+ return;
+ }
+
+ // check server game commands
+ if ( com_sv_running && com_sv_running->integer && SV_GameCommand() ) {
+ return;
+ }
+
+ // check ui commands
+ if ( com_cl_running && com_cl_running->integer && UI_GameCommand() ) {
+ return;
+ }
+
+ // send it as a server command if we are connected
+ CL_ForwardCommandToServer ( text );
+}
+
+/*
+============
+Cmd_List_f
+============
+*/
+void Cmd_List_f (void)
+{
+ cmd_function_t* cmd;
+ int i;
+ const char* match;
+
+ if ( Cmd_Argc() > 1 ) {
+ match = Cmd_Argv( 1 );
+ } else {
+ match = nullptr;
+ }
+
+ i = 0;
+ for (cmd=cmd_functions ; cmd ; cmd=cmd->next) {
+ if (match && !Com_Filter(match, cmd->name, false)) continue;
+
+ Com_Printf ("%s\n", cmd->name);
+ i++;
+ }
+ Com_Printf ("%i commands\n", i);
+}
+
+/*
+==================
+Cmd_CompleteCfgName
+==================
+*/
+void Cmd_CompleteCfgName( char *args, int argNum ) {
+ if( argNum == 2 ) {
+ Field_CompleteFilename( "", "cfg", false, true );
+ }
+}
+
+/*
+============
+Cmd_Init
+============
+*/
+void Cmd_Init (void) {
+ Cmd_AddCommand ("cmdlist",Cmd_List_f);
+ Cmd_AddCommand ("exec",Cmd_Exec_f);
+ Cmd_AddCommand ("execq",Cmd_Exec_f);
+ Cmd_SetCommandCompletionFunc( "exec", Cmd_CompleteCfgName );
+ Cmd_SetCommandCompletionFunc( "execq", Cmd_CompleteCfgName );
+ Cmd_AddCommand ("vstr",Cmd_Vstr_f);
+ Cmd_SetCommandCompletionFunc( "vstr", Cvar_CompleteCvarName );
+ Cmd_AddCommand ("echo",Cmd_Echo_f);
+ Cmd_AddCommand ("wait", Cmd_Wait_f);
+}
diff --git a/src/qcommon/cmd.h b/src/qcommon/cmd.h
new file mode 100644
index 0000000..390fa85
--- /dev/null
+++ b/src/qcommon/cmd.h
@@ -0,0 +1,115 @@
+/*
+ * This file is part of Tremulous.
+ * Copyright © 2017 Victor Roemer (blowfish) <victor@badsec.org>
+ * Copyright (C) 2015-2019 GrangerHub
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CMD_H
+#define CMD_H
+
+/*
+==============================================================
+
+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.
+
+*/
+
+using xcommand_t = void(*)();
+
+void Cmd_Init(void);
+
+bool Cmd_CommadExists( const char *cmd_name );
+
+void Cmd_AddCommand(const char *cmd_name, xcommand_t function);
+// called by the init functions of other parts of the program to
+// register commands and functions to call for them.
+// The cmd_name is referenced later, so it should not be in temp memory
+// if function is NULL, the command will be forwarded to the server
+// as a clc_clientCommand instead of executed locally
+
+void Cmd_RemoveCommand(const char *cmd_name);
+
+typedef void (*completionFunc_t)(char *args, int argNum);
+
+// don't allow VMs to remove system commands
+void Cmd_RemoveCommandSafe(const char *cmd_name);
+
+void Cmd_CommandCompletion(void (*callback)(const char *s));
+// callback with each valid string
+void Cmd_SetCommandCompletionFunc(const char *command, completionFunc_t complete);
+void Cmd_CompleteArgument(const char *command, char *args, int argNum);
+void Cmd_CompleteCfgName(char *args, int argNum);
+
+int Cmd_Argc(void);
+char *Cmd_Argv(int arg);
+void Cmd_ArgvBuffer(int arg, char *buffer, int bufferLength);
+char *Cmd_Args(void);
+char *Cmd_ArgsFrom(int arg);
+void Cmd_ArgsBuffer(char *buffer, int bufferLength);
+void Cmd_LiteralArgsBuffer(char *buffer, int bufferLength);
+char *Cmd_Cmd(void);
+// 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);
+
+#endif
diff --git a/src/qcommon/common.cpp b/src/qcommon/common.cpp
new file mode 100644
index 0000000..051f475
--- /dev/null
+++ b/src/qcommon/common.cpp
@@ -0,0 +1,3662 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+// common.c -- misc functions used in client and server
+
+#include "qcommon.h"
+
+#include <setjmp.h>
+#ifdef _WIN32
+#include <winsock.h>
+#else
+#include <netinet/in.h>
+//#include <sys/stat.h> // umask
+#endif
+
+#include "sys/sys_shared.h"
+
+#include "cmd.h"
+#include "crypto.h"
+#include "cvar.h"
+#include "files.h"
+#define JSON_IMPLEMENTATION
+#include "json.h"
+#include "msg.h"
+#include "q_shared.h"
+#include "vm.h"
+
+int demo_protocols[] = { PROTOCOL_VERSION, 70, 69, 0 };
+
+#define MAX_NUM_ARGVS 50
+
+#define MIN_DEDICATED_COMHUNKMEGS 16
+#define MIN_COMHUNKMEGS 256
+#define DEF_COMHUNKMEGS 256
+#define DEF_COMZONEMEGS 48
+#define DEF_COMHUNKMEGS_S XSTRING(DEF_COMHUNKMEGS)
+#define DEF_COMZONEMEGS_S XSTRING(DEF_COMZONEMEGS)
+
+int com_argc;
+char* com_argv[MAX_NUM_ARGVS+1];
+
+jmp_buf abortframe; // an ERR_DROP occured, exit the entire frame
+
+
+FILE *debuglogfile;
+static fileHandle_t pipefile;
+static fileHandle_t logfile;
+fileHandle_t com_journalFile; // events are written here
+fileHandle_t com_journalDataFile; // config files are written here
+
+cvar_t *com_speeds;
+cvar_t *com_developer;
+cvar_t *com_dedicated;
+cvar_t *com_timescale;
+cvar_t *com_fixedtime;
+cvar_t *com_journal;
+cvar_t *com_maxfps;
+cvar_t *com_altivec;
+cvar_t *com_timedemo;
+cvar_t *com_sv_running;
+cvar_t *com_cl_running;
+cvar_t *com_logfile; // 1 = buffer log, 2 = flush after each print
+cvar_t *com_pipefile;
+cvar_t *com_showtrace;
+cvar_t *com_version;
+cvar_t *com_buildScript; // for automated data building scripts
+#ifdef CINEMATICS_INTRO
+cvar_t *com_introPlayed;
+#endif
+cvar_t *cl_paused;
+cvar_t *sv_paused;
+cvar_t *cl_packetdelay;
+cvar_t *sv_packetdelay;
+cvar_t *com_cameraMode;
+cvar_t *com_ansiColor;
+cvar_t *com_unfocused;
+cvar_t *com_maxfpsUnfocused;
+cvar_t *com_minimized;
+cvar_t *com_maxfpsMinimized;
+cvar_t *com_standalone;
+cvar_t *com_gamename;
+cvar_t *com_protocol;
+#ifdef LEGACY_PROTOCOL
+cvar_t *com_legacyprotocol;
+#endif
+cvar_t *com_basegame;
+cvar_t *com_homepath;
+cvar_t *com_busyWait;
+
+#if id386
+void (QDECL *Q_SnapVector)(vec3_t vec);
+#endif
+
+// com_speeds times
+int time_game;
+int time_frontend; // renderer frontend time
+int time_backend; // renderer backend time
+
+int com_frameTime;
+int com_frameNumber;
+
+bool com_errorEntered = false;
+bool com_fullyInitialized = false;
+bool com_gameRestarting = false;
+
+char com_errorMessage[MAXPRINTMSG];
+
+void Com_WriteConfig_f( void );
+void CIN_CloseAllVideos( void );
+
+//============================================================================
+
+static char* rd_buffer;
+static unsigned 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 bool opening_qconsole = false;
+
+
+ va_start (argptr,fmt);
+ Q_vsnprintf (msg, sizeof(msg), fmt, argptr);
+ va_end (argptr);
+
+ if ( rd_buffer )
+ {
+ if ((strlen(msg) + strlen(rd_buffer)) > (rd_buffersize - 1))
+ {
+ rd_flush(rd_buffer);
+ *rd_buffer = 0;
+ }
+ Q_strcat(rd_buffer, rd_buffersize, msg);
+ // TTimo nooo .. that would defeat the purpose
+ //rd_flush(rd_buffer);
+ //*rd_buffer = 0;
+ return;
+ }
+
+#ifndef DEDICATED
+ CL_ConsolePrint( msg );
+#endif
+
+ Q_StripIndentMarker( msg );
+
+ // echo to dedicated console and early console
+ Sys_Print( msg );
+
+ // logfile
+ if ( com_logfile && com_logfile->integer ) {
+ // TTimo: only open the qconsole.log if the filesystem is in an initialized state
+ // also, avoid recursing in the qconsole.log opening (i.e. if fs_debug is on)
+ if ( !logfile && FS_Initialized() && !opening_qconsole) {
+ struct tm *newtime;
+ time_t aclock;
+
+ opening_qconsole = true;
+
+ 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 = false;
+ }
+ 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;
+
+ va_start(argptr,fmt);
+ Q_vsnprintf(msg, sizeof(msg), fmt, argptr);
+ va_end(argptr);
+
+ Com_Printf("%s", msg);
+}
+
+/*
+=============
+Com_Error
+
+Both client and server can use this, and it will
+do the appropriate thing.
+=============
+*/
+void QDECL Com_Error( int code, const char *fmt, ... )
+{
+ va_list argptr;
+ static int lastErrorTime;
+ static int errorCount;
+ int currentTime;
+
+ if(com_errorEntered)
+ Sys_Error("recursive error after: %s", com_errorMessage);
+
+ com_errorEntered = true;
+
+ Cvar_Set("com_errorCode", va("%i", code));
+
+ // when we are running automated scripts, make sure we
+ // know if anything failed
+ if ( com_buildScript && com_buildScript->integer )
+ code = ERR_FATAL;
+
+ // if we are getting a solid stream of ERR_DROP, do an ERR_FATAL
+ currentTime = Sys_Milliseconds();
+ if ( currentTime - lastErrorTime < 100 )
+ {
+ if ( ++errorCount > 3 )
+ code = ERR_FATAL;
+ }
+ else
+ {
+ errorCount = 0;
+ }
+ lastErrorTime = currentTime;
+
+ va_start(argptr,fmt);
+ Q_vsnprintf(com_errorMessage, sizeof(com_errorMessage),fmt,argptr);
+ va_end(argptr);
+
+ if ( code != ERR_DISCONNECT )
+ Cvar_Set("com_errorMessage", com_errorMessage);
+
+ if (code == ERR_DISCONNECT || code == ERR_SERVERDISCONNECT)
+ {
+ VM_Forced_Unload_Start();
+ SV_Shutdown( "Server disconnected" );
+ CL_Disconnect( true );
+ CL_FlushMemory( );
+ VM_Forced_Unload_Done();
+ // make sure we can get at our local stuff
+ FS_PureServerSetLoadedPaks("", "");
+ com_errorEntered = false;
+ longjmp (abortframe, -1);
+ }
+ else if (code == ERR_DROP || code == ERR_RECONNECT)
+ {
+ Com_Printf ("********************\nERROR: %s\n********************\n", com_errorMessage);
+ VM_Forced_Unload_Start();
+ SV_Shutdown(va("Server crashed: %s", com_errorMessage));
+ CL_Disconnect( true );
+ CL_FlushMemory( );
+ VM_Forced_Unload_Done();
+ FS_PureServerSetLoadedPaks("", "");
+ com_errorEntered = false;
+
+ static int reconnectCount = 0;
+ if ( code == ERR_RECONNECT && reconnectCount <= 0 )
+ {
+ reconnectCount++;
+ Cbuf_AddText("reconnect\n");
+ }
+ else
+ {
+ reconnectCount = 0;
+ }
+
+ longjmp(abortframe, -1);
+ }
+ else
+ {
+ VM_Forced_Unload_Start();
+ CL_Shutdown(va("Client fatal crashed: %s", com_errorMessage), true, true);
+ SV_Shutdown(va("Server fatal crashed: %s", com_errorMessage));
+ VM_Forced_Unload_Done();
+ }
+
+ 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 Engine_Exit(const char* p )
+{
+ // don't try to shutdown if we are in a recursive error
+ if ( !com_errorEntered )
+ {
+ // Some VMs might execute "quit" command directly,
+ // which would trigger an unload of active VM error.
+ // Sys_Quit will kill this process anyways, so
+ // a corrupt call stack makes no difference
+ VM_Forced_Unload_Start();
+ SV_Shutdown(p[0] ? p : "Server quit");
+ CL_Shutdown(p[0] ? p : "Client quit", true, true);
+ VM_Forced_Unload_Done();
+ Com_Shutdown();
+ FS_Shutdown(true);
+ }
+ Sys_Quit ();
+}
+
+void Com_Quit_f( void )
+{
+ char *p = Cmd_Args();
+ Engine_Exit(p);
+}
+
+
+
+/*
+============================================================================
+
+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[0] == '+' && !inq) || commandLine[0] == '\n' || commandLine[0] == '\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
+===================
+*/
+bool Com_SafeMode( void )
+{
+ for ( int 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 true;
+ }
+ }
+
+ return false;
+}
+
+/*
+===============
+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 )
+{
+ for (int i = 0 ; i < com_numConsoleLines ; i++)
+ {
+ Cmd_TokenizeString( com_consoleLines[i] );
+ if ( strcmp( Cmd_Argv(0), "set" ) ) {
+ continue;
+ }
+
+ const char* s = Cmd_Argv(1);
+ if(!match || !strcmp(s, match))
+ {
+ if(Cvar_Flags(s) == CVAR_NONEXISTENT)
+ Cvar_Get(s, Cmd_ArgsFrom(2), CVAR_USER_CREATED);
+ else
+ Cvar_Set2(s, Cmd_ArgsFrom(2), false);
+ }
+ }
+}
+
+/*
+=================
+Com_AddStartupCommands
+
+Adds command line parameters as script statements
+Commands are seperated by + signs
+
+Returns true if any late commands were added
+=================
+*/
+bool Com_AddStartupCommands( void )
+{
+ bool added = false;
+
+ // quote every token, so args with semicolons can work
+ for ( int i = 0 ; i < com_numConsoleLines ; i++)
+ {
+ if ( !com_consoleLines[i] || !com_consoleLines[i][0] )
+ continue;
+
+ // set commands already added with Com_StartupVariable
+ if ( !Q_stricmpn(com_consoleLines[i], "set ", 4) )
+ continue;
+
+ added = true;
+ Cbuf_AddText( com_consoleLines[i] );
+ Cbuf_AddText( "\n" );
+ }
+
+ return added;
+}
+
+//============================================================================
+
+void Info_Print( const char *s )
+{
+ char key[BIG_INFO_KEY];
+ char value[BIG_INFO_VALUE];
+ char* o;
+ int l;
+
+ if (*s == '\\')
+ s++;
+
+ while (*s)
+ {
+ o = key;
+ while (*s && *s != '\\')
+ *o++ = *s++;
+
+ l = o - key;
+ if (l < 20)
+ {
+ ::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(const 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 false;
+ name = ptr + strlen(buf);
+ }
+ }
+ else if (*filter == '?') {
+ filter++;
+ name++;
+ }
+ else if (*filter == '[' && *(filter+1) == '[') {
+ filter++;
+ }
+ else if (*filter == '[') {
+ filter++;
+ found = false;
+ 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 = true;
+ }
+ else {
+ if (toupper(*name) >= toupper(*filter) &&
+ toupper(*name) <= toupper(*(filter+2))) found = true;
+ }
+ filter += 3;
+ }
+ else {
+ if (casesensitive) {
+ if (*filter == *name) found = true;
+ }
+ else {
+ if (toupper(*filter) == toupper(*name)) found = true;
+ }
+ filter++;
+ }
+ }
+ if (!found) return false;
+ while(*filter) {
+ if (*filter == ']' && *(filter+1) != ']') break;
+ filter++;
+ }
+ filter++;
+ name++;
+ }
+ else {
+ if (casesensitive) {
+ if (*filter != *name) return false;
+ }
+ else {
+ if (toupper(*filter) != toupper(*name)) return false;
+ }
+ filter++;
+ name++;
+ }
+ }
+ return true;
+}
+
+/*
+============
+Com_FilterPath
+============
+*/
+int Com_FilterPath(const char *filter, char *name, int casesensitive)
+{
+ int i;
+ char new_filter[MAX_QPATH];
+ char new_name[MAX_QPATH];
+
+ for (i = 0; i < MAX_QPATH-1 && filter[i]; i++) {
+ if ( filter[i] == '\\' || filter[i] == ':' ) {
+ new_filter[i] = '/';
+ }
+ else {
+ new_filter[i] = filter[i];
+ }
+ }
+ new_filter[i] = '\0';
+ for (i = 0; i < MAX_QPATH-1 && name[i]; i++) {
+ if ( name[i] == '\\' || name[i] == ':' ) {
+ new_name[i] = '/';
+ }
+ else {
+ new_name[i] = name[i];
+ }
+ }
+ new_name[i] = '\0';
+ return Com_Filter(new_filter, new_name, casesensitive);
+}
+
+/*
+================
+Com_RealTime
+================
+*/
+int Com_RealTime(qtime_t *qtime)
+{
+ time_t t;
+ struct tm *tms;
+
+ t = time(NULL);
+ if (!qtime)
+ return t;
+ tms = localtime(&t);
+ if (tms) {
+ qtime->tm_sec = tms->tm_sec;
+ qtime->tm_min = tms->tm_min;
+ qtime->tm_hour = tms->tm_hour;
+ qtime->tm_mday = tms->tm_mday;
+ qtime->tm_mon = tms->tm_mon;
+ qtime->tm_year = tms->tm_year;
+ qtime->tm_wday = tms->tm_wday;
+ qtime->tm_yday = tms->tm_yday;
+ qtime->tm_isdst = tms->tm_isdst;
+ }
+ return t;
+}
+
+/*
+==============================================================================
+
+ZONE MEMORY ALLOCATION
+
+There is never any space between memblocks, and there will never be two
+contiguous free memblocks.
+
+The rover can be left pointing at a non-empty block
+
+The zone calls are pretty much only used for small strings and structures,
+all big things are allocated on the hunk.
+==============================================================================
+*/
+
+#define ZONEID 0x1d4a11
+#define MINFRAGMENT 64
+
+typedef struct zonedebug_s {
+ const char *label;
+ const 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_Printf(S_COLOR_YELLOW "Z_Free: NULL pointer" );
+ return;
+ }
+
+ 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...
+ ::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;
+ }
+}
+
+
+/*
+================
+Z_FreeTags
+================
+*/
+void Z_FreeTags( int tag )
+{
+ memzone_t *zone;
+
+ if ( tag == TAG_SMALL )
+ {
+ zone = smallzone;
+ }
+ else
+ {
+ zone = mainzone;
+ }
+ // use the rover as our pointer, because
+ // Z_Free automatically adjusts it
+ zone->rover = zone->blocklist.next;
+ do {
+ if ( zone->rover->tag == tag ) {
+ 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, const char *label, const char *file, int line )
+#else
+void *Z_TagMalloc( int size, int tag )
+#endif
+{
+ int extra;
+ 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;
+
+#ifdef ZONE_DEBUG
+ int allocSize = size;
+#endif
+ //
+ // 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)
+ {
+ // scaned all the way around the list
+#ifdef ZONE_DEBUG
+ Z_LogHeap();
+
+ Com_Error(ERR_FATAL, "Z_Malloc: failed on allocation of %i bytes from the %s zone: %s, line: %d (%s)",
+ size, zone == smallzone ? "small" : "main", file, line, label);
+#else
+ Com_Error(ERR_FATAL, "Z_Malloc: failed on allocation of %i bytes from the %s zone",
+ size, zone == smallzone ? "small" : "main");
+#endif
+ 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; // n_o 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, const char *label, const char *file, int line )
+#else
+void *Z_Malloc( int size )
+#endif
+{
+ void *buf;
+
+ //Z_CheckHeap(); // XXX DEBUG
+
+#ifdef ZONE_DEBUG
+ buf = Z_TagMallocDebug( size, TAG_GENERAL, label, file, line );
+#else
+ buf = Z_TagMalloc( size, TAG_GENERAL );
+#endif
+ ::memset( buf, 0, size );
+
+ return buf;
+}
+
+#ifdef ZONE_DEBUG
+void *S_MallocDebug( int size, const char *label, const 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" );
+
+ if ( block->next->prev != block)
+ Com_Error( ERR_FATAL, "Z_CheckHeap: next block doesn't have proper back link" );
+
+ if ( !block->tag && !block->next->tag )
+ Com_Error( ERR_FATAL, "Z_CheckHeap: two consecutive free blocks" );
+ }
+}
+
+/*
+========================
+Z_LogZoneHeap
+========================
+*/
+void Z_LogZoneHeap( memzone_t *zone, const 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 = numBlocks = 0;
+#ifdef ZONE_DEBUG
+ allocSize = 0;
+#endif
+ Com_sprintf(buf, sizeof(buf), "\r\n================\r\n%s log\r\n================\r\n", name);
+ FS_Write(buf, strlen(buf), logfile);
+
+ for (block = zone->blocklist.next ; block->next != &zone->blocklist; block = block->next)
+ {
+ if (block->tag)
+ {
+#ifdef ZONE_DEBUG
+ ptr = ((char *) block) + sizeof(memblock_t);
+ j = 0;
+ for (i = 0; i < 20 && i < block->d.allocSize; i++)
+ {
+ if (ptr[i] >= 32 && ptr[i] < 127) {
+ dump[j++] = ptr[i];
+ }
+ else {
+ dump[j++] = '_';
+ }
+ }
+ dump[j] = '\0';
+ Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s) [%s]\r\n", block->d.allocSize, block->d.file, block->d.line, block->d.label, dump);
+ FS_Write(buf, strlen(buf), logfile);
+ allocSize += block->d.allocSize;
+#endif
+ size += block->size;
+ numBlocks++;
+ }
+ }
+#ifdef ZONE_DEBUG
+ // subtract debug memory
+ size -= numBlocks * sizeof(zonedebug_t);
+#else
+ allocSize = numBlocks * sizeof(memblock_t); // + 32 bit alignment
+#endif
+ Com_sprintf(buf, sizeof(buf), "%d %s memory in %d blocks\r\n", size, name, numBlocks);
+ FS_Write(buf, strlen(buf), logfile);
+ Com_sprintf(buf, sizeof(buf), "%d %s memory overhead\r\n", size - allocSize, name);
+ FS_Write(buf, strlen(buf), logfile);
+}
+
+/*
+========================
+Z_LogHeap
+========================
+*/
+void Z_LogHeap( void )
+{
+ Z_LogZoneHeap( mainzone, "MAIN" );
+ Z_LogZoneHeap( smallzone, "SMALL" );
+}
+
+// static mem blocks to reduce a lot of small zone overhead
+typedef struct memstatic_s {
+ memblock_t b;
+ byte mem[2];
+} memstatic_t;
+
+memstatic_t emptystring = {
+ {(sizeof(memblock_t)+2 + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'\0', '\0'}
+};
+
+memstatic_t numberstring[] = {
+ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'0', '\0'} },
+ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'1', '\0'} },
+ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'2', '\0'} },
+ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'3', '\0'} },
+ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'4', '\0'} },
+ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'5', '\0'} },
+ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'6', '\0'} },
+ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'7', '\0'} },
+ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'8', '\0'} },
+ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'9', '\0'} }
+};
+
+/*
+========================
+CopyString
+
+NOTE: never write over the memory CopyString returns because
+memory from a memstatic_t might be returned
+========================
+*/
+char *CopyString( const char *in )
+{
+ char *out;
+
+ if (!in[0]) {
+ return ((char *)&emptystring) + sizeof(memblock_t);
+ }
+ else if (!in[1]) {
+ if (in[0] >= '0' && in[0] <= '9') {
+ return ((char *)&numberstring[in[0]-'0']) + sizeof(memblock_t);
+ }
+ }
+ out = (char*)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 {
+ unsigned int magic;
+ unsigned 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;
+ const char *label;
+ const 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;
+ int botlibBytes, rendererBytes;
+ int unused;
+
+ zoneBytes = 0;
+ botlibBytes = 0;
+ rendererBytes = 0;
+ zoneBlocks = 0;
+ for (block = mainzone->blocklist.next ; ; block = block->next) {
+ if ( Cmd_Argc() != 1 ) {
+ Com_Printf ("block:%p size:%7i tag:%3i\n",
+ (void *)block, block->size, block->tag);
+ }
+ if ( block->tag ) {
+ zoneBytes += block->size;
+ zoneBlocks++;
+ if ( block->tag == TAG_BOTLIB ) {
+ botlibBytes += block->size;
+ } else if ( block->tag == TAG_RENDERER ) {
+ rendererBytes += block->size;
+ }
+ }
+
+ if (block->next == &mainzone->blocklist) {
+ break; // all blocks have been hit
+ }
+ if ( (byte *)block + block->size != (byte *)block->next) {
+ Com_Printf ("ERROR: block size does not touch the next block\n");
+ }
+ if ( block->next->prev != block) {
+ Com_Printf ("ERROR: next block doesn't have proper back link\n");
+ }
+ if ( !block->tag && !block->next->tag ) {
+ Com_Printf ("ERROR: two consecutive free blocks\n");
+ }
+ }
+
+ smallZoneBytes = 0;
+ for (block = smallzone->blocklist.next ; ; block = block->next)
+ {
+ if ( block->tag )
+ smallZoneBytes += block->size;
+
+ if (block->next == &smallzone->blocklist)
+ break; // all blocks have been hit
+ }
+
+ Com_Printf( "%8i bytes total hunk\n", s_hunkTotal );
+ Com_Printf( "%8i bytes total zone\n", s_zoneTotal );
+ Com_Printf( "\n" );
+ Com_Printf( "%8i low mark\n", hunk_low.mark );
+ Com_Printf( "%8i low permanent\n", hunk_low.permanent );
+ if ( hunk_low.temp != hunk_low.permanent ) {
+ Com_Printf( "%8i low temp\n", hunk_low.temp );
+ }
+ Com_Printf( "%8i low tempHighwater\n", hunk_low.tempHighwater );
+ Com_Printf( "\n" );
+ Com_Printf( "%8i high mark\n", hunk_high.mark );
+ Com_Printf( "%8i high permanent\n", hunk_high.permanent );
+ if ( hunk_high.temp != hunk_high.permanent ) {
+ Com_Printf( "%8i high temp\n", hunk_high.temp );
+ }
+ Com_Printf( "%8i high tempHighwater\n", hunk_high.tempHighwater );
+ Com_Printf( "\n" );
+ Com_Printf( "%8i total hunk in use\n", hunk_low.permanent + hunk_high.permanent );
+ unused = 0;
+ if ( hunk_low.tempHighwater > hunk_low.permanent ) {
+ unused += hunk_low.tempHighwater - hunk_low.permanent;
+ }
+ if ( hunk_high.tempHighwater > hunk_high.permanent ) {
+ unused += hunk_high.tempHighwater - hunk_high.permanent;
+ }
+ Com_Printf( "%8i unused highwater\n", unused );
+ Com_Printf( "\n" );
+ Com_Printf( "%8i bytes in %i zone blocks\n", zoneBytes, zoneBlocks );
+ Com_Printf( " %8i bytes in dynamic botlib\n", botlibBytes );
+ Com_Printf( " %8i bytes in dynamic renderer\n", rendererBytes );
+ Com_Printf( " %8i bytes in dynamic other\n", zoneBytes - ( botlibBytes + rendererBytes ) );
+ Com_Printf( " %8i bytes in small Zone memory\n", smallZoneBytes );
+}
+
+/*
+===============
+Com_TouchMemory
+
+Touch all known used data to make sure it is paged in
+===============
+*/
+void Com_TouchMemory( void )
+{
+ int start, end;
+ int i, j;
+ int sum;
+ memblock_t *block;
+
+ Z_CheckHeap();
+
+ start = Sys_Milliseconds();
+
+ sum = 0;
+
+ j = hunk_low.permanent >> 2;
+ for ( i = 0 ; i < j ; i+=64 ) { // only need to touch each page
+ sum += ((int *)s_hunkData)[i];
+ }
+
+ i = ( s_hunkTotal - hunk_high.permanent ) >> 2;
+ j = hunk_high.permanent >> 2;
+ for ( ; i < j ; i+=64 ) { // only need to touch each page
+ sum += ((int *)s_hunkData)[i];
+ }
+
+ for (block = mainzone->blocklist.next ; ; block = block->next) {
+ if ( block->tag ) {
+ j = block->size >> 2;
+ for ( i = 0 ; i < j ; i+=64 ) { // only need to touch each page
+ sum += ((int *)block)[i];
+ }
+ }
+ if ( block->next == &mainzone->blocklist ) {
+ break; // all blocks have been hit
+ }
+ }
+
+ end = Sys_Milliseconds();
+
+ Com_Printf( "Com_TouchMemory: %i msec\n", end - start );
+}
+
+
+
+/*
+=================
+Com_InitZoneMemory
+=================
+*/
+void Com_InitSmallZoneMemory( void )
+{
+ s_smallZoneTotal = (512 * 1024);
+ smallzone = (memzone_t*)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 );
+}
+
+void Com_InitZoneMemory( void )
+{
+ // Please note: com_zoneMegs can only be set on the command line, and not
+ // in q3config.cfg or Com_StartupVariable, as they haven't been executed by
+ // this point. It's a chicken and egg problem. We need the memory manager
+ // configured to handle those places where you would configure the memory
+ // manager.
+
+ // allocate the random block zone
+ cvar_t* cv = Cvar_Get( "com_zoneMegs", DEF_COMZONEMEGS_S, CVAR_LATCH | CVAR_ARCHIVE );
+
+ if ( cv->integer < DEF_COMZONEMEGS ) {
+ s_zoneTotal = 1024 * 1024 * DEF_COMZONEMEGS;
+ } else {
+ s_zoneTotal = cv->integer * 1024 * 1024;
+ }
+
+ mainzone = (memzone_t*)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)
+{
+ char buf[4096];
+
+ if (!logfile || !FS_Initialized())
+ return;
+
+ int size = 0;
+ int numBlocks = 0;
+
+ Com_sprintf(buf, sizeof(buf), "\r\n================\r\nHunk log\r\n================\r\n");
+ FS_Write(buf, strlen(buf), logfile);
+
+ for ( hunkblock_t* 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)
+{
+ char buf[4096];
+
+ if (!logfile || !FS_Initialized())
+ return;
+
+ for ( hunkblock_t* block = hunkblocks ; block; block = block->next )
+ block->printed = false;
+
+ int size = 0;
+ int numBlocks = 0;
+
+ Com_sprintf(buf, sizeof(buf), "\r\n================\r\nHunk Small log\r\n================\r\n");
+ FS_Write(buf, strlen(buf), logfile);
+
+ for ( hunkblock_t* block = hunkblocks; block; block = block->next )
+ {
+ if (block->printed)
+ continue;
+
+ int locsize = block->size;
+ for ( hunkblock_t* 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 = true;
+ }
+#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_InitHunkZoneMemory
+=================
+*/
+void Com_InitHunkMemory( void )
+{
+ cvar_t *cv;
+ int nMinAlloc;
+ const 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 );
+ Cvar_SetDescription(cv, "The size of the hunk memory segment");
+
+ // if we are not dedicated min allocation is 56, otherwise min is 1
+ if (com_dedicated && com_dedicated->integer)
+ {
+ nMinAlloc = MIN_DEDICATED_COMHUNKMEGS;
+ pMsg = "Minimum com_hunkMegs for a dedicated server is %i, allocating %i megs.\n";
+ }
+ else
+ {
+ nMinAlloc = MIN_COMHUNKMEGS;
+ pMsg = "Minimum com_hunkMegs is %i, allocating %i megs.\n";
+ }
+
+ if ( cv->integer < nMinAlloc )
+ {
+ s_hunkTotal = 1024 * 1024 * nMinAlloc;
+ Com_Printf(pMsg, nMinAlloc, s_hunkTotal / (1024 * 1024));
+ }
+ else
+ {
+ s_hunkTotal = cv->integer * 1024 * 1024;
+ }
+
+ s_hunkData = (byte*)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 = hunk_low.permanent > hunk_low.temp ? hunk_low.permanent : hunk_low.temp;
+ int 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
+=================
+*/
+bool Hunk_CheckMark( void )
+{
+ if( hunk_low.mark || hunk_high.mark )
+ return true;
+ return false;
+}
+
+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, const char *label, const 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();
+
+ Com_Error(ERR_DROP, "Hunk_Alloc failed on %i: %s, line: %d (%s)", size, file, line, label);
+#else
+ Com_Error(ERR_DROP, "Hunk_Alloc failed on %i", size);
+#endif
+ }
+
+ 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;
+
+ ::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 )
+{
+ // 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;
+ }
+
+ hunkHeader_t* 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;
+}
+
+/*
+===================================================================
+
+EVENTS AND JOURNALING
+
+In addition to these events, .cfg files are also copied to the
+journaled file
+===================================================================
+*/
+
+#define MAX_PUSHED_EVENTS 1024
+static int com_pushedEventsHead = 0;
+static int com_pushedEventsTail = 0;
+static sysEvent_t com_pushedEvents[MAX_PUSHED_EVENTS];
+
+/*
+=================
+Com_InitJournaling
+=================
+*/
+void Com_InitJournaling( void )
+{
+ Com_StartupVariable( "journal" );
+ com_journal = Cvar_Get ("journal", "0", CVAR_INIT);
+ if ( !com_journal->integer ) {
+ return;
+ }
+
+ if ( com_journal->integer == 1 ) {
+ Com_Printf( "Journaling events\n");
+ com_journalFile = FS_FOpenFileWrite( "journal.dat" );
+ com_journalDataFile = FS_FOpenFileWrite( "journaldata.dat" );
+ } else if ( com_journal->integer == 2 ) {
+ Com_Printf( "Replaying journaled events\n");
+ FS_FOpenFileRead( "journal.dat", &com_journalFile, true );
+ FS_FOpenFileRead( "journaldata.dat", &com_journalDataFile, true );
+ }
+
+ if ( !com_journalFile || !com_journalDataFile ) {
+ Cvar_Set( "com_journal", "0" );
+ com_journalFile = 0;
+ com_journalDataFile = 0;
+ Com_Printf( "Couldn't open journal files\n" );
+ }
+}
+
+/*
+========================================================================
+
+EVENT LOOP
+
+========================================================================
+*/
+
+#define MAX_QUEUED_EVENTS 256
+#define MASK_QUEUED_EVENTS ( MAX_QUEUED_EVENTS - 1 )
+
+static sysEvent_t eventQueue[ MAX_QUEUED_EVENTS ];
+static int eventHead = 0;
+static int eventTail = 0;
+
+/*
+================
+Com_QueueEvent
+
+A time of 0 will get the current time
+Ptr should either be null, or point to a block of data that can
+be freed by the game later.
+================
+*/
+void Com_QueueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr )
+{
+ sysEvent_t *ev;
+
+ // combine mouse movement with previous mouse event
+ if ( type == SE_MOUSE && eventHead != eventTail )
+ {
+ ev = &eventQueue[ ( eventHead + MAX_QUEUED_EVENTS - 1 ) & MASK_QUEUED_EVENTS ];
+
+ if ( ev->evType == SE_MOUSE )
+ {
+ ev->evValue += value;
+ ev->evValue2 += value2;
+ return;
+ }
+ }
+
+ ev = &eventQueue[ eventHead & MASK_QUEUED_EVENTS ];
+
+ if ( eventHead - eventTail >= MAX_QUEUED_EVENTS )
+ {
+ Com_Printf("Com_QueueEvent: overflow\n");
+ // we are discarding an event, but don't leak memory
+ if ( ev->evPtr )
+ {
+ Z_Free( ev->evPtr );
+ }
+ eventTail++;
+ }
+
+ eventHead++;
+
+ if ( time == 0 )
+ {
+ time = Sys_Milliseconds();
+ }
+
+ ev->evTime = time;
+ ev->evType = type;
+ ev->evValue = value;
+ ev->evValue2 = value2;
+ ev->evPtrLength = ptrLength;
+ ev->evPtr = ptr;
+}
+
+/*
+================
+Com_GetSystemEvent
+
+================
+*/
+sysEvent_t Com_GetSystemEvent( void )
+{
+ sysEvent_t ev;
+ char *s;
+
+ // return if we have data
+ if ( eventHead > eventTail )
+ {
+ eventTail++;
+ return eventQueue[ ( eventTail - 1 ) & MASK_QUEUED_EVENTS ];
+ }
+
+ // check for console commands
+ s = Sys_ConsoleInput();
+ if ( s )
+ {
+ char *b;
+ int len;
+
+ len = strlen( s ) + 1;
+ b = (char*)Z_Malloc( len );
+ strcpy( b, s );
+ Com_QueueEvent( 0, SE_CONSOLE, 0, 0, len, b );
+ }
+
+ // return if we have data
+ if ( eventHead > eventTail )
+ {
+ eventTail++;
+ return eventQueue[ ( eventTail - 1 ) & MASK_QUEUED_EVENTS ];
+ }
+
+ // create an empty event to return
+ memset( &ev, 0, sizeof( ev ) );
+ ev.evTime = Sys_Milliseconds();
+
+ return ev;
+}
+
+/*
+=================
+Com_GetRealEvent
+=================
+*/
+sysEvent_t Com_GetRealEvent( void )
+{
+ sysEvent_t ev;
+
+ // either get an event from the system or the journal file
+ if ( com_journal->integer == 2 )
+ {
+ int r = FS_Read( &ev, sizeof(ev), com_journalFile );
+ if ( r != sizeof(ev) )
+ Com_Error( ERR_FATAL, "Error reading from journal file" );
+
+ if ( ev.evPtrLength )
+ {
+ ev.evPtr = Z_Malloc( ev.evPtrLength );
+ r = FS_Read( ev.evPtr, ev.evPtrLength, com_journalFile );
+ if ( r != ev.evPtrLength ) {
+ Com_Error( ERR_FATAL, "Error reading from journal file" );
+ }
+ }
+ }
+ else
+ {
+ ev = Com_GetSystemEvent();
+
+ // write the journal value out if needed
+ if ( com_journal->integer == 1 )
+ {
+ int r = FS_Write( &ev, sizeof(ev), com_journalFile );
+ if ( r != sizeof(ev) )
+ Com_Error( ERR_FATAL, "Error writing to journal file" );
+
+ if ( ev.evPtrLength )
+ {
+ r = FS_Write( ev.evPtr, ev.evPtrLength, com_journalFile );
+ if ( r != ev.evPtrLength )
+ Com_Error( ERR_FATAL, "Error writing to journal file" );
+ }
+ }
+ }
+
+ return ev;
+}
+
+
+/*
+=================
+Com_InitPushEvent
+=================
+*/
+void Com_InitPushEvent( void ) {
+ // clear the static buffer array
+ // this requires SE_NONE to be accepted as a valid but NOP event
+ memset( com_pushedEvents, 0, sizeof(com_pushedEvents) );
+ // reset counters while we are at it
+ // beware: GetEvent might still return an SE_NONE from the buffer
+ com_pushedEventsHead = 0;
+ com_pushedEventsTail = 0;
+}
+
+
+/*
+=================
+Com_PushEvent
+=================
+*/
+void Com_PushEvent( sysEvent_t *event )
+{
+ static int printedWarning = 0;
+ sysEvent_t *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 = true;
+ Com_Printf("WARNING: Com_PushEvent overflow\n");
+ }
+
+ if ( ev->evPtr )
+ Z_Free( ev->evPtr );
+
+ com_pushedEventsTail++;
+ }
+ else
+ {
+ printedWarning = false;
+ }
+
+ *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 = 0;
+ if ( com_speeds->integer )
+ t1 = Sys_Milliseconds();
+
+ SV_PacketEvent(*evFrom, buf);
+
+ if ( com_speeds->integer )
+ {
+ int t2 = Sys_Milliseconds();
+ int 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));
+
+ for (;;)
+ {
+ 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 );
+
+ // if the server just shut down, flush the events
+ while ( NET_GetLoopPacket( NS_SERVER, &evFrom, &buf ) )
+ if ( com_sv_running->integer )
+ Com_RunAndTimeServerPacket( &evFrom, &buf );
+
+ return ev.evTime;
+ }
+
+ switch(ev.evType)
+ {
+ case SE_KEY:
+ CL_KeyEvent( ev.evValue, (bool)ev.evValue2, ev.evTime );
+ break;
+ case SE_CHAR:
+ CL_CharEvent( ev.evValue );
+ break;
+ case SE_MOUSE:
+ CL_MouseEvent( ev.evValue, ev.evValue2, ev.evTime );
+ break;
+ case SE_JOYSTICK_AXIS:
+ CL_JoystickEvent( ev.evValue, ev.evValue2, ev.evTime );
+ break;
+ case SE_CONSOLE:
+ Cbuf_AddText( (char *)ev.evPtr );
+ Cbuf_AddText( "\n" );
+ break;
+ default:
+ Com_Error( ERR_FATAL, "Com_EventLoop: bad event type %i", ev.evType );
+ break;
+ }
+
+ // free any block data
+ if ( ev.evPtr )
+ Z_Free( ev.evPtr );
+ }
+
+ return 0; // never reached
+}
+
+/*
+================
+Com_Milliseconds
+
+Can be used for profiling, but will be journaled accurately
+================
+*/
+int Com_Milliseconds(void)
+{
+ sysEvent_t ev;
+ // get events and push them until we get a null event with the current time
+ do {
+ ev = Com_GetRealEvent();
+
+ if ( ev.evType != SE_NONE )
+ Com_PushEvent( &ev );
+
+ } while ( ev.evType != SE_NONE );
+
+ return ev.evTime;
+}
+
+//============================================================================
+
+/*
+=============
+Com_Error_f
+
+Just throw a fatal error to
+test error shutdown procedures
+=============
+*/
+static void __attribute__((__noreturn__)) 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();
+
+ for (;;)
+ {
+ 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 )
+{
+ *( volatile int * )0 = 0x12345678;
+}
+
+/*
+==================
+Com_ExecuteCfg
+
+For controlling environment variables
+==================
+*/
+
+void Com_ExecuteCfg(void)
+{
+ Cbuf_ExecuteText(EXEC_NOW, "exec default.cfg\n");
+ Cbuf_Execute(); // Always execute after exec to prevent text buffer overflowing
+
+ if(!Com_SafeMode())
+ {
+ // skip the q3config.cfg and autoexec.cfg if "safe" is on the command line
+ Cbuf_ExecuteText(EXEC_NOW, "exec " Q3CONFIG_CFG "\n");
+ Cbuf_Execute();
+ Cbuf_ExecuteText(EXEC_NOW, "exec autoexec.cfg\n");
+ Cbuf_Execute();
+ }
+}
+
+/*
+==================
+Com_GameRestart
+
+Change to a new mod properly with cleaning up cvars before switching.
+==================
+*/
+
+void Com_GameRestart(int checksumFeed, bool disconnect)
+{
+ // make sure no recursion can be triggered
+ if(!com_gameRestarting && com_fullyInitialized)
+ {
+ int clWasRunning;
+
+ com_gameRestarting = true;
+ clWasRunning = com_cl_running->integer;
+
+ // Kill server if we have one
+ if(com_sv_running->integer)
+ SV_Shutdown("Game directory changed");
+
+ if(clWasRunning)
+ {
+ if(disconnect)
+ CL_Disconnect(false);
+
+ CL_Shutdown("Game directory changed", disconnect, false);
+ }
+
+ FS_Restart(checksumFeed);
+
+ // Clean out any user and VM created cvars
+ Cvar_Restart(true);
+ Com_ExecuteCfg();
+
+ if(disconnect)
+ {
+ // We don't want to change any network settings if gamedir
+ // change was triggered by a connect to server because the
+ // new network settings might make the connection fail.
+ NET_Restart_f();
+ }
+
+ if(clWasRunning)
+ {
+ CL_Init();
+ CL_StartHunkUsers(false);
+ }
+
+ com_gameRestarting = false;
+ }
+}
+
+/*
+==================
+Com_GameRestart_f
+
+Expose possibility to change current running mod to the user
+==================
+*/
+
+void Com_GameRestart_f(void)
+{
+ if(!FS_FilenameCompare(Cmd_Argv(1), BASEGAME))
+ {
+ // This is the standard base game. Servers and clients should
+ // use "" and not the standard basegame name because this messes
+ // up pak file negotiation and lots of other stuff
+
+ Cvar_Set("fs_game", "");
+ }
+ else
+ Cvar_Set("fs_game", Cmd_Argv(1));
+
+ Com_GameRestart(0, true);
+}
+
+static void Com_DetectAltivec(void)
+{
+ // Only detect if user hasn't forcibly disabled it.
+ if ( com_altivec->integer )
+ {
+ static bool altivec = false;
+ static bool detected = false;
+
+ if (!detected)
+ {
+ altivec = ( Sys_GetProcessorFeatures( ) & CF_ALTIVEC ) == CF_ALTIVEC;
+ detected = true;
+ }
+
+ if (!altivec)
+ Cvar_Set( "com_altivec", "0" ); // we don't have it! Disable support!
+ }
+}
+
+/*
+=================
+Com_DetectSSE
+Find out whether we have SSE support
+=================
+*/
+
+#if id386 || idx64
+static void Com_DetectSSE(void)
+{
+#if !idx64
+ cpuFeatures_t feat = Sys_GetProcessorFeatures();
+ if(feat & CF_SSE)
+ {
+ if(feat & CF_SSE2)
+ Q_SnapVector = qsnapvectorsse;
+ else
+ Q_SnapVector = qsnapvectorx87;
+#endif
+ Com_Printf("Have SSE support\n");
+#if !idx64
+ }
+ else
+ {
+ Q_SnapVector = qsnapvectorx87;
+
+ Com_Printf("No SSE support on this machine\n");
+ }
+#endif
+}
+
+#else
+
+#define Com_DetectSSE()
+
+#endif
+
+/*
+=================
+Com_InitRand
+Seed the random number generator, if possible with an OS supplied random seed.
+=================
+*/
+static void Com_InitRand(void)
+{
+ unsigned int seed;
+
+ if(Sys_RandomBytes((byte *) &seed, sizeof(seed)))
+ srand(seed);
+ else
+ srand(time(NULL));
+}
+
+/*
+=================
+Com_Init
+=================
+*/
+void Com_Init( char *commandLine )
+{
+ int qport;
+
+ if ( setjmp (abortframe) ) {
+ Sys_Error ("Error during initialization");
+ }
+
+ // Clear queues
+ ::memset( &eventQueue[ 0 ], 0, MAX_QUEUED_EVENTS * sizeof( sysEvent_t ) );
+
+ // initialize the weak pseudo-random number generator for use later.
+ Com_InitRand();
+
+ // do this before anything else decides to push events
+ Com_InitPushEvent();
+
+ Com_InitSmallZoneMemory();
+ Cvar_Init();
+
+ // prepare enough of the subsystems to handle
+ // cvar and command buffer management
+ Com_ParseCommandLine( commandLine );
+
+ //Swap_Init ();
+ Cbuf_Init ();
+
+ Com_DetectSSE();
+
+ // override anything from the config files with command line args
+ Com_StartupVariable( NULL );
+
+ Com_InitZoneMemory();
+ Cmd_Init ();
+
+ // get the developer cvar set as early as possible
+ com_developer = Cvar_Get("developer", "0", CVAR_TEMP);
+
+ // done early so bind command exists
+ CL_InitKeyCommands();
+
+ com_homepath = Cvar_Get("com_homepath", "", CVAR_INIT);
+
+ FS_InitFilesystem ();
+
+ Com_InitJournaling();
+
+ // Add some commands here already so users can use them from config files
+ if (com_developer && com_developer->integer)
+ {
+ Cmd_AddCommand ("error", Com_Error_f);
+ Cmd_AddCommand ("crash", Com_Crash_f);
+ Cmd_AddCommand ("freeze", Com_Freeze_f);
+ }
+ Cmd_AddCommand ("quit", Com_Quit_f);
+ Cmd_AddCommand ("changeVectors", MSG_ReportChangeVectors_f );
+ Cmd_AddCommand ("writeconfig", Com_WriteConfig_f );
+ Cmd_SetCommandCompletionFunc( "writeconfig", Cmd_CompleteCfgName );
+ Cmd_AddCommand("game_restart", Com_GameRestart_f);
+
+ Com_ExecuteCfg();
+
+ // override anything from the config files with command line args
+ Com_StartupVariable( NULL );
+
+ // get dedicated here for proper hunk megs initialization
+#ifdef DEDICATED
+ com_dedicated = Cvar_Get ("dedicated", "1", CVAR_INIT);
+ Cvar_CheckRange( com_dedicated, 1, 2, true );
+#else
+ com_dedicated = Cvar_Get ("dedicated", "0", CVAR_LATCH);
+ Cvar_CheckRange( com_dedicated, 0, 2, true );
+#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_logfile = Cvar_Get ("logfile", "0", CVAR_TEMP );
+
+ com_timescale = Cvar_Get ("timescale", "1", CVAR_CHEAT | CVAR_SYSTEMINFO );
+ com_fixedtime = Cvar_Get ("fixedtime", "0", CVAR_CHEAT);
+ com_showtrace = Cvar_Get ("com_showtrace", "0", CVAR_CHEAT);
+ com_speeds = Cvar_Get ("com_speeds", "0", 0);
+ com_timedemo = Cvar_Get ("timedemo", "0", CVAR_CHEAT);
+ com_cameraMode = Cvar_Get ("com_cameraMode", "0", CVAR_CHEAT);
+
+ cl_paused = Cvar_Get ("cl_paused", "0", CVAR_ROM);
+ sv_paused = Cvar_Get ("sv_paused", "0", CVAR_ROM);
+ cl_packetdelay = Cvar_Get ("cl_packetdelay", "0", CVAR_CHEAT);
+ sv_packetdelay = Cvar_Get ("sv_packetdelay", "0", CVAR_CHEAT);
+ com_sv_running = Cvar_Get ("sv_running", "0", CVAR_ROM);
+ com_cl_running = Cvar_Get ("cl_running", "0", CVAR_ROM);
+ com_buildScript = Cvar_Get( "com_buildScript", "0", 0 );
+ com_ansiColor = Cvar_Get( "com_ansiColor", "0", CVAR_ARCHIVE );
+
+ com_unfocused = Cvar_Get( "com_unfocused", "0", CVAR_ROM );
+ com_maxfpsUnfocused = Cvar_Get( "com_maxfpsUnfocused", "0", CVAR_ARCHIVE );
+ com_minimized = Cvar_Get( "com_minimized", "0", CVAR_ROM );
+ com_maxfpsMinimized = Cvar_Get( "com_maxfpsMinimized", "0", CVAR_ARCHIVE );
+ com_busyWait = Cvar_Get("com_busyWait", "0", CVAR_ARCHIVE);
+ Cvar_Get("com_errorMessage", "", CVAR_ROM | CVAR_NORESTART);
+ Cvar_Get("com_demoErrorMessage", "", CVAR_ROM | CVAR_NORESTART);
+
+ com_version = Cvar_Get ("version", PRODUCT_NAME, CVAR_ROM | CVAR_SERVERINFO );
+ Cvar_Get ("protocol", va("%i", PROTOCOL_VERSION), CVAR_SERVERINFO | CVAR_ROM);
+ com_gamename = Cvar_Get("com_gamename", GAMENAME_FOR_MASTER, CVAR_SERVERINFO | CVAR_INIT);
+
+ Sys_Init();
+
+ // Pick a random port value
+ Com_RandomBytes( (byte*)&qport, sizeof(int) );
+ Netchan_Init( qport & 0xffff );
+
+ VM_Init();
+ Crypto_Init();
+ SV_Init();
+
+ com_dedicated->modified = false;
+#ifndef DEDICATED
+ CL_Init();
+#endif
+
+ // set com_frameTime so that if a map is started on the
+ // command line it will still be able to count on com_frameTime
+ // being random enough for a serverid
+ com_frameTime = Com_Milliseconds();
+
+ // add + commands from command line
+ if ( !Com_AddStartupCommands() ) {
+#ifdef CINEMATICS_LOGO
+ // if the user didn't give any commands, run default action
+ if ( !com_dedicated->integer ) {
+ Cbuf_AddText ("cinematic splash.RoQ\n");
+ }
+#endif
+ }
+
+ // start in full screen ui mode
+ Cvar_Set("r_uiFullScreen", "1");
+
+ CL_StartHunkUsers( false );
+
+ com_fullyInitialized = true;
+
+ // always set the cvar, but only print the info if it makes sense.
+ Com_DetectAltivec();
+#if idppc
+ Com_Printf ("Altivec support is %s\n", com_altivec->integer ? "enabled" : "disabled");
+#endif
+
+ com_pipefile = Cvar_Get( "com_pipefile", "", CVAR_ARCHIVE|CVAR_LATCH );
+ if( com_pipefile->string[0] )
+ {
+ pipefile = FS_FCreateOpenPipeFile( com_pipefile->string );
+ }
+
+ Com_Printf ("--- Common Initialization Complete ---\n");
+}
+
+/*
+===============
+Com_ReadFromPipe
+
+Read whatever is in com_pipefile, if anything, and execute it
+===============
+*/
+void Com_ReadFromPipe( void )
+{
+ static char buf[MAX_STRING_CHARS];
+ static int accu = 0;
+ int read;
+
+ if( !pipefile )
+ return;
+
+ while( ( read = FS_Read( buf + accu, sizeof( buf ) - accu - 1, pipefile ) ) > 0 )
+ {
+ char *brk = NULL;
+
+ for( int i = accu; i < accu + read; ++i )
+ {
+ if( buf[ i ] == '\0' )
+ buf[ i ] = '\n';
+ if( buf[ i ] == '\n' || buf[ i ] == '\r' )
+ brk = &buf[ i + 1 ];
+ }
+ buf[ accu + read ] = '\0';
+
+ accu += read;
+
+ if( brk )
+ {
+ char tmp = *brk;
+ *brk = '\0';
+ Cbuf_ExecuteText( EXEC_APPEND, buf );
+ *brk = tmp;
+
+ accu -= brk - buf;
+ memmove( buf, brk, accu + 1 );
+ }
+ else if( accu >= sizeof( buf ) - 1 ) // full
+ {
+ Cbuf_ExecuteText( EXEC_APPEND, buf );
+ accu = 0;
+ }
+ }
+}
+
+
+//==================================================================
+
+void Com_WriteConfigToFile( const char *filename )
+{
+ fileHandle_t f;
+
+ f = FS_FOpenFileWrite( filename );
+ if ( !f )
+ {
+ Com_Printf("Couldn't write %s.\n", filename );
+ return;
+ }
+
+ FS_Printf(f, "// generated by tremulous, do not modify\n");
+
+ Key_WriteBindings(f);
+ Cvar_WriteVariables(f);
+ FS_FCloseFile(f);
+}
+
+
+/*
+===============
+Com_WriteConfiguration
+
+Writes key bindings and archived cvars to config file if modified
+===============
+*/
+void Com_WriteConfiguration( void )
+{
+ // if we are quiting without fully initializing, make sure
+ // we don't write out anything
+ if ( !com_fullyInitialized )
+ return;
+
+ if ( !(cvar_modifiedFlags & CVAR_ARCHIVE) )
+ return;
+
+ cvar_modifiedFlags &= ~CVAR_ARCHIVE;
+
+ Com_WriteConfigToFile(Q3CONFIG_CFG);
+}
+
+/*
+===============
+Com_WriteConfig_f
+
+Write the config file to a specific name
+===============
+*/
+void Com_WriteConfig_f( void )
+{
+ char filename[MAX_QPATH];
+
+ if ( Cmd_Argc() != 2 ) {
+ Com_Printf( "Usage: writeconfig <filename>\n" );
+ return;
+ }
+
+ Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) );
+ COM_DefaultExtension( filename, sizeof( filename ), ".cfg" );
+
+ if ( !COM_CompareExtension(filename, ".cfg") )
+ {
+ Com_Printf("Com_WriteConfig_f: Only the \".cfg\" extension is supported by this command!\n");
+ return;
+ }
+
+ 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_TimeVal
+=================
+*/
+
+int Com_TimeVal(int minMsec)
+{
+ int timeVal = Sys_Milliseconds() - com_frameTime;
+
+ if(timeVal >= minMsec)
+ timeVal = 0;
+ else
+ timeVal = minMsec - timeVal;
+
+ return timeVal;
+}
+
+/*
+=================
+Com_Frame
+=================
+*/
+void Com_Frame( void )
+{
+
+ int msec, minMsec;
+ int timeVal, timeValSV;
+ static int lastTime = 0, bias = 0;
+
+ int timeBeforeFirstEvents;
+ int timeBeforeServer;
+ int timeBeforeEvents;
+ int timeBeforeClient;
+ int timeAfter;
+
+ if ( setjmp(abortframe) )
+ return; // an ERR_DROP was thrown
+
+ timeBeforeFirstEvents =0;
+ timeBeforeServer =0;
+ timeBeforeEvents =0;
+ timeBeforeClient = 0;
+ timeAfter = 0;
+
+ // write config file if anything changed
+ Com_WriteConfiguration();
+
+ //
+ // main event loop
+ //
+ if ( com_speeds->integer )
+ timeBeforeFirstEvents = Sys_Milliseconds();
+
+ // Figure out how much time we have
+ if ( !com_timedemo->integer )
+ {
+ if(com_dedicated->integer)
+ {
+ minMsec = SV_FrameMsec();
+ }
+ else
+ {
+ if(com_minimized->integer && com_maxfpsMinimized->integer > 0)
+ minMsec = 1000 / com_maxfpsMinimized->integer;
+ else if(com_unfocused->integer && com_maxfpsUnfocused->integer > 0)
+ minMsec = 1000 / com_maxfpsUnfocused->integer;
+ else if(com_maxfps->integer > 0)
+ minMsec = 1000 / com_maxfps->integer;
+ else
+ minMsec = 1;
+
+ timeVal = com_frameTime - lastTime;
+ bias += timeVal - minMsec;
+
+ if(bias > minMsec)
+ bias = minMsec;
+
+ // Adjust minMsec if previous frame took too long to render so
+ // that framerate is stable at the requested value.
+ minMsec -= bias;
+ }
+ }
+ else
+ {
+ minMsec = 1;
+ }
+
+ do {
+ if ( com_sv_running->integer )
+ {
+ timeValSV = SV_SendQueuedPackets();
+ timeVal = Com_TimeVal(minMsec);
+
+ if ( timeValSV < timeVal )
+ timeVal = timeValSV;
+ }
+ else
+ {
+ timeVal = Com_TimeVal(minMsec);
+ }
+
+ if ( com_busyWait->integer || timeVal < 1 )
+ NET_Sleep(0);
+ else
+ NET_Sleep(timeVal - 1);
+ } while( Com_TimeVal(minMsec) );
+
+ IN_Frame();
+
+ lastTime = com_frameTime;
+ com_frameTime = Com_EventLoop();
+
+ msec = com_frameTime - lastTime;
+
+ Cbuf_Execute();
+
+ if ( com_altivec->modified )
+ {
+ Com_DetectAltivec();
+ com_altivec->modified = false;
+ }
+
+ // mess with msec if needed
+ msec = Com_ModifyMsec(msec);
+
+ //
+ // server side
+ //
+ if ( com_speeds->integer )
+ timeBeforeServer = Sys_Milliseconds();
+
+ SV_Frame(msec);
+
+ // if "dedicated" has been modified, start up
+ // or shut down the client system.
+ // Do this after the server may have started,
+ // but before the client tries to auto-connect
+ if ( com_dedicated->modified )
+ {
+ // get the latched value
+ Cvar_Get("dedicated", "0", 0);
+ com_dedicated->modified = false;
+
+ if ( !com_dedicated->integer )
+ {
+ SV_Shutdown("dedicated set to 0");
+ CL_FlushMemory();
+ }
+ }
+
+#ifndef DEDICATED
+ //
+ // client system
+ //
+ //
+ // run event loop a second time to get server to client packets
+ // without a frame of latency
+ //
+ if ( com_speeds->integer )
+ timeBeforeEvents = Sys_Milliseconds();
+
+ Com_EventLoop();
+ Cbuf_Execute();
+
+ //
+ // client side
+ //
+ if ( com_speeds->integer )
+ timeBeforeClient = Sys_Milliseconds();
+
+ CL_Frame(msec);
+
+ if ( com_speeds->integer )
+ timeAfter = Sys_Milliseconds();
+#else
+ if ( com_speeds->integer )
+ {
+ timeAfter = Sys_Milliseconds();
+ timeBeforeEvents = timeAfter;
+ timeBeforeClient = timeAfter;
+ }
+#endif
+
+ NET_FlushPacketQueue();
+
+ //
+ // report timing information
+ //
+ if ( com_speeds->integer )
+ {
+ int all = timeAfter - timeBeforeServer;
+ int sv = timeBeforeEvents - timeBeforeServer;
+ int ev = timeBeforeServer - timeBeforeFirstEvents + timeBeforeClient - timeBeforeEvents;
+ int cl = timeAfter - timeBeforeClient;
+
+ sv -= time_game;
+ cl -= time_frontend + time_backend;
+
+ Com_Printf("frame:%i all:%3i sv:%3i ev:%3i cl:%3i gm:%3i rf:%3i bk:%3i\n",
+ com_frameNumber, all, sv, ev, cl, time_game, time_frontend, time_backend );
+ }
+
+ //
+ // trace optimization tracking
+ //
+ if ( com_showtrace->integer )
+ {
+ extern int c_traces, c_brush_traces, c_patch_traces;
+ extern int c_pointcontents;
+
+ Com_Printf("%4i traces (%ib %ip) %4i points\n",
+ c_traces, c_brush_traces, c_patch_traces, c_pointcontents);
+
+ c_traces = 0;
+ c_brush_traces = 0;
+ c_patch_traces = 0;
+ c_pointcontents = 0;
+ }
+
+ Com_ReadFromPipe();
+
+ com_frameNumber++;
+}
+
+/*
+=================
+Com_Shutdown
+=================
+*/
+void Com_Shutdown(void)
+{
+ if (logfile)
+ {
+ FS_FCloseFile (logfile);
+ logfile = 0;
+ }
+
+ if ( com_journalFile )
+ {
+ FS_FCloseFile( com_journalFile );
+ com_journalFile = 0;
+ }
+
+ if( pipefile )
+ {
+ FS_FCloseFile( pipefile );
+ FS_HomeRemove( com_pipefile->string );
+ }
+}
+
+/*
+===========================================
+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 )
+{
+ 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 ( int 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 )
+{
+ for( int i = 0; i < strlen( s ); i++ )
+ if( s[ i ] == ';' )
+ return &s[ i ];
+
+ return NULL;
+}
+
+/*
+===============
+Field_Complete
+===============
+*/
+static bool Field_Complete( void )
+{
+ if( matchCount == 0 )
+ return true;
+
+ int completionOffset = strlen( completionField->buffer ) - strlen( completionString );
+
+ Q_strncpyz( &completionField->buffer[ completionOffset ], shortestMatch,
+ sizeof( completionField->buffer ) - completionOffset );
+
+ completionField->cursor = strlen( completionField->buffer );
+
+ if( matchCount == 1 )
+ {
+ Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " );
+ completionField->cursor++;
+ return true;
+ }
+
+ Com_Printf( "]%s\n", completionField->buffer );
+
+ return false;
+}
+
+#ifndef DEDICATED
+/*
+===============
+Field_CompleteKeyname
+===============
+*/
+void Field_CompleteKeyname( void )
+{
+ matchCount = 0;
+ shortestMatch[ 0 ] = 0;
+
+ Key_KeynameCompletion( FindMatches );
+
+ if( !Field_Complete( ) )
+ Key_KeynameCompletion( PrintMatches );
+}
+#endif
+
+/*
+===============
+Field_CompleteFilename
+===============
+*/
+void Field_CompleteFilename( const char *dir, const char *ext,
+ bool stripExt, bool allowNonPureFilesOnDisk )
+{
+ matchCount = 0;
+ shortestMatch[ 0 ] = 0;
+
+ FS_FilenameCompletion( dir, ext, stripExt, FindMatches, allowNonPureFilesOnDisk );
+
+ if( !Field_Complete( ) )
+ FS_FilenameCompletion( dir, ext, stripExt, PrintMatches, allowNonPureFilesOnDisk );
+}
+
+/*
+============
+Field_ListCompletion
+============
+*/
+void Field_ListCompletion( char *listJson, void(*callback)(const char *s) )
+{
+ char item[ 256 ];
+ const char *arrayPtr;
+ const char *listEnd = listJson + strlen( listJson );
+
+ // JSON parse array
+ for ( arrayPtr = JSON_ArrayGetFirstValue( listJson, listEnd );
+ arrayPtr ;
+ arrayPtr = JSON_ArrayGetNextValue( arrayPtr, listEnd ) )
+ {
+ JSON_ValueGetString( arrayPtr, listEnd, item, 256 );
+ callback( item );
+ }
+}
+
+/*
+===============
+Field_CompleteList
+
+Completes an arbirary list of JSON encoded items passed from a VM
+===============
+*/
+void Field_CompleteList( char *listJson )
+{
+ matchCount = 0;
+ shortestMatch[ 0 ] = 0;
+
+ Field_ListCompletion( listJson, FindMatches );
+
+ if( !Field_Complete() )
+ Field_ListCompletion( listJson, PrintMatches );
+}
+
+/*
+===============
+Field_CompleteCommand
+===============
+*/
+void Field_CompleteCommand( char *cmd, bool doCommands, bool doCvars )
+{
+ // Skip leading whitespace and quotes
+ cmd = Com_SkipCharset( cmd, " \"" );
+
+ Cmd_TokenizeStringIgnoreQuotes( cmd );
+ int 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 ( completionString == nullptr )
+ return;
+
+#ifndef DEDICATED
+ // Unconditionally add a '\' to the start of the buffer
+ if( completionField->buffer[ 0 ] &&
+ completionField->buffer[ 0 ] != '\\' )
+ {
+ if( completionField->buffer[ 0 ] != '/' )
+ {
+ // Buffer is full, refuse to complete
+ if( strlen( completionField->buffer ) + 1 >=
+ sizeof( completionField->buffer ) )
+ return;
+
+ memmove( &completionField->buffer[ 1 ],
+ &completionField->buffer[ 0 ],
+ strlen( completionField->buffer ) + 1 );
+ completionField->cursor++;
+ }
+
+ completionField->buffer[ 0 ] = '\\';
+ }
+#endif
+
+ if( completionArgument > 1 )
+ {
+ const char *baseCmd = Cmd_Argv( 0 );
+ char *p;
+
+#ifndef DEDICATED
+ // This should always be true
+ if( baseCmd[ 0 ] == '\\' || baseCmd[ 0 ] == '/' )
+ baseCmd++;
+#endif
+
+ if( ( p = Field_FindFirstSeparator( cmd ) ) )
+ Field_CompleteCommand( p + 1, true, true ); // Compound command
+ else
+ Cmd_CompleteArgument( baseCmd, cmd, completionArgument );
+ }
+ else
+ {
+ if( completionString[0] == '\\' || completionString[0] == '/' )
+ completionString++;
+
+ matchCount = 0;
+ shortestMatch[ 0 ] = 0;
+
+ if( strlen( completionString ) == 0 )
+ return;
+
+ if( doCommands )
+ Cmd_CommandCompletion( FindMatches );
+
+ if( doCvars )
+ Cvar_CommandCompletion( FindMatches );
+
+ if( !Field_Complete( ) )
+ {
+ // run through again, printing matches
+ if( doCommands )
+ Cmd_CommandCompletion( PrintMatches );
+
+ if( doCvars )
+ Cvar_CommandCompletion( PrintCvarMatches );
+ }
+ }
+}
+
+/*
+===============
+Field_AutoComplete
+
+Perform Tab expansion
+===============
+*/
+void Field_AutoComplete( field_t *field )
+{
+ completionField = field;
+ Field_CompleteCommand( completionField->buffer, true, true );
+}
+
+/*
+==================
+Com_RandomBytes
+
+fills string array with len random bytes, preferably from the OS randomizer
+==================
+*/
+void Com_RandomBytes( byte *string, int len )
+{
+ if( Sys_RandomBytes( string, len ) )
+ return;
+
+ Com_Printf( "Com_RandomBytes: using weak randomization\n" );
+ for( int i = 0; i < len; i++ )
+ string[i] = (unsigned char)( rand() % 256 );
+}
+
+
+/*
+==================
+Com_IsVoipTarget
+
+Returns non-zero if given clientNum is enabled in voipTargets, zero otherwise.
+If clientNum is negative return if any bit is set.
+==================
+*/
+bool Com_IsVoipTarget(uint8_t *voipTargets, int voipTargetsSize, int clientNum)
+{
+ int i = 0;
+
+ if ( clientNum < 0 )
+ {
+ for ( i = 0; i < voipTargetsSize; i++ )
+ {
+ if(voipTargets[i])
+ return true;
+ }
+
+ return false;
+ }
+
+ i = clientNum >> 3;
+
+ if( i < voipTargetsSize )
+ return (bool)(voipTargets[i] & (1 << (clientNum & 0x07)));
+
+ return false;
+}
+
+/*
+===============
+Field_CompletePlayerName
+===============
+*/
+static bool Field_CompletePlayerNameFinal( bool whitespace )
+{
+ if( matchCount == 0 )
+ return true;
+
+ int completionOffset = strlen( completionField->buffer ) - strlen( completionString );
+
+ Q_strncpyz( &completionField->buffer[ completionOffset ], shortestMatch,
+ sizeof( completionField->buffer ) - completionOffset );
+
+ completionField->cursor = strlen( completionField->buffer );
+
+ if( matchCount == 1 && whitespace )
+ {
+ Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " );
+ completionField->cursor++;
+ return true;
+ }
+
+ return false;
+}
+
+static void Name_PlayerNameCompletion( const char **names, int nameCount, void(*callback)(const char *s) )
+{
+ for( int i = 0; i < nameCount; i++ )
+ callback( names[ i ] );
+}
+
+bool Com_FieldStringToPlayerName( char *name, int length, const char *rawname )
+{
+ char hex[5];
+
+ if( name == NULL || rawname == NULL )
+ return false;
+
+ if( length <= 0 )
+ return true;
+
+ int i;
+ for( i = 0; *rawname && i + 1 <= length; rawname++, i++ )
+ {
+ if( *rawname == '\\' )
+ {
+ Q_strncpyz( hex, rawname + 1, sizeof(hex) );
+ int ch = Com_HexStrToInt( hex );
+ if( ch > -1 )
+ {
+ name[i] = ch;
+ rawname += 4; //hex string length, 0xXX
+ }
+ else
+ {
+ name[i] = *rawname;
+ }
+ } else {
+ name[i] = *rawname;
+ }
+ }
+ name[i] = '\0';
+
+ return true;
+}
+
+bool Com_PlayerNameToFieldString( char *str, int length, const char *name )
+{
+ const char *p;
+ int i;
+ int x1, x2;
+
+ if( str == NULL || name == NULL )
+ return false;
+
+ if( length <= 0 )
+ return true;
+
+ *str = '\0';
+ p = name;
+
+ for( i = 0; *p != '\0'; i++, p++ )
+ {
+ if( i + 1 >= length )
+ break;
+
+ if( *p <= ' ' )
+ {
+ if( i + 5 + 1 >= length )
+ break;
+
+ x1 = *p >> 4;
+ x2 = *p & 15;
+
+ str[i+0] = '\\';
+ str[i+1] = '0';
+ str[i+2] = 'x';
+ str[i+3] = x1 > 9 ? x1 - 10 + 'a' : x1 + '0';
+ str[i+4] = x2 > 9 ? x2 - 10 + 'a' : x2 + '0';
+
+ i += 4;
+ } else {
+ str[i] = *p;
+ }
+ }
+ str[i] = '\0';
+
+ return true;
+}
+
+void Field_CompletePlayerName( const char **names, int nameCount )
+{
+
+ matchCount = 0;
+ shortestMatch[ 0 ] = 0;
+
+ if( nameCount <= 0 )
+ return;
+
+ Name_PlayerNameCompletion( names, nameCount, FindMatches );
+
+ if( completionString[0] == '\0' )
+ Com_PlayerNameToFieldString( shortestMatch, sizeof( shortestMatch ), names[ 0 ] );
+
+ //allow to tab player names
+ //if full player name switch to next player name
+ if( completionString[0] != '\0'
+ && Q_stricmp( shortestMatch, completionString ) == 0
+ && nameCount > 1 )
+ {
+ for( int i = 0; i < nameCount; i++ )
+ {
+ if( Q_stricmp( names[ i ], completionString ) == 0 )
+ {
+ i++;
+
+ if( i >= nameCount )
+ i = 0;
+
+ Com_PlayerNameToFieldString( shortestMatch, sizeof( shortestMatch ), names[ i ] );
+ break;
+ }
+ }
+ }
+
+ if( matchCount > 1 )
+ {
+ Com_Printf( "]%s\n", completionField->buffer );
+
+ Name_PlayerNameCompletion( names, nameCount, PrintMatches );
+ }
+
+ bool whitespace = nameCount == 1 ? true : false;
+ Field_CompletePlayerNameFinal(whitespace);
+}
+
+int QDECL Com_strCompare( const void *a, const void *b )
+{
+ const char **pa = (const char **)a;
+ const char **pb = (const char **)b;
+ return strcmp( *pa, *pb );
+}
diff --git a/src/qcommon/crypto.cpp b/src/qcommon/crypto.cpp
new file mode 100644
index 0000000..dd71371
--- /dev/null
+++ b/src/qcommon/crypto.cpp
@@ -0,0 +1,92 @@
+/*
+===========================================================================
+Copyright (C) 2007-2008 Amanieu d'Antras (amanieu@gmail.com)
+Copyright (C) 2015-2016 Jeff Kent (jeff@jkent.net)
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "crypto.h"
+
+#include "sys/sys_shared.h"
+
+#include "cvar.h"
+#include "q_shared.h"
+#include "qcommon.h"
+
+#define TO_REAL_PTR(x) ((uint8_t*)x - sizeof(size_t))
+#define TO_MOCK_PTR(x) ((uint8_t*)x + sizeof(size_t))
+#define REAL_PTR_SIZE(x) (*((size_t *)x))
+#define MOCK_PTR_SIZE(x) (REAL_PTR_SIZE(TO_REAL_PTR(x)))
+
+
+static void *crypto_alloc( size_t size )
+{
+ void *p;
+
+ assert( size > 0 );
+
+ p = malloc( sizeof(size_t) + size );
+ if ( !p )
+ Com_Error( ERR_FATAL, "crypto_alloc: Virtual memory exhausted." );
+
+ REAL_PTR_SIZE( p ) = size;
+ return TO_MOCK_PTR( p );
+}
+
+static void *crypto_realloc( void *old, size_t old_size, size_t new_size )
+{
+ void *p;
+
+ old_size = MOCK_PTR_SIZE( old );
+ if ( new_size == old_size ) {
+ return old;
+ }
+
+ p = malloc( sizeof(size_t) + new_size );
+ if ( !p )
+ Com_Error( ERR_FATAL, "crypto_realloc: Virtual memory exhausted." );
+ REAL_PTR_SIZE( p ) = new_size;
+
+ p = TO_MOCK_PTR( p );
+ memcpy( p, old, MIN( old_size, new_size ) );
+ old = TO_REAL_PTR( old );
+ memset( old, 0, sizeof(size_t) + old_size );
+ free( old );
+
+ return p;
+}
+
+static void crypto_free( void *p, size_t size )
+{
+ p = TO_REAL_PTR( p );
+ size = REAL_PTR_SIZE( p );
+ memset( p, 0, sizeof(size_t) + size );
+ free( p );
+}
+
+void Crypto_Init( void )
+{
+ mp_set_memory_functions( crypto_alloc, crypto_realloc, crypto_free );
+}
+
+void qnettle_random( void *ctx, size_t length, uint8_t *dst )
+{
+ Sys_CryptoRandomBytes( dst, length );
+}
diff --git a/src/qcommon/crypto.h b/src/qcommon/crypto.h
new file mode 100644
index 0000000..9b21943
--- /dev/null
+++ b/src/qcommon/crypto.h
@@ -0,0 +1,45 @@
+/*
+===========================================================================
+Copyright (C) 2007-2008 Amanieu d'Antras (amanieu@gmail.com)
+Copyright (C) 2015-2016 Jeff Kent (jeff@jkent.net)
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#ifndef __CRYPTO_H__
+#define __CRYPTO_H__
+
+#include "nettle/bignum.h"
+#include "nettle/buffer.h"
+#include "nettle/rsa.h"
+#include "nettle/sha2.h"
+
+
+#define RSA_PRIVATE_KEY_FILE "rsa_private_key"
+#define RSA_PUBLIC_KEY_FILE "rsa_public_key"
+
+#define RSA_PUBLIC_EXPONENT 65537
+
+#define RSA_KEY_LENGTH 4096
+#define RSA_STRING_LENGTH (RSA_KEY_LENGTH / 4 + 1)
+
+void Crypto_Init( void );
+void qnettle_random( void *ctx, size_t length, uint8_t *dst );
+
+#endif /* __CRYPTO_H__ */
diff --git a/src/qcommon/cvar.cpp b/src/qcommon/cvar.cpp
new file mode 100644
index 0000000..560138a
--- /dev/null
+++ b/src/qcommon/cvar.cpp
@@ -0,0 +1,1498 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// cvar.c -- dynamic variable tracking
+
+#include "cvar.h"
+
+#include "cmd.h"
+#include "files.h"
+#include "q_shared.h"
+#include "qcommon.h"
+
+static cvar_t *cvar_vars = nullptr;
+cvar_t *cvar_cheats;
+int cvar_modifiedFlags = 0;
+
+#define MAX_CVARS 2048
+static cvar_t cvar_indexes[MAX_CVARS];
+static int cvar_numIndexes;
+
+#define FILE_HASH_SIZE 256
+static cvar_t *hashTable[FILE_HASH_SIZE];
+
+/*
+================
+return a hash value for the filename
+================
+*/
+static long generateHashValue(const char *fname)
+{
+ long hash = 0;
+ int i = 0;
+ while (fname[i] != '\0')
+ {
+ char letter = tolower(fname[i]);
+ hash += (long)(letter) * (i + 119);
+ i++;
+ }
+ hash &= (FILE_HASH_SIZE - 1);
+ return hash;
+}
+
+/*
+============
+Cvar_ValidateString
+============
+*/
+bool Cvar_ValidateString(const char *s)
+{
+ if (!s)
+ {
+ return false;
+ }
+ if (strchr(s, '\\'))
+ {
+ return false;
+ }
+ if (strchr(s, '\"'))
+ {
+ return false;
+ }
+ if (strchr(s, ';'))
+ {
+ return false;
+ }
+ return true;
+}
+
+/*
+============
+Cvar_FindVar
+============
+*/
+cvar_t *Cvar_FindVar(const char *var_name)
+{
+ long hash = generateHashValue(var_name);
+
+ for (cvar_t *var = hashTable[hash]; var; var = var->hashNext)
+ if (!Q_stricmp(var_name, var->name))
+ return var;
+
+ return nullptr;
+}
+
+/*
+============
+Cvar_VariableValue
+============
+*/
+float Cvar_VariableValue(const char *var_name)
+{
+ cvar_t *var = Cvar_FindVar(var_name);
+ if (!var)
+ return 0;
+ return var->value;
+}
+
+/*
+============
+Cvar_VariableIntegerValue
+============
+*/
+int Cvar_VariableIntegerValue(const char *var_name)
+{
+ cvar_t *var = Cvar_FindVar(var_name);
+ if (!var)
+ return 0;
+ return var->integer;
+}
+
+/*
+============
+Cvar_VariableString
+============
+*/
+const char *Cvar_VariableString(const char *var_name)
+{
+ cvar_t *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 = Cvar_FindVar(var_name);
+ if (var)
+ Q_strncpyz(buffer, var->string, bufsize);
+ else
+ *buffer = 0;
+}
+
+/*
+============
+Cvar_Flags
+============
+*/
+unsigned int Cvar_Flags(const char *var_name)
+{
+ cvar_t *var;
+
+ if (!(var = Cvar_FindVar(var_name)))
+ return CVAR_NONEXISTENT;
+ else if (var->modified)
+ return var->flags | CVAR_MODIFIED;
+
+ return var->flags;
+}
+
+/*
+============
+Cvar_CommandCompletion
+============
+*/
+void Cvar_CommandCompletion(void (*callback)(const char *s))
+{
+ for (cvar_t *cvar = cvar_vars; cvar; cvar = cvar->next)
+ {
+ if (cvar->name)
+ callback(cvar->name);
+ }
+}
+
+/*
+============
+Cvar_Validate
+============
+*/
+const char *Cvar_Validate(cvar_t *var, const char *value, bool warn)
+{
+ static char s[MAX_CVAR_VALUE_STRING];
+ float valuef;
+ bool changed = false;
+
+ if (!var->validate)
+ return value;
+
+ if (!value)
+ return nullptr;
+
+ if (Q_isanumber(value))
+ {
+ valuef = atof(value);
+
+ if (var->integral)
+ {
+ if (!Q_isintegral(valuef))
+ {
+ if (warn)
+ Com_Printf("WARNING: cvar '%s' must be integral", var->name);
+
+ valuef = (int)valuef;
+ changed = true;
+ }
+ }
+ }
+ else
+ {
+ if (warn)
+ Com_Printf("WARNING: cvar '%s' must be numeric", var->name);
+
+ valuef = atof(var->resetString);
+ changed = true;
+ }
+
+ if (valuef < var->min)
+ {
+ if (warn)
+ {
+ if (changed)
+ Com_Printf(" and is");
+ else
+ Com_Printf("WARNING: cvar '%s'", var->name);
+
+ if (Q_isintegral(var->min))
+ Com_Printf(" out of range (min %d)", (int)var->min);
+ else
+ Com_Printf(" out of range (min %f)", var->min);
+ }
+
+ valuef = var->min;
+ changed = true;
+ }
+ else if (valuef > var->max)
+ {
+ if (warn)
+ {
+ if (changed)
+ Com_Printf(" and is");
+ else
+ Com_Printf("WARNING: cvar '%s'", var->name);
+
+ if (Q_isintegral(var->max))
+ Com_Printf(" out of range (max %d)", (int)var->max);
+ else
+ Com_Printf(" out of range (max %f)", var->max);
+ }
+
+ valuef = var->max;
+ changed = true;
+ }
+
+ if (changed)
+ {
+ if (Q_isintegral(valuef))
+ {
+ Com_sprintf(s, sizeof(s), "%d", (int)valuef);
+
+ if (warn)
+ Com_Printf(", setting to %d\n", (int)valuef);
+ }
+ else
+ {
+ Com_sprintf(s, sizeof(s), "%f", valuef);
+
+ if (warn)
+ Com_Printf(", setting to %f\n", valuef);
+ }
+
+ return s;
+ }
+
+ return value;
+}
+
+/*
+============
+Cvar_Get
+
+If the variable already exists, the value will not be set unless CVAR_ROM
+The flags will be or'ed in if the variable exists.
+============
+*/
+cvar_t *Cvar_Get(const char *var_name, const char *var_value, int flags)
+{
+ if (!var_name || !var_value)
+ {
+ Com_Error(ERR_FATAL, "Cvar_Get: nullptr 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
+
+ cvar_t *var = Cvar_FindVar(var_name);
+ if (var)
+ {
+ var_value = Cvar_Validate(var, var_value, false);
+
+ // Make sure the game code cannot mark engine-added variables as gamecode vars
+ if (var->flags & CVAR_VM_CREATED)
+ {
+ if (!(flags & CVAR_VM_CREATED))
+ var->flags &= ~CVAR_VM_CREATED;
+ }
+ else if (!(var->flags & CVAR_USER_CREATED))
+ {
+ if (flags & CVAR_VM_CREATED)
+ flags &= ~CVAR_VM_CREATED;
+ }
+
+ // if the C code is now specifying a variable that the user already
+ // set a value for, take the new value as the reset value
+ if (var->flags & CVAR_USER_CREATED)
+ {
+ var->flags &= ~CVAR_USER_CREATED;
+ Z_Free(var->resetString);
+ var->resetString = CopyString(var_value);
+
+ if (flags & CVAR_ROM)
+ {
+ // this variable was set by the user,
+ // so force it to value given by the engine.
+
+ if (var->latchedString)
+ Z_Free(var->latchedString);
+
+ var->latchedString = CopyString(var_value);
+ }
+ }
+
+ // Make sure servers cannot mark engine-added variables as SERVER_CREATED
+ if (var->flags & CVAR_SERVER_CREATED)
+ {
+ if (!(flags & CVAR_SERVER_CREATED))
+ var->flags &= ~CVAR_SERVER_CREATED;
+ }
+ else
+ {
+ if (flags & CVAR_SERVER_CREATED)
+ flags &= ~CVAR_SERVER_CREATED;
+ }
+
+ var->flags |= flags;
+
+ // only allow one non-empty reset string without a warning
+ if (!var->resetString[0])
+ {
+ // we don't have a reset string yet
+ Z_Free(var->resetString);
+ var->resetString = CopyString(var_value);
+ }
+ else if (var_value[0] && strcmp(var->resetString, var_value))
+ {
+ Com_DPrintf("Warning: cvar \"%s\" given initial values: \"%s\" and \"%s\"\n",
+ var_name, var->resetString, var_value);
+ }
+ // if we have a latched string, take that value now
+ if (var->latchedString)
+ {
+ char *s = var->latchedString;
+ var->latchedString = nullptr; // otherwise cvar_set2 would free it
+ Cvar_Set2(var_name, s, true);
+ Z_Free(s);
+ }
+
+ // ZOID--needs to be set so that cvars the game sets as
+ // SERVERINFO get sent to clients
+ cvar_modifiedFlags |= flags;
+ if (flags & CVAR_ALTERNATE_SYSTEMINFO)
+ {
+ cvar_modifiedFlags |= CVAR_SYSTEMINFO;
+ }
+
+ return var;
+ }
+
+ //
+ // allocate a new cvar
+ //
+
+ // find a free cvar
+ int i;
+ for (i = 0; i < MAX_CVARS; i++)
+ if (!cvar_indexes[i].name)
+ break;
+
+ if (i >= MAX_CVARS)
+ {
+ if (!com_errorEntered)
+ Com_Error(ERR_FATAL, "Error: Too many cvars, cannot create a new one!");
+ return nullptr;
+ }
+
+ var = &cvar_indexes[i];
+
+ if (i >= cvar_numIndexes)
+ cvar_numIndexes = i + 1;
+
+ var->name = CopyString(var_name);
+ var->string = CopyString(var_value);
+ var->modified = true;
+ var->modificationCount = 1;
+ var->value = atof(var->string);
+ var->integer = atoi(var->string);
+ var->resetString = CopyString(var_value);
+ var->validate = false;
+ var->description = nullptr;
+
+ // link the variable in
+ var->next = cvar_vars;
+ if (cvar_vars)
+ cvar_vars->prev = var;
+
+ var->prev = nullptr;
+ cvar_vars = var;
+
+ var->flags = flags;
+ // note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo)
+ cvar_modifiedFlags |= var->flags;
+ if (var->flags & CVAR_ALTERNATE_SYSTEMINFO)
+ cvar_modifiedFlags |= CVAR_SYSTEMINFO;
+
+ long hash = generateHashValue(var_name);
+ var->hashIndex = hash;
+
+ var->hashNext = hashTable[hash];
+ if (hashTable[hash])
+ hashTable[hash]->hashPrev = var;
+
+ var->hashPrev = nullptr;
+ hashTable[hash] = var;
+
+ return var;
+}
+
+/*
+============
+Cvar_Print
+
+Prints the value, default, and latched string of the given variable
+============
+*/
+void Cvar_Print(cvar_t *v)
+{
+ Com_Printf("\"%s\" is:\"%s" S_COLOR_WHITE "\"", v->name, v->string);
+
+ if (!(v->flags & CVAR_ROM))
+ {
+ if (!Q_stricmp(v->string, v->resetString))
+ Com_Printf(", the default");
+ else
+ Com_Printf(" default:\"%s" S_COLOR_WHITE "\"", v->resetString);
+ }
+
+ Com_Printf("\n");
+
+ if (v->latchedString)
+ Com_Printf("latched: \"%s\"\n", v->latchedString);
+
+ if (v->description)
+ Com_Printf("%s\n", v->description);
+}
+
+/*
+============
+Cvar_Set2
+============
+*/
+cvar_t *Cvar_Set2(const char *var_name, const char *value, bool force)
+{
+ 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
+
+ cvar_t *var = Cvar_FindVar(var_name);
+ if (!var)
+ {
+ if (!value)
+ return nullptr;
+
+ if (!force)
+ return Cvar_Get(var_name, value, CVAR_USER_CREATED);
+
+ return Cvar_Get(var_name, value, 0);
+ }
+
+ if (!value)
+ value = var->resetString;
+
+ value = Cvar_Validate(var, value, true);
+
+ if ((var->flags & CVAR_LATCH) && var->latchedString)
+ {
+ if (!strcmp(value, var->string))
+ {
+ Z_Free(var->latchedString);
+ var->latchedString = nullptr;
+ return var;
+ }
+
+ if (!strcmp(value, var->latchedString))
+ return var;
+ }
+ else if (!strcmp(value, var->string))
+ return var;
+
+ // note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo)
+ cvar_modifiedFlags |= var->flags;
+ if (var->flags & CVAR_ALTERNATE_SYSTEMINFO)
+ {
+ cvar_modifiedFlags |= CVAR_SYSTEMINFO;
+ }
+
+ 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);
+ var->latchedString = nullptr;
+ }
+ 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 = true;
+ 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 = nullptr;
+ }
+ }
+
+ if (!strcmp(value, var->string))
+ return var; // not changed
+
+ var->modified = true;
+ 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, true);
+}
+/*
+============
+Cvar_SetSafe
+============
+*/
+void Cvar_SetSafe(const char *var_name, const char *value)
+{
+ unsigned flags = Cvar_Flags(var_name);
+
+ if ((flags != CVAR_NONEXISTENT) && (flags & CVAR_PROTECTED))
+ {
+ if (value)
+ Com_Error(ERR_DROP, "Restricted source tried to set \"%s\" to \"%s\"",
+ var_name, value);
+ else
+ Com_Error(ERR_DROP, "Restricted source tried to modify \"%s\"",
+ var_name);
+ return;
+ }
+ Cvar_Set(var_name, value);
+}
+
+/*
+============
+Cvar_SetLatched
+============
+*/
+void Cvar_SetLatched(const char *var_name, const char *value)
+{
+ Cvar_Set2(var_name, value, false);
+}
+/*
+============
+Cvar_SetValue
+============
+*/
+void Cvar_SetValue(const char *var_name, float value)
+{
+ char val[32];
+
+ if (value == (int)value)
+ {
+ Com_sprintf(val, sizeof(val), "%i", (int)value);
+ }
+ else
+ {
+ Com_sprintf(val, sizeof(val), "%f", value);
+ }
+ Cvar_Set(var_name, val);
+}
+
+/*
+============
+Cvar_SetValueSafe
+============
+*/
+void Cvar_SetValueSafe(const char *var_name, float value)
+{
+ char val[32];
+
+ if (Q_isintegral(value))
+ Com_sprintf(val, sizeof(val), "%i", (int)value);
+ else
+ Com_sprintf(val, sizeof(val), "%f", value);
+ Cvar_SetSafe(var_name, val);
+}
+
+/*
+============
+Cvar_Reset
+============
+*/
+void Cvar_Reset(const char *var_name)
+{
+ Cvar_Set2(var_name, nullptr, false);
+}
+/*
+============
+Cvar_ForceReset
+============
+*/
+void Cvar_ForceReset(const char *var_name)
+{
+ Cvar_Set2(var_name, nullptr, true);
+}
+/*
+============
+Cvar_SetCheatState
+
+Any testing variables will be reset to the safe values
+============
+*/
+void Cvar_SetCheatState(void)
+{
+ // set all default vars to the safe value
+ for (cvar_t *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 = nullptr;
+ }
+ if (strcmp(var->resetString, var->string))
+ Cvar_Set(var->name, var->resetString);
+ }
+ }
+}
+
+/*
+============
+Cvar_Command
+
+Handles variable inspection and changing from the console
+============
+*/
+bool Cvar_Command(void)
+{
+ cvar_t *v = Cvar_FindVar(Cmd_Argv(0));
+ if (!v)
+ {
+ return false;
+ }
+
+ // perform a variable print or set
+ if (Cmd_Argc() == 1)
+ {
+ Cvar_Print(v);
+ return true;
+ }
+
+ // set the value if forcing isn't required
+ Cvar_Set2(v->name, Cmd_Args(), false);
+ return true;
+}
+
+/*
+============
+Cvar_Print_f
+
+Prints the contents of a cvar
+(preferred over Cvar_Command where cvar names and commands conflict)
+============
+*/
+void Cvar_Print_f(void)
+{
+ if (Cmd_Argc() != 2)
+ {
+ Com_Printf("usage: print <variable>\n");
+ return;
+ }
+
+ const char *name = Cmd_Argv(1);
+ cvar_t *cv = Cvar_FindVar(name);
+
+ if (cv)
+ Cvar_Print(cv);
+ else
+ Com_Printf("Cvar %s does not exist.\n", name);
+}
+
+/*
+============
+Cvar_Toggle_f
+
+Toggles a cvar for easy single key binding, optionally through a list of
+given values
+============
+*/
+void Cvar_Toggle_f(void)
+{
+ int c = Cmd_Argc();
+ if (c < 2)
+ {
+ Com_Printf("usage: toggle <variable> [value1, value2, ...]\n");
+ return;
+ }
+ else if (c == 2)
+ {
+ Cvar_Set2(Cmd_Argv(1), va("%d", !Cvar_VariableValue(Cmd_Argv(1))), false);
+ return;
+ }
+ else if (c == 3)
+ {
+ Com_Printf("toggle: nothing to toggle to\n");
+ return;
+ }
+
+ const char *curval = Cvar_VariableString(Cmd_Argv(1));
+
+ // don't bother checking the last arg for a match since the desired
+ // behaviour is the same as no match (set to the first argument)
+ for (int i = 2; i + 1 < c; i++)
+ {
+ if (strcmp(curval, Cmd_Argv(i)) == 0)
+ {
+ Cvar_Set2(Cmd_Argv(1), Cmd_Argv(i + 1), false);
+ return;
+ }
+ }
+
+ // fallback
+ Cvar_Set2(Cmd_Argv(1), Cmd_Argv(2), false);
+}
+
+/*
+============
+Cvar_Set_f
+
+Allows setting and defining of arbitrary cvars from console, even if they
+weren't declared in C code.
+============
+*/
+void Cvar_Set_f(void)
+{
+ int c = Cmd_Argc();
+ const char *cmd = Cmd_Argv(0);
+
+ if (c < 2)
+ {
+ Com_Printf("usage: %s <variable> <value>\n", cmd);
+ return;
+ }
+ else if (c == 2)
+ {
+ Cvar_Print_f();
+ return;
+ }
+
+ cvar_t *v = Cvar_Set2(Cmd_Argv(1), Cmd_ArgsFrom(2), false);
+ if (!v)
+ {
+ return;
+ }
+
+ switch (cmd[3])
+ {
+ case 'a':
+ if (!(v->flags & CVAR_ARCHIVE))
+ {
+ v->flags |= CVAR_ARCHIVE;
+ cvar_modifiedFlags |= CVAR_ARCHIVE;
+ }
+ break;
+ case 'u':
+ if (!(v->flags & CVAR_USERINFO))
+ {
+ v->flags |= CVAR_USERINFO;
+ cvar_modifiedFlags |= CVAR_USERINFO;
+ }
+ break;
+ case 's':
+ if (!(v->flags & CVAR_SERVERINFO))
+ {
+ v->flags |= CVAR_SERVERINFO;
+ cvar_modifiedFlags |= CVAR_SERVERINFO;
+ }
+ break;
+ }
+}
+
+/*
+============
+Cvar_Reset_f
+============
+*/
+void Cvar_Reset_f(void)
+{
+ if (Cmd_Argc() != 2)
+ {
+ Com_Printf("usage: reset <variable>\n");
+ return;
+ }
+ Cvar_Reset(Cmd_Argv(1));
+}
+
+/*
+============
+Cvar_WriteVariables
+
+Appends lines containing "set variable value" for all variables
+with the archive flag set to true.
+============
+*/
+void Cvar_WriteVariables(fileHandle_t f)
+{
+ cvar_t *var;
+ char buffer[1024];
+
+ for (var = cvar_vars; var; var = var->next)
+ {
+ if (!var->name)
+ continue;
+
+ if (var->flags & CVAR_ARCHIVE)
+ {
+ // write the latched value, even if it hasn't taken effect yet
+ if (var->latchedString)
+ {
+ if (strlen(var->name) + strlen(var->latchedString) + 10 > sizeof(buffer))
+ {
+ Com_Printf(S_COLOR_YELLOW
+ "WARNING: value of variable "
+ "\"%s\" too long to write to file\n",
+ var->name);
+ continue;
+ }
+ Com_sprintf(buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->latchedString);
+ }
+ else
+ {
+ if (strlen(var->name) + strlen(var->string) + 10 > sizeof(buffer))
+ {
+ Com_Printf(S_COLOR_YELLOW
+ "WARNING: value of variable "
+ "\"%s\" too long to write to file\n",
+ var->name);
+ continue;
+ }
+ Com_sprintf(buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->string);
+ }
+ FS_Write(buffer, strlen(buffer), f);
+ }
+ }
+}
+
+/*
+============
+Cvar_List_f
+============
+*/
+void Cvar_List_f(void)
+{
+ cvar_t *var;
+ int i;
+ const char *match;
+
+ if (Cmd_Argc() > 1)
+ {
+ match = Cmd_Argv(1);
+ }
+ else
+ {
+ match = nullptr;
+ }
+
+ i = 0;
+ for (var = cvar_vars; var; var = var->next, i++)
+ {
+ if (!var->name || (match && !Com_Filter(match, var->name, false)))
+ continue;
+
+ if (var->flags & CVAR_SERVERINFO)
+ {
+ Com_Printf("S");
+ }
+ else
+ {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_SYSTEMINFO)
+ {
+ Com_Printf("s");
+ }
+ else
+ {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_USERINFO)
+ {
+ Com_Printf("U");
+ }
+ else
+ {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_ROM)
+ {
+ Com_Printf("R");
+ }
+ else
+ {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_INIT)
+ {
+ Com_Printf("I");
+ }
+ else
+ {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_ARCHIVE)
+ {
+ Com_Printf("A");
+ }
+ else
+ {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_LATCH)
+ {
+ Com_Printf("L");
+ }
+ else
+ {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_CHEAT)
+ {
+ Com_Printf("C");
+ }
+ else
+ {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_USER_CREATED)
+ {
+ Com_Printf("?");
+ }
+ else
+ {
+ Com_Printf(" ");
+ }
+
+ Com_Printf(" %s \"%s\"\n", var->name, var->string);
+ }
+
+ Com_Printf("\n%i total cvars\n", i);
+ Com_Printf("%i cvar indexes\n", cvar_numIndexes);
+}
+
+/*
+============
+Cvar_ListModified_f
+============
+*/
+void Cvar_ListModified_f(void)
+{
+ cvar_t *var;
+ int totalModified;
+ char *value;
+ const char *match;
+
+ if (Cmd_Argc() > 1)
+ {
+ match = Cmd_Argv(1);
+ }
+ else
+ {
+ match = nullptr;
+ }
+
+ totalModified = 0;
+ for (var = cvar_vars; var; var = var->next)
+ {
+ if (!var->name || !var->modificationCount)
+ continue;
+
+ value = var->latchedString ? var->latchedString : var->string;
+ if (!strcmp(value, var->resetString))
+ continue;
+
+ totalModified++;
+
+ if (match && !Com_Filter(match, var->name, false))
+ continue;
+
+ if (var->flags & CVAR_SERVERINFO)
+ {
+ Com_Printf("S");
+ }
+ else
+ {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_SYSTEMINFO)
+ {
+ Com_Printf("s");
+ }
+ else
+ {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_USERINFO)
+ {
+ Com_Printf("U");
+ }
+ else
+ {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_ROM)
+ {
+ Com_Printf("R");
+ }
+ else
+ {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_INIT)
+ {
+ Com_Printf("I");
+ }
+ else
+ {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_ARCHIVE)
+ {
+ Com_Printf("A");
+ }
+ else
+ {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_LATCH)
+ {
+ Com_Printf("L");
+ }
+ else
+ {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_CHEAT)
+ {
+ Com_Printf("C");
+ }
+ else
+ {
+ Com_Printf(" ");
+ }
+ if (var->flags & CVAR_USER_CREATED)
+ {
+ Com_Printf("?");
+ }
+ else
+ {
+ Com_Printf(" ");
+ }
+
+ Com_Printf(" %s \"%s\", default \"%s\"\n", var->name, value, var->resetString);
+ }
+
+ Com_Printf("\n%i total modified cvars\n", totalModified);
+}
+
+/*
+============
+Cvar_Unset
+
+Unsets a cvar
+============
+*/
+
+cvar_t *Cvar_Unset(cvar_t *cv)
+{
+ cvar_t *next = cv->next;
+
+ // note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo)
+ cvar_modifiedFlags |= cv->flags;
+
+ if (cv->name)
+ Z_Free(cv->name);
+ if (cv->string)
+ Z_Free(cv->string);
+ if (cv->latchedString)
+ Z_Free(cv->latchedString);
+ if (cv->resetString)
+ Z_Free(cv->resetString);
+ if (cv->description)
+ Z_Free(cv->description);
+
+ if (cv->prev)
+ cv->prev->next = cv->next;
+ else
+ cvar_vars = cv->next;
+ if (cv->next)
+ cv->next->prev = cv->prev;
+
+ if (cv->hashPrev)
+ cv->hashPrev->hashNext = cv->hashNext;
+ else
+ hashTable[cv->hashIndex] = cv->hashNext;
+ if (cv->hashNext)
+ cv->hashNext->hashPrev = cv->hashPrev;
+
+ ::memset(cv, '\0', sizeof(*cv));
+
+ return next;
+}
+
+/*
+============
+Cvar_Unset_f
+
+Unsets a userdefined cvar
+============
+*/
+
+void Cvar_Unset_f(void)
+{
+ cvar_t *cv;
+
+ if (Cmd_Argc() != 2)
+ {
+ Com_Printf("Usage: %s <varname>\n", Cmd_Argv(0));
+ return;
+ }
+
+ cv = Cvar_FindVar(Cmd_Argv(1));
+
+ if (!cv)
+ return;
+
+ if (cv->flags & CVAR_USER_CREATED)
+ Cvar_Unset(cv);
+ else
+ Com_Printf("Error: %s: Variable %s is not user created.\n", Cmd_Argv(0), cv->name);
+}
+
+/*
+============
+Cvar_Restart
+
+Resets all cvars to their hardcoded values and removes userdefined variables
+and variables added via the VMs if requested.
+============
+*/
+
+void Cvar_Restart(bool unsetVM)
+{
+ cvar_t *curvar;
+
+ curvar = cvar_vars;
+
+ while (curvar)
+ {
+ if ((curvar->flags & CVAR_USER_CREATED) || (unsetVM && (curvar->flags & CVAR_VM_CREATED)))
+ {
+ // throw out any variables the user/vm created
+ curvar = Cvar_Unset(curvar);
+ continue;
+ }
+
+ if (!(curvar->flags & (CVAR_ROM | CVAR_INIT | CVAR_NORESTART)))
+ {
+ // Just reset the rest to their default values.
+ Cvar_Set2(curvar->name, curvar->resetString, false);
+ }
+
+ curvar = curvar->next;
+ }
+}
+
+/*
+============
+Cvar_Restart_f
+
+Resets all cvars to their hardcoded values
+============
+*/
+void Cvar_Restart_f(void)
+{
+ Cvar_Restart(false);
+}
+/*
+=====================
+Cvar_InfoString
+=====================
+*/
+char *Cvar_InfoString(int bit)
+{
+ static char info[MAX_INFO_STRING];
+ cvar_t *var;
+
+ info[0] = 0;
+
+ for (var = cvar_vars; var; var = var->next)
+ {
+ if (var->name && (var->flags & bit))
+ Info_SetValueForKey(info, var->name, var->string);
+ }
+
+ return info;
+}
+
+/*
+=====================
+Cvar_InfoString_Big
+
+ handles large info strings ( CS_SYSTEMINFO )
+=====================
+*/
+char *Cvar_InfoString_Big(int bit)
+{
+ static char info[BIG_INFO_STRING];
+ cvar_t *var;
+
+ info[0] = 0;
+
+ for (var = cvar_vars; var; var = var->next)
+ {
+ if (var->name && (var->flags & bit))
+ Info_SetValueForKey_Big(info, var->name, var->string);
+ }
+ return info;
+}
+
+/*
+=====================
+Cvar_InfoStringBuffer
+=====================
+*/
+void Cvar_InfoStringBuffer(int bit, char *buff, int buffsize)
+{
+ Q_strncpyz(buff, Cvar_InfoString(bit), buffsize);
+}
+/*
+=====================
+Cvar_CheckRange
+=====================
+*/
+void Cvar_CheckRange(cvar_t *var, float min, float max, bool integral)
+{
+ var->validate = true;
+ var->min = min;
+ var->max = max;
+ var->integral = integral;
+
+ // Force an initial range check
+ Cvar_Set(var->name, var->string);
+}
+
+/*
+=====================
+Cvar_SetDescription
+=====================
+*/
+void Cvar_SetDescription(cvar_t *var, const char *var_description)
+{
+ if (var_description && var_description[0] != '\0')
+ {
+ if (var->description != nullptr)
+ {
+ Z_Free(var->description);
+ }
+ var->description = CopyString(var_description);
+ }
+}
+
+/*
+=====================
+Cvar_Register
+
+basically a slightly modified Cvar_Get for the interpreted modules
+=====================
+*/
+void Cvar_Register(vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags)
+{
+ cvar_t *cv;
+
+ // There is code in Cvar_Get to prevent CVAR_ROM cvars being changed by the
+ // user. In other words CVAR_ARCHIVE and CVAR_ROM are mutually exclusive
+ // flags. Unfortunately some historical game code (including single player
+ // baseq3) sets both flags. We unset CVAR_ROM for such cvars.
+ if ((flags & (CVAR_ARCHIVE | CVAR_ROM)) == (CVAR_ARCHIVE | CVAR_ROM))
+ {
+ Com_DPrintf(S_COLOR_YELLOW
+ "WARNING: Unsetting CVAR_ROM cvar '%s', "
+ "since it is also CVAR_ARCHIVE\n",
+ varName);
+ flags &= ~CVAR_ROM;
+ }
+
+ cv = Cvar_Get(varName, defaultValue, flags | CVAR_VM_CREATED);
+
+ if (!vmCvar)
+ return;
+
+ vmCvar->handle = cv - cvar_indexes;
+ vmCvar->modificationCount = -1;
+ Cvar_Update(vmCvar);
+}
+
+/*
+=====================
+Cvar_Update
+
+updates an interpreted modules' version of a cvar
+=====================
+*/
+void Cvar_Update(vmCvar_t *vmCvar)
+{
+ cvar_t *cv = nullptr;
+ assert(vmCvar);
+
+ if (vmCvar->handle >= cvar_numIndexes)
+ {
+ Com_Error(ERR_DROP, "Cvar_Update: handle out of range");
+ }
+
+ cv = cvar_indexes + vmCvar->handle;
+
+ if (cv->modificationCount == vmCvar->modificationCount)
+ {
+ return;
+ }
+ if (!cv->string)
+ {
+ return; // variable might have been cleared by a cvar_restart
+ }
+ vmCvar->modificationCount = cv->modificationCount;
+ if (strlen(cv->string) + 1 > MAX_CVAR_VALUE_STRING)
+ Com_Error(ERR_DROP, "Cvar_Update: src %s length %u exceeds MAX_CVAR_VALUE_STRING", cv->string,
+ (unsigned int)strlen(cv->string));
+ Q_strncpyz(vmCvar->string, cv->string, MAX_CVAR_VALUE_STRING);
+
+ vmCvar->value = cv->value;
+ vmCvar->integer = cv->integer;
+}
+
+/*
+==================
+Cvar_CompleteCvarName
+==================
+*/
+void Cvar_CompleteCvarName(char *args, int argNum)
+{
+ if (argNum == 2)
+ {
+ // Skip "<cmd> "
+ char *p = Com_SkipTokens(args, 1, " ");
+
+ if (p > args)
+ Field_CompleteCommand(p, false, true);
+ }
+}
+
+/*
+============
+Cvar_Init
+
+Reads in all archived cvars
+============
+*/
+void Cvar_Init(void)
+{
+ ::memset(cvar_indexes, '\0', sizeof(cvar_indexes));
+ ::memset(hashTable, '\0', sizeof(hashTable));
+
+ cvar_cheats = Cvar_Get("sv_cheats", "1", CVAR_ROM | CVAR_SYSTEMINFO);
+
+ Cmd_AddCommand("print", Cvar_Print_f);
+ Cmd_AddCommand("toggle", Cvar_Toggle_f);
+ Cmd_SetCommandCompletionFunc("toggle", Cvar_CompleteCvarName);
+ Cmd_AddCommand("set", Cvar_Set_f);
+ Cmd_SetCommandCompletionFunc("set", Cvar_CompleteCvarName);
+ Cmd_AddCommand("sets", Cvar_Set_f);
+ Cmd_SetCommandCompletionFunc("sets", Cvar_CompleteCvarName);
+ Cmd_AddCommand("setu", Cvar_Set_f);
+ Cmd_SetCommandCompletionFunc("setu", Cvar_CompleteCvarName);
+ Cmd_AddCommand("seta", Cvar_Set_f);
+ Cmd_SetCommandCompletionFunc("seta", Cvar_CompleteCvarName);
+ Cmd_AddCommand("reset", Cvar_Reset_f);
+ Cmd_SetCommandCompletionFunc("reset", Cvar_CompleteCvarName);
+ Cmd_AddCommand("unset", Cvar_Unset_f);
+ Cmd_SetCommandCompletionFunc("unset", Cvar_CompleteCvarName);
+
+ Cmd_AddCommand("cvarlist", Cvar_List_f);
+ Cmd_AddCommand("cvar_modified", Cvar_ListModified_f);
+ Cmd_AddCommand("cvar_restart", Cvar_Restart_f);
+}
diff --git a/src/qcommon/cvar.h b/src/qcommon/cvar.h
new file mode 100644
index 0000000..465a99d
--- /dev/null
+++ b/src/qcommon/cvar.h
@@ -0,0 +1,199 @@
+/*
+ * This file is part of Tremulous.
+ * Copyright © 2017 Victor Roemer (blowfish) <victor@badsec.org>
+ * Copyright (C) 2015-2019 GrangerHub
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CVAR_H
+#define CVAR_H
+
+#include "q_platform.h"
+#include "q_shared.h"
+
+/*
+==========================================================
+
+CVARS (console variables)
+
+Many variables can be used for cheating purposes, so when
+cheats is zero, force all unspecified variables to their
+default values.
+==========================================================
+*/
+
+#define CVAR_ARCHIVE 0x0001 // set to cause it to be saved to vars.rc
+// used for system variables, not for player
+// specific configurations
+#define CVAR_USERINFO 0x0002 // sent to server on connect or change
+#define CVAR_SERVERINFO 0x0004 // sent in response to front end requests
+#define CVAR_SYSTEMINFO 0x0008 // these cvars will be duplicated on all clients
+#define CVAR_INIT 0x0010 // don't allow change from console at all,
+// but can be set from the command line
+#define CVAR_LATCH 0x0020 // will only change when C code next does
+// a Cvar_Get(), so it can't be changed without proper initialization.
+// modified will be set, even though the value hasn't changed yet
+#define CVAR_ROM 0x0040 // display only, cannot be set by user at all
+#define CVAR_USER_CREATED 0x0080 // created by a set command
+#define CVAR_TEMP 0x0100 // can be set even when cheats are disabled, but is not archived
+#define CVAR_CHEAT 0x0200 // can not be changed if cheats are disabled
+#define CVAR_NORESTART 0x0400 // do not clear when a cvar_restart is issued
+
+#define CVAR_SERVER_CREATED 0x0800 // cvar was created by a server the client connected to.
+#define CVAR_VM_CREATED 0x1000 // cvar was created exclusively in one of the VMs.
+#define CVAR_PROTECTED 0x2000 // prevent modifying this var from VMs or the server
+#define CVAR_ALTERNATE_SYSTEMINFO 0x1000000
+// These flags are only returned by the Cvar_Flags() function
+#define CVAR_MODIFIED 0x40000000 // Cvar was modified
+#define CVAR_NONEXISTENT 0x80000000 // Cvar doesn't exist.
+
+// nothing outside the Cvar_*() functions should modify these fields!
+typedef struct cvar_s cvar_t;
+
+struct cvar_s {
+ char *name;
+ char *string;
+ char *resetString; // cvar_restart will reset to this value
+ char *latchedString; // for CVAR_LATCH vars
+ int flags;
+ bool 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 )
+ bool validate;
+ bool integral;
+ float min;
+ float max;
+ char *description;
+
+ cvar_t *next;
+ cvar_t *prev;
+ cvar_t *hashNext;
+ cvar_t *hashPrev;
+ int hashIndex;
+};
+
+/*
+==============================================================
+
+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
+
+cvar_t *Cvar_Set2(const char *var_name, const char *value, bool force);
+// same as Cvar_Set, but allows more control over setting of cvar
+
+void Cvar_SetSafe(const char *var_name, const char *value);
+// sometimes we set variables from an untrusted source: fail if flags & CVAR_PROTECTED
+
+void Cvar_SetLatched(const char *var_name, const char *value);
+// don't set the cvar immediately
+
+void Cvar_SetValue(const char *var_name, float value);
+void Cvar_SetValueSafe(const char *var_name, float value);
+// expands value to a string and calls Cvar_Set/Cvar_SetSafe
+
+// Validate String used to validate cvar names
+bool Cvar_ValidateString(const char *s);
+cvar_t *Cvar_FindVar(const char *var_name);
+const char *Cvar_Validate(cvar_t *var, const char *value, bool warn);
+void Cvar_Print(cvar_t *v);
+
+float Cvar_VariableValue(const char *var_name);
+int Cvar_VariableIntegerValue(const char *var_name);
+// returns 0 if not defined or non numeric
+
+const 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
+
+unsigned 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
+
+bool Cvar_Command(void);
+// called by Cmd_ExecuteString when Cmd_Argv(0) doesn't match a known
+// command. Returns true if the command was a variable reference that
+// was handled. (print or change)
+
+void Cvar_WriteVariables(fileHandle_t f);
+// writes lines containing "set variable value" for all variables
+// with the archive flag set to true.
+
+void Cvar_Init(void);
+
+char *Cvar_InfoString(int bit);
+char *Cvar_InfoString_Big(int bit);
+// returns an info string containing all the cvars that have the given bit set
+// in their flags ( CVAR_USERINFO, CVAR_SERVERINFO, CVAR_SYSTEMINFO, etc )
+void Cvar_InfoStringBuffer(int bit, char *buff, int buffsize);
+void Cvar_CheckRange(cvar_t *cv, float minVal, float maxVal, bool shouldBeIntegral);
+void Cvar_SetDescription(cvar_t *var, const char *var_description);
+
+void Cvar_Restart(bool unsetVM);
+void Cvar_Restart_f(void);
+
+void Cvar_CompleteCvarName(char *args, int argNum);
+
+extern int cvar_modifiedFlags;
+// whenever a cvar is modifed, its flags will be OR'd into this, so
+// a single check can determine if any CVAR_USERINFO, CVAR_SERVERINFO,
+// etc, variables have been modified since the last check. The bit
+// can then be cleared to allow another change detection.
+
+#endif
diff --git a/src/qcommon/files.cpp b/src/qcommon/files.cpp
new file mode 100644
index 0000000..68b72b3
--- /dev/null
+++ b/src/qcommon/files.cpp
@@ -0,0 +1,3986 @@
+/*
+ Copyright (C) 2016 Victor Roemer (wtfbbqhax), <victor@badsec.org>.
+ Copyright (C) 2000-2013 Darklegion Development
+ Copyright (C) 1999-2005 Id Software, Inc.
+ Copyright (C) 2015-2019 GrangerHub
+
+ 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 3 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, see <https://www.gnu.org/licenses/>
+
+*/
+
+#include "files.h"
+
+#ifdef _WIN32
+#include <io.h>
+#include <windows.h>
+#endif
+
+#include <cctype>
+#include <cstdarg>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <string>
+
+#include "cmd.h"
+#include "cvar.h"
+#include "md4.h"
+#include "q_platform.h"
+#include "q_shared.h"
+#include "qcommon.h"
+#include "unzip.h"
+#include "vm.h"
+
+#ifndef DEDICATED
+#include "client/cl_rest.h"
+#endif
+#include "sys/sys_shared.h"
+
+using namespace std;
+
+#define MAX_ZPATH 256
+#define MAX_SEARCH_PATHS 4096
+#define MAX_FILEHASH_SIZE 1024
+
+static bool FS_IsDemoExt(const char *filename);
+static bool FS_IsExt(const char *filename, const char *ext, int namelen);
+
+struct fileInPack_t {
+ char* name;
+ unsigned long pos; // file info position in zip
+ unsigned long len; // uncompressed file size
+ fileInPack_t* next;
+};
+
+struct pack_t {
+ char pakPathname[MAX_OSPATH]; // /tremulous/baseq3
+ 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.
+ // some multiprotocol stuff
+ bool onlyPrimary;
+ bool onlyAlternate;
+ pack_t *primaryVersion;
+
+ // member functions
+ inline fileInPack_t* find(string filename);
+ inline bool is_pure();
+};
+
+struct directory_t {
+ char path[MAX_OSPATH];
+ char fullpath[MAX_OSPATH]; // /tremulous/base
+ char gamedir[MAX_OSPATH]; // base
+};
+
+struct searchpath_t {
+ pack_t *pack; // only one of pack / dir will be non nullptr
+ directory_t *dir;
+ searchpath_t *next;
+};
+
+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;
+#ifdef __APPLE__
+static cvar_t *fs_apppath; // Also search the .app bundle for .pk3 files
+#endif
+static cvar_t *fs_gamedirvar;
+
+static searchpath_t *fs_searchpaths;
+static int fs_readCount; // total bytes read
+static int fs_loadCount; // total files read
+static int fs_loadStack; // total files in memory
+static int fs_packFiles = 0; // total number of files in packs
+
+static int fs_checksumFeed;
+
+union qfile_gut {
+ FILE *o;
+ unzFile z;
+};
+
+struct qfile_ut {
+ qfile_gut file;
+ bool unique;
+};
+
+struct fileHandleData_t {
+ qfile_ut handleFiles;
+ bool handleSync;
+ int fileSize;
+ int zipFilePos;
+ int zipFileLen;
+ bool zipFile;
+ char name[MAX_ZPATH];
+
+ void close();
+};
+
+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 bool fs_reordered;
+
+// never load anything from pk3 files that are not present at the server when pure
+
+static int fs_numServerPaks = 0;
+static int fs_serverPaks[MAX_SEARCH_PATHS]; // checksums
+static char *fs_serverPakNames[MAX_SEARCH_PATHS]; // pk3 names
+
+// only used for autodownload, to make sure the client has at least
+// all the pk3 files that are referenced at the server side
+
+static int fs_numServerReferencedPaks;
+static int fs_serverReferencedPaks[MAX_SEARCH_PATHS]; // checksums
+static char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS]; // pk3 names
+
+// last valid game folder used
+char lastValidBase[MAX_OSPATH];
+char lastValidGame[MAX_OSPATH];
+
+#ifdef FS_MISSING
+FILE *missingFiles = nullptr;
+#endif
+
+/*
+==============
+FS_Initialized
+==============
+*/
+
+bool FS_Initialized(void) { return fs_searchpaths ? true : false; }
+
+/*
+=================
+pack_t::is_pure()
+
+FIXME: also use hashed file names
+=================
+*/
+inline bool pack_t::is_pure()
+{
+ if (fs_numServerPaks)
+ {
+ for (int i = 0; i < fs_numServerPaks; i++)
+ if (checksum == fs_serverPaks[i])
+ return true;
+
+ return false;
+ }
+ return true;
+}
+
+/*
+=================
+FS_LoadStack
+return load stack
+=================
+*/
+int FS_LoadStack(void) { return fs_loadStack; }
+
+inline fileInPack_t* pack_t::find(string filename)
+{
+ long hash = 0;
+ auto fn = filename.c_str();
+ for (long i = 0; fn[i]; i++)
+ {
+ long c = tolower(fn[i]);
+ if (c == '.')
+ break; // FIXME probably a bad idea
+
+ if (c == '\\')
+ c = '/';
+ hash += c * (i + 119);
+ }
+ hash = (hash ^ (hash >> 10) ^ (hash >> 20));
+ hash &= (hashSize - 1);
+
+ for (auto file = hashTable[hash]; file; file = file->next)
+ {
+ if (FS_FilenameCompare(file->name, fn) == false)
+ return file;
+ }
+ return nullptr;
+}
+/*
+================
+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)
+{
+ for (int i = 1; i < MAX_FILE_HANDLES; i++)
+ {
+ if (fsh[i].handleFiles.file.o == nullptr)
+ {
+ return i;
+ }
+ }
+ Com_Error(ERR_DROP, "FS_HandleForFile: none free");
+ return 0;
+}
+
+static FILE *FS_FileForHandle(fileHandle_t f)
+{
+ if (f < 1 || f >= MAX_FILE_HANDLES)
+ {
+ Com_Error(ERR_DROP, "FS_FileForHandle: out of range");
+ }
+
+ if (fsh[f].zipFile == true)
+ {
+ 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: nullptr");
+ }
+
+ return fsh[f].handleFiles.file.o;
+}
+
+void FS_ForceFlush(fileHandle_t f)
+{
+ FILE *file = FS_FileForHandle(f);
+ setvbuf(file, nullptr, _IONBF, 0);
+}
+
+/*
+================
+FS_fplength
+================
+*/
+
+long FS_fplength(FILE *h)
+{
+ long pos = ftell(h);
+ fseek(h, 0, SEEK_END);
+
+ long end = ftell(h);
+ fseek(h, pos, SEEK_SET);
+
+ return end;
+}
+
+/*
+================
+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.
+================
+*/
+long FS_filelength(fileHandle_t f)
+{
+ FILE *h = FS_FileForHandle(f);
+ if (h == nullptr) return -1;
+
+ return FS_fplength(h);
+}
+
+/*
+====================
+FS_ReplaceSeparators
+
+Fix things up differently for win/unix/mac
+====================
+*/
+void FS_ReplaceSeparators(char *path)
+{
+ bool lastCharWasSep = false;
+
+ for (char *s = path; *s; s++)
+ {
+ if (*s == '/' || *s == '\\')
+ {
+ if (!lastCharWasSep)
+ {
+ *s = PATH_SEP;
+ lastCharWasSep = true;
+ }
+ else
+ {
+ memmove(s, s + 1, strlen(s));
+ }
+ }
+ else
+ {
+ lastCharWasSep = false;
+ }
+ }
+}
+
+/*
+===================
+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];
+ // "FIXME FS_BuildOSPath() returns static buffer with function scope"
+
+ // This code will alternate between 2 different buffers-
+ // XXX 3 or more calls to FS_BuildOSPath in a row are not safe.
+ static char ospath[2][MAX_OSPATH];
+ static bool toggle;
+
+ toggle = !toggle; // 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);
+
+ Com_DPrintf(S_COLOR_GREEN "%s: returning " S_COLOR_RED "%s\n",
+ __FUNCTION__, ospath[toggle]);
+
+ return ospath[toggle];
+}
+
+/*
+============
+FS_OpenWithDefault
+
+Wrapper for Sys_OpenWithDefault()
+============
+*/
+static bool FS_OpenWithDefault( const char *path )
+{
+ if( Sys_OpenWithDefault( path ) )
+ {
+ // minimize the client's window
+ Cmd_ExecuteString( "minimize" );
+ return true;
+ }
+
+ return false;
+}
+
+/*
+============
+FS_BrowseHomepath
+
+Opens the homepath in the default file manager
+============
+*/
+bool FS_BrowseHomepath( void )
+{
+ const char *homePath = Sys_DefaultHomePath( );
+
+ if (!homePath || !homePath[0])
+ {
+ homePath = fs_basepath->string;
+ }
+
+ if( FS_OpenWithDefault( homePath ) )
+ return true;
+
+ Com_Printf( S_COLOR_RED "FS_BrowseHomepath: failed to open the homepath with the default file manager.\n" S_COLOR_WHITE );
+ return false;
+}
+
+/*
+============
+FS_OpenBaseGamePath
+
+Opens the given path for the
+base game in the default file manager
+============
+*/
+bool FS_OpenBaseGamePath( const char *baseGamePath )
+{
+ const char *homePath = Sys_DefaultHomePath( );
+ const char *path;
+
+ if (!homePath || !homePath[0])
+ {
+ homePath = fs_basepath->string;
+ }
+
+ path = FS_BuildOSPath( homePath, fs_basegame->string, baseGamePath);
+
+ if( FS_OpenWithDefault( path ) )
+ return true;
+
+ Com_Printf( S_COLOR_RED "FS_BrowseHomepath: failed to open the homepath with the default file manager.\n" S_COLOR_WHITE );
+ return false;
+}
+
+/*
+============
+FS_CreatePath
+
+Creates any directories needed to store the given filename
+============
+*/
+bool FS_CreatePath(const char *OSPath)
+{
+ // make absolutely sure that it can't back up the path
+ // FIXME: is c: allowed???
+ if (strstr(OSPath, "..") || strstr(OSPath, "::"))
+ {
+ Com_Printf("WARNING: refusing to create relative path \"%s\"\n", OSPath);
+ return true;
+ }
+
+ char path[MAX_OSPATH];
+ Q_strncpyz(path, OSPath, sizeof(path));
+ FS_ReplaceSeparators(path);
+
+ // Skip creation of the root directory as it will always be there
+ char *ofs = strchr(path, PATH_SEP);
+ if (ofs != nullptr)
+ {
+ ofs++;
+ }
+
+ for (; ofs != nullptr && *ofs; ofs++)
+ {
+ if (*ofs == PATH_SEP)
+ {
+ // create the directory
+ *ofs = 0;
+ if (!Sys_Mkdir(path))
+ {
+ Com_Error(ERR_FATAL, "FS_CreatePath: failed to create path \"%s\"", path);
+ }
+ *ofs = PATH_SEP;
+ }
+ }
+
+ return false;
+}
+
+/*
+=================
+FS_CheckFilenameIsMutable
+
+ERR_FATAL if trying to maniuplate a file with the platform library, QVM, or pk3 extension
+=================
+ */
+static void FS_CheckFilenameIsMutable(const char *filename, const char *function)
+{
+ // Check if the filename ends with the library, QVM, or pk3 extension
+ if (Sys_DllExtension(filename) || COM_CompareExtension(filename, ".qvm") ||
+ COM_CompareExtension(filename, ".lua") || COM_CompareExtension(filename, ".pk3"))
+ {
+ Com_Error(ERR_FATAL, "%s: Not allowed to manipulate '%s' due to %s extension",
+ function, filename, COM_GetExtension(filename));
+ }
+}
+
+/*
+===========
+FS_Remove
+
+===========
+*/
+void FS_Remove(const char *osPath)
+{
+ FS_CheckFilenameIsMutable(osPath, __FUNCTION__);
+// RB begin
+#if defined(_WIN32)
+ ::DeleteFile(osPath);
+#else
+ remove(osPath);
+#endif
+}
+
+/*
+===========
+FS_HomeRemove
+
+===========
+*/
+void FS_HomeRemove(const char *homePath)
+{
+ FS_CheckFilenameIsMutable(homePath, __FUNCTION__);
+ FS_Remove(FS_BuildOSPath(fs_homepath->string, fs_gamedir, homePath));
+}
+
+#if 0
+bool FS_RemoveDir(const char* relativePath)
+{
+ bool success = true;
+ success = Sys_Rmdir(FS_BuildOSPath(fs_homepath->string, fs_gamedir, relativePath));
+ return success;
+}
+#endif
+
+/*
+================
+FS_FileInPathExists
+
+Tests if path and file exists
+================
+*/
+bool FS_FileInPathExists(const char *testpath)
+{
+ FILE *filep;
+
+ filep = Sys_FOpen(testpath, "rb");
+
+ if (filep)
+ {
+ fclose(filep);
+ return true;
+ }
+
+ return false;
+}
+
+/*
+================
+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
+================
+*/
+bool FS_FileExists(const char *file)
+{
+ return FS_FileInPathExists(FS_BuildOSPath(fs_homepath->string, fs_gamedir, file));
+}
+
+/*
+================
+FS_SV_FileExists
+
+Tests if the file exists
+================
+*/
+bool FS_SV_FileExists(const char *file)
+{
+ char *testpath = FS_BuildOSPath(fs_homepath->string, file, "");
+ testpath[strlen(testpath) - 1] = '\0';
+
+ return FS_FileInPathExists(testpath);
+}
+
+/*
+===========
+FS_SV_FOpenFileWrite
+
+===========
+*/
+fileHandle_t FS_SV_FOpenFileWrite(const char *filename)
+{
+ if (!fs_searchpaths)
+ {
+ Com_Error(ERR_FATAL, "Filesystem call made without initialization");
+ }
+
+ char *ospath = FS_BuildOSPath(fs_homepath->string, filename, "");
+ ospath[strlen(ospath) - 1] = '\0';
+
+ fileHandle_t f = FS_HandleForFile();
+ fsh[f].zipFile = false;
+
+ Com_DPrintf("FS_SV_FOpenFileWrite: %s\n", ospath);
+
+ FS_CheckFilenameIsMutable(ospath, __FUNCTION__);
+
+ if (FS_CreatePath(ospath))
+ {
+ return 0;
+ }
+
+ Com_DPrintf("writing to: %s\n", ospath);
+ fsh[f].handleFiles.file.o = Sys_FOpen(ospath, "wb");
+
+ Q_strncpyz(fsh[f].name, filename, sizeof(fsh[f].name));
+
+ fsh[f].handleSync = false;
+ if (!fsh[f].handleFiles.file.o)
+ {
+ f = 0;
+ }
+
+ return f;
+}
+
+/*
+===========
+FS_SV_FOpenFileRead
+
+Search for a file somewhere below the home path then base path
+in that order
+===========
+*/
+long FS_SV_FOpenFileRead(const char *filename, fileHandle_t *fp)
+{
+ if (!fs_searchpaths)
+ {
+ Com_Error(ERR_FATAL, "Filesystem call made without initialization");
+ }
+
+ fileHandle_t f = FS_HandleForFile();
+ fsh[f].zipFile = false;
+
+ Q_strncpyz(fsh[f].name, filename, sizeof(fsh[f].name));
+
+ // don't let sound stutter
+ S_ClearSoundBuffer();
+
+ // search homepath
+ char *ospath = FS_BuildOSPath(fs_homepath->string, filename, "");
+ // remove trailing slash
+ ospath[strlen(ospath) - 1] = '\0';
+
+ Com_DPrintf("FS_SV_FOpenFileRead (fs_homepath): %s\n", ospath);
+
+ fsh[f].handleFiles.file.o = Sys_FOpen(ospath, "rb");
+ fsh[f].handleSync = false;
+ if (!fsh[f].handleFiles.file.o)
+ {
+ // If fs_homepath == fs_basepath, don't bother
+ if (Q_stricmp(fs_homepath->string, fs_basepath->string))
+ {
+ // search basepath
+ ospath = FS_BuildOSPath(fs_basepath->string, filename, "");
+ ospath[strlen(ospath) - 1] = '\0';
+
+ Com_DPrintf("FS_SV_FOpenFileRead (fs_basepath): %s\n", ospath);
+
+ fsh[f].handleFiles.file.o = Sys_FOpen(ospath, "rb");
+ fsh[f].handleSync = false;
+ }
+
+ if (!fsh[f].handleFiles.file.o)
+ {
+ f = 0;
+ }
+ }
+
+ *fp = f;
+ if (f)
+ {
+ return FS_filelength(f);
+ }
+
+ return -1;
+}
+
+/*
+===========
+FS_SV_Rename
+
+===========
+*/
+void FS_SV_Rename(const char *from, const char *to, bool safe)
+{
+ if (!fs_searchpaths)
+ {
+ Com_Error(ERR_FATAL, "Filesystem call made without initialization");
+ }
+
+ // don't let sound stutter
+ S_ClearSoundBuffer();
+
+ char *from_ospath = FS_BuildOSPath(fs_homepath->string, from, "");
+ char *to_ospath = FS_BuildOSPath(fs_homepath->string, to, "");
+
+ from_ospath[strlen(from_ospath) - 1] = '\0';
+ to_ospath[strlen(to_ospath) - 1] = '\0';
+
+ Com_DPrintf("FS_SV_Rename: (%s) %s --> %s\n", safe ? "safe" : "unsafe", from_ospath, to_ospath);
+
+ if (safe)
+ {
+ FS_CheckFilenameIsMutable(to_ospath, __FUNCTION__);
+ }
+
+ rename(from_ospath, to_ospath);
+}
+
+/*
+===========
+FS_Rename
+
+===========
+*/
+void FS_Rename(const char *from, const char *to)
+{
+ if (!fs_searchpaths)
+ {
+ Com_Error(ERR_FATAL, "Filesystem call made without initialization");
+ }
+
+ // don't let sound stutter
+ S_ClearSoundBuffer();
+
+ char *from_ospath = FS_BuildOSPath(fs_homepath->string, fs_gamedir, from);
+ char *to_ospath = FS_BuildOSPath(fs_homepath->string, fs_gamedir, to);
+
+ Com_DPrintf("FS_Rename: %s --> %s\n", from_ospath, to_ospath);
+
+ FS_CheckFilenameIsMutable(to_ospath, __FUNCTION__);
+
+ rename(from_ospath, to_ospath);
+}
+
+/*
+==============
+FS_FCloseFile
+
+If the FILE pointer is an open pak file, leave it open.
+
+For some reason, other dll's can't just cal fclose()
+on files returned by FS_FOpenFile...
+==============
+*/
+void fileHandleData_t::close()
+{
+ if (zipFile == true)
+ {
+ unzCloseCurrentFile(handleFiles.file.z);
+
+ if (handleFiles.unique)
+ unzClose(handleFiles.file.z);
+ }
+ // we didn't find it as a pak, so close it as a unique file
+ else if (handleFiles.file.o)
+ {
+ ::fclose(handleFiles.file.o);
+ }
+
+ ::memset(this, 0, sizeof(*this));
+}
+
+void FS_FCloseFile(fileHandle_t f)
+{
+ if (!fs_searchpaths)
+ Com_Error(ERR_FATAL, "Filesystem call made without initialization");
+
+ fsh[f].close();
+
+ ::memset(&fsh[f], 0, sizeof(fsh[f]));
+}
+
+/*
+===========
+FS_FOpenFileWrite
+
+===========
+*/
+fileHandle_t FS_FOpenFileWrite(const char *filename)
+{
+
+ if (!fs_searchpaths)
+ {
+ Com_Error(ERR_FATAL, "Filesystem call made without initialization");
+ }
+
+ fileHandle_t f = FS_HandleForFile();
+ fsh[f].zipFile = false;
+
+ char *ospath = FS_BuildOSPath(fs_homepath->string, fs_gamedir, filename);
+
+ Com_DPrintf("FS_FOpenFileWrite: %s\n", ospath);
+
+ FS_CheckFilenameIsMutable(ospath, __FUNCTION__);
+
+ 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 = Sys_FOpen(ospath, "wb");
+
+ Q_strncpyz(fsh[f].name, filename, sizeof(fsh[f].name));
+
+ fsh[f].handleSync = false;
+ 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");
+ }
+
+ f = FS_HandleForFile();
+ fsh[f].zipFile = false;
+
+ 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);
+
+ Com_DPrintf("FS_FOpenFileAppend: %s\n", ospath);
+
+ FS_CheckFilenameIsMutable(ospath, __FUNCTION__);
+
+ if (FS_CreatePath(ospath))
+ {
+ return 0;
+ }
+
+ fsh[f].handleFiles.file.o = Sys_FOpen(ospath, "ab");
+ fsh[f].handleSync = false;
+ if (!fsh[f].handleFiles.file.o)
+ {
+ f = 0;
+ }
+ return f;
+}
+
+/*
+===========
+FS_FCreateOpenPipeFile
+
+===========
+*/
+fileHandle_t FS_FCreateOpenPipeFile(const char *filename)
+{
+ if (!fs_searchpaths)
+ {
+ Com_Error(ERR_FATAL, "Filesystem call made without initialization");
+ }
+
+ fileHandle_t f = FS_HandleForFile();
+ fsh[f].zipFile = false;
+
+ Q_strncpyz(fsh[f].name, filename, sizeof(fsh[f].name));
+
+ // don't let sound stutter
+ S_ClearSoundBuffer();
+
+ char *ospath = FS_BuildOSPath(fs_homepath->string, fs_gamedir, filename);
+
+ Com_DPrintf("FS_FCreateOpenPipeFile: %s\n", ospath);
+
+ FS_CheckFilenameIsMutable(ospath, __FUNCTION__);
+
+ FILE *fifo = Sys_Mkfifo(ospath);
+ if (fifo)
+ {
+ fsh[f].handleFiles.file.o = fifo;
+ fsh[f].handleSync = false;
+ }
+ else
+ {
+ Com_Printf(S_COLOR_YELLOW
+ "WARNING: Could not create new com_pipefile at %s. "
+ "com_pipefile will not be used.\n",
+ ospath);
+ f = 0;
+ }
+
+ return f;
+}
+
+/*
+===========
+FS_FilenameCompare
+
+Ignore case and seprator char distinctions
+===========
+*/
+bool 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 true; // strings not equal
+ }
+ } while (c1);
+
+ return false; // strings are equal
+}
+
+/*
+===========
+FS_IsExt
+
+Return true if ext matches file extension filename
+===========
+*/
+static bool FS_IsExt(const char *filename, const char *ext, int namelen)
+{
+ int extlen = strlen(ext);
+
+ if (extlen > namelen) return false;
+
+ filename += namelen - extlen;
+
+ return !Q_stricmp(filename, ext);
+}
+
+/*
+===========
+FS_IsDemoExt
+
+Return true if filename has a demo extension
+===========
+*/
+
+static bool FS_IsDemoExt(const char *filename)
+{
+ const char *ext_test = strrchr(filename, '.');
+ if (ext_test && !Q_stricmpn(ext_test + 1, DEMOEXT, ARRAY_LEN(DEMOEXT) - 1))
+ {
+ int protocol = atoi(ext_test + ARRAY_LEN(DEMOEXT));
+ if (protocol == PROTOCOL_VERSION) return true;
+
+ for (int i = 0; demo_protocols[i]; i++)
+ if (demo_protocols[i] == protocol) return true;
+ }
+
+ return false;
+}
+
+/*
+===========
+FS_FOpenFileReadDir
+
+Tries opening file "filename" in searchpath "search"
+Returns filesize and an open FILE pointer.
+===========
+*/
+long FS_FOpenFileReadDir(
+ const char *filename, void *_search, fileHandle_t *file, bool uniqueFILE, bool unpure)
+{
+ pack_t *pak;
+ directory_t *dir;
+ char *netpath;
+ FILE *filep;
+ int len;
+
+ searchpath_t *search = static_cast<searchpath_t *>(_search);
+
+ if (filename == nullptr)
+ Com_Error(ERR_FATAL, "FS_FOpenFileRead: nullptr 'filename' parameter passed");
+
+ // 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, "::"))
+ {
+ if (file == nullptr) return false;
+
+ *file = 0;
+ return -1;
+ }
+
+ if (file == nullptr)
+ {
+ // just wants to see if file is there
+
+ if ( fs_debug->integer )
+ {
+ Com_Printf(S_COLOR_GREEN "Searching for: " S_COLOR_RED "%s\n", filename);
+ }
+
+ // is the element a pak file?
+ if (search->pack)
+ {
+ auto pakfile = search->pack->find(filename);
+ if (pakfile)
+ {
+ // found it!
+ if (!pakfile->len)
+ {
+ // FIXME: It's not nice, but legacy code depends on
+ // positive value if file exists no matter what size
+ return 1;
+ }
+
+ return pakfile->len;
+ }
+ }
+ else if (search->dir)
+ {
+ dir = search->dir;
+
+ netpath = FS_BuildOSPath(dir->path, dir->gamedir, filename);
+ filep = Sys_FOpen(netpath, "rb");
+
+ if (filep)
+ {
+ len = FS_fplength(filep);
+ fclose(filep);
+
+ if (len)
+ return len;
+ else
+ return 1;
+ }
+ }
+
+ return 0;
+ }
+
+ *file = FS_HandleForFile();
+ fsh[*file].handleFiles.unique = uniqueFILE;
+
+ // is the element a pak file?
+ if (search->pack)
+ {
+ pak = search->pack;
+ auto pakfile = pak->find(filename);
+ if (pakfile )
+ {
+ if ( fs_debug->integer == 2 )
+ Com_Printf(S_COLOR_GREEN "#2 Searching for: " S_COLOR_RED "%s\n", filename);
+
+ // disregard if it doesn't match one of the allowed pure pak files
+ if (!unpure && !pak->is_pure())
+ {
+ if ( fs_debug->integer == 2 )
+ Com_Printf(S_COLOR_GREEN "Ugh-oh %s found in unpure pk3\n", filename);
+
+ *file = 0;
+ return -1;
+ }
+
+ len = strlen(filename);
+
+ if (!(pak->referenced & FS_GENERAL_REF))
+ {
+ if ( !FS_IsExt(filename, ".shader", len)
+ && !FS_IsExt(filename, ".mtr", len)
+ && !FS_IsExt(filename, ".txt", len)
+ && !FS_IsExt(filename, ".cfg", len)
+ && !FS_IsExt(filename, ".config", len)
+ && !FS_IsExt(filename, ".arena", len)
+ && !FS_IsExt(filename, ".menu", len)
+ && !strstr(filename, "levelshots") )
+ {
+ pak->referenced |= FS_GENERAL_REF;
+ }
+ }
+
+ if (strstr(filename, "cgame.qvm"))
+ pak->referenced |= FS_CGAME_REF;
+
+ if (strstr(filename, "ui.qvm"))
+ pak->referenced |= FS_UI_REF;
+
+ if (uniqueFILE)
+ {
+ fsh[*file].handleFiles.file.z = unzOpen(pak->pakFilename);
+ if ( !fsh[*file].handleFiles.file.z )
+ Com_Error(ERR_FATAL, "Couldn't open %s", pak->pakFilename);
+ }
+ else
+ {
+ fsh[*file].handleFiles.file.z = pak->handle;
+ }
+
+ Q_strncpyz(fsh[*file].name, filename, sizeof(fsh[*file].name));
+ fsh[*file].zipFile = true;
+
+ // set the file position in the zip file (also sets the current file info)
+ unzSetOffset(fsh[*file].handleFiles.file.z, pakfile->pos);
+
+ // open the file in the zip
+ unzOpenCurrentFile(fsh[*file].handleFiles.file.z);
+ fsh[*file].zipFilePos = pakfile->pos;
+ fsh[*file].zipFileLen = pakfile->len;
+
+ if ( fs_debug->integer )
+ {
+ Com_Printf( "FS_FOpenFileRead: %s (found in '%s')\n",
+ filename, pak->pakFilename);
+ }
+
+ return pakfile->len;
+ }
+ }
+ 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
+ len = 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 (!unpure && fs_numServerPaks)
+ {
+ if (!FS_IsExt(filename, ".cfg", len) && // for config files
+ !FS_IsExt(filename, ".lua", len) && // lua
+ !FS_IsExt(filename, ".menu", len) && // menu files
+ !FS_IsExt(filename, ".game", len) && // menu files
+ !FS_IsExt(filename, ".dat", len) && // for journal files
+ !FS_IsDemoExt(filename)) // demos
+ {
+ *file = 0;
+ return -1;
+ }
+ }
+
+ dir = search->dir;
+
+ netpath = FS_BuildOSPath(dir->path, dir->gamedir, filename);
+ filep = Sys_FOpen(netpath, "rb");
+
+ if (filep == nullptr)
+ {
+ *file = 0;
+ return -1;
+ }
+
+ Q_strncpyz(fsh[*file].name, filename, sizeof(fsh[*file].name));
+ fsh[*file].zipFile = false;
+
+ if (fs_debug->integer)
+ {
+ Com_Printf("FS_FOpenFileRead: %s (found in '%s%c%s')\n",
+ filename, dir->path, PATH_SEP, dir->gamedir);
+ }
+
+ fsh[*file].handleFiles.file.o = filep;
+ return FS_fplength(filep);
+ }
+
+ *file = 0;
+ return -1;
+}
+
+/*
+===========
+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.
+===========
+*/
+long FS_FOpenFileRead(const char *filename, fileHandle_t *file, bool uniqueFILE)
+{
+ searchpath_t *search;
+ long len;
+
+ if (!fs_searchpaths) Com_Error(ERR_FATAL, "Filesystem call made without initialization");
+
+ bool isLocalConfig = !strcmp(filename, "autoexec.cfg") || !strcmp(filename, Q3CONFIG_CFG);
+ for (search = fs_searchpaths; search; search = search->next)
+ {
+ // autoexec.cfg and q3config.cfg can only be loaded outside of pk3 files.
+ if (isLocalConfig && search->pack) continue;
+
+ len = FS_FOpenFileReadDir(filename, search, file, uniqueFILE, false);
+
+ if (file == nullptr)
+ {
+ if (len > 0) return len;
+ }
+ else
+ {
+ if (len >= 0 && *file) return len;
+ }
+ }
+
+#ifdef FS_MISSING
+ if (missingFiles) fprintf(missingFiles, "%s\n", filename);
+#endif
+
+ if (file)
+ {
+ *file = 0;
+ return -1;
+ }
+ else
+ {
+ // When file is nullptr, we're querying the existance of the file
+ // If we've got here, it doesn't exist
+ return 0;
+ }
+}
+
+/*
+=================
+FS_FindVM
+
+Find a suitable VM file in search path order.
+
+In each searchpath try:
+ - open DLL file if DLL loading enabled
+ - open QVM file
+
+Enable search for DLL by setting enableDll to FSVM_ENABLEDLL
+
+write found DLL or QVM to "found" and return VMI_NATIVE if DLL, VMI_COMPILED if QVM
+Return the searchpath in "startSearch".
+=================
+*/
+
+int FS_FindVM(void **startSearch, char *found, int foundlen, const char *name, int enableDll)
+{
+ searchpath_t *search, *lastSearch;
+ directory_t *dir;
+ pack_t *pack;
+ char dllName[MAX_OSPATH], qvmName[MAX_OSPATH];
+ char *netpath;
+
+ if (!fs_searchpaths) Com_Error(ERR_FATAL, "Filesystem call made without initialization");
+
+ if (enableDll) Com_sprintf(dllName, sizeof(dllName), "%s" DLL_EXT, name);
+
+ Com_sprintf(qvmName, sizeof(qvmName), "vm/%s.qvm", name);
+
+ lastSearch = static_cast<searchpath_t *>(*startSearch);
+ if (*startSearch == nullptr)
+ search = fs_searchpaths;
+ else
+ search = lastSearch->next;
+
+ while (search)
+ {
+ if (search->dir && (!fs_numServerPaks || !strcmp(name, "game")))
+ {
+ dir = search->dir;
+
+ if (enableDll)
+ {
+ netpath = FS_BuildOSPath(dir->path, dir->gamedir, dllName);
+
+ if (FS_FileInPathExists(netpath))
+ {
+ Q_strncpyz(found, netpath, foundlen);
+ *startSearch = search;
+
+ return VMI_NATIVE;
+ }
+ }
+
+ if (FS_FOpenFileReadDir(qvmName, search, nullptr, false, false) > 0)
+ {
+ *startSearch = search;
+ return VMI_COMPILED;
+ }
+ }
+ else if (search->pack)
+ {
+ pack = search->pack;
+
+ if (lastSearch && lastSearch->pack)
+ {
+ // make sure we only try loading one VM file per game dir
+ // i.e. if VM from pak7.pk3 fails we won't try one from pak6.pk3
+
+ if (!FS_FilenameCompare(lastSearch->pack->pakPathname, pack->pakPathname))
+ {
+ search = search->next;
+ continue;
+ }
+ }
+
+ if (FS_FOpenFileReadDir(qvmName, search, nullptr, false, false) > 0)
+ {
+ *startSearch = search;
+
+ return VMI_COMPILED;
+ }
+ }
+
+ search = search->next;
+ }
+
+ return -1;
+}
+
+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");
+ }
+
+ if (!f)
+ {
+ return 0;
+ }
+
+ buf = (byte *)buffer;
+ fs_readCount += len;
+
+ if (fsh[f].zipFile == false)
+ {
+ 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)
+{
+ if (!fs_searchpaths)
+ {
+ Com_Error(ERR_FATAL, "Filesystem call made without initialization");
+ }
+
+ if (!h)
+ {
+ return 0;
+ }
+
+ FILE *f = FS_FileForHandle(h);
+ byte *buf = (byte *)buffer;
+
+ int remaining = len;
+ int tries = 0;
+ while (remaining)
+ {
+ int block = remaining;
+ int 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, enum FS_Origin origin)
+{
+ if (!fs_searchpaths)
+ {
+ Com_Error(ERR_FATAL, "Filesystem call made without initialization");
+ return -1;
+ }
+
+ if (fsh[f].zipFile == true)
+ {
+ // FIXME: this is really, really crappy
+ //(but better than what was here before)
+ byte buffer[PK3_SEEK_BUFFER_SIZE];
+ int remainder;
+ int currentPosition = FS_FTell(f);
+
+ // change negative offsets into FS_SEEK_SET
+ if (offset < 0)
+ {
+ switch (origin)
+ {
+ case FS_SEEK_END:
+ remainder = fsh[f].zipFileLen + offset;
+ break;
+
+ case FS_SEEK_CUR:
+ remainder = currentPosition + offset;
+ break;
+
+ case FS_SEEK_SET:
+ default:
+ remainder = 0;
+ break;
+ }
+
+ if (remainder < 0)
+ {
+ remainder = 0;
+ }
+
+ origin = FS_SEEK_SET;
+ }
+ else
+ {
+ if (origin == FS_SEEK_END)
+ {
+ remainder = fsh[f].zipFileLen - currentPosition + offset;
+ }
+ else
+ {
+ remainder = offset;
+ }
+ }
+
+ switch (origin)
+ {
+ case FS_SEEK_SET:
+ if (remainder == currentPosition)
+ {
+ return offset;
+ }
+ unzSetOffset(fsh[f].handleFiles.file.z, fsh[f].zipFilePos);
+ unzOpenCurrentFile(fsh[f].handleFiles.file.z);
+ // fallthrough
+
+ case FS_SEEK_END: // fall through
+ 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;
+
+ default:
+ Com_Error(ERR_FATAL, "Bad origin in FS_Seek");
+ return -1;
+ }
+ }
+ else
+ {
+ FILE *file;
+ file = FS_FileForHandle(f);
+ int _origin;
+ 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:
+ Com_Error(ERR_FATAL, "Bad origin in FS_Seek");
+ break;
+ }
+
+ return fseek(file, offset, _origin);
+ }
+}
+
+/*
+======================================================================================
+
+CONVENIENCE FUNCTIONS FOR ENTIRE FILES
+
+======================================================================================
+*/
+
+int FS_FileIsInPAK_A(bool alternate, const char *filename, int *pChecksum)
+{
+ if (!fs_searchpaths)
+ Com_Error(ERR_FATAL, "Filesystem call made without initialization");
+
+ if (!filename)
+ Com_Error(ERR_FATAL, "FS_FOpenFileRead: nullptr 'filename' parameter passed");
+
+ // 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 (auto search = fs_searchpaths; search; search = search->next)
+ {
+ if (!search->pack)
+ continue;
+
+ // disregard if it doesn't match one of the allowed pure pak files
+ if (!search->pack->is_pure())
+ continue;
+
+ if ((alternate && search->pack->onlyPrimary) ||
+ (!alternate && search->pack->onlyAlternate))
+ continue;
+
+ auto found = search->pack->find(filename);
+ if (found)
+ {
+ if (pChecksum)
+ *pChecksum = search->pack->pure_checksum;
+
+ return 1;
+ }
+ }
+ return -1;
+}
+
+int FS_FileIsInPAK(const char *filename, int *pChecksum)
+{
+ return FS_FileIsInPAK_A(false, filename, pChecksum);
+}
+/*
+============
+FS_ReadFileDir
+
+Filename are relative to the quake search path
+a null buffer will just return the file length without loading
+If searchPath is non-nullptr search only in that specific search path
+============
+*/
+long FS_ReadFileDir(const char *qpath, void *searchPath, bool unpure, void **buffer)
+{
+ fileHandle_t h;
+ byte *buf;
+ bool isConfig;
+ long len;
+
+ if (!fs_searchpaths)
+ {
+ Com_Error(ERR_FATAL, "Filesystem call made without initialization");
+ }
+
+ if (!qpath || !qpath[0])
+ {
+ Com_Error(ERR_FATAL, "FS_ReadFile with empty name");
+ }
+
+ buf = nullptr; // 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 = true;
+ if (com_journal && com_journal->integer == 2)
+ {
+ Com_DPrintf("Loading %s from journal file.\n", qpath);
+
+ int r = FS_Read(&len, sizeof(len), com_journalDataFile);
+ if (r != sizeof(len))
+ {
+ if (buffer != nullptr) *buffer = nullptr;
+ return -1;
+ }
+
+ // if the file didn't exist when the journal was created
+ if (!len)
+ {
+ if (buffer == nullptr)
+ {
+ return 1; // hack for old journal files
+ }
+ *buffer = nullptr;
+ return -1;
+ }
+
+ if (buffer == nullptr)
+ {
+ return len;
+ }
+
+ buf = static_cast<byte *>(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 = false;
+ }
+
+ searchpath_t *search = static_cast<searchpath_t *>(searchPath);
+ if (search == nullptr)
+ {
+ // look for it in the filesystem or pack files
+ len = FS_FOpenFileRead(qpath, &h, false);
+ }
+ else
+ {
+ // look for it in a specific search path only
+ len = FS_FOpenFileReadDir(qpath, search, &h, false, unpure);
+ }
+
+ if (h == 0)
+ {
+ if (buffer)
+ {
+ *buffer = nullptr;
+ }
+ // 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 = static_cast<byte *>(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_ReadFile
+
+Filename are relative to the quake search path
+a null buffer will just return the file length without loading
+============
+*/
+long FS_ReadFile(const char *qpath, void **buffer)
+{
+ return FS_ReadFileDir(qpath, nullptr, false, buffer);
+}
+/*
+=============
+FS_FreeFile
+=============
+*/
+void FS_FreeFile(void *buffer)
+{
+ if (!fs_searchpaths)
+ {
+ Com_Error(ERR_FATAL, "Filesystem call made without initialization");
+ }
+ if (!buffer)
+ {
+ Com_Error(ERR_FATAL, "FS_FreeFile( nullptr )");
+ }
+
+ 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 relative to the quake search path
+============
+*/
+void FS_WriteFile(const char *qpath, const void *buffer, int size)
+{
+ if (!fs_searchpaths)
+ {
+ Com_Error(ERR_FATAL, "Filesystem call made without initialization");
+ }
+
+ if (!qpath || !buffer)
+ {
+ Com_Error(ERR_FATAL, "FS_WriteFile: nullptr parameter");
+ }
+
+ fileHandle_t 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 pack_t in the search chain for the contents of a zip file.
+=================
+*/
+static pack_t *FS_LoadZipFile(const char *zipfile, const char *basename)
+{
+ int fs_numHeaderLongs = 0;
+ unsigned long len = 0;
+ char filename[MAX_ZPATH];
+
+ auto z = unzOpen(zipfile);
+
+ unz_global_info gi;
+ int err = unzGetGlobalInfo(z, &gi);
+ if (err) return nullptr;
+
+ err = unzGoToFirstFile(z);
+ if (err) return nullptr;
+
+ for (uLong i = 0; i < gi.number_entry; i++)
+ {
+ unz_file_info fi;
+ err = unzGetCurrentFileInfo(
+ z, &fi, filename, sizeof(filename), nullptr, 0, nullptr, 0);
+
+ if (err) break;
+
+ len += strlen(filename) + 1;
+ unzGoToNextFile(z);
+ }
+
+ fileInPack_t *buildBuffer =
+ static_cast<fileInPack_t *>(Z_Malloc((gi.number_entry * sizeof(fileInPack_t)) + len));
+
+ char *namePtr = ((char *)buildBuffer) + gi.number_entry * sizeof(fileInPack_t);
+
+ int *fs_headerLongs = static_cast<int *>(Z_Malloc((gi.number_entry + 1) * sizeof(int)));
+
+ fs_headerLongs[fs_numHeaderLongs] = LittleLong(fs_checksumFeed);
+ fs_numHeaderLongs++;
+
+ // 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
+ uLong hashsiz;
+ for (hashsiz = 1; hashsiz <= MAX_FILEHASH_SIZE; hashsiz <<= 1)
+ {
+ if (hashsiz > gi.number_entry) break;
+ }
+
+ pack_t *pack = static_cast<pack_t *>(Z_Malloc(sizeof(pack_t) + hashsiz * sizeof(fileInPack_t *)));
+
+ pack->hashSize = hashsiz;
+ pack->hashTable = (fileInPack_t **)(((char *)pack) + sizeof(pack_t));
+
+ for (int i = 0; i < pack->hashSize; i++)
+ {
+ pack->hashTable[i] = nullptr;
+ }
+
+ 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 = z;
+ pack->numfiles = gi.number_entry;
+ unzGoToFirstFile(z);
+ for (uLong i = 0; i < gi.number_entry; i++)
+ {
+ unz_file_info fi;
+ err = unzGetCurrentFileInfo(
+ z, &fi, filename, sizeof(filename), nullptr, 0, nullptr, 0);
+
+ if (err) break;
+
+ if (fi.uncompressed_size)
+ {
+ fs_headerLongs[fs_numHeaderLongs] = LittleLong(fi.crc);
+ fs_numHeaderLongs++;
+ }
+
+ Q_strlwr(filename);
+ long hash = FS_HashFileName(filename, pack->hashSize);
+
+ buildBuffer[i].name = namePtr;
+ strcpy(buildBuffer[i].name, filename);
+ namePtr += strlen(filename) + 1;
+
+ // store the file position in the zip
+ buildBuffer[i].pos = unzGetOffset(z);
+ buildBuffer[i].len = fi.uncompressed_size;
+ buildBuffer[i].next = pack->hashTable[hash];
+
+ pack->hashTable[hash] = &buildBuffer[i];
+ unzGoToNextFile(z);
+ }
+
+ pack->checksum =
+ Com_BlockChecksum(&fs_headerLongs[1], sizeof(*fs_headerLongs) * (fs_numHeaderLongs - 1));
+ pack->pure_checksum =
+ Com_BlockChecksum(fs_headerLongs, sizeof(*fs_headerLongs) * fs_numHeaderLongs);
+ pack->checksum = LittleLong(pack->checksum);
+ pack->pure_checksum = LittleLong(pack->pure_checksum);
+
+ Z_Free(fs_headerLongs);
+
+ pack->buildBuffer = buildBuffer;
+ return pack;
+}
+
+/*
+=================
+FS_FreePak
+
+Frees a pak structure and releases all associated resources
+=================
+*/
+
+static void FS_FreePak(pack_t *thepak)
+{
+ unzClose(thepak->handle);
+ Z_Free(thepak->buildBuffer);
+ Z_Free(thepak);
+}
+
+/*
+=================
+FS_GetZipChecksum
+
+Compares whether the given pak file matches a referenced checksum
+=================
+*/
+bool FS_CompareZipChecksum(const char *zipfile)
+{
+ pack_t *thepak = FS_LoadZipFile(zipfile, "");
+
+ if (!thepak) return false;
+
+ int checksum = thepak->checksum;
+ FS_FreePak(thepak);
+
+ for (int i = 0; i < fs_numServerReferencedPaks; i++)
+ {
+ if (checksum == fs_serverReferencedPaks[i]) return true;
+ }
+
+ return false;
+}
+
+/*
+=================================================================================
+
+DIRECTORY SCANNING FUNCTIONS
+
+=================================================================================
+*/
+
+#define MAX_FOUND_FILES 0x1000
+
+static int FS_ReturnPath(const char *zname, char *zpath, int *depth)
+{
+ int newdep = 0;
+ int len = 0;
+ int at = 0;
+ zpath[0] = 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)
+{
+ if (nfiles == MAX_FOUND_FILES - 1)
+ {
+ return nfiles;
+ }
+
+ for (int 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, const char *filter,
+ int *numfiles, bool allowNonPureFilesOnDisk)
+{
+ int nfiles;
+ char **listCopy;
+ char *list[MAX_FOUND_FILES];
+ searchpath_t *search;
+ int i;
+ int pathLength;
+ int extensionLength;
+ int length, pathDepth, temp;
+ pack_t *pak;
+ fileInPack_t *buildBuffer;
+ char zpath[MAX_ZPATH];
+
+ if (!fs_searchpaths)
+ {
+ Com_Error(ERR_FATAL, "Filesystem call made without initialization");
+ }
+
+ if (!path)
+ {
+ *numfiles = 0;
+ return nullptr;
+ }
+ 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 (!search->pack->is_pure())
+ {
+ 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, false)) 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
+
+ // don't scan directories for files if we are pure or restricted
+ if (fs_numServerPaks && !allowNonPureFilesOnDisk)
+ {
+ continue;
+ }
+ else
+ {
+ int numSysFiles;
+ char *netpath = FS_BuildOSPath(search->dir->path, search->dir->gamedir, path);
+ char **sysFiles = Sys_ListFiles(netpath, extension, filter, &numSysFiles, false);
+ for (i = 0; i < numSysFiles; i++)
+ {
+ // unique the match
+ char *name = sysFiles[i];
+ nfiles = FS_AddFileToList(name, list, nfiles);
+ }
+ Sys_FreeFileList(sysFiles);
+ }
+ }
+ }
+
+ // return a copy of the list
+ *numfiles = nfiles;
+
+ if (!nfiles)
+ {
+ return nullptr;
+ }
+
+ listCopy = static_cast<char **>(Z_Malloc((nfiles + 1) * sizeof(*listCopy)));
+ for (i = 0; i < nfiles; i++)
+ {
+ listCopy[i] = list[i];
+ }
+ listCopy[i] = nullptr;
+
+ return listCopy;
+}
+
+/*
+=================
+FS_ListFiles
+=================
+*/
+char **FS_ListFiles(const char *path, const char *extension, int *numfiles)
+{
+ return FS_ListFilteredFiles(path, extension, nullptr, numfiles, false);
+}
+
+/*
+=================
+FS_FreeFileList
+=================
+*/
+void FS_FreeFileList(char **list)
+{
+ if (!fs_searchpaths)
+ {
+ Com_Error(ERR_FATAL, "Filesystem call made without initialization");
+ }
+
+ if (!list)
+ {
+ return;
+ }
+
+ for (int 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 = 0;
+ int nTotal = 0;
+ *listbuf = 0;
+
+ if (Q_stricmp(path, "$modlist") == 0)
+ {
+ return FS_GetModList(listbuf, bufsize);
+ }
+
+ char **pFiles = FS_ListFiles(path, extension, &nFiles);
+
+ for (int i = 0; i < nFiles; i++)
+ {
+ int 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;
+}
+
+/*
+================
+FS_GetFilteredFiles
+================
+*/
+int FS_GetFilteredFiles(
+ const char *path, const char *extension, const char *filter, char *listbuf, int bufsize)
+{
+ int nFiles = 0;
+ int nTotal = 0;
+ *listbuf = 0;
+
+ char **pFiles = FS_ListFilteredFiles(path, extension, filter, &nFiles, false);
+
+ for (int i = 0; i < nFiles; i++)
+ {
+ int 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)
+{
+ unsigned int i = 0;
+ if (list)
+ {
+ while (*list)
+ {
+ list++;
+ i++;
+ }
+ }
+ return i;
+}
+
+static char **Sys_ConcatenateFileLists(char **list0, char **list1)
+{
+ char **cat, **dst;
+
+ int totalLength = 0;
+ totalLength += Sys_CountFileList(list0);
+ totalLength += Sys_CountFileList(list1);
+
+ /* Create new list. */
+ dst = cat = static_cast<char **>(Z_Malloc((totalLength + 1) * sizeof(char *)));
+
+ /* Copy over lists. */
+ if (list0)
+ {
+ for (char **src = list0; *src; src++, dst++) *dst = *src;
+ }
+
+ if (list1)
+ {
+ for (char **src = list1; *src; src++, dst++) *dst = *src;
+ }
+
+ // Terminate the list
+ *dst = nullptr;
+
+ // Free our old lists.
+ // NOTE: not freeing their content, it's been merged in dst and still being used
+ if (list0) Z_Free(list0);
+ if (list1) Z_Free(list1);
+
+ return cat;
+}
+
+/*
+================
+FS_GetModList
+
+Returns a list of mod directory names
+A mod directory is a peer to baseq3 with a pk3 or pk3dir in it
+================
+*/
+int FS_GetModList( char *listbuf, int bufsize )
+{
+ char * start = listbuf;
+ *listbuf = '\0';
+
+ // paths to search for mods
+ const char * const paths[] = {
+ fs_basepath->string,
+ fs_homepath->string
+ };
+
+ char **pFiles = nullptr;
+ for (int i = 0; i < ARRAY_LEN(paths); i++)
+ {
+ int dummy;
+ char **pFiles0 = Sys_ListFiles(paths[i], NULL, NULL, &dummy, true);
+ pFiles = Sys_ConcatenateFileLists(pFiles, pFiles0);
+ }
+
+ int nMods = 0;
+ int nTotal = 0;
+ for (int i = 0; i < Sys_CountFileList(pFiles); i++)
+ {
+ const char* name = pFiles[i];
+
+ if ( name[0] == '.' )
+ continue;
+
+ // In order to be a valid mod the directory must contain at least one
+ // .pk3 or .pk3dir we didn't keep the information when we merged the
+ // directory names, as to what OS Path it was found under so we will
+ // try each of them here.
+ int nPaks = 0;
+ int nPakDirs = 0;
+ for (int j = 0; j < ARRAY_LEN(paths); j++)
+ {
+ const char* path = FS_BuildOSPath(paths[j], name, "");
+ int nDirs = 0;
+
+ char **pPaks = Sys_ListFiles(path, ".pk3", NULL, &nPaks, false);
+ char **pDirs = Sys_ListFiles(path, "/", NULL, &nDirs, false);
+ for (int k = 0; k < nDirs; k++)
+ {
+ // we only want to count directories ending with ".pk3dir"
+ if (FS_IsExt(pDirs[k], ".pk3dir", strlen(pDirs[k])))
+ nPakDirs++;
+ }
+
+ // we only use Sys_ListFiles to check whether files are present
+ Sys_FreeFileList(pPaks);
+ Sys_FreeFileList(pDirs);
+
+ if (nPaks > 0 || nPakDirs > 0)
+ break;
+ }
+
+ if (nPaks > 0 || nPakDirs > 0)
+ {
+ size_t nLen = strlen(name) + 1;
+
+ if (nTotal + nLen + 1 < bufsize)
+ {
+ strcpy(listbuf, name);
+ listbuf += nLen;
+ nTotal += nLen;
+ nMods++;
+ }
+ else
+ {
+ Com_Printf(S_COLOR_RED "Warning: Too many mods!\n");
+ break;
+ }
+ }
+ }
+
+ Sys_FreeFileList( pFiles );
+ return nMods;
+}
+
+//============================================================================
+
+/*
+================
+FS_Dir_f
+================
+*/
+void FS_Dir_f(void)
+{
+ const char *path;
+ const char *extension;
+
+ 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");
+
+ int ndirs;
+ char **dirnames = FS_ListFiles(path, extension, &ndirs);
+
+ for (int 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
+ }
+ else if (c1 > c2)
+ {
+ return 1;
+ }
+ } while (c1);
+
+ return 0; // strings are equal
+}
+
+/*
+================
+FS_SortFileList
+================
+*/
+void FS_SortFileList(char **filelist, int numfiles)
+{
+ char **sortedlist = static_cast<char **>(Z_Malloc((numfiles + 1) * sizeof(*sortedlist)));
+ sortedlist[0] = nullptr;
+
+ int numsortedfiles = 0;
+ for (int i = 0; i < numfiles; i++)
+ {
+ int j;
+ for (j = 0; j < numsortedfiles; j++)
+ {
+ if (FS_PathCmp(filelist[i], sortedlist[j]) < 0) break;
+ }
+
+ int k;
+ for (k = numsortedfiles; k > j; k--)
+ {
+ sortedlist[k] = sortedlist[k - 1];
+ }
+
+ sortedlist[j] = filelist[i];
+ numsortedfiles++;
+ }
+ ::memcpy(filelist, sortedlist, numfiles * sizeof(*filelist));
+ Z_Free(sortedlist);
+}
+
+/*
+================
+FS_NewDir_f
+================
+*/
+void FS_NewDir_f(void)
+{
+ if (Cmd_Argc() < 2)
+ {
+ Com_Printf("usage: fdir <filter>\n");
+ Com_Printf("example: fdir *q3dm*.bsp\n");
+ return;
+ }
+
+ Com_Printf("---------------\n");
+
+ const char *filter = Cmd_Argv(1);
+
+ int ndirs;
+ char **dirnames = FS_ListFilteredFiles("", "", filter, &ndirs, false);
+
+ FS_SortFileList(dirnames, ndirs);
+ for (int 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)
+{
+ Com_Printf("We are looking in the current search path:\n");
+
+ for (auto s = fs_searchpaths; s; s = s->next)
+ {
+ if (s->pack)
+ {
+ Com_Printf("%s (%i files%s)\n",
+ s->pack->pakFilename,
+ s->pack->numfiles,
+ s->pack->onlyPrimary ? ", not for 1.1"
+ : s->pack->onlyAlternate ? ", only for 1.1" : "");
+
+ if (s->pack->primaryVersion)
+ Com_Printf(" (the 1.1 version of %s)\n",
+ s->pack->primaryVersion->pakFilename);
+
+ if (fs_numServerPaks)
+ {
+ if (!s->pack->is_pure())
+ {
+ Com_Printf(" not on the pure list\n");
+ }
+ else
+ {
+ Com_Printf(" on the pure list\n");
+ }
+ }
+ }
+ else
+ {
+ Com_Printf("%s%c%s\n", s->dir->path, PATH_SEP, s->dir->gamedir);
+ }
+ }
+
+ Com_Printf("\n");
+ for (int i = 1; i < MAX_FILE_HANDLES; i++)
+ {
+ if (fsh[i].handleFiles.file.o)
+ {
+ Com_Printf("handle %i: %s\n", i, fsh[i].name);
+ }
+ }
+}
+
+/*
+============
+FS_TouchFile_f
+============
+*/
+void FS_TouchFile_f(void)
+{
+ if (Cmd_Argc() != 2)
+ {
+ Com_Printf("Usage: touchFile <file>\n");
+ return;
+ }
+
+ fileHandle_t f;
+ FS_FOpenFileRead(Cmd_Argv(1), &f, false);
+ if (f)
+ {
+ FS_FCloseFile(f);
+ }
+}
+
+/*
+============
+FS_Which
+============
+*/
+
+bool FS_Which(const char *filename, void *searchPath)
+{
+ searchpath_t *search = static_cast<searchpath_t *>(searchPath);
+
+ if (FS_FOpenFileReadDir(filename, search, nullptr, false, false) > 0)
+ {
+ if (search->pack)
+ {
+ Com_Printf("File \"%s\" found in \"%s\"\n", filename, search->pack->pakFilename);
+ return true;
+ }
+ else if (search->dir)
+ {
+ Com_Printf("File \"%s\" found at \"%s\"\n", filename, search->dir->fullpath);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+============
+FS_Which_f
+============
+*/
+void FS_Which_f(void)
+{
+ const char *filename = Cmd_Argv(1);
+
+ if (!filename[0])
+ {
+ Com_Printf("Usage: which <file>\n");
+ return;
+ }
+
+ // qpaths are not supposed to have a leading slash
+ if (filename[0] == '/' || filename[0] == '\\') filename++;
+
+ // just wants to see if file is there
+ for (searchpath_t *search = fs_searchpaths; search; search = search->next)
+ if (FS_Which(filename, search)) return;
+
+ Com_Printf("File not found: \"%s\"\n", filename);
+}
+
+//===========================================================================
+
+static int QDECL paksort(const void *a, const void *b)
+{
+ char *aa = *(char **)a;
+ char *bb = *(char **)b;
+
+ return FS_PathCmp(aa, bb);
+}
+
+/*
+================
+FS_AddGameDirectory
+
+Sets fs_gamedir, adds the directory to the head of the path,
+then loads the zip headers
+================
+*/
+void FS_AddGameDirectory(const char *path, const char *dir)
+{
+ pack_t *pak;
+ char curpath[MAX_OSPATH + 1];
+ int numfiles;
+ char **pakfiles;
+ int pakfilesi;
+ char **pakfilestmp;
+ int numdirs;
+ char **pakdirs;
+ int pakdirsi;
+ char **pakdirstmp;
+
+ int lengths[10][2];
+
+ // Unique
+ for (searchpath_t *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));
+
+ // find all pak files in this directory
+ Q_strncpyz(curpath, FS_BuildOSPath(path, dir, ""), sizeof(curpath));
+ curpath[strlen(curpath) - 1] = '\0'; // strip the trailing slash
+
+ // Get .pk3 files
+ pakfiles = Sys_ListFiles(curpath, ".pk3", nullptr, &numfiles, false);
+
+ qsort(pakfiles, numfiles, sizeof(char *), paksort);
+
+ if (fs_numServerPaks)
+ {
+ numdirs = 0;
+ pakdirs = nullptr;
+ }
+ else
+ {
+ // Get top level directories (we'll filter them later since the Sys_ListFiles filtering is
+ // terrible)
+ pakdirs = Sys_ListFiles(curpath, "/", nullptr, &numdirs, false);
+ qsort(pakdirs, numdirs, sizeof(char *), paksort);
+ }
+
+ char prefixBuf[MAX_STRING_CHARS];
+ Q_strncpyz(prefixBuf, Cvar_VariableString("fs_pk3PrefixPairs"), sizeof(prefixBuf));
+ int numPairs = 0;
+
+ char *p = prefixBuf;
+ if (!p[0]) p = nullptr;
+
+ const char *prefixes[10][2];
+ while (p)
+ {
+ prefixes[numPairs][0] = p;
+ p = strchr(p, '&');
+ if (!p)
+ {
+ Com_Printf(S_COLOR_YELLOW "WARNING: fs_pk3PrefixPairs ends with an incomplete pair\n");
+ break;
+ }
+ lengths[numPairs][0] = (int)(p - prefixes[numPairs][0]);
+ *p++ = '\0';
+ prefixes[numPairs][1] = p;
+ p = strchr(p, '|');
+ if (p)
+ {
+ lengths[numPairs][1] = (int)(p - prefixes[numPairs][1]);
+ *p++ = '\0';
+ }
+ else
+ {
+ lengths[numPairs][1] = (int)strlen(prefixes[numPairs][1]);
+ }
+ if (lengths[numPairs][0] == 0 && lengths[numPairs][1] == 0)
+ {
+ Com_Printf(S_COLOR_YELLOW
+ "WARNING: fs_pk3PrefixPairs contains a null-null pair, "
+ "skipping this pair\n");
+ continue;
+ }
+ if (lengths[numPairs][0] != 0 && lengths[numPairs][1] != 0 &&
+ !Q_stricmpn(prefixes[numPairs][0], prefixes[numPairs][1],
+ MIN(lengths[numPairs][0], lengths[numPairs][1])))
+ {
+ Com_Printf(S_COLOR_YELLOW
+ "WARNING: in fs_pk3PrefixPairs, one of '%s' and '%s' is a real prefix "
+ "of the other, skipping this pair\n",
+ prefixes[numPairs][0], prefixes[numPairs][1]);
+
+ continue;
+ }
+ ++numPairs;
+ }
+ searchpath_t *otherSearchpaths = fs_searchpaths;
+
+ pakfilesi = 0;
+ pakdirsi = 0;
+
+ while ((pakfilesi < numfiles) || (pakdirsi < numdirs))
+ {
+ bool pakwhich;
+ // Check if a pakfile or pakdir comes next
+ if (pakfilesi >= numfiles)
+ {
+ pakwhich = false; // We've used all the pak files, it must be a pak directory.
+ }
+ else if (pakdirsi >= numdirs)
+ {
+ pakwhich = true; // We've used all the pak directories, it must be a pak file.
+ }
+ else
+ {
+ // Could be either, compare to see which name comes first
+ // Need tmp variables for appropriate indirection for paksort()
+ pakfilestmp = &pakfiles[pakfilesi];
+ pakdirstmp = &pakdirs[pakdirsi];
+ pakwhich = (paksort(pakfilestmp, pakdirstmp) < 0);
+ }
+
+ if (pakwhich)
+ {
+ // The next .pk3 file is before the next .pk3dir
+ char *pakfile = FS_BuildOSPath(path, dir, pakfiles[pakfilesi]);
+ if ((pak = FS_LoadZipFile(pakfile, pakfiles[pakfilesi])) == 0)
+ {
+ // This isn't a .pk3! Next!
+ pakfilesi++;
+ continue;
+ }
+
+ Q_strncpyz(pak->pakPathname, curpath, sizeof(pak->pakPathname));
+ // store the game name for downloading
+ Q_strncpyz(pak->pakGamename, dir, sizeof(pak->pakGamename));
+
+ fs_packFiles += pak->numfiles;
+
+ searchpath_t *search = static_cast<searchpath_t *>(Z_Malloc(sizeof(searchpath_t)));
+ search->pack = pak;
+ search->next = fs_searchpaths;
+ fs_searchpaths = search;
+
+ pak->onlyPrimary = false;
+ pak->onlyAlternate = false;
+ for (int i = 0; i < numPairs; ++i)
+ {
+ if (lengths[i][0] && !Q_stricmpn(pak->pakBasename, prefixes[i][0], lengths[i][0]))
+ {
+ pak->onlyPrimary = true;
+ break;
+ }
+ else if (lengths[i][1] &&
+ !Q_stricmpn(pak->pakBasename, prefixes[i][1], lengths[i][1]))
+ {
+ pak->onlyAlternate = true;
+ break;
+ }
+ }
+
+ pak->primaryVersion = nullptr;
+ pakfilesi++;
+ }
+ else
+ {
+ // The next .pk3dir is before the next .pk3 file
+ // But wait, this could be any directory, we're filtering to only ending with ".pk3dir"
+ // here.
+ int len = strlen(pakdirs[pakdirsi]);
+ if (!FS_IsExt(pakdirs[pakdirsi], ".pk3dir", len))
+ {
+ // This isn't a .pk3dir! Next!
+ pakdirsi++;
+ continue;
+ }
+
+ char *pakfile = FS_BuildOSPath(path, dir, pakdirs[pakdirsi]);
+
+ // add the directory to the search path
+ searchpath_t *search = static_cast<searchpath_t *>(Z_Malloc(sizeof(searchpath_t)));
+ search->dir = static_cast<directory_t *>(Z_Malloc(sizeof(*search->dir)));
+
+ Q_strncpyz(search->dir->path, curpath, sizeof(search->dir->path)); // c:\quake3\baseq3
+ Q_strncpyz(search->dir->fullpath, pakfile,
+ sizeof(search->dir->fullpath)); // c:\quake3\baseq3\mypak.pk3dir
+ Q_strncpyz(search->dir->gamedir, pakdirs[pakdirsi],
+ sizeof(search->dir->gamedir)); // mypak.pk3dir
+
+ search->next = fs_searchpaths;
+ fs_searchpaths = search;
+
+ pakdirsi++;
+ }
+ }
+
+ // done
+ Sys_FreeFileList(pakfiles);
+ Sys_FreeFileList(pakdirs);
+
+ if (numPairs > 0)
+ {
+ int bnlengths[2];
+ for (searchpath_t *search = fs_searchpaths; search != otherSearchpaths;
+ search = search->next)
+ {
+ if (!(search->pack && search->pack->onlyPrimary))
+ {
+ continue;
+ }
+
+ bnlengths[0] = (int)strlen(search->pack->pakBasename);
+ for (searchpath_t *srch = fs_searchpaths; srch != otherSearchpaths; srch = srch->next)
+ {
+ if (!(srch->pack && srch->pack->onlyAlternate))
+ {
+ continue;
+ }
+
+ bnlengths[1] = (int)strlen(srch->pack->pakBasename);
+ for (int i = 0; i < numPairs; ++i)
+ {
+ if (lengths[i][0] && lengths[i][1] && bnlengths[0] >= lengths[i][0] &&
+ bnlengths[1] >= lengths[i][1] &&
+ !Q_stricmp(search->pack->pakBasename + lengths[i][0],
+ srch->pack->pakBasename + lengths[i][1]))
+ {
+ srch->pack->primaryVersion = search->pack;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ //
+ // add the directory to the search path
+ //
+ searchpath_t *search = static_cast<searchpath_t *>(Z_Malloc(sizeof(searchpath_t)));
+ search->dir = static_cast<directory_t *>(Z_Malloc(sizeof(*search->dir)));
+
+ Q_strncpyz(search->dir->path, path, sizeof(search->dir->path));
+ Q_strncpyz(search->dir->fullpath, curpath, sizeof(search->dir->fullpath));
+ Q_strncpyz(search->dir->gamedir, dir, sizeof(search->dir->gamedir));
+
+ search->next = fs_searchpaths;
+ fs_searchpaths = search;
+}
+
+/*
+================
+FS_CheckDirTraversal
+
+Check whether the string contains stuff like "../" to prevent directory traversal bugs
+and return true if it does.
+================
+*/
+
+bool FS_CheckDirTraversal(const char *checkdir)
+{
+ if (strstr(checkdir, "../") || strstr(checkdir, "..\\")) return true;
+
+ return false;
+}
+
+/*
+================
+FS_ComparePaks
+
+----------------
+dlstring == true
+
+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 == false
+
+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)
+
+================
+*/
+bool FS_ComparePaks(char *neededpaks, int len, bool dlstring)
+{
+ if (!fs_numServerReferencedPaks)
+ {
+ return false; // Server didn't send any pack information along
+ }
+
+ char *origpos = neededpaks;
+ *neededpaks = '\0';
+
+ bool havepak = false;
+ for (int i = 0; i < fs_numServerReferencedPaks; i++)
+ {
+ // Ok, see if we have this pak file
+ havepak = false;
+
+ // 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 (searchpath_t *sp = fs_searchpaths; sp; sp = sp->next)
+ {
+ if (sp->pack && sp->pack->checksum == fs_serverReferencedPaks[i])
+ {
+ havepak = true; // 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 true;
+ }
+
+ return false; // We have them all
+}
+
+/*
+================
+FS_Shutdown
+
+Frees all resources.
+================
+*/
+
+void FS_Shutdown(bool closemfp)
+{
+ for (int i = 0; i < MAX_FILE_HANDLES; i++)
+ {
+ if (fsh[i].fileSize) FS_FCloseFile(i);
+ }
+
+ searchpath_t *next;
+ // free everything
+ for (auto p = fs_searchpaths; p; p = next)
+ {
+ next = p->next;
+ if (p->pack) FS_FreePak(p->pack);
+ if (p->dir) Z_Free(p->dir);
+ Z_Free(p);
+ }
+
+ // Any FS_ calls will now be an error until reinitialized
+ fs_searchpaths = nullptr;
+
+ Cmd_RemoveCommand("path");
+ Cmd_RemoveCommand("dir");
+ Cmd_RemoveCommand("fdir");
+ Cmd_RemoveCommand("touchFile");
+ Cmd_RemoveCommand("which");
+
+#ifdef FS_MISSING
+ if (closemfp)
+ {
+ fclose(missingFiles);
+ }
+#endif
+}
+
+/*
+================
+FS_ReorderPurePaks
+NOTE TTimo: the reordering that happens here is not reflected in the cvars (\cvarlist *pak*)
+ this can lead to misleading situations, see
+https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
+================
+*/
+static void FS_ReorderPurePaks(void)
+{
+ // do this before fs_numServerPaks check?
+ fs_reordered = false;
+
+ // only relevant when connected to pure server
+ if (!fs_numServerPaks) return;
+
+ // we insert in order at the beginning of the list
+ auto p_insert_index = &fs_searchpaths;
+ for (int i = 0; i < fs_numServerPaks; i++)
+ {
+ // track the pointer-to-current-item
+ auto p_previous = p_insert_index;
+ for (auto 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 = true;
+
+ // 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;
+
+ // iterate to next server pack
+ break;
+ }
+ p_previous = &s->next;
+ }
+ }
+}
+
+/*
+================
+FS_Startup
+================
+*/
+static void FS_Startup(const char *gameName)
+{
+ Com_Printf("----- FS_Startup -----\n");
+ fs_packFiles = 0;
+
+ fs_debug = Cvar_Get("fs_debug", "0", 0);
+ fs_basepath = Cvar_Get("fs_basepath", Sys_DefaultInstallPath(), CVAR_INIT | CVAR_PROTECTED);
+ fs_basegame = Cvar_Get("fs_basegame", BASEGAME, CVAR_INIT);
+
+ const char *homePath = Sys_DefaultHomePath();
+ if (!homePath || !homePath[0])
+ {
+ homePath = fs_basepath->string;
+ }
+
+ fs_homepath = Cvar_Get("fs_homepath", homePath, CVAR_INIT | CVAR_PROTECTED);
+ fs_gamedirvar = Cvar_Get("fs_game", BASEGAME, CVAR_INIT | CVAR_SYSTEMINFO);
+
+#ifdef DEDICATED
+ // add search path elements in reverse priority order
+ if (fs_basepath->string[0])
+ FS_AddGameDirectory(fs_basepath->string, gameName);
+
+ // NOTE: same filtering below for mods and basegame
+ if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string))
+ {
+ FS_CreatePath(fs_homepath->string);
+ FS_AddGameDirectory(fs_homepath->string, gameName);
+ }
+
+ // check for additional base game so mods can be based upon other mods
+ if ( fs_basegame->string[0] && Q_stricmp( fs_basegame->string, gameName ) )
+ {
+ if (fs_basepath->string[0])
+ FS_AddGameDirectory(fs_basepath->string, fs_basegame->string);
+ if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string))
+ FS_AddGameDirectory(fs_homepath->string, fs_basegame->string);
+ }
+
+ // check for additional game folder for mods
+ if ( fs_gamedirvar->string[0] && Q_stricmp( fs_gamedirvar->string, gameName ) )
+ {
+ if (fs_basepath->string[0])
+ FS_AddGameDirectory(fs_basepath->string, fs_gamedirvar->string);
+ if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string))
+ FS_AddGameDirectory(fs_homepath->string, fs_gamedirvar->string);
+ }
+
+#else
+
+ // add search path elements in reverse priority order
+ if (fs_basepath->string[0])
+ {
+ FS_AddGameDirectory(fs_basepath->string, "base");
+ }
+
+#ifdef __APPLE__
+ // Make MacOSX also include the base path included with the .app bundle
+ fs_apppath = Cvar_Get("fs_apppath", Sys_DefaultAppPath(), CVAR_INIT | CVAR_PROTECTED);
+ if (fs_apppath->string[0])
+ {
+ FS_AddGameDirectory(fs_apppath->string, "base");
+ }
+#endif
+
+ // NOTE: same filtering below for mods and basegame
+ if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string, fs_basepath->string))
+ {
+ FS_CreatePath(fs_homepath->string);
+ FS_AddGameDirectory(fs_homepath->string, "base");
+ }
+
+ // check for additional base game so mods can be based upon other mods
+ if (fs_basegame->string[0] && Q_stricmp(fs_basegame->string, gameName))
+ {
+ if (fs_basepath->string[0])
+ FS_AddGameDirectory(fs_basepath->string, fs_basegame->string);
+ if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string, fs_basepath->string))
+ FS_AddGameDirectory(fs_homepath->string, fs_basegame->string);
+ }
+
+ // check for additional game folder for mods
+ if (fs_gamedirvar->string[0] && Q_stricmp(fs_gamedirvar->string, gameName))
+ {
+ if (fs_basepath->string[0])
+ FS_AddGameDirectory(fs_basepath->string, fs_gamedirvar->string);
+ if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string, fs_basepath->string))
+ FS_AddGameDirectory(fs_homepath->string, fs_gamedirvar->string);
+ }
+
+ // NOTE: same filtering below for mods and basegame
+ if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string, fs_basepath->string))
+ {
+ FS_CreatePath(fs_homepath->string);
+ FS_AddGameDirectory(fs_homepath->string, gameName);
+ }
+
+ // add search path elements in reverse priority order
+ if (fs_basepath->string[0])
+ {
+ FS_AddGameDirectory(fs_basepath->string, gameName);
+ }
+
+#ifdef __APPLE__
+ // Make MacOSX also include the base path included with the .app bundle
+ fs_apppath = Cvar_Get("fs_apppath", Sys_DefaultAppPath(), CVAR_INIT | CVAR_PROTECTED);
+ if (fs_apppath->string[0])
+ {
+ FS_AddGameDirectory(fs_apppath->string, gameName);
+ }
+#endif
+#endif
+
+ // add our commands
+ Cmd_AddCommand("path", FS_Path_f);
+ Cmd_AddCommand("dir", FS_Dir_f);
+ Cmd_AddCommand("fdir", FS_NewDir_f);
+ Cmd_AddCommand("touchFile", FS_TouchFile_f);
+ Cmd_AddCommand("which", FS_Which_f);
+
+ // reorder the pure pk3 files according to server order
+ FS_ReorderPurePaks();
+
+ // print the current search paths
+ FS_Path_f();
+
+ // We just loaded, it's not modified
+ fs_gamedirvar->modified = false;
+
+ Com_Printf("----------------------\n");
+
+#ifdef FS_MISSING
+ if (missingFiles == nullptr)
+ {
+ missingFiles = Sys_FOpen("\\missing.txt", "ab");
+ }
+#endif
+
+ Com_Printf("%d files in pk3 files\n", fs_packFiles);
+}
+
+/*
+=====================
+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(bool alternate)
+{
+ static char info[BIG_INFO_STRING];
+ info[0] = 0;
+
+ for (auto search = fs_searchpaths; search; search = search->next)
+ {
+ // is the element a pak file?
+ if (!search->pack) continue;
+ if ((alternate && search->pack->onlyPrimary) || (!alternate && search->pack->onlyAlternate))
+ 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(bool alternate)
+{
+ static char info[BIG_INFO_STRING];
+ info[0] = 0;
+
+ for (auto search = fs_searchpaths; search; search = search->next)
+ {
+ // is the element a pak file?
+ if (!search->pack) continue;
+ if ((alternate && search->pack->onlyPrimary) || (!alternate && search->pack->onlyAlternate))
+ continue;
+ if (info[0]) 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(bool alternate)
+{
+ static char info[BIG_INFO_STRING];
+ info[0] = 0;
+
+ for (auto search = fs_searchpaths; search; search = search->next)
+ {
+ // is the element a pak file?
+ if (!search->pack) continue;
+ if ((alternate && search->pack->onlyPrimary) || (!alternate && search->pack->onlyAlternate))
+ 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(bool alternate)
+{
+ static char info[BIG_INFO_STRING];
+ info[0] = 0;
+
+ for (auto search = fs_searchpaths; search; search = search->next)
+ {
+ // is the element a pak file?
+ if (search->pack)
+ {
+ if ((alternate and search->pack->onlyPrimary) or
+ (!alternate and search->pack->onlyAlternate))
+ continue;
+
+ if (search->pack->referenced or
+ (search->pack->primaryVersion and search->pack->primaryVersion->referenced) or
+ (*fs_gamedirvar->string and Q_stricmp(fs_gamedirvar->string, BASEGAME) and
+ Q_stricmp(search->pack->pakGamename, fs_gamedirvar->string) == 0))
+ {
+ 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];
+ info[0] = 0;
+
+ int checksum = fs_checksumFeed;
+ int numPaks = 0;
+ for (int 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 (auto search = fs_searchpaths; search; search = search->next)
+ {
+ // is the element a pak file and has it been referenced based on flag?
+ if (search->pack && (search->pack->referenced & nFlags))
+ {
+ Q_strcat(info, sizeof(info), va("%i ", search->pack->pure_checksum));
+ if (nFlags & (FS_CGAME_REF | FS_UI_REF)) break;
+
+ checksum ^= search->pack->pure_checksum;
+ numPaks++;
+ }
+ }
+ }
+ // last checksum is the encoded number of referenced pk3s
+ checksum ^= numPaks;
+ Q_strcat(info, sizeof(info), va("%i ", checksum));
+
+ return info;
+}
+
+/*
+=====================
+FS_ReferencedPakNames
+
+Returns a space separated string containing the names of all referenced pk3 files.
+The server will send this to the clients so they can check which files should be auto-downloaded.
+=====================
+*/
+const char *FS_ReferencedPakNames(bool alternate)
+{
+ static char info[BIG_INFO_STRING];
+ info[0] = 0;
+
+ // we want to return ALL pk3's from the fs_game path
+ // and referenced one's from base
+ for (auto search = fs_searchpaths; search; search = search->next)
+ {
+ // is the element a pak file?
+ if (search->pack)
+ {
+ if ((alternate && search->pack->onlyPrimary) ||
+ (!alternate && search->pack->onlyAlternate))
+ continue;
+
+ if (search->pack->referenced ||
+ (search->pack->primaryVersion && search->pack->primaryVersion->referenced) ||
+ (fs_gamedirvar->string[0] && Q_stricmp(fs_gamedirvar->string, BASEGAME) &&
+ !Q_stricmp(search->pack->pakGamename, fs_gamedirvar->string)))
+ {
+ if (*info) Q_strcat(info, sizeof(info), " ");
+
+ Q_strcat(info, sizeof(info), search->pack->pakGamename);
+ Q_strcat(info, sizeof(info), "/");
+ Q_strcat(info, sizeof(info), search->pack->pakBasename);
+ }
+ }
+ }
+
+ return info;
+}
+
+/*
+=====================
+FS_ClearPakReferences
+=====================
+*/
+void FS_ClearPakReferences(int flags)
+{
+ if (!flags) flags = -1;
+
+ for (auto 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)
+{
+ Cmd_TokenizeString(pakSums);
+
+ int c = Cmd_Argc();
+ if (c > MAX_SEARCH_PATHS) c = MAX_SEARCH_PATHS;
+
+ fs_numServerPaks = c;
+
+ for (int 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)
+ {
+ // 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 (int i = 0; i < c; i++)
+ {
+ if (fs_serverPakNames[i]) Z_Free(fs_serverPakNames[i]);
+ fs_serverPakNames[i] = nullptr;
+ }
+
+ if (pakNames && pakNames[0])
+ {
+ Cmd_TokenizeString(pakNames);
+
+ int d = Cmd_Argc();
+ if (d > MAX_SEARCH_PATHS) d = MAX_SEARCH_PATHS;
+
+ for (int 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)
+{
+ Cmd_TokenizeString(pakSums);
+
+ unsigned c = Cmd_Argc();
+ if (c > MAX_SEARCH_PATHS) c = MAX_SEARCH_PATHS;
+
+ for (unsigned i = 0; i < c; i++) fs_serverReferencedPaks[i] = atoi(Cmd_Argv(i));
+
+ for (unsigned i = 0; i < ARRAY_LEN(fs_serverReferencedPakNames); i++)
+ {
+ if (fs_serverReferencedPakNames[i]) Z_Free(fs_serverReferencedPakNames[i]);
+ fs_serverReferencedPakNames[i] = nullptr;
+ }
+
+ unsigned d = 0;
+ if (pakNames && *pakNames)
+ {
+ Cmd_TokenizeString(pakNames);
+
+ d = Cmd_Argc();
+ if (d > c) d = c;
+
+ for (unsigned i = 0; i < d; i++) fs_serverReferencedPakNames[i] = CopyString(Cmd_Argv(i));
+ }
+
+ // ensure that there are as many checksums as there are pak names.
+ if (d < c) c = d;
+
+ fs_numServerReferencedPaks = c;
+}
+
+/*
+================
+FS_InitFilesystem
+
+Called only at inital startup, not when the filesystem
+is resetting due to a game change
+================
+*/
+void FS_InitFilesystem(void)
+{
+ // allow command line parms to override our defaults
+ // we have to specially handle this, because normal command
+ // line variable sets don't happen until after the filesystem
+ // has already been initialized
+ Com_StartupVariable("fs_basepath");
+ Com_StartupVariable("fs_homepath");
+ Com_StartupVariable("fs_game");
+ Com_StartupVariable("fs_pk3PrefixPairs");
+
+ if (!FS_FilenameCompare(Cvar_VariableString("fs_game"), BASEGAME)) Cvar_Set("fs_game", "");
+
+ // try to start up normally
+ FS_Startup(BASEGAME);
+
+ // if we can't find default.cfg, assume that the paths are
+ // busted and error out now, rather than getting an unreadable
+ // graphics screen when the font fails to load
+ if (FS_ReadFile("default.cfg", nullptr) <= 0)
+ {
+ Com_Error(ERR_FATAL, "Couldn't load default.cfg");
+ }
+
+ Q_strncpyz(lastValidBase, fs_basegame->string, sizeof(lastValidBase));
+ Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame));
+}
+
+/*
+================
+FS_Restart
+================
+*/
+void FS_Restart(int checksumFeed)
+{
+ // free anything we currently have loaded
+ FS_Shutdown(false);
+
+ // 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", nullptr) <= 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_basegame", lastValidBase);
+ Cvar_Set("fs_game", lastValidGame);
+ lastValidBase[0] = lastValidGame[0] = '\0';
+ FS_Restart(checksumFeed);
+ Com_Error(ERR_DROP, "Invalid game folder");
+ return;
+ }
+ Com_Error(ERR_FATAL, "Couldn't load default.cfg");
+ }
+
+ if (Q_stricmp(fs_gamedirvar->string, lastValidGame))
+ {
+ // skip the autogen.cfg if "safe" is on the command line
+ if (!Com_SafeMode())
+ {
+ Cbuf_AddText("exec " Q3CONFIG_CFG "\n");
+ }
+ }
+
+ Q_strncpyz(lastValidBase, fs_basegame->string, sizeof(lastValidBase));
+ Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame));
+}
+
+/*
+=================
+FS_ConditionalRestart
+
+Restart if necessary
+Return true if restarting due to game directory changed, false otherwise
+=================
+*/
+bool FS_ConditionalRestart(int checksumFeed, bool disconnect)
+{
+ if (fs_gamedirvar->modified)
+ {
+ if (FS_FilenameCompare(lastValidGame, fs_gamedirvar->string) &&
+ (*lastValidGame || FS_FilenameCompare(fs_gamedirvar->string, BASEGAME)) &&
+ (*fs_gamedirvar->string || FS_FilenameCompare(lastValidGame, BASEGAME)))
+ {
+ Com_GameRestart(checksumFeed, disconnect);
+ return true;
+ }
+ fs_gamedirvar->modified = false;
+ }
+
+ if (checksumFeed != fs_checksumFeed)
+ FS_Restart(checksumFeed);
+
+ else if (fs_numServerPaks && !fs_reordered)
+ FS_ReorderPurePaks();
+
+ return false;
+}
+
+/*
+========================================================================================
+
+Handle based file calls for virtual machines
+
+========================================================================================
+*/
+
+int FS_FOpenFileByMode(const char *qpath, fileHandle_t *f, enum FS_Mode mode)
+{
+ int r;
+ bool sync = false;
+
+ switch (mode)
+ {
+ case FS_READ:
+ r = FS_FOpenFileRead(qpath, f, true);
+ break;
+
+ case FS_WRITE:
+ *f = FS_FOpenFileWrite(qpath);
+ r = 0;
+ if (*f == 0) r = -1;
+ break;
+
+ case FS_APPEND_SYNC:
+ sync = true;
+ // fall through
+
+ case FS_APPEND:
+ *f = FS_FOpenFileAppend(qpath);
+ r = 0;
+ if (*f == 0) r = -1;
+ break;
+
+ default:
+ Com_Error(ERR_FATAL, "FS_FOpenFileByMode: bad mode");
+ return -1;
+ }
+
+ if (!f) return r;
+
+ if (*f)
+ {
+ fsh[*f].fileSize = r;
+ }
+ fsh[*f].handleSync = sync;
+
+ return r;
+}
+
+int FS_FTell(fileHandle_t f)
+{
+ if (fsh[f].zipFile == true) return unztell(fsh[f].handleFiles.file.z);
+ return ftell(fsh[f].handleFiles.file.o);
+}
+
+void FS_Flush(fileHandle_t f)
+{
+ fflush(fsh[f].handleFiles.file.o);
+}
+
+void FS_FilenameCompletion(const char *dir, const char *ext, bool stripExt,
+ void (*callback)(const char *s), bool allowNonPureFilesOnDisk)
+{
+ int nfiles;
+ char filename[MAX_STRING_CHARS];
+ char **filenames = FS_ListFilteredFiles(dir, ext, nullptr, &nfiles, allowNonPureFilesOnDisk);
+
+ FS_SortFileList(filenames, nfiles);
+
+ for (int i = 0; i < nfiles; i++)
+ {
+ FS_ConvertPath(filenames[i]);
+ Q_strncpyz(filename, filenames[i], MAX_STRING_CHARS);
+ if (stripExt) COM_StripExtension(filename, filename, sizeof(filename));
+ callback(filename);
+ }
+ FS_FreeFileList(filenames);
+}
+
+const char *FS_GetCurrentGameDir(void)
+{
+ if (fs_gamedirvar->string[0]) return fs_gamedirvar->string;
+ return BASEGAME;
+}
diff --git a/src/qcommon/files.h b/src/qcommon/files.h
new file mode 100644
index 0000000..d54bdf4
--- /dev/null
+++ b/src/qcommon/files.h
@@ -0,0 +1,286 @@
+/*
+ * This file is part of Tremulous.
+ * Copyright © 2016 Victor Roemer (blowfish) <victor@badsec.org>
+ * Copyright (C) 2015-2019 GrangerHub
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef QC_FILES_H
+#define QC_FILES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "q_platform.h"
+#include "q_shared.h"
+
+// 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 MAX_FILE_HANDLES 64
+
+#define BASEGAME "gpp"
+
+#ifdef DEDICATED
+#define Q3CONFIG_CFG "autogen_server.cfg"
+#else
+#define Q3CONFIG_CFG "autogen.cfg"
+#endif
+
+/*
+ =============================================================
+
+ 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 relative to the base path, so base path should usually be writable.
+
+ The "home path" is the path used for all write access. On win32 systems we have "base path"
+ == "home path", but on *nix systems the base installation is usually readonly, and
+ "home path" points to ~/.q3a or similar
+
+ The user can also install custom mods and content in "home path", so it should be searched
+ along with "home path" and "cd path" for game content.
+
+
+ The "base game" is the directory under the paths where data comes from by default, and
+ can be "base".
+
+ The "current game" may be the same as the base game, or it may be the name of another
+ directory under the paths that should be searched for files before looking in the base game.
+ This is the basis for addons.
+
+ Clients automatically set the game directory after receiving a gamestate from a server,
+ so only servers need to worry about +set fs_game.
+
+ No other directories outside of the base game and current game will ever be referenced by
+ filesystem functions.
+
+ To save disk space and speed loading, directory trees can be collapsed into zip files.
+ The files use a ".pk3" extension to prevent users from unzipping them accidentally, but
+ otherwise the are simply normal uncompressed zip files. A game directory can have multiple
+ zip files of the form "pak0.pk3", "pak1.pk3", etc. Zip files are searched in decending order
+ from the highest number to the lowest, and will always take precedence over the filesystem.
+ This allows a pk3 distributed as a patch to override all existing data.
+
+ Because we will have updated executables freely available online, there is no point to
+ trying to restrict demo / oem versions of the game with code changes. Demo / oem versions
+ should be exactly the same executables as release versions, but with different data that
+ automatically restricts where game media can come from to prevent add-ons from working.
+
+ File search order: when FS_FOpenFileRead gets called it will go through the fs_searchpaths
+ structure and stop on the first successful hit. fs_searchpaths is built with successive
+ calls to FS_AddGameDirectory
+
+ Additionaly, we search in several subdirectories:
+ current game is the current mode
+ base game is a variable to allow mods based on other mods
+ (such as base + missionpack content combination in a mod for instance)
+ BASEGAME is the hardcoded base game ("base")
+
+ e.g. the qpath "sound/newstuff/test.wav" would be searched for in the following places:
+
+ home path + current game's zip files
+ home path + current game's directory
+ base path + current game's zip files
+ base path + current game's directory
+ cd path + current game's zip files
+ cd path + current game's directory
+
+ home path + base game's zip file
+ home path + base game's directory
+ base path + base game's zip file
+ base path + base game's directory
+ cd path + base game's zip file
+ cd path + base game's directory
+
+ home path + BASEGAME's zip file
+ home path + BASEGAME's directory
+ base path + BASEGAME's zip file
+ base path + BASEGAME's directory
+ cd path + BASEGAME's zip file
+ cd path + BASEGAME's directory
+
+ server download, to be written to home path + current game's directory
+
+
+ The filesystem can be safely shutdown and reinitialized with different
+ basedir / cddir / game combinations, but all other subsystems that rely on it
+ (sound, video) must also be forced to restart.
+
+ Because the same files are loaded by both the clip model (CM_) and renderer (TR_)
+ subsystems, a simple single-file caching scheme is used. The CM_ subsystems will
+ load the file with a request to cache. Only one file will be kept cached at a time,
+ so any models that are going to be referenced by both subsystems should alternate
+ between the CM_ load function and the ref load function.
+
+ TODO: A qpath that starts with a leading slash will always refer to the base game, even if another
+ game is currently active. This allows character models, skins, and sounds to be downloaded
+ to a common directory no matter which game is active.
+
+ How to prevent downloading zip files?
+ Pass pk3 file names in systeminfo, and download before FS_Restart (void)?
+
+ 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
+
+ =============================================================================
+*/
+
+//enum FS_Mode {
+// FS_READ,
+// FS_WRITE,
+// FS_APPEND,
+// FS_APPEND_SYNC
+//};
+//
+//enum FS_Origin {
+// FS_SEEK_CUR,
+// FS_SEEK_END,
+// FS_SEEK_SET
+//};
+
+const char* FS_GetCurrentGameDir (void);
+void FS_FilenameCompletion (const char* dir, const char* ext, bool stripExt, void (* callback)(const char* s), bool allowNonPureFilesOnDisk);
+int FS_FOpenFileByMode (const char* qpath, fileHandle_t* f, enum FS_Mode mode);
+bool FS_ConditionalRestart (int checksumFeed, bool disconnect);
+void FS_InitFilesystem (void);
+void FS_PureServerSetReferencedPaks (const char* pakSums, const char* pakNames);
+void FS_Restart (int checksumFeed);
+void FS_PureServerSetLoadedPaks (const char* pakSums, const char* pakNames);
+void FS_ClearPakReferences (int flags);
+const char* FS_ReferencedPakNames (bool alternate);
+const char* FS_ReferencedPakPureChecksums (void);
+const char* FS_ReferencedPakChecksums (bool alternate);
+const char* FS_LoadedPakPureChecksums (bool alternate);
+const char* FS_LoadedPakNames (bool alternate);
+const char* FS_LoadedPakChecksums (bool alternate);
+void FS_Shutdown (bool closemfp);
+bool FS_ComparePaks (char* neededpaks, int len, bool dlstring);
+bool FS_CheckDirTraversal (const char* checkdir);
+void FS_AddGameDirectory (const char* path, const char* dir);
+bool FS_Which (const char* filename, void* searchPath);
+void FS_SortFileList (char** filelist, int numfiles);
+int FS_PathCmp (const char* s1, const char* s2);
+void FS_ConvertPath (char* s);
+int FS_GetModList (char* listbuf, int bufsize);
+int FS_GetFileList (const char* path, const char* extension, char* listbuf, int bufsize);
+void FS_FreeFileList (char** list);
+int FS_GetFilteredFiles (const char *path, const char *extension, const char *filter, char *listbuf, int bufsize);
+char** FS_ListFiles (const char* path, const char* extension, int* numfiles);
+char** FS_ListFilteredFiles (const char* path, const char* extension, const char* filter, int* numfiles, bool allowNonPureFilesOnDisk);
+bool FS_CompareZipChecksum (const char* zipfile);
+void FS_WriteFile (const char* qpath, const void* buffer, int size);
+void FS_FreeFile (void* buffer);
+long FS_ReadFile (const char* qpath, void** buffer);
+void FS_Flush (fileHandle_t f);
+long FS_ReadFileDir (const char* qpath, void* searchPath, bool unpure, void** buffer);
+int FS_FileIsInPAK_A(bool alternate, const char *filename, int *pChecksum);
+int FS_FileIsInPAK (const char* filename, int* pChecksum);
+int FS_FTell (fileHandle_t f);
+int FS_Seek (fileHandle_t f, long offset, enum FS_Origin origin);
+void QDECL FS_Printf (fileHandle_t h, const char* fmt, ...);
+int FS_Write (const void* buffer, int len, fileHandle_t h);
+int FS_Read (void* buffer, int len, fileHandle_t f);
+int FS_Read (void* buffer, int len, fileHandle_t f);
+int FS_FindVM (void** startSearch, char* found, int foundlen, const char* name, int enableDll);
+long FS_FOpenFileRead (const char* filename, fileHandle_t* file, bool uniqueFILE);
+long FS_FOpenFileReadDir (const char* filename, void* search, fileHandle_t* file, bool uniqueFILE, bool unpure);
+bool FS_FilenameCompare (const char* s1, const char* s2);
+fileHandle_t FS_FCreateOpenPipeFile (const char* filename);
+fileHandle_t FS_FOpenFileAppend (const char* filename);
+fileHandle_t FS_FOpenFileWrite (const char* filename);
+void FS_FCloseFile (fileHandle_t f);
+void FS_Rename (const char* from, const char* to);
+void FS_SV_Rename (const char* from, const char* to, bool safe);
+long FS_SV_FOpenFileRead (const char* filename, fileHandle_t* fp);
+fileHandle_t FS_SV_FOpenFileWrite (const char* filename);
+bool FS_SV_FileExists (const char* file);
+bool FS_FileExists (const char* file);
+bool FS_FileInPathExists (const char* testpath);
+void FS_HomeRemove (const char* homePath);
+void FS_Remove (const char* osPath);
+bool FS_BrowseHomepath ( void );
+bool FS_OpenBaseGamePath( const char *baseGamePath );
+bool FS_CreatePath (const char* OSPath);
+char* FS_BuildOSPath (const char* base, const char* game, const char* qpath);
+long FS_filelength (fileHandle_t f);
+void FS_ReplaceSeparators (char *path);
+void FS_ForceFlush (fileHandle_t f);
+int FS_LoadStack (void);
+bool FS_Initialized (void);
+
+void FS_Which_f (void);
+void FS_TouchFile_f (void);
+void FS_Path_f (void);
+void FS_NewDir_f (void);
+void FS_Dir_f (void);
+
+
+// XXX Delete me.
+#if defined (FS_MISSING)
+extern FILE *missingFiles;
+#endif
+
+extern char lastValidGame[MAX_OSPATH];
+extern char lastValidBase[MAX_OSPATH];
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/qcommon/huffman.cpp b/src/qcommon/huffman.cpp
new file mode 100644
index 0000000..4cb0d8b
--- /dev/null
+++ b/src/qcommon/huffman.cpp
@@ -0,0 +1,558 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+/* 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 "huffman.h"
+
+#include "alternatePlayerstate.h"
+#include "cvar.h"
+#include "msg.h"
+#include "q_shared.h"
+#include "qcommon.h"
+
+static int bloc = 0;
+
+void Huff_putBit(int bit, uint8_t *fout, int *offset)
+{
+ bloc = *offset;
+ if ((bloc & 7) == 0)
+ {
+ fout[(bloc >> 3)] = 0;
+ }
+ fout[(bloc >> 3)] |= bit << (bloc & 7);
+ bloc++;
+ *offset = bloc;
+}
+
+int Huff_getBloc(void) { return bloc; }
+void Huff_setBloc(int _bloc) { bloc = _bloc; }
+int Huff_getBit(uint8_t *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, uint8_t *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(uint8_t *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, uint8_t 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, uint8_t *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!");
+ }
+ return (*ch = node->symbol);
+}
+
+/* Get a symbol */
+void Huff_offsetReceive(node_t *node, int *ch, uint8_t *fin, int *offset, int maxoffset)
+{
+ bloc = *offset;
+ while (node && node->symbol == INTERNAL_NODE)
+ {
+ if ( bloc >= maxoffset )
+ {
+ *ch = 0;
+ *offset = maxoffset + 1;
+ return;
+ }
+
+ if (get_bit(fin))
+ {
+ node = node->right;
+ }
+ else
+ {
+ node = node->left;
+ }
+ }
+ if (!node)
+ {
+ *ch = 0;
+ return;
+ // Com_Error(ERR_DROP, "Illegal tree!");
+ }
+ *ch = node->symbol;
+ *offset = bloc;
+}
+
+/* Send the prefix code for this node */
+static void send(node_t *node, node_t *child, uint8_t *fout, int maxoffset)
+{
+ if (node->parent)
+ {
+ send(node->parent, node, fout, maxoffset);
+ }
+ if (child)
+ {
+ if (bloc >= maxoffset)
+ {
+ bloc = maxoffset + 1;
+ return;
+ }
+
+ if (node->right == child)
+ {
+ add_bit(1, fout);
+ }
+ else
+ {
+ add_bit(0, fout);
+ }
+ }
+}
+
+/* Send a symbol */
+void Huff_transmit(huff_t *huff, int ch, uint8_t *fout, int maxoffset)
+{
+ int i;
+ if (huff->loc[ch] == NULL)
+ {
+ /* node_t hasn't been transmitted, send a NYT, then the symbol */
+ Huff_transmit(huff, NYT, fout, maxoffset);
+ for (i = 7; i >= 0; i--)
+ {
+ add_bit((char)((ch >> i) & 0x1), fout);
+ }
+ }
+ else
+ {
+ send(huff->loc[ch], NULL, fout, maxoffset);
+ }
+}
+
+void Huff_offsetTransmit(huff_t *huff, int ch, uint8_t *fout, int *offset, int maxoffset)
+{
+ bloc = *offset;
+ send(huff->loc[ch], NULL, fout, maxoffset);
+ *offset = bloc;
+}
+
+void Huff_Decompress(struct msg_t *mbuf, int offset)
+{
+ int ch, cch, i, j, size;
+ uint8_t seq[65536];
+ uint8_t *buffer;
+ huff_t huff;
+
+ size = mbuf->cursize - offset;
+ buffer = mbuf->data + offset;
+
+ if (size <= 0)
+ {
+ return;
+ }
+
+ 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 an 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, (uint8_t)ch); /* Increment node */
+ }
+ mbuf->cursize = cch + offset;
+ memcpy(mbuf->data + offset, seq, cch);
+}
+
+extern int oldsize;
+
+void Huff_Compress(struct msg_t *mbuf, int offset)
+{
+ int i, ch, size;
+ uint8_t seq[65536];
+ uint8_t *buffer;
+ huff_t huff;
+
+ size = mbuf->cursize - offset;
+ buffer = mbuf->data + +offset;
+
+ if (size <= 0)
+ {
+ return;
+ }
+
+ 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;
+
+ seq[0] = (size >> 8);
+ seq[1] = size & 0xff;
+
+ bloc = 16;
+
+ for (i = 0; i < size; i++)
+ {
+ ch = buffer[i];
+ Huff_transmit(&huff, ch, seq, size << 3); /* Transmit symbol */
+ Huff_addRef(&huff, (uint8_t)ch); /* Do update */
+ }
+
+ bloc += 8; // next uint8_t
+
+ mbuf->cursize = (bloc >> 3) + offset;
+ memcpy(mbuf->data + offset, seq, (bloc >> 3));
+}
+
+void Huff_Init(huffman_t *huff)
+{
+ memset(&huff->compressor, 0, sizeof(huff_t));
+ 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;
+}
diff --git a/src/qcommon/huffman.h b/src/qcommon/huffman.h
new file mode 100644
index 0000000..217fb9f
--- /dev/null
+++ b/src/qcommon/huffman.h
@@ -0,0 +1,59 @@
+#ifndef QCOMMON_HUFFMAN_H
+#define QCOMMON_HUFFMAN_H 1
+
+#include <stdint.h>
+
+/* 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(struct msg_t *buf, int offset);
+void Huff_Decompress(struct msg_t *buf, int offset);
+void Huff_Init(huffman_t *huff);
+void Huff_addRef(huff_t *huff, uint8_t ch);
+int Huff_Receive(node_t *node, int *ch, uint8_t *fin);
+void Huff_transmit(huff_t *huff, int ch, uint8_t *fout, int maxoffset);
+void Huff_offsetReceive(node_t *node, int *ch, uint8_t *fin, int *offset, int maxoffset);
+void Huff_offsetTransmit(huff_t *huff, int ch, uint8_t *fout, int *offset, int maxoffset);
+void Huff_putBit(int bit, uint8_t *fout, int *offset);
+int Huff_getBit(uint8_t *fout, int *offset);
+
+// don't use if you don't know what you're doing.
+int Huff_getBloc(void);
+void Huff_setBloc(int _bloc);
+
+extern huffman_t clientHuffTables;
+
+#endif
diff --git a/src/qcommon/ioapi.cpp b/src/qcommon/ioapi.cpp
new file mode 100644
index 0000000..0d22f9f
--- /dev/null
+++ b/src/qcommon/ioapi.cpp
@@ -0,0 +1,373 @@
+/* ioapi.h -- IO base function header for compress/uncompress .zip
+ part of the MiniZip project
+
+ Copyright (C) 1998-2010 Gilles Vollant
+ http://www.winimage.com/zLibDll/minizip.html
+ Modifications for Zip64 support
+ Copyright (C) 2009-2010 Mathias Svensson
+ http://result42.com
+
+ This program is distributed under the terms of the same license as zlib.
+ See the accompanying LICENSE file for the full text of the license.
+*/
+
+
+#include "ioapi.h"
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+#include "zconf.h"
+
+#if defined(_WIN32)
+# define snprintf _snprintf
+#endif
+
+#ifdef __APPLE__
+/* In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for specific 64 bit functions */
+# define FOPEN_FUNC(filename, mode) fopen(filename, mode)
+# define FTELLO_FUNC(stream) ftello(stream)
+# define FSEEKO_FUNC(stream, offset, origin) fseeko(stream, offset, origin)
+#else
+# define FOPEN_FUNC(filename, mode) fopen64(filename, mode)
+# define FTELLO_FUNC(stream) ftello64(stream)
+# define FSEEKO_FUNC(stream, offset, origin) fseeko64(stream, offset, origin)
+#endif
+
+/* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */
+#ifndef SEEK_CUR
+# define SEEK_CUR 1
+#endif
+#ifndef SEEK_END
+# define SEEK_END 2
+#endif
+#ifndef SEEK_SET
+# define SEEK_SET 0
+#endif
+
+voidpf call_zopen64 (const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode)
+{
+ if (pfilefunc->zfile_func64.zopen64_file != NULL)
+ return (*(pfilefunc->zfile_func64.zopen64_file)) (pfilefunc->zfile_func64.opaque,filename,mode);
+ return (*(pfilefunc->zopen32_file))(pfilefunc->zfile_func64.opaque,(const char*)filename,mode);
+}
+
+voidpf call_zopendisk64 OF((const zlib_filefunc64_32_def* pfilefunc, voidpf filestream, unsigned long number_disk, int mode))
+{
+ if (pfilefunc->zfile_func64.zopendisk64_file != NULL)
+ return (*(pfilefunc->zfile_func64.zopendisk64_file)) (pfilefunc->zfile_func64.opaque,filestream,number_disk,mode);
+ return (*(pfilefunc->zopendisk32_file))(pfilefunc->zfile_func64.opaque,filestream,number_disk,mode);
+}
+
+long call_zseek64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin)
+{
+ uLong offsetTruncated;
+ if (pfilefunc->zfile_func64.zseek64_file != NULL)
+ return (*(pfilefunc->zfile_func64.zseek64_file)) (pfilefunc->zfile_func64.opaque,filestream,offset,origin);
+ offsetTruncated = (uLong)offset;
+ if (offsetTruncated != offset)
+ return -1;
+ return (*(pfilefunc->zseek32_file))(pfilefunc->zfile_func64.opaque,filestream,offsetTruncated,origin);
+}
+
+ZPOS64_T call_ztell64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream)
+{
+ uLong tell_uLong;
+ if (pfilefunc->zfile_func64.zseek64_file != NULL)
+ return (*(pfilefunc->zfile_func64.ztell64_file)) (pfilefunc->zfile_func64.opaque,filestream);
+ tell_uLong = (*(pfilefunc->ztell32_file))(pfilefunc->zfile_func64.opaque,filestream);
+ if ((tell_uLong) == 0xffffffff)
+ return (ZPOS64_T)-1;
+ return tell_uLong;
+}
+
+void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32)
+{
+ p_filefunc64_32->zfile_func64.zopen64_file = NULL;
+ p_filefunc64_32->zfile_func64.zopendisk64_file = NULL;
+ p_filefunc64_32->zopen32_file = p_filefunc32->zopen_file;
+ p_filefunc64_32->zopendisk32_file = p_filefunc32->zopendisk_file;
+ p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file;
+ p_filefunc64_32->zfile_func64.zread_file = p_filefunc32->zread_file;
+ p_filefunc64_32->zfile_func64.zwrite_file = p_filefunc32->zwrite_file;
+ p_filefunc64_32->zfile_func64.ztell64_file = NULL;
+ p_filefunc64_32->zfile_func64.zseek64_file = NULL;
+ p_filefunc64_32->zfile_func64.zclose_file = p_filefunc32->zclose_file;
+ p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file;
+ p_filefunc64_32->zfile_func64.opaque = p_filefunc32->opaque;
+ p_filefunc64_32->zseek32_file = p_filefunc32->zseek_file;
+ p_filefunc64_32->ztell32_file = p_filefunc32->ztell_file;
+}
+
+static voidpf ZCALLBACK fopen_file_func OF((voidpf opaque, const char* filename, int mode));
+static uLong ZCALLBACK fread_file_func OF((voidpf opaque, voidpf stream, void* buf, uLong size));
+static uLong ZCALLBACK fwrite_file_func OF((voidpf opaque, voidpf stream, const void* buf,uLong size));
+static ZPOS64_T ZCALLBACK ftell64_file_func OF((voidpf opaque, voidpf stream));
+static long ZCALLBACK fseek64_file_func OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin));
+static int ZCALLBACK fclose_file_func OF((voidpf opaque, voidpf stream));
+static int ZCALLBACK ferror_file_func OF((voidpf opaque, voidpf stream));
+
+typedef struct
+{
+ FILE *file;
+ int filenameLength;
+ void *filename;
+} FILE_IOPOSIX;
+
+static voidpf file_build_ioposix(FILE *file, const char *filename)
+{
+ FILE_IOPOSIX *ioposix = NULL;
+ if (file == NULL)
+ return NULL;
+ ioposix = (FILE_IOPOSIX*)malloc(sizeof(FILE_IOPOSIX));
+ ioposix->file = file;
+ ioposix->filenameLength = (int)strlen(filename) + 1;
+ ioposix->filename = (char*)malloc(ioposix->filenameLength * sizeof(char));
+ strncpy((char*)ioposix->filename, filename, ioposix->filenameLength);
+ return (voidpf)ioposix;
+}
+
+static voidpf ZCALLBACK fopen_file_func (voidpf opaque, const char* filename, int mode)
+{
+ FILE* file = NULL;
+ const char* mode_fopen = NULL;
+ if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ)
+ mode_fopen = "rb";
+ else if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
+ mode_fopen = "r+b";
+ else if (mode & ZLIB_FILEFUNC_MODE_CREATE)
+ mode_fopen = "wb";
+
+ if ((filename != NULL) && (mode_fopen != NULL))
+ {
+ file = fopen(filename, mode_fopen);
+ return file_build_ioposix(file, filename);
+ }
+ return file;
+}
+
+static voidpf ZCALLBACK fopen64_file_func (voidpf opaque, const void* filename, int mode)
+{
+ FILE* file = NULL;
+ const char* mode_fopen = NULL;
+ if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ)
+ mode_fopen = "rb";
+ else if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
+ mode_fopen = "r+b";
+ else if (mode & ZLIB_FILEFUNC_MODE_CREATE)
+ mode_fopen = "wb";
+
+ if ((filename != NULL) && (mode_fopen != NULL))
+ {
+ file = FOPEN_FUNC((const char*)filename, mode_fopen);
+ return file_build_ioposix(file, (const char*)filename);
+ }
+ return file;
+}
+
+static voidpf ZCALLBACK fopendisk64_file_func (voidpf opaque, voidpf stream, unsigned long number_disk, int mode)
+{
+ FILE_IOPOSIX *ioposix = NULL;
+ char *diskFilename = NULL;
+ voidpf ret = NULL;
+ int i = 0;
+
+ if (stream == NULL)
+ return NULL;
+ ioposix = (FILE_IOPOSIX*)stream;
+ diskFilename = (char*)malloc(ioposix->filenameLength * sizeof(char));
+ strncpy(diskFilename, (const char*)ioposix->filename, ioposix->filenameLength);
+ for (i = ioposix->filenameLength - 1; i >= 0; i -= 1)
+ {
+ if (diskFilename[i] != '.')
+ continue;
+ snprintf(&diskFilename[i], ioposix->filenameLength - i, ".z%02lu", number_disk + 1);
+ break;
+ }
+ if (i >= 0)
+ ret = fopen64_file_func(opaque, diskFilename, mode);
+ free(diskFilename);
+ return ret;
+}
+
+static voidpf ZCALLBACK fopendisk_file_func (voidpf opaque, voidpf stream, unsigned long number_disk, int mode)
+{
+ FILE_IOPOSIX *ioposix = NULL;
+ char *diskFilename = NULL;
+ voidpf ret = NULL;
+ int i = 0;
+
+ if (stream == NULL)
+ return NULL;
+ ioposix = (FILE_IOPOSIX*)stream;
+ diskFilename = (char*)malloc(ioposix->filenameLength * sizeof(char));
+ strncpy(diskFilename, (const char*)ioposix->filename, ioposix->filenameLength);
+ for (i = ioposix->filenameLength - 1; i >= 0; i -= 1)
+ {
+ if (diskFilename[i] != '.')
+ continue;
+ snprintf(&diskFilename[i], ioposix->filenameLength - i, ".z%02lu", number_disk + 1);
+ break;
+ }
+ if (i >= 0)
+ ret = fopen_file_func(opaque, diskFilename, mode);
+ free(diskFilename);
+ return ret;
+}
+
+static uLong ZCALLBACK fread_file_func (voidpf opaque, voidpf stream, void* buf, uLong size)
+{
+ FILE_IOPOSIX *ioposix = NULL;
+ uLong ret;
+ if (stream == NULL)
+ return -1;
+ ioposix = (FILE_IOPOSIX*)stream;
+ ret = (uLong)fread(buf, 1, (size_t)size, ioposix->file);
+ return ret;
+}
+
+static uLong ZCALLBACK fwrite_file_func (voidpf opaque, voidpf stream, const void* buf, uLong size)
+{
+ FILE_IOPOSIX *ioposix = NULL;
+ uLong ret;
+ if (stream == NULL)
+ return -1;
+ ioposix = (FILE_IOPOSIX*)stream;
+ ret = (uLong)fwrite(buf, 1, (size_t)size, ioposix->file);
+ return ret;
+}
+
+static long ZCALLBACK ftell_file_func (voidpf opaque, voidpf stream)
+{
+ FILE_IOPOSIX *ioposix = NULL;
+ long ret = -1;
+ if (stream == NULL)
+ return ret;
+ ioposix = (FILE_IOPOSIX*)stream;
+ ret = ftell(ioposix->file);
+ return ret;
+}
+
+static ZPOS64_T ZCALLBACK ftell64_file_func (voidpf opaque, voidpf stream)
+{
+ FILE_IOPOSIX *ioposix = NULL;
+ ZPOS64_T ret = -1;
+ if (stream == NULL)
+ return ret;
+ ioposix = (FILE_IOPOSIX*)stream;
+ ret = FTELLO_FUNC(ioposix->file);
+ return ret;
+}
+
+static long ZCALLBACK fseek_file_func (voidpf opaque, voidpf stream, uLong offset, int origin)
+{
+ FILE_IOPOSIX *ioposix = NULL;
+ int fseek_origin = 0;
+ long ret = 0;
+
+ if (stream == NULL)
+ return -1;
+ ioposix = (FILE_IOPOSIX*)stream;
+
+ switch (origin)
+ {
+ case ZLIB_FILEFUNC_SEEK_CUR:
+ fseek_origin = SEEK_CUR;
+ break;
+ case ZLIB_FILEFUNC_SEEK_END:
+ fseek_origin = SEEK_END;
+ break;
+ case ZLIB_FILEFUNC_SEEK_SET:
+ fseek_origin = SEEK_SET;
+ break;
+ default:
+ return -1;
+ }
+ if (fseek(ioposix->file, offset, fseek_origin) != 0)
+ ret = -1;
+ return ret;
+}
+
+static long ZCALLBACK fseek64_file_func (voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)
+{
+ FILE_IOPOSIX *ioposix = NULL;
+ int fseek_origin = 0;
+ long ret = 0;
+
+ if (stream == NULL)
+ return -1;
+ ioposix = (FILE_IOPOSIX*)stream;
+
+ switch (origin)
+ {
+ case ZLIB_FILEFUNC_SEEK_CUR:
+ fseek_origin = SEEK_CUR;
+ break;
+ case ZLIB_FILEFUNC_SEEK_END:
+ fseek_origin = SEEK_END;
+ break;
+ case ZLIB_FILEFUNC_SEEK_SET:
+ fseek_origin = SEEK_SET;
+ break;
+ default:
+ return -1;
+ }
+
+ if(FSEEKO_FUNC(ioposix->file, offset, fseek_origin) != 0)
+ ret = -1;
+
+ return ret;
+}
+
+
+static int ZCALLBACK fclose_file_func (voidpf opaque, voidpf stream)
+{
+ FILE_IOPOSIX *ioposix = NULL;
+ int ret = -1;
+ if (stream == NULL)
+ return ret;
+ ioposix = (FILE_IOPOSIX*)stream;
+ if (ioposix->filename != NULL)
+ free(ioposix->filename);
+ ret = fclose(ioposix->file);
+ free(ioposix);
+ return ret;
+}
+
+static int ZCALLBACK ferror_file_func (voidpf opaque, voidpf stream)
+{
+ FILE_IOPOSIX *ioposix = NULL;
+ int ret = -1;
+ if (stream == NULL)
+ return ret;
+ ioposix = (FILE_IOPOSIX*)stream;
+ ret = ferror(ioposix->file);
+ return ret;
+}
+
+void fill_fopen_filefunc (zlib_filefunc_def* pzlib_filefunc_def)
+{
+ pzlib_filefunc_def->zopen_file = fopen_file_func;
+ pzlib_filefunc_def->zopendisk_file = fopendisk_file_func;
+ pzlib_filefunc_def->zread_file = fread_file_func;
+ pzlib_filefunc_def->zwrite_file = fwrite_file_func;
+ pzlib_filefunc_def->ztell_file = ftell_file_func;
+ pzlib_filefunc_def->zseek_file = fseek_file_func;
+ pzlib_filefunc_def->zclose_file = fclose_file_func;
+ pzlib_filefunc_def->zerror_file = ferror_file_func;
+ pzlib_filefunc_def->opaque = NULL;
+}
+
+void fill_fopen64_filefunc (zlib_filefunc64_def* pzlib_filefunc_def)
+{
+ pzlib_filefunc_def->zopen64_file = fopen64_file_func;
+ pzlib_filefunc_def->zopendisk64_file = fopendisk64_file_func;
+ pzlib_filefunc_def->zread_file = fread_file_func;
+ pzlib_filefunc_def->zwrite_file = fwrite_file_func;
+ pzlib_filefunc_def->ztell64_file = ftell64_file_func;
+ pzlib_filefunc_def->zseek64_file = fseek64_file_func;
+ pzlib_filefunc_def->zclose_file = fclose_file_func;
+ pzlib_filefunc_def->zerror_file = ferror_file_func;
+ pzlib_filefunc_def->opaque = NULL;
+}
diff --git a/src/qcommon/ioapi.h b/src/qcommon/ioapi.h
new file mode 100644
index 0000000..4b3c297
--- /dev/null
+++ b/src/qcommon/ioapi.h
@@ -0,0 +1,173 @@
+/* ioapi.h -- IO base function header for compress/uncompress .zip
+ part of the MiniZip project
+
+ Copyright (C) 1998-2010 Gilles Vollant
+ http://www.winimage.com/zLibDll/minizip.html
+ Modifications for Zip64 support
+ Copyright (C) 2009-2010 Mathias Svensson
+ http://result42.com
+
+ This program is distributed under the terms of the same license as zlib.
+ See the accompanying LICENSE file for the full text of the license.
+*/
+
+#ifndef _ZLIBIOAPI64_H
+#define _ZLIBIOAPI64_H
+
+#if (!defined(_WIN32)) && (!defined(WIN32)) && (!defined(__APPLE__))
+# ifndef __USE_FILE_OFFSET64
+# define __USE_FILE_OFFSET64
+# endif
+# ifndef __USE_LARGEFILE64
+# define __USE_LARGEFILE64
+# endif
+# ifndef _LARGEFILE64_SOURCE
+# define _LARGEFILE64_SOURCE
+# endif
+# ifndef _FILE_OFFSET_BIT
+# define _FILE_OFFSET_BIT 64
+# endif
+#endif
+
+#include "zconf.h"
+
+#if defined(USE_FILE32API)
+# define fopen64 fopen
+# define ftello64 ftell
+# define fseeko64 fseek
+#else
+# if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__OpenBSD__)
+# define fopen64 fopen
+# define ftello64 ftello
+# define fseeko64 fseeko
+# endif
+# ifdef _MSC_VER
+# define fopen64 fopen
+# if (_MSC_VER >= 1400) && (!(defined(NO_MSCVER_FILE64_FUNC)))
+# define ftello64 _ftelli64
+# define fseeko64 _fseeki64
+# else /* old MSC */
+# define ftello64 ftell
+# define fseeko64 fseek
+# endif
+# endif
+#endif
+
+/* a type choosen by DEFINE */
+#ifdef HAVE_64BIT_INT_CUSTOM
+typedef 64BIT_INT_CUSTOM_TYPE ZPOS64_T;
+#else
+# ifdef HAVE_STDINT_H
+# include "stdint.h"
+ typedef uint64_t ZPOS64_T;
+# else
+# if defined(_MSC_VER) || defined(__BORLANDC__)
+ typedef unsigned __int64 ZPOS64_T;
+# else
+ typedef unsigned long long int ZPOS64_T;
+# endif
+# endif
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ZLIB_FILEFUNC_SEEK_CUR (1)
+#define ZLIB_FILEFUNC_SEEK_END (2)
+#define ZLIB_FILEFUNC_SEEK_SET (0)
+
+#define ZLIB_FILEFUNC_MODE_READ (1)
+#define ZLIB_FILEFUNC_MODE_WRITE (2)
+#define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3)
+#define ZLIB_FILEFUNC_MODE_EXISTING (4)
+#define ZLIB_FILEFUNC_MODE_CREATE (8)
+
+#ifndef ZCALLBACK
+# if (defined(WIN32) || defined(_WIN32) || defined (WINDOWS) || defined (_WINDOWS)) \
+ && defined(CALLBACK) && defined (USEWINDOWS_CALLBACK)
+# define ZCALLBACK CALLBACK
+# else
+# define ZCALLBACK
+# endif
+#endif
+
+typedef voidpf (ZCALLBACK *open_file_func) OF((voidpf opaque, const char* filename, int mode));
+typedef voidpf (ZCALLBACK *opendisk_file_func) OF((voidpf opaque, voidpf stream, unsigned long number_disk, int mode));
+typedef uLong (ZCALLBACK *read_file_func) OF((voidpf opaque, voidpf stream, void* buf, uLong size));
+typedef uLong (ZCALLBACK *write_file_func) OF((voidpf opaque, voidpf stream, const void* buf, uLong size));
+typedef int (ZCALLBACK *close_file_func) OF((voidpf opaque, voidpf stream));
+typedef int (ZCALLBACK *testerror_file_func) OF((voidpf opaque, voidpf stream));
+
+typedef long (ZCALLBACK *tell_file_func) OF((voidpf opaque, voidpf stream));
+typedef long (ZCALLBACK *seek_file_func) OF((voidpf opaque, voidpf stream, uLong offset, int origin));
+
+/* here is the "old" 32 bits structure structure */
+typedef struct zlib_filefunc_def_s
+{
+ open_file_func zopen_file;
+ opendisk_file_func zopendisk_file;
+ read_file_func zread_file;
+ write_file_func zwrite_file;
+ tell_file_func ztell_file;
+ seek_file_func zseek_file;
+ close_file_func zclose_file;
+ testerror_file_func zerror_file;
+ voidpf opaque;
+} zlib_filefunc_def;
+
+typedef voidpf (ZCALLBACK *open64_file_func) OF((voidpf opaque, const void* filename, int mode));
+typedef voidpf (ZCALLBACK *opendisk64_file_func) OF((voidpf opaque, voidpf stream, unsigned long number_disk, int mode));
+typedef ZPOS64_T (ZCALLBACK *tell64_file_func) OF((voidpf opaque, voidpf stream));
+typedef long (ZCALLBACK *seek64_file_func) OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin));
+
+typedef struct zlib_filefunc64_def_s
+{
+ open64_file_func zopen64_file;
+ opendisk64_file_func zopendisk64_file;
+ read_file_func zread_file;
+ write_file_func zwrite_file;
+ tell64_file_func ztell64_file;
+ seek64_file_func zseek64_file;
+ close_file_func zclose_file;
+ testerror_file_func zerror_file;
+ voidpf opaque;
+} zlib_filefunc64_def;
+
+void fill_fopen_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def));
+void fill_fopen64_filefunc OF((zlib_filefunc64_def* pzlib_filefunc_def));
+
+/* now internal definition, only for zip.c and unzip.h */
+typedef struct zlib_filefunc64_32_def_s
+{
+ zlib_filefunc64_def zfile_func64;
+ open_file_func zopen32_file;
+ opendisk_file_func zopendisk32_file;
+ tell_file_func ztell32_file;
+ seek_file_func zseek32_file;
+} zlib_filefunc64_32_def;
+
+#define ZREAD64(filefunc,filestream,buf,size) ((*((filefunc).zfile_func64.zread_file)) ((filefunc).zfile_func64.opaque,filestream,buf,size))
+#define ZWRITE64(filefunc,filestream,buf,size) ((*((filefunc).zfile_func64.zwrite_file)) ((filefunc).zfile_func64.opaque,filestream,buf,size))
+/*#define ZTELL64(filefunc,filestream) ((*((filefunc).ztell64_file)) ((filefunc).opaque,filestream))*/
+/*#define ZSEEK64(filefunc,filestream,pos,mode) ((*((filefunc).zseek64_file)) ((filefunc).opaque,filestream,pos,mode))*/
+#define ZCLOSE64(filefunc,filestream) ((*((filefunc).zfile_func64.zclose_file)) ((filefunc).zfile_func64.opaque,filestream))
+#define ZERROR64(filefunc,filestream) ((*((filefunc).zfile_func64.zerror_file)) ((filefunc).zfile_func64.opaque,filestream))
+
+voidpf call_zopen64 OF((const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode));
+voidpf call_zopendisk64 OF((const zlib_filefunc64_32_def* pfilefunc, voidpf filestream, unsigned long number_disk, int mode));
+long call_zseek64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin));
+ZPOS64_T call_ztell64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream));
+
+void fill_zlib_filefunc64_32_def_from_filefunc32 OF((zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32));
+
+#define ZOPEN64(filefunc,filename,mode) (call_zopen64((&(filefunc)),(filename),(mode)))
+#define ZOPENDISK64(filefunc,filestream,diskn,mode) (call_zopendisk64((&(filefunc)),(filestream),(diskn),(mode)))
+#define ZTELL64(filefunc,filestream) (call_ztell64((&(filefunc)),(filestream)))
+#define ZSEEK64(filefunc,filestream,pos,mode) (call_zseek64((&(filefunc)),(filestream),(pos),(mode)))
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/qcommon/json.h b/src/qcommon/json.h
new file mode 100644
index 0000000..956ae84
--- /dev/null
+++ b/src/qcommon/json.h
@@ -0,0 +1,353 @@
+/*
+===========================================================================
+Copyright (C) 2016 James Canete
+Copyright (C) 2015-2019 GrangerHub
+
+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 3
+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, see <http://www.gnu.org/licenses/>.
+===========================================================================
+*/
+
+#ifndef JSON_H
+#define JSON_H
+
+enum
+{
+ JSONTYPE_STRING, // string
+ JSONTYPE_OBJECT, // object
+ JSONTYPE_ARRAY, // array
+ JSONTYPE_VALUE, // number, true, false, or null
+ JSONTYPE_ERROR // out of data
+};
+
+// --------------------------------------------------------------------------
+// Array Functions
+// --------------------------------------------------------------------------
+
+// Get pointer to first value in array
+// When given pointer to an array, returns pointer to the first
+// returns NULL if array is empty or not an array.
+const char *JSON_ArrayGetFirstValue(const char *json, const char *jsonEnd);
+
+// Get pointer to next value in array
+// When given pointer to a value, returns pointer to the next value
+// returns NULL when no next value.
+const char *JSON_ArrayGetNextValue(const char *json, const char *jsonEnd);
+
+// Get pointers to values in an array
+// returns 0 if not an array, array is empty, or out of data
+// returns number of values in the array and copies into index if successful
+unsigned int JSON_ArrayGetIndex(const char *json, const char *jsonEnd, const char **indexes, unsigned int numIndexes);
+
+// Get pointer to indexed value from array
+// returns NULL if not an array, no index, or out of data
+const char *JSON_ArrayGetValue(const char *json, const char *jsonEnd, unsigned int index);
+
+// --------------------------------------------------------------------------
+// Object Functions
+// --------------------------------------------------------------------------
+
+// Get pointer to named value from object
+// returns NULL if not an object, name not found, or out of data
+const char *JSON_ObjectGetNamedValue(const char *json, const char *jsonEnd, const char *name);
+
+// --------------------------------------------------------------------------
+// Value Functions
+// --------------------------------------------------------------------------
+
+// Get type of value
+// returns JSONTYPE_ERROR if out of data
+unsigned int JSON_ValueGetType(const char *json, const char *jsonEnd);
+
+// Get value as string
+// returns 0 if out of data
+// returns length and copies into string if successful, including terminating nul.
+// string values are stripped of enclosing quotes but not escaped
+unsigned int JSON_ValueGetString(const char *json, const char *jsonEnd, char *outString, unsigned int stringLen);
+
+// Get value as appropriate type
+// returns 0 if value is false, value is null, or out of data
+// returns 1 if value is true
+// returns value otherwise
+double JSON_ValueGetDouble(const char *json, const char *jsonEnd);
+float JSON_ValueGetFloat(const char *json, const char *jsonEnd);
+int JSON_ValueGetInt(const char *json, const char *jsonEnd);
+
+#endif
+
+#ifdef JSON_IMPLEMENTATION
+#include <stdio.h>
+
+// --------------------------------------------------------------------------
+// Internal Functions
+// --------------------------------------------------------------------------
+
+static const char *JSON_SkipSeparators(const char *json, const char *jsonEnd);
+static const char *JSON_SkipString(const char *json, const char *jsonEnd);
+static const char *JSON_SkipStruct(const char *json, const char *jsonEnd);
+static const char *JSON_SkipValue(const char *json, const char *jsonEnd);
+static const char *JSON_SkipValueAndSeparators(const char *json, const char *jsonEnd);
+
+#define IS_SEPARATOR(x) ((x) == ' ' || (x) == '\t' || (x) == '\n' || (x) == '\r' || (x) == ',' || (x) == ':')
+#define IS_STRUCT_OPEN(x) ((x) == '{' || (x) == '[')
+#define IS_STRUCT_CLOSE(x) ((x) == '}' || (x) == ']')
+
+static const char *JSON_SkipSeparators(const char *json, const char *jsonEnd)
+{
+ while (json < jsonEnd && IS_SEPARATOR(*json))
+ json++;
+
+ return json;
+}
+
+static const char *JSON_SkipString(const char *json, const char *jsonEnd)
+{
+ for (json++; json < jsonEnd && *json != '"'; json++)
+ if (*json == '\\')
+ json++;
+
+ return (json + 1 > jsonEnd) ? jsonEnd : json + 1;
+}
+
+static const char *JSON_SkipStruct(const char *json, const char *jsonEnd)
+{
+ json = JSON_SkipSeparators(json + 1, jsonEnd);
+ while (json < jsonEnd && !IS_STRUCT_CLOSE(*json))
+ json = JSON_SkipValueAndSeparators(json, jsonEnd);
+
+ return (json + 1 > jsonEnd) ? jsonEnd : json + 1;
+}
+
+static const char *JSON_SkipValue(const char *json, const char *jsonEnd)
+{
+ if (json >= jsonEnd)
+ return jsonEnd;
+ else if (*json == '"')
+ json = JSON_SkipString(json, jsonEnd);
+ else if (IS_STRUCT_OPEN(*json))
+ json = JSON_SkipStruct(json, jsonEnd);
+ else
+ {
+ while (json < jsonEnd && !IS_SEPARATOR(*json) && !IS_STRUCT_CLOSE(*json))
+ json++;
+ }
+
+ return json;
+}
+
+static const char *JSON_SkipValueAndSeparators(const char *json, const char *jsonEnd)
+{
+ json = JSON_SkipValue(json, jsonEnd);
+ return JSON_SkipSeparators(json, jsonEnd);
+}
+
+// returns 0 if value requires more parsing, 1 if no more data/false/null, 2 if true
+static unsigned int JSON_NoParse(const char *json, const char *jsonEnd)
+{
+ if (!json || json >= jsonEnd || *json == 'f' || *json == 'n')
+ return 1;
+
+ if (*json == 't')
+ return 2;
+
+ return 0;
+}
+
+// --------------------------------------------------------------------------
+// Array Functions
+// --------------------------------------------------------------------------
+
+const char *JSON_ArrayGetFirstValue(const char *json, const char *jsonEnd)
+{
+ if (!json || json >= jsonEnd || !IS_STRUCT_OPEN(*json))
+ return NULL;
+
+ json = JSON_SkipSeparators(json + 1, jsonEnd);
+
+ return (json >= jsonEnd || IS_STRUCT_CLOSE(*json)) ? NULL : json;
+}
+
+const char *JSON_ArrayGetNextValue(const char *json, const char *jsonEnd)
+{
+ if (!json || json >= jsonEnd || IS_STRUCT_CLOSE(*json))
+ return NULL;
+
+ json = JSON_SkipValueAndSeparators(json, jsonEnd);
+
+ return (json >= jsonEnd || IS_STRUCT_CLOSE(*json)) ? NULL : json;
+}
+
+unsigned int JSON_ArrayGetIndex(const char *json, const char *jsonEnd, const char **indexes, unsigned int numIndexes)
+{
+ unsigned int length = 0;
+
+ for (json = JSON_ArrayGetFirstValue(json, jsonEnd); json; json = JSON_ArrayGetNextValue(json, jsonEnd))
+ {
+ if (indexes && numIndexes)
+ {
+ *indexes++ = json;
+ numIndexes--;
+ }
+ length++;
+ }
+
+ return length;
+}
+
+const char *JSON_ArrayGetValue(const char *json, const char *jsonEnd, unsigned int index)
+{
+ for (json = JSON_ArrayGetFirstValue(json, jsonEnd); json && index; json = JSON_ArrayGetNextValue(json, jsonEnd))
+ index--;
+
+ return json;
+}
+
+// --------------------------------------------------------------------------
+// Object Functions
+// --------------------------------------------------------------------------
+
+const char *JSON_ObjectGetNamedValue(const char *json, const char *jsonEnd, const char *name)
+{
+ unsigned int nameLen = strlen(name);
+
+ for (json = JSON_ArrayGetFirstValue(json, jsonEnd); json; json = JSON_ArrayGetNextValue(json, jsonEnd))
+ {
+ if (*json == '"')
+ {
+ const char *thisNameStart, *thisNameEnd;
+
+ thisNameStart = json + 1;
+ json = JSON_SkipString(json, jsonEnd);
+ thisNameEnd = json - 1;
+ json = JSON_SkipSeparators(json, jsonEnd);
+
+ if ((unsigned int)(thisNameEnd - thisNameStart) == nameLen)
+ if (strncmp(thisNameStart, name, nameLen) == 0)
+ return json;
+ }
+ }
+
+ return NULL;
+}
+
+// --------------------------------------------------------------------------
+// Value Functions
+// --------------------------------------------------------------------------
+
+unsigned int JSON_ValueGetType(const char *json, const char *jsonEnd)
+{
+ if (!json || json >= jsonEnd)
+ return JSONTYPE_ERROR;
+ else if (*json == '"')
+ return JSONTYPE_STRING;
+ else if (*json == '{')
+ return JSONTYPE_OBJECT;
+ else if (*json == '[')
+ return JSONTYPE_ARRAY;
+
+ return JSONTYPE_VALUE;
+}
+
+unsigned int JSON_ValueGetString(const char *json, const char *jsonEnd, char *outString, unsigned int stringLen)
+{
+ const char *stringEnd, *stringStart;
+
+ if (!json)
+ {
+ *outString = '\0';
+ return 0;
+ }
+
+ stringStart = json;
+ stringEnd = JSON_SkipValue(stringStart, jsonEnd);
+ if (stringEnd >= jsonEnd)
+ {
+ *outString = '\0';
+ return 0;
+ }
+
+ // skip enclosing quotes if they exist
+ if (*stringStart == '"')
+ stringStart++;
+
+ if (*(stringEnd - 1) == '"')
+ stringEnd--;
+
+ stringLen--;
+ if (stringLen > stringEnd - stringStart)
+ stringLen = stringEnd - stringStart;
+
+ json = stringStart;
+ while (stringLen--)
+ *outString++ = *json++;
+ *outString = '\0';
+
+ return stringEnd - stringStart;
+}
+
+double JSON_ValueGetDouble(const char *json, const char *jsonEnd)
+{
+ char cValue[256];
+ double dValue = 0.0;
+ unsigned int np = JSON_NoParse(json, jsonEnd);
+
+ if (np)
+ return (double)(np - 1);
+
+ if (!JSON_ValueGetString(json, jsonEnd, cValue, 256))
+ return 0.0;
+
+ sscanf(cValue, "%lf", &dValue);
+
+ return dValue;
+}
+
+float JSON_ValueGetFloat(const char *json, const char *jsonEnd)
+{
+ char cValue[256];
+ float fValue = 0.0f;
+ unsigned int np = JSON_NoParse(json, jsonEnd);
+
+ if (np)
+ return (float)(np - 1);
+
+ if (!JSON_ValueGetString(json, jsonEnd, cValue, 256))
+ return 0.0f;
+
+ sscanf(cValue, "%f", &fValue);
+
+ return fValue;
+}
+
+int JSON_ValueGetInt(const char *json, const char *jsonEnd)
+{
+ char cValue[256];
+ int iValue = 0;
+ unsigned int np = JSON_NoParse(json, jsonEnd);
+
+ if (np)
+ return np - 1;
+
+ if (!JSON_ValueGetString(json, jsonEnd, cValue, 256))
+ return 0;
+
+ sscanf(cValue, "%d", &iValue);
+
+ return iValue;
+}
+
+#undef IS_SEPARATOR
+#undef IS_STRUCT_OPEN
+#undef IS_STRUCT_CLOSE
+
+#endif
diff --git a/src/qcommon/md4.cpp b/src/qcommon/md4.cpp
new file mode 100644
index 0000000..1b212f3
--- /dev/null
+++ b/src/qcommon/md4.cpp
@@ -0,0 +1,202 @@
+/*
+ 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 3
+ 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, see <http://www.gnu.org/licenses/>.
+
+ $Id: mdfour.c,v 1.1 2002/08/23 22:03:27 abster Exp $
+*/
+
+#include "cvar.h"
+#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 an 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;
+
+ memset(buf, 0, 128);
+ if (n) 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)
+{
+ copy4(out, md->A);
+ copy4(out+4, md->B);
+ copy4(out+8, md->C);
+ copy4(out+12, md->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/md4.h b/src/qcommon/md4.h
new file mode 100644
index 0000000..cbb8166
--- /dev/null
+++ b/src/qcommon/md4.h
@@ -0,0 +1,6 @@
+#ifndef QCOMMON_MD4_H
+#define QCOMMON_MD4_H
+
+unsigned Com_BlockChecksum( const void *buffer, int length );
+
+#endif
diff --git a/src/qcommon/md5.cpp b/src/qcommon/md5.cpp
new file mode 100644
index 0000000..a8e8c58
--- /dev/null
+++ b/src/qcommon/md5.cpp
@@ -0,0 +1,312 @@
+/*
+ * 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 "files.h"
+#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])
+{
+ 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_Read(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.cpp b/src/qcommon/msg.cpp
new file mode 100644
index 0000000..fc4a62a
--- /dev/null
+++ b/src/qcommon/msg.cpp
@@ -0,0 +1,2248 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "msg.h"
+
+#include "alternatePlayerstate.h"
+#include "cvar.h"
+#include "huffman.h"
+#include "q_shared.h"
+#include "qcommon.h"
+
+static huffman_t msgHuff;
+
+static bool msgInit = false;
+
+int pcount[256];
+
+/*
+==============================================================================
+
+ MESSAGE IO FUNCTIONS
+
+Handles uint8_t ordering and avoids alignment errors
+==============================================================================
+*/
+
+int oldsize = 0;
+
+void MSG_initHuffman(void);
+
+void MSG_Init(msg_t *buf, uint8_t *data, int length)
+{
+ if (!msgInit)
+ {
+ MSG_initHuffman();
+ }
+ ::memset(buf, 0, sizeof(*buf));
+ buf->data = data;
+ buf->maxsize = length;
+}
+
+void MSG_InitOOB(msg_t *buf, uint8_t *data, int length)
+{
+ if (!msgInit)
+ {
+ MSG_initHuffman();
+ }
+ ::memset(buf, 0, sizeof(*buf));
+ buf->data = data;
+ buf->maxsize = length;
+ buf->oob = true;
+}
+
+void MSG_Clear(msg_t *buf)
+{
+ buf->cursize = 0;
+ buf->overflowed = false;
+ buf->bit = 0; //<- in bits
+}
+
+void MSG_Bitstream(msg_t *buf) { buf->oob = false; }
+void MSG_BeginReading(msg_t *msg)
+{
+ msg->readcount = 0;
+ msg->bit = 0;
+ msg->oob = false;
+}
+
+void MSG_BeginReadingOOB(msg_t *msg)
+{
+ msg->readcount = 0;
+ msg->bit = 0;
+ msg->oob = true;
+}
+
+void MSG_Copy(msg_t *buf, uint8_t *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");
+ }
+ ::memcpy(buf, src, sizeof(msg_t));
+ buf->data = data;
+ ::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;
+
+ if ( msg->overflowed )
+ {
+ 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 (msg->cursize + (bits >> 3) > msg->maxsize)
+ {
+ msg->overflowed = true;
+ return;
+ }
+ if (bits == 8)
+ {
+ msg->data[msg->cursize] = value;
+ msg->cursize += 1;
+ msg->bit += 8;
+ }
+ else if (bits == 16)
+ {
+ short temp = value;
+
+ CopyLittleShort(&msg->data[msg->cursize], &temp);
+ msg->cursize += 2;
+ msg->bit += 16;
+ }
+ else if (bits == 32)
+ {
+ CopyLittleLong(&msg->data[msg->cursize], &value);
+ msg->cursize += 4;
+ msg->bit += 32;
+ }
+ else
+ Com_Error(ERR_DROP, "can't write %d bits", bits);
+ }
+ else
+ {
+ value &= (0xffffffff >> (32 - bits));
+ if (bits & 7)
+ {
+ int nbits;
+ nbits = bits & 7;
+
+ if ( msg->bit + nbits > msg->maxsize << 3 )
+ {
+ msg->overflowed = true;
+ return;
+ }
+
+ 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)
+ {
+ Huff_offsetTransmit(&msgHuff.compressor, (value & 0xff), msg->data, &msg->bit, msg->maxsize << 3);
+ value = (value >> 8);
+
+ if (msg->bit > msg->maxsize << 3)
+ {
+ msg->overflowed = true;
+ return;
+ }
+ }
+ }
+ msg->cursize = (msg->bit >> 3) + 1;
+ }
+}
+
+int MSG_ReadBits(msg_t *msg, int bits)
+{
+ int value;
+ int get;
+ bool sgn;
+
+
+ if (msg->readcount > msg->cursize)
+ {
+ return 0;
+ }
+
+ value = 0;
+
+ if (bits < 0)
+ {
+ bits = -bits;
+ sgn = true;
+ }
+ else
+ {
+ sgn = false;
+ }
+
+ if (msg->oob)
+ {
+ if (msg->readcount + (bits >> 3) > msg->cursize)
+ {
+ msg->readcount = msg->cursize + 1;
+ return 0;
+ }
+
+ if (bits == 8)
+ {
+ value = msg->data[msg->readcount];
+ msg->readcount += 1;
+ msg->bit += 8;
+ }
+ else if (bits == 16)
+ {
+ short temp;
+
+ CopyLittleShort(&temp, &msg->data[msg->readcount]);
+ value = temp;
+ msg->readcount += 2;
+ msg->bit += 16;
+ }
+ else if (bits == 32)
+ {
+ CopyLittleLong(&value, &msg->data[msg->readcount]);
+ msg->readcount += 4;
+ msg->bit += 32;
+ }
+ else
+ Com_Error(ERR_DROP, "can't read %d bits", bits);
+ }
+ else
+ {
+ int nbits = 0;
+ if (bits & 7)
+ {
+ nbits = bits & 7;
+ if (msg->bit + nbits > msg->cursize << 3)
+ {
+ msg->readcount = msg->cursize + 1;
+ return 0;
+ }
+ for (int i = 0; i < nbits; i++)
+ {
+ value |= (Huff_getBit(msg->data, &msg->bit) << i);
+ }
+ bits = bits - nbits;
+ }
+ if (bits)
+ {
+ for (int i = 0; i < bits; i += 8)
+ {
+ Huff_offsetReceive(msgHuff.decompressor.tree, &get, msg->data, &msg->bit, msg->cursize << 3);
+ value |= (get << (i + nbits));
+
+ if (msg->bit > msg->cursize << 3)
+ {
+ msg->readcount = msg->cursize + 1;
+ return 0;
+ }
+ }
+ }
+ msg->readcount = (msg->bit >> 3) + 1;
+ }
+ if (sgn && bits > 0 && bits < 32)
+ {
+ 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, ((uint8_t *)data)[i]);
+ }
+}
+
+void MSG_WriteShort(msg_t *sb, int c)
+{
+#ifdef PARANOID
+ if (c < ((short)0x8000) || c > (short)0x7fff) Com_Error(ERR_FATAL, "MSG_WriteShort: range error");
+#endif
+
+ MSG_WriteBits(sb, c, 16);
+}
+
+void MSG_WriteLong(msg_t *sb, int c) { MSG_WriteBits(sb, c, 32); }
+void MSG_WriteFloat(msg_t *sb, float f)
+{
+ floatint_t dat;
+ dat.f = f;
+ MSG_WriteBits(sb, dat.i, 32);
+}
+
+static void MSG_WriteString2(msg_t *sb, const char *s, int maxsize)
+{
+ int size = strlen(s) + 1;
+
+ if (size > maxsize)
+ {
+ Com_Printf("MSG_WriteString2: %i > %i\n", size, maxsize);
+ MSG_WriteData(sb, "", 1);
+ return;
+ }
+
+ MSG_WriteData(sb, s, size);
+}
+
+void MSG_WriteString(msg_t *sb, const char *s) { MSG_WriteString2(sb, s, MAX_STRING_CHARS); }
+void MSG_WriteBigString(msg_t *sb, const char *s) { MSG_WriteString2(sb, s, BIG_INFO_STRING); }
+void MSG_WriteAngle(msg_t *sb, float f) { MSG_WriteByte(sb, (int)(f * 256 / 360) & 255); }
+void MSG_WriteAngle16(msg_t *sb, float f) { MSG_WriteShort(sb, ANGLE2SHORT(f)); }
+//============================================================
+
+//
+// reading functions
+//
+
+// returns -1 if no more characters are available
+int MSG_ReadChar(msg_t *msg)
+{
+ int c;
+
+ c = (signed char)MSG_ReadBits(msg, 8);
+ if (msg->readcount > msg->cursize)
+ {
+ c = -1;
+ }
+
+ return c;
+}
+
+int MSG_ReadByte(msg_t *msg)
+{
+ int c;
+
+ c = (unsigned char)MSG_ReadBits(msg, 8);
+ if (msg->readcount > msg->cursize)
+ {
+ c = -1;
+ }
+ return c;
+}
+
+int MSG_LookaheadByte(msg_t *msg)
+{
+ const int bloc = Huff_getBloc();
+ const int readcount = msg->readcount;
+ const int bit = msg->bit;
+ int c = MSG_ReadByte(msg);
+ Huff_setBloc(bloc);
+ msg->readcount = readcount;
+ msg->bit = bit;
+ return c;
+}
+
+int MSG_ReadShort(msg_t *msg)
+{
+ int c;
+
+ c = (short)MSG_ReadBits(msg, 16);
+ if (msg->readcount > msg->cursize)
+ {
+ c = -1;
+ }
+
+ return c;
+}
+
+int MSG_ReadLong(msg_t *msg)
+{
+ int c;
+
+ c = MSG_ReadBits(msg, 32);
+ if (msg->readcount > msg->cursize)
+ {
+ c = -1;
+ }
+
+ return c;
+}
+
+float MSG_ReadFloat(msg_t *msg)
+{
+ floatint_t dat;
+
+ dat.i = MSG_ReadBits(msg, 32);
+ if (msg->readcount > msg->cursize)
+ {
+ dat.f = -1;
+ }
+
+ return dat.f;
+}
+
+char *MSG_ReadString(msg_t *msg)
+{
+ static char string[MAX_STRING_CHARS];
+
+ size_t l = 0;
+ do
+ {
+ int c = MSG_ReadByte(msg); // use ReadByte so -1 is out of bounds
+ if (c == -1 || c == 0)
+ {
+ break;
+ }
+
+ 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];
+
+ size_t l = 0;
+ do
+ {
+ int c = MSG_ReadByte(msg); // use ReadByte so -1 is out of bounds
+ if (c == -1 || c == 0)
+ {
+ break;
+ }
+
+ 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];
+
+ size_t l = 0;
+ do
+ {
+ int c = MSG_ReadByte(msg); // use ReadByte so -1 is out of bounds
+ if (c == -1 || c == 0 || c == '\n')
+ {
+ break;
+ }
+
+ 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++)
+ {
+ ((uint8_t *)data)[i] = MSG_ReadByte(msg);
+ }
+}
+
+// a string hasher which gives the same hash value even if the
+// string is later modified via the legacy MSG read/write code
+int MSG_HashKey(int alternateProtocol, const char *string, int maxlen)
+{
+ int hash, i;
+
+ hash = 0;
+ for (i = 0; i < maxlen && string[i] != '\0'; i++)
+ {
+ if (string[i] & 0x80 || (alternateProtocol == 2 && string[i] == '%'))
+ hash += '.' * (119 + i);
+ else
+ hash += string[i] * (119 + i);
+ }
+ hash = (hash ^ (hash >> 10) ^ (hash >> 20));
+ return hash;
+}
+
+/*
+=============================================================================
+
+delta functions
+
+=============================================================================
+*/
+
+extern cvar_t *cl_shownet;
+
+#define LOG(x) \
+ if (cl_shownet && cl_shownet->integer == 4) \
+ { \
+ Com_Printf("%s ", x); \
+ };
+
+void MSG_WriteDelta(msg_t *msg, int oldV, int newV, int bits)
+{
+ if (oldV == newV)
+ {
+ MSG_WriteBits(msg, 0, 1);
+ return;
+ }
+ MSG_WriteBits(msg, 1, 1);
+ MSG_WriteBits(msg, newV, bits);
+}
+
+int MSG_ReadDelta(msg_t *msg, int oldV, int bits)
+{
+ if (MSG_ReadBits(msg, 1))
+ {
+ return MSG_ReadBits(msg, bits);
+ }
+ return oldV;
+}
+
+void MSG_WriteDeltaFloat(msg_t *msg, float oldV, float newV)
+{
+ floatint_t fi;
+ if (oldV == newV)
+ {
+ MSG_WriteBits(msg, 0, 1);
+ return;
+ }
+ fi.f = newV;
+ MSG_WriteBits(msg, 1, 1);
+ MSG_WriteBits(msg, fi.i, 32);
+}
+
+float MSG_ReadDeltaFloat(msg_t *msg, float oldV)
+{
+ if (MSG_ReadBits(msg, 1))
+ {
+ floatint_t fi;
+
+ fi.i = MSG_ReadBits(msg, 32);
+ return fi.f;
+ }
+ return oldV;
+}
+
+/*
+=============================================================================
+
+delta functions with keys
+
+=============================================================================
+*/
+
+unsigned 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 - 1]);
+ }
+ return oldV;
+}
+
+void MSG_WriteDeltaKeyFloat(msg_t *msg, int key, float oldV, float newV)
+{
+ floatint_t fi;
+ if (oldV == newV)
+ {
+ MSG_WriteBits(msg, 0, 1);
+ return;
+ }
+ fi.f = newV;
+ MSG_WriteBits(msg, 1, 1);
+ MSG_WriteBits(msg, fi.i ^ key, 32);
+}
+
+float MSG_ReadDeltaKeyFloat(msg_t *msg, int key, float oldV)
+{
+ if (MSG_ReadBits(msg, 1))
+ {
+ floatint_t fi;
+
+ fi.i = MSG_ReadBits(msg, 32) ^ key;
+ return fi.f;
+ }
+ return oldV;
+}
+
+/*
+============================================================================
+
+usercmd_t communication
+
+============================================================================
+*/
+
+/*
+=====================
+MSG_WriteDeltaUsercmdKey
+=====================
+*/
+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_ReadDeltaUsercmdKey
+=====================
+*/
+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);
+ if (to->forwardmove == -128) to->forwardmove = -127;
+ to->rightmove = MSG_ReadDeltaKey(msg, key, from->rightmove, 8);
+ if (to->rightmove == -128) to->rightmove = -127;
+ to->upmove = MSG_ReadDeltaKey(msg, key, from->upmove, 8);
+ if (to->upmove == -128) to->upmove = -127;
+ 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 {
+ const char *name;
+ size_t offset;
+ int bits; // 0 = float
+} netField_t;
+
+// using the stringizing operator to save typing...
+#define NETF(x) #x, (size_t) & ((entityState_t *) 0)->x
+
+netField_t entityStateFields[] = {
+ {NETF(pos.trTime), 32},
+ {NETF(pos.trBase[0]), 0},
+ {NETF(pos.trBase[1]), 0},
+ {NETF(pos.trDelta[0]), 0},
+ {NETF(pos.trDelta[1]), 0},
+ {NETF(pos.trBase[2]), 0},
+ {NETF(apos.trBase[1]), 0},
+ {NETF(pos.trDelta[2]), 0},
+ {NETF(apos.trBase[0]), 0},
+ {NETF(event), 10},
+ {NETF(angles2[1]), 0},
+ {NETF(eType), 8},
+ {NETF(torsoAnim), 8},
+ {NETF(weaponAnim), 8},
+ {NETF(eventParm), 8},
+ {NETF(legsAnim), 8},
+ {NETF(groundEntityNum), GENTITYNUM_BITS},
+ {NETF(pos.trType), 8},
+ {NETF(eFlags), 19},
+ {NETF(otherEntityNum), GENTITYNUM_BITS},
+ {NETF(weapon), 8},
+ {NETF(clientNum), 8},
+ {NETF(angles[1]), 0},
+ {NETF(pos.trDuration), 32},
+ {NETF(apos.trType), 8},
+ {NETF(origin[0]), 0},
+ {NETF(origin[1]), 0},
+ {NETF(origin[2]), 0},
+ {NETF(solid), 24},
+ {NETF(misc), MAX_MISC},
+ {NETF(modelindex), 8},
+ {NETF(otherEntityNum2), GENTITYNUM_BITS},
+ {NETF(loopSound), 8},
+ {NETF(generic1), 10},
+ {NETF(origin2[2]), 0},
+ {NETF(origin2[0]), 0},
+ {NETF(origin2[1]), 0},
+ {NETF(modelindex2), 8},
+ {NETF(angles[0]), 0},
+ {NETF(time), 32},
+ {NETF(apos.trTime), 32},
+ {NETF(apos.trDuration), 32},
+ {NETF(apos.trBase[2]), 0},
+ {NETF(apos.trDelta[0]), 0},
+ {NETF(apos.trDelta[1]), 0},
+ {NETF(apos.trDelta[2]), 0},
+ {NETF(time2), 32},
+ {NETF(angles[2]), 0},
+ {NETF(angles2[0]), 0},
+ {NETF(angles2[2]), 0},
+ {NETF(constantLight), 32},
+ {NETF(frame), 16}
+};
+
+// if (int)f == f and (int)f + ( 1<<(FLOAT_INT_BITS-1) ) < ( 1 << FLOAT_INT_BITS )
+// the float will be sent with FLOAT_INT_BITS, otherwise all 32 bits will be sent
+#define FLOAT_INT_BITS 13
+#define FLOAT_INT_BIAS (1 << (FLOAT_INT_BITS - 1))
+
+/*
+==================
+MSG_WriteDeltaEntity
+
+Writes part of a packetentities message, including the entity number.
+Can delta from either a baseline or a previous packet_entity
+If to is NULL, a remove entity update will be sent
+If force is not set, then nothing at all will be generated if the entity is
+identical, under the assumption that the in-order delta code will catch it.
+==================
+*/
+void MSG_WriteDeltaEntity(int alternateProtocol, msg_t *msg, struct entityState_s *from, struct entityState_s *to, bool force)
+{
+ int i, lc;
+ int numFields;
+ netField_t *field;
+ int trunc;
+ float fullFloat;
+ int *fromF, *toF;
+
+ numFields = ARRAY_LEN(entityStateFields);
+
+ // all fields should be 32 bits to avoid any compiler packing issues
+ // the "number" field is not part of the field list
+ // if this assert fails, someone added a field to the entityState_t
+ // struct without updating the message fields
+ assert(numFields + 1 == sizeof(*from) / 4);
+
+ // a NULL to is a delta remove message
+ if (to == NULL)
+ {
+ if (from == NULL)
+ {
+ return;
+ }
+ MSG_WriteBits(msg, from->number, GENTITYNUM_BITS);
+ MSG_WriteBits(msg, 1, 1);
+ return;
+ }
+
+ if (to->number < 0 || to->number >= MAX_GENTITIES)
+ {
+ Com_Error(ERR_FATAL, "MSG_WriteDeltaEntity: Bad entity number: %i", to->number);
+ }
+
+ lc = 0;
+ // build the change vector as bytes so it is endien independent
+ for (i = 0, field = entityStateFields; i < numFields; i++, field++)
+ {
+ if (alternateProtocol == 2 && i == 13)
+ {
+ continue;
+ }
+ fromF = (int *)((uint8_t *)from + field->offset);
+ toF = (int *)((uint8_t *)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
+
+ if (alternateProtocol == 2 && lc - 1 > 13)
+ {
+ MSG_WriteByte(msg, lc - 1); // # of changes
+ }
+ else
+ {
+ MSG_WriteByte(msg, lc); // # of changes
+ }
+
+ oldsize += numFields;
+
+ for (i = 0, field = entityStateFields; i < lc; i++, field++)
+ {
+ if (alternateProtocol == 2 && i == 13)
+ {
+ continue;
+ }
+
+ fromF = (int *)((uint8_t *)from + field->offset);
+ toF = (int *)((uint8_t *)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
+ if (alternateProtocol == 2 && i == 33)
+ {
+ MSG_WriteBits(msg, *toF, 8);
+ }
+ else
+ {
+ MSG_WriteBits(msg, *toF, field->bits);
+ }
+ }
+ }
+ }
+}
+
+/*
+==================
+MSG_ReadDeltaEntity
+
+The entity number has already been read from the message, which
+is how the from state is identified.
+
+If the delta removes the entity, entityState_t->number will be set to MAX_GENTITIES-1
+
+Can go from either a baseline or a previous packet_entity
+==================
+*/
+void MSG_ReadDeltaEntity(int alternateProtocol, 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)
+ {
+ ::memset(to, 0, sizeof(*to));
+ to->number = MAX_GENTITIES - 1;
+ if (cl_shownet && (cl_shownet->integer >= 2 || cl_shownet->integer == -1))
+ {
+ Com_Printf("%3i: #%-3i remove\n", msg->readcount, number);
+ }
+ return;
+ }
+
+ // check for no delta
+ if (MSG_ReadBits(msg, 1) == 0)
+ {
+ *to = *from;
+ to->number = number;
+ return;
+ }
+
+ numFields = ARRAY_LEN(entityStateFields);
+ lc = MSG_ReadByte(msg);
+ if (alternateProtocol == 2 && lc - 1 >= 13)
+ {
+ ++lc;
+ }
+
+ if (lc > numFields || lc < 0)
+ {
+ Com_Error(ERR_DROP, "invalid entityState field count");
+ }
+
+ // shownet 2/3 will interleave with other printed info, -1 will
+ // just print the delta records`
+ if (cl_shownet && (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 *)((uint8_t *)from + field->offset);
+ toF = (int *)((uint8_t *)to + field->offset);
+ if (alternateProtocol == 2 && i == 13)
+ {
+ *toF = 0;
+ continue;
+ }
+
+ 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
+ if (alternateProtocol == 2 && i == 33)
+ {
+ *toF = MSG_ReadBits(msg, 8);
+ }
+ else
+ {
+ *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 *)((uint8_t *)from + field->offset);
+ toF = (int *)((uint8_t *)to + field->offset);
+ // no change
+ *toF = *fromF;
+ }
+
+ if (print)
+ {
+ if (msg->bit == 0)
+ {
+ endBit = msg->readcount * 8 - GENTITYNUM_BITS;
+ }
+ else
+ {
+ endBit = (msg->readcount - 1) * 8 + msg->bit - GENTITYNUM_BITS;
+ }
+ Com_Printf(" (%i bits)\n", endBit - startBit);
+ }
+}
+
+/*
+============================================================================
+
+plyer_state_t communication
+
+============================================================================
+*/
+
+// using the stringizing operator to save typing...
+#define PSF(x) #x, (size_t) & ((playerState_t *) 0)->x
+
+netField_t playerStateFields[] = {
+ {PSF(commandTime), 32},
+ {PSF(origin[0]), 0},
+ {PSF(origin[1]), 0},
+ {PSF(bobCycle), 8},
+ {PSF(velocity[0]), 0},
+ {PSF(velocity[1]), 0},
+ {PSF(viewangles[1]), 0},
+ {PSF(viewangles[0]), 0},
+ {PSF(weaponTime), -16},
+ {PSF(origin[2]), 0},
+ {PSF(velocity[2]), 0},
+ {PSF(legsTimer), 8},
+ {PSF(pm_time), -16},
+ {PSF(eventSequence), 16},
+ {PSF(torsoAnim), 8},
+ {PSF(weaponAnim), 8},
+ {PSF(movementDir), 4},
+ {PSF(events[0]), 8},
+ {PSF(legsAnim), 8},
+ {PSF(events[1]), 8},
+ {PSF(pm_flags), 24},
+ {PSF(groundEntityNum), GENTITYNUM_BITS},
+ {PSF(weaponstate), 4},
+ {PSF(eFlags), 16},
+ {PSF(externalEvent), 10},
+ {PSF(gravity), -16},
+ {PSF(speed), -16},
+ {PSF(delta_angles[1]), 16},
+ {PSF(externalEventParm), 8},
+ {PSF(viewheight), -8},
+ {PSF(damageEvent), 8},
+ {PSF(damageYaw), 8},
+ {PSF(damagePitch), 8},
+ {PSF(damageCount), 8},
+ {PSF(ammo), 12},
+ {PSF(clips), 4},
+ {PSF(generic1), 10},
+ {PSF(pm_type), 8},
+ {PSF(delta_angles[0]), 16},
+ {PSF(delta_angles[2]), 16},
+ {PSF(torsoTimer), 12},
+ {PSF(tauntTimer), 12},
+ {PSF(eventParms[0]), 8},
+ {PSF(eventParms[1]), 8},
+ {PSF(clientNum), 8},
+ {PSF(weapon), 5},
+ {PSF(viewangles[2]), 0},
+ {PSF(grapplePoint[0]), 0},
+ {PSF(grapplePoint[1]), 0},
+ {PSF(grapplePoint[2]), 0},
+ {PSF(otherEntityNum), GENTITYNUM_BITS},
+ {PSF(loopSound), 16}
+};
+
+#define APSF(x) #x, (size_t) & ((alternatePlayerState_t *) 0)->x
+
+netField_t alternatePlayerStateFields[] = {
+ {APSF(commandTime), 32},
+ {APSF(origin[0]), 0},
+ {APSF(origin[1]), 0},
+ {APSF(bobCycle), 8},
+ {APSF(velocity[0]), 0},
+ {APSF(velocity[1]), 0},
+ {APSF(viewangles[1]), 0},
+ {APSF(viewangles[0]), 0},
+ {APSF(weaponTime), -16},
+ {APSF(origin[2]), 0},
+ {APSF(velocity[2]), 0},
+ {APSF(legsTimer), 8},
+ {APSF(pm_time), -16},
+ {APSF(eventSequence), 16},
+ {APSF(torsoAnim), 8},
+ {APSF(movementDir), 4},
+ {APSF(events[0]), 8},
+ {APSF(legsAnim), 8},
+ {APSF(events[1]), 8},
+ {APSF(pm_flags), 16},
+ {APSF(groundEntityNum), GENTITYNUM_BITS},
+ {APSF(weaponstate), 4},
+ {APSF(eFlags), 16},
+ {APSF(externalEvent), 10},
+ {APSF(gravity), -16},
+ {APSF(speed), -16},
+ {APSF(delta_angles[1]), 16},
+ {APSF(externalEventParm), 8},
+ {APSF(viewheight), -8},
+ {APSF(damageEvent), 8},
+ {APSF(damageYaw), 8},
+ {APSF(damagePitch), 8},
+ {APSF(damageCount), 8},
+ {APSF(generic1), 8},
+ {APSF(pm_type), 8},
+ {APSF(delta_angles[0]), 16},
+ {APSF(delta_angles[2]), 16},
+ {APSF(torsoTimer), 12},
+ {APSF(eventParms[0]), 8},
+ {APSF(eventParms[1]), 8},
+ {APSF(clientNum), 8},
+ {APSF(weapon), 5},
+ {APSF(viewangles[2]), 0},
+ {APSF(grapplePoint[0]), 0},
+ {APSF(grapplePoint[1]), 0},
+ {APSF(grapplePoint[2]), 0},
+ {APSF(otherEntityNum), GENTITYNUM_BITS},
+ {APSF(loopSound), 16}
+};
+
+/*
+=============
+MSG_WriteDeltaPlayerstate
+
+=============
+*/
+void MSG_WriteDeltaPlayerstate(int alternateProtocol, msg_t *msg, struct playerState_s *from, struct playerState_s *to)
+{
+ int i;
+ playerState_t dummy;
+ int statsbits;
+ int persistantbits;
+ int altFromAmmo[3];
+ int altToAmmo[3];
+ int ammobits;
+ int miscbits;
+ int numFields;
+ netField_t *field;
+ int *fromF, *toF;
+ float fullFloat;
+ int trunc, lc;
+
+ if (!from)
+ {
+ from = &dummy;
+ ::memset(&dummy, 0, sizeof(dummy));
+ }
+
+ numFields = ARRAY_LEN(playerStateFields);
+
+ lc = 0;
+ for (i = 0, field = playerStateFields; i < numFields; i++, field++)
+ {
+ if (alternateProtocol == 2 && (i == 15 || i == 34 || i == 35 || i == 41))
+ {
+ continue;
+ }
+ fromF = (int *)((uint8_t *)from + field->offset);
+ toF = (int *)((uint8_t *)to + field->offset);
+ if (*fromF != *toF)
+ {
+ lc = i + 1;
+ }
+ }
+
+ if (alternateProtocol == 2)
+ {
+ if (lc - 1 > 41)
+ {
+ MSG_WriteByte(msg, lc - 4); // # of changes
+ }
+ else if (lc - 1 > 35)
+ {
+ MSG_WriteByte(msg, lc - 3); // # of changes
+ }
+ else if (lc - 1 > 34)
+ {
+ MSG_WriteByte(msg, lc - 2); // # of changes
+ }
+ else if (lc - 1 > 15)
+ {
+ MSG_WriteByte(msg, lc - 1); // # of changes
+ }
+ else
+ {
+ MSG_WriteByte(msg, lc); // # of changes
+ }
+ }
+ else
+ {
+ MSG_WriteByte(msg, lc); // # of changes
+ }
+
+ oldsize += numFields - lc;
+
+ for (i = 0, field = playerStateFields; i < lc; i++, field++)
+ {
+ if (alternateProtocol == 2 && (i == 15 || i == 34 || i == 35 || i == 41))
+ {
+ continue;
+ }
+
+ fromF = (int *)((uint8_t *)from + field->offset);
+ toF = (int *)((uint8_t *)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
+ if (alternateProtocol == 2)
+ {
+ if (i == 20)
+ {
+ MSG_WriteBits(msg, *toF, 16);
+ }
+ else if (i == 36)
+ {
+ MSG_WriteBits(msg, *toF, 8);
+ }
+ else
+ {
+ MSG_WriteBits(msg, *toF, field->bits);
+ }
+ }
+ else
+ {
+ MSG_WriteBits(msg, *toF, field->bits);
+ }
+ }
+ }
+
+ //
+ // 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;
+ }
+ }
+ if (alternateProtocol == 2)
+ {
+ altFromAmmo[0] = (from->weaponAnim & 0xFF) | ((from->pm_flags >> 8) & 0xFF00);
+ altFromAmmo[1] = (from->ammo & 0xFFF) | ((from->clips << 12) & 0xF000);
+ altFromAmmo[2] = (from->tauntTimer & 0xFFF) | ((from->generic1 << 4) & 0x3000);
+ altToAmmo[0] = (to->weaponAnim & 0xFF) | ((to->pm_flags >> 8) & 0xFF00);
+ altToAmmo[1] = (to->ammo & 0xFFF) | ((to->clips << 12) & 0xF000);
+ altToAmmo[2] = (to->tauntTimer & 0xFFF) | ((to->generic1 << 4) & 0x3000);
+ ammobits = 0;
+ for (i = 0; i < 3; i++)
+ {
+ if (altToAmmo[i] != altFromAmmo[i])
+ {
+ ammobits |= 1 << i;
+ }
+ }
+ }
+ else
+ {
+ ammobits = 0;
+ }
+ miscbits = 0;
+ for (i = 0; i < MAX_MISC; i++)
+ {
+ if (to->misc[i] != from->misc[i])
+ {
+ miscbits |= 1 << i;
+ }
+ }
+
+ if (!statsbits && !persistantbits && !ammobits && !miscbits)
+ {
+ MSG_WriteBits(msg, 0, 1); // no change
+ oldsize += 4;
+ return;
+ }
+ MSG_WriteBits(msg, 1, 1); // changed
+
+ if (statsbits)
+ {
+ MSG_WriteBits(msg, 1, 1); // changed
+ MSG_WriteBits(msg, statsbits, MAX_STATS);
+ for (i = 0; i < MAX_STATS; i++)
+ if (statsbits & (1 << i)) MSG_WriteShort(msg, to->stats[i]);
+ }
+ else
+ {
+ MSG_WriteBits(msg, 0, 1); // no change
+ }
+
+ if (persistantbits)
+ {
+ MSG_WriteBits(msg, 1, 1); // changed
+ MSG_WriteBits(msg, persistantbits, MAX_PERSISTANT);
+ for (i = 0; i < MAX_PERSISTANT; i++)
+ if (persistantbits & (1 << i)) MSG_WriteShort(msg, to->persistant[i]);
+ }
+ else
+ {
+ MSG_WriteBits(msg, 0, 1); // no change
+ }
+
+ if (alternateProtocol == 2)
+ {
+ if (ammobits)
+ {
+ MSG_WriteBits(msg, 1, 1); // changed
+ MSG_WriteBits(msg, ammobits, 16);
+ for (i = 0; i < 3; i++)
+ if (ammobits & (1 << i)) MSG_WriteShort(msg, altToAmmo[i]);
+ }
+ else
+ {
+ MSG_WriteBits(msg, 0, 1); // no change
+ }
+ }
+
+ if (miscbits)
+ {
+ MSG_WriteBits(msg, 1, 1); // changed
+ MSG_WriteBits(msg, miscbits, MAX_MISC);
+ for (i = 0; i < MAX_MISC; i++)
+ if (miscbits & (1 << i)) MSG_WriteLong(msg, to->misc[i]);
+ }
+ else
+ {
+ MSG_WriteBits(msg, 0, 1); // no change
+ }
+}
+
+/*
+===================
+MSG_ReadDeltaPlayerstate
+===================
+*/
+void MSG_ReadDeltaPlayerstate(msg_t *msg, playerState_t *from, playerState_t *to)
+{
+ int i, lc;
+ int bits;
+ netField_t *field;
+ int numFields;
+ int startBit, endBit;
+ int print;
+ int *fromF, *toF;
+ int trunc;
+ playerState_t dummy;
+
+ if (!from)
+ {
+ from = &dummy;
+ ::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 && (cl_shownet->integer >= 2 || cl_shownet->integer == -2))
+ {
+ print = 1;
+ Com_Printf("%3i: playerstate ", msg->readcount);
+ }
+ else
+ {
+ print = 0;
+ }
+
+ numFields = ARRAY_LEN(playerStateFields);
+ lc = MSG_ReadByte(msg);
+
+ if (lc > numFields || lc < 0)
+ {
+ Com_Error(ERR_DROP, "invalid playerState field count");
+ }
+
+ for (i = 0, field = playerStateFields; i < lc; i++, field++)
+ {
+ fromF = (int *)((uint8_t *)from + field->offset);
+ toF = (int *)((uint8_t *)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 *)((uint8_t *)from + field->offset);
+ toF = (int *)((uint8_t *)to + field->offset);
+ // no change
+ *toF = *fromF;
+ }
+
+ // read the arrays
+ if (MSG_ReadBits(msg, 1))
+ {
+ // parse stats
+ if (MSG_ReadBits(msg, 1))
+ {
+ LOG("PS_STATS");
+ bits = MSG_ReadBits(msg, MAX_STATS);
+ for (i = 0; i < MAX_STATS; i++)
+ {
+ if (bits & (1 << i))
+ {
+ to->stats[i] = MSG_ReadShort(msg);
+ }
+ }
+ }
+
+ // parse persistant stats
+ if (MSG_ReadBits(msg, 1))
+ {
+ LOG("PS_PERSISTANT");
+ bits = MSG_ReadBits(msg, MAX_PERSISTANT);
+ for (i = 0; i < MAX_PERSISTANT; i++)
+ {
+ if (bits & (1 << i))
+ {
+ to->persistant[i] = MSG_ReadShort(msg);
+ }
+ }
+ }
+
+ // parse misc data
+ if (MSG_ReadBits(msg, 1))
+ {
+ LOG("PS_MISC");
+ bits = MSG_ReadBits(msg, MAX_MISC);
+ for (i = 0; i < MAX_MISC; i++)
+ {
+ if (bits & (1 << i))
+ {
+ to->misc[i] = MSG_ReadLong(msg);
+ }
+ }
+ }
+ }
+
+ if (print)
+ {
+ if (msg->bit == 0)
+ {
+ endBit = msg->readcount * 8 - GENTITYNUM_BITS;
+ }
+ else
+ {
+ endBit = (msg->readcount - 1) * 8 + msg->bit - GENTITYNUM_BITS;
+ }
+ Com_Printf(" (%i bits)\n", endBit - startBit);
+ }
+}
+
+void MSG_ReadDeltaAlternatePlayerstate(msg_t *msg, alternatePlayerState_t *from, alternatePlayerState_t *to)
+{
+ int i, lc;
+ int bits;
+ netField_t *field;
+ int numFields;
+ int startBit, endBit;
+ int print;
+ int *fromF, *toF;
+ int trunc;
+ alternatePlayerState_t dummy;
+
+ if (!from)
+ {
+ from = &dummy;
+ ::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 && (cl_shownet->integer >= 2 || cl_shownet->integer == -2))
+ {
+ print = 1;
+ Com_Printf("%3i: playerstate ", msg->readcount);
+ }
+ else
+ {
+ print = 0;
+ }
+
+ numFields = ARRAY_LEN(alternatePlayerStateFields);
+ lc = MSG_ReadByte(msg);
+
+ if (lc > numFields || lc < 0)
+ {
+ Com_Error(ERR_DROP, "invalid playerState field count");
+ }
+
+ for (i = 0, field = alternatePlayerStateFields; i < lc; i++, field++)
+ {
+ fromF = (int *)((uint8_t *)from + field->offset);
+ toF = (int *)((uint8_t *)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 = &alternatePlayerStateFields[lc]; i < numFields; i++, field++)
+ {
+ fromF = (int *)((uint8_t *)from + field->offset);
+ toF = (int *)((uint8_t *)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 misc data
+ if (MSG_ReadBits(msg, 1))
+ {
+ LOG("PS_MISC");
+ bits = MSG_ReadBits(msg, MAX_MISC);
+ for (i = 0; i < MAX_MISC; i++)
+ {
+ if (bits & (1 << i))
+ {
+ to->misc[i] = MSG_ReadLong(msg);
+ }
+ }
+ }
+ }
+
+ if (print)
+ {
+ if (msg->bit == 0)
+ {
+ endBit = msg->readcount * 8 - GENTITYNUM_BITS;
+ }
+ else
+ {
+ endBit = (msg->readcount - 1) * 8 + msg->bit - GENTITYNUM_BITS;
+ }
+ Com_Printf(" (%i bits)\n", endBit - startBit);
+ }
+}
+
+int msg_hData[256] = {
+ 250315, // 0
+ 41193, // 1
+ 6292, // 2
+ 7106, // 3
+ 3730, // 4
+ 3750, // 5
+ 6110, // 6
+ 23283, // 7
+ 33317, // 8
+ 6950, // 9
+ 7838, // 10
+ 9714, // 11
+ 9257, // 12
+ 17259, // 13
+ 3949, // 14
+ 1778, // 15
+ 8288, // 16
+ 1604, // 17
+ 1590, // 18
+ 1663, // 19
+ 1100, // 20
+ 1213, // 21
+ 1238, // 22
+ 1134, // 23
+ 1749, // 24
+ 1059, // 25
+ 1246, // 26
+ 1149, // 27
+ 1273, // 28
+ 4486, // 29
+ 2805, // 30
+ 3472, // 31
+ 21819, // 32
+ 1159, // 33
+ 1670, // 34
+ 1066, // 35
+ 1043, // 36
+ 1012, // 37
+ 1053, // 38
+ 1070, // 39
+ 1726, // 40
+ 888, // 41
+ 1180, // 42
+ 850, // 43
+ 960, // 44
+ 780, // 45
+ 1752, // 46
+ 3296, // 47
+ 10630, // 48
+ 4514, // 49
+ 5881, // 50
+ 2685, // 51
+ 4650, // 52
+ 3837, // 53
+ 2093, // 54
+ 1867, // 55
+ 2584, // 56
+ 1949, // 57
+ 1972, // 58
+ 940, // 59
+ 1134, // 60
+ 1788, // 61
+ 1670, // 62
+ 1206, // 63
+ 5719, // 64
+ 6128, // 65
+ 7222, // 66
+ 6654, // 67
+ 3710, // 68
+ 3795, // 69
+ 1492, // 70
+ 1524, // 71
+ 2215, // 72
+ 1140, // 73
+ 1355, // 74
+ 971, // 75
+ 2180, // 76
+ 1248, // 77
+ 1328, // 78
+ 1195, // 79
+ 1770, // 80
+ 1078, // 81
+ 1264, // 82
+ 1266, // 83
+ 1168, // 84
+ 965, // 85
+ 1155, // 86
+ 1186, // 87
+ 1347, // 88
+ 1228, // 89
+ 1529, // 90
+ 1600, // 91
+ 2617, // 92
+ 2048, // 93
+ 2546, // 94
+ 3275, // 95
+ 2410, // 96
+ 3585, // 97
+ 2504, // 98
+ 2800, // 99
+ 2675, // 100
+ 6146, // 101
+ 3663, // 102
+ 2840, // 103
+ 14253, // 104
+ 3164, // 105
+ 2221, // 106
+ 1687, // 107
+ 3208, // 108
+ 2739, // 109
+ 3512, // 110
+ 4796, // 111
+ 4091, // 112
+ 3515, // 113
+ 5288, // 114
+ 4016, // 115
+ 7937, // 116
+ 6031, // 117
+ 5360, // 118
+ 3924, // 119
+ 4892, // 120
+ 3743, // 121
+ 4566, // 122
+ 4807, // 123
+ 5852, // 124
+ 6400, // 125
+ 6225, // 126
+ 8291, // 127
+ 23243, // 128
+ 7838, // 129
+ 7073, // 130
+ 8935, // 131
+ 5437, // 132
+ 4483, // 133
+ 3641, // 134
+ 5256, // 135
+ 5312, // 136
+ 5328, // 137
+ 5370, // 138
+ 3492, // 139
+ 2458, // 140
+ 1694, // 141
+ 1821, // 142
+ 2121, // 143
+ 1916, // 144
+ 1149, // 145
+ 1516, // 146
+ 1367, // 147
+ 1236, // 148
+ 1029, // 149
+ 1258, // 150
+ 1104, // 151
+ 1245, // 152
+ 1006, // 153
+ 1149, // 154
+ 1025, // 155
+ 1241, // 156
+ 952, // 157
+ 1287, // 158
+ 997, // 159
+ 1713, // 160
+ 1009, // 161
+ 1187, // 162
+ 879, // 163
+ 1099, // 164
+ 929, // 165
+ 1078, // 166
+ 951, // 167
+ 1656, // 168
+ 930, // 169
+ 1153, // 170
+ 1030, // 171
+ 1262, // 172
+ 1062, // 173
+ 1214, // 174
+ 1060, // 175
+ 1621, // 176
+ 930, // 177
+ 1106, // 178
+ 912, // 179
+ 1034, // 180
+ 892, // 181
+ 1158, // 182
+ 990, // 183
+ 1175, // 184
+ 850, // 185
+ 1121, // 186
+ 903, // 187
+ 1087, // 188
+ 920, // 189
+ 1144, // 190
+ 1056, // 191
+ 3462, // 192
+ 2240, // 193
+ 4397, // 194
+ 12136, // 195
+ 7758, // 196
+ 1345, // 197
+ 1307, // 198
+ 3278, // 199
+ 1950, // 200
+ 886, // 201
+ 1023, // 202
+ 1112, // 203
+ 1077, // 204
+ 1042, // 205
+ 1061, // 206
+ 1071, // 207
+ 1484, // 208
+ 1001, // 209
+ 1096, // 210
+ 915, // 211
+ 1052, // 212
+ 995, // 213
+ 1070, // 214
+ 876, // 215
+ 1111, // 216
+ 851, // 217
+ 1059, // 218
+ 805, // 219
+ 1112, // 220
+ 923, // 221
+ 1103, // 222
+ 817, // 223
+ 1899, // 224
+ 1872, // 225
+ 976, // 226
+ 841, // 227
+ 1127, // 228
+ 956, // 229
+ 1159, // 230
+ 950, // 231
+ 7791, // 232
+ 954, // 233
+ 1289, // 234
+ 933, // 235
+ 1127, // 236
+ 3207, // 237
+ 1020, // 238
+ 927, // 239
+ 1355, // 240
+ 768, // 241
+ 1040, // 242
+ 745, // 243
+ 952, // 244
+ 805, // 245
+ 1073, // 246
+ 740, // 247
+ 1013, // 248
+ 805, // 249
+ 1008, // 250
+ 796, // 251
+ 996, // 252
+ 1057, // 253
+ 11457, // 254
+ 13504, // 255
+};
+
+void MSG_initHuffman(void)
+{
+ int i, j;
+
+ msgInit = true;
+ Huff_Init(&msgHuff);
+ for (i = 0; i < 256; i++)
+ {
+ for (j = 0; j < msg_hData[i]; j++)
+ {
+ Huff_addRef(&msgHuff.compressor, (uint8_t)i); // Do update
+ Huff_addRef(&msgHuff.decompressor, (uint8_t)i); // Do update
+ }
+ }
+}
+
+/*
+void MSG_NUinitHuffman() {
+ uint8_t *data;
+ int size, i, ch;
+ int array[256];
+
+ msgInit = true;
+
+ 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/msg.h b/src/qcommon/msg.h
new file mode 100644
index 0000000..518f755
--- /dev/null
+++ b/src/qcommon/msg.h
@@ -0,0 +1,79 @@
+#ifndef QCOMMON_MSG_H
+#define QCOMMON_MSG_H 1
+
+#include <stdint.h>
+
+//
+// msg.c
+//
+struct msg_t {
+ bool allowoverflow; // if false, do a Com_Error
+ bool overflowed; // set to true if the buffer size failed (with allowoverflow set)
+ bool oob; // set to true if the buffer size failed (with allowoverflow set)
+ uint8_t *data;
+ int maxsize;
+ int cursize;
+ int readcount;
+ int bit; // for bitwise reads and writes
+};
+
+void MSG_Init(struct msg_t *buf, uint8_t *data, int length);
+void MSG_InitOOB(struct msg_t *buf, uint8_t *data, int length);
+void MSG_Clear(struct msg_t *buf);
+void MSG_WriteData(struct msg_t *buf, const void *data, int length);
+void MSG_Bitstream(struct msg_t *buf);
+
+// TTimo
+// copy a struct msg_t in case we need to store it as is for a bit
+// (as I needed this to keep an struct msg_t from a static var for later use)
+// sets data buffer as MSG_Init does prior to do the copy
+void MSG_Copy(struct msg_t *buf, uint8_t *data, int length, struct msg_t *src);
+
+typedef struct usercmd_s usercmd_t;
+typedef struct entityState_s entityState_t;
+typedef struct playerState_s playerState_t;
+
+void MSG_WriteBits(struct msg_t *msg, int value, int bits);
+
+void MSG_WriteChar(struct msg_t *sb, int c);
+void MSG_WriteByte(struct msg_t *sb, int c);
+void MSG_WriteShort(struct msg_t *sb, int c);
+void MSG_WriteLong(struct msg_t *sb, int c);
+void MSG_WriteFloat(struct msg_t *sb, float f);
+void MSG_WriteString(struct msg_t *sb, const char *s);
+void MSG_WriteBigString(struct msg_t *sb, const char *s);
+void MSG_WriteAngle16(struct msg_t *sb, float f);
+int MSG_HashKey(int alternateProtocol, const char *string, int maxlen);
+
+void MSG_BeginReading(struct msg_t *sb);
+void MSG_BeginReadingOOB(struct msg_t *sb);
+
+int MSG_ReadBits(struct msg_t *msg, int bits);
+
+int MSG_ReadChar(struct msg_t *sb);
+int MSG_ReadByte(struct msg_t *sb);
+int MSG_ReadShort(struct msg_t *sb);
+int MSG_ReadLong(struct msg_t *sb);
+float MSG_ReadFloat(struct msg_t *sb);
+char *MSG_ReadString(struct msg_t *sb);
+char *MSG_ReadBigString(struct msg_t *sb);
+char *MSG_ReadStringLine(struct msg_t *sb);
+float MSG_ReadAngle16(struct msg_t *sb);
+void MSG_ReadData(struct msg_t *sb, void *buffer, int size);
+int MSG_LookaheadByte(struct msg_t *msg);
+
+void MSG_WriteDeltaUsercmdKey(struct msg_t *msg, int key, usercmd_t *from, usercmd_t *to);
+void MSG_ReadDeltaUsercmdKey(struct msg_t *msg, int key, usercmd_t *from, usercmd_t *to);
+
+void MSG_WriteDeltaEntity(int alternateProtocol, struct msg_t *msg, struct entityState_s *from, struct entityState_s *to, bool force);
+void MSG_ReadDeltaEntity(int alternateProtocol, struct msg_t *msg, entityState_t *from, entityState_t *to, int number);
+
+void MSG_WriteDeltaPlayerstate(int alternateProtocol, struct msg_t *msg, struct playerState_s *from, struct playerState_s *to);
+void MSG_ReadDeltaPlayerstate(struct msg_t *msg, struct playerState_s *from, struct playerState_s *to);
+
+struct alternatePlayerState_t;
+void MSG_ReadDeltaAlternatePlayerstate(struct msg_t *msg, struct alternatePlayerState_t *from, struct alternatePlayerState_t *to);
+
+void MSG_ReportChangeVectors_f(void);
+
+#endif
diff --git a/src/qcommon/net.h b/src/qcommon/net.h
new file mode 100644
index 0000000..7766d23
--- /dev/null
+++ b/src/qcommon/net.h
@@ -0,0 +1,145 @@
+#ifndef QCOMMON_NET_H
+#define QCOMMON_NET_H 1
+
+#include <stdint.h>
+
+/*
+==============================================================
+
+NET
+
+==============================================================
+*/
+
+#define NET_ENABLEV4 0x01
+#define NET_ENABLEV6 0x02
+// if this flag is set, always attempt ipv6 connections instead of ipv4 if a v6 address is found.
+#define NET_PRIOV6 0x04
+// disables ipv6 multicast support if set.
+#define NET_DISABLEMCAST 0x08
+
+#define NET_ENABLEALT1PROTO 0x01
+#define NET_ENABLEALT2PROTO 0x02
+#define NET_DISABLEPRIMPROTO 0x04
+
+#define PACKET_BACKUP 32 // number of old messages that must be kept on client and
+ // server for delta compression and ping estimation
+#define PACKET_MASK (PACKET_BACKUP - 1)
+
+#define MAX_PACKET_USERCMDS 32 // max number of usercmd_t in a packet
+
+#define MAX_SNAPSHOT_ENTITIES 256
+
+#define PORT_ANY -1
+
+#define MAX_RELIABLE_COMMANDS 128 // max string commands buffered for retransmit
+
+enum netadrtype_t {
+ NA_BAD = 0, // an address lookup failed
+ NA_LOOPBACK,
+ NA_BROADCAST,
+ NA_IP,
+ NA_IP6,
+ NA_MULTICAST6,
+ NA_UNSPEC
+};
+
+typedef enum { NS_CLIENT, NS_SERVER } netsrc_t;
+
+#define NET_ADDRSTRMAXLEN 48 // maximum length of an IPv6 address string including trailing '\0'
+struct netadr_t {
+ enum netadrtype_t type;
+
+ uint8_t ip[4];
+ uint8_t ip6[16];
+
+ unsigned short port;
+ unsigned long scope_id; // Needed for IPv6 link-local addresses
+
+ int alternateProtocol;
+};
+
+void NET_Init(void);
+void NET_Shutdown(void);
+void NET_Restart_f(void);
+void NET_Config(bool enableNetworking);
+void NET_FlushPacketQueue(void);
+void NET_SendPacket(netsrc_t sock, int length, const void *data, struct netadr_t to);
+void NET_OutOfBandPrint(netsrc_t net_socket, struct netadr_t adr, const char *format, ...)
+ __attribute__((format(printf, 3, 4)));
+void NET_OutOfBandData(netsrc_t sock, struct netadr_t adr, uint8_t *format, int len);
+
+bool NET_CompareAdr(struct netadr_t a, struct netadr_t b);
+bool NET_CompareBaseAdrMask(struct netadr_t a, struct netadr_t b, int netmask);
+bool NET_CompareBaseAdr(struct netadr_t a, struct netadr_t b);
+bool NET_IsLocalAddress(struct netadr_t adr);
+const char *NET_AdrToString(struct netadr_t a);
+const char *NET_AdrToStringwPort(struct netadr_t a);
+int NET_StringToAdr(const char *s, struct netadr_t *a, enum netadrtype_t family);
+bool NET_GetLoopPacket(netsrc_t sock, struct netadr_t *net_from, struct msg_t *net_message);
+void NET_JoinMulticast6(void);
+void NET_LeaveMulticast6(void);
+void NET_Sleep(int msec);
+
+#define MAX_MSGLEN 16384 // max length of a message, which may be fragmented into multiple packets
+
+#define MAX_DOWNLOAD_WINDOW 48 // ACK window of 48 download chunks. Cannot set this higher, or clients
+ // will overflow the reliable commands buffer
+#define MAX_DOWNLOAD_BLKSIZE 1024 // 896 uint8_t block chunks
+
+#define NETCHAN_GENCHECKSUM(challenge, sequence) ((challenge) ^ ((sequence) * (challenge)))
+
+/*
+Netchan handles packet fragmentation and out of order / duplicate suppression
+*/
+
+typedef struct {
+ netsrc_t sock;
+
+ int dropped; // between last packet and previous
+
+ int alternateProtocol;
+ struct 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;
+ uint8_t fragmentBuffer[MAX_MSGLEN];
+
+ // outgoing fragment buffer
+ // we need to space out the sending of large fragmented messages
+ bool unsentFragments;
+ int unsentFragmentStart;
+ int unsentLength;
+ uint8_t unsentBuffer[MAX_MSGLEN];
+
+ int challenge;
+ int lastSentTime;
+ int lastSentSize;
+} netchan_t;
+
+void Netchan_Init(int qport);
+void Netchan_Setup(
+ int alternateProtocol, netsrc_t sock, netchan_t *chan, struct netadr_t adr, int qport, int challenge);
+
+void Netchan_Transmit(netchan_t *chan, int length, const uint8_t *data);
+void Netchan_TransmitNextFragment(netchan_t *chan);
+
+bool Netchan_Process(netchan_t *chan, struct msg_t *msg);
+
+void Sys_SendPacket(int length, const void *data, struct netadr_t to);
+bool Sys_StringToAdr(const char *s, struct netadr_t *a, enum netadrtype_t family); // Does NOT parse port numbers, only base addresses.
+bool Sys_IsLANAddress(struct netadr_t adr);
+void Sys_ShowIP(void);
+
+#define SV_ENCODE_START 4
+#define SV_DECODE_START 12
+#define CL_ENCODE_START 12
+#define CL_DECODE_START 4
+
+#endif
diff --git a/src/qcommon/net_chan.cpp b/src/qcommon/net_chan.cpp
new file mode 100644
index 0000000..1dab821
--- /dev/null
+++ b/src/qcommon/net_chan.cpp
@@ -0,0 +1,697 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "net.h"
+
+#include "sys/sys_shared.h"
+
+#include "cvar.h"
+#include "huffman.h"
+#include "msg.h"
+#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 game play.
+
+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 const 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(int alternateProtocol, netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, int challenge)
+{
+ ::memset(chan, 0, sizeof(*chan));
+
+ chan->sock = sock;
+ chan->remoteAddress = adr;
+ chan->qport = qport;
+ chan->incomingSequence = 0;
+ chan->outgoingSequence = 1;
+ chan->challenge = challenge;
+ chan->alternateProtocol = alternateProtocol;
+}
+
+/*
+=================
+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;
+ int outgoingSequence;
+
+ // write the packet header
+ MSG_InitOOB(&send, send_buf, sizeof(send_buf)); // <-- only do the oob here
+
+ outgoingSequence = chan->outgoingSequence | FRAGMENT_BIT;
+ MSG_WriteLong(&send, outgoingSequence);
+
+ // send the qport if we are a client
+ if (chan->sock == NS_CLIENT)
+ {
+ MSG_WriteShort(&send, qport->integer);
+ }
+
+ if (chan->alternateProtocol == 0)
+ MSG_WriteLong(&send, NETCHAN_GENCHECKSUM(chan->challenge, chan->outgoingSequence));
+
+ // 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);
+
+ // Store send time and size of this packet for rate control
+ chan->lastSentTime = Sys_Milliseconds();
+ chan->lastSentSize = send.cursize;
+
+ 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 = false;
+ }
+}
+
+/*
+===============
+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 = true;
+ chan->unsentLength = length;
+ ::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);
+
+ // send the qport if we are a client
+ if (chan->sock == NS_CLIENT) MSG_WriteShort(&send, qport->integer);
+
+ if (chan->alternateProtocol == 0)
+ MSG_WriteLong(&send, NETCHAN_GENCHECKSUM(chan->challenge, chan->outgoingSequence));
+
+ chan->outgoingSequence++;
+
+ MSG_WriteData(&send, data, length);
+
+ // send the datagram
+ NET_SendPacket(chan->sock, send.cursize, send.data, chan->remoteAddress);
+
+ // Store send time and size of this packet for rate control
+ chan->lastSentTime = Sys_Milliseconds();
+ chan->lastSentSize = send.cursize;
+
+ 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 false 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.
+=================
+*/
+bool Netchan_Process(netchan_t *chan, msg_t *msg)
+{
+ int sequence;
+ int fragmentStart, fragmentLength;
+ int checksum;
+ bool 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 = true;
+ }
+ else
+ {
+ fragmented = false;
+ }
+
+ // read the qport if we are a server
+ if (chan->sock == NS_SERVER)
+ {
+ MSG_ReadShort(msg);
+ }
+
+ if (chan->alternateProtocol == 0)
+ {
+ checksum = MSG_ReadLong(msg);
+
+ // UDP spoofing protection
+ if (NETCHAN_GENCHECKSUM(chan->challenge, sequence) != checksum) return false;
+ }
+
+ // 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 false;
+ }
+
+ //
+ // 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 fragment 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 false;
+ }
+
+ // copy the fragment to the fragment buffer
+ if (fragmentLength < 0 || msg->readcount + fragmentLength > msg->cursize
+ || (size_t)(chan->fragmentLength + fragmentLength) > sizeof(chan->fragmentBuffer))
+ {
+ if (showdrop->integer || showpackets->integer)
+ {
+ Com_Printf("%s:illegal fragment length\n", NET_AdrToString(chan->remoteAddress));
+ }
+ return false;
+ }
+
+ ::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 false;
+ }
+
+ if (chan->fragmentLength > msg->maxsize)
+ {
+ Com_Printf(
+ "%s:fragmentLength %i > msg->maxsize\n", NET_AdrToString(chan->remoteAddress), chan->fragmentLength);
+ return false;
+ }
+
+ // copy the full message over the partial fragment
+
+ // make sure the sequence number is still there
+ *(int *)msg->data = LittleLong(sequence);
+
+ ::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 true;
+ }
+
+ //
+ // the message can now be read from the current message pointer
+ //
+ chan->incomingSequence = sequence;
+
+ return true;
+}
+
+//==============================================================================
+
+/*
+=============================================================================
+
+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];
+
+bool 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 false;
+
+ i = loop->get & (MAX_LOOPBACK - 1);
+ loop->get++;
+
+ ::memcpy(net_message->data, loop->msgs[i].data, loop->msgs[i].datalen);
+ net_message->cursize = loop->msgs[i].datalen;
+ ::memset(net_from, 0, sizeof(*net_from));
+ net_from->type = NA_LOOPBACK;
+ return true;
+}
+
+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++;
+
+ ::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 = (packetQueue_t *)S_Malloc(sizeof(packetQueue_t));
+ _new->data = (byte *)S_Malloc(length);
+ ::memcpy(_new->data, data, length);
+ _new->length = length;
+ _new->to = to;
+ _new->release = Sys_Milliseconds() + (int)((float)offset / com_timescale->value);
+ _new->next = NULL;
+
+ if (!packetQueue)
+ {
+ packetQueue = _new;
+ return;
+ }
+ while (next)
+ {
+ if (!next->next)
+ {
+ next->next = _new;
+ return;
+ }
+ next = next->next;
+ }
+}
+
+void NET_FlushPacketQueue(void)
+{
+ packetQueue_t *last;
+ int now;
+
+ while (packetQueue)
+ {
+ now = Sys_Milliseconds();
+ if (packetQueue->release >= now) break;
+ Sys_SendPacket(packetQueue->length, packetQueue->data, packetQueue->to);
+ last = packetQueue;
+ packetQueue = packetQueue->next;
+ Z_Free(last->data);
+ Z_Free(last);
+ }
+}
+
+void NET_SendPacket(netsrc_t sock, int length, const void *data, netadr_t to)
+{
+ // sequenced packets are shown in netchan, so just show oob
+ if (showpackets->integer && *(int *)data == -1)
+ {
+ Com_Printf("send packet %4i\n", length);
+ }
+
+ if (to.type == NA_LOOPBACK)
+ {
+ NET_SendLoopPacket(sock, length, data, to);
+ return;
+ }
+ if (to.type == NA_BAD)
+ {
+ return;
+ }
+
+ if (sock == NS_CLIENT && cl_packetdelay->integer > 0)
+ {
+ NET_QueuePacket(length, data, to, cl_packetdelay->integer);
+ }
+ else if (sock == NS_SERVER && sv_packetdelay->integer > 0)
+ {
+ NET_QueuePacket(length, data, to, sv_packetdelay->integer);
+ }
+ else
+ {
+ Sys_SendPacket(length, data, to);
+ }
+}
+
+/*
+===============
+NET_OutOfBandPrint
+
+Sends a text message in an out-of-band datagram
+================
+*/
+void QDECL NET_OutOfBandPrint(netsrc_t sock, netadr_t adr, const char *format, ...)
+{
+ va_list argptr;
+ char string[MAX_MSGLEN];
+
+ // set the header
+ string[0] = -1;
+ string[1] = -1;
+ string[2] = -1;
+ string[3] = -1;
+
+ va_start(argptr, format);
+ Q_vsnprintf(string + 4, sizeof(string) - 4, format, argptr);
+ va_end(argptr);
+
+ // send the datagram
+ NET_SendPacket(sock, strlen(string), string, adr);
+}
+
+/*
+===============
+NET_OutOfBandPrint
+
+Sends a data message in an out-of-band datagram (only used for "connect")
+================
+*/
+void QDECL NET_OutOfBandData(netsrc_t sock, netadr_t adr, byte *format, int len)
+{
+ byte string[MAX_MSGLEN * 2];
+ int i;
+ msg_t mbuf;
+
+ // set the header
+ string[0] = 0xff;
+ string[1] = 0xff;
+ string[2] = 0xff;
+ string[3] = 0xff;
+
+ for (i = 0; i < len; i++)
+ {
+ string[i + 4] = format[i];
+ }
+
+ mbuf.data = string;
+ mbuf.cursize = len + 4;
+ Huff_Compress(&mbuf, 12);
+ // send the datagram
+ NET_SendPacket(sock, mbuf.cursize, mbuf.data, adr);
+}
+
+/*
+=============
+NET_StringToAdr
+
+Traps "localhost" for loopback, passes everything else to system
+return 0 on address not found, 1 on address found with port, 2 on address found without port.
+=============
+*/
+int NET_StringToAdr(const char *s, netadr_t *a, netadrtype_t family)
+{
+ char base[MAX_STRING_CHARS], *search;
+ char *port = NULL;
+
+ if (!strcmp(s, "localhost"))
+ {
+ ::memset(a, 0, sizeof(*a));
+ a->type = NA_LOOPBACK;
+ // as NA_LOOPBACK doesn't require ports report port was given.
+ return 1;
+ }
+
+ Q_strncpyz(base, s, sizeof(base));
+
+ if (*base == '[' || Q_CountChar(base, ':') > 1)
+ {
+ // This is an ipv6 address, handle it specially.
+ search = strchr(base, ']');
+ if (search)
+ {
+ *search = '\0';
+ search++;
+
+ if (*search == ':') port = search + 1;
+ }
+
+ if (*base == '[')
+ search = base + 1;
+ else
+ search = base;
+ }
+ else
+ {
+ // look for a port number
+ port = strchr(base, ':');
+
+ if (port)
+ {
+ *port = '\0';
+ port++;
+ }
+
+ search = base;
+ }
+
+ a->alternateProtocol = 0;
+
+ if (!Sys_StringToAdr(search, a, family))
+ {
+ a->type = NA_BAD;
+ return 0;
+ }
+
+ if (port)
+ {
+ a->port = BigShort((short)atoi(port));
+ return 1;
+ }
+ else
+ {
+ a->port = BigShort(PORT_SERVER);
+ return 2;
+ }
+}
diff --git a/src/qcommon/net_ip.cpp b/src/qcommon/net_ip.cpp
new file mode 100644
index 0000000..910301d
--- /dev/null
+++ b/src/qcommon/net_ip.cpp
@@ -0,0 +1,1842 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "net.h"
+
+#include "cmd.h"
+#include "cvar.h"
+#include "msg.h"
+#include "q_shared.h"
+#include "qcommon.h"
+
+#ifdef _WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#if WINVER < 0x501
+#ifdef __MINGW32__
+// wspiapi.h isn't available on MinGW, so if it's
+// present it's because the end user has added it
+// and we should look for it in our tree
+#include "wspiapi.h"
+#else
+#include <wspiapi.h>
+#endif
+#else
+#include <ws2spi.h>
+#endif
+
+typedef int socklen_t;
+#ifdef ADDRESS_FAMILY
+#define sa_family_t ADDRESS_FAMILY
+#else
+typedef unsigned short sa_family_t;
+#endif
+
+#define EAGAIN WSAEWOULDBLOCK
+#define EADDRNOTAVAIL WSAEADDRNOTAVAIL
+#define EAFNOSUPPORT WSAEAFNOSUPPORT
+#define ECONNRESET WSAECONNRESET
+typedef u_long ioctlarg_t;
+#define socketError WSAGetLastError()
+
+static WSADATA winsockdata;
+static bool winsockInitialized = false;
+
+#else
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED == 1020
+// needed for socklen_t on OSX 10.2
+#define _BSD_SOCKLEN_T_
+#endif
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#if !defined(__sun) && !defined(__sgi)
+#include <ifaddrs.h>
+#endif
+
+#ifdef __sun
+#include <sys/filio.h>
+#endif
+
+typedef int SOCKET;
+#define INVALID_SOCKET -1
+#define SOCKET_ERROR -1
+#define closesocket close
+#define ioctlsocket ioctl
+typedef int ioctlarg_t;
+#define socketError errno
+
+#endif
+
+static bool usingSocks = false;
+static int networkingEnabled = 0;
+
+static cvar_t *net_enabled;
+static cvar_t *net_alternateProtocols;
+
+static cvar_t *net_socksEnabled;
+static cvar_t *net_socksServer;
+static cvar_t *net_socksPort;
+static cvar_t *net_socksUsername;
+static cvar_t *net_socksPassword;
+
+static cvar_t *net_ip;
+static cvar_t *net_ip6;
+static cvar_t *net_ports[3];
+static cvar_t *net_port6s[3];
+static cvar_t *net_mcast6addr;
+static cvar_t *net_mcast6iface;
+
+static cvar_t *net_dropsim;
+
+static struct sockaddr socksRelayAddr;
+
+static SOCKET ip_sockets[3] = {INVALID_SOCKET, INVALID_SOCKET, INVALID_SOCKET};
+static SOCKET ip6_sockets[3] = {INVALID_SOCKET, INVALID_SOCKET, INVALID_SOCKET};
+/*
+TODO: accommodate
+static SOCKET socks_socket = INVALID_SOCKET;
+static SOCKET multicast6_socket = INVALID_SOCKET;
+*/
+
+// Keep track of currently joined multicast group.
+static struct ipv6_mreq curgroup;
+// And the currently bound address.
+static struct sockaddr_in6 boundto;
+
+#ifndef IF_NAMESIZE
+#define IF_NAMESIZE 16
+#endif
+
+// use an admin local address per default so that network admins can decide on how to handle quake3 traffic.
+#define NET_MULTICAST_IP6 "ff04::696f:7175:616b:6533"
+
+#define MAX_IPS 32
+
+typedef struct {
+ char ifname[IF_NAMESIZE];
+
+ netadrtype_t type;
+ sa_family_t family;
+ struct sockaddr_storage addr;
+ struct sockaddr_storage netmask;
+} nip_localaddr_t;
+
+static nip_localaddr_t localIP[MAX_IPS];
+static int numIP;
+
+//=============================================================================
+
+/*
+====================
+NET_ErrorString
+====================
+*/
+const char *NET_ErrorString(void)
+{
+#ifdef _WIN32
+ // FIXME: replace with FormatMessage?
+ switch (socketError)
+ {
+ case WSAEINTR:
+ return "WSAEINTR";
+ case WSAEBADF:
+ return "WSAEBADF";
+ case WSAEACCES:
+ return "WSAEACCES";
+ case WSAEDISCON:
+ return "WSAEDISCON";
+ case WSAEFAULT:
+ return "WSAEFAULT";
+ case WSAEINVAL:
+ return "WSAEINVAL";
+ case WSAEMFILE:
+ return "WSAEMFILE";
+ case WSAEWOULDBLOCK:
+ return "WSAEWOULDBLOCK";
+ case WSAEINPROGRESS:
+ return "WSAEINPROGRESS";
+ case WSAEALREADY:
+ return "WSAEALREADY";
+ case WSAENOTSOCK:
+ return "WSAENOTSOCK";
+ case WSAEDESTADDRREQ:
+ return "WSAEDESTADDRREQ";
+ case WSAEMSGSIZE:
+ return "WSAEMSGSIZE";
+ case WSAEPROTOTYPE:
+ return "WSAEPROTOTYPE";
+ case WSAENOPROTOOPT:
+ return "WSAENOPROTOOPT";
+ case WSAEPROTONOSUPPORT:
+ return "WSAEPROTONOSUPPORT";
+ case WSAESOCKTNOSUPPORT:
+ return "WSAESOCKTNOSUPPORT";
+ case WSAEOPNOTSUPP:
+ return "WSAEOPNOTSUPP";
+ case WSAEPFNOSUPPORT:
+ return "WSAEPFNOSUPPORT";
+ case WSAEAFNOSUPPORT:
+ return "WSAEAFNOSUPPORT";
+ case WSAEADDRINUSE:
+ return "WSAEADDRINUSE";
+ case WSAEADDRNOTAVAIL:
+ return "WSAEADDRNOTAVAIL";
+ case WSAENETDOWN:
+ return "WSAENETDOWN";
+ case WSAENETUNREACH:
+ return "WSAENETUNREACH";
+ case WSAENETRESET:
+ return "WSAENETRESET";
+ case WSAECONNABORTED:
+ return "WSWSAECONNABORTEDAEINTR";
+ case WSAECONNRESET:
+ return "WSAECONNRESET";
+ case WSAENOBUFS:
+ return "WSAENOBUFS";
+ case WSAEISCONN:
+ return "WSAEISCONN";
+ case WSAENOTCONN:
+ return "WSAENOTCONN";
+ case WSAESHUTDOWN:
+ return "WSAESHUTDOWN";
+ case WSAETOOMANYREFS:
+ return "WSAETOOMANYREFS";
+ case WSAETIMEDOUT:
+ return "WSAETIMEDOUT";
+ case WSAECONNREFUSED:
+ return "WSAECONNREFUSED";
+ case WSAELOOP:
+ return "WSAELOOP";
+ case WSAENAMETOOLONG:
+ return "WSAENAMETOOLONG";
+ case WSAEHOSTDOWN:
+ return "WSAEHOSTDOWN";
+ case WSASYSNOTREADY:
+ return "WSASYSNOTREADY";
+ case WSAVERNOTSUPPORTED:
+ return "WSAVERNOTSUPPORTED";
+ case WSANOTINITIALISED:
+ return "WSANOTINITIALISED";
+ case WSAHOST_NOT_FOUND:
+ return "WSAHOST_NOT_FOUND";
+ case WSATRY_AGAIN:
+ return "WSATRY_AGAIN";
+ case WSANO_RECOVERY:
+ return "WSANO_RECOVERY";
+ case WSANO_DATA:
+ return "WSANO_DATA";
+ default:
+ return "NO ERROR";
+ }
+#else
+ return strerror(socketError);
+#endif
+}
+
+static void NetadrToSockadr(netadr_t *a, struct sockaddr *s)
+{
+ if (a->type == NA_BROADCAST)
+ {
+ ((struct sockaddr_in *)s)->sin_family = AF_INET;
+ ((struct sockaddr_in *)s)->sin_port = a->port;
+#ifdef __FreeBSD__
+ ((struct sockaddr_in *)s)->sin_addr.s_addr = INADDR_ANY;
+#else
+ ((struct sockaddr_in *)s)->sin_addr.s_addr = INADDR_BROADCAST;
+#endif
+ }
+ else if (a->type == NA_IP)
+ {
+ ((struct sockaddr_in *)s)->sin_family = AF_INET;
+ ((struct sockaddr_in *)s)->sin_addr.s_addr = *(int *)&a->ip;
+ ((struct sockaddr_in *)s)->sin_port = a->port;
+ }
+ else if (a->type == NA_IP6)
+ {
+ ((struct sockaddr_in6 *)s)->sin6_family = AF_INET6;
+ ((struct sockaddr_in6 *)s)->sin6_addr = *((struct in6_addr *)&a->ip6);
+ ((struct sockaddr_in6 *)s)->sin6_port = a->port;
+ ((struct sockaddr_in6 *)s)->sin6_scope_id = a->scope_id;
+ }
+ else if (a->type == NA_MULTICAST6)
+ {
+ ((struct sockaddr_in6 *)s)->sin6_family = AF_INET6;
+ ((struct sockaddr_in6 *)s)->sin6_addr = curgroup.ipv6mr_multiaddr;
+ ((struct sockaddr_in6 *)s)->sin6_port = a->port;
+ }
+}
+
+static void SockadrToNetadr(struct sockaddr *s, netadr_t *a)
+{
+ if (s->sa_family == AF_INET)
+ {
+ a->type = NA_IP;
+ *(int *)&a->ip = ((struct sockaddr_in *)s)->sin_addr.s_addr;
+ a->port = ((struct sockaddr_in *)s)->sin_port;
+ }
+ else if (s->sa_family == AF_INET6)
+ {
+ a->type = NA_IP6;
+ memcpy(a->ip6, &((struct sockaddr_in6 *)s)->sin6_addr, sizeof(a->ip6));
+ a->port = ((struct sockaddr_in6 *)s)->sin6_port;
+ a->scope_id = ((struct sockaddr_in6 *)s)->sin6_scope_id;
+ }
+ a->alternateProtocol = 0;
+}
+
+static struct addrinfo *SearchAddrInfo(struct addrinfo *hints, sa_family_t family)
+{
+ while (hints)
+ {
+ if (hints->ai_family == family) return hints;
+
+ hints = hints->ai_next;
+ }
+
+ return NULL;
+}
+
+/*
+=============
+Sys_StringToSockaddr
+=============
+*/
+static bool Sys_StringToSockaddr(const char *s, struct sockaddr *sadr, size_t sadr_len, sa_family_t family)
+{
+ struct addrinfo hints;
+ struct addrinfo *res = NULL;
+ struct addrinfo *search = NULL;
+ struct addrinfo *hintsp;
+ int retval;
+
+ memset(sadr, '\0', sizeof(*sadr));
+ memset(&hints, '\0', sizeof(hints));
+
+ hintsp = &hints;
+ hintsp->ai_family = family;
+ hintsp->ai_socktype = SOCK_DGRAM;
+
+ retval = getaddrinfo(s, NULL, hintsp, &res);
+
+ if (!retval)
+ {
+ if (family == AF_UNSPEC)
+ {
+ // Decide here and now which protocol family to use
+ if (net_enabled->integer & NET_PRIOV6)
+ {
+ if (net_enabled->integer & NET_ENABLEV6) search = SearchAddrInfo(res, AF_INET6);
+
+ if (!search && (net_enabled->integer & NET_ENABLEV4)) search = SearchAddrInfo(res, AF_INET);
+ }
+ else
+ {
+ if (net_enabled->integer & NET_ENABLEV4) search = SearchAddrInfo(res, AF_INET);
+
+ if (!search && (net_enabled->integer & NET_ENABLEV6)) search = SearchAddrInfo(res, AF_INET6);
+ }
+ }
+ else
+ search = SearchAddrInfo(res, family);
+
+ if (search)
+ {
+ if (search->ai_addrlen > sadr_len) search->ai_addrlen = sadr_len;
+
+ memcpy(sadr, search->ai_addr, search->ai_addrlen);
+ freeaddrinfo(res);
+
+ return true;
+ }
+ else
+ Com_Printf("Sys_StringToSockaddr: Error resolving %s: No address of required type found.\n", s);
+ }
+ else
+ Com_Printf("Sys_StringToSockaddr: Error resolving %s: %s\n", s, gai_strerror(retval));
+
+ if (res) freeaddrinfo(res);
+
+ return false;
+}
+
+/*
+=============
+Sys_SockaddrToString
+=============
+*/
+static void Sys_SockaddrToString(char *dest, int destlen, struct sockaddr *input)
+{
+ socklen_t inputlen;
+
+ if (input->sa_family == AF_INET6)
+ inputlen = sizeof(struct sockaddr_in6);
+ else
+ inputlen = sizeof(struct sockaddr_in);
+
+ if (getnameinfo(input, inputlen, dest, destlen, NULL, 0, NI_NUMERICHOST) && destlen > 0) *dest = '\0';
+}
+
+/*
+=============
+Sys_StringToAdr
+=============
+*/
+bool Sys_StringToAdr(const char *s, netadr_t *a, netadrtype_t family)
+{
+ struct sockaddr_storage sadr;
+ sa_family_t fam;
+
+ switch (family)
+ {
+ case NA_IP:
+ fam = AF_INET;
+ break;
+ case NA_IP6:
+ fam = AF_INET6;
+ break;
+ default:
+ fam = AF_UNSPEC;
+ break;
+ }
+ if (!Sys_StringToSockaddr(s, (struct sockaddr *)&sadr, sizeof(sadr), fam))
+ {
+ return false;
+ }
+
+ SockadrToNetadr((struct sockaddr *)&sadr, a);
+ return true;
+}
+
+/*
+===================
+NET_CompareBaseAdrMask
+
+Compare without port, and up to the bit number given in netmask.
+===================
+*/
+bool NET_CompareBaseAdrMask(netadr_t a, netadr_t b, int netmask)
+{
+ uint8_t cmpmask, *addra, *addrb;
+ int curbyte;
+
+ if (a.alternateProtocol != b.alternateProtocol) return false;
+
+ if (a.type != b.type) return false;
+
+ if (a.type == NA_LOOPBACK) return true;
+
+ if (a.type == NA_IP)
+ {
+ addra = (uint8_t *)&a.ip;
+ addrb = (uint8_t *)&b.ip;
+
+ if (netmask < 0 || netmask > 32) netmask = 32;
+ }
+ else if (a.type == NA_IP6)
+ {
+ addra = (uint8_t *)&a.ip6;
+ addrb = (uint8_t *)&b.ip6;
+
+ if (netmask < 0 || netmask > 128) netmask = 128;
+ }
+ else
+ {
+ Com_Printf("NET_CompareBaseAdr: bad address type\n");
+ return false;
+ }
+
+ curbyte = netmask >> 3;
+
+ if (curbyte && memcmp(addra, addrb, curbyte)) return false;
+
+ netmask &= 0x07;
+ if (netmask)
+ {
+ cmpmask = (1 << netmask) - 1;
+ cmpmask <<= 8 - netmask;
+
+ if ((addra[curbyte] & cmpmask) == (addrb[curbyte] & cmpmask)) return true;
+ }
+ else
+ return true;
+
+ return false;
+}
+
+/*
+===================
+NET_CompareBaseAdr
+
+Compares without the port
+===================
+*/
+bool NET_CompareBaseAdr(netadr_t a, netadr_t b) { return NET_CompareBaseAdrMask(a, b, -1); }
+const char *NET_AdrToString(netadr_t a)
+{
+ static char s[NET_ADDRSTRMAXLEN];
+
+ if (a.type == NA_LOOPBACK)
+ Com_sprintf(s, sizeof(s), "loopback");
+ else if (a.type == NA_IP || a.type == NA_IP6)
+ {
+ struct sockaddr_storage sadr;
+
+ memset(&sadr, 0, sizeof(sadr));
+ NetadrToSockadr(&a, (struct sockaddr *)&sadr);
+ Sys_SockaddrToString(s, sizeof(s), (struct sockaddr *)&sadr);
+ }
+
+ return s;
+}
+
+const char *NET_AdrToStringwPort(netadr_t a)
+{
+ static char s[NET_ADDRSTRMAXLEN];
+
+ if (a.type == NA_LOOPBACK)
+ Com_sprintf(s, sizeof(s), "loopback");
+ else if (a.type == NA_IP)
+ Com_sprintf(s, sizeof(s), "%s:%hu", NET_AdrToString(a), ntohs(a.port));
+ else if (a.type == NA_IP6)
+ Com_sprintf(s, sizeof(s), "[%s]:%hu", NET_AdrToString(a), ntohs(a.port));
+
+ return s;
+}
+
+bool NET_CompareAdr(netadr_t a, netadr_t b)
+{
+ if (!NET_CompareBaseAdr(a, b)) return false;
+
+ if (a.type == NA_IP || a.type == NA_IP6)
+ {
+ if (a.port == b.port) return true;
+ }
+ else
+ return true;
+
+ return false;
+}
+
+bool NET_IsLocalAddress(netadr_t adr) { return (bool)(adr.type == NA_LOOPBACK); }
+//=============================================================================
+
+/*
+==================
+NET_GetPacket
+
+Receive one packet
+==================
+*/
+bool NET_GetPacket(netadr_t *net_from, msg_t *net_message, fd_set *fdr)
+{
+ int a;
+ int ret;
+ struct sockaddr_storage from;
+ socklen_t fromlen;
+ int err;
+
+ for (a = 0; a < 3; ++a)
+ {
+ // indent
+ if (ip_sockets[a] != INVALID_SOCKET && FD_ISSET(ip_sockets[a], fdr))
+ {
+ fromlen = sizeof(from);
+ ret = recvfrom(
+ ip_sockets[a], (char *)net_message->data, net_message->maxsize, 0, (struct sockaddr *)&from, &fromlen);
+
+ if (ret == SOCKET_ERROR)
+ {
+ err = socketError;
+
+ if (err != EAGAIN && err != ECONNRESET) Com_Printf("NET_GetPacket: %s\n", NET_ErrorString());
+ }
+ else
+ {
+ memset(((struct sockaddr_in *)&from)->sin_zero, 0, 8);
+
+ if (usingSocks && memcmp(&from, &socksRelayAddr, fromlen) == 0)
+ {
+ if (ret < 10 || net_message->data[0] != 0 || net_message->data[1] != 0 ||
+ net_message->data[2] != 0 || net_message->data[3] != 1)
+ {
+ return false;
+ }
+ net_from->type = NA_IP;
+ net_from->ip[0] = net_message->data[4];
+ net_from->ip[1] = net_message->data[5];
+ net_from->ip[2] = net_message->data[6];
+ net_from->ip[3] = net_message->data[7];
+ net_from->port = *(short *)&net_message->data[8];
+ net_message->readcount = 10;
+ }
+ else
+ {
+ SockadrToNetadr((struct sockaddr *)&from, net_from);
+ net_message->readcount = 0;
+ }
+
+ net_from->alternateProtocol = a;
+
+ if (ret >= net_message->maxsize)
+ {
+ Com_Printf("Oversize packet from %s\n", NET_AdrToString(*net_from));
+ return false;
+ }
+
+ net_message->cursize = ret;
+ return true;
+ }
+ }
+
+ if (ip6_sockets[a] != INVALID_SOCKET && FD_ISSET(ip6_sockets[a], fdr))
+ {
+ fromlen = sizeof(from);
+ ret = recvfrom(
+ ip6_sockets[a], (char *)net_message->data, net_message->maxsize, 0, (struct sockaddr *)&from, &fromlen);
+
+ if (ret == SOCKET_ERROR)
+ {
+ err = socketError;
+
+ if (err != EAGAIN && err != ECONNRESET) Com_Printf("NET_GetPacket: %s\n", NET_ErrorString());
+ }
+ else
+ {
+ SockadrToNetadr((struct sockaddr *)&from, net_from);
+ net_message->readcount = 0;
+
+ net_from->alternateProtocol = a;
+
+ if (ret >= net_message->maxsize)
+ {
+ Com_Printf("Oversize packet from %s\n", NET_AdrToString(*net_from));
+ return false;
+ }
+
+ net_message->cursize = ret;
+ return true;
+ }
+ }
+
+ /*
+ TODO: accommodate
+ if(multicast6_socket != INVALID_SOCKET && multicast6_socket != ip6_socket && FD_ISSET(multicast6_socket, fdr))
+ {
+ fromlen = sizeof(from);
+ ret = recvfrom(multicast6_socket, (char*)net_message->data, net_message->maxsize, 0, (struct sockaddr *)
+ &from, &fromlen);
+
+ if (ret == SOCKET_ERROR)
+ {
+ err = socketError;
+
+ if( err != EAGAIN && err != ECONNRESET )
+ Com_Printf( "NET_GetPacket: %s\n", NET_ErrorString() );
+ }
+ else
+ {
+ SockadrToNetadr((struct sockaddr *) &from, net_from);
+ net_message->readcount = 0;
+
+ if(ret >= net_message->maxsize)
+ {
+ Com_Printf( "Oversize packet from %s\n", NET_AdrToString (*net_from) );
+ return false;
+ }
+
+ net_message->cursize = ret;
+ return true;
+ }
+ }
+ */
+ // outdent
+ }
+
+ return false;
+}
+
+//=============================================================================
+
+static char socksBuf[4096];
+
+/*
+==================
+Sys_SendPacket
+==================
+*/
+void Sys_SendPacket(int length, const void *data, netadr_t to)
+{
+ int ret = SOCKET_ERROR;
+ struct sockaddr_storage addr;
+
+ if (to.type != NA_BROADCAST && to.type != NA_IP && to.type != NA_IP6 && to.type != NA_MULTICAST6)
+ {
+ Com_Error(ERR_FATAL, "Sys_SendPacket: bad address type");
+ return;
+ }
+
+ if ((ip_sockets[to.alternateProtocol] == INVALID_SOCKET && to.type == NA_IP) ||
+ (ip_sockets[to.alternateProtocol] == INVALID_SOCKET && to.type == NA_BROADCAST) ||
+ (ip6_sockets[to.alternateProtocol] == INVALID_SOCKET && to.type == NA_IP6) ||
+ (/* TODO: accommodate ip6_socket == INVALID_SOCKET && */ to.type == NA_MULTICAST6))
+ return;
+
+ if (to.type == NA_MULTICAST6 && (net_enabled->integer & NET_DISABLEMCAST)) return;
+
+ memset(&addr, 0, sizeof(addr));
+ NetadrToSockadr(&to, (struct sockaddr *)&addr);
+
+ if (usingSocks && to.type == NA_IP)
+ {
+ socksBuf[0] = 0; // reserved
+ socksBuf[1] = 0;
+ socksBuf[2] = 0; // fragment (not fragmented)
+ socksBuf[3] = 1; // address type: IPV4
+ *(int *)&socksBuf[4] = ((struct sockaddr_in *)&addr)->sin_addr.s_addr;
+ *(short *)&socksBuf[8] = ((struct sockaddr_in *)&addr)->sin_port;
+ memcpy(&socksBuf[10], data, length);
+ ret = sendto(ip_sockets[to.alternateProtocol], (const char *)socksBuf, length + 10, 0, &socksRelayAddr,
+ sizeof(socksRelayAddr));
+ }
+ else
+ {
+ if (addr.ss_family == AF_INET)
+ ret = sendto(ip_sockets[to.alternateProtocol], (const char *)data, length, 0, (struct sockaddr *)&addr,
+ sizeof(struct sockaddr_in));
+ else if (addr.ss_family == AF_INET6)
+ ret = sendto(ip6_sockets[to.alternateProtocol], (const char *)data, length, 0, (struct sockaddr *)&addr,
+ sizeof(struct sockaddr_in6));
+ }
+ if (ret == SOCKET_ERROR)
+ {
+ int err = socketError;
+
+ // wouldblock is silent
+ if (err == EAGAIN)
+ {
+ return;
+ }
+
+ // some PPP links do not allow broadcasts and return an error
+ if ((err == EADDRNOTAVAIL) && ((to.type == NA_BROADCAST)))
+ {
+ return;
+ }
+
+ Com_Printf("Sys_SendPacket: %s\n", NET_ErrorString());
+ }
+}
+
+//=============================================================================
+
+/*
+==================
+Sys_IsLANAddress
+
+LAN clients will have their rate var ignored
+==================
+*/
+bool Sys_IsLANAddress(netadr_t adr)
+{
+ int index, run, addrsize;
+ bool differed;
+ uint8_t *compareadr, *comparemask, *compareip;
+
+ if (adr.type == NA_LOOPBACK)
+ {
+ return true;
+ }
+
+ if (adr.type == NA_IP)
+ {
+ // RFC1918:
+ // 10.0.0.0 - 10.255.255.255 (10/8 prefix)
+ // 172.16.0.0 - 172.31.255.255 (172.16/12 prefix)
+ // 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)
+ if (adr.ip[0] == 10) return true;
+ if (adr.ip[0] == 172 && (adr.ip[1] & 0xf0) == 16) return true;
+ if (adr.ip[0] == 192 && adr.ip[1] == 168) return true;
+
+ if (adr.ip[0] == 127) return true;
+ }
+ else if (adr.type == NA_IP6)
+ {
+ if (adr.ip6[0] == 0xfe && (adr.ip6[1] & 0xc0) == 0x80) return true;
+ if ((adr.ip6[0] & 0xfe) == 0xfc) return true;
+ }
+
+ // Now compare against the networks this computer is member of.
+ for (index = 0; index < numIP; index++)
+ {
+ if (localIP[index].type == adr.type)
+ {
+ if (adr.type == NA_IP)
+ {
+ compareip = (uint8_t *)&((struct sockaddr_in *)&localIP[index].addr)->sin_addr.s_addr;
+ comparemask = (uint8_t *)&((struct sockaddr_in *)&localIP[index].netmask)->sin_addr.s_addr;
+ compareadr = adr.ip;
+
+ addrsize = sizeof(adr.ip);
+ }
+ else
+ {
+ // TODO? should we check the scope_id here?
+
+ compareip = (uint8_t *)&((struct sockaddr_in6 *)&localIP[index].addr)->sin6_addr;
+ comparemask = (uint8_t *)&((struct sockaddr_in6 *)&localIP[index].netmask)->sin6_addr;
+ compareadr = adr.ip6;
+
+ addrsize = sizeof(adr.ip6);
+ }
+
+ differed = false;
+ for (run = 0; run < addrsize; run++)
+ {
+ if ((compareip[run] & comparemask[run]) != (compareadr[run] & comparemask[run]))
+ {
+ differed = true;
+ break;
+ }
+ }
+
+ if (!differed) return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+==================
+Sys_ShowIP
+==================
+*/
+void Sys_ShowIP(void)
+{
+ int i;
+ char addrbuf[NET_ADDRSTRMAXLEN];
+
+ for (i = 0; i < numIP; i++)
+ {
+ Sys_SockaddrToString(addrbuf, sizeof(addrbuf), (struct sockaddr *)&localIP[i].addr);
+
+ if (localIP[i].type == NA_IP)
+ Com_Printf("IP: %s\n", addrbuf);
+ else if (localIP[i].type == NA_IP6)
+ Com_Printf("IP6: %s\n", addrbuf);
+ }
+}
+
+//=============================================================================
+
+/*
+====================
+NET_IPSocket
+====================
+*/
+SOCKET NET_IPSocket(int alternateProtocol, char *net_interface, int port, int *err)
+{
+ SOCKET newsocket;
+ struct sockaddr_in address;
+ ioctlarg_t _true = 1;
+ int i = 1;
+
+ *err = 0;
+
+ if (net_interface)
+ {
+ Com_Printf("Opening%s IP socket: %s:%i\n",
+ (alternateProtocol == 2 ? " alternate-2" : alternateProtocol == 1 ? " alternate-1" : ""), net_interface,
+ port);
+ }
+ else
+ {
+ Com_Printf("Opening%s IP socket: 0.0.0.0:%i\n",
+ (alternateProtocol == 2 ? " alternate-2" : alternateProtocol == 1 ? " alternate-1" : ""), port);
+ }
+
+ if ((newsocket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET)
+ {
+ *err = socketError;
+ Com_Printf("WARNING: NET_IPSocket: socket: %s\n", NET_ErrorString());
+ return newsocket;
+ }
+ // make it non-blocking
+ if (ioctlsocket(newsocket, FIONBIO, &_true) == SOCKET_ERROR)
+ {
+ Com_Printf("WARNING: NET_IPSocket: ioctl FIONBIO: %s\n", NET_ErrorString());
+ *err = socketError;
+ closesocket(newsocket);
+ return INVALID_SOCKET;
+ }
+
+ // make it broadcast capable
+ if (setsockopt(newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i)) == SOCKET_ERROR)
+ {
+ Com_Printf("WARNING: NET_IPSocket: setsockopt SO_BROADCAST: %s\n", NET_ErrorString());
+ }
+
+ if (!net_interface || !net_interface[0])
+ {
+ address.sin_family = AF_INET;
+ address.sin_addr.s_addr = INADDR_ANY;
+ }
+ else
+ {
+ if (!Sys_StringToSockaddr(net_interface, (struct sockaddr *)&address, sizeof(address), AF_INET))
+ {
+ closesocket(newsocket);
+ return INVALID_SOCKET;
+ }
+ }
+
+ if (port == PORT_ANY)
+ {
+ address.sin_port = 0;
+ }
+ else
+ {
+ address.sin_port = htons((short)port);
+ }
+
+ if (bind(newsocket, (const sockaddr *)&address, sizeof(address)) == SOCKET_ERROR)
+ {
+ Com_Printf("WARNING: NET_IPSocket: bind: %s\n", NET_ErrorString());
+ *err = socketError;
+ closesocket(newsocket);
+ return INVALID_SOCKET;
+ }
+
+ return newsocket;
+}
+
+/*
+====================
+NET_IP6Socket
+====================
+*/
+SOCKET NET_IP6Socket(int alternateProtocol, char *net_interface, int port, struct sockaddr_in6 *bindto, int *err)
+{
+ SOCKET newsocket;
+ struct sockaddr_in6 address;
+ ioctlarg_t _true = 1;
+
+ *err = 0;
+
+ if (net_interface)
+ {
+ // Print the name in brackets if there is a colon:
+ if (Q_CountChar(net_interface, ':'))
+ Com_Printf("Opening%s IP6 socket: [%s]:%i\n",
+ (alternateProtocol == 2 ? " alternate-2" : alternateProtocol == 1 ? " alternate-1" : ""), net_interface,
+ port);
+ else
+ Com_Printf("Opening%s IP6 socket: %s:%i\n",
+ (alternateProtocol == 2 ? " alternate-2" : alternateProtocol == 1 ? " alternate-1" : ""), net_interface,
+ port);
+ }
+ else
+ Com_Printf("Opening%s IP6 socket: [::]:%i\n",
+ (alternateProtocol == 2 ? " alternate-2" : alternateProtocol == 1 ? " alternate-1" : ""), port);
+
+ if ((newsocket = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET)
+ {
+ *err = socketError;
+ Com_Printf("WARNING: NET_IP6Socket: socket: %s\n", NET_ErrorString());
+ return newsocket;
+ }
+
+ // make it non-blocking
+ if (ioctlsocket(newsocket, FIONBIO, &_true) == SOCKET_ERROR)
+ {
+ Com_Printf("WARNING: NET_IP6Socket: ioctl FIONBIO: %s\n", NET_ErrorString());
+ *err = socketError;
+ closesocket(newsocket);
+ return INVALID_SOCKET;
+ }
+
+#ifdef IPV6_V6ONLY
+ {
+ int i = 1;
+
+ // ipv4 addresses should not be allowed to connect via this socket.
+ if (setsockopt(newsocket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&i, sizeof(i)) == SOCKET_ERROR)
+ {
+ // win32 systems don't seem to support this anyways.
+ Com_DPrintf("WARNING: NET_IP6Socket: setsockopt IPV6_V6ONLY: %s\n", NET_ErrorString());
+ }
+ }
+#endif
+
+ if (!net_interface || !net_interface[0])
+ {
+ address.sin6_family = AF_INET6;
+ address.sin6_addr = in6addr_any;
+ }
+ else
+ {
+ if (!Sys_StringToSockaddr(net_interface, (struct sockaddr *)&address, sizeof(address), AF_INET6))
+ {
+ closesocket(newsocket);
+ return INVALID_SOCKET;
+ }
+ }
+
+ if (port == PORT_ANY)
+ {
+ address.sin6_port = 0;
+ }
+ else
+ {
+ address.sin6_port = htons((short)port);
+ }
+
+ if (bind(newsocket, (const sockaddr *)&address, sizeof(address)) == SOCKET_ERROR)
+ {
+ Com_Printf("WARNING: NET_IP6Socket: bind: %s\n", NET_ErrorString());
+ *err = socketError;
+ closesocket(newsocket);
+ return INVALID_SOCKET;
+ }
+
+ if (bindto) *bindto = address;
+
+ return newsocket;
+}
+
+/*
+====================
+NET_SetMulticast
+Set the current multicast group
+====================
+*/
+void NET_SetMulticast6(void)
+{
+ struct sockaddr_in6 addr;
+
+ if (!*net_mcast6addr->string ||
+ !Sys_StringToSockaddr(net_mcast6addr->string, (struct sockaddr *)&addr, sizeof(addr), AF_INET6))
+ {
+ Com_Printf(
+ "WARNING: NET_JoinMulticast6: Incorrect multicast address given, "
+ "please set cvar %s to a sane value.\n",
+ net_mcast6addr->name);
+
+ Cvar_SetValue(net_enabled->name, net_enabled->integer | NET_DISABLEMCAST);
+
+ return;
+ }
+
+ memcpy(&curgroup.ipv6mr_multiaddr, &addr.sin6_addr, sizeof(curgroup.ipv6mr_multiaddr));
+
+ if (*net_mcast6iface->string)
+ {
+#ifdef _WIN32
+ curgroup.ipv6mr_interface = net_mcast6iface->integer;
+#else
+ curgroup.ipv6mr_interface = if_nametoindex(net_mcast6iface->string);
+#endif
+ }
+ else
+ curgroup.ipv6mr_interface = 0;
+}
+
+/*
+====================
+NET_JoinMulticast
+Join an ipv6 multicast group
+====================
+*/
+void NET_JoinMulticast6(void)
+{
+ /*
+ TODO: accommodate
+ int err;
+
+ if(ip6_socket == INVALID_SOCKET || multicast6_socket != INVALID_SOCKET || (net_enabled->integer & NET_DISABLEMCAST))
+ return;
+
+ if(IN6_IS_ADDR_MULTICAST(&boundto.sin6_addr) || IN6_IS_ADDR_UNSPECIFIED(&boundto.sin6_addr))
+ {
+ // The way the socket was bound does not prohibit receiving multi-cast packets. So we don't need to open a
+ new one.
+ multicast6_socket = ip6_socket;
+ }
+ else
+ {
+ if((multicast6_socket = NET_IP6Socket(net_mcast6addr->string, ntohs(boundto.sin6_port), NULL, &err)) ==
+ INVALID_SOCKET)
+ {
+ // If the OS does not support binding to multicast addresses, like WinXP, at least try with the
+ normal file descriptor.
+ multicast6_socket = ip6_socket;
+ }
+ }
+
+ if(curgroup.ipv6mr_interface)
+ {
+ if (setsockopt(multicast6_socket, IPPROTO_IPV6, IPV6_MULTICAST_IF,
+ (char *) &curgroup.ipv6mr_interface, sizeof(curgroup.ipv6mr_interface)) < 0)
+ {
+ Com_Printf("NET_JoinMulticast6: Couldn't set scope on multicast socket: %s\n", NET_ErrorString());
+
+ if(multicast6_socket != ip6_socket)
+ {
+ closesocket(multicast6_socket);
+ multicast6_socket = INVALID_SOCKET;
+ return;
+ }
+ }
+ }
+
+ if (setsockopt(multicast6_socket, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *) &curgroup, sizeof(curgroup)))
+ {
+ Com_Printf("NET_JoinMulticast6: Couldn't join multicast group: %s\n", NET_ErrorString());
+
+ if(multicast6_socket != ip6_socket)
+ {
+ closesocket(multicast6_socket);
+ multicast6_socket = INVALID_SOCKET;
+ return;
+ }
+ }
+ */
+}
+
+void NET_LeaveMulticast6()
+{
+ /*
+ TODO: accommodate
+ if(multicast6_socket != INVALID_SOCKET)
+ {
+ if(multicast6_socket != ip6_socket)
+ closesocket(multicast6_socket);
+ else
+ setsockopt(multicast6_socket, IPPROTO_IPV6, IPV6_LEAVE_GROUP, (char *) &curgroup, sizeof(curgroup));
+
+ multicast6_socket = INVALID_SOCKET;
+ }
+ */
+}
+
+/*
+====================
+NET_OpenSocks
+====================
+*/
+void NET_OpenSocks(int port)
+{
+ /*
+ TODO: accommodate
+ struct sockaddr_in address;
+ struct hostent *h;
+ int len;
+ bool rfc1929;
+ unsigned char buf[64];
+
+ usingSocks = false;
+
+ Com_Printf( "Opening connection to SOCKS server.\n" );
+
+ if ( ( socks_socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ) ) == INVALID_SOCKET ) {
+ Com_Printf( "WARNING: NET_OpenSocks: socket: %s\n", NET_ErrorString() );
+ return;
+ }
+
+ h = gethostbyname( net_socksServer->string );
+ if ( h == NULL ) {
+ Com_Printf( "WARNING: NET_OpenSocks: gethostbyname: %s\n", NET_ErrorString() );
+ return;
+ }
+ if ( h->h_addrtype != AF_INET ) {
+ Com_Printf( "WARNING: NET_OpenSocks: gethostbyname: address type was not AF_INET\n" );
+ return;
+ }
+ address.sin_family = AF_INET;
+ address.sin_addr.s_addr = *(int *)h->h_addr_list[0];
+ address.sin_port = htons( (short)net_socksPort->integer );
+
+ if ( connect( socks_socket, (struct sockaddr *)&address, sizeof( address ) ) == SOCKET_ERROR ) {
+ Com_Printf( "NET_OpenSocks: connect: %s\n", NET_ErrorString() );
+ return;
+ }
+
+ // send socks authentication handshake
+ if ( *net_socksUsername->string || *net_socksPassword->string ) {
+ rfc1929 = true;
+ }
+ else {
+ rfc1929 = false;
+ }
+
+ buf[0] = 5; // SOCKS version
+ // method count
+ if ( rfc1929 ) {
+ buf[1] = 2;
+ len = 4;
+ }
+ else {
+ buf[1] = 1;
+ len = 3;
+ }
+ buf[2] = 0; // method #1 - method id #00: no authentication
+ if ( rfc1929 ) {
+ buf[2] = 2; // method #2 - method id #02: username/password
+ }
+ if ( send( socks_socket, (void *)buf, len, 0 ) == SOCKET_ERROR ) {
+ Com_Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() );
+ return;
+ }
+
+ // get the response
+ len = recv( socks_socket, (void *)buf, 64, 0 );
+ if ( len == SOCKET_ERROR ) {
+ Com_Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() );
+ return;
+ }
+ if ( len != 2 || buf[0] != 5 ) {
+ Com_Printf( "NET_OpenSocks: bad response\n" );
+ return;
+ }
+ switch( buf[1] ) {
+ case 0: // no authentication
+ break;
+ case 2: // username/password authentication
+ break;
+ default:
+ Com_Printf( "NET_OpenSocks: request denied\n" );
+ return;
+ }
+
+ // do username/password authentication if needed
+ if ( buf[1] == 2 ) {
+ int ulen;
+ int plen;
+
+ // build the request
+ ulen = strlen( net_socksUsername->string );
+ plen = strlen( net_socksPassword->string );
+
+ buf[0] = 1; // username/password authentication version
+ buf[1] = ulen;
+ if ( ulen ) {
+ memcpy( &buf[2], net_socksUsername->string, ulen );
+ }
+ buf[2 + ulen] = plen;
+ if ( plen ) {
+ memcpy( &buf[3 + ulen], net_socksPassword->string, plen );
+ }
+
+ // send it
+ if ( send( socks_socket, (void *)buf, 3 + ulen + plen, 0 ) == SOCKET_ERROR ) {
+ Com_Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() );
+ return;
+ }
+
+ // get the response
+ len = recv( socks_socket, (void *)buf, 64, 0 );
+ if ( len == SOCKET_ERROR ) {
+ Com_Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() );
+ return;
+ }
+ if ( len != 2 || buf[0] != 1 ) {
+ Com_Printf( "NET_OpenSocks: bad response\n" );
+ return;
+ }
+ if ( buf[1] != 0 ) {
+ Com_Printf( "NET_OpenSocks: authentication failed\n" );
+ return;
+ }
+ }
+
+ // send the UDP associate request
+ buf[0] = 5; // SOCKS version
+ buf[1] = 3; // command: UDP associate
+ buf[2] = 0; // reserved
+ buf[3] = 1; // address type: IPV4
+ *(int *)&buf[4] = INADDR_ANY;
+ *(short *)&buf[8] = htons( (short)port ); // port
+ if ( send( socks_socket, (void *)buf, 10, 0 ) == SOCKET_ERROR ) {
+ Com_Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() );
+ return;
+ }
+
+ // get the response
+ len = recv( socks_socket, (void *)buf, 64, 0 );
+ if( len == SOCKET_ERROR ) {
+ Com_Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() );
+ return;
+ }
+ if( len < 2 || buf[0] != 5 ) {
+ Com_Printf( "NET_OpenSocks: bad response\n" );
+ return;
+ }
+ // check completion code
+ if( buf[1] != 0 ) {
+ Com_Printf( "NET_OpenSocks: request denied: %i\n", buf[1] );
+ return;
+ }
+ if( buf[3] != 1 ) {
+ Com_Printf( "NET_OpenSocks: relay address is not IPV4: %i\n", buf[3] );
+ return;
+ }
+ ((struct sockaddr_in *)&socksRelayAddr)->sin_family = AF_INET;
+ ((struct sockaddr_in *)&socksRelayAddr)->sin_addr.s_addr = *(int *)&buf[4];
+ ((struct sockaddr_in *)&socksRelayAddr)->sin_port = *(short *)&buf[8];
+ memset( ((struct sockaddr_in *)&socksRelayAddr)->sin_zero, 0, 8 );
+
+ usingSocks = true;
+ */
+}
+
+/*
+=====================
+NET_AddLocalAddress
+=====================
+*/
+static void NET_AddLocalAddress(char *ifname, struct sockaddr *addr, struct sockaddr *netmask)
+{
+ int addrlen;
+ sa_family_t family;
+
+ // only add addresses that have all required info.
+ if (!addr || !netmask || !ifname) return;
+
+ family = addr->sa_family;
+
+ if (numIP < MAX_IPS)
+ {
+ if (family == AF_INET)
+ {
+ addrlen = sizeof(struct sockaddr_in);
+ localIP[numIP].type = NA_IP;
+ }
+ else if (family == AF_INET6)
+ {
+ addrlen = sizeof(struct sockaddr_in6);
+ localIP[numIP].type = NA_IP6;
+ }
+ else
+ return;
+
+ Q_strncpyz(localIP[numIP].ifname, ifname, sizeof(localIP[numIP].ifname));
+
+ localIP[numIP].family = family;
+
+ memcpy(&localIP[numIP].addr, addr, addrlen);
+ memcpy(&localIP[numIP].netmask, netmask, addrlen);
+
+ numIP++;
+ }
+}
+
+#if defined(__linux__) || defined(__APPLE__) || defined(__BSD__)
+static void NET_GetLocalAddress(void)
+{
+ struct ifaddrs *ifap, *search;
+
+ numIP = 0;
+
+ if (getifaddrs(&ifap))
+ Com_Printf("NET_GetLocalAddress: Unable to get list of network interfaces: %s\n", NET_ErrorString());
+ else
+ {
+ for (search = ifap; search; search = search->ifa_next)
+ {
+ // Only add interfaces that are up.
+ if (ifap->ifa_flags & IFF_UP) NET_AddLocalAddress(search->ifa_name, search->ifa_addr, search->ifa_netmask);
+ }
+
+ freeifaddrs(ifap);
+
+ Sys_ShowIP();
+ }
+}
+#else
+static void NET_GetLocalAddress(void)
+{
+ char hostname[256];
+ struct addrinfo hint;
+ struct addrinfo *res = NULL;
+
+ numIP = 0;
+
+ if (gethostname(hostname, 256) == SOCKET_ERROR) return;
+
+ memset(&hint, 0, sizeof(hint));
+
+ hint.ai_family = AF_UNSPEC;
+ hint.ai_socktype = SOCK_DGRAM;
+
+ if (!getaddrinfo(hostname, NULL, &hint, &res))
+ {
+ struct sockaddr_in mask4;
+ struct sockaddr_in6 mask6;
+ struct addrinfo *search;
+
+ /* On operating systems where it's more difficult to find out the configured interfaces, we'll just assume a
+ * netmask with all bits set. */
+
+ memset(&mask4, 0, sizeof(mask4));
+ memset(&mask6, 0, sizeof(mask6));
+ mask4.sin_family = AF_INET;
+ memset(&mask4.sin_addr.s_addr, 0xFF, sizeof(mask4.sin_addr.s_addr));
+ mask6.sin6_family = AF_INET6;
+ memset(&mask6.sin6_addr, 0xFF, sizeof(mask6.sin6_addr));
+
+ // add all IPs from returned list.
+ for (search = res; search; search = search->ai_next)
+ {
+ if (search->ai_family == AF_INET)
+ NET_AddLocalAddress("", search->ai_addr, (struct sockaddr *)&mask4);
+ else if (search->ai_family == AF_INET6)
+ NET_AddLocalAddress("", search->ai_addr, (struct sockaddr *)&mask6);
+ }
+
+ Sys_ShowIP();
+ }
+
+ if (res) freeaddrinfo(res);
+}
+#endif
+
+/*
+====================
+NET_OpenIP
+====================
+*/
+void NET_OpenIP(void)
+{
+ int a;
+ int i;
+ int err;
+ int ports[3];
+ int port6s[3];
+
+ for (a = 0; a < 3; ++a)
+ {
+ ports[a] = net_ports[a]->integer;
+ port6s[a] = net_port6s[a]->integer;
+ }
+
+ NET_GetLocalAddress();
+
+ for (a = 0; a < 3; ++a)
+ {
+ // indent
+ if (a == 0 && (net_alternateProtocols->integer & NET_DISABLEPRIMPROTO)) continue;
+ if (a == 1 && !(net_alternateProtocols->integer & NET_ENABLEALT1PROTO)) continue;
+ if (a == 2 && !(net_alternateProtocols->integer & NET_ENABLEALT2PROTO)) continue;
+
+ // automatically scan for a valid port, so multiple
+ // dedicated servers can be started without requiring
+ // a different net_port for each one
+
+ if (net_enabled->integer & NET_ENABLEV6)
+ {
+ for (i = 0; i < 10; i++)
+ {
+ ip6_sockets[a] = NET_IP6Socket(a, net_ip6->string, port6s[a] + i, &boundto, &err);
+ if (ip6_sockets[a] != INVALID_SOCKET)
+ {
+ Cvar_SetValue((a == 2 ? "net_alt2port6" : a == 1 ? "net_alt1port6" : "net_port6"), port6s[a] + i);
+ break;
+ }
+ else
+ {
+ if (err == EAFNOSUPPORT) break;
+ }
+ }
+ if (ip6_sockets[a] == INVALID_SOCKET)
+ Com_Printf("WARNING: Couldn't bind to a%s v6 ip address.\n",
+ (a == 2 ? "n alternate-2" : a == 1 ? "n alternate-1" : ""));
+ }
+
+ if (net_enabled->integer & NET_ENABLEV4)
+ {
+ for (i = 0; i < 10; i++)
+ {
+ ip_sockets[a] = NET_IPSocket(a, net_ip->string, ports[a] + i, &err);
+ if (ip_sockets[a] != INVALID_SOCKET)
+ {
+ Cvar_SetValue((a == 2 ? "net_alt2port" : a == 1 ? "net_alt1port" : "net_port"), ports[a] + i);
+
+ if (net_socksEnabled->integer) NET_OpenSocks(ports[a] + i);
+
+ break;
+ }
+ else
+ {
+ if (err == EAFNOSUPPORT) break;
+ }
+ }
+
+ if (ip_sockets[a] == INVALID_SOCKET)
+ Com_Printf("WARNING: Couldn't bind to a%s v4 ip address.\n",
+ (a == 2 ? "n alternate-2" : a == 1 ? "n alternate-1" : ""));
+ }
+ // outdent
+ }
+}
+
+//===================================================================
+
+/*
+====================
+NET_GetCvars
+====================
+*/
+static bool NET_GetCvars(void)
+{
+ int modified;
+ int a;
+
+#ifdef DEDICATED
+ // I want server owners to explicitly turn on ipv6 support.
+ net_enabled = Cvar_Get("net_enabled", "1", CVAR_LATCH | CVAR_ARCHIVE);
+#else
+ /* End users have it enabled so they can connect to ipv6-only hosts, but ipv4 will be
+ * used if available due to ping */
+ net_enabled = Cvar_Get("net_enabled", "3", CVAR_LATCH | CVAR_ARCHIVE);
+#endif
+ modified = net_enabled->modified;
+ net_enabled->modified = false;
+
+ net_alternateProtocols = Cvar_Get("net_alternateProtocols", "3", CVAR_LATCH | CVAR_ARCHIVE);
+ modified += net_alternateProtocols->modified;
+ net_alternateProtocols->modified = false;
+
+ net_ip = Cvar_Get("net_ip", "0.0.0.0", CVAR_LATCH);
+ modified += net_ip->modified;
+ net_ip->modified = false;
+
+ net_ip6 = Cvar_Get("net_ip6", "::", CVAR_LATCH);
+ modified += net_ip6->modified;
+ net_ip6->modified = false;
+
+ for (a = 0; a < 3; ++a)
+ {
+ net_ports[a] = Cvar_Get((a == 2 ? "net_alt2port" : a == 1 ? "net_alt1port" : "net_port"),
+ (a == 2 ? XSTRING(ALT2PORT_SERVER) : a == 1 ? XSTRING(ALT1PORT_SERVER) : XSTRING(PORT_SERVER)), CVAR_LATCH);
+ modified += net_ports[a]->modified;
+ net_ports[a]->modified = false;
+
+ net_port6s[a] = Cvar_Get((a == 2 ? "net_alt2port6" : a == 1 ? "net_alt1port6" : "net_port6"),
+ (a == 2 ? XSTRING(ALT2PORT_SERVER) : a == 1 ? XSTRING(ALT1PORT_SERVER) : XSTRING(PORT_SERVER)), CVAR_LATCH);
+ modified += net_port6s[a]->modified;
+ net_port6s[a]->modified = false;
+ }
+
+ // Some cvars for configuring multicast options which facilitates scanning for servers on local subnets.
+ net_mcast6addr = Cvar_Get("net_mcast6addr", NET_MULTICAST_IP6, CVAR_LATCH | CVAR_ARCHIVE);
+ modified += net_mcast6addr->modified;
+ net_mcast6addr->modified = false;
+
+#ifdef _WIN32
+ net_mcast6iface = Cvar_Get("net_mcast6iface", "0", CVAR_LATCH | CVAR_ARCHIVE);
+#else
+ net_mcast6iface = Cvar_Get("net_mcast6iface", "", CVAR_LATCH | CVAR_ARCHIVE);
+#endif
+ modified += net_mcast6iface->modified;
+ net_mcast6iface->modified = false;
+
+ net_socksEnabled = Cvar_Get("net_socksEnabled", "0", CVAR_LATCH | CVAR_ARCHIVE);
+ modified += net_socksEnabled->modified;
+ net_socksEnabled->modified = false;
+
+ net_socksServer = Cvar_Get("net_socksServer", "", CVAR_LATCH | CVAR_ARCHIVE);
+ modified += net_socksServer->modified;
+ net_socksServer->modified = false;
+
+ net_socksPort = Cvar_Get("net_socksPort", "1080", CVAR_LATCH | CVAR_ARCHIVE);
+ modified += net_socksPort->modified;
+ net_socksPort->modified = false;
+
+ net_socksUsername = Cvar_Get("net_socksUsername", "", CVAR_LATCH | CVAR_ARCHIVE);
+ modified += net_socksUsername->modified;
+ net_socksUsername->modified = false;
+
+ net_socksPassword = Cvar_Get("net_socksPassword", "", CVAR_LATCH | CVAR_ARCHIVE);
+ modified += net_socksPassword->modified;
+ net_socksPassword->modified = false;
+
+ net_dropsim = Cvar_Get("net_dropsim", "", CVAR_TEMP);
+
+ return modified ? true : false;
+}
+
+/*
+====================
+NET_Config
+====================
+*/
+void NET_Config(bool enableNetworking)
+{
+ bool modified;
+ bool stop;
+ bool start;
+ int a;
+
+ // get any latched changes to cvars
+ modified = NET_GetCvars();
+
+ if (!net_enabled->integer)
+ {
+ enableNetworking = false;
+ }
+
+ // if enable state is the same and no cvars were modified, we have nothing to do
+ if (enableNetworking == networkingEnabled && !modified)
+ {
+ return;
+ }
+
+ if (enableNetworking == networkingEnabled)
+ {
+ if (enableNetworking)
+ {
+ stop = true;
+ start = true;
+ }
+ else
+ {
+ stop = false;
+ start = false;
+ }
+ }
+ else
+ {
+ if (enableNetworking)
+ {
+ stop = false;
+ start = true;
+ }
+ else
+ {
+ stop = true;
+ start = false;
+ }
+ networkingEnabled = enableNetworking;
+ }
+
+ if (stop)
+ {
+ for (a = 0; a < 3; ++a)
+ {
+ if (ip_sockets[a] != INVALID_SOCKET)
+ {
+ closesocket(ip_sockets[a]);
+ ip_sockets[a] = INVALID_SOCKET;
+ }
+
+ if (ip6_sockets[a] != INVALID_SOCKET)
+ {
+ closesocket(ip6_sockets[a]);
+ ip6_sockets[a] = INVALID_SOCKET;
+ }
+ }
+
+ /*
+ TODO: accommodate
+ if(multicast6_socket != INVALID_SOCKET)
+ {
+ if(multicast6_socket != ip6_socket)
+ closesocket(multicast6_socket);
+
+ multicast6_socket = INVALID_SOCKET;
+ }
+
+ if ( socks_socket != INVALID_SOCKET ) {
+ closesocket( socks_socket );
+ socks_socket = INVALID_SOCKET;
+ }
+ */
+ }
+
+ if (start)
+ {
+ if (net_enabled->integer)
+ {
+ NET_OpenIP();
+ NET_SetMulticast6();
+ }
+ }
+}
+
+/*
+====================
+NET_Init
+====================
+*/
+void NET_Init(void)
+{
+#ifdef _WIN32
+ int r;
+
+ r = WSAStartup(MAKEWORD(1, 1), &winsockdata);
+ if (r)
+ {
+ Com_Printf("WARNING: Winsock initialization failed, returned %d\n", r);
+ return;
+ }
+
+ winsockInitialized = true;
+ Com_Printf("Winsock Initialized\n");
+#endif
+
+ NET_Config(true);
+
+ Cmd_AddCommand("net_restart", NET_Restart_f);
+}
+
+/*
+====================
+NET_Shutdown
+====================
+*/
+void NET_Shutdown(void)
+{
+ if (!networkingEnabled)
+ {
+ return;
+ }
+
+ NET_Config(false);
+
+#ifdef _WIN32
+ WSACleanup();
+ winsockInitialized = false;
+#endif
+}
+
+/*
+====================
+NET_Event
+
+Called from NET_Sleep which uses select() to determine which sockets have seen action.
+====================
+*/
+
+void NET_Event(fd_set *fdr)
+{
+ uint8_t bufData[MAX_MSGLEN + 1];
+ netadr_t from;
+ msg_t netmsg;
+
+ memset(&from, 0, sizeof(from));
+
+ while (1)
+ {
+ MSG_Init(&netmsg, bufData, sizeof(bufData));
+
+ if (NET_GetPacket(&from, &netmsg, fdr))
+ {
+ if (net_dropsim->value > 0.0f && net_dropsim->value <= 100.0f)
+ {
+ // com_dropsim->value percent of incoming packets get dropped.
+ if (rand() < (int)(((double)RAND_MAX) / 100.0 * (double)net_dropsim->value))
+ continue; // drop this packet
+ }
+
+ if (com_sv_running->integer)
+ Com_RunAndTimeServerPacket(&from, &netmsg);
+ else
+ CL_PacketEvent(from, &netmsg);
+ }
+ else
+ break;
+ }
+}
+
+/*
+====================
+NET_Sleep
+
+Sleeps msec or until something happens on the network
+====================
+*/
+void NET_Sleep(int msec)
+{
+ struct timeval timeout;
+ fd_set fdr;
+ int retval;
+ int a;
+ SOCKET highestfd = INVALID_SOCKET;
+
+ if (msec < 0) msec = 0;
+
+ FD_ZERO(&fdr);
+
+ for (a = 0; a < 3; ++a)
+ {
+ if (ip_sockets[a] != INVALID_SOCKET)
+ {
+ FD_SET(ip_sockets[a], &fdr);
+
+ if (highestfd == INVALID_SOCKET || ip_sockets[a] > highestfd) highestfd = ip_sockets[a];
+ }
+ if (ip6_sockets[a] != INVALID_SOCKET)
+ {
+ FD_SET(ip6_sockets[a], &fdr);
+
+ if (highestfd == INVALID_SOCKET || ip6_sockets[a] > highestfd) highestfd = ip6_sockets[a];
+ }
+ }
+
+#ifdef _WIN32
+ if (highestfd == INVALID_SOCKET)
+ {
+ // windows ain't happy when select is called without valid FDs
+ SleepEx(msec, 0);
+ return;
+ }
+#endif
+
+ timeout.tv_sec = msec / 1000;
+ timeout.tv_usec = (msec % 1000) * 1000;
+
+ retval = select(highestfd + 1, &fdr, NULL, NULL, &timeout);
+
+ if (retval == SOCKET_ERROR)
+ Com_Printf("Warning: select() syscall failed: %s\n", NET_ErrorString());
+ else if (retval > 0)
+ NET_Event(&fdr);
+}
+
+/*
+====================
+NET_Restart_f
+====================
+*/
+void NET_Restart_f(void) { NET_Config(true); }
diff --git a/src/qcommon/parse.cpp b/src/qcommon/parse.cpp
new file mode 100644
index 0000000..aa72dec
--- /dev/null
+++ b/src/qcommon/parse.cpp
@@ -0,0 +1,3725 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "files.h"
+#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
+{
+ const 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
+{
+ const char *name;
+ bool (*func)(source_t *source);
+} directive_t;
+
+#define DEFINEHASHSIZE 1024
+
+static bool Parse_ReadToken(source_t *source, token_t *token);
+static bool Parse_AddDefineToSourceFromString( source_t *source, const char *string );
+
+int numtokens;
+
+//list with global defines added to every source loaded
+define_t *globaldefines;
+
+//longer punctuations first
+punctuation_t default_punctuations[] =
+{
+ //binary operators
+ {">>=",P_RSHIFT_ASSIGN, NULL},
+ {"<<=",P_LSHIFT_ASSIGN, NULL},
+ //
+ {"...",P_PARMS, NULL},
+ //define merge operator
+ {"##",P_PRECOMPMERGE, NULL},
+ //logic operators
+ {"&&",P_LOGIC_AND, NULL},
+ {"||",P_LOGIC_OR, NULL},
+ {">=",P_LOGIC_GEQ, NULL},
+ {"<=",P_LOGIC_LEQ, NULL},
+ {"==",P_LOGIC_EQ, NULL},
+ {"!=",P_LOGIC_UNEQ, NULL},
+ //arithmatic operators
+ {"*=",P_MUL_ASSIGN, NULL},
+ {"/=",P_DIV_ASSIGN, NULL},
+ {"%=",P_MOD_ASSIGN, NULL},
+ {"+=",P_ADD_ASSIGN, NULL},
+ {"-=",P_SUB_ASSIGN, NULL},
+ {"++",P_INC, NULL},
+ {"--",P_DEC, NULL},
+ //binary operators
+ {"&=",P_BIN_AND_ASSIGN, NULL},
+ {"|=",P_BIN_OR_ASSIGN, NULL},
+ {"^=",P_BIN_XOR_ASSIGN, NULL},
+ {">>",P_RSHIFT, NULL},
+ {"<<",P_LSHIFT, NULL},
+ //reference operators
+ {"->",P_POINTERREF, NULL},
+ //C++
+ {"::",P_CPP1, NULL},
+ {".*",P_CPP2, NULL},
+ //arithmatic operators
+ {"*",P_MUL, NULL},
+ {"/",P_DIV, NULL},
+ {"%",P_MOD, NULL},
+ {"+",P_ADD, NULL},
+ {"-",P_SUB, NULL},
+ {"=",P_ASSIGN, NULL},
+ //binary operators
+ {"&",P_BIN_AND, NULL},
+ {"|",P_BIN_OR, NULL},
+ {"^",P_BIN_XOR, NULL},
+ {"~",P_BIN_NOT, NULL},
+ //logic operators
+ {"!",P_LOGIC_NOT, NULL},
+ {">",P_LOGIC_GREATER, NULL},
+ {"<",P_LOGIC_LESS, NULL},
+ //reference operator
+ {".",P_REF, NULL},
+ //seperators
+ {",",P_COMMA, NULL},
+ {";",P_SEMICOLON, NULL},
+ //label indication
+ {":",P_COLON, NULL},
+ //if statement
+ {"?",P_QUESTIONMARK, NULL},
+ //embracements
+ {"(",P_PARENTHESESOPEN, NULL},
+ {")",P_PARENTHESESCLOSE, NULL},
+ {"{",P_BRACEOPEN, NULL},
+ {"}",P_BRACECLOSE, NULL},
+ {"[",P_SQBRACKETOPEN, NULL},
+ {"]",P_SQBRACKETCLOSE, NULL},
+ //
+ {"\\",P_BACKSLASH, NULL},
+ //precompiler operator
+ {"#",P_PRECOMP, NULL},
+ {"$",P_DOLLAR, NULL},
+ {NULL, 0, NULL}
+};
+
+/*
+===============
+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 *));
+
+ ::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
+===============
+*/
+__attribute__ ((format (printf, 2, 3))) static void QDECL Parse_ScriptError(script_t *script, const 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
+===============
+*/
+__attribute__ ((format (printf, 2, 3))) static void QDECL Parse_ScriptWarning(script_t *script, const 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 bool Parse_ReadWhiteSpace(script_t *script)
+{
+ while(1)
+ {
+ //skip white space
+ while(*script->script_p <= ' ')
+ {
+ if (!*script->script_p) return false;
+ 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 false;
+ }
+ while(*script->script_p != '\n');
+ script->line++;
+ script->script_p++;
+ if (!*script->script_p) return false;
+ continue;
+ }
+ //comments /* */
+ else if (*(script->script_p+1) == '*')
+ {
+ script->script_p++;
+ do
+ {
+ script->script_p++;
+ if (!*script->script_p) return false;
+ if (*script->script_p == '\n') script->line++;
+ }
+ while(!(*script->script_p == '*' && *(script->script_p+1) == '/'));
+ script->script_p++;
+ if (!*script->script_p) return false;
+ script->script_p++;
+ if (!*script->script_p) return false;
+ continue;
+ }
+ }
+ break;
+ }
+ return true;
+}
+
+/*
+===============
+Parse_ReadEscapeCharacter
+===============
+*/
+static bool 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 true;
+}
+
+/*
+===============
+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 bool 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 false;
+ }
+ //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 false;
+ }
+ 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 false;
+ }
+ if (*script->script_p == '\n')
+ {
+ token->string[len] = 0;
+ Parse_ScriptError(script, "newline inside string %s", token->string);
+ return false;
+ }
+ 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 true;
+}
+
+/*
+===============
+Parse_ReadName
+===============
+*/
+static bool 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 false;
+ }
+ 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 true;
+}
+
+/*
+===============
+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 bool 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 false;
+ }
+ 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 false;
+ }
+ c = *script->script_p;
+ }
+ token->subtype |= TT_BINARY;
+ }
+#endif //BINARYNUMBERS
+ else //decimal or octal integer or floating point number
+ {
+ octal = false;
+ dot = false;
+ if (*script->script_p == '0') octal = true;
+ while(1)
+ {
+ c = *script->script_p;
+ if (c == '.') dot = true;
+ else if (c == '8' || c == '9') octal = false;
+ 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 false;
+ }
+ }
+ if (octal) token->subtype |= TT_OCTAL;
+ else token->subtype |= TT_DECIMAL;
+ if (dot) token->subtype |= TT_FLOAT;
+ }
+ for (i = 0; i < 2; i++)
+ {
+ c = *script->script_p;
+ //check for a LONG number
+ if ( (c == 'l' || c == 'L')
+ && !(token->subtype & TT_LONG))
+ {
+ script->script_p++;
+ token->subtype |= TT_LONG;
+ }
+ //check for an UNSIGNED number
+ else if ( (c == 'u' || c == 'U')
+ && !(token->subtype & (TT_UNSIGNED | TT_FLOAT)))
+ {
+ script->script_p++;
+ token->subtype |= TT_UNSIGNED;
+ }
+ }
+ token->string[len] = '\0';
+ Parse_NumberValue(token->string, token->subtype, &token->intvalue, &token->floatvalue);
+ if (!(token->subtype & TT_FLOAT)) token->subtype |= TT_INTEGER;
+ return true;
+}
+
+/*
+===============
+Parse_ReadPunctuation
+===============
+*/
+static bool Parse_ReadPunctuation(script_t *script, token_t *token)
+{
+ int len;
+ const 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 true;
+ }
+ }
+ }
+ return false;
+}
+
+/*
+===============
+Parse_ReadPrimitive
+===============
+*/
+static bool 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 false;
+ }
+ token->string[len++] = *script->script_p++;
+ }
+ token->string[len] = 0;
+ //copy the token into the script structure
+ ::memcpy(&script->token, token, sizeof(token_t));
+ //primitive reading successfull
+ return true;
+}
+
+/*
+===============
+Parse_ReadScriptToken
+===============
+*/
+static bool Parse_ReadScriptToken(script_t *script, token_t *token)
+{
+ //if there is a token available (from UnreadToken)
+ if (script->tokenavailable)
+ {
+ script->tokenavailable = 0;
+ ::memcpy(token, &script->token, sizeof(token_t));
+ return true;
+ }
+ //save script pointer
+ script->lastscript_p = script->script_p;
+ //save line counter
+ script->lastline = script->line;
+ //clear the token stuff
+ ::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 false;
+
+ 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 false;
+ }
+ //if an literal
+ else if (*script->script_p == '\'')
+ {
+ //if (!Parse_ReadLiteral(script, token)) return false;
+ if (!Parse_ReadString(script, token, '\'')) return false;
+ }
+ //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 false;
+ }
+ //check for punctuations
+ else if (!Parse_ReadPunctuation(script, token))
+ {
+ Parse_ScriptError(script, "can't read token");
+ return false;
+ }
+ //copy the token into the script structure
+ ::memcpy(&script->token, token, sizeof(token_t));
+ //succesfully read a token
+ return true;
+}
+
+/*
+===============
+Parse_StripDoubleQuotes
+===============
+*/
+static void Parse_StripDoubleQuotes(char *string)
+{
+ if (*string == '\"')
+ {
+ memmove( string, string + 1, strlen( string ) + 1 );
+ }
+ if (string[strlen(string)-1] == '\"')
+ {
+ string[strlen(string)-1] = '\0';
+ }
+}
+
+/*
+===============
+Parse_EndOfScript
+===============
+*/
+static bool Parse_EndOfScript(script_t *script)
+{
+ return script->script_p >= script->end_p;
+}
+
+/*
+===============
+Parse_LoadScriptFile
+===============
+*/
+static script_t *Parse_LoadScriptFile(const char *filename)
+{
+ fileHandle_t fp;
+ int length;
+ void *buffer;
+ script_t *script;
+
+ length = FS_FOpenFileRead( filename, &fp, false );
+ if (!fp) return NULL;
+
+ buffer = Z_Malloc(sizeof(script_t) + length + 1);
+ ::memset( buffer, 0, sizeof(script_t) + length + 1 );
+
+ script = (script_t *) buffer;
+ ::memset(script, 0, sizeof(script_t));
+ strcpy(script->filename, filename);
+ script->buffer = (char *) buffer + sizeof(script_t);
+ script->buffer[length] = 0;
+ script->length = length;
+ //pointer in script buffer
+ script->script_p = script->buffer;
+ //pointer in script buffer before reading token
+ script->lastscript_p = script->buffer;
+ //pointer to end of script buffer
+ script->end_p = &script->buffer[length];
+ //set if there's a token available in script->token
+ script->tokenavailable = 0;
+ //
+ script->line = 1;
+ script->lastline = 1;
+ //
+ Parse_SetScriptPunctuations(script, NULL);
+ //
+ FS_Read(script->buffer, length, fp);
+ FS_FCloseFile(fp);
+ //
+
+ return script;
+}
+
+/*
+===============
+Parse_LoadScriptMemory
+===============
+*/
+static script_t *Parse_LoadScriptMemory(const char *ptr, int length, const char *name)
+{
+ void *buffer;
+ script_t *script;
+
+ buffer = Z_Malloc(sizeof(script_t) + length + 1);
+ ::memset( buffer, 0, sizeof(script_t) + length + 1 );
+
+ script = (script_t *) buffer;
+ ::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);
+ //
+ ::memcpy(script->buffer, ptr, length);
+ //
+ return script;
+}
+
+/*
+===============
+Parse_FreeScript
+===============
+*/
+static void Parse_FreeScript(script_t *script)
+{
+ if (script->punctuationtable) Z_Free(script->punctuationtable);
+ Z_Free(script);
+}
+
+/*
+===============
+Parse_SourceError
+===============
+*/
+__attribute__ ((format (printf, 2, 3))) static void QDECL Parse_SourceError(source_t *source, const 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
+===============
+*/
+__attribute__ ((format (printf, 2, 3))) static void QDECL Parse_SourceWarning(source_t *source, const 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;
+ ::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 bool Parse_ReadSourceToken(source_t *source, token_t *token)
+{
+ token_t *t;
+ script_t *script;
+ int type, skip, lines;
+
+ lines = 0;
+ //if there's no token already available
+ while(!source->tokens)
+ {
+ //if there's a token to read from the script
+ if( Parse_ReadScriptToken( source->scriptstack, token ) )
+ {
+ token->linescrossed += lines;
+ return true;
+ }
+
+ // if lines were crossed before the end of the script, count them
+ lines += source->scriptstack->line - source->scriptstack->lastline;
+
+ //if at the end of the script
+ if (Parse_EndOfScript(source->scriptstack))
+ {
+ //remove all indents of the script
+ while(source->indentstack &&
+ source->indentstack->script == source->scriptstack)
+ {
+ Parse_SourceWarning(source, "missing #endif");
+ Parse_PopIndent(source, &type, &skip);
+ }
+ }
+ //if this was the initial script
+ if (!source->scriptstack->next) return false;
+ //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
+ ::memcpy(token, source->tokens, sizeof(token_t));
+ //free the read token
+ t = source->tokens;
+ source->tokens = source->tokens->next;
+ Parse_FreeToken(t);
+ return true;
+}
+
+/*
+===============
+Parse_UnreadSourceToken
+===============
+*/
+static bool Parse_UnreadSourceToken(source_t *source, token_t *token)
+{
+ token_t *t;
+
+ t = Parse_CopyToken(token);
+ t->next = source->tokens;
+ source->tokens = t;
+ return true;
+}
+
+/*
+===============
+Parse_ReadDefineParms
+===============
+*/
+static bool 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 false;
+ }
+ //
+ if (define->numparms > maxparms)
+ {
+ Parse_SourceError(source, "define with more than %d parameters", maxparms);
+ return false;
+ }
+ //
+ 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 false;
+ }
+ //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 false;
+ }
+ if (numparms >= define->numparms)
+ {
+ Parse_SourceWarning(source, "define %s has too many parms", define->name);
+ return false;
+ }
+ parms[numparms] = NULL;
+ lastcomma = 1;
+ last = NULL;
+ while(!done)
+ {
+ //
+ if (!Parse_ReadSourceToken(source, &token))
+ {
+ Parse_SourceError(source, "define %s incomplete", define->name);
+ return false;
+ }
+ //
+ 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 true;
+}
+
+/*
+===============
+Parse_StringizeTokens
+===============
+*/
+static bool 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 true;
+}
+
+/*
+===============
+Parse_MergeTokens
+===============
+*/
+static bool 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 true;
+ }
+ //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 true;
+ }
+ //FIXME: merging of two number of the same sub type
+ return false;
+}
+
+/*
+===============
+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 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 bool Parse_ExpandBuiltinDefine(source_t *source, token_t *deftoken, define_t *define,
+ token_t **firsttoken, token_t **lasttoken)
+{
+ token_t *token;
+ time_t t;
+
+ char *curtime;
+
+ token = Parse_CopyToken(deftoken);
+ switch(define->builtin)
+ {
+ case BUILTIN_LINE:
+ {
+ sprintf(token->string, "%d", deftoken->line);
+ token->intvalue = deftoken->line;
+ token->floatvalue = deftoken->line;
+ token->type = TT_NUMBER;
+ token->subtype = TT_DECIMAL | TT_INTEGER;
+ *firsttoken = token;
+ *lasttoken = token;
+ break;
+ }
+ case BUILTIN_FILE:
+ {
+ strcpy(token->string, source->scriptstack->filename);
+ token->type = TT_NAME;
+ token->subtype = strlen(token->string);
+ *firsttoken = token;
+ *lasttoken = token;
+ break;
+ }
+ case BUILTIN_DATE:
+ {
+ t = time(NULL);
+ curtime = ctime(&t);
+ strcpy(token->string, "\"");
+ strncat(token->string, curtime+4, 7);
+ strncat(token->string+7, curtime+20, 4);
+ strcat(token->string, "\"");
+ free(curtime);
+ token->type = TT_NAME;
+ token->subtype = strlen(token->string);
+ *firsttoken = token;
+ *lasttoken = token;
+ break;
+ }
+ case BUILTIN_TIME:
+ {
+ t = time(NULL);
+ curtime = ctime(&t);
+ strcpy(token->string, "\"");
+ strncat(token->string, curtime+11, 8);
+ strcat(token->string, "\"");
+ free(curtime);
+ token->type = TT_NAME;
+ token->subtype = strlen(token->string);
+ *firsttoken = token;
+ *lasttoken = token;
+ break;
+ }
+ case BUILTIN_STDC:
+ default:
+ {
+ *firsttoken = NULL;
+ *lasttoken = NULL;
+ break;
+ }
+ }
+ return true;
+}
+
+/*
+===============
+Parse_ExpandDefine
+===============
+*/
+static bool 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 false;
+ }
+ //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 false;
+ }
+ 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 false;
+ }
+ 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 true;
+}
+
+/*
+===============
+Parse_ExpandDefineIntoSource
+===============
+*/
+static bool Parse_ExpandDefineIntoSource(source_t *source, token_t *deftoken, define_t *define)
+{
+ token_t *firsttoken, *lasttoken;
+
+ if (!Parse_ExpandDefine(source, deftoken, define, &firsttoken, &lasttoken)) return false;
+
+ if (firsttoken && lasttoken)
+ {
+ lasttoken->next = source->tokens;
+ source->tokens = firsttoken;
+ return true;
+ }
+ return false;
+}
+
+/*
+===============
+Parse_ConvertPath
+===============
+*/
+static void Parse_ConvertPath(char *path)
+{
+ char *ptr;
+
+ //remove double path seperators
+ for (ptr = path; *ptr;)
+ {
+ if ((*ptr == '\\' || *ptr == '/') &&
+ (*(ptr+1) == '\\' || *(ptr+1) == '/'))
+ {
+ memmove(ptr, ptr+1, strlen(ptr));
+ }
+ else
+ {
+ ptr++;
+ }
+ }
+ //set OS dependent path seperators
+ for (ptr = path; *ptr;)
+ {
+ if (*ptr == '/' || *ptr == '\\') *ptr = PATH_SEP;
+ ptr++;
+ }
+}
+
+/*
+===============
+Parse_ReadLine
+
+reads a token from the current line, continues reading on the next
+line only if a backslash '\' is encountered.
+===============
+*/
+static bool Parse_ReadLine(source_t *source, token_t *token)
+{
+ int crossline;
+
+ crossline = 0;
+ do
+ {
+ if (!Parse_ReadSourceToken(source, token)) return false;
+
+ if (token->linescrossed > crossline)
+ {
+ Parse_UnreadSourceToken(source, token);
+ return false;
+ }
+ crossline = 1;
+ } while(!strcmp(token->string, "\\"));
+ return true;
+}
+
+/*
+===============
+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 bool 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 false;
+}
+
+#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 bool 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 = false;
+ //
+ 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 = true;
+ 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 = false;
+ // 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 = false;
+ 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 = false;
+ break;
+ }
+ case P_QUESTIONMARK:
+ {
+ if (gotquestmarkvalue)
+ {
+ Parse_SourceError(source, "? after ? in #if/#elif");
+ error = 1;
+ break;
+ }
+ questmarkintvalue = v1->intvalue;
+ questmarkfloatvalue = v1->floatvalue;
+ gotquestmarkvalue = true;
+ break;
+ }
+ }
+ if (error) break;
+ //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 true;
+ if (intvalue) *intvalue = 0;
+ if (floatvalue) *floatvalue = 0;
+ return false;
+}
+
+/*
+===============
+Parse_Evaluate
+===============
+*/
+static bool 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 = false;
+
+ if (intvalue) *intvalue = 0;
+ if (floatvalue) *floatvalue = 0;
+ //
+ if (!Parse_ReadLine(source, &token))
+ {
+ Parse_SourceError(source, "no value after #if/#elif");
+ return false;
+ }
+ firsttoken = NULL;
+ lasttoken = NULL;
+ do
+ {
+ //if the token is a name
+ if (token.type == TT_NAME)
+ {
+ if (defined)
+ {
+ defined = false;
+ t = Parse_CopyToken(&token);
+ t->next = NULL;
+ if (lasttoken) lasttoken->next = t;
+ else firsttoken = t;
+ lasttoken = t;
+ }
+ else if (!strcmp(token.string, "defined"))
+ {
+ defined = true;
+ 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 false;
+ }
+ if (!Parse_ExpandDefineIntoSource(source, &token, define)) return false;
+ }
+ }
+ //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 false;
+ }
+ } while(Parse_ReadLine(source, &token));
+ //
+ if (!Parse_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return false;
+ //
+ for (t = firsttoken; t; t = nexttoken)
+ {
+ nexttoken = t->next;
+ Parse_FreeToken(t);
+ }
+ //
+ return true;
+}
+
+/*
+===============
+Parse_DollarEvaluate
+===============
+*/
+static bool Parse_DollarEvaluate(source_t *source, signed long int *intvalue,
+ double *floatvalue, int integer)
+{
+ int indent, defined = false;
+ 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 false;
+ }
+ if (!Parse_ReadSourceToken(source, &token))
+ {
+ Parse_SourceError(source, "nothing to evaluate");
+ return false;
+ }
+ indent = 1;
+ firsttoken = NULL;
+ lasttoken = NULL;
+ do
+ {
+ //if the token is a name
+ if (token.type == TT_NAME)
+ {
+ if (defined)
+ {
+ defined = false;
+ t = Parse_CopyToken(&token);
+ t->next = NULL;
+ if (lasttoken) lasttoken->next = t;
+ else firsttoken = t;
+ lasttoken = t;
+ }
+ else if (!strcmp(token.string, "defined"))
+ {
+ defined = true;
+ 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 false;
+ }
+ if (!Parse_ExpandDefineIntoSource(source, &token, define)) return false;
+ }
+ }
+ //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 false;
+ }
+ } while(Parse_ReadSourceToken(source, &token));
+ //
+ if (!Parse_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return false;
+ //
+ for (t = firsttoken; t; t = nexttoken)
+ {
+ nexttoken = t->next;
+ Parse_FreeToken(t);
+ }
+ //
+ return true;
+}
+
+/*
+===============
+Parse_Directive_include
+===============
+*/
+static bool Parse_Directive_include(source_t *source)
+{
+ script_t *script;
+ token_t token;
+ char path[MAX_QPATH];
+
+ if (source->skip > 0) return true;
+ //
+ if (!Parse_ReadSourceToken(source, &token))
+ {
+ Parse_SourceError(source, "#include without file name");
+ return false;
+ }
+ if (token.linescrossed > 0)
+ {
+ Parse_SourceError(source, "#include without file name");
+ return false;
+ }
+ if (token.type == TT_STRING)
+ {
+ Parse_StripDoubleQuotes(token.string);
+ Parse_ConvertPath(token.string);
+ script = Parse_LoadScriptFile(token.string);
+ if (!script)
+ {
+ strcpy(path, source->includepath);
+ strcat(path, token.string);
+ script = Parse_LoadScriptFile(path);
+ }
+ }
+ else if (token.type == TT_PUNCTUATION && *token.string == '<')
+ {
+ strcpy(path, source->includepath);
+ while(Parse_ReadSourceToken(source, &token))
+ {
+ if (token.linescrossed > 0)
+ {
+ Parse_UnreadSourceToken(source, &token);
+ break;
+ }
+ if (token.type == TT_PUNCTUATION && *token.string == '>') break;
+ strncat(path, token.string, MAX_QPATH - 1);
+ }
+ if (*token.string != '>')
+ {
+ Parse_SourceWarning(source, "#include missing trailing >");
+ }
+ if (!strlen(path))
+ {
+ Parse_SourceError(source, "#include without file name between < >");
+ return false;
+ }
+ Parse_ConvertPath(path);
+ script = Parse_LoadScriptFile(path);
+ }
+ else
+ {
+ Parse_SourceError(source, "#include without file name");
+ return false;
+ }
+ if (!script)
+ {
+ Parse_SourceError(source, "file %s not found", path);
+ return false;
+ }
+ Parse_PushScript(source, script);
+ return true;
+}
+
+/*
+===============
+Parse_WhiteSpaceBeforeToken
+===============
+*/
+static bool 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 bool Parse_Directive_undef(source_t *source)
+{
+ token_t token;
+ define_t *define, *lastdefine;
+ int hash;
+
+ if (source->skip > 0) return true;
+ //
+ if (!Parse_ReadLine(source, &token))
+ {
+ Parse_SourceError(source, "undef without name");
+ return false;
+ }
+ if (token.type != TT_NAME)
+ {
+ Parse_UnreadSourceToken(source, &token);
+ Parse_SourceError(source, "expected name, found %s", token.string);
+ return false;
+ }
+
+ 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 true;
+}
+
+/*
+===============
+Parse_Directive_elif
+===============
+*/
+static bool 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 false;
+ }
+ if (!Parse_Evaluate(source, &value, NULL, true)) return false;
+ skip = (value == 0);
+ Parse_PushIndent(source, INDENT_ELIF, skip);
+ return true;
+}
+
+/*
+===============
+Parse_Directive_if
+===============
+*/
+static bool Parse_Directive_if(source_t *source)
+{
+ signed long int value;
+ int skip;
+
+ if (!Parse_Evaluate(source, &value, NULL, true)) return false;
+ skip = (value == 0);
+ Parse_PushIndent(source, INDENT_IF, skip);
+ return true;
+}
+
+/*
+===============
+Parse_Directive_line
+===============
+*/
+static bool Parse_Directive_line(source_t *source)
+{
+ Parse_SourceError(source, "#line directive not supported");
+ return false;
+}
+
+/*
+===============
+Parse_Directive_error
+===============
+*/
+static bool 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 false;
+}
+
+/*
+===============
+Parse_Directive_pragma
+===============
+*/
+static bool Parse_Directive_pragma(source_t *source)
+{
+ token_t token;
+
+ Parse_SourceWarning(source, "#pragma directive not supported");
+ while(Parse_ReadLine(source, &token)) ;
+ return true;
+}
+
+/*
+===============
+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 bool Parse_Directive_eval(source_t *source)
+{
+ signed long int value;
+ token_t token;
+
+ if (!Parse_Evaluate(source, &value, NULL, true)) return false;
+ //
+ 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, "%ld", labs(value));
+ token.type = TT_NUMBER;
+ token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL;
+ Parse_UnreadSourceToken(source, &token);
+ if (value < 0) Parse_UnreadSignToken(source);
+ return true;
+}
+
+/*
+===============
+Parse_Directive_evalfloat
+===============
+*/
+static bool Parse_Directive_evalfloat(source_t *source)
+{
+ double value;
+ token_t token;
+
+ if (!Parse_Evaluate(source, NULL, &value, false)) return false;
+ 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 true;
+}
+
+/*
+===============
+Parse_DollarDirective_evalint
+===============
+*/
+static bool Parse_DollarDirective_evalint(source_t *source)
+{
+ signed long int value;
+ token_t token;
+
+ if (!Parse_DollarEvaluate(source, &value, NULL, true)) return false;
+ //
+ 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, "%ld", labs(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 true;
+}
+
+/*
+===============
+Parse_DollarDirective_evalfloat
+===============
+*/
+static bool Parse_DollarDirective_evalfloat(source_t *source)
+{
+ double value;
+ token_t token;
+
+ if (!Parse_DollarEvaluate(source, NULL, &value, false)) return false;
+ 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 true;
+}
+
+/*
+===============
+Parse_ReadDollarDirective
+===============
+*/
+directive_t dollardirectives[20] =
+{
+ {"evalint", Parse_DollarDirective_evalint},
+ {"evalfloat", Parse_DollarDirective_evalfloat},
+ {NULL, NULL}
+};
+
+static bool 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 false;
+ }
+ //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 false;
+ }
+ //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 false;
+}
+
+/*
+===============
+Parse_Directive_if_def
+===============
+*/
+static bool 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 false;
+ }
+ if (token.type != TT_NAME)
+ {
+ Parse_UnreadSourceToken(source, &token);
+ Parse_SourceError(source, "expected name after #ifdef, found %s", token.string);
+ return false;
+ }
+ d = Parse_FindHashedDefine(source->definehash, token.string);
+ skip = (type == INDENT_IFDEF) == (d == NULL);
+ Parse_PushIndent(source, type, skip);
+ return true;
+}
+
+/*
+===============
+Parse_Directive_ifdef
+===============
+*/
+static bool Parse_Directive_ifdef(source_t *source)
+{
+ return Parse_Directive_if_def(source, INDENT_IFDEF);
+}
+
+/*
+===============
+Parse_Directive_ifndef
+===============
+*/
+static bool Parse_Directive_ifndef(source_t *source)
+{
+ return Parse_Directive_if_def(source, INDENT_IFNDEF);
+}
+
+/*
+===============
+Parse_Directive_else
+===============
+*/
+static bool Parse_Directive_else(source_t *source)
+{
+ int type, skip;
+
+ Parse_PopIndent(source, &type, &skip);
+ if (!type)
+ {
+ Parse_SourceError(source, "misplaced #else");
+ return false;
+ }
+ if (type == INDENT_ELSE)
+ {
+ Parse_SourceError(source, "#else after #else");
+ return false;
+ }
+ Parse_PushIndent(source, INDENT_ELSE, !skip);
+ return true;
+}
+
+/*
+===============
+Parse_Directive_endif
+===============
+*/
+static bool Parse_Directive_endif(source_t *source)
+{
+ int type, skip;
+
+ Parse_PopIndent(source, &type, &skip);
+ if (!type)
+ {
+ Parse_SourceError(source, "misplaced #endif");
+ return false;
+ }
+ return true;
+}
+
+/*
+===============
+Parse_CheckTokenString
+===============
+*/
+static bool Parse_CheckTokenString(source_t *source, const char *string)
+{
+ token_t tok;
+
+ if (!Parse_ReadToken(source, &tok)) return false;
+ //if the token is available
+ if (!strcmp(tok.string, string)) return true;
+ //
+ Parse_UnreadSourceToken(source, &tok);
+ return false;
+}
+
+/*
+===============
+Parse_Directive_define
+===============
+*/
+static bool Parse_Directive_define(source_t *source)
+{
+ token_t token, *t, *last;
+ define_t *define;
+
+ if (source->skip > 0) return true;
+ //
+ if (!Parse_ReadLine(source, &token))
+ {
+ Parse_SourceError(source, "#define without name");
+ return false;
+ }
+ if (token.type != TT_NAME)
+ {
+ Parse_UnreadSourceToken(source, &token);
+ Parse_SourceError(source, "expected name after #define, found %s", token.string);
+ return false;
+ }
+ //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 false;
+ }
+ 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 false;
+ //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);
+ ::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 true;
+ //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 false;
+ }
+ //if it isn't a name
+ if (token.type != TT_NAME)
+ {
+ Parse_SourceError(source, "invalid define parameter");
+ return false;
+ }
+ //
+ if (Parse_FindDefineParm(define, token.string) >= 0)
+ {
+ Parse_SourceError(source, "two the same define parameters");
+ return false;
+ }
+ //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 false;
+ }
+ //
+ if (!strcmp(token.string, ")")) break;
+ //then it must be a comma
+ if (strcmp(token.string, ","))
+ {
+ Parse_SourceError(source, "define not terminated");
+ return false;
+ }
+ }
+ }
+ if (!Parse_ReadLine(source, &token)) return true;
+ }
+ //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 false;
+ }
+ }
+ return true;
+}
+
+/*
+===============
+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 bool 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 false;
+ }
+ //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 false;
+ }
+ //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 false;
+}
+
+/*
+===============
+Parse_UnreadToken
+===============
+*/
+static void Parse_UnreadToken(source_t *source, token_t *token)
+{
+ Parse_UnreadSourceToken(source, token);
+}
+
+/*
+===============
+Parse_ReadEnumeration
+
+It is assumed that the 'enum' token has already been consumed
+This is fairly basic: it doesn't catch some fairly obvious errors like nested
+enums, and enumerated names conflict with #define parameters
+===============
+*/
+static bool Parse_ReadEnumeration( source_t *source )
+{
+ token_t newtoken;
+ int value;
+
+ if( !Parse_ReadToken( source, &newtoken ) )
+ return false;
+
+ if( newtoken.type != TT_PUNCTUATION || newtoken.subtype != P_BRACEOPEN )
+ {
+ Parse_SourceError( source, "Found %s when expecting {\n",
+ newtoken.string );
+ return false;
+ }
+
+ for( value = 0;; value++ )
+ {
+ token_t name;
+
+ // read the name
+ if( !Parse_ReadToken( source, &name ) )
+ break;
+
+ // it's ok for the enum to end immediately
+ if( name.type == TT_PUNCTUATION && name.subtype == P_BRACECLOSE )
+ {
+ if( !Parse_ReadToken( source, &name ) )
+ break;
+
+ // ignore trailing semicolon
+ if( name.type != TT_PUNCTUATION || name.subtype != P_SEMICOLON )
+ Parse_UnreadToken( source, &name );
+
+ return true;
+ }
+
+ // ... but not for it to do anything else
+ if( name.type != TT_NAME )
+ {
+ Parse_SourceError( source, "Found %s when expecting identifier\n",
+ name.string );
+ return false;
+ }
+
+ if( !Parse_ReadToken( source, &newtoken ) )
+ break;
+
+ if( newtoken.type != TT_PUNCTUATION )
+ {
+ Parse_SourceError( source, "Found %s when expecting , or = or }\n",
+ newtoken.string );
+ return false;
+ }
+
+ if( newtoken.subtype == P_ASSIGN )
+ {
+ int neg = 1;
+
+ if( !Parse_ReadToken( source, &newtoken ) )
+ break;
+
+ // Parse_ReadToken doesn't seem to read negative numbers, so we do it
+ // ourselves
+ if( newtoken.type == TT_PUNCTUATION && newtoken.subtype == P_SUB )
+ {
+ neg = -1;
+
+ // the next token should be the number
+ if( !Parse_ReadToken( source, &newtoken ) )
+ break;
+ }
+
+ if( newtoken.type != TT_NUMBER || !( newtoken.subtype & TT_INTEGER ) )
+ {
+ Parse_SourceError( source, "Found %s when expecting integer\n",
+ newtoken.string );
+ return false;
+ }
+
+ // this is somewhat silly, but cheap to check
+ if( neg == -1 && ( newtoken.subtype & TT_UNSIGNED ) )
+ {
+ Parse_SourceWarning( source, "Value in enumeration is negative and "
+ "unsigned\n" );
+ }
+
+ // set the new define value
+ value = newtoken.intvalue * neg;
+
+ if( !Parse_ReadToken( source, &newtoken ) )
+ break;
+ }
+
+ if( newtoken.type != TT_PUNCTUATION || ( newtoken.subtype != P_COMMA &&
+ newtoken.subtype != P_BRACECLOSE ) )
+ {
+ Parse_SourceError( source, "Found %s when expecting , or }\n",
+ newtoken.string );
+ return false;
+ }
+
+ if( !Parse_AddDefineToSourceFromString( source, va( "%s %d\n", name.string,
+ value ) ) )
+ {
+ Parse_SourceWarning( source, "Couldn't add define to source: %s = %d\n",
+ name.string, value );
+ return false;
+ }
+
+ if( newtoken.subtype == P_BRACECLOSE )
+ {
+ if( !Parse_ReadToken( source, &name ) )
+ break;
+
+ // ignore trailing semicolon
+ if( name.type != TT_PUNCTUATION || name.subtype != P_SEMICOLON )
+ Parse_UnreadToken( source, &name );
+
+ return true;
+ }
+ }
+
+ // got here if a ReadToken returned false
+ return false;
+}
+
+/*
+===============
+Parse_ReadToken
+===============
+*/
+static bool Parse_ReadToken(source_t *source, token_t *token)
+{
+ define_t *define;
+
+ while(1)
+ {
+ if (!Parse_ReadSourceToken(source, token)) return false;
+ //check for precompiler directives
+ if (token->type == TT_PUNCTUATION && *token->string == '#')
+ {
+ {
+ //read the precompiler directive
+ if (!Parse_ReadDirective(source)) return false;
+ continue;
+ }
+ }
+ if (token->type == TT_PUNCTUATION && *token->string == '$')
+ {
+ {
+ //read the precompiler directive
+ if (!Parse_ReadDollarDirective(source)) return false;
+ continue;
+ }
+ }
+ if( token->type == TT_NAME && !Q_stricmp( token->string, "enum" ) )
+ {
+ if( !Parse_ReadEnumeration( source ) )
+ return false;
+ 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 false;
+ }
+ 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 false;
+ continue;
+ }
+ }
+ //copy token for unreading
+ ::memcpy(&source->token, token, sizeof(token_t));
+ //found a token
+ return true;
+ }
+}
+
+/*
+===============
+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
+ ::memset(&src, 0, sizeof(source_t));
+ strncpy(src.filename, "*extern", MAX_QPATH);
+ src.scriptstack = script;
+ src.definehash = (define_t**)Z_Malloc(DEFINEHASHSIZE * sizeof(define_t *));
+ ::memset( src.definehash, 0, DEFINEHASHSIZE * sizeof(define_t *));
+ //create a define from the source
+ res = Parse_Directive_define(&src);
+ //free any tokens if left
+ for (t = src.tokens; t; t = src.tokens)
+ {
+ src.tokens = src.tokens->next;
+ Parse_FreeToken(t);
+ }
+ def = NULL;
+ for (i = 0; i < DEFINEHASHSIZE; i++)
+ {
+ if (src.definehash[i])
+ {
+ def = src.definehash[i];
+ break;
+ }
+ }
+ //
+ Z_Free(src.definehash);
+ //
+ Parse_FreeScript(script);
+ //if the define was created succesfully
+ if (res > 0) return def;
+ //free the define is created
+ if (src.defines) Parse_FreeDefine(def);
+ //
+ return NULL;
+}
+
+/*
+===============
+Parse_AddDefineToSourceFromString
+===============
+*/
+static bool Parse_AddDefineToSourceFromString( source_t *source, const char *string )
+{
+ Parse_PushScript( source, Parse_LoadScriptMemory(string, strlen(string), "*extern") );
+ return Parse_Directive_define( source );
+}
+
+/*
+===============
+Parse_AddGlobalDefine
+
+add a globals define that will be added to all opened sources
+===============
+*/
+bool Parse_AddGlobalDefine(char *string)
+{
+ define_t *define;
+
+ define = Parse_DefineFromString(string);
+ if (!define) return false;
+ define->next = globaldefines;
+ globaldefines = define;
+ return true;
+}
+
+/*
+===============
+Parse_CopyDefine
+===============
+*/
+static define_t *Parse_CopyDefine(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(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));
+ ::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 = (define_t**)Z_Malloc(DEFINEHASHSIZE * sizeof(define_t *));
+ ::memset( source->definehash, 0, DEFINEHASHSIZE * sizeof(define_t *));
+ Parse_AddGlobalDefinesToSource(source);
+ return source;
+}
+
+/*
+===============
+Parse_FreeSource
+===============
+*/
+static void Parse_FreeSource(source_t *source)
+{
+ script_t *script;
+ token_t *token;
+ define_t *define;
+ indent_t *indent;
+ int i;
+
+ //Parse_PrintDefineHashTable(source->definehash);
+ //free all the scripts
+ while(source->scriptstack)
+ {
+ script = source->scriptstack;
+ source->scriptstack = source->scriptstack->next;
+ Parse_FreeScript(script);
+ }
+ //free all the tokens
+ while(source->tokens)
+ {
+ token = source->tokens;
+ source->tokens = source->tokens->next;
+ Parse_FreeToken(token);
+ }
+ for (i = 0; i < DEFINEHASHSIZE; i++)
+ {
+ while(source->definehash[i])
+ {
+ define = source->definehash[i];
+ source->definehash[i] = source->definehash[i]->hashnext;
+ Parse_FreeDefine(define);
+ }
+ }
+ //free all indents
+ while(source->indentstack)
+ {
+ indent = source->indentstack;
+ source->indentstack = source->indentstack->next;
+ Z_Free(indent);
+ }
+ //
+ if (source->definehash) Z_Free(source->definehash);
+ //free the source itself
+ Z_Free(source);
+}
+
+#define MAX_SOURCEFILES 64
+
+source_t *sourceFiles[MAX_SOURCEFILES];
+
+/*
+===============
+Parse_LoadSourceHandle
+===============
+*/
+int Parse_LoadSourceHandle(const char *filename)
+{
+ source_t *source;
+ int i;
+
+ for (i = 1; i < MAX_SOURCEFILES; i++)
+ {
+ if (!sourceFiles[i])
+ break;
+ }
+ if (i >= MAX_SOURCEFILES)
+ return 0;
+ source = Parse_LoadSourceFile(filename);
+ if (!source)
+ return 0;
+ sourceFiles[i] = source;
+ return i;
+}
+
+/*
+===============
+Parse_FreeSourceHandle
+===============
+*/
+bool Parse_FreeSourceHandle(int handle)
+{
+ if (handle < 1 || handle >= MAX_SOURCEFILES)
+ return false;
+ if (!sourceFiles[handle])
+ return false;
+
+ Parse_FreeSource(sourceFiles[handle]);
+ sourceFiles[handle] = NULL;
+ return true;
+}
+
+/*
+===============
+Parse_ReadTokenHandle
+===============
+*/
+bool Parse_ReadTokenHandle(int handle, pc_token_t *pc_token)
+{
+ token_t token;
+ bool ret;
+
+ if (handle < 1 || handle >= MAX_SOURCEFILES)
+ return false;
+ if (!sourceFiles[handle])
+ return false;
+
+ 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
+===============
+*/
+bool Parse_SourceFileAndLine(int handle, char *filename, int *line)
+{
+ if (handle < 1 || handle >= MAX_SOURCEFILES)
+ return false;
+ if (!sourceFiles[handle])
+ return false;
+
+ strcpy(filename, sourceFiles[handle]->filename);
+ if (sourceFiles[handle]->scriptstack)
+ *line = sourceFiles[handle]->scriptstack->line;
+ else
+ *line = 0;
+ return true;
+}
diff --git a/src/qcommon/puff.cpp b/src/qcommon/puff.cpp
new file mode 100644
index 0000000..6874b28
--- /dev/null
+++ b/src/qcommon/puff.cpp
@@ -0,0 +1,759 @@
+/*
+ * This is a modified version of Mark Adlers work,
+ * see below for the original copyright.
+ * 2006 - Joerg Dietrich <dietrich_joerg@gmx.de>
+ */
+
+/*
+ * puff.c
+ * Copyright (C) 2002-2004 Mark Adler
+ * For conditions of distribution and use, see copyright notice in puff.h
+ * version 1.8, 9 Jan 2004
+ *
+ * puff.c is a simple inflate written to be an unambiguous way to specify the
+ * deflate format. It is not written for speed but rather simplicity. As a
+ * side benefit, this code might actually be useful when small code is more
+ * important than speed, such as bootstrap applications. For typical deflate
+ * data, zlib's inflate() is about four times as fast as puff(). zlib's
+ * inflate compiles to around 20K on my machine, whereas puff.c compiles to
+ * around 4K on my machine (a PowerPC using GNU cc). If the faster decode()
+ * function here is used, then puff() is only twice as slow as zlib's
+ * inflate().
+ *
+ * All dynamically allocated memory comes from the stack. The stack required
+ * is less than 2K bytes. This code is compatible with 16-bit int's and
+ * assumes that long's are at least 32 bits. puff.c uses the short data type,
+ * assumed to be 16 bits, for arrays in order to to conserve memory. The code
+ * works whether integers are stored big endian or little endian.
+ *
+ * In the comments below are "Format notes" that describe the inflate process
+ * and document some of the less obvious aspects of the format. This source
+ * code is meant to supplement RFC 1951, which formally describes the deflate
+ * format:
+ *
+ * http://www.zlib.org/rfc-deflate.html
+ */
+
+/*
+ * Change history:
+ *
+ * 1.0 10 Feb 2002 - First version
+ * 1.1 17 Feb 2002 - Clarifications of some comments and notes
+ * - Update puff() dest and source pointers on negative
+ * errors to facilitate debugging deflators
+ * - Remove longest from struct huffman -- not needed
+ * - Simplify offs[] index in construct()
+ * - Add input size and checking, using longjmp() to
+ * maintain easy readability
+ * - Use short data type for large arrays
+ * - Use pointers instead of long to specify source and
+ * destination sizes to avoid arbitrary 4 GB limits
+ * 1.2 17 Mar 2002 - Add faster version of decode(), doubles speed (!),
+ * but leave simple version for readabilty
+ * - Make sure invalid distances detected if pointers
+ * are 16 bits
+ * - Fix fixed codes table error
+ * - Provide a scanning mode for determining size of
+ * uncompressed data
+ * 1.3 20 Mar 2002 - Go back to lengths for puff() parameters [Jean-loup]
+ * - Add a puff.h file for the interface
+ * - Add braces in puff() for else do [Jean-loup]
+ * - Use indexes instead of pointers for readability
+ * 1.4 31 Mar 2002 - Simplify construct() code set check
+ * - Fix some comments
+ * - Add FIXLCODES #define
+ * 1.5 6 Apr 2002 - Minor comment fixes
+ * 1.6 7 Aug 2002 - Minor format changes
+ * 1.7 3 Mar 2003 - Added test code for distribution
+ * - Added zlib-like license
+ * 1.8 9 Jan 2004 - Added some comments on no distance codes case
+ */
+
+#include "puff.h" /* prototype for puff() */
+
+#include <setjmp.h> /* for setjmp(), longjmp(), and jmp_buf */
+
+#define local static /* for local function definitions */
+
+/*
+ * Maximums for allocations and loops. It is not useful to change these --
+ * they are fixed by the deflate format.
+ */
+#define MAXBITS 15 /* maximum bits in a code */
+#define MAXLCODES 286 /* maximum number of literal/length codes */
+#define MAXDCODES 30 /* maximum number of distance codes */
+#define MAXCODES (MAXLCODES+MAXDCODES) /* maximum codes lengths to read */
+#define FIXLCODES 288 /* number of fixed literal/length codes */
+
+/* input and output state */
+struct state {
+ /* output state */
+ uint8_t *out; /* output buffer */
+ uint32_t outlen; /* available space at out */
+ uint32_t outcnt; /* bytes written to out so far */
+
+ /* input state */
+ uint8_t *in; /* input buffer */
+ uint32_t inlen; /* available input at in */
+ uint32_t incnt; /* bytes read so far */
+ int32_t bitbuf; /* bit buffer */
+ int32_t bitcnt; /* number of bits in bit buffer */
+
+ /* input limit error return state for bits() and decode() */
+ jmp_buf env;
+};
+
+/*
+ * Return need bits from the input stream. This always leaves less than
+ * eight bits in the buffer. bits() works properly for need == 0.
+ *
+ * Format notes:
+ *
+ * - Bits are stored in bytes from the least significant bit to the most
+ * significant bit. Therefore bits are dropped from the bottom of the bit
+ * buffer, using shift right, and new bytes are appended to the top of the
+ * bit buffer, using shift left.
+ */
+local int32_t bits(struct state *s, int32_t need)
+{
+ int32_t val; /* bit accumulator (can use up to 20 bits) */
+
+ /* load at least need bits into val */
+ val = s->bitbuf;
+ while (s->bitcnt < need) {
+ if (s->incnt == s->inlen) longjmp(s->env, 1); /* out of input */
+ val |= (int32_t)(s->in[s->incnt++]) << s->bitcnt; /* load eight bits */
+ s->bitcnt += 8;
+ }
+
+ /* drop need bits and update buffer, always zero to seven bits left */
+ s->bitbuf = (int32_t)(val >> need);
+ s->bitcnt -= need;
+
+ /* return need bits, zeroing the bits above that */
+ return (int32_t)(val & ((1L << need) - 1));
+}
+
+/*
+ * Process a stored block.
+ *
+ * Format notes:
+ *
+ * - After the two-bit stored block type (00), the stored block length and
+ * stored bytes are byte-aligned for fast copying. Therefore any leftover
+ * bits in the byte that has the last bit of the type, as many as seven, are
+ * discarded. The value of the discarded bits are not defined and should not
+ * be checked against any expectation.
+ *
+ * - The second inverted copy of the stored block length does not have to be
+ * checked, but it's probably a good idea to do so anyway.
+ *
+ * - A stored block can have zero length. This is sometimes used to byte-align
+ * subsets of the compressed data for random access or partial recovery.
+ */
+local int32_t stored(struct state *s)
+{
+ uint32_t len; /* length of stored block */
+
+ /* discard leftover bits from current byte (assumes s->bitcnt < 8) */
+ s->bitbuf = 0;
+ s->bitcnt = 0;
+
+ /* get length and check against its one's complement */
+ if (s->incnt + 4 > s->inlen) return 2; /* not enough input */
+ len = s->in[s->incnt++];
+ len |= s->in[s->incnt++] << 8;
+ if (s->in[s->incnt++] != (~len & 0xff) ||
+ s->in[s->incnt++] != ((~len >> 8) & 0xff))
+ return -2; /* didn't match complement! */
+
+ /* copy len bytes from in to out */
+ if (s->incnt + len > s->inlen) return 2; /* not enough input */
+ if (s->out != NULL) {
+ if (s->outcnt + len > s->outlen)
+ return 1; /* not enough output space */
+ while (len--)
+ s->out[s->outcnt++] = s->in[s->incnt++];
+ }
+ else { /* just scanning */
+ s->outcnt += len;
+ s->incnt += len;
+ }
+
+ /* done with a valid stored block */
+ return 0;
+}
+
+/*
+ * Huffman code decoding tables. count[1..MAXBITS] is the number of symbols of
+ * each length, which for a canonical code are stepped through in order.
+ * symbol[] are the symbol values in canonical order, where the number of
+ * entries is the sum of the counts in count[]. The decoding process can be
+ * seen in the function decode() below.
+ */
+struct huffman {
+ int16_t *count; /* number of symbols of each length */
+ int16_t *symbol; /* canonically ordered symbols */
+};
+
+/*
+ * Decode a code from the stream s using huffman table h. Return the symbol or
+ * a negative value if there is an error. If all of the lengths are zero, i.e.
+ * an empty code, or if the code is incomplete and an invalid code is received,
+ * then -9 is returned after reading MAXBITS bits.
+ *
+ * Format notes:
+ *
+ * - The codes as stored in the compressed data are bit-reversed relative to
+ * a simple integer ordering of codes of the same lengths. Hence below the
+ * bits are pulled from the compressed data one at a time and used to
+ * build the code value reversed from what is in the stream in order to
+ * permit simple integer comparisons for decoding. A table-based decoding
+ * scheme (as used in zlib) does not need to do this reversal.
+ *
+ * - The first code for the shortest length is all zeros. Subsequent codes of
+ * the same length are simply integer increments of the previous code. When
+ * moving up a length, a zero bit is appended to the code. For a complete
+ * code, the last code of the longest length will be all ones.
+ *
+ * - Incomplete codes are handled by this decoder, since they are permitted
+ * in the deflate format. See the format notes for fixed() and dynamic().
+ */
+local int32_t decode(struct state *s, struct huffman *h)
+{
+ int32_t len; /* current number of bits in code */
+ int32_t code; /* len bits being decoded */
+ int32_t first; /* first code of length len */
+ int32_t count; /* number of codes of length len */
+ int32_t index; /* index of first code of length len in symbol table */
+ int32_t bitbuf; /* bits from stream */
+ int32_t left; /* bits left in next or left to process */
+ int16_t *next; /* next number of codes */
+
+ bitbuf = s->bitbuf;
+ left = s->bitcnt;
+ code = first = index = 0;
+ len = 1;
+ next = h->count + 1;
+ while (1) {
+ while (left--) {
+ code |= bitbuf & 1;
+ bitbuf >>= 1;
+ count = *next++;
+ if (code < first + count) { /* if length len, return symbol */
+ s->bitbuf = bitbuf;
+ s->bitcnt = (s->bitcnt - len) & 7;
+ return h->symbol[index + (code - first)];
+ }
+ index += count; /* else update for next length */
+ first += count;
+ first <<= 1;
+ code <<= 1;
+ len++;
+ }
+ left = (MAXBITS+1) - len;
+ if (left == 0) break;
+ if (s->incnt == s->inlen) longjmp(s->env, 1); /* out of input */
+ bitbuf = s->in[s->incnt++];
+ if (left > 8) left = 8;
+ }
+ return -9; /* ran out of codes */
+}
+
+/*
+ * Given the list of code lengths length[0..n-1] representing a canonical
+ * Huffman code for n symbols, construct the tables required to decode those
+ * codes. Those tables are the number of codes of each length, and the symbols
+ * sorted by length, retaining their original order within each length. The
+ * return value is zero for a complete code set, negative for an over-
+ * subscribed code set, and positive for an incomplete code set. The tables
+ * can be used if the return value is zero or positive, but they cannot be used
+ * if the return value is negative. If the return value is zero, it is not
+ * possible for decode() using that table to return an error--any stream of
+ * enough bits will resolve to a symbol. If the return value is positive, then
+ * it is possible for decode() using that table to return an error for received
+ * codes past the end of the incomplete lengths.
+ *
+ * Not used by decode(), but used for error checking, h->count[0] is the number
+ * of the n symbols not in the code. So n - h->count[0] is the number of
+ * codes. This is useful for checking for incomplete codes that have more than
+ * one symbol, which is an error in a dynamic block.
+ *
+ * Assumption: for all i in 0..n-1, 0 <= length[i] <= MAXBITS
+ * This is assured by the construction of the length arrays in dynamic() and
+ * fixed() and is not verified by construct().
+ *
+ * Format notes:
+ *
+ * - Permitted and expected examples of incomplete codes are one of the fixed
+ * codes and any code with a single symbol which in deflate is coded as one
+ * bit instead of zero bits. See the format notes for fixed() and dynamic().
+ *
+ * - Within a given code length, the symbols are kept in ascending order for
+ * the code bits definition.
+ */
+local int32_t construct(struct huffman *h, int16_t *length, int32_t n)
+{
+ int32_t symbol; /* current symbol when stepping through length[] */
+ int32_t len; /* current length when stepping through h->count[] */
+ int32_t left; /* number of possible codes left of current length */
+ int16_t offs[MAXBITS+1]; /* offsets in symbol table for each length */
+
+ /* count number of codes of each length */
+ for (len = 0; len <= MAXBITS; len++)
+ h->count[len] = 0;
+ for (symbol = 0; symbol < n; symbol++)
+ (h->count[length[symbol]])++; /* assumes lengths are within bounds */
+ if (h->count[0] == n) /* no codes! */
+ return 0; /* complete, but decode() will fail */
+
+ /* check for an over-subscribed or incomplete set of lengths */
+ left = 1; /* one possible code of zero length */
+ for (len = 1; len <= MAXBITS; len++) {
+ left <<= 1; /* one more bit, double codes left */
+ left -= h->count[len]; /* deduct count from possible codes */
+ if (left < 0) return left; /* over-subscribed--return negative */
+ } /* left > 0 means incomplete */
+
+ /* generate offsets into symbol table for each length for sorting */
+ offs[1] = 0;
+ for (len = 1; len < MAXBITS; len++)
+ offs[len + 1] = offs[len] + h->count[len];
+
+ /*
+ * put symbols in table sorted by length, by symbol order within each
+ * length
+ */
+ for (symbol = 0; symbol < n; symbol++)
+ if (length[symbol] != 0)
+ h->symbol[offs[length[symbol]]++] = symbol;
+
+ /* return zero for complete set, positive for incomplete set */
+ return left;
+}
+
+/*
+ * Decode literal/length and distance codes until an end-of-block code.
+ *
+ * Format notes:
+ *
+ * - Compressed data that is after the block type if fixed or after the code
+ * description if dynamic is a combination of literals and length/distance
+ * pairs terminated by and end-of-block code. Literals are simply Huffman
+ * coded bytes. A length/distance pair is a coded length followed by a
+ * coded distance to represent a string that occurs earlier in the
+ * uncompressed data that occurs again at the current location.
+ *
+ * - Literals, lengths, and the end-of-block code are combined into a single
+ * code of up to 286 symbols. They are 256 literals (0..255), 29 length
+ * symbols (257..285), and the end-of-block symbol (256).
+ *
+ * - There are 256 possible lengths (3..258), and so 29 symbols are not enough
+ * to represent all of those. Lengths 3..10 and 258 are in fact represented
+ * by just a length symbol. Lengths 11..257 are represented as a symbol and
+ * some number of extra bits that are added as an integer to the base length
+ * of the length symbol. The number of extra bits is determined by the base
+ * length symbol. These are in the static arrays below, lens[] for the base
+ * lengths and lext[] for the corresponding number of extra bits.
+ *
+ * - The reason that 258 gets its own symbol is that the longest length is used
+ * often in highly redundant files. Note that 258 can also be coded as the
+ * base value 227 plus the maximum extra value of 31. While a good deflate
+ * should never do this, it is not an error, and should be decoded properly.
+ *
+ * - If a length is decoded, including its extra bits if any, then it is
+ * followed a distance code. There are up to 30 distance symbols. Again
+ * there are many more possible distances (1..32768), so extra bits are added
+ * to a base value represented by the symbol. The distances 1..4 get their
+ * own symbol, but the rest require extra bits. The base distances and
+ * corresponding number of extra bits are below in the static arrays dist[]
+ * and dext[].
+ *
+ * - Literal bytes are simply written to the output. A length/distance pair is
+ * an instruction to copy previously uncompressed bytes to the output. The
+ * copy is from distance bytes back in the output stream, copying for length
+ * bytes.
+ *
+ * - Distances pointing before the beginning of the output data are not
+ * permitted.
+ *
+ * - Overlapped copies, where the length is greater than the distance, are
+ * allowed and common. For example, a distance of one and a length of 258
+ * simply copies the last byte 258 times. A distance of four and a length of
+ * twelve copies the last four bytes three times. A simple forward copy
+ * ignoring whether the length is greater than the distance or not implements
+ * this correctly. You should not use memcpy() since its behavior is not
+ * defined for overlapped arrays. You should not use memmove() or bcopy()
+ * since though their behavior -is- defined for overlapping arrays, it is
+ * defined to do the wrong thing in this case.
+ */
+local int32_t codes(struct state *s,
+ struct huffman *lencode,
+ struct huffman *distcode)
+{
+ int32_t symbol; /* decoded symbol */
+ int32_t len; /* length for copy */
+ uint32_t dist; /* distance for copy */
+ static const int16_t lens[29] = { /* Size base for length codes 257..285 */
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
+ 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258};
+ static const int16_t lext[29] = { /* Extra bits for length codes 257..285 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
+ 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0};
+ static const int16_t dists[30] = { /* Offset base for distance codes 0..29 */
+ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
+ 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
+ 8193, 12289, 16385, 24577};
+ static const int16_t dext[30] = { /* Extra bits for distance codes 0..29 */
+ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
+ 7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
+ 12, 12, 13, 13};
+
+ /* decode literals and length/distance pairs */
+ do {
+ symbol = decode(s, lencode);
+ if (symbol < 0) return symbol; /* invalid symbol */
+ if (symbol < 256) { /* literal: symbol is the byte */
+ /* write out the literal */
+ if (s->out != NULL) {
+ if (s->outcnt == s->outlen) return 1;
+ s->out[s->outcnt] = symbol;
+ }
+ s->outcnt++;
+ }
+ else if (symbol > 256) { /* length */
+ /* get and compute length */
+ symbol -= 257;
+ if (symbol >= 29) return -9; /* invalid fixed code */
+ len = lens[symbol] + bits(s, lext[symbol]);
+
+ /* get and check distance */
+ symbol = decode(s, distcode);
+ if (symbol < 0) return symbol; /* invalid symbol */
+ dist = dists[symbol] + bits(s, dext[symbol]);
+ if (dist > s->outcnt)
+ return -10; /* distance too far back */
+
+ /* copy length bytes from distance bytes back */
+ if (s->out != NULL) {
+ if (s->outcnt + len > s->outlen) return 1;
+ while (len--) {
+ s->out[s->outcnt] = s->out[s->outcnt - dist];
+ s->outcnt++;
+ }
+ }
+ else
+ s->outcnt += len;
+ }
+ } while (symbol != 256); /* end of block symbol */
+
+ /* done with a valid fixed or dynamic block */
+ return 0;
+}
+
+/*
+ * Process a fixed codes block.
+ *
+ * Format notes:
+ *
+ * - This block type can be useful for compressing small amounts of data for
+ * which the size of the code descriptions in a dynamic block exceeds the
+ * benefit of custom codes for that block. For fixed codes, no bits are
+ * spent on code descriptions. Instead the code lengths for literal/length
+ * codes and distance codes are fixed. The specific lengths for each symbol
+ * can be seen in the "for" loops below.
+ *
+ * - The literal/length code is complete, but has two symbols that are invalid
+ * and should result in an error if received. This cannot be implemented
+ * simply as an incomplete code since those two symbols are in the "middle"
+ * of the code. They are eight bits long and the longest literal/length\
+ * code is nine bits. Therefore the code must be constructed with those
+ * symbols, and the invalid symbols must be detected after decoding.
+ *
+ * - The fixed distance codes also have two invalid symbols that should result
+ * in an error if received. Since all of the distance codes are the same
+ * length, this can be implemented as an incomplete code. Then the invalid
+ * codes are detected while decoding.
+ */
+local int32_t fixed(struct state *s)
+{
+ static int32_t virgin = 1;
+ static int16_t lencnt[MAXBITS+1], lensym[FIXLCODES];
+ static int16_t distcnt[MAXBITS+1], distsym[MAXDCODES];
+ static struct huffman lencode = {lencnt, lensym};
+ static struct huffman distcode = {distcnt, distsym};
+
+ /* build fixed huffman tables if first call (may not be thread safe) */
+ if (virgin) {
+ int32_t symbol;
+ int16_t lengths[FIXLCODES];
+
+ /* literal/length table */
+ for (symbol = 0; symbol < 144; symbol++)
+ lengths[symbol] = 8;
+ for (; symbol < 256; symbol++)
+ lengths[symbol] = 9;
+ for (; symbol < 280; symbol++)
+ lengths[symbol] = 7;
+ for (; symbol < FIXLCODES; symbol++)
+ lengths[symbol] = 8;
+ construct(&lencode, lengths, FIXLCODES);
+
+ /* distance table */
+ for (symbol = 0; symbol < MAXDCODES; symbol++)
+ lengths[symbol] = 5;
+ construct(&distcode, lengths, MAXDCODES);
+
+ /* do this just once */
+ virgin = 0;
+ }
+
+ /* decode data until end-of-block code */
+ return codes(s, &lencode, &distcode);
+}
+
+/*
+ * Process a dynamic codes block.
+ *
+ * Format notes:
+ *
+ * - A dynamic block starts with a description of the literal/length and
+ * distance codes for that block. New dynamic blocks allow the compressor to
+ * rapidly adapt to changing data with new codes optimized for that data.
+ *
+ * - The codes used by the deflate format are "canonical", which means that
+ * the actual bits of the codes are generated in an unambiguous way simply
+ * from the number of bits in each code. Therefore the code descriptions
+ * are simply a list of code lengths for each symbol.
+ *
+ * - The code lengths are stored in order for the symbols, so lengths are
+ * provided for each of the literal/length symbols, and for each of the
+ * distance symbols.
+ *
+ * - If a symbol is not used in the block, this is represented by a zero as
+ * as the code length. This does not mean a zero-length code, but rather
+ * that no code should be created for this symbol. There is no way in the
+ * deflate format to represent a zero-length code.
+ *
+ * - The maximum number of bits in a code is 15, so the possible lengths for
+ * any code are 1..15.
+ *
+ * - The fact that a length of zero is not permitted for a code has an
+ * interesting consequence. Normally if only one symbol is used for a given
+ * code, then in fact that code could be represented with zero bits. However
+ * in deflate, that code has to be at least one bit. So for example, if
+ * only a single distance base symbol appears in a block, then it will be
+ * represented by a single code of length one, in particular one 0 bit. This
+ * is an incomplete code, since if a 1 bit is received, it has no meaning,
+ * and should result in an error. So incomplete distance codes of one symbol
+ * should be permitted, and the receipt of invalid codes should be handled.
+ *
+ * - It is also possible to have a single literal/length code, but that code
+ * must be the end-of-block code, since every dynamic block has one. This
+ * is not the most efficient way to create an empty block (an empty fixed
+ * block is fewer bits), but it is allowed by the format. So incomplete
+ * literal/length codes of one symbol should also be permitted.
+ *
+ * - If there are only literal codes and no lengths, then there are no distance
+ * codes. This is represented by one distance code with zero bits.
+ *
+ * - The list of up to 286 length/literal lengths and up to 30 distance lengths
+ * are themselves compressed using Huffman codes and run-length encoding. In
+ * the list of code lengths, a 0 symbol means no code, a 1..15 symbol means
+ * that length, and the symbols 16, 17, and 18 are run-length instructions.
+ * Each of 16, 17, and 18 are follwed by extra bits to define the length of
+ * the run. 16 copies the last length 3 to 6 times. 17 represents 3 to 10
+ * zero lengths, and 18 represents 11 to 138 zero lengths. Unused symbols
+ * are common, hence the special coding for zero lengths.
+ *
+ * - The symbols for 0..18 are Huffman coded, and so that code must be
+ * described first. This is simply a sequence of up to 19 three-bit values
+ * representing no code (0) or the code length for that symbol (1..7).
+ *
+ * - A dynamic block starts with three fixed-size counts from which is computed
+ * the number of literal/length code lengths, the number of distance code
+ * lengths, and the number of code length code lengths (ok, you come up with
+ * a better name!) in the code descriptions. For the literal/length and
+ * distance codes, lengths after those provided are considered zero, i.e. no
+ * code. The code length code lengths are received in a permuted order (see
+ * the order[] array below) to make a short code length code length list more
+ * likely. As it turns out, very short and very long codes are less likely
+ * to be seen in a dynamic code description, hence what may appear initially
+ * to be a peculiar ordering.
+ *
+ * - Given the number of literal/length code lengths (nlen) and distance code
+ * lengths (ndist), then they are treated as one long list of nlen + ndist
+ * code lengths. Therefore run-length coding can and often does cross the
+ * boundary between the two sets of lengths.
+ *
+ * - So to summarize, the code description at the start of a dynamic block is
+ * three counts for the number of code lengths for the literal/length codes,
+ * the distance codes, and the code length codes. This is followed by the
+ * code length code lengths, three bits each. This is used to construct the
+ * code length code which is used to read the remainder of the lengths. Then
+ * the literal/length code lengths and distance lengths are read as a single
+ * set of lengths using the code length codes. Codes are constructed from
+ * the resulting two sets of lengths, and then finally you can start
+ * decoding actual compressed data in the block.
+ *
+ * - For reference, a "typical" size for the code description in a dynamic
+ * block is around 80 bytes.
+ */
+local int32_t dynamic(struct state *s)
+{
+ int32_t nlen, ndist, ncode; /* number of lengths in descriptor */
+ int32_t index; /* index of lengths[] */
+ int32_t err; /* construct() return value */
+ int16_t lengths[MAXCODES]; /* descriptor code lengths */
+ int16_t lencnt[MAXBITS+1], lensym[MAXLCODES]; /* lencode memory */
+ int16_t distcnt[MAXBITS+1], distsym[MAXDCODES]; /* distcode memory */
+ struct huffman lencode = {lencnt, lensym}; /* length code */
+ struct huffman distcode = {distcnt, distsym}; /* distance code */
+ static const int16_t order[19] = /* permutation of code length codes */
+ {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+
+ /* get number of lengths in each table, check lengths */
+ nlen = bits(s, 5) + 257;
+ ndist = bits(s, 5) + 1;
+ ncode = bits(s, 4) + 4;
+ if (nlen > MAXLCODES || ndist > MAXDCODES)
+ return -3; /* bad counts */
+
+ /* read code length code lengths (really), missing lengths are zero */
+ for (index = 0; index < ncode; index++)
+ lengths[order[index]] = bits(s, 3);
+ for (; index < 19; index++)
+ lengths[order[index]] = 0;
+
+ /* build huffman table for code lengths codes (use lencode temporarily) */
+ err = construct(&lencode, lengths, 19);
+ if (err != 0) return -4; /* require complete code set here */
+
+ /* read length/literal and distance code length tables */
+ index = 0;
+ while (index < nlen + ndist) {
+ int32_t symbol; /* decoded value */
+ int32_t len; /* last length to repeat */
+
+ symbol = decode(s, &lencode);
+ if (symbol < 16) /* length in 0..15 */
+ lengths[index++] = symbol;
+ else { /* repeat instruction */
+ len = 0; /* assume repeating zeros */
+ if (symbol == 16) { /* repeat last length 3..6 times */
+ if (index == 0) return -5; /* no last length! */
+ len = lengths[index - 1]; /* last length */
+ symbol = 3 + bits(s, 2);
+ }
+ else if (symbol == 17) /* repeat zero 3..10 times */
+ symbol = 3 + bits(s, 3);
+ else /* == 18, repeat zero 11..138 times */
+ symbol = 11 + bits(s, 7);
+ if (index + symbol > nlen + ndist)
+ return -6; /* too many lengths! */
+ while (symbol--) /* repeat last or zero symbol times */
+ lengths[index++] = len;
+ }
+ }
+
+ /* build huffman table for literal/length codes */
+ err = construct(&lencode, lengths, nlen);
+ if (err < 0 || (err > 0 && nlen - lencode.count[0] != 1))
+ return -7; /* only allow incomplete codes if just one code */
+
+ /* build huffman table for distance codes */
+ err = construct(&distcode, lengths + nlen, ndist);
+ if (err < 0 || (err > 0 && ndist - distcode.count[0] != 1))
+ return -8; /* only allow incomplete codes if just one code */
+
+ /* decode data until end-of-block code */
+ return codes(s, &lencode, &distcode);
+}
+
+/*
+ * Inflate source to dest. On return, destlen and sourcelen are updated to the
+ * size of the uncompressed data and the size of the deflate data respectively.
+ * On success, the return value of puff() is zero. If there is an error in the
+ * source data, i.e. it is not in the deflate format, then a negative value is
+ * returned. If there is not enough input available or there is not enough
+ * output space, then a positive error is returned. In that case, destlen and
+ * sourcelen are not updated to facilitate retrying from the beginning with the
+ * provision of more input data or more output space. In the case of invalid
+ * inflate data (a negative error), the dest and source pointers are updated to
+ * facilitate the debugging of deflators.
+ *
+ * puff() also has a mode to determine the size of the uncompressed output with
+ * no output written. For this dest must be (uint8_t *)0. In this case,
+ * the input value of *destlen is ignored, and on return *destlen is set to the
+ * size of the uncompressed output.
+ *
+ * The return codes are:
+ *
+ * 2: available inflate data did not terminate
+ * 1: output space exhausted before completing inflate
+ * 0: successful inflate
+ * -1: invalid block type (type == 3)
+ * -2: stored block length did not match one's complement
+ * -3: dynamic block code description: too many length or distance codes
+ * -4: dynamic block code description: code lengths codes incomplete
+ * -5: dynamic block code description: repeat lengths with no first length
+ * -6: dynamic block code description: repeat more than specified lengths
+ * -7: dynamic block code description: invalid literal/length code lengths
+ * -8: dynamic block code description: invalid distance code lengths
+ * -9: invalid literal/length or distance code in fixed or dynamic block
+ * -10: distance is too far back in fixed or dynamic block
+ *
+ * Format notes:
+ *
+ * - Three bits are read for each block to determine the kind of block and
+ * whether or not it is the last block. Then the block is decoded and the
+ * process repeated if it was not the last block.
+ *
+ * - The leftover bits in the last byte of the deflate data after the last
+ * block (if it was a fixed or dynamic block) are undefined and have no
+ * expected values to check.
+ */
+int32_t puff(uint8_t *dest, /* pointer to destination pointer */
+ uint32_t *destlen, /* amount of output space */
+ uint8_t *source, /* pointer to source data pointer */
+ uint32_t *sourcelen) /* amount of input available */
+{
+ struct state s; /* input/output state */
+ int32_t last, type; /* block information */
+ int32_t err; /* return value */
+
+ /* initialize output state */
+ s.out = dest;
+ s.outlen = *destlen; /* ignored if dest is NULL */
+ s.outcnt = 0;
+
+ /* initialize input state */
+ s.in = source;
+ s.inlen = *sourcelen;
+ s.incnt = 0;
+ s.bitbuf = 0;
+ s.bitcnt = 0;
+
+ /* return if bits() or decode() tries to read past available input */
+ if (setjmp(s.env) != 0) /* if came back here via longjmp() */
+ err = 2; /* then skip do-loop, return error */
+ else {
+ /* process blocks until last block or error */
+ do {
+ last = bits(&s, 1); /* one if last block */
+ type = bits(&s, 2); /* block type 0..3 */
+ err = type == 0 ? stored(&s) :
+ (type == 1 ? fixed(&s) :
+ (type == 2 ? dynamic(&s) :
+ -1)); /* type == 3, invalid */
+ if (err != 0) break; /* return with error */
+ } while (!last);
+ }
+
+ /* update the lengths and return */
+ if (err <= 0) {
+ *destlen = s.outcnt;
+ *sourcelen = s.incnt;
+ }
+ return err;
+}
diff --git a/src/qcommon/puff.h b/src/qcommon/puff.h
new file mode 100644
index 0000000..14070f6
--- /dev/null
+++ b/src/qcommon/puff.h
@@ -0,0 +1,43 @@
+/*
+ * This is a modified version of Mark Adlers work,
+ * see below for the original copyright.
+ * 2006 - Joerg Dietrich <dietrich_joerg@gmx.de>
+ */
+
+/* puff.h
+ Copyright (C) 2002, 2003 Mark Adler, all rights reserved
+ version 1.7, 3 Mar 2002
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the author be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+ Mark Adler madler@alumni.caltech.edu
+ */
+
+#ifndef __PUFF_H
+#define __PUFF_H
+
+#include "q_shared.h" /* for definitions of the <stdint.h> types */
+
+/*
+ * See puff.c for purpose and usage.
+ */
+int32_t puff(uint8_t *dest, /* pointer to destination pointer */
+ uint32_t *destlen, /* amount of output space */
+ uint8_t *source, /* pointer to source data pointer */
+ uint32_t *sourcelen); /* amount of input available */
+
+#endif // __PUFF_H
diff --git a/src/qcommon/q3_lauxlib.cpp b/src/qcommon/q3_lauxlib.cpp
new file mode 100644
index 0000000..ee7efa2
--- /dev/null
+++ b/src/qcommon/q3_lauxlib.cpp
@@ -0,0 +1,46 @@
+#include "q3_lauxlib.h"
+
+#include <sys/types.h>
+
+#include <cstdarg>
+#include <iostream>
+
+#include "sys/sys_shared.h"
+
+#include "cvar.h"
+#include "msg.h"
+#include "net.h"
+#include "q_shared.h"
+#include "qcommon.h"
+
+size_t qlua_writestring(const char* string, size_t n)
+{
+#ifndef DEDICATED
+ CL_ConsolePrint( string );
+#endif
+ Q_StripIndentMarker( const_cast<char*>(string) );
+ Sys_Print( string );
+
+ return n;
+}
+
+int qlua_writeline(void)
+{
+#ifndef DEDICATED
+ CL_ConsolePrint( "\n" );
+#endif
+ Sys_Print( "\n" );
+ return 0;
+}
+
+int qlua_writestringerror(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ char m[MAXPRINTMSG];
+ Q_vsnprintf(m, sizeof(m), fmt, ap);
+ va_end (ap);
+ Com_Printf(S_COLOR_YELLOW "%s\n", m);
+ return 0;
+}
+
diff --git a/src/qcommon/q3_lauxlib.h b/src/qcommon/q3_lauxlib.h
new file mode 100644
index 0000000..caba8e4
--- /dev/null
+++ b/src/qcommon/q3_lauxlib.h
@@ -0,0 +1,46 @@
+// This file is part of Tremulous.
+// Copyright © 2016 Victor Roemer (blowfish) <victor@badsec.org>
+// Copyright (C) 2015-2019 GrangerHub
+//
+// 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 3 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, see <http://www.gnu.org/licenses/>.
+
+#ifndef OVERRIDE_LAUXLIB_H
+#define OVERRIDE_LAUXLIB_H
+
+#include <stdarg.h>
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+size_t qlua_writestring(const char* string, size_t n);
+int qlua_writeline(void);
+int qlua_writestringerror(const char *fmt, ...);
+
+#define lua_writestring qlua_writestring
+#define lua_writeline qlua_writeline
+#define lua_writestringerror qlua_writestringerror
+
+#define LUA_TMPNAMTEMPLATE "/tmp/tremulous_XXXXXX"
+
+// Because: src/lua-5.3.3/include/luaconf.h:69:9: warning: 'LUA_USE_POSIX' macro redefined [-Wmacro-redefined]
+//#ifndef _WIN32
+//#define LUA_USE_POSIX 1
+//#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/src/qcommon/q_math.c b/src/qcommon/q_math.c
index 2a641a9..9944850 100644
--- a/src/qcommon/q_math.c
+++ b/src/qcommon/q_math.c
@@ -1,13 +1,14 @@
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
-Copyright (C) 2000-2006 Tim Angus
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
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,
+published by the Free Software Foundation; either version 3 of the License,
or (at your option) any later version.
Tremulous is distributed in the hope that it will be
@@ -16,8 +17,8 @@ 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
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
===========================================================================
*/
//
@@ -36,133 +37,28 @@ vec3_t vec3_origin = {0,0,0};
vec3_t axisDefault[3] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } };
-vec4_t colorBlack = {0.000f, 0.000f, 0.000f, 1.000f};
-vec4_t colorRed = {1.000f, 0.000f, 0.000f, 1.000f};
-vec4_t colorGreen = {0.000f, 1.000f, 0.000f, 1.000f};
-vec4_t colorBlue = {0.000f, 0.000f, 1.000f, 1.000f};
-vec4_t colorYellow = {1.000f, 1.000f, 0.000f, 1.000f};
-vec4_t colorMagenta = {1.000f, 0.000f, 1.000f, 1.000f};
-vec4_t colorCyan = {0.000f, 1.000f, 1.000f, 1.000f};
-vec4_t colorWhite = {1.000f, 1.000f, 1.000f, 1.000f};
-vec4_t colorGray = {0.502f, 0.502f, 0.502f, 1.000f};
-vec4_t colorOrange = {1.000f, 0.686f, 0.000f, 1.000f};
-vec4_t colorRoseBud = {0.996f, 0.671f, 0.604f, 1.000f};
-vec4_t colorPaleGreen = {0.596f, 0.984f, 0.596f, 1.000f};
-vec4_t colorPaleGolden = {0.933f, 0.910f, 0.667f, 1.000f};
-vec4_t colorColumbiaBlue = {0.608f, 0.867f, 1.000f, 1.000f};
-vec4_t colorPaleTurquoise = {0.686f, 0.933f, 0.933f, 1.000f};
-vec4_t colorPaleVioletRed = {0.859f, 0.439f, 0.576f, 1.000f};
-vec4_t colorPalacePaleWhite = {0.910f, 0.898f, 0.863f, 1.000f};
-vec4_t colorOlive = {0.231f, 0.235f, 0.212f, 1.000f};
-vec4_t colorTomato = {1.000f, 0.388f, 0.278f, 1.000f};
-vec4_t colorLime = {0.749f, 1.000f, 0.000f, 1.000f};
-vec4_t colorLemon = {1.000f, 0.969f, 0.000f, 1.000f};
-vec4_t colorBlueBerry = {0.310f, 0.525f, 0.969f, 1.000f};
-vec4_t colorTurquoise = {0.251f, 0.878f, 0.816f, 1.000f};
-vec4_t colorWildWatermelon = {0.992f, 0.357f, 0.471f, 1.000f};
-vec4_t colorSaltpan = {0.933f, 0.953f, 0.898f, 1.000f};
-vec4_t colorGrayChateau = {0.624f, 0.639f, 0.655f, 1.000f};
-vec4_t colorRust = {0.718f, 0.255f, 0.055f, 1.000f};
-vec4_t colorCopperGreen = {0.431f, 0.553f, 0.443f, 1.000f};
-vec4_t colorGold = {1.000f, 0.843f, 0.000f, 1.000f};
-vec4_t colorSteelBlue = {0.275f, 0.510f, 0.706f, 1.000f};
-vec4_t colorSteelGray = {0.482f, 0.565f, 0.584f, 1.000f};
-vec4_t colorBronze = {0.804f, 0.498f, 0.196f, 1.000f};
-vec4_t colorSilver = {0.753f, 0.753f, 0.753f, 1.000f};
-vec4_t colorDarkGray = {0.663f, 0.663f, 0.663f, 1.000f};
-vec4_t colorDarkOrange = {1.000f, 0.549f, 0.000f, 1.000f};
-vec4_t colorDarkGreen = {0.000f, 0.392f, 0.000f, 1.000f};
-vec4_t colorRedOrange = {1.000f, 0.247f, 0.204f, 1.000f};
-vec4_t colorForestGreen = {0.133f, 0.545f, 0.133f, 1.000f};
-vec4_t colorBrightSun = {0.926f, 0.741f, 0.173f, 1.000f};
-vec4_t colorMediumSlateBlue = {0.482f, 0.408f, 0.933f, 1.000f};
-vec4_t colorCeleste = {0.698f, 1.000f, 1.000f, 1.000f};
-vec4_t colorIronstone = {0.525f, 0.314f, 0.251f, 1.000f};
-vec4_t colorTimberwolf = {0.859f, 0.843f, 0.824f, 1.000f};
-vec4_t colorOnyx = {0.059f, 0.059f, 0.059f, 1.000f};
-vec4_t colorRosewood = {0.396f, 0.000f, 0.043f, 1.000f};
-vec4_t colorKokoda = {0.482f, 0.471f, 0.353f, 1.000f};
-vec4_t colorPorsche = {0.875f, 0.616f, 0.357f, 1.000f};
-vec4_t colorCloudBurst = {0.208f, 0.369f, 0.310f, 1.000f};
-vec4_t colorBlueDiane = {0.208f, 0.318f, 0.310f, 1.000f};
-vec4_t colorRope = {0.557f, 0.349f, 0.235f, 1.000f};
-vec4_t colorBlonde = {0.980f, 0.941f, 0.745f, 1.000f};
-vec4_t colorSmokeyBlack = {0.063f, 0.047f, 0.031f, 1.000f};
-vec4_t colorAmericanRose = {1.000f, 0.012f, 0.243f, 1.000f};
-vec4_t colorNeonGreen = {0.224f, 1.000f, 0.078f, 1.000f};
-vec4_t colorNeonYellow = {0.980f, 0.929f, 0.153f, 1.000f};
-vec4_t colorUltramarine = {0.071f, 0.039f, 0.561f, 1.000f};
-vec4_t colorTurquoiseBlue = {0.000f, 1.000f, 0.937f, 1.000f};
-vec4_t colorDarkMagenta = {0.545f, 0.000f, 0.545f, 1.000f};
-vec4_t colorMagicMint = {0.667f, 0.941f, 0.820f, 1.000f};
-vec4_t colorLightGray = {0.827f, 0.827f, 0.827f, 1.000f};
-vec4_t colorLightSalmon = {1.000f, 0.600f, 0.600f, 1.000f};
-vec4_t colorLightGreen = {0.565f, 0.933f, 0.565f, 1.000f};
-
-vec4_t g_color_table[62] =
+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.250f, 0.250f, 0.250f, 1.000f},
- {1.000f, 0.000f, 0.000f, 1.000f},
- {0.000f, 1.000f, 0.000f, 1.000f},
- {1.000f, 1.000f, 0.000f, 1.000f},
- {0.000f, 0.000f, 1.000f, 1.000f},
- {0.000f, 1.000f, 1.000f, 1.000f},
- {1.000f, 0.000f, 1.000f, 1.000f},
- {1.000f, 1.000f, 1.000f, 1.000f},
- {0.502f, 0.502f, 0.502f, 1.000f},
- {1.000f, 0.686f, 0.000f, 1.000f},
- {0.996f, 0.671f, 0.604f, 1.000f},
- {0.596f, 0.984f, 0.596f, 1.000f},
- {0.933f, 0.910f, 0.667f, 1.000f},
- {0.608f, 0.867f, 1.000f, 1.000f},
- {0.686f, 0.933f, 0.933f, 1.000f},
- {0.859f, 0.439f, 0.576f, 1.000f},
- {0.910f, 0.898f, 0.863f, 1.000f},
- {0.231f, 0.235f, 0.212f, 1.000f},
- {1.000f, 0.388f, 0.278f, 1.000f},
- {0.749f, 1.000f, 0.000f, 1.000f},
- {1.000f, 0.969f, 0.000f, 1.000f},
- {0.310f, 0.525f, 0.969f, 1.000f},
- {0.251f, 0.878f, 0.816f, 1.000f},
- {0.992f, 0.357f, 0.471f, 1.000f},
- {0.933f, 0.953f, 0.898f, 1.000f},
- {0.624f, 0.639f, 0.655f, 1.000f},
- {0.718f, 0.255f, 0.055f, 1.000f},
- {0.431f, 0.553f, 0.443f, 1.000f},
- {1.000f, 0.843f, 0.000f, 1.000f},
- {0.275f, 0.510f, 0.706f, 1.000f},
- {0.482f, 0.565f, 0.584f, 1.000f},
- {0.804f, 0.498f, 0.196f, 1.000f},
- {0.753f, 0.753f, 0.753f, 1.000f},
- {0.663f, 0.663f, 0.663f, 1.000f},
- {1.000f, 0.549f, 0.000f, 1.000f},
- {0.000f, 0.392f, 0.000f, 1.000f},
- {1.000f, 0.247f, 0.204f, 1.000f},
- {0.133f, 0.545f, 0.133f, 1.000f},
- {0.926f, 0.741f, 0.173f, 1.000f},
- {0.482f, 0.408f, 0.933f, 1.000f},
- {0.698f, 1.000f, 1.000f, 1.000f},
- {0.525f, 0.314f, 0.251f, 1.000f},
- {0.859f, 0.843f, 0.824f, 1.000f},
- {0.059f, 0.059f, 0.059f, 1.000f},
- {0.396f, 0.000f, 0.043f, 1.000f},
- {0.482f, 0.471f, 0.353f, 1.000f},
- {0.875f, 0.616f, 0.357f, 1.000f},
- {0.208f, 0.369f, 0.310f, 1.000f},
- {0.208f, 0.318f, 0.310f, 1.000f},
- {0.557f, 0.349f, 0.235f, 1.000f},
- {0.980f, 0.941f, 0.745f, 1.000f},
- {0.063f, 0.047f, 0.031f, 1.000f},
- {1.000f, 0.012f, 0.243f, 1.000f},
- {0.224f, 1.000f, 0.078f, 1.000f},
- {0.980f, 0.929f, 0.153f, 1.000f},
- {0.071f, 0.039f, 0.561f, 1.000f},
- {0.000f, 1.000f, 0.937f, 1.000f},
- {0.545f, 0.000f, 0.545f, 1.000f},
- {0.667f, 0.941f, 0.820f, 1.000f},
- {0.827f, 0.827f, 0.827f, 1.000f},
- {1.000f, 0.600f, 0.600f, 1.000f},
- {0.565f, 0.933f, 0.565f, 1.000f},
+ {0.2, 0.2, 0.2, 1.0},
+ {1.0, 0.0, 0.0, 1.0},
+ {0.0, 1.0, 0.0, 1.0},
+ {1.0, 1.0, 0.0, 1.0},
+ {0.0, 0.0, 1.0, 1.0},
+ {0.0, 1.0, 1.0, 1.0},
+ {1.0, 0.0, 1.0, 1.0},
+ {1.0, 1.0, 1.0, 1.0},
};
@@ -601,7 +497,7 @@ void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal )
inv_denom = 1.0f / DotProduct( normal, normal );
#ifndef Q3_VM
- assert( Q_fabs(inv_denom) != 0.0f ); // bk010122 - zero vectors get here
+ assert( Q_fabs(inv_denom) != 0.0f ); // zero vectors get here
#endif
inv_denom = 1.0f / inv_denom;
@@ -655,10 +551,7 @@ void VectorRotate( vec3_t in, vec3_t matrix[3], vec3_t out )
*/
float Q_rsqrt( float number )
{
- union {
- float f;
- int i;
- } t;
+ floatint_t t;
float x2, y;
const float threehalfs = 1.5F;
@@ -669,14 +562,14 @@ float Q_rsqrt( float number )
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;
+ floatint_t fi;
+ fi.f = f;
+ fi.i &= 0x7FFFFFFF;
+ return fi.f;
}
#endif
@@ -804,50 +697,14 @@ void SetPlaneSignbits (cplane_t *out) {
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)
+int BoxOnPlaneSide(vec3_t emins, vec3_t emaxs, struct cplane_s *p)
{
- float dist1, dist2;
- int sides;
+ float dist[2];
+ int sides, b, i;
-// fast axial cases
+ // fast axial cases
if (p->type < 3)
{
if (p->dist <= emins[p->type])
@@ -857,291 +714,27 @@ int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p)
return 3;
}
-// general case
- switch (p->signbits)
+ // general case
+ dist[0] = dist[1] = 0;
+ if (p->signbits < 8) // >= 8: default case is original code (dist[0]=dist[1]=0)
{
- 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;
+ for (i=0 ; i<3 ; i++)
+ {
+ b = (p->signbits >> i) & 1;
+ dist[ b] += p->normal[i]*emaxs[i];
+ dist[!b] += p->normal[i]*emins[i];
+ }
}
sides = 0;
- if (dist1 >= p->dist)
+ if (dist[0] >= p->dist)
sides = 1;
- if (dist2 < p->dist)
+ if (dist[1] < 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
/*
=================
@@ -1191,16 +784,64 @@ void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ) {
}
}
+qboolean BoundsIntersect(const vec3_t mins, const vec3_t maxs,
+ const vec3_t mins2, const vec3_t maxs2)
+{
+ if ( maxs[0] < mins2[0] ||
+ maxs[1] < mins2[1] ||
+ maxs[2] < mins2[2] ||
+ mins[0] > maxs2[0] ||
+ mins[1] > maxs2[1] ||
+ mins[2] > maxs2[2])
+ {
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+qboolean BoundsIntersectSphere(const vec3_t mins, const vec3_t maxs,
+ const vec3_t origin, vec_t radius)
+{
+ if ( origin[0] - radius > maxs[0] ||
+ origin[0] + radius < mins[0] ||
+ origin[1] - radius > maxs[1] ||
+ origin[1] + radius < mins[1] ||
+ origin[2] - radius > maxs[2] ||
+ origin[2] + radius < mins[2])
+ {
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+qboolean BoundsIntersectPoint(const vec3_t mins, const vec3_t maxs,
+ const vec3_t origin)
+{
+ if ( origin[0] > maxs[0] ||
+ origin[0] < mins[0] ||
+ origin[1] > maxs[1] ||
+ origin[1] < mins[1] ||
+ origin[2] > maxs[2] ||
+ origin[2] < mins[2])
+ {
+ return qfalse;
+ }
+
+ return qtrue;
+}
vec_t VectorNormalize( vec3_t v ) {
- // 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;
+ /* writing it this way allows gcc to recognize that rsqrt can be used */
+ ilength = 1/(float)sqrt (length);
+ /* sqrt(length) = length * (1 / sqrt(length)) */
+ length *= ilength;
v[0] *= ilength;
v[1] *= ilength;
v[2] *= ilength;
@@ -1213,21 +854,17 @@ 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;
+ /* writing it this way allows gcc to recognize that rsqrt can be used */
+ ilength = 1/(float)sqrt (length);
+ /* sqrt(length) = length * (1 / sqrt(length)) */
+ length *= ilength;
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 );
}
@@ -1653,15 +1290,40 @@ Don't pass doubles to this
*/
int Q_isnan( float x )
{
- union
- {
- float f;
- unsigned int i;
- } t;
+ floatint_t fi;
+
+ fi.f = x;
+ fi.ui &= 0x7FFFFFFF;
+ fi.ui = 0x7F800000 - fi.ui;
+
+ return (int)( (unsigned int)fi.ui >> 31 );
+}
+//------------------------------------------------------------------------
+
+#ifndef Q3_VM
+/*
+=====================
+Q_acos
- t.f = x;
- t.i &= 0x7FFFFFFF;
- t.i = 0x7F800000 - t.i;
+the msvc acos doesn't always return a value between -PI and PI:
- return (int)( (unsigned int)t.i >> 31 );
+int i;
+i = 1065353246;
+acos(*(float*) &i) == -1.#IND0
+
+=====================
+*/
+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;
}
+#endif
diff --git a/src/qcommon/q_platform.h b/src/qcommon/q_platform.h
index 84b87b9..b72d285 100644
--- a/src/qcommon/q_platform.h
+++ b/src/qcommon/q_platform.h
@@ -1,13 +1,14 @@
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
-Copyright (C) 2000-2006 Tim Angus
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
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,
+published by the Free Software Foundation; either version 3 of the License,
or (at your option) any later version.
Tremulous is distributed in the hope that it will be
@@ -16,20 +17,27 @@ 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
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
===========================================================================
*/
-//
+
#ifndef __Q_PLATFORM_H
#define __Q_PLATFORM_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
// this is for determining if we have an asm version of a C function
+#define idx64 0
+
#ifdef Q3_VM
#define id386 0
#define idppc 0
#define idppc_altivec 0
+#define idsparc 0
#else
@@ -59,12 +67,19 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define idppc_altivec 0
#endif
+#if defined(__sparc__) && !defined(C_ONLY)
+#define idsparc 1
+#else
+#define idsparc 0
+#endif
+
#endif
#ifndef __ASM_I386__ // don't include the C bits if included from qasm.h
// for windows fastcall option
#define QDECL
+#define QCALL
//================================================================= WIN64/32 ===
@@ -101,7 +116,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
// For cl_updates.cpp
#define RELEASE_PACKAGE_NAME ( "release-mingw32-" ARCH_STRING ".zip" )
-
+ #define RELEASE_SIGNATURE_NAME ( "release-mingw32-" ARCH_STRING ".zip.sig" )
+ #define GRANGER_EXE ( "granger" EXE_EXT )
+
#elif defined(_WIN32) || defined(__WIN32__)
#undef QDECL
@@ -132,7 +149,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
// For cl_updates.cpp
#define RELEASE_PACKAGE_NAME ( "release-mingw32-" ARCH_STRING ".zip" )
-
+ #define RELEASE_SIGNATURE_NAME ( "release-mingw32-" ARCH_STRING ".zip.sig" )
+ #define GRANGER_EXE ( "granger" EXE_EXT )
+
#endif
@@ -162,7 +181,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
// For cl_updates.cpp
#define RELEASE_PACKAGE_NAME ( "release-darwin-" ARCH_STRING ".zip" )
-
+ #define RELEASE_SIGNATURE_NAME ( "release-darwin-" ARCH_STRING ".zip.sig" )
+ #define GRANGER_EXE ( "granger" EXE_EXT )
+
#endif
//================================================================= LINUX ===
@@ -194,6 +215,23 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# define Q3_LITTLE_ENDIAN
#undef idx64
#define idx64 1
+ #elif defined __arm__
+ # if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+ # error "Big endian ARM is not supported"
+ # endif
+ # if defined __armhf__
+ # define ARCH_STRING "armhf"
+ # define Q3_LITTLE_ENDIAN
+ # else
+ # define ARCH_STRING "armel"
+ # define Q3_LITTLE_ENDIAN
+ # endif
+ #elif defined __aarch64__
+ # if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+ # error "Big endian ARM is not supported"
+ # endif
+ # define ARCH_STRING "aarch64"
+ # define Q3_LITTLE_ENDIAN
#endif
#define DLL_EXT ".so"
@@ -201,6 +239,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
// For cl_updates.cpp
#define RELEASE_PACKAGE_NAME ( "release-linux-" ARCH_STRING ".zip" )
+ #define RELEASE_SIGNATURE_NAME ( "release-linux-" ARCH_STRING ".zip.sig" )
+ #define GRANGER_EXE ( "granger" EXE_EXT )
#endif
@@ -336,51 +376,63 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
//endianness
+void CopyShortSwap (void *dest, void *src);
+void CopyLongSwap (void *dest, void *src);
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"
+ #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
+ #define CopyLittleShort(dest, src) CopyShortSwap(dest, src)
+ #define CopyLittleLong(dest, src) CopyLongSwap(dest, src)
+ #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)
+ #define CopyLittleShort(dest, src) memcpy(dest, src, 2)
+ #define CopyLittleLong(dest, src) memcpy(dest, src, 4)
+ #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
+ #define LittleShort
+ #define LittleLong
+ #define LittleFloat
+ #define BigShort
+ #define BigLong
+ #define BigFloat
#else
-#error "Endianness not defined"
+
+ #error "Endianness not defined"
+
#endif
//platform string
#ifdef NDEBUG
-#define PLATFORM_STRING OS_STRING "-" ARCH_STRING
+ #define PLATFORM_STRING OS_STRING "-" ARCH_STRING
#else
-#define PLATFORM_STRING OS_STRING "-" ARCH_STRING "-debug"
+ #define PLATFORM_STRING OS_STRING "-" ARCH_STRING "-debug"
+#endif
+
#endif
+#ifdef __cplusplus
+}
#endif
#endif
diff --git a/src/qcommon/q_shared.c b/src/qcommon/q_shared.c
index 2ac5537..938a7e5 100644
--- a/src/qcommon/q_shared.c
+++ b/src/qcommon/q_shared.c
@@ -1,13 +1,14 @@
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
-Copyright (C) 2000-2006 Tim Angus
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
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,
+published by the Free Software Foundation; either version 3 of the License,
or (at your option) any later version.
Tremulous is distributed in the hope that it will be
@@ -16,8 +17,8 @@ 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
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
===========================================================================
*/
//
@@ -56,50 +57,77 @@ char *COM_SkipPath (char *pathname)
/*
============
+COM_GetExtension
+============
+*/
+const char *COM_GetExtension( const char *name )
+{
+ const char *dot = strrchr(name, '.'), *slash;
+ if (dot && (!(slash = strrchr(name, '/')) || slash < dot))
+ return dot + 1;
+ else
+ return "";
+}
+
+
+/*
+============
COM_StripExtension
============
*/
-void COM_StripExtension( const char *in, char *out, int destsize ) {
- int length;
+void COM_StripExtension( const char *in, char *out, int destsize )
+{
+ const char *dot = strrchr(in, '.'), *slash;
- Q_strncpyz(out, in, destsize);
+ if (dot && (!(slash = strrchr(in, '/')) || slash < dot))
+ destsize = (destsize < dot-in+1 ? destsize : dot-in+1);
- length = strlen(out)-1;
- while (length > 0 && out[length] != '.')
+ if ( in == out && destsize > 1 )
+ out[destsize-1] = '\0';
+ else
+ Q_strncpyz(out, in, destsize);
+}
+
+/*
+============
+COM_CompareExtension
+
+string compare the end of the strings and return qtrue if strings match
+============
+*/
+qboolean COM_CompareExtension(const char *in, const char *ext)
+{
+ int inlen, extlen;
+
+ inlen = strlen(in);
+ extlen = strlen(ext);
+
+ if(extlen <= inlen)
{
- length--;
- if (out[length] == '/')
- return; // no extension
+ in += inlen - extlen;
+
+ if(!Q_stricmp(in, ext))
+ return qtrue;
}
- if (length)
- out[length] = 0;
+
+ return qfalse;
}
-
/*
==================
COM_DefaultExtension
+
+if path doesn't have an extension, then append
+ the specified one (which should include the .)
==================
*/
-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 );
+void COM_DefaultExtension( char *path, int maxSize, const char *extension )
+{
+ const char *dot = strrchr(path, '.'), *slash;
+ if (dot && (!(slash = strrchr(path, '/')) || slash < dot))
+ return;
+ else
+ Q_strcat(path, maxSize, extension);
}
/*
@@ -131,6 +159,24 @@ float BigFloat (const float *l) {return _BigFloat(l);}
float LittleFloat (const float *l) {return _LittleFloat(l);}
*/
+void CopyShortSwap(void *dest, void *src)
+{
+ byte *to = dest, *from = src;
+
+ to[0] = from[1];
+ to[1] = from[0];
+}
+
+void CopyLongSwap(void *dest, void *src)
+{
+ byte *to = dest, *from = src;
+
+ to[0] = from[3];
+ to[1] = from[2];
+ to[2] = from[1];
+ to[3] = from[0];
+}
+
short ShortSwap (short l)
{
byte b1,b2;
@@ -158,47 +204,17 @@ int LongSwap (int l)
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)
+float FloatSwap(const float *f)
{
- return ll;
-}
-
-typedef union {
- float f;
- unsigned int i;
-} _FloatByteUnion;
-
-float FloatSwap (const float *f) {
- _FloatByteUnion out;
+ floatint_t out;
out.f = *f;
- out.i = LongSwap(out.i);
+ out.ui = LongSwap(out.ui);
return out.f;
}
-float FloatNoSwap (const float *f)
+float FloatNoSwap(const float *f)
{
return *f;
}
@@ -251,15 +267,22 @@ PARSING
static char com_token[MAX_TOKEN_CHARS];
static char com_parsename[MAX_TOKEN_CHARS];
static int com_lines;
+static int com_tokenline;
void COM_BeginParseSession( const char *name )
{
- com_lines = 0;
+ com_lines = 1;
+ com_tokenline = 0;
Com_sprintf(com_parsename, sizeof(com_parsename), "%s", name);
}
int COM_GetCurrentParseLine( void )
{
+ if ( com_tokenline )
+ {
+ return com_tokenline;
+ }
+
return com_lines;
}
@@ -274,10 +297,10 @@ void COM_ParseError( char *format, ... )
static char string[4096];
va_start (argptr, format);
- vsprintf (string, format, argptr);
+ Q_vsnprintf (string, sizeof(string), format, argptr);
va_end (argptr);
- Com_Printf("ERROR: %s, line %d: %s\n", com_parsename, com_lines, string);
+ Com_Printf("ERROR: %s, line %d: %s\n", com_parsename, COM_GetCurrentParseLine(), string);
}
void COM_ParseWarning( char *format, ... )
@@ -286,10 +309,10 @@ void COM_ParseWarning( char *format, ... )
static char string[4096];
va_start (argptr, format);
- vsprintf (string, format, argptr);
+ Q_vsnprintf (string, sizeof(string), format, argptr);
va_end (argptr);
- Com_Printf("WARNING: %s, line %d: %s\n", com_parsename, com_lines, string);
+ Com_Printf("WARNING: %s, line %d: %s\n", com_parsename, COM_GetCurrentParseLine(), string);
}
/*
@@ -340,52 +363,53 @@ int COM_Compress( char *data_p ) {
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
+ // 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++;
- }
+ // 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;
}
- *out = 0;
return out - data_p;
}
@@ -398,6 +422,7 @@ char *COM_ParseExt( char **data_p, qboolean allowLineBreaks )
data = *data_p;
len = 0;
com_token[0] = 0;
+ com_tokenline = 0;
// make sure incoming data is valid
if ( !data )
@@ -437,6 +462,10 @@ char *COM_ParseExt( char **data_p, qboolean allowLineBreaks )
data += 2;
while ( *data && ( *data != '*' || data[1] != '/' ) )
{
+ if ( *data == '\n' )
+ {
+ com_lines++;
+ }
data++;
}
if ( *data )
@@ -450,6 +479,9 @@ char *COM_ParseExt( char **data_p, qboolean allowLineBreaks )
}
}
+ // token starts on this line
+ com_tokenline = com_lines;
+
// handle quoted strings
if (c == '\"')
{
@@ -463,6 +495,10 @@ char *COM_ParseExt( char **data_p, qboolean allowLineBreaks )
*data_p = ( char * ) data;
return com_token;
}
+ if ( c == '\n' )
+ {
+ com_lines++;
+ }
if (len < MAX_TOKEN_CHARS - 1)
{
com_token[len] = c;
@@ -481,8 +517,6 @@ char *COM_ParseExt( char **data_p, qboolean allowLineBreaks )
}
data++;
c = *data;
- if ( c == '\n' )
- com_lines++;
} while (c>32);
com_token[len] = 0;
@@ -491,62 +525,6 @@ char *COM_ParseExt( char **data_p, qboolean allowLineBreaks )
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
@@ -566,16 +544,14 @@ void COM_MatchToken( char **buf_p, char *match ) {
=================
SkipBracedSection
-The next token should be an open brace.
+The next token should be an open brace or set depth to 1 if already parsed it.
Skips until a matching close brace is found.
Internal brace depths are properly skipped.
=================
*/
-void SkipBracedSection (char **program) {
+qboolean SkipBracedSection (char **program, int depth) {
char *token;
- int depth;
- depth = 0;
do {
token = COM_ParseExt( program, qtrue );
if( token[1] == 0 ) {
@@ -587,6 +563,8 @@ void SkipBracedSection (char **program) {
}
}
} while( depth && *program );
+
+ return ( depth == 0 );
}
/*
@@ -599,6 +577,10 @@ void SkipRestOfLine ( char **data ) {
int c;
p = *data;
+
+ if ( !*p )
+ return;
+
while ( (c = *p++) != 0 ) {
if ( c == '\n' ) {
com_lines++;
@@ -648,6 +630,45 @@ void Parse3DMatrix (char **buf_p, int z, int y, int x, float *m) {
COM_MatchToken( buf_p, ")" );
}
+/*
+===================
+Com_HexStrToInt
+===================
+*/
+int Com_HexStrToInt( const char *str )
+{
+ if ( !str || !str[ 0 ] )
+ return -1;
+
+ // check for hex code
+ if( str[ 0 ] == '0' && str[ 1 ] == 'x' )
+ {
+ size_t i;
+ int n = 0;
+
+ for( i = 2; i < strlen( str ); i++ )
+ {
+ char digit;
+
+ n *= 16;
+
+ digit = tolower( str[ i ] );
+
+ if( digit >= '0' && digit <= '9' )
+ digit -= '0';
+ else if( digit >= 'a' && digit <= 'f' )
+ digit = digit - 'a' + 10;
+ else
+ return -1;
+
+ n += digit;
+ }
+
+ return n;
+ }
+
+ return -1;
+}
/*
============================================================================
@@ -685,32 +706,56 @@ int Q_isalpha( int c )
return ( 0 );
}
-int Q_isdigit( int c )
+qboolean Q_isanumber( const char *s )
{
- if ((c >= '0' && c <= '9'))
- return ( 1 );
- return ( 0 );
+ char *p;
+ double UNUSED_VAR d;
+
+ if( *s == '\0' )
+ return qfalse;
+
+ d = strtod( s, &p );
+
+ return *p == '\0';
}
-char* Q_strrchr( const char* string, int c )
+qboolean Q_isintegral( float f )
{
- char cc = c;
- char *s;
- char *sp=(char *)0;
+ return (int)f == f;
+}
- s = (char*)string;
+#ifdef _MSC_VER
+/*
+=============
+Q_vsnprintf
+
+Special wrapper function for Microsoft's broken _vsnprintf() function.
+MinGW comes with its own snprintf() which is not broken.
+=============
+*/
+
+int Q_vsnprintf(char *str, size_t size, const char *format, va_list ap)
+{
+ int retval;
+
+ retval = _vsnprintf(str, size, format, ap);
- while (*s)
+ if(retval < 0 || retval == size)
{
- if (*s == cc)
- sp = s;
- s++;
+ // Microsoft doesn't adhere to the C99 standard of vsnprintf,
+ // which states that the return value must be the number of
+ // bytes written if the output string had sufficient length.
+ //
+ // Obviously we cannot determine that value from Microsoft's
+ // implementation, so we have no choice but to return size.
+
+ str[size - 1] = '\0';
+ return size;
}
- if (cc == 0)
- sp = s;
-
- return sp;
+
+ return retval;
}
+#endif
/*
=============
@@ -720,7 +765,6 @@ 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" );
}
@@ -738,7 +782,6 @@ void Q_strncpyz( char *dest, const char *src, int destsize ) {
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;
@@ -832,6 +875,38 @@ void Q_strcat( char *dest, int size, const char *src ) {
Q_strncpyz( dest + l1, src, size - l1 );
}
+/*
+* Find the first occurrence of find in s.
+*/
+const char *Q_stristr( const char *s, const char *find)
+{
+ char c, sc;
+ size_t len;
+
+ if ((c = *find++) != 0)
+ {
+ if (c >= 'a' && c <= 'z')
+ {
+ c -= ('a' - 'A');
+ }
+ len = strlen(find);
+ do
+ {
+ do
+ {
+ if ((sc = *s++) == 0)
+ return NULL;
+ if (sc >= 'a' && sc <= 'z')
+ {
+ sc -= ('a' - 'A');
+ }
+ } while (sc != c);
+ } while (Q_stricmpn(s, find, len) != 0);
+ s--;
+ }
+ return s;
+}
+
int Q_PrintStrlen( const char *string ) {
int len;
@@ -877,29 +952,52 @@ char *Q_CleanStr( char *string ) {
return string;
}
+int Q_CountChar(const char *string, char tocount)
+{
+ int count;
+
+ for(count = 0; *string; string++)
+ {
+ if(*string == tocount)
+ count++;
+ }
+
+ return count;
+}
-void QDECL Com_sprintf( char *dest, int size, const char *fmt, ...) {
+void Q_StripIndentMarker(char *string)
+{
+ int i, j;
+
+ for (i = j = 0; string[i]; i++) {
+ if (string[i] != INDENT_MARKER) {
+ string[j++] = string[i];
+ }
+ }
+ string[j] = 0;
+}
+
+void Q_ParseNewlines( char *dest, const char *src, int destsize )
+{
+ for( ; *src && destsize > 1; src++, destsize-- )
+ *dest++ = ( ( *src == '\\' && *( ++src ) == 'n' ) ? '\n' : *src );
+ *dest++ = '\0';
+}
+
+int 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);
+ len = Q_vsnprintf(dest, size, 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 );
-}
+ if(len >= size)
+ Com_Printf("Com_sprintf: Output length %d too short, require %d bytes.\n", size, len + 1);
+
+ return len;
+}
/*
============
@@ -907,20 +1005,19 @@ 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, ... ) {
+const char * QDECL va(const char *format, ... ) {
va_list argptr;
- static char string[2][32000]; // in case va is called by nested functions
- static int index = 0;
- char *buf;
+ 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);
+ Q_vsnprintf (buf, sizeof(*string), format, argptr);
va_end (argptr);
return buf;
@@ -1100,7 +1197,8 @@ void Info_RemoveKey( char *s, const char *key ) {
if (!strcmp (key, pkey) )
{
- strcpy (start, s); // remove this part
+ memmove(start, s, strlen(s) + 1); // remove this part
+
return;
}
@@ -1155,7 +1253,7 @@ void Info_RemoveKey_Big( char *s, const char *key ) {
if (!strcmp (key, pkey) )
{
- strcpy (start, s); // remove this part
+ memmove(start, s, strlen(s) + 1); // remove this part
return;
}
@@ -1241,6 +1339,7 @@ void Info_SetValueForKey( char *s, const char *key, const char *value ) {
Info_SetValueForKey_Big
Changes or adds a key/value pair
+Includes and retains zero-length values
==================
*/
void Info_SetValueForKey_Big( char *s, const char *key, const char *value ) {
@@ -1261,14 +1360,15 @@ void Info_SetValueForKey_Big( char *s, const char *key, const char *value ) {
}
Info_RemoveKey_Big (s, key);
- if (!value || !strlen(value))
+ if (!value)
return;
Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value);
if (strlen(newi) + strlen(s) >= BIG_INFO_STRING)
{
- Com_Printf ("BIG Info string length exceeded\n");
+ Com_Printf ("BIG Info string length exceeded: setting %s to %s "
+ "failed\n", key, value);
return;
}
@@ -1285,9 +1385,9 @@ void Info_SetValueForKey_Big( char *s, const char *key, const char *value ) {
Com_CharIsOneOfCharset
==================
*/
-static qboolean Com_CharIsOneOfCharset( char c, char *set )
+static qboolean Com_CharIsOneOfCharset( char c, const char *set )
{
- int i;
+ size_t i;
for( i = 0; i < strlen( set ); i++ )
{
@@ -1303,7 +1403,7 @@ static qboolean Com_CharIsOneOfCharset( char c, char *set )
Com_SkipCharset
==================
*/
-char *Com_SkipCharset( char *s, char *sep )
+char *Com_SkipCharset( char *s, const char *sep )
{
char *p = s;
@@ -1323,7 +1423,7 @@ char *Com_SkipCharset( char *s, char *sep )
Com_SkipTokens
==================
*/
-char *Com_SkipTokens( char *s, int numTokens, char *sep )
+char *Com_SkipTokens( char *s, int numTokens, const char *sep )
{
int sepCount = 0;
char *p = s;
@@ -1345,3 +1445,85 @@ char *Com_SkipTokens( char *s, int numTokens, char *sep )
else
return s;
}
+
+/*
+============
+Com_ClientListContains
+============
+*/
+qboolean Com_ClientListContains( const clientList_t *list, int clientNum )
+{
+ if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list )
+ return qfalse;
+ if( clientNum < 32 )
+ return ( ( list->lo & ( 1 << clientNum ) ) != 0 );
+ else
+ return ( ( list->hi & ( 1 << ( clientNum - 32 ) ) ) != 0 );
+}
+
+/*
+============
+Com_ClientListAdd
+============
+*/
+void Com_ClientListAdd( clientList_t *list, int clientNum )
+{
+ if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list )
+ return;
+ if( clientNum < 32 )
+ list->lo |= ( 1 << clientNum );
+ else
+ list->hi |= ( 1 << ( clientNum - 32 ) );
+}
+
+/*
+============
+Com_ClientListRemove
+============
+*/
+void Com_ClientListRemove( clientList_t *list, int clientNum )
+{
+ if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list )
+ return;
+ if( clientNum < 32 )
+ list->lo &= ~( 1 << clientNum );
+ else
+ list->hi &= ~( 1 << ( clientNum - 32 ) );
+}
+
+/*
+============
+Com_ClientListString
+============
+*/
+char *Com_ClientListString( const clientList_t *list )
+{
+ static char s[ 17 ];
+
+ s[ 0 ] = '\0';
+ if( !list )
+ return s;
+ Com_sprintf( s, sizeof( s ), "%08x%08x", list->hi, list->lo );
+ return s;
+}
+
+/*
+============
+Com_ClientListParse
+============
+*/
+void Com_ClientListParse( clientList_t *list, const char *s )
+{
+ char t[ 9 ];
+ if( !list )
+ return;
+ list->lo = 0;
+ list->hi = 0;
+ if( !s )
+ return;
+ if( strlen( s ) != 16 )
+ return;
+ Q_strncpyz( t, s, 9 );
+ sscanf( t, "%x", &list->hi );
+ sscanf( s + 8, "%x", &list->lo );
+}
diff --git a/src/qcommon/q_shared.h b/src/qcommon/q_shared.h
index 06f1bb2..a854de8 100644
--- a/src/qcommon/q_shared.h
+++ b/src/qcommon/q_shared.h
@@ -1,13 +1,14 @@
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
-Copyright (C) 2000-2006 Tim Angus
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
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,
+published by the Free Software Foundation; either version 3 of the License,
or (at your option) any later version.
Tremulous is distributed in the hope that it will be
@@ -16,28 +17,42 @@ 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
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
===========================================================================
*/
//
#ifndef __Q_SHARED_H
#define __Q_SHARED_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
// 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
+#define PRODUCT_NAME "tremulous"
+
+#ifndef PRODUCT_VERSION
+# define PRODUCT_VERSION "1.3.0 alpha"
#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
+#define CLIENT_WINDOW_TITLE "Tremulous " PRODUCT_VERSION
+#define CLIENT_WINDOW_MIN_TITLE "Tremulous"
+#define Q3_VERSION PRODUCT_NAME " " PRODUCT_VERSION
+
+#define GAMENAME_FOR_MASTER "Tremulous"
+#define HOMEPATH_NAME_UNIX ".tremulous"
+#define HOMEPATH_NAME_WIN "Tremulous"
+#define HOMEPATH_NAME_MACOSX HOMEPATH_NAME_WIN
+
+// Heartbeat for dpmaster protocol. You shouldn't change this unless you know what you're doing
+#define HEARTBEAT_FOR_MASTER GAMENAME_FOR_MASTER
+
+#define MAX_MASTER_SERVERS 5 // number of supported master servers
+
+#define DEMOEXT "dm_" // standard demo extension
#ifdef _MSC_VER
@@ -72,6 +87,12 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#endif
#endif
+#ifdef __GNUC__
+#define UNUSED_VAR __attribute__((unused))
+#else
+#define UNUSED_VAR
+#endif
+
#if (defined _MSC_VER)
#define Q_EXPORT __declspec(dllexport)
#elif (defined __SUNPRO_C)
@@ -100,7 +121,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#ifdef Q3_VM
-#include "../game/bg_lib.h"
+#include "game/bg_lib.h"
+
+typedef int intptr_t;
#else
@@ -114,60 +137,136 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include <ctype.h>
#include <limits.h>
+#ifdef _MSC_VER
+ #include <io.h>
+
+ typedef __int64 int64_t;
+ typedef __int32 int32_t;
+ typedef __int16 int16_t;
+ typedef __int8 int8_t;
+ typedef unsigned __int64 uint64_t;
+ typedef unsigned __int32 uint32_t;
+ typedef unsigned __int16 uint16_t;
+ typedef unsigned __int8 uint8_t;
+
+ // vsnprintf is ISO/IEC 9899:1999
+ // abstracting this to make it portable
+ int Q_vsnprintf(char *str, size_t size, const char *format, va_list ap);
+#else
+ #include <stdint.h>
+
+ #define Q_vsnprintf vsnprintf
+ #define Q_snprintf snprintf
#endif
-#include "q_platform.h"
+#endif
-//=============================================================
-#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
+#include "qcommon/q_platform.h"
+
+//=============================================================
typedef unsigned char byte;
-typedef enum {qfalse, qtrue} qboolean;
+typedef enum {qfalse, qtrue} qboolean;
+
+typedef union {
+ float f;
+ int i;
+ unsigned int ui;
+} floatint_t;
typedef int qhandle_t;
typedef int sfxHandle_t;
typedef int fileHandle_t;
typedef int clipHandle_t;
-#define PAD(x,y) (((x)+(y)-1) & ~((y)-1))
+#define PAD(base, alignment) (((base)+(alignment)-1) & ~((alignment)-1))
+#define PADLEN(base, alignment) (PAD((base), (alignment)) - (base))
+
+#define PADP(base, alignment) ((void *) PAD((intptr_t) (base), (alignment)))
#ifdef __GNUC__
-#define ALIGN(x) __attribute__((aligned(x)))
+#define QALIGN(x) __attribute__((aligned(x)))
#else
-#define ALIGN(x)
+#define QALIGN(x)
#endif
#ifndef NULL
#define NULL ((void *)0)
#endif
+#define STRING(s) #s
+// expand constants before stringifying them
+#define XSTRING(s) STRING(s)
+
#define MAX_QINT 0x7fffffff
#define MIN_QINT (-MAX_QINT-1)
+#define ARRAY_LEN(x) (sizeof(x) / sizeof(*(x)))
+#define STRARRAY_LEN(x) (ARRAY_LEN(x) - 1)
// angle indexes
#define PITCH 0 // up / down
#define YAW 1 // left / right
#define ROLL 2 // fall over
+/* FILESYSTEM */
+enum FS_Mode {
+ FS_READ,
+ FS_WRITE,
+ FS_APPEND,
+ FS_APPEND_SYNC
+};
+
+enum FS_Origin {
+ FS_SEEK_CUR,
+ FS_SEEK_END,
+ FS_SEEK_SET
+};
+
+/* CVAR */
+
+#define CVAR_ARCHIVE 0x0001 // set to cause it to be saved to vars.rc
+// used for system variables, not for player
+// specific configurations
+#define CVAR_USERINFO 0x0002 // sent to server on connect or change
+#define CVAR_SERVERINFO 0x0004 // sent in response to front end requests
+#define CVAR_SYSTEMINFO 0x0008 // these cvars will be duplicated on all clients
+#define CVAR_INIT 0x0010 // don't allow change from console at all,
+// but can be set from the command line
+#define CVAR_LATCH 0x0020 // will only change when C code next does
+// a Cvar_Get(), so it can't be changed without proper initialization.
+// modified will be set, even though the value hasn't changed yet
+#define CVAR_ROM 0x0040 // display only, cannot be set by user at all
+#define CVAR_USER_CREATED 0x0080 // created by a set command
+#define CVAR_TEMP 0x0100 // can be set even when cheats are disabled, but is not archived
+#define CVAR_CHEAT 0x0200 // can not be changed if cheats are disabled
+#define CVAR_NORESTART 0x0400 // do not clear when a cvar_restart is issued
+
+#define CVAR_SERVER_CREATED 0x0800 // cvar was created by a server the client connected to.
+#define CVAR_VM_CREATED 0x1000 // cvar was created exclusively in one of the VMs.
+#define CVAR_PROTECTED 0x2000 // prevent modifying this var from VMs or the server
+#define CVAR_ALTERNATE_SYSTEMINFO 0x1000000
+// These flags are only returned by the Cvar_Flags() function
+#define CVAR_MODIFIED 0x40000000 // Cvar was modified
+#define CVAR_NONEXISTENT 0x80000000 // Cvar doesn't exist.
+
+#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;
+
+
// 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
@@ -182,6 +281,7 @@ typedef int clipHandle_t;
#define BIG_INFO_KEY 8192
#define BIG_INFO_VALUE 8192
+#define MAX_NEWS_STRING 10000
#define MAX_QPATH 64 // max length of a quake game pathname
#ifdef PATH_MAX
@@ -193,7 +293,7 @@ typedef int clipHandle_t;
#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
+#define MAX_SAY_TEXT 800
// paramters for command buffer stuffing
typedef enum {
@@ -203,7 +303,6 @@ typedef enum {
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?
//
@@ -225,37 +324,20 @@ typedef enum {
// 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
+ 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_RECONNECT
} 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)
+#if !defined(NDEBUG) && !defined(BSPC)
#define HUNK_DEBUG
#endif
@@ -267,19 +349,11 @@ typedef enum {
#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 );
+void *Hunk_AllocDebug( int size, ha_pref preference, const char *label, const char *file, int line );
#else
void *Hunk_Alloc( int size, ha_pref preference );
#endif
-#if defined(__GNUC__) && !defined(__MINGW32__) && !defined(__APPLE__)
-// 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
@@ -304,10 +378,6 @@ 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
@@ -348,192 +418,37 @@ extern vec4_t colorYellow;
extern vec4_t colorMagenta;
extern vec4_t colorCyan;
extern vec4_t colorWhite;
-extern vec4_t colorGray;
-extern vec4_t colorOrange;
-extern vec4_t colorRoseBud;
-extern vec4_t colorPaleGreen;
-extern vec4_t colorPaleGolden;
-extern vec4_t colorColumbiaBlue;
-extern vec4_t colorPaleTurquoise;
-extern vec4_t colorPaleVioletRed;
-extern vec4_t colorPalacePaleWhite;
-extern vec4_t colorOlive;
-extern vec4_t colorTomato;
-extern vec4_t colorLime;
-extern vec4_t colorLemon;
-extern vec4_t colorBlueBerry;
-extern vec4_t colorTurquoise;
-extern vec4_t colorWildWatermelon;
-extern vec4_t colorSaltpan;
-extern vec4_t colorGrayChateau;
-extern vec4_t colorRust;
-extern vec4_t colorCopperGreen;
-extern vec4_t colorGold;
-extern vec4_t colorSteelBlue;
-extern vec4_t colorSteelGray;
-extern vec4_t colorBronze;
-extern vec4_t colorSilver;
-extern vec4_t colorDarkGray;
-extern vec4_t colorDarkOrange;
-extern vec4_t colorDarkGreen;
-extern vec4_t colorRedOrange;
-extern vec4_t colorForestGreen;
-extern vec4_t colorBrightSun;
-extern vec4_t colorMediumSlateBlue;
-extern vec4_t colorCeleste;
-extern vec4_t colorIronstone;
-extern vec4_t colorTimberwolf;
-extern vec4_t colorOnyx;
-extern vec4_t colorRosewood;
-extern vec4_t colorKokoda;
-extern vec4_t colorPorsche;
-extern vec4_t colorCloudBurst;
-extern vec4_t colorBlueDiane;
-extern vec4_t colorRope;
-extern vec4_t colorBlonde;
-extern vec4_t colorSmokeyBlack;
-extern vec4_t colorAmericanRose;
-extern vec4_t colorNeonGreen;
-extern vec4_t colorNeonYellow;
-extern vec4_t colorUltramarine;
-extern vec4_t colorTurquoiseBlue;
-extern vec4_t colorDarkMagenta;
-extern vec4_t colorMagicMint;
-extern vec4_t colorLightGray;
-extern vec4_t colorLightSalmon;
-extern vec4_t colorLightGreen;
+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 Q_IsColorString(p) ((p) && *(p) == Q_COLOR_ESCAPE && *((p)+1) && isalnum(*((p)+1))) // ^[0-9a-zA-Z]
-#define COLOR_BLACK '0'
-#define COLOR_RED '1'
-#define COLOR_GREEN '2'
+#define COLOR_BLACK '0'
+#define COLOR_RED '1'
+#define COLOR_GREEN '2'
#define COLOR_YELLOW '3'
-#define COLOR_BLUE '4'
-#define COLOR_CYAN '5'
+#define COLOR_BLUE '4'
+#define COLOR_CYAN '5'
#define COLOR_MAGENTA '6'
-#define COLOR_WHITE '7'
-#define COLOR_GRAY '8'
-#define COLOR_ORANGE '9'
-#define COLOR_ROSE_BUD 'a'
-#define COLOR_PALE_GREEN 'b'
-#define COLOR_PALE_GOLDEN 'c'
-#define COLOR_COLUMBIA_BLUE 'd'
-#define COLOR_PALE_TURQUOISE 'e'
-#define COLOR_PALE_VIOLET_RED 'f'
-#define COLOR_PALACE_PALE_WHITE 'g'
-#define COLOR_OLIVE 'h'
-#define COLOR_TOMATO 'i'
-#define COLOR_LIME 'j'
-#define COLOR_LEMON 'k'
-#define COLOR_BLUE_BERRY 'l'
-#define COLOR_TURQUOISE 'm'
-#define COLOR_WILD_WATERMELON 'n'
-#define COLOR_SALTPAN 'o'
-#define COLOR_GRAY_CHATEAU 'p'
-#define COLOR_RUST 'q'
-#define COLOR_COPPER_GREEN 'r'
-#define COLOR_GOLD 's'
-#define COLOR_STEEL_BLUE 't'
-#define COLOR_STEEL_GRAY 'u'
-#define COLOR_BRONZE 'v'
-#define COLOR_SILVER 'w'
-#define COLOR_DARK_GRAY 'x'
-#define COLOR_DARK_ORANGE 'y'
-#define COLOR_DARK_GREEN 'z'
-#define COLOR_RED_ORANGE 'A'
-#define COLOR_FOREST_GREEN 'B'
-#define COLOR_BRIGHT_SUN 'C'
-#define COLOR_MEDIUM_SLATE_BLUE 'D'
-#define COLOR_CELESTE 'E'
-#define COLOR_IRONSTONE 'F'
-#define COLOR_TIMBERWOLF 'G'
-#define COLOR_ONYX 'H'
-#define COLOR_ROSEWOOD 'I'
-#define COLOR_KOKODA 'J'
-#define COLOR_PORSCHE 'K'
-#define COLOR_CLOUD_BURST 'L'
-#define COLOR_BLUE_DIANE 'M'
-#define COLOR_ROPE 'N'
-#define COLOR_BLONDE 'O'
-#define COLOR_SMOKEY_BLACK 'P'
-#define COLOR_AMERICAN_ROSE 'Q'
-#define COLOR_NEON_GREEN 'R'
-#define COLOR_NEON_YELLOW 'S'
-#define COLOR_ULTRAMARINE 'T'
-#define COLOR_TURQUOISE_BLUE 'U'
-#define COLOR_DARK_MAGENTA 'V'
-#define COLOR_MAGIC_MINT 'W'
-#define COLOR_LIGHT_GRAY 'X'
-#define COLOR_LIGHT_SALMON 'Y'
-#define COLOR_LIGHT_GREEN 'Z'
-#define ColorIndex(c) (((((c) >= '0') && ((c) <= '9')) ? ((c) - '0') : ((((c) >= 'a') && ((c) <= 'z')) ? ((c) - 'a' + 10) : ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 36) : 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"
-#define S_COLOR_GRAY '^8'
-#define S_COLOR_ORANGE '^9'
-#define S_COLOR_ROSE_BUD '^a'
-#define S_COLOR_PALE_GREEN '^b'
-#define S_COLOR_PALE_GOLDEN '^c'
-#define S_COLOR_COLUMBIA_BLUE '^d'
-#define S_COLOR_PALE_TURQUOISE '^e'
-#define S_COLOR_PALE_VIOLET_RED '^f'
-#define S_COLOR_PALACE_PALE_WHITE '^g'
-#define S_COLOR_OLIVE '^h'
-#define S_COLOR_TOMATO '^i'
-#define S_COLOR_LIME '^j'
-#define S_COLOR_LEMON '^k'
-#define S_COLOR_BLUE_BERRY '^l'
-#define S_COLOR_TURQUOISE '^m'
-#define S_COLOR_WILD_WATERMELON '^n'
-#define S_COLOR_SALTPAN '^o'
-#define S_COLOR_GRAY_CHATEAU '^p'
-#define S_COLOR_RUST '^q'
-#define S_COLOR_COPPER_GREEN '^r'
-#define S_COLOR_GOLD '^s'
-#define S_COLOR_STEEL_BLUE '^t'
-#define S_COLOR_STEEL_GRAY '^u'
-#define S_COLOR_BRONZE '^v'
-#define S_COLOR_SILVER '^w'
-#define S_COLOR_DARK_GRAY '^x'
-#define S_COLOR_DARK_ORANGE '^y'
-#define S_COLOR_DARK_GREEN '^z'
-#define S_COLOR_RED_ORANGE '^A'
-#define S_COLOR_FOREST_GREEN '^B'
-#define S_COLOR_BRIGHT_SUN '^C'
-#define S_COLOR_MEDIUM_SLATE_BLUE '^D'
-#define S_COLOR_CELESTE '^E'
-#define S_COLOR_IRONSTONE '^F'
-#define S_COLOR_TIMBERWOLF '^G'
-#define S_COLOR_ONYX '^H'
-#define S_COLOR_ROSEWOOD '^I'
-#define S_COLOR_KOKODA '^J'
-#define S_COLOR_PORSCHE '^K'
-#define S_COLOR_CLOUD_BURST '^L'
-#define S_COLOR_BLUE_DIANE '^M'
-#define S_COLOR_ROPE '^N'
-#define S_COLOR_BLONDE '^O'
-#define S_COLOR_SMOKEY_BLACK '^P'
-#define S_COLOR_AMERICAN_ROSE '^Q'
-#define S_COLOR_NEON_GREEN '^R'
-#define S_COLOR_NEON_YELLOW '^S'
-#define S_COLOR_ULTRAMARINE '^T'
-#define S_COLOR_TURQUOISE_BLUE '^U'
-#define S_COLOR_DARK_MAGENTA '^V'
-#define S_COLOR_MAGIC_MINT '^W'
-#define S_COLOR_LIGHT_GRAY '^X'
-#define S_COLOR_LIGHT_SALMON '^Y'
-#define S_COLOR_LIGHT_GREEN '^Z'
-
-extern vec4_t g_color_table[62];
+#define COLOR_WHITE '7'
+#define ColorIndexForNumber(c) ((c) & 0x07)
+#define ColorIndex(c) (ColorIndexForNumber((c) - '0'))
+
+#define S_COLOR_BLACK "^0"
+#define S_COLOR_RED "^1"
+#define S_COLOR_GREEN "^2"
+#define S_COLOR_YELLOW "^3"
+#define S_COLOR_BLUE "^4"
+#define S_COLOR_CYAN "^5"
+#define S_COLOR_MAGENTA "^6"
+#define S_COLOR_WHITE "^7"
+
+#define INDENT_MARKER '\v'
+void Q_StripIndentMarker(char *string);
+
+extern vec4_t g_color_table[8];
#define MAKERGB( v, r, g, b ) v[0]=r;v[1]=g;v[2]=b
#define MAKERGBA( v, r, g, b, a ) v[0]=r;v[1]=g;v[2]=b;v[3]=a
@@ -550,23 +465,57 @@ extern vec3_t axisDefault[3];
#define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask)
-#if idppc
+int Q_isnan(float x);
+
+#if idx64
+ extern long qftolsse(float f);
+ extern int qvmftolsse(void);
+ extern void qsnapvectorsse(vec3_t vec);
+
+ #define Q_ftol qftolsse
+ #define Q_SnapVector qsnapvectorsse
+
+#elif id386
+ extern long QDECL qftolx87(float f);
+ extern long QDECL qftolsse(float f);
+ extern int QDECL qvmftolx87(void);
+ extern int QDECL qvmftolsse(void);
+ extern void QDECL qsnapvectorx87(vec3_t vec);
+ extern void QDECL qsnapvectorsse(vec3_t vec);
+ extern long (QDECL *Q_ftol)(float f);
+ extern void (QDECL *Q_SnapVector)(vec3_t vec);
+#else
+ // Q_ftol must expand to a function name so the pluggable renderer can take
+ // its address
+ #define Q_ftol lrintf
+ #define Q_SnapVector(vec)\
+ do\
+ {\
+ vec3_t *temp = (vec3_t*)(vec);\
+ \
+ (*temp)[0] = round((*temp)[0]);\
+ (*temp)[1] = round((*temp)[1]);\
+ (*temp)[2] = round((*temp)[2]);\
+ } while(0)
+#endif
+
+#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));
+ float x = 0.5f * number;
+ float y;
+#ifdef __GNUC__
+ asm("frsqrte %0,%1" : "=f" (y) : "f" (number));
#else
- y = __frsqrte( number );
+ y = __frsqrte( number );
#endif
- return y * (1.5f - (x * y * y));
- }
+ return y * (1.5f - (x * y * y));
+}
-#ifdef __GNUC__
+#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;
}
@@ -588,28 +537,15 @@ signed short ClampShort( int i );
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]),\
+#define VectorLerp2( 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
+ (r)[2]=(s)[2]+(f)*((e)[2]-(s)[2]))
#ifdef Q3_VM
#ifdef VectorCopy
@@ -628,9 +564,18 @@ typedef struct {
#define VectorSet(v, x, y, z) ((v)[0]=(x), (v)[1]=(y), (v)[2]=(z))
#define Vector4Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3])
#define Vector4Add(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2],(c)[3]=(a)[3]+(b)[3])
+#define Vector4Lerp( f, s, e, r ) ((r)[0]=(s)[0]+(f)*((e)[0]-(s)[0]),\
+ (r)[1]=(s)[1]+(f)*((e)[1]-(s)[1]),\
+ (r)[2]=(s)[2]+(f)*((e)[2]-(s)[2]),\
+ (r)[3]=(s)[3]+(f)*((e)[3]-(s)[3]))
+
+#define SnapVector(v) ( (v)[0] = (int)(v)[0],\
+ (v)[1] = (int)(v)[1],\
+ (v)[2] = (int)(v)[2] )
+
+#define Byte4Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[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
+// just in case you don'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 );
@@ -651,12 +596,11 @@ void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs );
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 )
+static ID_INLINE int VectorCompareEpsilon( const vec3_t v1, const vec3_t v2, float epsilon )
{
vec3_t d;
@@ -728,7 +672,7 @@ 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 );
@@ -762,6 +706,13 @@ void AxisCopy( vec3_t in[3], vec3_t out[3] );
void SetPlaneSignbits( struct cplane_s *out );
int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *plane);
+qboolean BoundsIntersect(const vec3_t mins, const vec3_t maxs,
+ const vec3_t mins2, const vec3_t maxs2);
+qboolean BoundsIntersectSphere(const vec3_t mins, const vec3_t maxs,
+ const vec3_t origin, vec_t radius);
+qboolean BoundsIntersectPoint(const vec3_t mins, const vec3_t maxs,
+ const vec3_t origin);
+
float AngleMod(float a);
float LerpAngle (float from, float to, float frac);
float AngleSubtract( float a1, float a2 );
@@ -784,7 +735,6 @@ 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 );
@@ -818,7 +768,9 @@ vec_t DistanceBetweenLineSegments(
float Com_Clamp( float min, float max, float value );
char *COM_SkipPath( char *pathname );
+const char *COM_GetExtension( const char *name );
void COM_StripExtension(const char *in, char *out, int destsize);
+qboolean COM_CompareExtension(const char *in, const char *ext);
void COM_DefaultExtension( char *path, int maxSize, const char *extension );
void COM_BeginParseSession( const char *name );
@@ -828,7 +780,6 @@ 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
@@ -854,33 +805,32 @@ typedef struct pc_token_s
void COM_MatchToken( char**buf_p, char *match );
-void SkipBracedSection (char **program);
+qboolean SkipBracedSection (char **program, int depth);
void SkipRestOfLine ( char **data );
void Parse1DMatrix (char **buf_p, int x, float *m);
void Parse2DMatrix (char **buf_p, int y, int x, float *m);
void Parse3DMatrix (char **buf_p, int z, int y, int x, float *m);
+int Com_HexStrToInt( const char *str );
-void QDECL Com_sprintf (char *dest, int size, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
+int 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 );
+char *Com_SkipTokens( char *s, int numTokens, const char *sep );
+char *Com_SkipCharset( char *s, const 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 struct
+{
+ unsigned int hi;
+ unsigned int lo;
+} clientList_t;
-typedef enum {
- FS_SEEK_CUR,
- FS_SEEK_END,
- FS_SEEK_SET
-} fsOrigin_t;
+qboolean Com_ClientListContains( const clientList_t *list, int clientNum );
+void Com_ClientListAdd( clientList_t *list, int clientNum );
+void Com_ClientListRemove( clientList_t *list, int clientNum );
+char *Com_ClientListString( const clientList_t *list );
+void Com_ClientListParse( clientList_t *list, const char *s );
//=============================================
@@ -888,7 +838,8 @@ 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 );
+qboolean Q_isanumber( const char *s );
+qboolean Q_isintegral( float f );
// portable case insensitive compare
int Q_stricmp (const char *s1, const char *s2);
@@ -896,7 +847,7 @@ int Q_strncmp (const char *s1, const char *s2, int n);
int Q_stricmpn (const char *s1, const char *s2, int n);
char *Q_strlwr( char *s1 );
char *Q_strupr( char *s1 );
-char *Q_strrchr( const char* string, int c );
+const char *Q_stristr( const char *s, const char *find);
// buffer size safe library replacements
void Q_strncpyz( char *dest, const char *src, int destsize );
@@ -906,37 +857,17 @@ void Q_strcat( char *dest, int size, const char *src );
int Q_PrintStrlen( const char *string );
// removes color sequences from string
char *Q_CleanStr( char *string );
+// parse "\n" into '\n'
+void Q_ParseNewlines( char *dest, const char *src, int destsize );
+// Count the number of char tocount encountered in string
+int Q_CountChar(const char *string, char tocount);
-//=============================================
-// 64-bit integers for global rankings interface
-// implemented as a struct for qvm compatibility
-typedef struct
-{
- byte b0;
- byte b1;
- byte b2;
- byte b3;
- byte b4;
- byte b5;
- byte b6;
- byte b7;
-} qint64;
+#define rc(x) va("%s^7", x) ///< shortcut for color reset after printing variable
//=============================================
-/*
-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)));
+
+const char * QDECL va(const char *format, ...) __attribute__ ((format (printf, 1, 2)));
#define TRUNCATE_LENGTH 64
void Com_TruncateLongString( char *buffer, const char *s );
@@ -948,78 +879,32 @@ void Com_TruncateLongString( char *buffer, const char *s );
//
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_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_Error( int level, const char *error, ... ) __attribute__ ((noreturn, format(printf, 2, 3)));
void QDECL Com_Printf( const char *msg, ... ) __attribute__ ((format (printf, 1, 2)));
/*
-==========================================================
+==============================================================
-CVARS (console variables)
+VoIP
-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;
+// if you change the count of flags be sure to also change VOIP_FLAGNUM
+#define VOIP_SPATIAL 0x01 // spatialized voip message
+#define VOIP_DIRECT 0x02 // non-spatialized voip message
-// 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;
+// number of flags voip knows. You will have to bump protocol version number if you
+// change this.
+#define VOIP_FLAGCNT 2
/*
==============================================================
@@ -1029,7 +914,7 @@ COLLISION DETECTION
==============================================================
*/
-#include "surfaceflags.h" // shared with the q3map utility
+#include "qcommon/surfaceflags.h" // shared with the q3map utility
// plane types are used to speed some tests
// 0-2 are axial planes
@@ -1070,7 +955,8 @@ typedef enum {
// 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
+// FIXME: startsolid is supposed to be bool
+ int /*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
@@ -1083,15 +969,12 @@ typedef struct {
// trace->entityNum can also be 0 to (MAX_GENTITIES-1)
// or ENTITYNUM_NONE, ENTITYNUM_WORLD
-
-// markfragments are returned by CM_MarkFragments()
+// markfragments are returned by R_MarkFragments()
typedef struct {
int firstPoint;
int numPoints;
} markFragment_t;
-
-
typedef struct {
vec3_t origin;
vec3_t axis[3];
@@ -1104,7 +987,7 @@ typedef struct {
// 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_MESSAGE 0x0004
#define KEYCATCH_CGAME 0x0008
@@ -1146,6 +1029,7 @@ typedef enum {
#define GENTITYNUM_BITS 10 // don't need to send any more
#define MAX_GENTITIES (1<<GENTITYNUM_BITS)
+#define GENTITYNUM_MASK (MAX_GENTITIES - 1)
// entitynums are communicated with GENTITY_BITS, so any reserved
// values that are going to be communcated over the net need to
@@ -1183,7 +1067,7 @@ typedef struct {
#define MAX_STATS 16
#define MAX_PERSISTANT 16
#define MAX_MISC 16
-#define MAX_WEAPONS 16
+#define MAX_WEAPONS 16
#define MAX_PS_EVENTS 2
@@ -1283,7 +1167,7 @@ typedef struct playerState_s {
//
#define BUTTON_ATTACK 1
#define BUTTON_TALK 2 // displays talk balloon and disables actions
-#define BUTTON_USE_HOLDABLE 4
+#define BUTTON_USE_HOLDABLE 4 // activate upgrade
#define BUTTON_GESTURE 8
#define BUTTON_WALKING 16 // walking can't just be infered from MOVE_RUN
// because a key pressed late in the frame will
@@ -1291,12 +1175,9 @@ typedef struct playerState_s {
// 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_DODGE 64 // start a dodge or sprint motion
+#define BUTTON_USE_EVOLVE 128 // use target or open evolve menu
+#define BUTTON_SPRINT 256
#define BUTTON_ANY 2048 // any key whatsoever
@@ -1308,7 +1189,7 @@ typedef struct usercmd_s {
int serverTime;
int angles[3];
int buttons;
- byte weapon; // weapon
+ byte weapon; // weapon
signed char forwardmove, rightmove, upmove;
} usercmd_t;
@@ -1324,7 +1205,7 @@ typedef enum {
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?
+ TR_BUOYANCY
} trType_t;
typedef struct {
@@ -1362,7 +1243,7 @@ typedef struct entityState_s {
int otherEntityNum; // shotgun sources, etc
int otherEntityNum2;
- int groundEntityNum; // -1 = in air
+ int groundEntityNum; // ENTITYNUM_NONE = in air
int constantLight; // r + (g<<8) + (b<<16) + (intensity<<24)
int loopSound; // constantly loop this sound
@@ -1390,7 +1271,7 @@ typedef struct entityState_s {
typedef enum {
CA_UNINITIALIZED,
CA_DISCONNECTED, // not talking to a server
- CA_AUTHORIZING, // not used any more, was checking cd key
+ 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
@@ -1400,13 +1281,14 @@ typedef enum {
CA_CINEMATIC // playing a cinematic or a static pic, not connected to a server
} connstate_t;
-// font support
+// 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
+#define GLYPHS_PER_FONT (GLYPH_END - GLYPH_START + 1)
+
typedef struct {
int height; // number of scan lines
int top; // top of glyph in buffer
@@ -1449,11 +1331,11 @@ typedef struct qtime_s {
// server browser sources
-// TTimo: AS_MPLAYER is no longer used
-#define AS_GLOBAL 0
-#define AS_MPLAYER 1
-#define AS_LOCAL 2
-#define AS_FAVORITES 3
+// AS_MPLAYER is no longer used
+#define AS_GLOBAL 0
+#define AS_MPLAYER 1
+#define AS_LOCAL 2
+#define AS_FAVORITES 3
// cinematic states
@@ -1467,20 +1349,10 @@ typedef enum {
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;
@@ -1490,12 +1362,31 @@ typedef enum {
#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
-#define SAY_HADMINS 6
+#define MAX_EMOTICON_NAME_LEN 16
+#define MAX_EMOTICONS 64
+typedef struct
+{
+ char name[ MAX_EMOTICON_NAME_LEN ];
+#ifndef GAME
+ int width;
+ qhandle_t shader;
+#endif
+} emoticon_t;
+
+// flags for com_downloadPrompt
+#define DLP_TYPE_MASK 0x0f
+#define DLP_IGNORE 0x01 // don't download anything
+#define DLP_CURL 0x02 // download via HTTP redirect
+#define DLP_UDP 0x04 // download from server
+#define DLP_SHOW 0x10 // prompt needs to be shown
+#define DLP_PROMPTED 0x20 // prompt has been processed by client
+#define DLP_STALE 0x40 // prompt is not being shown by UI VM
+
+#define LERP( a, b, w ) ( ( a ) * ( 1.0f - ( w ) ) + ( b ) * ( w ) )
+#define LUMA( red, green, blue ) ( 0.2126f * ( red ) + 0.7152f * ( green ) + 0.0722f * ( blue ) )
+
+#ifdef __cplusplus
+};
+#endif
#endif // __Q_SHARED_H
diff --git a/src/qcommon/qcommon.h b/src/qcommon/qcommon.h
new file mode 100644
index 0000000..f258ce9
--- /dev/null
+++ b/src/qcommon/qcommon.h
@@ -0,0 +1,413 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// qcommon.h -- definitions common between client and server, but not game.or ref modules
+#ifndef _QCOMMON_H_
+#define _QCOMMON_H_
+
+#include <stdbool.h>
+
+#include "cm_public.h"
+
+//Ignore __attribute__ on non-gcc platforms
+#ifndef __GNUC__
+#ifndef __attribute__
+#define __attribute__(x)
+#endif
+#endif
+
+struct netadr_t;
+struct msg_t;
+
+/*
+==============================================================
+
+PROTOCOL
+
+==============================================================
+*/
+
+#define PROTOCOL_VERSION 71
+
+// maintain a list of compatible protocols for demo playing
+// NOTE: that stuff only works with two digits protocols
+extern int demo_protocols[];
+
+// override on command line, config files etc.
+#ifndef MASTER_SERVER_NAME
+#define MASTER_SERVER_NAME "master.tremulous.net"
+#endif
+
+#define PORT_MASTER 30700
+#define PORT_SERVER 30720
+#define ALT1PORT_MASTER 30700
+#define ALT1PORT_SERVER 30721
+#define ALT2PORT_MASTER 30710
+#define ALT2PORT_SERVER 30722
+#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,
+
+// new commands, supported only by ioquake3 protocol but not legacy
+ svc_voipSpeex, // not wrapped in USE_VOIP, so this value is reserved.
+ svc_voipOpus, //
+};
+
+
+//
+// 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,
+
+// new commands, supported only by ioquake3 protocol but not legacy
+ clc_voipSpeex, // not wrapped in USE_VOIP, so this value is reserved.
+ clc_voipOpus, //
+};
+
+//#include "cvar.h"
+
+typedef struct cvar_s cvar_t;
+
+/*
+==============================================================
+
+Edit fields and command line history/completion
+
+==============================================================
+*/
+
+#define MAX_EDIT_LINE 256
+typedef struct {
+ int cursor;
+ int scroll;
+ int widthInChars;
+ char buffer[MAX_EDIT_LINE];
+} field_t;
+
+void Field_Clear( field_t *edit );
+void Field_AutoComplete( field_t *edit );
+void Field_CompleteKeyname( void );
+void Field_CompleteFilename( const char *dir, const char *ext, bool stripExt, bool allowNonPureFilesOnDisk );
+void Field_CompleteCommand( char *cmd, bool doCommands, bool doCvars );
+void Field_CompletePlayerName( const char **names, int count );
+void Field_CompleteList( char *listJson );
+
+/*
+==============================================================
+
+MISC
+
+==============================================================
+*/
+
+// centralized and cleaned, that's the max string you can send to a Com_Printf / Com_DPrintf (above gets truncated)
+#define MAXPRINTMSG 4096
+
+
+typedef enum {
+ // SE_NONE must be zero
+ SE_NONE = 0, // evTime is still valid
+ SE_KEY, // evValue is a key code, evValue2 is the down flag
+ SE_CHAR, // evValue is an ascii char
+ SE_MOUSE, // evValue and evValue2 are relative signed x / y moves
+ SE_JOYSTICK_AXIS, // evValue is an axis number and evValue2 is the current state (-127 to 127)
+ SE_CONSOLE // evPtr is a char*
+} sysEventType_t;
+
+typedef struct {
+ int evTime;
+ sysEventType_t evType;
+ int evValue, evValue2;
+ int evPtrLength; // bytes of data pointed to by evPtr, for journaling
+ void *evPtr; // this must be manually freed if not NULL
+} sysEvent_t;
+
+void Com_QueueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr );
+int Com_EventLoop( void );
+sysEvent_t Com_GetSystemEvent( void );
+
+char *CopyString( const char *in );
+void Info_Print( const char *s );
+
+void Com_BeginRedirect (char *buffer, int buffersize, void (*flush)(char *));
+void Com_EndRedirect( void );
+
+//#ifndef __Q_SHARED_H
+void QDECL Com_Printf( const char *fmt, ... ) __attribute__ ((format (printf, 1, 2)));
+void QDECL Com_Error( int code, const char *fmt, ... ) __attribute__ ((noreturn, format(printf, 2, 3)));
+//#endif
+void QDECL Com_DPrintf( const char *fmt, ... ) __attribute__ ((format (printf, 1, 2)));
+void Engine_Exit(const char* p ) __attribute__ ((noreturn));
+void Com_Quit_f( void ) __attribute__ ((noreturn));
+void Com_GameRestart(int checksumFeed, bool disconnect);
+
+int Com_Milliseconds( void ); // will be journaled properly
+char *Com_MD5File(const char *filename, int length, const char *prefix, int prefix_len);
+int Com_Filter(const char* filter, char *name, int casesensitive);
+int Com_FilterPath(const char *filter, char *name, int casesensitive);
+int Com_RealTime(qtime_t *qtime);
+bool Com_SafeMode( void );
+void Com_RunAndTimeServerPacket(struct netadr_t *evFrom, struct msg_t *buf);
+
+bool Com_IsVoipTarget(uint8_t *voipTargets, int voipTargetsSize, int clientNum);
+
+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.
+
+bool Com_PlayerNameToFieldString( char *str, int length, const char *name );
+bool Com_FieldStringToPlayerName( char *name, int length, const char *rawname );
+int QDECL Com_strCompare( const void *a, const void *b );
+
+
+extern cvar_t *com_developer;
+extern cvar_t *com_dedicated;
+extern cvar_t *com_speeds;
+extern cvar_t *com_timescale;
+extern cvar_t *com_sv_running;
+extern cvar_t *com_cl_running;
+extern cvar_t *com_version;
+extern cvar_t *com_buildScript; // for building release pak files
+extern cvar_t *com_journal;
+extern cvar_t *com_cameraMode;
+extern cvar_t *com_ansiColor;
+extern cvar_t *com_unfocused;
+extern cvar_t *com_maxfpsUnfocused;
+extern cvar_t *com_minimized;
+extern cvar_t *com_maxfpsMinimized;
+extern cvar_t *com_altivec;
+extern cvar_t *com_homepath;
+
+// both client and server must agree to pause
+extern cvar_t *cl_paused;
+extern cvar_t *sv_paused;
+
+extern cvar_t *cl_packetdelay;
+extern cvar_t *sv_packetdelay;
+
+extern cvar_t *com_gamename;
+
+// com_speeds times
+extern int time_game;
+extern int time_frontend;
+extern int time_backend; // renderer backend time
+
+extern int com_frameTime;
+
+extern bool com_errorEntered;
+extern bool com_fullyInitialized;
+
+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(NDEBUG) && !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, const char *label, const char *file, int line ); // NOT 0 filled memory
+void *Z_MallocDebug( int size, const char *label, const char *file, int line ); // returns 0 filled memory
+void *S_MallocDebug( int size, const char *label, const 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 );
+bool 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 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( bool showMainMenu );
+void CL_Shutdown(const char *finalmsg, bool disconnect, bool quit);
+void CL_Frame( int msec );
+bool CL_GameCommand( void );
+void CL_KeyEvent (int key, bool 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( struct netadr_t from, struct msg_t *msg );
+
+void CL_ConsolePrint( const 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_FlushMemory( void );
+// dump all memory on an error
+
+void CL_ShutdownAll(bool shutdownRef);
+// shutdown client
+
+void CL_StartHunkUsers( bool rendererOnly );
+// start all the client stuff using the hunk
+
+void Key_KeynameCompletion( void(*callback)(const char *s) );
+// for keyname autocompletion
+
+void Key_WriteBindings( fileHandle_t f );
+// for writing the config files
+
+void S_ClearSoundBuffer( void );
+// call before filesystem access
+
+void SCR_DebugGraph (float value); // FIXME: move logging to common?
+
+//
+// server interface
+//
+void SV_Init( void );
+void SV_Shutdown( const char *finalmsg );
+void SV_Frame( int msec );
+void SV_PacketEvent( struct netadr_t from, struct msg_t *msg );
+int SV_FrameMsec(void);
+bool SV_GameCommand( void );
+int SV_SendQueuedPackets(void);
+
+//
+// UI interface
+//
+bool UI_GameCommand( void );
+
+/*
+==============================================================
+
+NON-PORTABLE SYSTEM SERVICES
+
+==============================================================
+*/
+
+bool Parse_AddGlobalDefine(char *string);
+int Parse_LoadSourceHandle(const char *filename);
+bool Parse_FreeSourceHandle(int handle);
+bool Parse_ReadTokenHandle(int handle, pc_token_t *pc_token);
+bool Parse_SourceFileAndLine(int handle, char *filename, int *line);
+
+// 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
index 7e901b7..91bcf04 100644
--- a/src/qcommon/qfiles.h
+++ b/src/qcommon/qfiles.h
@@ -1,13 +1,14 @@
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
-Copyright (C) 2000-2006 Tim Angus
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
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,
+published by the Free Software Foundation; either version 3 of the License,
or (at your option) any later version.
Tremulous is distributed in the hope that it will be
@@ -16,19 +17,21 @@ 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
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
===========================================================================
*/
+
+// qfiles.h: quake file formats
+
#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
+#include "q_shared.h"
+
+// Ignore __attribute__ on non-gcc platforms
#ifndef __GNUC__
#ifndef __attribute__
#define __attribute__(x)
@@ -36,12 +39,11 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#endif
// surface geometry should not exceed these limits
-#define SHADER_MAX_VERTEXES 1000
-#define SHADER_MAX_INDEXES (6*SHADER_MAX_VERTEXES)
-
+#define SHADER_MAX_VERTEXES 1000
+#define SHADER_MAX_INDEXES (6 * SHADER_MAX_VERTEXES)
// the maximum size of game relative pathnames
-#define MAX_QPATH 64
+#define MAX_QPATH 64
/*
========================================================================
@@ -51,69 +53,25 @@ QVM files
========================================================================
*/
-#define VM_MAGIC 0x12721444
-#define VM_MAGIC_VER2 0x12721445
+#define VM_MAGIC 0x12721444
+#define VM_MAGIC_VER2 0x12721445
typedef struct {
- int vmMagic;
+ int vmMagic;
- int instructionCount;
+ int instructionCount;
- int codeOffset;
- int codeLength;
+ 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
+ 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
+ //!!! 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;
-
-
-
/*
========================================================================
@@ -122,195 +80,112 @@ typedef struct _TargaHeader {
========================================================================
*/
-#define MD3_IDENT (('3'<<24)+('P'<<16)+('D'<<8)+'I')
-#define MD3_VERSION 15
+#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
+#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)
+#define MD3_XYZ_SCALE (1.0 / 64)
typedef struct md3Frame_s {
- vec3_t bounds[2];
- vec3_t localOrigin;
- float radius;
- char name[16];
+ 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];
+ 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
+** 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; //
+ int ident; //
- char name[MAX_QPATH]; // polyset name
+ char name[MAX_QPATH]; // polyset name
- int flags;
- int numFrames; // all surfaces in a model should have the same
+ 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 numShaders; // all surfaces in a model should have the same
+ int numVerts;
- int numTriangles;
- int ofsTriangles;
+ 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 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
+ int ofsEnd; // next surface follows
} md3Surface_t;
typedef struct {
- char name[MAX_QPATH];
- int shaderIndex; // for in-game use
+ char name[MAX_QPATH];
+ int shaderIndex; // for in-game use
} md3Shader_t;
typedef struct {
- int indexes[3];
+ int indexes[3];
} md3Triangle_t;
typedef struct {
- float st[2];
+ float st[2];
} md3St_t;
typedef struct {
- short xyz[3];
- short normal;
+ short xyz[3];
+ short normal;
} md3XyzNormal_t;
typedef struct {
- int ident;
- int version;
+ int ident;
+ int version;
- char name[MAX_QPATH]; // model name
+ char name[MAX_QPATH]; // model name
- int flags;
+ int flags;
- int numFrames;
- int numTags;
- int numSurfaces;
+ int numFrames;
+ int numTags;
+ int numSurfaces;
- int numSkins;
+ int numSkins;
- int ofsFrames; // offset for first frame
- int ofsTags; // numFrames * numTags
- int ofsSurfaces; // first surface, others follow
+ int ofsFrames; // offset for first frame
+ int ofsTags; // numFrames * numTags
+ int ofsSurfaces; // first surface, others follow
- int ofsEnd; // end of file
+ int ofsEnd; // end of file
} md3Header_t;
/*
==============================================================================
-MD4 file format
+MDR 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
@@ -326,116 +201,108 @@ typedef struct {
* - 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
+#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;
+ 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
+ vec3_t normal;
+ vec2_t texCoords;
+ int numWeights;
+ mdrWeight_t weights[1]; // variable sized
} mdrVertex_t;
typedef struct {
- int indexes[3];
+ int indexes[3];
} mdrTriangle_t;
typedef struct {
- int ident;
+ int ident;
- char name[MAX_QPATH]; // polyset name
- char shader[MAX_QPATH];
- int shaderIndex; // for in-game use
+ 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 ofsHeader; // this will be a negative number
- int numVerts;
- int ofsVerts;
+ int numVerts;
+ int ofsVerts;
- int numTriangles;
- int ofsTriangles;
+ 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;
+ // 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
+ int ofsEnd; // next surface follows
} mdrSurface_t;
typedef struct {
- float matrix[3][4];
+ 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]
+ 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
+ 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]
+ 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
+ int numSurfaces;
+ int ofsSurfaces; // first surface, others follow
+ int ofsEnd; // next lod follows
} mdrLOD_t;
typedef struct {
- int boneIndex;
- char name[32];
+ int boneIndex;
+ char name[32];
} mdrTag_t;
typedef struct {
- int ident;
- int version;
+ int ident;
+ int version;
- char name[MAX_QPATH]; // model name
+ 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]
+ // 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;
+ // each level of detail has completely separate sets of surfaces
+ int numLODs;
+ int ofsLODs;
- int numTags;
- int ofsTags;
+ int numTags;
+ int ofsTags;
- int ofsEnd; // end of file
+ int ofsEnd; // end of file
} mdrHeader_t;
-#endif
-
/*
==============================================================================
@@ -444,183 +311,172 @@ typedef struct {
==============================================================================
*/
+#define BSP_IDENT (('P' << 24) + ('S' << 16) + ('B' << 8) + 'I')
+// little-endian "IBSP"
-#define BSP_IDENT (('P'<<24)+('S'<<16)+('B'<<8)+'I')
- // little-endian "IBSP"
-
-#define BSP_VERSION 46
-
+#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
-
+#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
+#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 ANGLE_UP -1
+#define ANGLE_DOWN -2
-#define LIGHTMAP_WIDTH 128
-#define LIGHTMAP_HEIGHT 128
+#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 )
+#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;
+ 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];
+#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;
+ float mins[3], maxs[3];
+ int firstSurface, numSurfaces;
+ int firstBrush, numBrushes;
} dmodel_t;
typedef struct {
- char shader[MAX_QPATH];
- int surfaceFlags;
- int contentFlags;
+ 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;
+ 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];
+ 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 cluster; // -1 = opaque cluster (do I still store these?)
+ int area;
- int mins[3]; // for frustum culling
- int maxs[3];
+ int mins[3]; // for frustum culling
+ int maxs[3];
- int firstLeafSurface;
- int numLeafSurfaces;
+ int firstLeafSurface;
+ int numLeafSurfaces;
- int firstLeafBrush;
- int numLeafBrushes;
+ int firstLeafBrush;
+ int numLeafBrushes;
} dleaf_t;
typedef struct {
- int planeNum; // positive plane side faces out of the leaf
- int shaderNum;
+ 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
+ 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)
+ 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];
+ 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}}
+#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 enum { MST_BAD, MST_PLANAR, MST_PATCH, MST_TRIANGLE_SOUP, MST_FLARE } mapSurfaceType_t;
typedef struct {
- int shaderNum;
- int fogNum;
- int surfaceType;
+ int shaderNum;
+ int fogNum;
+ int surfaceType;
- int firstVert;
- int numVerts;
+ int firstVert;
+ int numVerts;
- int firstIndex;
- int numIndexes;
+ int firstIndex;
+ int numIndexes;
- int lightmapNum;
- int lightmapX, lightmapY;
- int lightmapWidth, lightmapHeight;
+ int lightmapNum;
+ int lightmapX, lightmapY;
+ int lightmapWidth, lightmapHeight;
- vec3_t lightmapOrigin;
- vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds
+ vec3_t lightmapOrigin;
+ vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds
- int patchWidth;
- int patchHeight;
+ int patchWidth;
+ int patchHeight;
} dsurface_t;
-
#endif
diff --git a/src/qcommon/surfaceflags.h b/src/qcommon/surfaceflags.h
index 31ece5c..f47006d 100644
--- a/src/qcommon/surfaceflags.h
+++ b/src/qcommon/surfaceflags.h
@@ -1,13 +1,14 @@
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
-Copyright (C) 2000-2006 Tim Angus
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
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,
+published by the Free Software Foundation; either version 3 of the License,
or (at your option) any later version.
Tremulous is distributed in the hope that it will be
@@ -16,8 +17,8 @@ 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
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
===========================================================================
*/
//
@@ -60,10 +61,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define CONTENTS_TRIGGER 0x40000000
#define CONTENTS_NODROP 0x80000000 // don't leave bodies or items (death fog, lava)
-//TA: custominfoparms below
+// custominfoparms below
#define CONTENTS_NOALIENBUILD 0x1000 //disallow alien building
-#define CONTENTS_NOHUMANBUILD 0x2000 //disallow alien building
-#define CONTENTS_NOBUILD 0x4000 //disallow alien building
+#define CONTENTS_NOHUMANBUILD 0x2000 //disallow human building
+#define CONTENTS_NOBUILD 0x4000 //disallow building
#define SURF_NODAMAGE 0x1 // never give falling damage
#define SURF_SLICK 0x2 // effects game physics
@@ -85,7 +86,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#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
+// custominfoparms below
#define SURF_NOALIENBUILD 0x80000 //disallow alien building
-#define SURF_NOHUMANBUILD 0x100000 //disallow alien building
-#define SURF_NOBUILD 0x200000 //disallow alien building
+#define SURF_NOHUMANBUILD 0x100000 //disallow human building
+#define SURF_NOBUILD 0x200000 //disallow building
diff --git a/src/qcommon/unzip.cpp b/src/qcommon/unzip.cpp
new file mode 100644
index 0000000..8564ece
--- /dev/null
+++ b/src/qcommon/unzip.cpp
@@ -0,0 +1,1951 @@
+/* unzip.c -- IO for uncompress .zip files using zlib
+ Version 1.1, February 14h, 2010
+ part of the MiniZip project
+
+ Copyright (C) 1998-2010 Gilles Vollant
+ http://www.winimage.com/zLibDll/minizip.html
+ Modifications of Unzip for Zip64
+ Copyright (C) 2007-2008 Even Rouault
+ Modifications for Zip64 support on both zip and unzip
+ Copyright (C) 2009-2010 Mathias Svensson
+ http://result42.com
+ Modifications for AES, PKWARE disk spanning
+ Copyright (C) 2010-2014 Nathan Moinvaziri
+
+ This program is distributed under the terms of the same license as zlib.
+ See the accompanying LICENSE file for the full text of the license.
+
+ Mar 8th, 2016 - Lucio Cosmo
+ Fixed support for 64bit builds for archives with "PKWARE" password.
+ Changed long, unsigned long, unsigned to unsigned int in
+ access functions to crctables and pkeys
+*/
+
+#define NOUNCRYPT
+#include "unzip.h"
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <cstddef>
+
+#include "q_shared.h"
+#include "qcommon.h"
+
+#include "zconf.h"
+#include "zlib.h"
+
+#ifdef HAVE_AES
+# define AES_METHOD (99)
+# define AES_PWVERIFYSIZE (2)
+# define AES_MAXSALTLENGTH (16)
+# define AES_AUTHCODESIZE (10)
+# define AES_HEADERSIZE (11)
+# define AES_KEYSIZE(mode) (64 + (mode * 64))
+
+# include "aes/aes.h"
+# include "aes/fileenc.h"
+#endif
+#ifndef NOUNCRYPT
+# include "crypt.h"
+#endif
+
+#ifndef static
+# define static static
+#endif
+/* compile with -Dlocal if your debugger can't find static symbols */
+
+#define DISKHEADERMAGIC (0x08074b50)
+#define LOCALHEADERMAGIC (0x04034b50)
+#define CENTRALHEADERMAGIC (0x02014b50)
+#define ENDHEADERMAGIC (0x06054b50)
+#define ZIP64ENDHEADERMAGIC (0x06064b50)
+#define ZIP64ENDLOCHEADERMAGIC (0x07064b50)
+
+#define SIZECENTRALDIRITEM (0x2e)
+#define SIZECENTRALHEADERLOCATOR (0x14) /* 20 */
+#define SIZEZIPLOCALHEADER (0x1e)
+
+#ifndef BUFREADCOMMENT
+# define BUFREADCOMMENT (0x400)
+#endif
+
+#ifndef UNZ_BUFSIZE
+# define UNZ_BUFSIZE (64 * 1024)
+#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
+
+const char unz_copyright[] =
+ " unzip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll";
+
+/* unz_file_info_interntal contain internal info about a file in zipfile*/
+typedef struct unz_file_info64_internal_s
+{
+ ZPOS64_T offset_curfile; /* relative offset of static header 8 bytes */
+ ZPOS64_T byte_before_the_zipfile; /* byte before the zipfile, (>0 for sfx) */
+#ifdef HAVE_AES
+ uLong aes_encryption_mode;
+ uLong aes_compression_method;
+ uLong aes_version;
+#endif
+} unz_file_info64_internal;
+
+/* file_in_zip_read_info_s contain internal information about a file in zipfile */
+typedef struct
+{
+ Bytef *read_buffer; /* internal buffer for compressed data */
+ z_stream stream; /* zLib stream structure for inflate */
+
+#ifdef HAVE_BZIP2
+ bz_stream bstream; /* bzLib stream structure for bziped */
+#endif
+#ifdef HAVE_AES
+ fcrypt_ctx aes_ctx;
+#endif
+
+ ZPOS64_T pos_in_zipfile; /* position in byte on the zipfile, for fseek */
+ uLong stream_initialised; /* flag set if stream structure is initialised */
+
+ ZPOS64_T offset_local_extrafield; /* offset of the static extra field */
+ uInt size_local_extrafield; /* size of the static extra field */
+ ZPOS64_T pos_local_extrafield; /* position in the static extra field in read */
+ ZPOS64_T total_out_64;
+
+ uLong crc32; /* crc32 of all data uncompressed */
+ uLong crc32_wait; /* crc32 we must obtain after decompress all */
+ ZPOS64_T rest_read_compressed; /* number of byte to be decompressed */
+ ZPOS64_T rest_read_uncompressed; /* number of byte to be obtained after decomp */
+
+ zlib_filefunc64_32_def z_filefunc;
+
+ voidpf filestream; /* io structore of the zipfile */
+ uLong compression_method; /* compression method (0==store) */
+ ZPOS64_T byte_before_the_zipfile; /* byte before the zipfile, (>0 for sfx) */
+ int raw;
+} file_in_zip64_read_info_s;
+
+/* unz64_s contain internal information about the zipfile */
+typedef struct
+{
+ zlib_filefunc64_32_def z_filefunc;
+ voidpf filestream; /* io structure of the current zipfile */
+ voidpf filestream_with_CD; /* io structure of the disk with the central directory */
+ unz_global_info64 gi; /* public global information */
+ ZPOS64_T byte_before_the_zipfile; /* byte before the zipfile, (>0 for sfx)*/
+ ZPOS64_T num_file; /* number of the current file in the zipfile*/
+ ZPOS64_T pos_in_central_dir; /* pos of the current file in the central dir*/
+ ZPOS64_T current_file_ok; /* flag about the usability of the current file*/
+ ZPOS64_T central_pos; /* position of the beginning of the central dir*/
+ uLong number_disk; /* number of the current disk, used for spanning ZIP*/
+ ZPOS64_T size_central_dir; /* size of the central directory */
+ ZPOS64_T offset_central_dir; /* offset of start of central directory with
+ respect to the starting disk number */
+
+ unz_file_info64 cur_file_info; /* public info about the current file in zip*/
+ unz_file_info64_internal cur_file_info_internal;
+ /* private info about it*/
+ file_in_zip64_read_info_s* pfile_in_zip_read;
+ /* structure about the current file if we are decompressing it */
+ int isZip64; /* is the current file zip64 */
+#ifndef NOUNCRYPT
+ unsigned int keys[3]; /* keys defining the pseudo-random sequence */
+ const unsigned int* pcrc_32_tab;
+#endif
+} unz64_s;
+
+/* Translate date/time from Dos format to tm_unz (readable more easily) */
+static void unz64local_DosDateToTmuDate (ZPOS64_T ulDosDate, tm_unz* ptm)
+{
+ ZPOS64_T uDate = (ZPOS64_T)(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));
+
+#define unz64local_in_range(min, max, value) ((min) <= (value) && (value) <= (max))
+ if (!unz64local_in_range(0, 11, ptm->tm_mon) ||
+ !unz64local_in_range(1, 31, ptm->tm_mday) ||
+ !unz64local_in_range(0, 23, ptm->tm_hour) ||
+ !unz64local_in_range(0, 59, ptm->tm_min) ||
+ !unz64local_in_range(0, 59, ptm->tm_sec))
+ /* Invalid date stored, so don't return it. */
+ memset(ptm, 0, sizeof(tm_unz));
+#undef unz64local_in_range
+}
+
+/* Read a byte from a gz_stream; Return EOF for end of file. */
+static int unz64local_getByte OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pi));
+static int unz64local_getByte(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pi)
+{
+ unsigned char c;
+ int err = (int)ZREAD64(*pzlib_filefunc_def, filestream, &c, 1);
+ if (err == 1)
+ {
+ *pi = (int)c;
+ return UNZ_OK;
+ }
+ *pi = 0;
+ if (ZERROR64(*pzlib_filefunc_def, filestream))
+ return UNZ_ERRNO;
+ return UNZ_EOF;
+}
+
+static int unz64local_getShort OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX));
+static int unz64local_getShort (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX)
+{
+ uLong x;
+ int i = 0;
+ int err;
+
+ err = unz64local_getByte(pzlib_filefunc_def, filestream, &i);
+ x = (uLong)i;
+ if (err == UNZ_OK)
+ err = unz64local_getByte(pzlib_filefunc_def, filestream, &i);
+ x |= ((uLong)i)<<8;
+
+ if (err == UNZ_OK)
+ *pX = x;
+ else
+ *pX = 0;
+ return err;
+}
+
+static int unz64local_getLong OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX));
+static int unz64local_getLong (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX)
+{
+ uLong x;
+ int i = 0;
+ int err;
+
+ err = unz64local_getByte(pzlib_filefunc_def, filestream, &i);
+ x = (uLong)i;
+ if (err == UNZ_OK)
+ err = unz64local_getByte(pzlib_filefunc_def, filestream, &i);
+ x |= ((uLong)i)<<8;
+ if (err == UNZ_OK)
+ err = unz64local_getByte(pzlib_filefunc_def, filestream, &i);
+ x |= ((uLong)i)<<16;
+ if (err == UNZ_OK)
+ err = unz64local_getByte(pzlib_filefunc_def, filestream, &i);
+ x += ((uLong)i)<<24;
+
+ if (err == UNZ_OK)
+ *pX = x;
+ else
+ *pX = 0;
+ return err;
+}
+
+static int unz64local_getLong64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX));
+static int unz64local_getLong64 (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX)
+{
+ ZPOS64_T x;
+ int i = 0;
+ int err;
+
+ err = unz64local_getByte(pzlib_filefunc_def, filestream, &i);
+ x = (ZPOS64_T)i;
+ if (err == UNZ_OK)
+ err = unz64local_getByte(pzlib_filefunc_def, filestream, &i);
+ x |= ((ZPOS64_T)i)<<8;
+ if (err == UNZ_OK)
+ err = unz64local_getByte(pzlib_filefunc_def, filestream, &i);
+ x |= ((ZPOS64_T)i)<<16;
+ if (err == UNZ_OK)
+ err = unz64local_getByte(pzlib_filefunc_def, filestream, &i);
+ x |= ((ZPOS64_T)i)<<24;
+ if (err == UNZ_OK)
+ err = unz64local_getByte(pzlib_filefunc_def, filestream, &i);
+ x |= ((ZPOS64_T)i)<<32;
+ if (err == UNZ_OK)
+ err = unz64local_getByte(pzlib_filefunc_def, filestream, &i);
+ x |= ((ZPOS64_T)i)<<40;
+ if (err == UNZ_OK)
+ err = unz64local_getByte(pzlib_filefunc_def, filestream, &i);
+ x |= ((ZPOS64_T)i)<<48;
+ if (err == UNZ_OK)
+ err = unz64local_getByte(pzlib_filefunc_def, filestream, &i);
+ x |= ((ZPOS64_T)i)<<56;
+
+ if (err == UNZ_OK)
+ *pX = x;
+ else
+ *pX = 0;
+ return err;
+}
+
+/* Locate the Central directory of a zip file (at the end, just before the global comment) */
+static ZPOS64_T unz64local_SearchCentralDir OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream));
+static ZPOS64_T unz64local_SearchCentralDir(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)
+{
+ unsigned char* buf;
+ ZPOS64_T file_size;
+ ZPOS64_T back_read = 4;
+ ZPOS64_T max_back = 0xffff; /* maximum size of global comment */
+ ZPOS64_T pos_found = 0;
+ uLong read_size;
+ ZPOS64_T read_pos;
+ int i;
+
+ buf = (unsigned char*)ALLOC(BUFREADCOMMENT + 4);
+ if (buf == NULL)
+ return 0;
+
+ if (ZSEEK64(*pzlib_filefunc_def, filestream, 0, ZLIB_FILEFUNC_SEEK_END) != 0)
+ {
+ TRYFREE(buf);
+ return 0;
+ }
+
+ file_size = ZTELL64(*pzlib_filefunc_def, filestream);
+
+ if (max_back > file_size)
+ max_back = file_size;
+
+ while (back_read < max_back)
+ {
+ if (back_read + BUFREADCOMMENT > max_back)
+ back_read = max_back;
+ else
+ back_read += BUFREADCOMMENT;
+
+ read_pos = file_size - back_read;
+ read_size = ((BUFREADCOMMENT + 4) < (file_size - read_pos)) ?
+ (BUFREADCOMMENT + 4) : (uLong)(file_size - read_pos);
+
+ if (ZSEEK64(*pzlib_filefunc_def, filestream, read_pos, ZLIB_FILEFUNC_SEEK_SET) != 0)
+ break;
+ if (ZREAD64(*pzlib_filefunc_def, filestream, buf, read_size) != read_size)
+ break;
+
+ for (i = (int)read_size-3; (i--) > 0;)
+ if (((*(buf+i)) == (ENDHEADERMAGIC & 0xff)) &&
+ ((*(buf+i+1)) == (ENDHEADERMAGIC >> 8 & 0xff)) &&
+ ((*(buf+i+2)) == (ENDHEADERMAGIC >> 16 & 0xff)) &&
+ ((*(buf+i+3)) == (ENDHEADERMAGIC >> 24 & 0xff)))
+ {
+ pos_found = read_pos+i;
+ break;
+ }
+
+ if (pos_found != 0)
+ break;
+ }
+ TRYFREE(buf);
+ return pos_found;
+}
+
+/* Locate the Central directory 64 of a zipfile (at the end, just before the global comment) */
+static ZPOS64_T unz64local_SearchCentralDir64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream,
+ const ZPOS64_T endcentraloffset));
+static ZPOS64_T unz64local_SearchCentralDir64(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream,
+ const ZPOS64_T endcentraloffset)
+{
+ ZPOS64_T offset;
+ uLong uL;
+
+ /* Zip64 end of central directory locator */
+ if (ZSEEK64(*pzlib_filefunc_def, filestream, endcentraloffset - SIZECENTRALHEADERLOCATOR, ZLIB_FILEFUNC_SEEK_SET) != 0)
+ return 0;
+
+ /* read locator signature */
+ if (unz64local_getLong(pzlib_filefunc_def, filestream, &uL) != UNZ_OK)
+ return 0;
+ if (uL != ZIP64ENDLOCHEADERMAGIC)
+ return 0;
+ /* number of the disk with the start of the zip64 end of central directory */
+ if (unz64local_getLong(pzlib_filefunc_def, filestream, &uL) != UNZ_OK)
+ return 0;
+ /* relative offset of the zip64 end of central directory record */
+ if (unz64local_getLong64(pzlib_filefunc_def, filestream, &offset) != UNZ_OK)
+ return 0;
+ /* total number of disks */
+ if (unz64local_getLong(pzlib_filefunc_def, filestream, &uL) != UNZ_OK)
+ return 0;
+ /* Goto end of central directory record */
+ if (ZSEEK64(*pzlib_filefunc_def, filestream, offset, ZLIB_FILEFUNC_SEEK_SET) != 0)
+ return 0;
+ /* the signature */
+ if (unz64local_getLong(pzlib_filefunc_def, filestream, &uL) != UNZ_OK)
+ return 0;
+ if (uL != ZIP64ENDHEADERMAGIC)
+ return 0;
+
+ return offset;
+}
+
+static unzFile unzOpenInternal(const void *path, zlib_filefunc64_32_def* pzlib_filefunc64_32_def)
+{
+ unz64_s us;
+ unz64_s *s;
+ ZPOS64_T central_pos;
+ ZPOS64_T central_pos64;
+ uLong uL;
+ ZPOS64_T uL64;
+ voidpf filestream = NULL;
+ ZPOS64_T number_entry_CD;
+ int err = UNZ_OK;
+
+ if (unz_copyright[0]!=' ')
+ return NULL;
+
+ us.filestream = NULL;
+ us.filestream_with_CD = NULL;
+ us.z_filefunc.zseek32_file = NULL;
+ us.z_filefunc.ztell32_file = NULL;
+ if (pzlib_filefunc64_32_def == NULL)
+ fill_fopen64_filefunc(&us.z_filefunc.zfile_func64);
+ else
+ us.z_filefunc = *pzlib_filefunc64_32_def;
+
+ us.filestream = ZOPEN64(us.z_filefunc, path, ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_EXISTING);
+
+ if (us.filestream == NULL)
+ return NULL;
+
+ us.filestream_with_CD = us.filestream;
+ us.isZip64 = 0;
+
+ /* Search for end of central directory header */
+ central_pos = unz64local_SearchCentralDir(&us.z_filefunc, us.filestream);
+ if (central_pos)
+ {
+ if (ZSEEK64(us.z_filefunc, us.filestream, central_pos, ZLIB_FILEFUNC_SEEK_SET) != 0)
+ err = UNZ_ERRNO;
+
+ /* the signature, already checked */
+ if (unz64local_getLong(&us.z_filefunc, us.filestream, &uL) != UNZ_OK)
+ err = UNZ_ERRNO;
+ /* number of this disk */
+ if (unz64local_getShort(&us.z_filefunc, us.filestream, &uL) != UNZ_OK)
+ err = UNZ_ERRNO;
+ us.number_disk = uL;
+ /* number of the disk with the start of the central directory */
+ if (unz64local_getShort(&us.z_filefunc, us.filestream,& uL) != UNZ_OK)
+ err = UNZ_ERRNO;
+ us.gi.number_disk_with_CD = uL;
+ /* total number of entries in the central directory on this disk */
+ if (unz64local_getShort(&us.z_filefunc, us.filestream, &uL) != UNZ_OK)
+ err = UNZ_ERRNO;
+ us.gi.number_entry = uL;
+ /* total number of entries in the central directory */
+ if (unz64local_getShort(&us.z_filefunc, us.filestream, &uL) != UNZ_OK)
+ err = UNZ_ERRNO;
+ number_entry_CD = uL;
+ if (number_entry_CD != us.gi.number_entry)
+ err = UNZ_BADZIPFILE;
+ /* size of the central directory */
+ if (unz64local_getLong(&us.z_filefunc, us.filestream, &uL) != UNZ_OK)
+ err = UNZ_ERRNO;
+ us.size_central_dir = uL;
+ /* offset of start of central directory with respect to the starting disk number */
+ if (unz64local_getLong(&us.z_filefunc, us.filestream, &uL) != UNZ_OK)
+ err = UNZ_ERRNO;
+ us.offset_central_dir = uL;
+ /* zipfile comment length */
+ if (unz64local_getShort(&us.z_filefunc, us.filestream, &us.gi.size_comment) != UNZ_OK)
+ err = UNZ_ERRNO;
+
+ if (err == UNZ_OK)
+ {
+ /* Search for Zip64 end of central directory header */
+ central_pos64 = unz64local_SearchCentralDir64(&us.z_filefunc, us.filestream, central_pos);
+ if (central_pos64)
+ {
+ central_pos = central_pos64;
+ us.isZip64 = 1;
+
+ if (ZSEEK64(us.z_filefunc, us.filestream, central_pos, ZLIB_FILEFUNC_SEEK_SET) != 0)
+ err = UNZ_ERRNO;
+
+ /* the signature, already checked */
+ if (unz64local_getLong(&us.z_filefunc, us.filestream, &uL) != UNZ_OK)
+ err = UNZ_ERRNO;
+ /* size of zip64 end of central directory record */
+ if (unz64local_getLong64(&us.z_filefunc, us.filestream, &uL64) != UNZ_OK)
+ err = UNZ_ERRNO;
+ /* version made by */
+ if (unz64local_getShort(&us.z_filefunc, us.filestream, &uL) != UNZ_OK)
+ err = UNZ_ERRNO;
+ /* version needed to extract */
+ if (unz64local_getShort(&us.z_filefunc, us.filestream, &uL) != UNZ_OK)
+ err = UNZ_ERRNO;
+ /* number of this disk */
+ if (unz64local_getLong(&us.z_filefunc, us.filestream, &us.number_disk) != UNZ_OK)
+ err = UNZ_ERRNO;
+ /* number of the disk with the start of the central directory */
+ if (unz64local_getLong(&us.z_filefunc, us.filestream, &us.gi.number_disk_with_CD) != UNZ_OK)
+ err = UNZ_ERRNO;
+ /* total number of entries in the central directory on this disk */
+ if (unz64local_getLong64(&us.z_filefunc, us.filestream, &us.gi.number_entry) != UNZ_OK)
+ err = UNZ_ERRNO;
+ /* total number of entries in the central directory */
+ if (unz64local_getLong64(&us.z_filefunc, us.filestream, &number_entry_CD) != UNZ_OK)
+ err = UNZ_ERRNO;
+ if (number_entry_CD != us.gi.number_entry)
+ err = UNZ_BADZIPFILE;
+ /* size of the central directory */
+ if (unz64local_getLong64(&us.z_filefunc, us.filestream, &us.size_central_dir) != UNZ_OK)
+ err = UNZ_ERRNO;
+ /* offset of start of central directory with respect to the starting disk number */
+ if (unz64local_getLong64(&us.z_filefunc, us.filestream, &us.offset_central_dir) != UNZ_OK)
+ err = UNZ_ERRNO;
+ }
+ else if ((us.gi.number_entry == 0xffff) || (us.size_central_dir == 0xffff) || (us.offset_central_dir == 0xffffffff))
+ err = UNZ_BADZIPFILE;
+ }
+ }
+ else
+ err = UNZ_ERRNO;
+
+ if ((err == UNZ_OK) && (central_pos < us.offset_central_dir + us.size_central_dir))
+ err = UNZ_BADZIPFILE;
+
+ if (err != UNZ_OK)
+ {
+ ZCLOSE64(us.z_filefunc, us.filestream);
+ return NULL;
+ }
+
+ if (us.gi.number_disk_with_CD == 0)
+ {
+ /* If there is only one disk open another stream so we don't have to seek between the CD
+ and the file headers constantly */
+ filestream = ZOPEN64(us.z_filefunc, path, ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_EXISTING);
+ if (filestream != NULL)
+ us.filestream = filestream;
+ }
+
+ /* Hack for zip files that have no respect for zip64
+ if ((central_pos > 0xffffffff) && (us.offset_central_dir < 0xffffffff))
+ us.offset_central_dir = central_pos - us.size_central_dir;*/
+
+ 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 = (unz64_s*)ALLOC(sizeof(unz64_s));
+ if (s != NULL)
+ {
+ *s = us;
+ unzGoToFirstFile((unzFile)s);
+ }
+ return (unzFile)s;
+}
+
+extern unzFile ZEXPORT unzOpen2(const char *path, zlib_filefunc_def* pzlib_filefunc32_def)
+{
+ if (pzlib_filefunc32_def != NULL)
+ {
+ zlib_filefunc64_32_def zlib_filefunc64_32_def_fill;
+ fill_zlib_filefunc64_32_def_from_filefunc32(&zlib_filefunc64_32_def_fill, pzlib_filefunc32_def);
+ return unzOpenInternal(path, &zlib_filefunc64_32_def_fill);
+ }
+ return unzOpenInternal(path, NULL);
+}
+
+extern unzFile ZEXPORT unzOpen2_64(const void *path, zlib_filefunc64_def* pzlib_filefunc_def)
+{
+ if (pzlib_filefunc_def != NULL)
+ {
+ zlib_filefunc64_32_def zlib_filefunc64_32_def_fill;
+ zlib_filefunc64_32_def_fill.zfile_func64 = *pzlib_filefunc_def;
+ zlib_filefunc64_32_def_fill.ztell32_file = NULL;
+ zlib_filefunc64_32_def_fill.zseek32_file = NULL;
+ return unzOpenInternal(path, &zlib_filefunc64_32_def_fill);
+ }
+ return unzOpenInternal(path, NULL);
+}
+
+extern unzFile ZEXPORT unzOpen(const char *path)
+{
+ return unzOpenInternal(path, NULL);
+}
+
+extern unzFile ZEXPORT unzOpen64(const void *path)
+{
+ return unzOpenInternal(path, NULL);
+}
+
+extern int ZEXPORT unzClose(unzFile file)
+{
+ unz64_s* s;
+ if (file == NULL)
+ return UNZ_PARAMERROR;
+ s = (unz64_s*)file;
+
+ if (s->pfile_in_zip_read != NULL)
+ unzCloseCurrentFile(file);
+
+ if ((s->filestream != NULL) && (s->filestream != s->filestream_with_CD))
+ ZCLOSE64(s->z_filefunc, s->filestream);
+ if (s->filestream_with_CD != NULL)
+ ZCLOSE64(s->z_filefunc, s->filestream_with_CD);
+
+ s->filestream = NULL;
+ s->filestream_with_CD = NULL;
+ TRYFREE(s);
+ return UNZ_OK;
+}
+
+/* Goto to the next available disk for spanned archives */
+static int unzGoToNextDisk OF((unzFile file));
+static int unzGoToNextDisk(unzFile file)
+{
+ unz64_s* s;
+ uLong number_disk_next = 0;
+
+ s = (unz64_s*)file;
+ if (s == NULL)
+ return UNZ_PARAMERROR;
+ number_disk_next = s->number_disk;
+
+ if ((s->pfile_in_zip_read != NULL) && (s->pfile_in_zip_read->rest_read_uncompressed > 0))
+ /* We are currently reading a file and we need the next sequential disk */
+ number_disk_next += 1;
+ else
+ /* Goto the disk for the current file */
+ number_disk_next = s->cur_file_info.disk_num_start;
+
+ if (number_disk_next != s->number_disk)
+ {
+ /* Switch disks */
+ if ((s->filestream != NULL) && (s->filestream != s->filestream_with_CD))
+ ZCLOSE64(s->z_filefunc, s->filestream);
+
+ if (number_disk_next == s->gi.number_disk_with_CD)
+ {
+ s->filestream = s->filestream_with_CD;
+ }
+ else
+ {
+ s->filestream = ZOPENDISK64(s->z_filefunc, s->filestream_with_CD, number_disk_next,
+ ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_EXISTING);
+ }
+
+ if (s->filestream == NULL)
+ return UNZ_ERRNO;
+
+ s->number_disk = number_disk_next;
+ }
+
+ return UNZ_OK;
+}
+
+extern int ZEXPORT unzGetGlobalInfo(unzFile file, unz_global_info* pglobal_info32)
+{
+ unz64_s* s;
+ if (file == NULL)
+ return UNZ_PARAMERROR;
+ s = (unz64_s*)file;
+ /* to do : check if number_entry is not truncated */
+ pglobal_info32->number_entry = (uLong)s->gi.number_entry;
+ pglobal_info32->size_comment = s->gi.size_comment;
+ pglobal_info32->number_disk_with_CD = s->gi.number_disk_with_CD;
+ return UNZ_OK;
+}
+
+extern int ZEXPORT unzGetGlobalInfo64(unzFile file, unz_global_info64* pglobal_info)
+{
+ unz64_s* s;
+ if (file == NULL)
+ return UNZ_PARAMERROR;
+ s = (unz64_s*)file;
+ *pglobal_info = s->gi;
+ return UNZ_OK;
+}
+
+extern int ZEXPORT unzGetGlobalComment(unzFile file, char *comment, uLong comment_size)
+{
+ unz64_s* s;
+ uLong bytes_to_read = comment_size;
+ if (file == NULL)
+ return (int)UNZ_PARAMERROR;
+ s = (unz64_s*)file;
+
+ if (bytes_to_read > s->gi.size_comment)
+ bytes_to_read = s->gi.size_comment;
+
+ if (ZSEEK64(s->z_filefunc, s->filestream_with_CD, s->central_pos + 22, ZLIB_FILEFUNC_SEEK_SET) != 0)
+ return UNZ_ERRNO;
+
+ if (bytes_to_read>0)
+ {
+ *comment = 0;
+ if (ZREAD64(s->z_filefunc, s->filestream_with_CD, comment, bytes_to_read) != bytes_to_read)
+ return UNZ_ERRNO;
+ }
+
+ if ((comment != NULL) && (comment_size > s->gi.size_comment))
+ *(comment+s->gi.size_comment) = 0;
+ return (int)bytes_to_read;
+}
+
+/* Get Info about the current file in the zipfile, with internal only info */
+static int unz64local_GetCurrentFileInfoInternal(unzFile file, unz_file_info64 *pfile_info,
+ unz_file_info64_internal *pfile_info_internal, char *filename, uLong filename_size, void *extrafield,
+ uLong extrafield_size, char *comment, uLong comment_size)
+{
+ unz64_s* s;
+ unz_file_info64 file_info;
+ unz_file_info64_internal file_info_internal;
+ ZPOS64_T bytes_to_read;
+ int err = UNZ_OK;
+ uLong uMagic;
+ long lSeek = 0;
+ ZPOS64_T current_pos = 0;
+ uLong acc = 0;
+ uLong uL;
+ ZPOS64_T uL64;
+
+ if (file == NULL)
+ return UNZ_PARAMERROR;
+ s = (unz64_s*)file;
+
+ if (ZSEEK64(s->z_filefunc, s->filestream_with_CD,
+ s->pos_in_central_dir + s->byte_before_the_zipfile, ZLIB_FILEFUNC_SEEK_SET) != 0)
+ err = UNZ_ERRNO;
+
+ /* Check the magic */
+ if (err == UNZ_OK)
+ {
+ if (unz64local_getLong(&s->z_filefunc, s->filestream_with_CD, &uMagic) != UNZ_OK)
+ err = UNZ_ERRNO;
+ else if (uMagic != CENTRALHEADERMAGIC)
+ err = UNZ_BADZIPFILE;
+ }
+
+ /* Read central directory header */
+ if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &file_info.version) != UNZ_OK)
+ err = UNZ_ERRNO;
+ if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &file_info.version_needed) != UNZ_OK)
+ err = UNZ_ERRNO;
+ if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &file_info.flag) != UNZ_OK)
+ err = UNZ_ERRNO;
+ if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &file_info.compression_method) != UNZ_OK)
+ err = UNZ_ERRNO;
+ if (unz64local_getLong(&s->z_filefunc, s->filestream_with_CD, &file_info.dosDate) != UNZ_OK)
+ err = UNZ_ERRNO;
+ unz64local_DosDateToTmuDate(file_info.dosDate, &file_info.tmu_date);
+ if (unz64local_getLong(&s->z_filefunc, s->filestream_with_CD, &file_info.crc) != UNZ_OK)
+ err = UNZ_ERRNO;
+ if (unz64local_getLong(&s->z_filefunc, s->filestream_with_CD, &uL) != UNZ_OK)
+ err = UNZ_ERRNO;
+ file_info.compressed_size = uL;
+ if (unz64local_getLong(&s->z_filefunc, s->filestream_with_CD, &uL) != UNZ_OK)
+ err = UNZ_ERRNO;
+ file_info.uncompressed_size = uL;
+ if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &file_info.size_filename) != UNZ_OK)
+ err = UNZ_ERRNO;
+ if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &file_info.size_file_extra) != UNZ_OK)
+ err = UNZ_ERRNO;
+ if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &file_info.size_file_comment) != UNZ_OK)
+ err = UNZ_ERRNO;
+ if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &file_info.disk_num_start) != UNZ_OK)
+ err = UNZ_ERRNO;
+ if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &file_info.internal_fa) != UNZ_OK)
+ err = UNZ_ERRNO;
+ if (unz64local_getLong(&s->z_filefunc, s->filestream_with_CD, &file_info.external_fa) != UNZ_OK)
+ err = UNZ_ERRNO;
+ /* Relative offset of static header */
+ if (unz64local_getLong(&s->z_filefunc, s->filestream_with_CD, &uL) != UNZ_OK)
+ err = UNZ_ERRNO;
+
+ file_info.size_file_extra_internal = 0;
+ file_info.disk_offset = uL;
+ file_info_internal.offset_curfile = uL;
+#ifdef HAVE_AES
+ file_info_internal.aes_compression_method = 0;
+ file_info_internal.aes_encryption_mode = 0;
+ file_info_internal.aes_version = 0;
+#endif
+
+ lSeek += file_info.size_filename;
+
+ if ((err == UNZ_OK) && (filename != NULL))
+ {
+ if (file_info.size_filename < filename_size)
+ {
+ *(filename+file_info.size_filename) = 0;
+ bytes_to_read = file_info.size_filename;
+ }
+ else
+ bytes_to_read = filename_size;
+
+ if ((file_info.size_filename > 0) && (filename_size > 0))
+ if (ZREAD64(s->z_filefunc, s->filestream_with_CD,filename, (uLong)bytes_to_read) != bytes_to_read)
+ err = UNZ_ERRNO;
+ lSeek -= (uLong)bytes_to_read;
+ }
+
+ /* Read extrafield */
+ if ((err == UNZ_OK) && (extrafield != NULL))
+ {
+ if (file_info.size_file_extra < extrafield_size)
+ bytes_to_read = file_info.size_file_extra;
+ else
+ bytes_to_read = extrafield_size;
+
+ if (lSeek != 0)
+ {
+ if (ZSEEK64(s->z_filefunc, s->filestream_with_CD, lSeek, ZLIB_FILEFUNC_SEEK_CUR) == 0)
+ lSeek=0;
+ else
+ err = UNZ_ERRNO;
+ }
+
+ if ((file_info.size_file_extra > 0) && (extrafield_size > 0))
+ if (ZREAD64(s->z_filefunc, s->filestream_with_CD, extrafield, (uLong)bytes_to_read) != bytes_to_read)
+ err = UNZ_ERRNO;
+ lSeek += file_info.size_file_extra - (uLong)bytes_to_read;
+ }
+ else
+ lSeek += file_info.size_file_extra;
+
+ if ((err == UNZ_OK) && (file_info.size_file_extra != 0))
+ {
+ if (lSeek != 0)
+ {
+ if (ZSEEK64(s->z_filefunc, s->filestream_with_CD, lSeek, ZLIB_FILEFUNC_SEEK_CUR) == 0)
+ lSeek=0;
+ else
+ err = UNZ_ERRNO;
+ }
+
+ /* We are going to parse the extra field so we need to move back */
+ current_pos = ZTELL64(s->z_filefunc, s->filestream_with_CD);
+ if (current_pos < file_info.size_file_extra)
+ err = UNZ_ERRNO;
+ current_pos -= file_info.size_file_extra;
+ if (ZSEEK64(s->z_filefunc, s->filestream_with_CD, current_pos, ZLIB_FILEFUNC_SEEK_SET) != 0)
+ err = UNZ_ERRNO;
+
+ while((err != UNZ_ERRNO) && (acc < file_info.size_file_extra))
+ {
+ uLong headerid;
+ uLong datasize;
+
+ if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &headerid) != UNZ_OK)
+ err = UNZ_ERRNO;
+ if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &datasize) != UNZ_OK)
+ err = UNZ_ERRNO;
+
+ /* ZIP64 extra fields */
+ if (headerid == 0x0001)
+ {
+ /* Subtract size of ZIP64 field, since ZIP64 is handled internally */
+ file_info.size_file_extra_internal += 2 + 2 + datasize;
+
+ if (file_info.uncompressed_size == 0xffffffff)
+ {
+ if (unz64local_getLong64(&s->z_filefunc, s->filestream_with_CD, &file_info.uncompressed_size) != UNZ_OK)
+ err = UNZ_ERRNO;
+ }
+ if (file_info.compressed_size == 0xffffffff)
+ {
+ if (unz64local_getLong64(&s->z_filefunc, s->filestream_with_CD, &file_info.compressed_size) != UNZ_OK)
+ err = UNZ_ERRNO;
+ }
+ if (file_info_internal.offset_curfile == 0xffffffff)
+ {
+ /* Relative Header offset */
+ if (unz64local_getLong64(&s->z_filefunc, s->filestream_with_CD, &uL64) != UNZ_OK)
+ err = UNZ_ERRNO;
+ file_info_internal.offset_curfile = uL64;
+ file_info.disk_offset = uL64;
+ }
+ if (file_info.disk_num_start == 0xffffffff)
+ {
+ /* Disk Start Number */
+ if (unz64local_getLong(&s->z_filefunc, s->filestream_with_CD, &file_info.disk_num_start) != UNZ_OK)
+ err = UNZ_ERRNO;
+ }
+ }
+#ifdef HAVE_AES
+ /* AES header */
+ else if (headerid == 0x9901)
+ {
+ /* Subtract size of AES field, since AES is handled internally */
+ file_info.size_file_extra_internal += 2 + 2 + datasize;
+
+ /* Verify version info */
+ if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &uL) != UNZ_OK)
+ err = UNZ_ERRNO;
+ /* Support AE-1 and AE-2 */
+ if (uL != 1 && uL != 2)
+ err = UNZ_ERRNO;
+ file_info_internal.aes_version = uL;
+ if (unz64local_getByte(&s->z_filefunc, s->filestream_with_CD, &uL) != UNZ_OK)
+ err = UNZ_ERRNO;
+ if ((char)uL != 'A')
+ err = UNZ_ERRNO;
+ if (unz64local_getByte(&s->z_filefunc, s->filestream_with_CD, &uL) != UNZ_OK)
+ err = UNZ_ERRNO;
+ if ((char)uL != 'E')
+ err = UNZ_ERRNO;
+ /* Get AES encryption strength and actual compression method */
+ if (unz64local_getByte(&s->z_filefunc, s->filestream_with_CD, &uL) != UNZ_OK)
+ err = UNZ_ERRNO;
+ file_info_internal.aes_encryption_mode = uL;
+ if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &uL) != UNZ_OK)
+ err = UNZ_ERRNO;
+ file_info_internal.aes_compression_method = uL;
+ }
+#endif
+ else
+ {
+ if (ZSEEK64(s->z_filefunc, s->filestream_with_CD,datasize, ZLIB_FILEFUNC_SEEK_CUR) != 0)
+ err = UNZ_ERRNO;
+ }
+
+ acc += 2 + 2 + datasize;
+ }
+ }
+
+ if (file_info.disk_num_start == s->gi.number_disk_with_CD)
+ file_info_internal.byte_before_the_zipfile = s->byte_before_the_zipfile;
+ else
+ file_info_internal.byte_before_the_zipfile = 0;
+
+ if ((err == UNZ_OK) && (comment != NULL))
+ {
+ if (file_info.size_file_comment < comment_size)
+ {
+ *(comment+file_info.size_file_comment) = 0;
+ bytes_to_read = file_info.size_file_comment;
+ }
+ else
+ bytes_to_read = comment_size;
+
+ if (lSeek != 0)
+ {
+ if (ZSEEK64(s->z_filefunc, s->filestream_with_CD, lSeek, ZLIB_FILEFUNC_SEEK_CUR) != 0)
+ err = UNZ_ERRNO;
+ }
+
+ if ((file_info.size_file_comment > 0) && (comment_size > 0))
+ if (ZREAD64(s->z_filefunc, s->filestream_with_CD, comment, (uLong)bytes_to_read) != bytes_to_read)
+ err = UNZ_ERRNO;
+ lSeek += file_info.size_file_comment - (uLong)bytes_to_read;
+ }
+ 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;
+}
+
+extern int ZEXPORT unzGetCurrentFileInfo(unzFile file, unz_file_info * pfile_info, char *filename,
+ uLong filename_size, void *extrafield, uLong extrafield_size, char* comment, uLong comment_size)
+{
+ unz_file_info64 file_info64;
+ int err;
+
+ err = unz64local_GetCurrentFileInfoInternal(file, &file_info64, NULL, filename, filename_size,
+ extrafield, extrafield_size, comment, comment_size);
+
+ if ((err == UNZ_OK) && (pfile_info != NULL))
+ {
+ pfile_info->version = file_info64.version;
+ pfile_info->version_needed = file_info64.version_needed;
+ pfile_info->flag = file_info64.flag;
+ pfile_info->compression_method = file_info64.compression_method;
+ pfile_info->dosDate = file_info64.dosDate;
+ pfile_info->crc = file_info64.crc;
+
+ pfile_info->size_filename = file_info64.size_filename;
+ pfile_info->size_file_extra = file_info64.size_file_extra - file_info64.size_file_extra_internal;
+ pfile_info->size_file_comment = file_info64.size_file_comment;
+
+ pfile_info->disk_num_start = file_info64.disk_num_start;
+ pfile_info->internal_fa = file_info64.internal_fa;
+ pfile_info->external_fa = file_info64.external_fa;
+
+ pfile_info->tmu_date = file_info64.tmu_date,
+
+ pfile_info->compressed_size = (uLong)file_info64.compressed_size;
+ pfile_info->uncompressed_size = (uLong)file_info64.uncompressed_size;
+
+ }
+ return err;
+}
+
+extern int ZEXPORT unzGetCurrentFileInfo64(unzFile file, unz_file_info64 * pfile_info, char *filename,
+ uLong filename_size, void *extrafield, uLong extrafield_size, char* comment, uLong comment_size)
+{
+ return unz64local_GetCurrentFileInfoInternal(file, pfile_info, NULL, filename, filename_size,
+ extrafield, extrafield_size, comment,comment_size);
+}
+
+/* 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 unz64local_CheckCurrentFileCoherencyHeader(unz64_s* s, uInt* piSizeVar, ZPOS64_T *poffset_local_extrafield,
+ uInt *psize_local_extrafield)
+{
+ uLong uMagic, uL, uFlags;
+ uLong size_filename;
+ uLong size_extra_field;
+ int err = UNZ_OK;
+ int compression_method = 0;
+
+ *piSizeVar = 0;
+ *poffset_local_extrafield = 0;
+ *psize_local_extrafield = 0;
+
+ err = unzGoToNextDisk((unzFile)s);
+ if (err != UNZ_OK)
+ return err;
+
+ if (ZSEEK64(s->z_filefunc, s->filestream, s->cur_file_info_internal.offset_curfile +
+ s->cur_file_info_internal.byte_before_the_zipfile, ZLIB_FILEFUNC_SEEK_SET) != 0)
+ return UNZ_ERRNO;
+
+ if (err == UNZ_OK)
+ {
+ if (unz64local_getLong(&s->z_filefunc, s->filestream, &uMagic) != UNZ_OK)
+ err = UNZ_ERRNO;
+ else if (uMagic != LOCALHEADERMAGIC)
+ err = UNZ_BADZIPFILE;
+ }
+
+ if (unz64local_getShort(&s->z_filefunc, s->filestream, &uL) != UNZ_OK)
+ err = UNZ_ERRNO;
+ if (unz64local_getShort(&s->z_filefunc, s->filestream, &uFlags) != UNZ_OK)
+ err = UNZ_ERRNO;
+ if (unz64local_getShort(&s->z_filefunc, s->filestream, &uL) != UNZ_OK)
+ err = UNZ_ERRNO;
+ else if ((err == UNZ_OK) && (uL != s->cur_file_info.compression_method))
+ err = UNZ_BADZIPFILE;
+
+ compression_method = (int)s->cur_file_info.compression_method;
+#ifdef HAVE_AES
+ if (compression_method == AES_METHOD)
+ compression_method = (int)s->cur_file_info_internal.aes_compression_method;
+#endif
+
+ if ((err == UNZ_OK) && (compression_method != 0) &&
+#ifdef HAVE_BZIP2
+ (compression_method != Z_BZIP2ED) &&
+#endif
+ (compression_method != Z_DEFLATED))
+ err = UNZ_BADZIPFILE;
+
+ if (unz64local_getLong(&s->z_filefunc, s->filestream, &uL) != UNZ_OK) /* date/time */
+ err = UNZ_ERRNO;
+ if (unz64local_getLong(&s->z_filefunc, s->filestream, &uL) != UNZ_OK) /* crc */
+ err = UNZ_ERRNO;
+ else if ((err == UNZ_OK) && (uL != s->cur_file_info.crc) && ((uFlags & 8) == 0))
+ err = UNZ_BADZIPFILE;
+ if (unz64local_getLong(&s->z_filefunc, s->filestream, &uL) != UNZ_OK) /* size compr */
+ err = UNZ_ERRNO;
+ else if ((uL != 0xffffffff) && (err == UNZ_OK) && (uL != s->cur_file_info.compressed_size) && ((uFlags & 8) == 0))
+ err = UNZ_BADZIPFILE;
+ if (unz64local_getLong(&s->z_filefunc, s->filestream, &uL) != UNZ_OK) /* size uncompr */
+ err = UNZ_ERRNO;
+ else if ((uL != 0xffffffff) && (err == UNZ_OK) && (uL != s->cur_file_info.uncompressed_size) && ((uFlags & 8) == 0))
+ err = UNZ_BADZIPFILE;
+ if (unz64local_getShort(&s->z_filefunc, s->filestream, &size_filename) != UNZ_OK)
+ err = UNZ_ERRNO;
+ else if ((err == UNZ_OK) && (size_filename != s->cur_file_info.size_filename))
+ err = UNZ_BADZIPFILE;
+
+ *piSizeVar += (uInt)size_filename;
+
+ if (unz64local_getShort(&s->z_filefunc, s->filestream, &size_extra_field) != UNZ_OK)
+ err = UNZ_ERRNO;
+ *poffset_local_extrafield = s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + size_filename;
+ *psize_local_extrafield = (uInt)size_extra_field;
+
+ *piSizeVar += (uInt)size_extra_field;
+
+ return err;
+}
+
+/*
+ Open for reading data the current file in the zipfile.
+ If there is no error and the file is opened, the return value is UNZ_OK.
+*/
+extern int ZEXPORT unzOpenCurrentFile3(unzFile file, int* method, int* level, int raw, const char* password)
+{
+ int err = UNZ_OK;
+ int compression_method;
+ uInt iSizeVar;
+ unz64_s* s;
+ file_in_zip64_read_info_s* pfile_in_zip_read_info;
+ ZPOS64_T offset_local_extrafield;
+ uInt size_local_extrafield;
+#ifndef NOUNCRYPT
+ char source[12];
+#else
+ if (password != NULL)
+ return UNZ_PARAMERROR;
+#endif
+ if (file == NULL)
+ return UNZ_PARAMERROR;
+ s = (unz64_s*)file;
+ if (!s->current_file_ok)
+ return UNZ_PARAMERROR;
+
+ if (s->pfile_in_zip_read != NULL)
+ unzCloseCurrentFile(file);
+
+ if (unz64local_CheckCurrentFileCoherencyHeader(s, &iSizeVar, &offset_local_extrafield, &size_local_extrafield) != UNZ_OK)
+ return UNZ_BADZIPFILE;
+
+ pfile_in_zip_read_info = (file_in_zip64_read_info_s*)ALLOC(sizeof(file_in_zip64_read_info_s));
+ if (pfile_in_zip_read_info == NULL)
+ return UNZ_INTERNALERROR;
+
+ pfile_in_zip_read_info->read_buffer = (Bytef*)ALLOC(UNZ_BUFSIZE);
+ pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield;
+ pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield;
+ pfile_in_zip_read_info->pos_local_extrafield = 0;
+ pfile_in_zip_read_info->raw = raw;
+
+ if (pfile_in_zip_read_info->read_buffer == NULL)
+ {
+ TRYFREE(pfile_in_zip_read_info);
+ return UNZ_INTERNALERROR;
+ }
+
+ pfile_in_zip_read_info->stream_initialised = 0;
+
+ compression_method = (int)s->cur_file_info.compression_method;
+#ifdef HAVE_AES
+ if (compression_method == AES_METHOD)
+ compression_method = (int)s->cur_file_info_internal.aes_compression_method;
+#endif
+
+ if (method != NULL)
+ *method = compression_method;
+
+ if (level != NULL)
+ {
+ *level = 6;
+ switch (s->cur_file_info.flag & 0x06)
+ {
+ case 6 : *level = 1; break;
+ case 4 : *level = 2; break;
+ case 2 : *level = 9; break;
+ }
+ }
+
+ if ((compression_method != 0) &&
+#ifdef HAVE_BZIP2
+ (compression_method != Z_BZIP2ED) &&
+#endif
+ (compression_method != Z_DEFLATED))
+ {
+ TRYFREE(pfile_in_zip_read_info);
+ return UNZ_BADZIPFILE;
+ }
+
+ pfile_in_zip_read_info->crc32_wait = s->cur_file_info.crc;
+ pfile_in_zip_read_info->crc32 = 0;
+ pfile_in_zip_read_info->total_out_64 = 0;
+ pfile_in_zip_read_info->compression_method = compression_method;
+ pfile_in_zip_read_info->filestream = s->filestream;
+ pfile_in_zip_read_info->z_filefunc = s->z_filefunc;
+ if (s->number_disk == s->gi.number_disk_with_CD)
+ pfile_in_zip_read_info->byte_before_the_zipfile = s->byte_before_the_zipfile;
+ else
+ pfile_in_zip_read_info->byte_before_the_zipfile = 0;
+ pfile_in_zip_read_info->stream.total_out = 0;
+ pfile_in_zip_read_info->stream.total_in = 0;
+ pfile_in_zip_read_info->stream.next_in = NULL;
+
+ if (!raw)
+ {
+ if (compression_method == Z_BZIP2ED)
+ {
+#ifdef HAVE_BZIP2
+ pfile_in_zip_read_info->bstream.bzalloc = (void *(*) (void *, int, int))0;
+ pfile_in_zip_read_info->bstream.bzfree = (free_func)0;
+ pfile_in_zip_read_info->bstream.opaque = (voidpf)0;
+ pfile_in_zip_read_info->bstream.state = (voidpf)0;
+
+ pfile_in_zip_read_info->stream.zalloc = (alloc_func)0;
+ pfile_in_zip_read_info->stream.zfree = (free_func)0;
+ pfile_in_zip_read_info->stream.opaque = (voidpf)0;
+ pfile_in_zip_read_info->stream.next_in = (voidpf)0;
+ pfile_in_zip_read_info->stream.avail_in = 0;
+
+ err = BZ2_bzDecompressInit(&pfile_in_zip_read_info->bstream, 0, 0);
+ if (err == Z_OK)
+ pfile_in_zip_read_info->stream_initialised = Z_BZIP2ED;
+ else
+ {
+ TRYFREE(pfile_in_zip_read_info);
+ return err;
+ }
+#else
+ pfile_in_zip_read_info->raw = 1;
+#endif
+ }
+ else if (compression_method == Z_DEFLATED)
+ {
+ pfile_in_zip_read_info->stream.zalloc = (alloc_func)0;
+ pfile_in_zip_read_info->stream.zfree = (free_func)0;
+ pfile_in_zip_read_info->stream.opaque = (voidpf)s;
+ pfile_in_zip_read_info->stream.next_in = 0;
+ pfile_in_zip_read_info->stream.avail_in = 0;
+
+ err = inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS);
+ if (err == Z_OK)
+ pfile_in_zip_read_info->stream_initialised = Z_DEFLATED;
+ else
+ {
+ TRYFREE(pfile_in_zip_read_info);
+ return err;
+ }
+ /* windowBits is passed < 0 to tell that there is no zlib header.
+ * Note that in this case inflate *requires* an extra "dummy" byte
+ * after the compressed stream in order to complete decompression and
+ * return Z_STREAM_END.
+ * In unzip, i don't wait absolutely Z_STREAM_END because I known the
+ * size of both compressed and uncompressed data
+ */
+ }
+ }
+
+ pfile_in_zip_read_info->rest_read_compressed = s->cur_file_info.compressed_size;
+ pfile_in_zip_read_info->rest_read_uncompressed = s->cur_file_info.uncompressed_size;
+ pfile_in_zip_read_info->pos_in_zipfile = s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + iSizeVar;
+ pfile_in_zip_read_info->stream.avail_in = (uInt)0;
+
+ s->pfile_in_zip_read = pfile_in_zip_read_info;
+
+#ifndef NOUNCRYPT
+ s->pcrc_32_tab = NULL;
+
+ if ((password != NULL) && ((s->cur_file_info.flag & 1) != 0))
+ {
+ if (ZSEEK64(s->z_filefunc, s->filestream,
+ s->pfile_in_zip_read->pos_in_zipfile + s->pfile_in_zip_read->byte_before_the_zipfile,
+ ZLIB_FILEFUNC_SEEK_SET) != 0)
+ return UNZ_INTERNALERROR;
+#ifdef HAVE_AES
+ if (s->cur_file_info.compression_method == AES_METHOD)
+ {
+ unsigned char passverify_archive[AES_PWVERIFYSIZE];
+ unsigned char passverify_password[AES_PWVERIFYSIZE];
+ unsigned char saltvalue[AES_MAXSALTLENGTH];
+ uInt saltlength;
+
+ if ((s->cur_file_info_internal.aes_encryption_mode < 1) ||
+ (s->cur_file_info_internal.aes_encryption_mode > 3))
+ return UNZ_INTERNALERROR;
+
+ saltlength = SALT_LENGTH(s->cur_file_info_internal.aes_encryption_mode);
+
+ if (ZREAD64(s->z_filefunc, s->filestream, saltvalue, saltlength) != saltlength)
+ return UNZ_INTERNALERROR;
+ if (ZREAD64(s->z_filefunc, s->filestream, passverify_archive, AES_PWVERIFYSIZE) != AES_PWVERIFYSIZE)
+ return UNZ_INTERNALERROR;
+
+ fcrypt_init(s->cur_file_info_internal.aes_encryption_mode, password, strlen(password), saltvalue,
+ passverify_password, &s->pfile_in_zip_read->aes_ctx);
+
+ if (memcmp(passverify_archive, passverify_password, AES_PWVERIFYSIZE) != 0)
+ return UNZ_BADPASSWORD;
+
+ s->pfile_in_zip_read->rest_read_compressed -= saltlength + AES_PWVERIFYSIZE;
+ s->pfile_in_zip_read->rest_read_compressed -= AES_AUTHCODESIZE;
+
+ s->pfile_in_zip_read->pos_in_zipfile += saltlength + AES_PWVERIFYSIZE;
+ }
+ else
+#endif
+ {
+ int i;
+ s->pcrc_32_tab = (const unsigned int*)get_crc_table();
+ init_keys(password, s->keys, s->pcrc_32_tab);
+
+ if (ZREAD64(s->z_filefunc, s->filestream, source, 12) < 12)
+ return UNZ_INTERNALERROR;
+
+ for (i = 0; i < 12; i++)
+ zdecode(s->keys, s->pcrc_32_tab, source[i]);
+
+ s->pfile_in_zip_read->rest_read_compressed -= 12;
+
+ s->pfile_in_zip_read->pos_in_zipfile += 12;
+ }
+ }
+#endif
+
+ return UNZ_OK;
+}
+
+extern int ZEXPORT unzOpenCurrentFile(unzFile file)
+{
+ return unzOpenCurrentFile3(file, NULL, NULL, 0, NULL);
+}
+
+extern int ZEXPORT unzOpenCurrentFilePassword(unzFile file, const char* password)
+{
+ return unzOpenCurrentFile3(file, NULL, NULL, 0, password);
+}
+
+extern int ZEXPORT unzOpenCurrentFile2(unzFile file, int* method, int* level, int raw)
+{
+ return unzOpenCurrentFile3(file, method, level, raw, NULL);
+}
+
+/* Read bytes from the current file.
+ buf contain buffer where data must be copied
+ len the size of buf.
+
+ return the number of byte copied if some bytes are copied
+ return 0 if the end of file was reached
+ return <0 with error code if there is an error (UNZ_ERRNO for IO error, or zLib error for uncompress error) */
+extern int ZEXPORT unzReadCurrentFile(unzFile file, voidp buf, unsigned len)
+{
+ int err = UNZ_OK;
+ uInt read = 0;
+ unz64_s* s;
+ if (file == NULL)
+ return UNZ_PARAMERROR;
+ s = (unz64_s*)file;
+
+ if (s->pfile_in_zip_read == NULL)
+ return UNZ_PARAMERROR;
+ if (s->pfile_in_zip_read->read_buffer == NULL)
+ return UNZ_END_OF_LIST_OF_FILE;
+ if (len == 0)
+ return 0;
+
+ s->pfile_in_zip_read->stream.next_out = (Bytef*)buf;
+ s->pfile_in_zip_read->stream.avail_out = (uInt)len;
+
+ if (s->pfile_in_zip_read->raw)
+ {
+ if (len > s->pfile_in_zip_read->rest_read_compressed + s->pfile_in_zip_read->stream.avail_in)
+ s->pfile_in_zip_read->stream.avail_out = (uInt)s->pfile_in_zip_read->rest_read_compressed +
+ s->pfile_in_zip_read->stream.avail_in;
+ }
+ else
+ {
+ if (len > s->pfile_in_zip_read->rest_read_uncompressed)
+ s->pfile_in_zip_read->stream.avail_out = (uInt)s->pfile_in_zip_read->rest_read_uncompressed;
+ }
+
+ while (s->pfile_in_zip_read->stream.avail_out > 0)
+ {
+ if (s->pfile_in_zip_read->stream.avail_in == 0)
+ {
+ uLong bytes_to_read = UNZ_BUFSIZE;
+ uLong bytes_not_read = 0;
+ uLong bytes_read = 0;
+ uLong total_bytes_read = 0;
+
+ if (s->pfile_in_zip_read->stream.next_in != NULL)
+ bytes_not_read = s->pfile_in_zip_read->read_buffer + UNZ_BUFSIZE -
+ s->pfile_in_zip_read->stream.next_in;
+ bytes_to_read -= bytes_not_read;
+ if (bytes_not_read > 0)
+ memcpy(s->pfile_in_zip_read->read_buffer, s->pfile_in_zip_read->stream.next_in, bytes_not_read);
+ if (s->pfile_in_zip_read->rest_read_compressed < bytes_to_read)
+ bytes_to_read = (uInt)s->pfile_in_zip_read->rest_read_compressed;
+
+ while (total_bytes_read != bytes_to_read)
+ {
+ if (ZSEEK64(s->pfile_in_zip_read->z_filefunc, s->pfile_in_zip_read->filestream,
+ s->pfile_in_zip_read->pos_in_zipfile + s->pfile_in_zip_read->byte_before_the_zipfile,
+ ZLIB_FILEFUNC_SEEK_SET) != 0)
+ return UNZ_ERRNO;
+
+ bytes_read = ZREAD64(s->pfile_in_zip_read->z_filefunc, s->pfile_in_zip_read->filestream,
+ s->pfile_in_zip_read->read_buffer + bytes_not_read + total_bytes_read,
+ bytes_to_read - total_bytes_read);
+
+ total_bytes_read += bytes_read;
+ s->pfile_in_zip_read->pos_in_zipfile += bytes_read;
+
+ if (bytes_read == 0)
+ {
+ if (ZERROR64(s->pfile_in_zip_read->z_filefunc, s->pfile_in_zip_read->filestream))
+ return UNZ_ERRNO;
+
+ err = unzGoToNextDisk(file);
+ if (err != UNZ_OK)
+ return err;
+
+ s->pfile_in_zip_read->pos_in_zipfile = 0;
+ s->pfile_in_zip_read->filestream = s->filestream;
+ }
+ }
+
+#ifndef NOUNCRYPT
+ if ((s->cur_file_info.flag & 1) != 0)
+ {
+#ifdef HAVE_AES
+ if (s->cur_file_info.compression_method == AES_METHOD)
+ {
+ fcrypt_decrypt(s->pfile_in_zip_read->read_buffer, bytes_to_read, &s->pfile_in_zip_read->aes_ctx);
+ }
+ else
+#endif
+ if (s->pcrc_32_tab != NULL)
+ {
+ uInt i;
+ for(i = 0; i < total_bytes_read; i++)
+ s->pfile_in_zip_read->read_buffer[i] =
+ zdecode(s->keys, s->pcrc_32_tab, s->pfile_in_zip_read->read_buffer[i]);
+ }
+ }
+#endif
+
+ s->pfile_in_zip_read->rest_read_compressed -= total_bytes_read;
+ s->pfile_in_zip_read->stream.next_in = (Bytef*)s->pfile_in_zip_read->read_buffer;
+ s->pfile_in_zip_read->stream.avail_in = (uInt)(bytes_not_read + total_bytes_read);
+ }
+
+ if ((s->pfile_in_zip_read->compression_method == 0) || (s->pfile_in_zip_read->raw))
+ {
+ uInt copy, i;
+
+ if ((s->pfile_in_zip_read->stream.avail_in == 0) &&
+ (s->pfile_in_zip_read->rest_read_compressed == 0))
+ return (read == 0) ? UNZ_EOF : read;
+
+ if (s->pfile_in_zip_read->stream.avail_out < s->pfile_in_zip_read->stream.avail_in)
+ copy = s->pfile_in_zip_read->stream.avail_out;
+ else
+ copy = s->pfile_in_zip_read->stream.avail_in;
+
+ for (i = 0; i < copy; i++)
+ *(s->pfile_in_zip_read->stream.next_out+i) =
+ *(s->pfile_in_zip_read->stream.next_in+i);
+
+ s->pfile_in_zip_read->total_out_64 = s->pfile_in_zip_read->total_out_64 + copy;
+ s->pfile_in_zip_read->rest_read_uncompressed -= copy;
+ s->pfile_in_zip_read->crc32 = crc32(s->pfile_in_zip_read->crc32,
+ s->pfile_in_zip_read->stream.next_out, copy);
+
+ s->pfile_in_zip_read->stream.avail_in -= copy;
+ s->pfile_in_zip_read->stream.avail_out -= copy;
+ s->pfile_in_zip_read->stream.next_out += copy;
+ s->pfile_in_zip_read->stream.next_in += copy;
+ s->pfile_in_zip_read->stream.total_out += copy;
+ read += copy;
+ }
+ else if (s->pfile_in_zip_read->compression_method == Z_BZIP2ED)
+ {
+#ifdef HAVE_BZIP2
+ uLong total_out_before, total_out_after;
+ const Bytef *buf_before;
+ uLong out_bytes;
+
+ s->pfile_in_zip_read->bstream.next_in = (char*)s->pfile_in_zip_read->stream.next_in;
+ s->pfile_in_zip_read->bstream.avail_in = s->pfile_in_zip_read->stream.avail_in;
+ s->pfile_in_zip_read->bstream.total_in_lo32 = (uInt)s->pfile_in_zip_read->stream.total_in;
+ s->pfile_in_zip_read->bstream.total_in_hi32 = s->pfile_in_zip_read->stream.total_in >> 32;
+
+ s->pfile_in_zip_read->bstream.next_out = (char*)s->pfile_in_zip_read->stream.next_out;
+ s->pfile_in_zip_read->bstream.avail_out = s->pfile_in_zip_read->stream.avail_out;
+ s->pfile_in_zip_read->bstream.total_out_lo32 = (uInt)s->pfile_in_zip_read->stream.total_out;
+ s->pfile_in_zip_read->bstream.total_out_hi32 = s->pfile_in_zip_read->stream.total_out >> 32;
+
+ total_out_before = s->pfile_in_zip_read->bstream.total_out_lo32 +
+ (((uLong)s->pfile_in_zip_read->bstream.total_out_hi32) << 32);
+ buf_before = (const Bytef *)s->pfile_in_zip_read->bstream.next_out;
+
+ err = BZ2_bzDecompress(&s->pfile_in_zip_read->bstream);
+
+ total_out_after = s->pfile_in_zip_read->bstream.total_out_lo32 +
+ (((uLong)s->pfile_in_zip_read->bstream.total_out_hi32) << 32);
+
+ out_bytes = total_out_after-total_out_before;
+
+ s->pfile_in_zip_read->total_out_64 = s->pfile_in_zip_read->total_out_64 + out_bytes;
+ s->pfile_in_zip_read->rest_read_uncompressed -= out_bytes;
+ s->pfile_in_zip_read->crc32 = crc32(s->pfile_in_zip_read->crc32,buf_before, (uInt)(out_bytes));
+
+ read += (uInt)(total_out_after - total_out_before);
+
+ s->pfile_in_zip_read->stream.next_in = (Bytef*)s->pfile_in_zip_read->bstream.next_in;
+ s->pfile_in_zip_read->stream.avail_in = s->pfile_in_zip_read->bstream.avail_in;
+ s->pfile_in_zip_read->stream.total_in = s->pfile_in_zip_read->bstream.total_in_lo32;
+ s->pfile_in_zip_read->stream.next_out = (Bytef*)s->pfile_in_zip_read->bstream.next_out;
+ s->pfile_in_zip_read->stream.avail_out = s->pfile_in_zip_read->bstream.avail_out;
+ s->pfile_in_zip_read->stream.total_out = s->pfile_in_zip_read->bstream.total_out_lo32;
+
+ if (err == BZ_STREAM_END)
+ return (read == 0) ? UNZ_EOF : read;
+ if (err != BZ_OK)
+ break;
+#endif
+ }
+ else
+ {
+ ZPOS64_T total_out_before, total_out_after;
+ const Bytef *buf_before;
+ ZPOS64_T out_bytes;
+ int flush=Z_SYNC_FLUSH;
+
+ total_out_before = s->pfile_in_zip_read->stream.total_out;
+ buf_before = s->pfile_in_zip_read->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(&s->pfile_in_zip_read->stream,flush);
+
+ if ((err >= 0) && (s->pfile_in_zip_read->stream.msg != NULL))
+ err = Z_DATA_ERROR;
+
+ total_out_after = s->pfile_in_zip_read->stream.total_out;
+ out_bytes = total_out_after-total_out_before;
+
+ s->pfile_in_zip_read->total_out_64 += out_bytes;
+ s->pfile_in_zip_read->rest_read_uncompressed -= out_bytes;
+ s->pfile_in_zip_read->crc32 =
+ crc32(s->pfile_in_zip_read->crc32,buf_before, (uInt)(out_bytes));
+
+ read += (uInt)(total_out_after - total_out_before);
+
+ if (err == Z_STREAM_END)
+ return (read == 0) ? UNZ_EOF : read;
+ if (err != Z_OK)
+ break;
+ }
+ }
+
+ if (err == Z_OK)
+ return read;
+ return err;
+}
+
+extern ZPOS64_T ZEXPORT unzGetCurrentFileZStreamPos64(unzFile file)
+{
+ unz64_s* s;
+ s = (unz64_s*)file;
+ if (file == NULL)
+ return 0; /* UNZ_PARAMERROR */
+ if (s->pfile_in_zip_read == NULL)
+ return 0; /* UNZ_PARAMERROR */
+ return s->pfile_in_zip_read->pos_in_zipfile + s->pfile_in_zip_read->byte_before_the_zipfile;
+}
+
+extern int ZEXPORT unzGetLocalExtrafield(unzFile file, voidp buf, unsigned len)
+{
+ unz64_s* s;
+ uInt read_now;
+ ZPOS64_T size_to_read;
+
+ if (file == NULL)
+ return UNZ_PARAMERROR;
+ s = (unz64_s*)file;
+ if (s->pfile_in_zip_read == NULL)
+ return UNZ_PARAMERROR;
+
+ size_to_read = s->pfile_in_zip_read->size_local_extrafield - s->pfile_in_zip_read->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 (ZSEEK64(s->pfile_in_zip_read->z_filefunc, s->pfile_in_zip_read->filestream,
+ s->pfile_in_zip_read->offset_local_extrafield + s->pfile_in_zip_read->pos_local_extrafield,
+ ZLIB_FILEFUNC_SEEK_SET) != 0)
+ return UNZ_ERRNO;
+
+ if (ZREAD64(s->pfile_in_zip_read->z_filefunc, s->pfile_in_zip_read->filestream, buf, read_now) != read_now)
+ return UNZ_ERRNO;
+
+ return (int)read_now;
+}
+
+extern int ZEXPORT unzCloseCurrentFile(unzFile file)
+{
+ int err = UNZ_OK;
+
+ unz64_s* s;
+ file_in_zip64_read_info_s* pfile_in_zip_read_info;
+ if (file == NULL)
+ return UNZ_PARAMERROR;
+ s = (unz64_s*)file;
+ pfile_in_zip_read_info=s->pfile_in_zip_read;
+
+ if (pfile_in_zip_read_info == NULL)
+ return UNZ_PARAMERROR;
+
+#ifdef HAVE_AES
+ if (s->cur_file_info.compression_method == AES_METHOD)
+ {
+ unsigned char authcode[AES_AUTHCODESIZE];
+ unsigned char rauthcode[AES_AUTHCODESIZE];
+
+ if (ZREAD64(s->z_filefunc, s->filestream, authcode, AES_AUTHCODESIZE) != AES_AUTHCODESIZE)
+ return UNZ_ERRNO;
+
+ if (fcrypt_end(rauthcode, &s->pfile_in_zip_read->aes_ctx) != AES_AUTHCODESIZE)
+ err = UNZ_CRCERROR;
+ if (memcmp(authcode, rauthcode, AES_AUTHCODESIZE) != 0)
+ err = UNZ_CRCERROR;
+ }
+ /* AES zip version AE-1 will expect a valid crc as well */
+ if ((s->cur_file_info.compression_method != AES_METHOD) ||
+ (s->cur_file_info_internal.aes_version == 0x0001))
+#endif
+ {
+ if ((pfile_in_zip_read_info->rest_read_uncompressed == 0) &&
+ (!pfile_in_zip_read_info->raw))
+ {
+ if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait)
+ err = UNZ_CRCERROR;
+ }
+ }
+
+ TRYFREE(pfile_in_zip_read_info->read_buffer);
+ pfile_in_zip_read_info->read_buffer = NULL;
+ if (pfile_in_zip_read_info->stream_initialised == Z_DEFLATED)
+ inflateEnd(&pfile_in_zip_read_info->stream);
+#ifdef HAVE_BZIP2
+ else if (pfile_in_zip_read_info->stream_initialised == Z_BZIP2ED)
+ BZ2_bzDecompressEnd(&pfile_in_zip_read_info->bstream);
+#endif
+
+ pfile_in_zip_read_info->stream_initialised = 0;
+ TRYFREE(pfile_in_zip_read_info);
+
+ s->pfile_in_zip_read = NULL;
+
+ return err;
+}
+
+extern int ZEXPORT unzGoToFirstFile2(unzFile file, unz_file_info64 *pfile_info, char *filename,
+ uLong filename_size, void *extrafield, uLong extrafield_size, char *comment, uLong comment_size)
+{
+ int err = UNZ_OK;
+ unz64_s* s;
+ if (file == NULL)
+ return UNZ_PARAMERROR;
+ s = (unz64_s*)file;
+ s->pos_in_central_dir = s->offset_central_dir;
+ s->num_file = 0;
+ err = unz64local_GetCurrentFileInfoInternal(file, &s->cur_file_info, &s->cur_file_info_internal,
+ filename,filename_size, extrafield,extrafield_size, comment,comment_size);
+ s->current_file_ok = (err == UNZ_OK);
+ if ((err == UNZ_OK) && (pfile_info != NULL))
+ memcpy(pfile_info, &s->cur_file_info, sizeof(unz_file_info64));
+ return err;
+}
+
+extern int ZEXPORT unzGoToFirstFile(unzFile file)
+{
+ return unzGoToFirstFile2(file, NULL, NULL, 0, NULL, 0, NULL, 0);
+}
+
+extern int ZEXPORT unzGoToNextFile2(unzFile file, unz_file_info64 *pfile_info, char *filename,
+ uLong filename_size, void *extrafield, uLong extrafield_size, char *comment, uLong comment_size)
+{
+ unz64_s* s;
+ int err;
+
+ if (file == NULL)
+ return UNZ_PARAMERROR;
+ s = (unz64_s*)file;
+ if (!s->current_file_ok)
+ return UNZ_END_OF_LIST_OF_FILE;
+ if (s->gi.number_entry != 0xffff) /* 2^16 files overflow hack */
+ if (s->num_file+1 == s->gi.number_entry)
+ return UNZ_END_OF_LIST_OF_FILE;
+ s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename +
+ s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment;
+ s->num_file++;
+ err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, &s->cur_file_info_internal,
+ filename, filename_size, extrafield,extrafield_size, comment,comment_size);
+ s->current_file_ok = (err == UNZ_OK);
+ if ((err == UNZ_OK) && (pfile_info != NULL))
+ memcpy(pfile_info, &s->cur_file_info, sizeof(unz_file_info64));
+ return err;
+}
+
+extern int ZEXPORT unzGoToNextFile(unzFile file)
+{
+ return unzGoToNextFile2(file, NULL, NULL, 0, NULL, 0, NULL, 0);
+}
+
+extern int ZEXPORT unzLocateFile(unzFile file, const char *filename, unzFileNameComparer filename_compare_func)
+{
+ unz64_s* s;
+ int err;
+ unz_file_info64 cur_file_info_saved;
+ unz_file_info64_internal cur_file_info_internal_saved;
+ ZPOS64_T num_file_saved;
+ ZPOS64_T pos_in_central_dir_saved;
+ char current_filename[UNZ_MAXFILENAMEINZIP+1];
+
+ if (file == NULL)
+ return UNZ_PARAMERROR;
+ if (strlen(filename) >= UNZ_MAXFILENAMEINZIP)
+ return UNZ_PARAMERROR;
+ s = (unz64_s*)file;
+ if (!s->current_file_ok)
+ return UNZ_END_OF_LIST_OF_FILE;
+
+ /* Save the current state */
+ num_file_saved = s->num_file;
+ pos_in_central_dir_saved = s->pos_in_central_dir;
+ cur_file_info_saved = s->cur_file_info;
+ cur_file_info_internal_saved = s->cur_file_info_internal;
+
+ err = unzGoToFirstFile2(file, NULL, current_filename, sizeof(current_filename)-1, NULL, 0, NULL, 0);
+
+ while (err == UNZ_OK)
+ {
+ if (filename_compare_func != NULL)
+ err = filename_compare_func(file, current_filename, filename);
+ else
+ err = strcmp(current_filename, filename);
+ if (err == 0)
+ return UNZ_OK;
+ err = unzGoToNextFile2(file, NULL, current_filename, sizeof(current_filename)-1, NULL, 0, NULL, 0);
+ }
+
+ /* We failed, so restore the state of the 'current file' to where we were. */
+ s->num_file = num_file_saved;
+ s->pos_in_central_dir = pos_in_central_dir_saved;
+ s->cur_file_info = cur_file_info_saved;
+ s->cur_file_info_internal = cur_file_info_internal_saved;
+ return err;
+}
+
+extern int ZEXPORT unzGetFilePos(unzFile file, unz_file_pos* file_pos)
+{
+ unz64_file_pos file_pos64;
+ int err = unzGetFilePos64(file,&file_pos64);
+ if (err == UNZ_OK)
+ {
+ file_pos->pos_in_zip_directory = (uLong)file_pos64.pos_in_zip_directory;
+ file_pos->num_of_file = (uLong)file_pos64.num_of_file;
+ }
+ return err;
+}
+
+extern int ZEXPORT unzGoToFilePos(unzFile file, unz_file_pos* file_pos)
+{
+ unz64_file_pos file_pos64;
+
+ if (file_pos == NULL)
+ return UNZ_PARAMERROR;
+ file_pos64.pos_in_zip_directory = file_pos->pos_in_zip_directory;
+ file_pos64.num_of_file = file_pos->num_of_file;
+ return unzGoToFilePos64(file,&file_pos64);
+}
+
+extern int ZEXPORT unzGetFilePos64(unzFile file, unz64_file_pos* file_pos)
+{
+ unz64_s* s;
+
+ if (file == NULL || file_pos == NULL)
+ return UNZ_PARAMERROR;
+ s = (unz64_s*)file;
+ if (!s->current_file_ok)
+ return UNZ_END_OF_LIST_OF_FILE;
+
+ file_pos->pos_in_zip_directory = s->pos_in_central_dir;
+ file_pos->num_of_file = s->num_file;
+
+ return UNZ_OK;
+}
+
+extern int ZEXPORT unzGoToFilePos64(unzFile file, const unz64_file_pos* file_pos)
+{
+ unz64_s* s;
+ int err;
+
+ if (file == NULL || file_pos == NULL)
+ return UNZ_PARAMERROR;
+ s = (unz64_s*)file;
+
+ /* jump to the right spot */
+ s->pos_in_central_dir = file_pos->pos_in_zip_directory;
+ s->num_file = file_pos->num_of_file;
+
+ /* set the current file */
+ err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, &s->cur_file_info_internal,NULL,0,NULL,0,NULL,0);
+ /* return results */
+ s->current_file_ok = (err == UNZ_OK);
+ return err;
+}
+
+extern uLong ZEXPORT unzGetOffset(unzFile file)
+{
+ ZPOS64_T offset64;
+
+ if (file == NULL)
+ return 0; /* UNZ_PARAMERROR; */
+ offset64 = unzGetOffset64(file);
+ return (uLong)offset64;
+}
+
+extern ZPOS64_T ZEXPORT unzGetOffset64(unzFile file)
+{
+ unz64_s* s;
+
+ if (file == NULL)
+ return 0; /* UNZ_PARAMERROR; */
+ s = (unz64_s*)file;
+ if (!s->current_file_ok)
+ return 0;
+ if (s->gi.number_entry != 0 && s->gi.number_entry != 0xffff)
+ if (s->num_file == s->gi.number_entry)
+ return 0;
+ return s->pos_in_central_dir;
+}
+
+extern int ZEXPORT unzSetOffset(unzFile file, uLong pos)
+{
+ return unzSetOffset64(file, pos);
+}
+
+extern int ZEXPORT unzSetOffset64(unzFile file, ZPOS64_T pos)
+{
+ unz64_s* s;
+ int err;
+
+ if (file == NULL)
+ return UNZ_PARAMERROR;
+ s = (unz64_s*)file;
+ s->pos_in_central_dir = pos;
+ s->num_file = s->gi.number_entry; /* hack */
+
+ err = unz64local_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;
+}
+
+extern z_off_t ZEXPORT unztell(unzFile file)
+{
+ unz64_s* s;
+ if (file == NULL)
+ return UNZ_PARAMERROR;
+ s = (unz64_s*)file;
+ if (s->pfile_in_zip_read == NULL)
+ return UNZ_PARAMERROR;
+ return (z_off_t)s->pfile_in_zip_read->stream.total_out;
+}
+
+extern ZPOS64_T ZEXPORT unztell64(unzFile file)
+{
+ unz64_s* s;
+ if (file == NULL)
+ return (ZPOS64_T)-1;
+ s = (unz64_s*)file;
+ if (s->pfile_in_zip_read == NULL)
+ return (ZPOS64_T)-1;
+ return s->pfile_in_zip_read->total_out_64;
+}
+
+extern int ZEXPORT unzseek(unzFile file, z_off_t offset, int origin)
+{
+ return unzseek64(file, (ZPOS64_T)offset, origin);
+}
+
+extern int ZEXPORT unzseek64(unzFile file, ZPOS64_T offset, int origin)
+{
+ unz64_s* s;
+ ZPOS64_T stream_pos_begin;
+ ZPOS64_T stream_pos_end;
+ int isWithinBuffer;
+ ZPOS64_T position;
+
+ if (file == NULL)
+ return UNZ_PARAMERROR;
+
+ s = (unz64_s*)file;
+
+ if (s->pfile_in_zip_read == NULL)
+ return UNZ_ERRNO;
+ if (s->pfile_in_zip_read->compression_method != 0)
+ return UNZ_ERRNO;
+
+ if (origin == SEEK_SET)
+ position = offset;
+ else if (origin == SEEK_CUR)
+ position = s->pfile_in_zip_read->total_out_64 + offset;
+ else if (origin == SEEK_END)
+ position = s->cur_file_info.compressed_size + offset;
+ else
+ return UNZ_PARAMERROR;
+
+ if (position > s->cur_file_info.compressed_size)
+ return UNZ_PARAMERROR;
+
+ stream_pos_end = s->pfile_in_zip_read->pos_in_zipfile;
+ stream_pos_begin = stream_pos_end;
+
+ if (stream_pos_begin > UNZ_BUFSIZE)
+ stream_pos_begin -= UNZ_BUFSIZE;
+ else
+ stream_pos_begin = 0;
+
+ isWithinBuffer = s->pfile_in_zip_read->stream.avail_in != 0 &&
+ (s->pfile_in_zip_read->rest_read_compressed != 0 || s->cur_file_info.compressed_size < UNZ_BUFSIZE) &&
+ position >= stream_pos_begin && position < stream_pos_end;
+
+ if (isWithinBuffer)
+ {
+ s->pfile_in_zip_read->stream.next_in += position - s->pfile_in_zip_read->total_out_64;
+ s->pfile_in_zip_read->stream.avail_in = (uInt)(stream_pos_end - position);
+ }
+ else
+ {
+ s->pfile_in_zip_read->stream.avail_in = 0;
+ s->pfile_in_zip_read->stream.next_in = 0;
+
+ s->pfile_in_zip_read->pos_in_zipfile = s->pfile_in_zip_read->offset_local_extrafield + position;
+ s->pfile_in_zip_read->rest_read_compressed = s->cur_file_info.compressed_size - position;
+ }
+
+ s->pfile_in_zip_read->rest_read_uncompressed -= (position - s->pfile_in_zip_read->total_out_64);
+ s->pfile_in_zip_read->stream.total_out = (uLong)position;
+ s->pfile_in_zip_read->total_out_64 = position;
+
+ return UNZ_OK;
+}
+
+extern int ZEXPORT unzeof(unzFile file)
+{
+ unz64_s* s;
+ if (file == NULL)
+ return UNZ_PARAMERROR;
+ s = (unz64_s*)file;
+ if (s->pfile_in_zip_read == NULL)
+ return UNZ_PARAMERROR;
+ if (s->pfile_in_zip_read->rest_read_uncompressed == 0)
+ return 1;
+ return 0;
+}
diff --git a/src/qcommon/unzip.h b/src/qcommon/unzip.h
new file mode 100644
index 0000000..09e930a
--- /dev/null
+++ b/src/qcommon/unzip.h
@@ -0,0 +1,321 @@
+/* unzip.h -- IO for uncompress .zip files using zlib
+ Version 1.1, February 14h, 2010
+ part of the MiniZip project
+
+ Copyright (C) 1998-2010 Gilles Vollant
+ http://www.winimage.com/zLibDll/minizip.html
+ Modifications of Unzip for Zip64
+ Copyright (C) 2007-2008 Even Rouault
+ Modifications for Zip64 support on both zip and unzip
+ Copyright (C) 2009-2010 Mathias Svensson
+ http://result42.com
+
+ This program is distributed under the terms of the same license as zlib.
+ See the accompanying LICENSE file for the full text of the license.
+*/
+
+#ifndef _UNZ_H
+#define _UNZ_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef _ZLIB_H
+#include "zconf.h"
+#include "zlib.h"
+#endif
+
+#ifndef _ZLIBIOAPI_H
+#include "ioapi.h"
+#endif
+
+#ifdef HAVE_BZIP2
+#include "bzlib.h"
+#endif
+
+#define Z_BZIP2ED 12
+
+#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP)
+/* like the STRICT of WIN32, we define a pointer that cannot be converted
+ from (void*) without cast */
+typedef struct TagunzFile__ { int unused; } unzFile__;
+typedef unzFile__ *unzFile;
+#else
+typedef voidp unzFile;
+#endif
+
+#define UNZ_OK (0)
+#define UNZ_END_OF_LIST_OF_FILE (-100)
+#define UNZ_ERRNO (Z_ERRNO)
+#define UNZ_EOF (0)
+#define UNZ_PARAMERROR (-102)
+#define UNZ_BADZIPFILE (-103)
+#define UNZ_INTERNALERROR (-104)
+#define UNZ_CRCERROR (-105)
+#define UNZ_BADPASSWORD (-106)
+
+/* tm_unz contain date/time info */
+typedef struct tm_unz_s
+{
+ uInt tm_sec; /* seconds after the minute - [0,59] */
+ uInt tm_min; /* minutes after the hour - [0,59] */
+ uInt tm_hour; /* hours since midnight - [0,23] */
+ uInt tm_mday; /* day of the month - [1,31] */
+ uInt tm_mon; /* months since January - [0,11] */
+ uInt tm_year; /* years - [1980..2044] */
+} tm_unz;
+
+/* unz_global_info structure contain global data about the ZIPfile
+ These data comes from the end of central dir */
+typedef struct unz_global_info64_s
+{
+ ZPOS64_T number_entry; /* total number of entries in the central dir on this disk */
+ uLong number_disk_with_CD; /* number the the disk with central dir, used for spanning ZIP*/
+ uLong size_comment; /* size of the global comment of the zipfile */
+} unz_global_info64;
+
+typedef struct unz_global_info_s
+{
+ uLong number_entry; /* total number of entries in the central dir on this disk */
+ uLong number_disk_with_CD; /* number the the disk with central dir, used for spanning ZIP*/
+ uLong size_comment; /* size of the global comment of the zipfile */
+} unz_global_info;
+
+/* unz_file_info contain information about a file in the zipfile */
+typedef struct unz_file_info64_s
+{
+ uLong version; /* version made by 2 bytes */
+ uLong version_needed; /* version needed to extract 2 bytes */
+ uLong flag; /* general purpose bit flag 2 bytes */
+ uLong compression_method; /* compression method 2 bytes */
+ uLong dosDate; /* last mod file date in Dos fmt 4 bytes */
+ uLong crc; /* crc-32 4 bytes */
+ ZPOS64_T compressed_size; /* compressed size 8 bytes */
+ ZPOS64_T uncompressed_size; /* uncompressed size 8 bytes */
+ uLong size_filename; /* filename length 2 bytes */
+ uLong size_file_extra; /* extra field length 2 bytes */
+ uLong size_file_comment; /* file comment length 2 bytes */
+
+ uLong disk_num_start; /* disk number start 2 bytes */
+ uLong internal_fa; /* internal file attributes 2 bytes */
+ uLong external_fa; /* external file attributes 4 bytes */
+
+ tm_unz tmu_date;
+ ZPOS64_T disk_offset;
+ uLong size_file_extra_internal;
+} unz_file_info64;
+
+typedef struct unz_file_info_s
+{
+ uLong version; /* version made by 2 bytes */
+ uLong version_needed; /* version needed to extract 2 bytes */
+ uLong flag; /* general purpose bit flag 2 bytes */
+ uLong compression_method; /* compression method 2 bytes */
+ uLong dosDate; /* last mod file date in Dos fmt 4 bytes */
+ uLong crc; /* crc-32 4 bytes */
+ uLong compressed_size; /* compressed size 4 bytes */
+ uLong uncompressed_size; /* uncompressed size 4 bytes */
+ uLong size_filename; /* filename length 2 bytes */
+ uLong size_file_extra; /* extra field length 2 bytes */
+ uLong size_file_comment; /* file comment length 2 bytes */
+
+ uLong disk_num_start; /* disk number start 2 bytes */
+ uLong internal_fa; /* internal file attributes 2 bytes */
+ uLong external_fa; /* external file attributes 4 bytes */
+
+ tm_unz tmu_date;
+ uLong disk_offset;
+} unz_file_info;
+
+/***************************************************************************/
+/* Opening and close a zip file */
+
+extern unzFile ZEXPORT unzOpen OF((const char *path));
+extern unzFile ZEXPORT unzOpen64 OF((const void *path));
+/* Open a Zip file.
+
+ path should contain the full pathname (by example, on a Windows XP computer
+ "c:\\zlib\\zlib113.zip" or on an Unix computer "zlib/zlib113.zip".
+ return NULL if zipfile cannot be opened or doesn't exist
+ return unzFile handle if no error
+
+ NOTE: The "64" function take a const void* pointer, because the path is just the value passed to the
+ open64_file_func callback. Under Windows, if UNICODE is defined, using fill_fopen64_filefunc, the path
+ is a pointer to a wide unicode string (LPCTSTR is LPCWSTR), so const char* does not describe the reality */
+
+extern unzFile ZEXPORT unzOpen2 OF((const char *path, zlib_filefunc_def* pzlib_filefunc_def));
+/* Open a Zip file, like unzOpen, but provide a set of file low level API for read/write operations */
+extern unzFile ZEXPORT unzOpen2_64 OF((const void *path, zlib_filefunc64_def* pzlib_filefunc_def));
+/* Open a Zip file, like unz64Open, but provide a set of file low level API for read/write 64-bit operations */
+
+extern int ZEXPORT unzClose OF((unzFile file));
+/* Close a ZipFile opened with unzOpen. If there is files inside the .Zip opened with unzOpenCurrentFile,
+ these files MUST be closed with unzipCloseCurrentFile before call unzipClose.
+
+ return UNZ_OK if there is no error */
+
+extern int ZEXPORT unzGetGlobalInfo OF((unzFile file, unz_global_info *pglobal_info));
+extern int ZEXPORT unzGetGlobalInfo64 OF((unzFile file, unz_global_info64 *pglobal_info));
+/* Write info about the ZipFile in the *pglobal_info structure.
+
+ return UNZ_OK if no error */
+
+extern int ZEXPORT unzGetGlobalComment OF((unzFile file, char *comment, uLong comment_size));
+/* Get the global comment string of the ZipFile, in the comment buffer.
+
+ uSizeBuf is the size of the szComment buffer.
+ return the number of byte copied or an error code <0 */
+
+/***************************************************************************/
+/* Reading the content of the current zipfile, you can open it, read data from it, and close it
+ (you can close it before reading all the file) */
+
+extern int ZEXPORT unzOpenCurrentFile OF((unzFile file));
+/* Open for reading data the current file in the zipfile.
+
+ return UNZ_OK if no error */
+
+extern int ZEXPORT unzOpenCurrentFilePassword OF((unzFile file, const char* password));
+/* Open for reading data the current file in the zipfile.
+ password is a crypting password
+
+ return UNZ_OK if no error */
+
+extern int ZEXPORT unzOpenCurrentFile2 OF((unzFile file, int* method, int* level, int raw));
+/* Same as unzOpenCurrentFile, but open for read raw the file (not uncompress)
+ if raw==1 *method will receive method of compression, *level will receive level of compression
+
+ NOTE: you can set level parameter as NULL (if you did not want known level,
+ but you CANNOT set method parameter as NULL */
+
+extern int ZEXPORT unzOpenCurrentFile3 OF((unzFile file, int* method, int* level, int raw, const char* password));
+/* Same as unzOpenCurrentFile, but takes extra parameter password for encrypted files */
+
+extern int ZEXPORT unzReadCurrentFile OF((unzFile file, voidp buf, unsigned len));
+/* Read bytes from the current file (opened by unzOpenCurrentFile)
+ buf contain buffer where data must be copied
+ len the size of buf.
+
+ return the number of byte copied if somes bytes are copied
+ return 0 if the end of file was reached
+ return <0 with error code if there is an error (UNZ_ERRNO for IO error, or zLib error for uncompress error) */
+
+extern int ZEXPORT unzGetCurrentFileInfo OF((unzFile file, unz_file_info *pfile_info, char *filename,
+ uLong filename_size, void *extrafield, uLong extrafield_size, char *comment, uLong comment_size));
+extern int ZEXPORT unzGetCurrentFileInfo64 OF((unzFile file, unz_file_info64 *pfile_info, char *filename,
+ uLong filename_size, void *extrafield, uLong extrafield_size, char *comment, uLong comment_size));
+/* Get Info about the current file
+
+ pfile_info if != NULL, the *pfile_info structure will contain somes info about the current file
+ filename if != NULL, the file name string will be copied in filename
+ filename_size is the size of the filename buffer
+ extrafield if != NULL, the extra field information from the central header will be copied in to
+ extrafield_size is the size of the extraField buffer
+ comment if != NULL, the comment string of the file will be copied in to
+ comment_size is the size of the comment buffer */
+
+extern ZPOS64_T ZEXPORT unzGetCurrentFileZStreamPos64 OF((unzFile file));
+
+extern int ZEXPORT unzGetLocalExtrafield OF((unzFile file, voidp buf, unsigned len));
+/* Read extra field from the current file (opened by unzOpenCurrentFile)
+ This is the local-header version of the extra field (sometimes, there is
+ more info in the local-header version than in the central-header)
+
+ if buf == NULL, it return the size of the local extra field
+ if buf != NULL, len is the size of the buffer, the extra header is copied in buf.
+
+ return number of bytes copied in buf, or (if <0) the error code */
+
+extern int ZEXPORT unzCloseCurrentFile OF((unzFile file));
+/* Close the file in zip opened with unzOpenCurrentFile
+
+ return UNZ_CRCERROR if all the file was read but the CRC is not good */
+
+/***************************************************************************/
+/* Browse the directory of the zipfile */
+
+typedef int (*unzFileNameComparer)(unzFile file, const char *filename1, const char *filename2);
+typedef int (*unzIteratorFunction)(unzFile file);
+typedef int (*unzIteratorFunction2)(unzFile file, unz_file_info64 *pfile_info, char *filename,
+ uLong filename_size, void *extrafield, uLong extrafield_size, char *comment, uLong comment_size);
+
+extern int ZEXPORT unzGoToFirstFile OF((unzFile file));
+/* Set the current file of the zipfile to the first file.
+
+ return UNZ_OK if no error */
+
+extern int ZEXPORT unzGoToFirstFile2 OF((unzFile file, unz_file_info64 *pfile_info, char *filename,
+ uLong filename_size, void *extrafield, uLong extrafield_size, char *comment, uLong comment_size));
+/* Set the current file of the zipfile to the first file and retrieves the current info on success.
+ Not as seek intensive as unzGoToFirstFile + unzGetCurrentFileInfo.
+
+ return UNZ_OK if no error */
+
+extern int ZEXPORT unzGoToNextFile OF((unzFile file));
+/* Set the current file of the zipfile to the next file.
+
+ return UNZ_OK if no error
+ return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest */
+
+extern int ZEXPORT unzGoToNextFile2 OF((unzFile file, unz_file_info64 *pfile_info, char *filename,
+ uLong filename_size, void *extrafield, uLong extrafield_size, char *comment, uLong comment_size));
+/* Set the current file of the zipfile to the next file and retrieves the current
+ info on success. Does less seeking around than unzGotoNextFile + unzGetCurrentFileInfo.
+
+ return UNZ_OK if no error
+ return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest */
+
+extern int ZEXPORT unzLocateFile OF((unzFile file, const char *filename, unzFileNameComparer filename_compare_func));
+/* Try locate the file szFileName in the zipfile. For custom filename comparison pass in comparison function.
+
+ return UNZ_OK if the file is found (it becomes the current file)
+ return UNZ_END_OF_LIST_OF_FILE if the file is not found */
+
+/***************************************************************************/
+/* Raw access to zip file */
+
+typedef struct unz_file_pos_s
+{
+ uLong pos_in_zip_directory; /* offset in zip file directory */
+ uLong num_of_file; /* # of file */
+} unz_file_pos;
+
+extern int ZEXPORT unzGetFilePos OF((unzFile file, unz_file_pos* file_pos));
+extern int ZEXPORT unzGoToFilePos OF((unzFile file, unz_file_pos* file_pos));
+
+typedef struct unz64_file_pos_s
+{
+ ZPOS64_T pos_in_zip_directory; /* offset in zip file directory */
+ ZPOS64_T num_of_file; /* # of file */
+} unz64_file_pos;
+
+extern int ZEXPORT unzGetFilePos64 OF((unzFile file, unz64_file_pos* file_pos));
+extern int ZEXPORT unzGoToFilePos64 OF((unzFile file, const unz64_file_pos* file_pos));
+
+extern uLong ZEXPORT unzGetOffset OF((unzFile file));
+extern ZPOS64_T ZEXPORT unzGetOffset64 OF((unzFile file));
+/* Get the current file offset */
+
+extern int ZEXPORT unzSetOffset OF((unzFile file, uLong pos));
+extern int ZEXPORT unzSetOffset64 OF((unzFile file, ZPOS64_T pos));
+/* Set the current file offset */
+
+extern z_off_t ZEXPORT unztell OF((unzFile file));
+extern ZPOS64_T ZEXPORT unztell64 OF((unzFile file));
+/* return current position in uncompressed data */
+
+extern int ZEXPORT unzseek OF((unzFile file, z_off_t offset, int origin));
+extern int ZEXPORT unzseek64 OF((unzFile file, ZPOS64_T offset, int origin));
+/* Seek within the uncompressed data if compression method is storage */
+
+extern int ZEXPORT unzeof OF((unzFile file));
+/* return 1 if the end of file was reached, 0 elsewhere */
+
+/***************************************************************************/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _UNZ_H */
diff --git a/src/qcommon/vm.cpp b/src/qcommon/vm.cpp
new file mode 100644
index 0000000..ab36a33
--- /dev/null
+++ b/src/qcommon/vm.cpp
@@ -0,0 +1,1020 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// 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.h"
+#include "vm_local.h"
+
+#include "sys/sys_shared.h"
+
+#include "cmd.h"
+#include "cvar.h"
+#include "files.h"
+
+vm_t *currentVM = NULL;
+vm_t *lastVM = NULL;
+int vm_debugLevel;
+
+// used by Com_Error to get rid of running vm's before longjmp
+static int forced_unload;
+
+#define MAX_VM 3
+vm_t vmTable[MAX_VM];
+
+
+void VM_VmInfo_f( void );
+void VM_VmProfile_f( void );
+
+
+
+#if 0 // 64bit!
+// converts a VM pointer to a C pointer and
+// checks to make sure that the range is acceptable
+void *VM_VM2C( vmptr_t p, int length ) {
+ return (void *)p;
+}
+#endif
+
+void VM_Debug( int level ) {
+ vm_debugLevel = level;
+}
+
+/*
+==============
+VM_Init
+==============
+*/
+void VM_Init( void ) {
+ Cvar_Get( "vm_cgame", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2
+ Cvar_Get( "vm_game", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2
+ Cvar_Get( "vm_ui", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2
+
+ Cmd_AddCommand ("vmprofile", VM_VmProfile_f );
+ Cmd_AddCommand ("vminfo", VM_VmInfo_f );
+
+ ::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 ) {
+ union {
+ char *c;
+ void *v;
+ } mapfile;
+ char *text_p, *token;
+ char name[MAX_QPATH];
+ char symbols[MAX_QPATH];
+ vmSymbol_t **prev, *sym;
+ int count;
+ int value;
+ int chars;
+ int segment;
+ int numInstructions;
+
+ // don't load symbols if not developer
+ if ( !com_developer->integer ) {
+ return;
+ }
+
+ COM_StripExtension(vm->name, name, sizeof(name));
+ Com_sprintf( symbols, sizeof( symbols ), "vm/%s.map", name );
+ FS_ReadFile( symbols, &mapfile.v );
+ if ( !mapfile.c ) {
+ Com_Printf( "Couldn't load symbol file: %s\n", symbols );
+ return;
+ }
+
+ numInstructions = vm->instructionCount;
+
+ // parse the symbols
+ text_p = mapfile.c;
+ prev = &vm->symbols;
+ count = 0;
+
+ while ( 1 ) {
+ token = COM_Parse( &text_p );
+ if ( !token[0] ) {
+ break;
+ }
+ segment = ParseHex( token );
+ if ( segment ) {
+ COM_Parse( &text_p );
+ COM_Parse( &text_p );
+ continue; // only load code segment values
+ }
+
+ token = COM_Parse( &text_p );
+ if ( !token[0] ) {
+ Com_Printf( "WARNING: incomplete line at end of file\n" );
+ break;
+ }
+ value = ParseHex( token );
+
+ token = COM_Parse( &text_p );
+ if ( !token[0] ) {
+ Com_Printf( "WARNING: incomplete line at end of file\n" );
+ break;
+ }
+ chars = strlen( token );
+ sym = (vmSymbol_t*)Hunk_Alloc( sizeof( *sym ) + chars, h_high );
+ *prev = sym;
+ prev = &sym->next;
+ sym->next = NULL;
+
+ // convert value from an instruction number to a code offset
+ if ( value >= 0 && value < numInstructions ) {
+ value = vm->instructionPointers[value];
+ }
+
+ sym->symValue = value;
+ Q_strncpyz( sym->symName, token, chars + 1 );
+
+ count++;
+ }
+
+ vm->numSymbols = count;
+ Com_Printf( "%i symbols parsed from %s\n", count, symbols );
+ FS_FreeFile( mapfile.v );
+}
+
+/*
+============
+VM_DllSyscall
+
+Dlls will call this directly
+
+ rcg010206 The horror; the horror.
+
+ The syscall mechanism relies on stack manipulation to get its args.
+ This is likely due to C's inability to pass "..." parameters to
+ a function in one clean chunk. On PowerPC Linux, these parameters
+ are not necessarily passed on the stack, so while (&arg[0] == arg)
+ is true, (&arg[1] == 2nd function parameter) is not necessarily
+ accurate, as arg's value might have been stored to the stack or
+ other piece of scratch memory to give it a valid address, but the
+ next parameter might still be sitting in a register.
+
+ Quake's syscall system also assumes that the stack grows downward,
+ and that any needed types can be squeezed, safely, into a signed int.
+
+ This hack below copies all needed values for an argument to a
+ array in memory, so that Quake can get the correct values. This can
+ also be used on systems where the stack grows upwards, as the
+ presumably standard and safe stdargs.h macros are used.
+
+ As for having enough space in a signed int for your datatypes, well,
+ it might be better to wait for DOOM 3 before you start porting. :)
+
+ The original code, while probably still inherently dangerous, seems
+ to work well enough for the platforms it already works on. Rather
+ than add the performance hit for those platforms, the original code
+ is still in use there.
+
+ For speed, we just grab 15 arguments, and don't worry about exactly
+ how many the syscall actually needs; the extra is thrown away.
+
+============
+*/
+__attribute__((no_sanitize_address))
+intptr_t QDECL VM_DllSyscall( intptr_t arg, ... ) {
+#if !id386 || defined __clang__
+ // rcg010206 - see commentary above
+ intptr_t args[MAX_VMSYSCALL_ARGS];
+ int i;
+ va_list ap;
+
+ args[0] = arg;
+
+ va_start(ap, arg);
+ for (i = 1; i < ARRAY_LEN(args); i++)
+ args[i] = va_arg(ap, intptr_t);
+ va_end(ap);
+
+ return currentVM->systemCall( args );
+#else // original id code
+ return currentVM->systemCall( &arg );
+#endif
+}
+
+
+/*
+=================
+VM_LoadQVM
+
+Load a .qvm file
+=================
+*/
+vmHeader_t *VM_LoadQVM( vm_t *vm, bool alloc, bool unpure)
+{
+ int dataLength;
+ int i;
+ char filename[MAX_QPATH];
+ union {
+ vmHeader_t *h;
+ void *v;
+ } header;
+
+ // load the image
+ Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", vm->name );
+ Com_Printf( "Loading vm file %s...\n", filename );
+
+ FS_ReadFileDir(filename, vm->searchPath, unpure, &header.v);
+
+ if ( !header.h ) {
+ Com_Printf( "Failed.\n" );
+ VM_Free( vm );
+
+ Com_Printf(S_COLOR_YELLOW "Warning: Couldn't open VM file %s\n", filename);
+
+ return NULL;
+ }
+
+ // show where the qvm was loaded from
+ FS_Which(filename, vm->searchPath);
+
+ if( LittleLong( header.h->vmMagic ) == VM_MAGIC_VER2 ) {
+ Com_Printf( "...which has vmMagic VM_MAGIC_VER2\n" );
+
+ // byte swap the header
+ for ( i = 0 ; i < sizeof( vmHeader_t ) / 4 ; i++ ) {
+ ((int *)header.h)[i] = LittleLong( ((int *)header.h)[i] );
+ }
+
+ // validate
+ if ( header.h->jtrgLength < 0
+ || header.h->bssLength < 0
+ || header.h->dataLength < 0
+ || header.h->litLength < 0
+ || header.h->codeLength <= 0 )
+ {
+ VM_Free(vm);
+ FS_FreeFile(header.v);
+
+ Com_Printf(S_COLOR_YELLOW "Warning: %s has bad header\n", filename);
+ return NULL;
+ }
+ } else if( LittleLong( header.h->vmMagic ) == VM_MAGIC ) {
+ // byte swap the header
+ // sizeof( vmHeader_t ) - sizeof( int ) is the 1.32b vm header size
+ for ( i = 0 ; i < ( sizeof( vmHeader_t ) - sizeof( int ) ) / 4 ; i++ ) {
+ ((int *)header.h)[i] = LittleLong( ((int *)header.h)[i] );
+ }
+
+ // validate
+ if ( header.h->bssLength < 0
+ || header.h->dataLength < 0
+ || header.h->litLength < 0
+ || header.h->codeLength <= 0 )
+ {
+ VM_Free(vm);
+ FS_FreeFile(header.v);
+
+ Com_Printf(S_COLOR_YELLOW "Warning: %s has bad header\n", filename);
+ return NULL;
+ }
+ } else {
+ VM_Free( vm );
+ FS_FreeFile(header.v);
+
+ Com_Printf(S_COLOR_YELLOW "Warning: %s does not have a recognisable "
+ "magic number in its header\n", filename);
+ return NULL;
+ }
+
+ // round up to next power of 2 so all data operations can
+ // be mask protected
+ dataLength = header.h->dataLength + header.h->litLength +
+ header.h->bssLength;
+ for ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) {
+ }
+ dataLength = 1 << i;
+
+ if(alloc)
+ {
+ // allocate zero filled space for initialized and uninitialized data
+ vm->dataBase = (unsigned char*)Hunk_Alloc(dataLength, h_high);
+ vm->dataMask = dataLength - 1;
+ }
+ else
+ {
+ // clear the data, but make sure we're not clearing more than allocated
+ if(vm->dataMask + 1 != dataLength)
+ {
+ VM_Free(vm);
+ FS_FreeFile(header.v);
+
+ Com_Printf(S_COLOR_YELLOW "Warning: Data region size of %s not matching after "
+ "VM_Restart()\n", filename);
+ return NULL;
+ }
+
+ ::memset(vm->dataBase, 0, dataLength);
+ }
+
+ // copy the intialized data
+ ::memcpy( vm->dataBase, (byte *)header.h + header.h->dataOffset,
+ header.h->dataLength + header.h->litLength );
+
+ // byte swap the longs
+ for ( i = 0 ; i < header.h->dataLength ; i += 4 ) {
+ *(int *)(vm->dataBase + i) = LittleLong( *(int *)(vm->dataBase + i ) );
+ }
+
+ if(header.h->vmMagic == VM_MAGIC_VER2)
+ {
+ int previousNumJumpTableTargets = vm->numJumpTableTargets;
+
+ header.h->jtrgLength &= ~0x03;
+
+ vm->numJumpTableTargets = header.h->jtrgLength >> 2;
+ Com_Printf("Loading %d jump table targets\n", vm->numJumpTableTargets);
+
+ if(alloc)
+ {
+ vm->jumpTableTargets = (unsigned char*)Hunk_Alloc(header.h->jtrgLength, h_high);
+ }
+ else
+ {
+ if(vm->numJumpTableTargets != previousNumJumpTableTargets)
+ {
+ VM_Free(vm);
+ FS_FreeFile(header.v);
+
+ Com_Printf(S_COLOR_YELLOW "Warning: Jump table size of %s not matching after "
+ "VM_Restart()\n", filename);
+ return NULL;
+ }
+
+ ::memset(vm->jumpTableTargets, 0, header.h->jtrgLength);
+ }
+
+ ::memcpy(vm->jumpTableTargets, (byte *) header.h + header.h->dataOffset +
+ header.h->dataLength + header.h->litLength, header.h->jtrgLength);
+
+ // byte swap the longs
+ for ( i = 0 ; i < header.h->jtrgLength ; i += 4 ) {
+ *(int *)(vm->jumpTableTargets + i) = LittleLong( *(int *)(vm->jumpTableTargets + i ) );
+ }
+ }
+
+ return header.h;
+}
+
+/*
+=================
+VM_Restart
+
+Reload the data, but leave everything else in place
+This allows a server to do a map_restart without changing memory allocation
+
+We need to make sure that servers can access unpure QVMs (not contained in any pak)
+even if the client is pure, so take "unpure" as argument.
+=================
+*/
+vm_t *VM_Restart(vm_t *vm, bool unpure)
+{
+ 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, false, unpure)))
+ {
+ Com_Error(ERR_DROP, "VM_Restart failed");
+ return NULL;
+ }
+
+ // free the original file
+ FS_FreeFile(header);
+
+ return vm;
+}
+
+/*
+================
+VM_Create
+
+If image ends in .qvm it will be interpreted, otherwise
+it will attempt to load as a system dll
+================
+*/
+vm_t *VM_Create( const char *module, intptr_t (*systemCalls)(intptr_t *),
+ vmInterpret_t interpret ) {
+ vm_t *vm;
+ vmHeader_t *header;
+ int i, remaining, retval;
+ char filename[MAX_OSPATH];
+ void *startSearch = NULL;
+
+ 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));
+
+ do
+ {
+ retval = FS_FindVM(&startSearch, filename, sizeof(filename), module, (interpret == VMI_NATIVE));
+
+ if(retval == VMI_NATIVE)
+ {
+ Com_Printf("Try loading dll file %s\n", filename);
+
+ vm->dllHandle = Sys_LoadGameDll(filename, &vm->entryPoint, VM_DllSyscall);
+
+ if(vm->dllHandle)
+ {
+ vm->systemCall = systemCalls;
+ return vm;
+ }
+
+ Com_Printf("Failed loading dll, trying next\n");
+ }
+ else if(retval == VMI_COMPILED)
+ {
+ vm->searchPath = startSearch;
+ if((header = VM_LoadQVM(vm, true, false)))
+ break;
+
+ // VM_Free overwrites the name on failed load
+ Q_strncpyz(vm->name, module, sizeof(vm->name));
+ }
+ } while(retval >= 0);
+
+ if(retval < 0)
+ return NULL;
+
+ vm->systemCall = systemCalls;
+
+ // allocate space for the jump targets, which will be filled in by the compile/prep functions
+ vm->instructionCount = header->instructionCount;
+ vm->instructionPointers = (intptr_t*)Hunk_Alloc(vm->instructionCount * sizeof(*vm->instructionPointers), h_high);
+
+ // copy or compile the instructions
+ vm->codeLength = header->codeLength;
+
+ vm->compiled = false;
+
+#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_BYTECODE)
+ {
+ vm->compiled = true;
+ VM_Compile( vm, header );
+ }
+#endif
+ // VM_Compile may have reset vm->compiled if compilation failed
+ if (!vm->compiled)
+ {
+ VM_PrepareInterpreter( vm, header );
+ }
+
+ // free the original file
+ FS_FreeFile( header );
+
+ // load the map file
+ VM_LoadSymbols( vm );
+
+ // the stack is implicitly at the end of the image
+ vm->programStack = vm->dataMask + 1;
+ vm->stackBottom = vm->programStack - PROGRAM_STACK_SIZE;
+
+ Com_Printf("%s loaded in %d bytes on the hunk\n", module, remaining - Hunk_MemoryRemaining());
+
+ return vm;
+}
+
+/*
+==============
+VM_Free
+==============
+*/
+void VM_Free( vm_t *vm ) {
+
+ if(!vm) {
+ return;
+ }
+
+ if(vm->callLevel) {
+ if(!forced_unload) {
+ Com_Error( ERR_FATAL, "VM_Free(%s) on running vm", vm->name );
+ return;
+ } else {
+ Com_Printf( "forcefully unloading %s vm\n", vm->name );
+ }
+ }
+
+ if(vm->destroy)
+ vm->destroy(vm);
+
+ if ( vm->dllHandle ) {
+ Sys_UnloadDll( vm->dllHandle );
+ ::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
+ ::memset( vm, 0, sizeof( *vm ) );
+
+ currentVM = NULL;
+ lastVM = NULL;
+}
+
+void VM_Clear(void) {
+ int i;
+ for (i=0;i<MAX_VM; i++) {
+ VM_Free(&vmTable[i]);
+ }
+}
+
+void VM_Forced_Unload_Start(void) {
+ forced_unload = 1;
+}
+
+void VM_Forced_Unload_Done(void) {
+ forced_unload = 0;
+}
+
+void VM_ClearCallLevel(vm_t *vm) {
+ vm->callLevel = 0;
+}
+
+void *VM_ArgPtr( intptr_t intValue ) {
+ if ( !intValue ) {
+ return NULL;
+ }
+ // currentVM is missing on reconnect
+ if ( currentVM==NULL )
+ return NULL;
+
+ if ( currentVM->entryPoint ) {
+ return (void *)(currentVM->dataBase + intValue);
+ }
+ else {
+ return (void *)(currentVM->dataBase + (intValue & currentVM->dataMask));
+ }
+}
+
+void *VM_ExplicitArgPtr( vm_t *vm, intptr_t intValue ) {
+ if ( !intValue ) {
+ return NULL;
+ }
+
+ // currentVM is missing on reconnect here as well?
+ if ( currentVM==NULL )
+ return NULL;
+
+ //
+ if ( vm->entryPoint ) {
+ return (void *)(vm->dataBase + intValue);
+ }
+ else {
+ return (void *)(vm->dataBase + (intValue & vm->dataMask));
+ }
+}
+
+
+/*
+==============
+VM_Call
+
+
+Upon a system call, the stack will look like:
+
+sp+32 parm1
+sp+28 parm0
+sp+24 return value
+sp+20 return address
+sp+16 local1
+sp+14 local0
+sp+12 arg1
+sp+8 arg0
+sp+4 return stack
+sp return address
+
+An interpreted function will immediately execute
+an OP_ENTER instruction, which will subtract space for
+locals from sp
+==============
+*/
+__attribute__((no_sanitize_address))
+intptr_t QDECL VM_Call( vm_t *vm, int callnum, ... )
+{
+ vm_t *oldVM;
+ intptr_t r;
+
+ if(!vm || !vm->name[0])
+ Com_Error(ERR_FATAL, "VM_Call with NULL vm");
+
+ oldVM = currentVM;
+ currentVM = vm;
+ lastVM = vm;
+
+ if ( vm_debugLevel ) {
+ Com_Printf( "VM_Call( %d )\n", callnum );
+ }
+
+ ++vm->callLevel;
+ // if we have a dll loaded, call it directly
+ if ( vm->entryPoint ) {
+ //rcg010207 - see dissertation at top of VM_DllSyscall() in this file.
+ int args[MAX_VMMAIN_ARGS-1];
+ va_list ap;
+ va_start(ap, callnum);
+ for (unsigned i = 0; i < ARRAY_LEN(args); i++)
+ {
+ args[i] = va_arg(ap, int);
+ }
+ va_end(ap);
+
+ r = vm->entryPoint( callnum, args[0], args[1], args[2] );
+ } else {
+#if ( id386 || idsparc ) && !defined __clang__ // calling convention doesn't need conversion in some cases
+#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[MAX_VMMAIN_ARGS-1];
+ } a;
+ va_list ap;
+
+ a.callnum = callnum;
+ va_start(ap, callnum);
+ for (unsigned i = 0; i < ARRAY_LEN(a.args); i++)
+ {
+ a.args[i] = va_arg(ap, int);
+ }
+ va_end(ap);
+#ifndef NO_VM_COMPILED
+ if ( vm->compiled )
+ r = VM_CallCompiled( vm, &a.callnum );
+ else
+#endif
+ r = VM_CallInterpreted( vm, &a.callnum );
+#endif
+ }
+ --vm->callLevel;
+
+ if ( oldVM != NULL )
+ currentVM = oldVM;
+ return r;
+}
+
+//=================================================================
+
+static int QDECL VM_ProfileSort( const void *a, const void *b ) {
+ vmSymbol_t *sa, *sb;
+
+ sa = *(vmSymbol_t **)a;
+ sb = *(vmSymbol_t **)b;
+
+ if ( sa->profileCount < sb->profileCount ) {
+ return -1;
+ }
+ if ( sa->profileCount > sb->profileCount ) {
+ return 1;
+ }
+ return 0;
+}
+
+/*
+==============
+VM_VmProfile_f
+
+==============
+*/
+void VM_VmProfile_f( void ) {
+ vm_t *vm;
+ vmSymbol_t **sorted, *sym;
+ int i;
+ double total;
+
+ if ( !lastVM ) {
+ return;
+ }
+
+ vm = lastVM;
+
+ if ( !vm->numSymbols ) {
+ return;
+ }
+
+ sorted = (vmSymbol_t**)Z_Malloc( vm->numSymbols * sizeof( *sorted ) );
+ sorted[0] = vm->symbols;
+ total = sorted[0]->profileCount;
+ for ( i = 1 ; i < vm->numSymbols ; i++ ) {
+ sorted[i] = sorted[i-1]->next;
+ total += sorted[i]->profileCount;
+ }
+
+ qsort( sorted, vm->numSymbols, sizeof( *sorted ), VM_ProfileSort );
+
+ for ( i = 0 ; i < vm->numSymbols ; i++ ) {
+ int perc;
+
+ sym = sorted[i];
+
+ perc = 100 * (float) sym->profileCount / total;
+ Com_Printf( "%2i%% %9i %s\n", perc, sym->profileCount, sym->symName );
+ sym->profileCount = 0;
+ }
+
+ Com_Printf(" %9.0f total\n", total );
+
+ Z_Free( sorted );
+}
+
+/*
+==============
+VM_VmInfo_f
+
+==============
+*/
+void VM_VmInfo_f( void ) {
+ vm_t *vm;
+ int i;
+
+ Com_Printf( "Registered virtual machines:\n" );
+ for ( i = 0 ; i < MAX_VM ; i++ ) {
+ vm = &vmTable[i];
+ if ( !vm->name[0] ) {
+ break;
+ }
+ Com_Printf( "%s : ", vm->name );
+ if ( vm->dllHandle ) {
+ Com_Printf( "native\n" );
+ continue;
+ }
+ if ( vm->compiled ) {
+ Com_Printf( "compiled on load\n" );
+ } else {
+ Com_Printf( "interpreted\n" );
+ }
+ Com_Printf( " code length : %7i\n", vm->codeLength );
+ Com_Printf( " table length: %7i\n", vm->instructionCount*4 );
+ Com_Printf( " data length : %7i\n", vm->dataMask + 1 );
+ }
+}
+
+/*
+===============
+VM_LogSyscalls
+
+Insert calls to this while debugging the vm compiler
+===============
+*/
+void VM_LogSyscalls( int *args ) {
+ static int callnum;
+ static FILE *f;
+
+ if ( !f ) {
+ f = fopen("syscalls.log", "w" );
+ }
+ callnum++;
+ fprintf(f, "%i: %p (%i) = %i %i %i %i\n", callnum, (void*)(args - (int *)currentVM->dataBase),
+ args[0], args[1], args[2], args[3], args[4] );
+}
+
+/*
+=================
+VM_BlockCopy
+Executes a block copy operation within currentVM data space
+=================
+*/
+
+void VM_BlockCopy(unsigned int dest, unsigned int src, size_t n)
+{
+ unsigned int dataMask = currentVM->dataMask;
+
+ if ((dest & dataMask) != dest
+ || (src & dataMask) != src
+ || ((dest + n) & dataMask) != dest + n
+ || ((src + n) & dataMask) != src + n)
+ {
+ Com_Error(ERR_DROP, "OP_BLOCK_COPY out of range!");
+ }
+
+ ::memcpy(currentVM->dataBase + dest, currentVM->dataBase + src, n);
+}
diff --git a/src/qcommon/vm.h b/src/qcommon/vm.h
new file mode 100644
index 0000000..26705af
--- /dev/null
+++ b/src/qcommon/vm.h
@@ -0,0 +1,67 @@
+#ifndef QCOMMON_VM_H
+#define QCOMMON_VM_H 1
+
+#include "q_shared.h"
+
+/*
+==============================================================
+
+VIRTUAL MACHINE
+
+==============================================================
+*/
+
+typedef struct vm_s vm_t;
+
+typedef enum {
+ VMI_NATIVE,
+ VMI_BYTECODE,
+ VMI_COMPILED
+} vmInterpret_t;
+
+typedef enum {
+ TRAP_MEMSET = 100,
+ TRAP_MEMCPY,
+ TRAP_STRNCPY,
+ TRAP_SIN,
+ TRAP_COS,
+ TRAP_ATAN2,
+ TRAP_SQRT,
+ TRAP_MATRIXMULTIPLY,
+ TRAP_ANGLEVECTORS,
+ TRAP_PERPENDICULARVECTOR,
+ TRAP_FLOOR,
+ TRAP_CEIL,
+
+ TRAP_TESTPRINTINT,
+ TRAP_TESTPRINTFLOAT
+} sharedTraps_t;
+
+void VM_Init( void );
+vm_t *VM_Create( const char *module, intptr_t (*systemCalls)(intptr_t *), vmInterpret_t interpret );
+// module should be bare: "cgame", not "cgame.dll" or "vm/cgame.qvm"
+
+void VM_Free( vm_t *vm );
+void VM_Clear(void);
+void VM_Forced_Unload_Start(void);
+void VM_Forced_Unload_Done(void);
+void VM_ClearCallLevel(vm_t *vm);
+vm_t *VM_Restart(vm_t *vm, bool unpure);
+
+intptr_t QDECL VM_Call( vm_t *vm, int callNum, ... );
+
+void VM_Debug( int level );
+
+void *VM_ArgPtr( intptr_t intValue );
+void *VM_ExplicitArgPtr( vm_t *vm, intptr_t intValue );
+
+#define VMA(x) VM_ArgPtr(args[x])
+static ID_INLINE float _vmf(intptr_t x)
+{
+ floatint_t fi;
+ fi.i = (int) x;
+ return fi.f;
+}
+#define VMF(x) _vmf(args[x])
+
+#endif
diff --git a/src/qcommon/vm_interpreted.cpp b/src/qcommon/vm_interpreted.cpp
new file mode 100644
index 0000000..08cdfcd
--- /dev/null
+++ b/src/qcommon/vm_interpreted.cpp
@@ -0,0 +1,904 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+#include "vm.h"
+#include "vm_local.h"
+
+//#define DEBUG_VM
+#ifdef DEBUG_VM
+static char *opnames[256] = {
+ "OP_UNDEF",
+
+ "OP_IGNORE",
+
+ "OP_BREAK",
+
+ "OP_ENTER",
+ "OP_LEAVE",
+ "OP_CALL",
+ "OP_PUSH",
+ "OP_POP",
+
+ "OP_CONST",
+
+ "OP_LOCAL",
+
+ "OP_JUMP",
+
+ //-------------------
+
+ "OP_EQ",
+ "OP_NE",
+
+ "OP_LTI",
+ "OP_LEI",
+ "OP_GTI",
+ "OP_GEI",
+
+ "OP_LTU",
+ "OP_LEU",
+ "OP_GTU",
+ "OP_GEU",
+
+ "OP_EQF",
+ "OP_NEF",
+
+ "OP_LTF",
+ "OP_LEF",
+ "OP_GTF",
+ "OP_GEF",
+
+ //-------------------
+
+ "OP_LOAD1",
+ "OP_LOAD2",
+ "OP_LOAD4",
+ "OP_STORE1",
+ "OP_STORE2",
+ "OP_STORE4",
+ "OP_ARG",
+
+ "OP_BLOCK_COPY",
+
+ //-------------------
+
+ "OP_SEX8",
+ "OP_SEX16",
+
+ "OP_NEGI",
+ "OP_ADD",
+ "OP_SUB",
+ "OP_DIVI",
+ "OP_DIVU",
+ "OP_MODI",
+ "OP_MODU",
+ "OP_MULI",
+ "OP_MULU",
+
+ "OP_BAND",
+ "OP_BOR",
+ "OP_BXOR",
+ "OP_BCOM",
+
+ "OP_LSH",
+ "OP_RSHI",
+ "OP_RSHU",
+
+ "OP_NEGF",
+ "OP_ADDF",
+ "OP_SUBF",
+ "OP_DIVF",
+ "OP_MULF",
+
+ "OP_CVIF",
+ "OP_CVFI"
+};
+#endif
+
+#if idppc
+
+//FIXME: these, um... look the same to me
+#if defined(__GNUC__)
+static ID_INLINE unsigned int loadWord(void *addr) {
+ unsigned int word;
+
+ asm("lwbrx %0,0,%1" : "=r" (word) : "r" (addr));
+ return word;
+}
+#else
+static ID_INLINE unsigned int __lwbrx(register void *addr,
+ register int offset) {
+ register unsigned int word;
+
+ asm("lwbrx %0,%2,%1" : "=r" (word) : "r" (addr), "b" (offset));
+ return word;
+}
+#define loadWord(addr) __lwbrx(addr,0)
+#endif
+
+#else
+ static ID_INLINE int loadWord(void *addr) {
+ int word;
+ memcpy(&word, addr, 4);
+ return LittleLong(word);
+ }
+#endif
+
+const char *VM_Indent( vm_t *vm ) {
+ const char *string = " ";
+ if ( vm->callLevel > 20 ) {
+ return string;
+ }
+ return string + 2 * ( 20 - vm->callLevel );
+}
+
+void VM_StackTrace( vm_t *vm, int programCounter, int programStack ) {
+ int count;
+
+ count = 0;
+ do {
+ Com_Printf( "%s\n", VM_ValueToSymbol( vm, programCounter ) );
+ programStack = *(int *)&vm->dataBase[programStack+4];
+ programCounter = *(int *)&vm->dataBase[programStack];
+ } while ( programCounter != -1 && ++count < 32 );
+
+}
+
+
+/*
+====================
+VM_PrepareInterpreter
+====================
+*/
+void VM_PrepareInterpreter( vm_t *vm, vmHeader_t *header ) {
+ int op;
+ int byte_pc;
+ int int_pc;
+ byte *code;
+ int instruction;
+ int *codeBase;
+
+ vm->codeBase = (unsigned char*)Hunk_Alloc( vm->codeLength*4, h_high ); // we're now int aligned
+// memcpy( vm->codeBase, (byte *)header + header->codeOffset, vm->codeLength );
+
+ // we don't need to translate the instructions, but we still need
+ // to find each instructions starting point for jumps
+ int_pc = byte_pc = 0;
+ instruction = 0;
+ code = (byte *)header + header->codeOffset;
+ codeBase = (int *)vm->codeBase;
+
+ // Copy and expand instructions to words while building instruction table
+ while ( instruction < header->instructionCount ) {
+ vm->instructionPointers[ instruction ] = int_pc;
+ instruction++;
+
+ op = (int)code[ byte_pc ];
+ codeBase[int_pc] = op;
+ if(byte_pc > header->codeLength)
+ Com_Error(ERR_DROP, "VM_PrepareInterpreter: pc > header->codeLength");
+
+ byte_pc++;
+ int_pc++;
+
+ // these are the only opcodes that aren't a single byte
+ switch ( op ) {
+ case OP_ENTER:
+ case OP_CONST:
+ case OP_LOCAL:
+ case OP_LEAVE:
+ case OP_EQ:
+ case OP_NE:
+ case OP_LTI:
+ case OP_LEI:
+ case OP_GTI:
+ case OP_GEI:
+ case OP_LTU:
+ case OP_LEU:
+ case OP_GTU:
+ case OP_GEU:
+ case OP_EQF:
+ case OP_NEF:
+ case OP_LTF:
+ case OP_LEF:
+ case OP_GTF:
+ case OP_GEF:
+ case OP_BLOCK_COPY:
+ codeBase[int_pc] = loadWord(&code[byte_pc]);
+ byte_pc += 4;
+ int_pc++;
+ break;
+ case OP_ARG:
+ codeBase[int_pc] = (int)code[byte_pc];
+ byte_pc++;
+ int_pc++;
+ break;
+ default:
+ break;
+ }
+
+ }
+ int_pc = 0;
+ instruction = 0;
+
+ // Now that the code has been expanded to int-sized opcodes, we'll translate instruction index
+ //into an index into codeBase[], which contains opcodes and operands.
+ while ( instruction < header->instructionCount ) {
+ op = codeBase[ int_pc ];
+ instruction++;
+ int_pc++;
+
+ switch ( op ) {
+ // These ops need to translate addresses in jumps from instruction index to int index
+ case OP_EQ:
+ case OP_NE:
+ case OP_LTI:
+ case OP_LEI:
+ case OP_GTI:
+ case OP_GEI:
+ case OP_LTU:
+ case OP_LEU:
+ case OP_GTU:
+ case OP_GEU:
+ case OP_EQF:
+ case OP_NEF:
+ case OP_LTF:
+ case OP_LEF:
+ case OP_GTF:
+ case OP_GEF:
+ if(codeBase[int_pc] < 0 || codeBase[int_pc] > vm->instructionCount)
+ Com_Error(ERR_DROP, "VM_PrepareInterpreter: Jump to invalid instruction number");
+
+ // codeBase[pc] is the instruction index. Convert that into an offset into
+ //the int-aligned codeBase[] by the lookup table.
+ codeBase[int_pc] = vm->instructionPointers[codeBase[int_pc]];
+ int_pc++;
+ break;
+
+ // These opcodes have an operand that isn't an instruction index
+ case OP_ENTER:
+ case OP_CONST:
+ case OP_LOCAL:
+ case OP_LEAVE:
+ case OP_BLOCK_COPY:
+ case OP_ARG:
+ int_pc++;
+ break;
+
+ default:
+ break;
+ }
+
+ }
+}
+
+/*
+==============
+VM_Call
+
+
+Upon a system call, the stack will look like:
+
+sp+32 parm1
+sp+28 parm0
+sp+24 return stack
+sp+20 return address
+sp+16 local1
+sp+14 local0
+sp+12 arg1
+sp+8 arg0
+sp+4 return stack
+sp return address
+
+An interpreted function will immediately execute
+an OP_ENTER instruction, which will subtract space for
+locals from sp
+==============
+*/
+
+#define DEBUGSTR va("%s%i", VM_Indent(vm), opStackOfs)
+
+int VM_CallInterpreted( vm_t *vm, int *args ) {
+ byte stack[OPSTACK_SIZE + 15];
+ int *opStack;
+ uint8_t opStackOfs;
+ int programCounter;
+ int programStack;
+ int stackOnEntry;
+ byte *image;
+ int *codeImage;
+ int v1;
+ int dataMask;
+ int arg;
+#ifdef DEBUG_VM
+ vmSymbol_t *profileSymbol;
+#endif
+
+ // interpret the code
+ vm->currentlyInterpreting = true;
+
+ // 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;
+
+ programCounter = 0;
+
+ programStack -= ( 8 + 4 * MAX_VMMAIN_ARGS );
+
+ for ( arg = 0; arg < MAX_VMMAIN_ARGS; arg++ )
+ *(int *)&image[ programStack + 8 + arg * 4 ] = args[ arg ];
+
+ *(int *)&image[ programStack + 4 ] = 0; // return stack
+ *(int *)&image[ programStack ] = -1; // will terminate the loop on return
+
+ VM_Debug(0);
+
+ // leave a free spot at start of stack so
+ // that as long as opStack is valid, opStack-1 will
+ // not corrupt anything
+ opStack = (int*)PADP(stack, 16);
+ *opStack = 0xDEADBEEF;
+ opStackOfs = 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 = opStack[opStackOfs];
+ r1 = opStack[(uint8_t) (opStackOfs - 1)];
+nextInstruction2:
+#ifdef DEBUG_VM
+ if ( (unsigned)programCounter >= vm->codeLength ) {
+ Com_Error( ERR_DROP, "VM pc out of range" );
+ return 0;
+ }
+
+ if ( programStack <= vm->stackBottom ) {
+ Com_Error( ERR_DROP, "VM stack overflow" );
+ return 0;
+ }
+
+ if ( programStack & 3 ) {
+ Com_Error( ERR_DROP, "VM program stack misaligned" );
+ return 0;
+ }
+
+ 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!
+ return 0;
+#endif
+ case OP_BREAK:
+ vm->breakCount++;
+ goto nextInstruction2;
+ case OP_CONST:
+ opStackOfs++;
+ r1 = r0;
+ r0 = opStack[opStackOfs] = r2;
+
+ programCounter += 1;
+ goto nextInstruction2;
+ case OP_LOCAL:
+ opStackOfs++;
+ r1 = r0;
+ r0 = opStack[opStackOfs] = r2+programStack;
+
+ programCounter += 1;
+ goto nextInstruction2;
+
+ case OP_LOAD4:
+#ifdef DEBUG_VM
+ if(opStack[opStackOfs] & 3)
+ {
+ Com_Error( ERR_DROP, "OP_LOAD4 misaligned" );
+ return 0;
+ }
+#endif
+ r0 = opStack[opStackOfs] = *(int *) &image[r0 & dataMask & ~3 ];
+ goto nextInstruction2;
+ case OP_LOAD2:
+ r0 = opStack[opStackOfs] = *(unsigned short *)&image[ r0&dataMask&~1 ];
+ goto nextInstruction2;
+ case OP_LOAD1:
+ r0 = opStack[opStackOfs] = image[ r0&dataMask ];
+ goto nextInstruction2;
+
+ case OP_STORE4:
+ *(int *)&image[ r1&(dataMask & ~3) ] = r0;
+ opStackOfs -= 2;
+ goto nextInstruction;
+ case OP_STORE2:
+ *(short *)&image[ r1&(dataMask & ~1) ] = r0;
+ opStackOfs -= 2;
+ goto nextInstruction;
+ case OP_STORE1:
+ image[ r1&dataMask ] = r0;
+ opStackOfs -= 2;
+ goto nextInstruction;
+
+ case OP_ARG:
+ // single byte offset from programStack
+ *(int *)&image[ (codeImage[programCounter] + programStack)&dataMask&~3 ] = r0;
+ opStackOfs--;
+ programCounter += 1;
+ goto nextInstruction;
+
+ case OP_BLOCK_COPY:
+ VM_BlockCopy(r1, r0, r2);
+ programCounter += 1;
+ opStackOfs -= 2;
+ goto nextInstruction;
+
+ case OP_CALL:
+ // save current program counter
+ *(int *)&image[ programStack ] = programCounter;
+
+ // jump to the location on the stack
+ programCounter = r0;
+ opStackOfs--;
+ if ( programCounter < 0 ) {
+ // system call
+ int r;
+// int temp;
+#ifdef DEBUG_VM
+ int stomped;
+
+ if ( vm_debugLevel ) {
+ Com_Printf( "%s---> systemcall(%i)\n", DEBUGSTR, -1 - programCounter );
+ }
+#endif
+ // save the stack to allow recursive VM entry
+// temp = vm->callLevel;
+ vm->programStack = programStack - 4;
+#ifdef DEBUG_VM
+ stomped = *(int *)&image[ programStack + 4 ];
+#endif
+ *(int *)&image[ programStack + 4 ] = -1 - programCounter;
+
+//VM_LogSyscalls( (int *)&image[ programStack + 4 ] );
+ {
+ // the vm has ints on the stack, we expect
+ // pointers so we might have to convert it
+ if (sizeof(intptr_t) != sizeof(int)) {
+ intptr_t argarr[ MAX_VMSYSCALL_ARGS ];
+ int *imagePtr = (int *)&image[ programStack ];
+ int i;
+ for (i = 0; i < ARRAY_LEN(argarr); ++i) {
+ argarr[i] = *(++imagePtr);
+ }
+ r = vm->systemCall( argarr );
+ } else {
+ intptr_t* argptr = (intptr_t *)&image[ programStack + 4 ];
+ r = vm->systemCall( argptr );
+ }
+ }
+
+#ifdef DEBUG_VM
+ // this is just our stack frame pointer, only needed
+ // for debugging
+ *(int *)&image[ programStack + 4 ] = stomped;
+#endif
+
+ // save return value
+ opStackOfs++;
+ opStack[opStackOfs] = r;
+ programCounter = *(int *)&image[ programStack ];
+// vm->callLevel = temp;
+#ifdef DEBUG_VM
+ if ( vm_debugLevel ) {
+ Com_Printf( "%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter ) );
+ }
+#endif
+ } else if ( (unsigned)programCounter >= vm->instructionCount ) {
+ Com_Error( ERR_DROP, "VM program counter out of range in OP_CALL" );
+ return 0;
+ } else {
+ programCounter = vm->instructionPointers[ programCounter ];
+ }
+ goto nextInstruction;
+
+ // push and pop are only needed for discarded or bad function return values
+ case OP_PUSH:
+ opStackOfs++;
+ goto nextInstruction;
+ case OP_POP:
+ opStackOfs--;
+ goto nextInstruction;
+
+ case OP_ENTER:
+#ifdef DEBUG_VM
+ profileSymbol = VM_ValueToFunctionSymbol( vm, programCounter );
+#endif
+ // get size of stack frame
+ v1 = r2;
+
+ programCounter += 1;
+ programStack -= v1;
+#ifdef DEBUG_VM
+ // save old stack frame for debugging traces
+ *(int *)&image[programStack+4] = programStack + v1;
+ if ( vm_debugLevel ) {
+ Com_Printf( "%s---> %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter - 5 ) );
+ if ( vm->breakFunction && programCounter - 5 == vm->breakFunction ) {
+ // this is to allow setting breakpoints here in the debugger
+ vm->breakCount++;
+// vm_debugLevel = 2;
+// VM_StackTrace( vm, programCounter, programStack );
+ }
+// vm->callLevel++;
+ }
+#endif
+ goto nextInstruction;
+ case OP_LEAVE:
+ // remove our stack frame
+ v1 = r2;
+
+ programStack += v1;
+
+ // grab the saved program counter
+ programCounter = *(int *)&image[ programStack ];
+#ifdef DEBUG_VM
+ profileSymbol = VM_ValueToFunctionSymbol( vm, programCounter );
+ if ( vm_debugLevel ) {
+// vm->callLevel--;
+ Com_Printf( "%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter ) );
+ }
+#endif
+ // check for leaving the VM
+ if ( programCounter == -1 ) {
+ goto done;
+ } else if ( (unsigned)programCounter >= vm->codeLength ) {
+ Com_Error( ERR_DROP, "VM program counter out of range in OP_LEAVE" );
+ return 0;
+ }
+ goto nextInstruction;
+
+ /*
+ ===================================================================
+ BRANCHES
+ ===================================================================
+ */
+
+ case OP_JUMP:
+ if ( (unsigned)r0 >= vm->instructionCount )
+ {
+ Com_Error( ERR_DROP, "VM program counter out of range in OP_JUMP" );
+ return 0;
+ }
+
+ programCounter = vm->instructionPointers[ r0 ];
+
+ opStackOfs--;
+ goto nextInstruction;
+
+ case OP_EQ:
+ opStackOfs -= 2;
+ if ( r1 == r0 ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 1;
+ goto nextInstruction;
+ }
+
+ case OP_NE:
+ opStackOfs -= 2;
+ if ( r1 != r0 ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 1;
+ goto nextInstruction;
+ }
+
+ case OP_LTI:
+ opStackOfs -= 2;
+ if ( r1 < r0 ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 1;
+ goto nextInstruction;
+ }
+
+ case OP_LEI:
+ opStackOfs -= 2;
+ if ( r1 <= r0 ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 1;
+ goto nextInstruction;
+ }
+
+ case OP_GTI:
+ opStackOfs -= 2;
+ if ( r1 > r0 ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 1;
+ goto nextInstruction;
+ }
+
+ case OP_GEI:
+ opStackOfs -= 2;
+ if ( r1 >= r0 ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 1;
+ goto nextInstruction;
+ }
+
+ case OP_LTU:
+ opStackOfs -= 2;
+ if ( ((unsigned)r1) < ((unsigned)r0) ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 1;
+ goto nextInstruction;
+ }
+
+ case OP_LEU:
+ opStackOfs -= 2;
+ if ( ((unsigned)r1) <= ((unsigned)r0) ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 1;
+ goto nextInstruction;
+ }
+
+ case OP_GTU:
+ opStackOfs -= 2;
+ if ( ((unsigned)r1) > ((unsigned)r0) ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 1;
+ goto nextInstruction;
+ }
+
+ case OP_GEU:
+ opStackOfs -= 2;
+ if ( ((unsigned)r1) >= ((unsigned)r0) ) {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 1;
+ goto nextInstruction;
+ }
+
+ case OP_EQF:
+ opStackOfs -= 2;
+
+ if(((float *) opStack)[(uint8_t) (opStackOfs + 1)] == ((float *) opStack)[(uint8_t) (opStackOfs + 2)])
+ {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 1;
+ goto nextInstruction;
+ }
+
+ case OP_NEF:
+ opStackOfs -= 2;
+
+ if(((float *) opStack)[(uint8_t) (opStackOfs + 1)] != ((float *) opStack)[(uint8_t) (opStackOfs + 2)])
+ {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 1;
+ goto nextInstruction;
+ }
+
+ case OP_LTF:
+ opStackOfs -= 2;
+
+ if(((float *) opStack)[(uint8_t) (opStackOfs + 1)] < ((float *) opStack)[(uint8_t) (opStackOfs + 2)])
+ {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 1;
+ goto nextInstruction;
+ }
+
+ case OP_LEF:
+ opStackOfs -= 2;
+
+ if(((float *) opStack)[(uint8_t) ((uint8_t) (opStackOfs + 1))] <= ((float *) opStack)[(uint8_t) ((uint8_t) (opStackOfs + 2))])
+ {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 1;
+ goto nextInstruction;
+ }
+
+ case OP_GTF:
+ opStackOfs -= 2;
+
+ if(((float *) opStack)[(uint8_t) (opStackOfs + 1)] > ((float *) opStack)[(uint8_t) (opStackOfs + 2)])
+ {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 1;
+ goto nextInstruction;
+ }
+
+ case OP_GEF:
+ opStackOfs -= 2;
+
+ if(((float *) opStack)[(uint8_t) (opStackOfs + 1)] >= ((float *) opStack)[(uint8_t) (opStackOfs + 2)])
+ {
+ programCounter = r2; //vm->instructionPointers[r2];
+ goto nextInstruction;
+ } else {
+ programCounter += 1;
+ goto nextInstruction;
+ }
+
+
+ //===================================================================
+
+ case OP_NEGI:
+ opStack[opStackOfs] = -r0;
+ goto nextInstruction;
+ case OP_ADD:
+ opStackOfs--;
+ opStack[opStackOfs] = r1 + r0;
+ goto nextInstruction;
+ case OP_SUB:
+ opStackOfs--;
+ opStack[opStackOfs] = r1 - r0;
+ goto nextInstruction;
+ case OP_DIVI:
+ opStackOfs--;
+ opStack[opStackOfs] = r1 / r0;
+ goto nextInstruction;
+ case OP_DIVU:
+ opStackOfs--;
+ opStack[opStackOfs] = ((unsigned) r1) / ((unsigned) r0);
+ goto nextInstruction;
+ case OP_MODI:
+ opStackOfs--;
+ opStack[opStackOfs] = r1 % r0;
+ goto nextInstruction;
+ case OP_MODU:
+ opStackOfs--;
+ opStack[opStackOfs] = ((unsigned) r1) % ((unsigned) r0);
+ goto nextInstruction;
+ case OP_MULI:
+ opStackOfs--;
+ opStack[opStackOfs] = r1 * r0;
+ goto nextInstruction;
+ case OP_MULU:
+ opStackOfs--;
+ opStack[opStackOfs] = ((unsigned) r1) * ((unsigned) r0);
+ goto nextInstruction;
+
+ case OP_BAND:
+ opStackOfs--;
+ opStack[opStackOfs] = ((unsigned) r1) & ((unsigned) r0);
+ goto nextInstruction;
+ case OP_BOR:
+ opStackOfs--;
+ opStack[opStackOfs] = ((unsigned) r1) | ((unsigned) r0);
+ goto nextInstruction;
+ case OP_BXOR:
+ opStackOfs--;
+ opStack[opStackOfs] = ((unsigned) r1) ^ ((unsigned) r0);
+ goto nextInstruction;
+ case OP_BCOM:
+ opStack[opStackOfs] = ~((unsigned) r0);
+ goto nextInstruction;
+
+ case OP_LSH:
+ opStackOfs--;
+ opStack[opStackOfs] = r1 << r0;
+ goto nextInstruction;
+ case OP_RSHI:
+ opStackOfs--;
+ opStack[opStackOfs] = r1 >> r0;
+ goto nextInstruction;
+ case OP_RSHU:
+ opStackOfs--;
+ opStack[opStackOfs] = ((unsigned) r1) >> r0;
+ goto nextInstruction;
+
+ case OP_NEGF:
+ ((float *) opStack)[opStackOfs] = -((float *) opStack)[opStackOfs];
+ goto nextInstruction;
+ case OP_ADDF:
+ opStackOfs--;
+ ((float *) opStack)[opStackOfs] = ((float *) opStack)[opStackOfs] + ((float *) opStack)[(uint8_t) (opStackOfs + 1)];
+ goto nextInstruction;
+ case OP_SUBF:
+ opStackOfs--;
+ ((float *) opStack)[opStackOfs] = ((float *) opStack)[opStackOfs] - ((float *) opStack)[(uint8_t) (opStackOfs + 1)];
+ goto nextInstruction;
+ case OP_DIVF:
+ opStackOfs--;
+ ((float *) opStack)[opStackOfs] = ((float *) opStack)[opStackOfs] / ((float *) opStack)[(uint8_t) (opStackOfs + 1)];
+ goto nextInstruction;
+ case OP_MULF:
+ opStackOfs--;
+ ((float *) opStack)[opStackOfs] = ((float *) opStack)[opStackOfs] * ((float *) opStack)[(uint8_t) (opStackOfs + 1)];
+ goto nextInstruction;
+
+ case OP_CVIF:
+ ((float *) opStack)[opStackOfs] = (float) opStack[opStackOfs];
+ goto nextInstruction;
+ case OP_CVFI:
+ opStack[opStackOfs] = static_cast<int>(((float *) opStack)[opStackOfs]);
+ goto nextInstruction;
+ case OP_SEX8:
+ opStack[opStackOfs] = (signed char) opStack[opStackOfs];
+ goto nextInstruction;
+ case OP_SEX16:
+ opStack[opStackOfs] = (short) opStack[opStackOfs];
+ goto nextInstruction;
+ }
+ }
+
+done:
+ vm->currentlyInterpreting = false;
+
+ if (opStackOfs != 1 || *opStack != 0xDEADBEEF)
+ Com_Error(ERR_DROP, "Interpreter error: opStack[0] = %X, opStackOfs = %d", opStack[0], opStackOfs);
+
+ vm->programStack = stackOnEntry;
+
+ // return the result
+ return opStack[opStackOfs];
+}
diff --git a/src/qcommon/vm_local.h b/src/qcommon/vm_local.h
new file mode 100644
index 0000000..b4ce844
--- /dev/null
+++ b/src/qcommon/vm_local.h
@@ -0,0 +1,204 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+#include "q_shared.h"
+#include "qcommon.h"
+
+// Max number of arguments to pass from engine to vm's vmMain function.
+// command number + 3 arguments
+#define MAX_VMMAIN_ARGS 4
+
+// Max number of arguments to pass from a vm to engine's syscall handler function for the vm.
+// syscall number + 9 arguments
+#define MAX_VMSYSCALL_ARGS 20
+
+// don't change, this is hardcoded into x86 VMs, opStack protection relies
+// on this
+#define OPSTACK_SIZE 1024
+#define OPSTACK_MASK (OPSTACK_SIZE-1)
+
+// don't change
+// Hardcoded in q3asm a reserved at end of bss
+#define PROGRAM_STACK_SIZE 0x10000
+#define PROGRAM_STACK_MASK (PROGRAM_STACK_SIZE-1)
+
+typedef enum {
+ OP_UNDEF,
+
+ OP_IGNORE,
+
+ OP_BREAK,
+
+ OP_ENTER,
+ OP_LEAVE,
+ OP_CALL,
+ OP_PUSH,
+ OP_POP,
+
+ OP_CONST,
+ OP_LOCAL,
+
+ OP_JUMP,
+
+ //-------------------
+
+ OP_EQ,
+ OP_NE,
+
+ OP_LTI,
+ OP_LEI,
+ OP_GTI,
+ OP_GEI,
+
+ OP_LTU,
+ OP_LEU,
+ OP_GTU,
+ OP_GEU,
+
+ OP_EQF,
+ OP_NEF,
+
+ OP_LTF,
+ OP_LEF,
+ OP_GTF,
+ OP_GEF,
+
+ //-------------------
+
+ OP_LOAD1,
+ OP_LOAD2,
+ OP_LOAD4,
+ OP_STORE1,
+ OP_STORE2,
+ OP_STORE4, // *(stack[top-1]) = stack[top]
+ OP_ARG,
+
+ OP_BLOCK_COPY,
+
+ //-------------------
+
+ OP_SEX8,
+ OP_SEX16,
+
+ OP_NEGI,
+ OP_ADD,
+ OP_SUB,
+ OP_DIVI,
+ OP_DIVU,
+ OP_MODI,
+ OP_MODU,
+ OP_MULI,
+ OP_MULU,
+
+ OP_BAND,
+ OP_BOR,
+ OP_BXOR,
+ OP_BCOM,
+
+ OP_LSH,
+ OP_RSHI,
+ OP_RSHU,
+
+ OP_NEGF,
+ OP_ADDF,
+ OP_SUBF,
+ OP_DIVF,
+ OP_MULF,
+
+ OP_CVIF,
+ OP_CVFI
+} opcode_t;
+
+
+
+typedef int vmptr_t;
+
+typedef struct vmSymbol_s {
+ struct vmSymbol_s *next;
+ int symValue;
+ int profileCount;
+ char symName[1]; // variable sized
+} vmSymbol_t;
+
+#define VM_OFFSET_PROGRAM_STACK 0
+#define VM_OFFSET_SYSTEM_CALL 4
+
+struct vm_s {
+ // DO NOT MOVE OR CHANGE THESE WITHOUT CHANGING THE VM_OFFSET_* DEFINES
+ // USED BY THE ASM CODE
+ int programStack; // the vm may be recursively entered
+ intptr_t (*systemCall)( intptr_t *parms );
+
+ //------------------------------------
+
+ char name[MAX_QPATH];
+ void *searchPath; // hint for FS_ReadFileDir()
+
+ // for dynamic linked modules
+ void *dllHandle;
+ intptr_t (QDECL *entryPoint)( int callNum, ... );
+ void (*destroy)(vm_t* self);
+
+ // for interpreted modules
+ bool currentlyInterpreting;
+
+ bool compiled;
+ byte *codeBase;
+ int entryOfs;
+ int codeLength;
+
+ intptr_t *instructionPointers;
+ int instructionCount;
+
+ byte *dataBase;
+ int dataMask;
+
+ int stackBottom; // if programStack < stackBottom, error
+
+ int numSymbols;
+ struct vmSymbol_s *symbols;
+
+ int callLevel; // counts recursive VM_Call
+ int breakFunction; // increment breakCount on function entry to this
+ int breakCount;
+
+ byte *jumpTableTargets;
+ int numJumpTableTargets;
+};
+
+
+extern vm_t *currentVM;
+extern int vm_debugLevel;
+
+void VM_Compile( vm_t *vm, vmHeader_t *header );
+int VM_CallCompiled( vm_t *vm, int *args );
+
+void VM_PrepareInterpreter( vm_t *vm, vmHeader_t *header );
+int VM_CallInterpreted( vm_t *vm, int *args );
+
+vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value );
+int VM_SymbolToValue( vm_t *vm, const char *symbol );
+const char *VM_ValueToSymbol( vm_t *vm, int value );
+void VM_LogSyscalls( int *args );
+
+void VM_BlockCopy(unsigned int dest, unsigned int src, size_t n);
diff --git a/src/qcommon/vm_x86.cpp b/src/qcommon/vm_x86.cpp
new file mode 100644
index 0000000..c3373aa
--- /dev/null
+++ b/src/qcommon/vm_x86.cpp
@@ -0,0 +1,1840 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+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 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// vm_x86.c -- load time compiler and execution environment for x86
+
+#include "vm.h"
+#include "vm_local.h"
+
+#ifdef _WIN32
+ #include <windows.h>
+#else
+ #ifdef __FreeBSD__
+ #include <sys/types.h>
+ #endif
+
+ #include <sys/mman.h> // for PROT_ stuff
+
+ /* need this on NX enabled systems (i386 with PAE kernel or
+ * noexec32=on x86_64) */
+ #define VM_X86_MMAP
+
+ // workaround for systems that use the old MAP_ANON macro
+ #ifndef MAP_ANONYMOUS
+ #define MAP_ANONYMOUS MAP_ANON
+ #endif
+#endif
+
+static void VM_Destroy_Compiled(vm_t* self);
+
+/*
+
+ eax scratch
+ ebx/bl opStack offset
+ ecx scratch (required for shifts)
+ edx scratch (required for divisions)
+ esi program stack
+ edi opStack base
+x86_64:
+ r8 vm->instructionPointers
+ r9 vm->dataBase
+
+*/
+
+#define VMFREE_BUFFERS() do {Z_Free(buf); Z_Free(jused);} while(0)
+static byte *buf = NULL;
+static byte *jused = NULL;
+static int jusedSize = 0;
+static int compiledOfs = 0;
+static byte *code = NULL;
+static int pc = 0;
+
+#define FTOL_PTR
+
+static int instruction, pass;
+static int lastConst = 0;
+static int oc0, oc1, pop0, pop1;
+static int jlabel;
+
+typedef enum
+{
+ LAST_COMMAND_NONE = 0,
+ LAST_COMMAND_MOV_STACK_EAX,
+ LAST_COMMAND_SUB_BL_1,
+ LAST_COMMAND_SUB_BL_2,
+} ELastCommand;
+
+typedef enum
+{
+ VM_JMP_VIOLATION = 0,
+ VM_BLOCK_COPY = 1
+} ESysCallType;
+
+static ELastCommand LastCommand;
+
+static int iss8(int32_t v)
+{
+ return (SCHAR_MIN <= v && v <= SCHAR_MAX);
+}
+
+#if 0
+static int isu8(uint32_t v)
+{
+ return (v <= UCHAR_MAX);
+}
+#endif
+
+static int NextConstant4(void)
+{
+ return (code[pc] | (code[pc+1]<<8) | (code[pc+2]<<16) | (code[pc+3]<<24));
+}
+
+static int Constant4( void ) {
+ int v;
+
+ v = NextConstant4();
+ 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;
+}
+
+static void Emit2(int v)
+{
+ Emit1(v & 255);
+ Emit1((v >> 8) & 255);
+}
+
+
+static void Emit4(int v)
+{
+ Emit1(v & 0xFF);
+ Emit1((v >> 8) & 0xFF);
+ Emit1((v >> 16) & 0xFF);
+ Emit1((v >> 24) & 0xFF);
+}
+
+static void EmitPtr(void *ptr)
+{
+ intptr_t v = (intptr_t) ptr;
+
+ Emit4(v);
+#if idx64
+ Emit1((v >> 32) & 0xFF);
+ Emit1((v >> 40) & 0xFF);
+ Emit1((v >> 48) & 0xFF);
+ Emit1((v >> 56) & 0xFF);
+#endif
+}
+
+static int Hex( int c ) {
+ if ( c >= 'a' && c <= 'f' ) {
+ return 10 + c - 'a';
+ }
+ if ( c >= 'A' && c <= 'F' ) {
+ return 10 + c - 'A';
+ }
+ if ( c >= '0' && c <= '9' ) {
+ return c - '0';
+ }
+
+ VMFREE_BUFFERS();
+ Com_Error( ERR_DROP, "Hex: bad char '%c'", c );
+
+ return 0;
+}
+static void EmitString( const char *string ) {
+ int c1, c2;
+ int v;
+
+ while ( 1 ) {
+ c1 = string[0];
+ c2 = string[1];
+
+ v = ( Hex( c1 ) << 4 ) | Hex( c2 );
+ Emit1( v );
+
+ if ( !string[2] ) {
+ break;
+ }
+ string += 3;
+ }
+}
+static void EmitRexString(byte rex, const char *string)
+{
+#if idx64
+ if(rex)
+ Emit1(rex);
+#endif
+
+ EmitString(string);
+}
+
+
+#define MASK_REG(modrm, mask) \
+ do { \
+ EmitString("81"); \
+ EmitString((modrm)); \
+ Emit4((mask)); \
+ } while(0)
+
+// add bl, bytes
+#define STACK_PUSH(bytes) \
+ do { \
+ EmitString("80 C3"); \
+ Emit1(bytes); \
+ } while(0)
+
+// sub bl, bytes
+#define STACK_POP(bytes) \
+ do { \
+ EmitString("80 EB"); \
+ Emit1(bytes); \
+ } while(0)
+
+static void EmitCommand(ELastCommand command)
+{
+ switch(command)
+ {
+ case LAST_COMMAND_MOV_STACK_EAX:
+ EmitString("89 04 9F"); // mov dword ptr [edi + ebx * 4], eax
+ break;
+
+ case LAST_COMMAND_SUB_BL_1:
+ STACK_POP(1); // sub bl, 1
+ break;
+
+ case LAST_COMMAND_SUB_BL_2:
+ STACK_POP(2); // sub bl, 2
+ break;
+ default:
+ break;
+ }
+ LastCommand = command;
+}
+
+static void EmitPushStack(vm_t *vm)
+{
+ if (!jlabel)
+ {
+ if(LastCommand == LAST_COMMAND_SUB_BL_1)
+ { // sub bl, 1
+ compiledOfs -= 3;
+ vm->instructionPointers[instruction - 1] = compiledOfs;
+ return;
+ }
+ if(LastCommand == LAST_COMMAND_SUB_BL_2)
+ { // sub bl, 2
+ compiledOfs -= 3;
+ vm->instructionPointers[instruction - 1] = compiledOfs;
+ STACK_POP(1); // sub bl, 1
+ return;
+ }
+ }
+
+ STACK_PUSH(1); // add bl, 1
+}
+
+static void EmitMovEAXStack(vm_t *vm, int andit)
+{
+ if(!jlabel)
+ {
+ if(LastCommand == LAST_COMMAND_MOV_STACK_EAX)
+ { // mov [edi + ebx * 4], eax
+ compiledOfs -= 3;
+ vm->instructionPointers[instruction - 1] = compiledOfs;
+ }
+ else if(pop1 == OP_CONST && buf[compiledOfs-7] == 0xC7 && buf[compiledOfs-6] == 0x04 && buf[compiledOfs - 5] == 0x9F)
+ { // mov [edi + ebx * 4], 0x12345678
+ compiledOfs -= 7;
+ vm->instructionPointers[instruction - 1] = compiledOfs;
+ EmitString("B8"); // mov eax, 0x12345678
+
+ if(andit)
+ Emit4(lastConst & andit);
+ else
+ Emit4(lastConst);
+
+ return;
+ }
+ else if(pop1 != OP_DIVI && pop1 != OP_DIVU && pop1 != OP_MULI && pop1 != OP_MULU &&
+ pop1 != OP_STORE4 && pop1 != OP_STORE2 && pop1 != OP_STORE1)
+ {
+ EmitString("8B 04 9F"); // mov eax, dword ptr [edi + ebx * 4]
+ }
+ }
+ else
+ EmitString("8B 04 9F"); // mov eax, dword ptr [edi + ebx * 4]
+
+ if(andit)
+ {
+ EmitString("25"); // and eax, 0x12345678
+ Emit4(andit);
+ }
+}
+
+void EmitMovECXStack(vm_t *vm)
+{
+ if(!jlabel)
+ {
+ if(LastCommand == LAST_COMMAND_MOV_STACK_EAX) // mov [edi + ebx * 4], eax
+ {
+ compiledOfs -= 3;
+ vm->instructionPointers[instruction - 1] = compiledOfs;
+ EmitString("89 C1"); // mov ecx, eax
+ return;
+ }
+ if(pop1 == OP_DIVI || pop1 == OP_DIVU || pop1 == OP_MULI || pop1 == OP_MULU ||
+ pop1 == OP_STORE4 || pop1 == OP_STORE2 || pop1 == OP_STORE1)
+ {
+ EmitString("89 C1"); // mov ecx, eax
+ return;
+ }
+ }
+
+ EmitString("8B 0C 9F"); // mov ecx, dword ptr [edi + ebx * 4]
+}
+
+
+void EmitMovEDXStack(vm_t *vm, int andit)
+{
+ if(!jlabel)
+ {
+ if(LastCommand == LAST_COMMAND_MOV_STACK_EAX)
+ { // mov dword ptr [edi + ebx * 4], eax
+ compiledOfs -= 3;
+ vm->instructionPointers[instruction - 1] = compiledOfs;
+
+ EmitString("8B D0"); // mov edx, eax
+ }
+ else if(pop1 == OP_DIVI || pop1 == OP_DIVU || pop1 == OP_MULI || pop1 == OP_MULU ||
+ pop1 == OP_STORE4 || pop1 == OP_STORE2 || pop1 == OP_STORE1)
+ {
+ EmitString("8B D0"); // mov edx, eax
+ }
+ else if(pop1 == OP_CONST && buf[compiledOfs-7] == 0xC7 && buf[compiledOfs-6] == 0x07 && buf[compiledOfs - 5] == 0x9F)
+ { // mov dword ptr [edi + ebx * 4], 0x12345678
+ compiledOfs -= 7;
+ vm->instructionPointers[instruction - 1] = compiledOfs;
+ EmitString("BA"); // mov edx, 0x12345678
+
+ if(andit)
+ Emit4(lastConst & andit);
+ else
+ Emit4(lastConst);
+
+ return;
+ }
+ else
+ EmitString("8B 14 9F"); // mov edx, dword ptr [edi + ebx * 4]
+
+ }
+ else
+ EmitString("8B 14 9F"); // mov edx, dword ptr [edi + ebx * 4]
+
+ if(andit)
+ MASK_REG("E2", andit); // and edx, 0x12345678
+}
+
+#define JUSED(x) \
+ do { \
+ if (x < 0 || x >= vm->instructionCount) { \
+ VMFREE_BUFFERS(); \
+ Com_Error( ERR_DROP, \
+ "VM_CompileX86: jump target out of range at offset %d", pc ); \
+ } \
+ jused[x] = 1; \
+ } while(0)
+
+#define SET_JMPOFS(x) do { buf[(x)] = compiledOfs - ((x) + 1); } while(0)
+
+
+/*
+=================
+ErrJump
+Error handler for jump/call to invalid instruction number
+=================
+*/
+
+static void __attribute__((__noreturn__)) ErrJump(void)
+{
+ Com_Error(ERR_DROP, "program tried to execute code outside VM");
+}
+
+/*
+=================
+DoSyscall
+
+Assembler helper routines will write its arguments directly to global variables so as to
+work around different calling conventions
+=================
+*/
+
+int vm_syscallNum;
+int vm_programStack;
+int *vm_opStackBase;
+uint8_t vm_opStackOfs;
+intptr_t vm_arg;
+
+static void DoSyscall(void)
+{
+ vm_t *savedVM;
+
+ // save currentVM so as to allow for recursive VM entry
+ savedVM = currentVM;
+ // modify VM stack pointer for recursive VM entry
+ currentVM->programStack = vm_programStack - 4;
+
+ if(vm_syscallNum < 0)
+ {
+ int *data, *ret;
+#if idx64
+ int index;
+ intptr_t args[MAX_VMSYSCALL_ARGS];
+#endif
+
+ data = (int *) (savedVM->dataBase + vm_programStack + 4);
+ ret = &vm_opStackBase[vm_opStackOfs + 1];
+
+#if idx64
+ args[0] = ~vm_syscallNum;
+ for(index = 1; index < ARRAY_LEN(args); index++)
+ args[index] = data[index];
+
+ *ret = savedVM->systemCall(args);
+#else
+ data[0] = ~vm_syscallNum;
+ *ret = savedVM->systemCall((intptr_t *) data);
+#endif
+ }
+ else
+ {
+ switch(vm_syscallNum)
+ {
+ case VM_JMP_VIOLATION:
+ ErrJump();
+ break;
+ case VM_BLOCK_COPY:
+ if(vm_opStackOfs < 1)
+ Com_Error(ERR_DROP, "VM_BLOCK_COPY failed due to corrupted opStack");
+
+ VM_BlockCopy(vm_opStackBase[(vm_opStackOfs - 1)], vm_opStackBase[vm_opStackOfs], vm_arg);
+ break;
+ default:
+ Com_Error(ERR_DROP, "Unknown VM operation %d", vm_syscallNum);
+ break;
+ }
+ }
+
+ currentVM = savedVM;
+}
+
+/*
+=================
+EmitCallRel
+Relative call to vm->codeBase + callOfs
+=================
+*/
+
+void EmitCallRel(vm_t *vm, int callOfs)
+{
+ EmitString("E8"); // call 0x12345678
+ Emit4(callOfs - compiledOfs - 4);
+}
+
+/*
+=================
+EmitCallDoSyscall
+Call to DoSyscall()
+=================
+*/
+
+int EmitCallDoSyscall(vm_t *vm)
+{
+ // use edx register to store DoSyscall address
+ EmitRexString(0x48, "BA"); // mov edx, DoSyscall
+ EmitPtr((void*)DoSyscall);
+
+ // Push important registers to stack as we can't really make
+ // any assumptions about calling conventions.
+ EmitString("51"); // push ebx
+ EmitString("56"); // push esi
+ EmitString("57"); // push edi
+#if idx64
+ EmitRexString(0x41, "50"); // push r8
+ EmitRexString(0x41, "51"); // push r9
+#endif
+
+ // write arguments to global vars
+ // syscall number
+ EmitString("A3"); // mov [0x12345678], eax
+ EmitPtr(&vm_syscallNum);
+ // vm_programStack value
+ EmitString("89 F0"); // mov eax, esi
+ EmitString("A3"); // mov [0x12345678], eax
+ EmitPtr(&vm_programStack);
+ // vm_opStackOfs
+ EmitString("88 D8"); // mov al, bl
+ EmitString("A2"); // mov [0x12345678], al
+ EmitPtr(&vm_opStackOfs);
+ // vm_opStackBase
+ EmitRexString(0x48, "89 F8"); // mov eax, edi
+ EmitRexString(0x48, "A3"); // mov [0x12345678], eax
+ EmitPtr(&vm_opStackBase);
+ // vm_arg
+ EmitString("89 C8"); // mov eax, ecx
+ EmitString("A3"); // mov [0x12345678], eax
+ EmitPtr(&vm_arg);
+
+ // align the stack pointer to a 16-byte-boundary
+ EmitString("55"); // push ebp
+ EmitRexString(0x48, "89 E5"); // mov ebp, esp
+ EmitRexString(0x48, "83 E4 F0"); // and esp, 0xFFFFFFF0
+
+ // call the syscall wrapper function DoSyscall()
+
+ EmitString("FF D2"); // call edx
+
+ // reset the stack pointer to its previous value
+ EmitRexString(0x48, "89 EC"); // mov esp, ebp
+ EmitString("5D"); // pop ebp
+
+#if idx64
+ EmitRexString(0x41, "59"); // pop r9
+ EmitRexString(0x41, "58"); // pop r8
+#endif
+ EmitString("5F"); // pop edi
+ EmitString("5E"); // pop esi
+ EmitString("59"); // pop ebx
+
+ EmitString("C3"); // ret
+
+ return compiledOfs;
+}
+
+/*
+=================
+EmitCallErrJump
+Emit the code that triggers execution of the jump violation handler
+=================
+*/
+
+static void EmitCallErrJump(vm_t *vm, int sysCallOfs)
+{
+ EmitString("B8"); // mov eax, 0x12345678
+ Emit4(VM_JMP_VIOLATION);
+
+ EmitCallRel(vm, sysCallOfs);
+}
+
+/*
+=================
+EmitCallProcedure
+VM OP_CALL procedure for call destinations obtained at runtime
+=================
+*/
+
+int EmitCallProcedure(vm_t *vm, int sysCallOfs)
+{
+ int jmpSystemCall, jmpBadAddr;
+ int retval;
+
+ EmitString("8B 04 9F"); // mov eax, dword ptr [edi + ebx * 4]
+ STACK_POP(1); // sub bl, 1
+ EmitString("85 C0"); // test eax, eax
+
+ // Jump to syscall code, 1 byte offset should suffice
+ EmitString("7C"); // jl systemCall
+ jmpSystemCall = compiledOfs++;
+
+ /************ Call inside VM ************/
+
+ EmitString("81 F8"); // cmp eax, vm->instructionCount
+ Emit4(vm->instructionCount);
+
+ // Error jump if invalid jump target
+ EmitString("73"); // jae badAddr
+ jmpBadAddr = compiledOfs++;
+
+#if idx64
+ EmitRexString(0x49, "FF 14 C0"); // call qword ptr [r8 + eax * 8]
+#else
+ EmitString("FF 14 85"); // call dword ptr [vm->instructionPointers + eax * 4]
+ Emit4((intptr_t) vm->instructionPointers);
+#endif
+ EmitString("8B 04 9F"); // mov eax, dword ptr [edi + ebx * 4]
+ EmitString("C3"); // ret
+
+ // badAddr:
+ SET_JMPOFS(jmpBadAddr);
+ EmitCallErrJump(vm, sysCallOfs);
+
+ /************ System Call ************/
+
+ // systemCall:
+ SET_JMPOFS(jmpSystemCall);
+ retval = compiledOfs;
+
+ EmitCallRel(vm, sysCallOfs);
+
+ // have opStack reg point at return value
+ STACK_PUSH(1); // add bl, 1
+ EmitString("C3"); // ret
+
+ return retval;
+}
+
+/*
+=================
+EmitJumpIns
+Jump to constant instruction number
+=================
+*/
+
+void EmitJumpIns(vm_t *vm, const char *jmpop, int cdest)
+{
+ JUSED(cdest);
+
+ EmitString(jmpop); // j??? 0x12345678
+
+ // we only know all the jump addresses in the third pass
+ if(pass == 2)
+ Emit4(vm->instructionPointers[cdest] - compiledOfs - 4);
+ else
+ compiledOfs += 4;
+}
+
+/*
+=================
+EmitCallIns
+Call to constant instruction number
+=================
+*/
+
+void EmitCallIns(vm_t *vm, int cdest)
+{
+ JUSED(cdest);
+
+ EmitString("E8"); // call 0x12345678
+
+ // we only know all the jump addresses in the third pass
+ if(pass == 2)
+ Emit4(vm->instructionPointers[cdest] - compiledOfs - 4);
+ else
+ compiledOfs += 4;
+}
+
+/*
+=================
+EmitCallConst
+Call to constant instruction number or syscall
+=================
+*/
+
+void EmitCallConst(vm_t *vm, int cdest, int callProcOfsSyscall)
+{
+ if(cdest < 0)
+ {
+ EmitString("B8"); // mov eax, cdest
+ Emit4(cdest);
+
+ EmitCallRel(vm, callProcOfsSyscall);
+ }
+ else
+ EmitCallIns(vm, cdest);
+}
+
+/*
+=================
+EmitBranchConditions
+Emits x86 branch condition as given in op
+=================
+*/
+void EmitBranchConditions(vm_t *vm, int op)
+{
+ switch(op)
+ {
+ case OP_EQ:
+ EmitJumpIns(vm, "0F 84", Constant4()); // je 0x12345678
+ break;
+ case OP_NE:
+ EmitJumpIns(vm, "0F 85", Constant4()); // jne 0x12345678
+ break;
+ case OP_LTI:
+ EmitJumpIns(vm, "0F 8C", Constant4()); // jl 0x12345678
+ break;
+ case OP_LEI:
+ EmitJumpIns(vm, "0F 8E", Constant4()); // jle 0x12345678
+ break;
+ case OP_GTI:
+ EmitJumpIns(vm, "0F 8F", Constant4()); // jg 0x12345678
+ break;
+ case OP_GEI:
+ EmitJumpIns(vm, "0F 8D", Constant4()); // jge 0x12345678
+ break;
+ case OP_LTU:
+ EmitJumpIns(vm, "0F 82", Constant4()); // jb 0x12345678
+ break;
+ case OP_LEU:
+ EmitJumpIns(vm, "0F 86", Constant4()); // jbe 0x12345678
+ break;
+ case OP_GTU:
+ EmitJumpIns(vm, "0F 87", Constant4()); // ja 0x12345678
+ break;
+ case OP_GEU:
+ EmitJumpIns(vm, "0F 83", Constant4()); // jae 0x12345678
+ break;
+ }
+}
+
+
+/*
+=================
+ConstOptimize
+Constant values for immediately following instructions may be translated to immediate values
+instead of opStack operations, which will save expensive operations on memory
+=================
+*/
+
+static bool ConstOptimize(vm_t *vm, int callProcOfsSyscall)
+{
+ int v;
+ int op1;
+
+ // we can safely perform optimizations only in case if
+ // we are 100% sure that next instruction is not a jump label
+ if (vm->jumpTableTargets && !jused[instruction])
+ op1 = code[pc+4];
+ else
+ return false;
+
+ switch ( op1 ) {
+
+ case OP_LOAD4:
+ EmitPushStack(vm);
+#if idx64
+ EmitRexString(0x41, "8B 81"); // mov eax, dword ptr [r9 + 0x12345678]
+ Emit4(Constant4() & vm->dataMask);
+#else
+ EmitString("B8"); // mov eax, 0x12345678
+ EmitPtr(vm->dataBase + (Constant4() & vm->dataMask));
+ EmitString("8B 00"); // mov eax, dword ptr [eax]
+#endif
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax
+
+ pc++; // OP_LOAD4
+ instruction += 1;
+ return true;
+
+ case OP_LOAD2:
+ EmitPushStack(vm);
+#if idx64
+ EmitRexString(0x41, "0F B7 81"); // movzx eax, word ptr [r9 + 0x12345678]
+ Emit4(Constant4() & vm->dataMask);
+#else
+ EmitString("B8"); // mov eax, 0x12345678
+ EmitPtr(vm->dataBase + (Constant4() & vm->dataMask));
+ EmitString("0F B7 00"); // movzx eax, word ptr [eax]
+#endif
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax
+
+ pc++; // OP_LOAD2
+ instruction += 1;
+ return true;
+
+ case OP_LOAD1:
+ EmitPushStack(vm);
+#if idx64
+ EmitRexString(0x41, "0F B6 81"); // movzx eax, byte ptr [r9 + 0x12345678]
+ Emit4(Constant4() & vm->dataMask);
+#else
+ EmitString("B8"); // mov eax, 0x12345678
+ EmitPtr(vm->dataBase + (Constant4() & vm->dataMask));
+ EmitString("0F B6 00"); // movzx eax, byte ptr [eax]
+#endif
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax
+
+ pc++; // OP_LOAD1
+ instruction += 1;
+ return true;
+
+ case OP_STORE4:
+ EmitMovEAXStack(vm, (vm->dataMask & ~3));
+#if idx64
+ EmitRexString(0x41, "C7 04 01"); // mov dword ptr [r9 + eax], 0x12345678
+ Emit4(Constant4());
+#else
+ EmitString("C7 80"); // mov dword ptr [eax + 0x12345678], 0x12345678
+ Emit4((intptr_t) vm->dataBase);
+ Emit4(Constant4());
+#endif
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ pc++; // OP_STORE4
+ instruction += 1;
+ return true;
+
+ case OP_STORE2:
+ EmitMovEAXStack(vm, (vm->dataMask & ~1));
+#if idx64
+ Emit1(0x66); // mov word ptr [r9 + eax], 0x1234
+ EmitRexString(0x41, "C7 04 01");
+ Emit2(Constant4());
+#else
+ EmitString("66 C7 80"); // mov word ptr [eax + 0x12345678], 0x1234
+ Emit4((intptr_t) vm->dataBase);
+ Emit2(Constant4());
+#endif
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+
+ pc++; // OP_STORE2
+ instruction += 1;
+ return true;
+
+ case OP_STORE1:
+ EmitMovEAXStack(vm, vm->dataMask);
+#if idx64
+ EmitRexString(0x41, "C6 04 01"); // mov byte [r9 + eax], 0x12
+ Emit1(Constant4());
+#else
+ EmitString("C6 80"); // mov byte ptr [eax + 0x12345678], 0x12
+ Emit4((intptr_t) vm->dataBase);
+ Emit1(Constant4());
+#endif
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+
+ pc++; // OP_STORE1
+ instruction += 1;
+ return true;
+
+ case OP_ADD:
+ v = Constant4();
+
+ EmitMovEAXStack(vm, 0);
+ if(iss8(v))
+ {
+ EmitString("83 C0"); // add eax, 0x7F
+ Emit1(v);
+ }
+ else
+ {
+ EmitString("05"); // add eax, 0x12345678
+ Emit4(v);
+ }
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX);
+
+ pc++; // OP_ADD
+ instruction += 1;
+ return true;
+
+ case OP_SUB:
+ v = Constant4();
+
+ EmitMovEAXStack(vm, 0);
+ if(iss8(v))
+ {
+ EmitString("83 E8"); // sub eax, 0x7F
+ Emit1(v);
+ }
+ else
+ {
+ EmitString("2D"); // sub eax, 0x12345678
+ Emit4(v);
+ }
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX);
+
+ pc++; // OP_SUB
+ instruction += 1;
+ return true;
+
+ case OP_MULI:
+ v = Constant4();
+
+ EmitMovEAXStack(vm, 0);
+ if(iss8(v))
+ {
+ EmitString("6B C0"); // imul eax, 0x7F
+ Emit1(v);
+ }
+ else
+ {
+ EmitString("69 C0"); // imul eax, 0x12345678
+ Emit4(v);
+ }
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX);
+ pc++; // OP_MULI
+ instruction += 1;
+
+ return true;
+
+ case OP_LSH:
+ v = NextConstant4();
+ if(v < 0 || v > 31)
+ break;
+
+ EmitMovEAXStack(vm, 0);
+ EmitString("C1 E0"); // shl eax, 0x12
+ Emit1(v);
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX);
+
+ pc += 5; // CONST + OP_LSH
+ instruction += 1;
+ return true;
+
+ case OP_RSHI:
+ v = NextConstant4();
+ if(v < 0 || v > 31)
+ break;
+
+ EmitMovEAXStack(vm, 0);
+ EmitString("C1 F8"); // sar eax, 0x12
+ Emit1(v);
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX);
+
+ pc += 5; // CONST + OP_RSHI
+ instruction += 1;
+ return true;
+
+ case OP_RSHU:
+ v = NextConstant4();
+ if(v < 0 || v > 31)
+ break;
+
+ EmitMovEAXStack(vm, 0);
+ EmitString("C1 E8"); // shr eax, 0x12
+ Emit1(v);
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX);
+
+ pc += 5; // CONST + OP_RSHU
+ instruction += 1;
+ return true;
+
+ case OP_BAND:
+ v = Constant4();
+
+ EmitMovEAXStack(vm, 0);
+ if(iss8(v))
+ {
+ EmitString("83 E0"); // and eax, 0x7F
+ Emit1(v);
+ }
+ else
+ {
+ EmitString("25"); // and eax, 0x12345678
+ Emit4(v);
+ }
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX);
+
+ pc += 1; // OP_BAND
+ instruction += 1;
+ return true;
+
+ case OP_BOR:
+ v = Constant4();
+
+ EmitMovEAXStack(vm, 0);
+ if(iss8(v))
+ {
+ EmitString("83 C8"); // or eax, 0x7F
+ Emit1(v);
+ }
+ else
+ {
+ EmitString("0D"); // or eax, 0x12345678
+ Emit4(v);
+ }
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX);
+
+ pc += 1; // OP_BOR
+ instruction += 1;
+ return true;
+
+ case OP_BXOR:
+ v = Constant4();
+
+ EmitMovEAXStack(vm, 0);
+ if(iss8(v))
+ {
+ EmitString("83 F0"); // xor eax, 0x7F
+ Emit1(v);
+ }
+ else
+ {
+ EmitString("35"); // xor eax, 0x12345678
+ Emit4(v);
+ }
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX);
+
+ pc += 1; // OP_BXOR
+ instruction += 1;
+ return true;
+
+ 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:
+ EmitMovEAXStack(vm, 0);
+ EmitCommand(LAST_COMMAND_SUB_BL_1);
+ EmitString("3D"); // cmp eax, 0x12345678
+ Emit4(Constant4());
+
+ pc++; // OP_*
+ EmitBranchConditions(vm, op1);
+ instruction++;
+
+ return true;
+
+ case OP_EQF:
+ case OP_NEF:
+ if(NextConstant4())
+ break;
+ pc += 5; // CONST + OP_EQF|OP_NEF
+
+ EmitMovEAXStack(vm, 0);
+ EmitCommand(LAST_COMMAND_SUB_BL_1);
+ // floating point hack :)
+ EmitString("25"); // and eax, 0x7FFFFFFF
+ Emit4(0x7FFFFFFF);
+ if(op1 == OP_EQF)
+ EmitJumpIns(vm, "0F 84", Constant4()); // jz 0x12345678
+ else
+ EmitJumpIns(vm, "0F 85", Constant4()); // jnz 0x12345678
+
+ instruction += 1;
+ return true;
+
+
+ case OP_JUMP:
+ EmitJumpIns(vm, "E9", Constant4()); // jmp 0x12345678
+
+ pc += 1; // OP_JUMP
+ instruction += 1;
+ return true;
+
+ case OP_CALL:
+ v = Constant4();
+ EmitCallConst(vm, v, callProcOfsSyscall);
+
+ pc += 1; // OP_CALL
+ instruction += 1;
+ return true;
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+#if idx64
+ #define EAX "%%rax"
+ #define EBX "%%rbx"
+ #define ESP "%%rsp"
+ #define EDI "%%rdi"
+#else
+ #define EAX "%%eax"
+ #define EBX "%%ebx"
+ #define ESP "%%esp"
+ #define EDI "%%edi"
+#endif
+
+static int Q_VMftol(void)
+{
+ int retval;
+
+ __asm__ volatile
+ (
+ "movss (" EDI ", " EBX ", 4), %%xmm0\n"
+ "cvttss2si %%xmm0, %0\n"
+ : "=r" (retval)
+ :
+ : "%xmm0"
+ );
+
+ return retval;
+}
+
+/*
+=================
+VM_Compile
+=================
+*/
+void VM_Compile(vm_t *vm, vmHeader_t *header)
+{
+ int op;
+ int maxLength;
+ int v;
+ int i;
+ int callProcOfsSyscall, callProcOfs, callDoSyscallOfs;
+
+ jusedSize = header->instructionCount + 2;
+
+ // allocate a very large temp buffer, we will shrink it later
+ maxLength = header->codeLength * 8 + 64;
+ buf = (byte*)Z_Malloc(maxLength);
+ jused = (byte*)Z_Malloc(jusedSize);
+ code = (byte*)Z_Malloc(header->codeLength+32);
+
+ ::memset(jused, 0, jusedSize);
+ ::memset(buf, 0, maxLength);
+
+ // copy code in larger buffer and put some zeros at the end
+ // so we can safely look ahead for a few instructions in it
+ // without a chance to get false-positive because of some garbage bytes
+ ::memset(code, 0, header->codeLength+32);
+ ::memcpy(code, (byte *)header + header->codeOffset, header->codeLength );
+
+ // ensure that the optimisation pass knows about all the jump
+ // table targets
+ pc = -1; // a bogus value to be printed in out-of-bounds error messages
+ for( i = 0; i < vm->numJumpTableTargets; i++ ) {
+ JUSED( *(int *)(vm->jumpTableTargets + ( i * sizeof( int ) ) ) );
+ }
+
+ // Start buffer with x86-VM specific procedures
+ compiledOfs = 0;
+
+ callDoSyscallOfs = compiledOfs;
+ callProcOfs = EmitCallDoSyscall(vm);
+ callProcOfsSyscall = EmitCallProcedure(vm, callDoSyscallOfs);
+ vm->entryOfs = compiledOfs;
+
+ for(pass=0; pass < 3; pass++) {
+ oc0 = -23423;
+ oc1 = -234354;
+ pop0 = -43435;
+ pop1 = -545455;
+
+ // translate all instructions
+ pc = 0;
+ instruction = 0;
+ //code = (byte *)header + header->codeOffset;
+ compiledOfs = vm->entryOfs;
+
+ LastCommand = LAST_COMMAND_NONE;
+
+ while(instruction < header->instructionCount)
+ {
+ if(compiledOfs > maxLength - 16)
+ {
+ VMFREE_BUFFERS();
+ Com_Error(ERR_DROP, "VM_CompileX86: maxLength exceeded");
+ }
+
+ vm->instructionPointers[ instruction ] = compiledOfs;
+
+ if ( !vm->jumpTableTargets )
+ jlabel = 1;
+ else
+ jlabel = jused[ instruction ];
+
+ instruction++;
+
+ if(pc > header->codeLength)
+ {
+ VMFREE_BUFFERS();
+ Com_Error(ERR_DROP, "VM_CompileX86: pc > header->codeLength");
+ }
+
+ op = code[ pc ];
+ pc++;
+ switch ( op ) {
+ case 0:
+ break;
+ case OP_BREAK:
+ EmitString("CC"); // int 3
+ break;
+ case OP_ENTER:
+ EmitString("81 EE"); // sub esi, 0x12345678
+ Emit4(Constant4());
+ break;
+ case OP_CONST:
+ if(ConstOptimize(vm, callProcOfsSyscall))
+ break;
+
+ EmitPushStack(vm);
+ EmitString("C7 04 9F"); // mov dword ptr [edi + ebx * 4], 0x12345678
+ lastConst = Constant4();
+
+ Emit4(lastConst);
+ if(code[pc] == OP_JUMP)
+ JUSED(lastConst);
+
+ break;
+ case OP_LOCAL:
+ EmitPushStack(vm);
+ EmitString("8D 86"); // lea eax, [0x12345678 + esi]
+ oc0 = oc1;
+ oc1 = Constant4();
+ Emit4(oc1);
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax
+ break;
+ case OP_ARG:
+ EmitMovEAXStack(vm, 0); // mov eax, dword ptr [edi + ebx * 4]
+ EmitString("8B D6"); // mov edx, esi
+ EmitString("81 C2"); // add edx, 0x12345678
+ Emit4((Constant1() & 0xFF));
+ MASK_REG("E2", vm->dataMask); // and edx, 0x12345678
+#if idx64
+ EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax
+#else
+ EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax
+ Emit4((intptr_t) vm->dataBase);
+#endif
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ break;
+ case OP_CALL:
+ EmitCallRel(vm, callProcOfs);
+ break;
+ case OP_PUSH:
+ EmitPushStack(vm);
+ break;
+ case OP_POP:
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ 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 -= 12;
+ vm->instructionPointers[instruction - 1] = compiledOfs;
+ }
+
+ pc++; // OP_CONST
+ v = Constant4();
+
+ EmitMovEDXStack(vm, vm->dataMask);
+ if(v == 1 && oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL)
+ {
+#if idx64
+ EmitRexString(0x41, "FF 04 11"); // inc dword ptr [r9 + edx]
+#else
+ EmitString("FF 82"); // inc dword ptr [edx + 0x12345678]
+ Emit4((intptr_t) vm->dataBase);
+#endif
+ }
+ else
+ {
+#if idx64
+ EmitRexString(0x41, "8B 04 11"); // mov eax, dword ptr [r9 + edx]
+#else
+ EmitString("8B 82"); // mov eax, dword ptr [edx + 0x12345678]
+ Emit4((intptr_t) vm->dataBase);
+#endif
+ EmitString("05"); // add eax, v
+ Emit4(v);
+
+ if (oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL)
+ {
+#if idx64
+ EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax
+#else
+ EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax
+ Emit4((intptr_t) vm->dataBase);
+#endif
+ }
+ else
+ {
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ EmitString("8B 14 9F"); // mov edx, dword ptr [edi + ebx * 4]
+ MASK_REG("E2", vm->dataMask); // and edx, 0x12345678
+#if idx64
+ EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax
+#else
+ EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax
+ Emit4((intptr_t) vm->dataBase);
+#endif
+ }
+ }
+
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ 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 -= 12;
+ vm->instructionPointers[instruction - 1] = compiledOfs;
+ }
+
+ pc++; // OP_CONST
+ v = Constant4();
+
+ EmitMovEDXStack(vm, vm->dataMask);
+ if(v == 1 && oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL)
+ {
+#if idx64
+ EmitRexString(0x41, "FF 0C 11"); // dec dword ptr [r9 + edx]
+#else
+ EmitString("FF 8A"); // dec dword ptr [edx + 0x12345678]
+ Emit4((intptr_t) vm->dataBase);
+#endif
+ }
+ else
+ {
+#if idx64
+ EmitRexString(0x41, "8B 04 11"); // mov eax, dword ptr [r9 + edx]
+#else
+ EmitString("8B 82"); // mov eax, dword ptr [edx + 0x12345678]
+ Emit4((intptr_t) vm->dataBase);
+#endif
+ EmitString("2D"); // sub eax, v
+ Emit4(v);
+
+ if(oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL)
+ {
+#if idx64
+ EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax
+#else
+ EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax
+ Emit4((intptr_t) vm->dataBase);
+#endif
+ }
+ else
+ {
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ EmitString("8B 14 9F"); // mov edx, dword ptr [edi + ebx * 4]
+ MASK_REG("E2", vm->dataMask); // and edx, 0x12345678
+#if idx64
+ EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax
+#else
+ EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax
+ Emit4((intptr_t) vm->dataBase);
+#endif
+ }
+ }
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ pc++; // OP_SUB
+ pc++; // OP_STORE
+ instruction += 3;
+ break;
+ }
+
+ if(buf[compiledOfs - 3] == 0x89 && buf[compiledOfs - 2] == 0x04 && buf[compiledOfs - 1] == 0x9F)
+ {
+ compiledOfs -= 3;
+ vm->instructionPointers[instruction - 1] = compiledOfs;
+ MASK_REG("E0", vm->dataMask); // and eax, 0x12345678
+#if idx64
+ EmitRexString(0x41, "8B 04 01"); // mov eax, dword ptr [r9 + eax]
+#else
+ EmitString("8B 80"); // mov eax, dword ptr [eax + 0x1234567]
+ Emit4((intptr_t) vm->dataBase);
+#endif
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax
+ break;
+ }
+
+ EmitMovEAXStack(vm, vm->dataMask);
+#if idx64
+ EmitRexString(0x41, "8B 04 01"); // mov eax, dword ptr [r9 + eax]
+#else
+ EmitString("8B 80"); // mov eax, dword ptr [eax + 0x12345678]
+ Emit4((intptr_t) vm->dataBase);
+#endif
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax
+ break;
+ case OP_LOAD2:
+ EmitMovEAXStack(vm, vm->dataMask);
+#if idx64
+ EmitRexString(0x41, "0F B7 04 01"); // movzx eax, word ptr [r9 + eax]
+#else
+ EmitString("0F B7 80"); // movzx eax, word ptr [eax + 0x12345678]
+ Emit4((intptr_t) vm->dataBase);
+#endif
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax
+ break;
+ case OP_LOAD1:
+ EmitMovEAXStack(vm, vm->dataMask);
+#if idx64
+ EmitRexString(0x41, "0F B6 04 01"); // movzx eax, byte ptr [r9 + eax]
+#else
+ EmitString("0F B6 80"); // movzx eax, byte ptr [eax + 0x12345678]
+ Emit4((intptr_t) vm->dataBase);
+#endif
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax
+ break;
+ case OP_STORE4:
+ EmitMovEAXStack(vm, 0);
+ EmitString("8B 54 9F FC"); // mov edx, dword ptr -4[edi + ebx * 4]
+ MASK_REG("E2", vm->dataMask & ~3); // and edx, 0x12345678
+#if idx64
+ EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax
+#else
+ EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax
+ Emit4((intptr_t) vm->dataBase);
+#endif
+ EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2
+ break;
+ case OP_STORE2:
+ EmitMovEAXStack(vm, 0);
+ EmitString("8B 54 9F FC"); // mov edx, dword ptr -4[edi + ebx * 4]
+ MASK_REG("E2", vm->dataMask & ~1); // and edx, 0x12345678
+#if idx64
+ Emit1(0x66); // mov word ptr [r9 + edx], eax
+ EmitRexString(0x41, "89 04 11");
+#else
+ EmitString("66 89 82"); // mov word ptr [edx + 0x12345678], eax
+ Emit4((intptr_t) vm->dataBase);
+#endif
+ EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2
+ break;
+ case OP_STORE1:
+ EmitMovEAXStack(vm, 0);
+ EmitString("8B 54 9F FC"); // mov edx, dword ptr -4[edi + ebx * 4]
+ MASK_REG("E2", vm->dataMask); // and edx, 0x12345678
+#if idx64
+ EmitRexString(0x41, "88 04 11"); // mov byte ptr [r9 + edx], eax
+#else
+ EmitString("88 82"); // mov byte ptr [edx + 0x12345678], eax
+ Emit4((intptr_t) vm->dataBase);
+#endif
+ EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2
+ break;
+
+ 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:
+ EmitMovEAXStack(vm, 0);
+ EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2
+ EmitString("39 44 9F 04"); // cmp eax, dword ptr 4[edi + ebx * 4]
+
+ EmitBranchConditions(vm, op);
+ break;
+ case OP_EQF:
+ case OP_NEF:
+ case OP_LTF:
+ case OP_LEF:
+ case OP_GTF:
+ case OP_GEF:
+ EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2
+ EmitString("D9 44 9F 04"); // fld dword ptr 4[edi + ebx * 4]
+ EmitString("D8 5C 9F 08"); // fcomp dword ptr 8[edi + ebx * 4]
+ EmitString("DF E0"); // fnstsw ax
+
+ switch(op)
+ {
+ case OP_EQF:
+ EmitString("F6 C4 40"); // test ah,0x40
+ EmitJumpIns(vm, "0F 85", Constant4()); // jne 0x12345678
+ break;
+ case OP_NEF:
+ EmitString("F6 C4 40"); // test ah,0x40
+ EmitJumpIns(vm, "0F 84", Constant4()); // je 0x12345678
+ break;
+ case OP_LTF:
+ EmitString("F6 C4 01"); // test ah,0x01
+ EmitJumpIns(vm, "0F 85", Constant4()); // jne 0x12345678
+ break;
+ case OP_LEF:
+ EmitString("F6 C4 41"); // test ah,0x41
+ EmitJumpIns(vm, "0F 85", Constant4()); // jne 0x12345678
+ break;
+ case OP_GTF:
+ EmitString("F6 C4 41"); // test ah,0x41
+ EmitJumpIns(vm, "0F 84", Constant4()); // je 0x12345678
+ break;
+ case OP_GEF:
+ EmitString("F6 C4 01"); // test ah,0x01
+ EmitJumpIns(vm, "0F 84", Constant4()); // je 0x12345678
+ break;
+ }
+ break;
+ case OP_NEGI:
+ EmitMovEAXStack(vm, 0);
+ EmitString("F7 D8"); // neg eax
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX);
+ break;
+ case OP_ADD:
+ EmitMovEAXStack(vm, 0); // mov eax, dword ptr [edi + ebx * 4]
+ EmitString("01 44 9F FC"); // add dword ptr -4[edi + ebx * 4], eax
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ break;
+ case OP_SUB:
+ EmitMovEAXStack(vm, 0); // mov eax, dword ptr [edi + ebx * 4]
+ EmitString("29 44 9F FC"); // sub dword ptr -4[edi + ebx * 4], eax
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ break;
+ case OP_DIVI:
+ EmitString("8B 44 9F FC"); // mov eax,dword ptr -4[edi + ebx * 4]
+ EmitString("99"); // cdq
+ EmitString("F7 3C 9F"); // idiv dword ptr [edi + ebx * 4]
+ EmitString("89 44 9F FC"); // mov dword ptr -4[edi + ebx * 4],eax
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ break;
+ case OP_DIVU:
+ EmitString("8B 44 9F FC"); // mov eax,dword ptr -4[edi + ebx * 4]
+ EmitString("33 D2"); // xor edx, edx
+ EmitString("F7 34 9F"); // div dword ptr [edi + ebx * 4]
+ EmitString("89 44 9F FC"); // mov dword ptr -4[edi + ebx * 4],eax
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ break;
+ case OP_MODI:
+ EmitString("8B 44 9F FC"); // mov eax,dword ptr -4[edi + ebx * 4]
+ EmitString("99" ); // cdq
+ EmitString("F7 3C 9F"); // idiv dword ptr [edi + ebx * 4]
+ EmitString("89 54 9F FC"); // mov dword ptr -4[edi + ebx * 4],edx
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ break;
+ case OP_MODU:
+ EmitString("8B 44 9F FC"); // mov eax,dword ptr -4[edi + ebx * 4]
+ EmitString("33 D2"); // xor edx, edx
+ EmitString("F7 34 9F"); // div dword ptr [edi + ebx * 4]
+ EmitString("89 54 9F FC"); // mov dword ptr -4[edi + ebx * 4],edx
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ break;
+ case OP_MULI:
+ EmitString("8B 44 9F FC"); // mov eax,dword ptr -4[edi + ebx * 4]
+ EmitString("F7 2C 9F"); // imul dword ptr [edi + ebx * 4]
+ EmitString("89 44 9F FC"); // mov dword ptr -4[edi + ebx * 4],eax
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ break;
+ case OP_MULU:
+ EmitString("8B 44 9F FC"); // mov eax,dword ptr -4[edi + ebx * 4]
+ EmitString("F7 24 9F"); // mul dword ptr [edi + ebx * 4]
+ EmitString("89 44 9F FC"); // mov dword ptr -4[edi + ebx * 4],eax
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ break;
+ case OP_BAND:
+ EmitMovEAXStack(vm, 0); // mov eax, dword ptr [edi + ebx * 4]
+ EmitString("21 44 9F FC"); // and dword ptr -4[edi + ebx * 4],eax
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ break;
+ case OP_BOR:
+ EmitMovEAXStack(vm, 0); // mov eax, dword ptr [edi + ebx * 4]
+ EmitString("09 44 9F FC"); // or dword ptr -4[edi + ebx * 4],eax
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ break;
+ case OP_BXOR:
+ EmitMovEAXStack(vm, 0); // mov eax, dword ptr [edi + ebx * 4]
+ EmitString("31 44 9F FC"); // xor dword ptr -4[edi + ebx * 4],eax
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ break;
+ case OP_BCOM:
+ EmitString("F7 14 9F"); // not dword ptr [edi + ebx * 4]
+ break;
+ case OP_LSH:
+ EmitMovECXStack(vm);
+ EmitString("D3 64 9F FC"); // shl dword ptr -4[edi + ebx * 4], cl
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ break;
+ case OP_RSHI:
+ EmitMovECXStack(vm);
+ EmitString("D3 7C 9F FC"); // sar dword ptr -4[edi + ebx * 4], cl
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ break;
+ case OP_RSHU:
+ EmitMovECXStack(vm);
+ EmitString("D3 6C 9F FC"); // shr dword ptr -4[edi + ebx * 4], cl
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ break;
+ case OP_NEGF:
+ EmitString("D9 04 9F"); // fld dword ptr [edi + ebx * 4]
+ EmitString("D9 E0"); // fchs
+ EmitString("D9 1C 9F"); // fstp dword ptr [edi + ebx * 4]
+ break;
+ case OP_ADDF:
+ EmitString("D9 44 9F FC"); // fld dword ptr -4[edi + ebx * 4]
+ EmitString("D8 04 9F"); // fadd dword ptr [edi + ebx * 4]
+ EmitString("D9 5C 9F FC"); // fstp dword ptr -4[edi + ebx * 4]
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ break;
+ case OP_SUBF:
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ EmitString("D9 04 9F"); // fld dword ptr [edi + ebx * 4]
+ EmitString("D8 64 9F 04"); // fsub dword ptr 4[edi + ebx * 4]
+ EmitString("D9 1C 9F"); // fstp dword ptr [edi + ebx * 4]
+ break;
+ case OP_DIVF:
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ EmitString("D9 04 9F"); // fld dword ptr [edi + ebx * 4]
+ EmitString("D8 74 9F 04"); // fdiv dword ptr 4[edi + ebx * 4]
+ EmitString("D9 1C 9F"); // fstp dword ptr [edi + ebx * 4]
+ break;
+ case OP_MULF:
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ EmitString("D9 04 9F"); // fld dword ptr [edi + ebx * 4]
+ EmitString("D8 4C 9F 04"); // fmul dword ptr 4[edi + ebx * 4]
+ EmitString("D9 1C 9F"); // fstp dword ptr [edi + ebx * 4]
+ break;
+ case OP_CVIF:
+ EmitString("DB 04 9F"); // fild dword ptr [edi + ebx * 4]
+ EmitString("D9 1C 9F"); // fstp dword ptr [edi + ebx * 4]
+ break;
+ case OP_CVFI:
+#ifndef FTOL_PTR // WHENHELLISFROZENOVER
+ // not IEEE complient, but simple and fast
+ EmitString("D9 04 9F"); // fld dword ptr [edi + ebx * 4]
+ EmitString("DB 1C 9F"); // fistp dword ptr [edi + ebx * 4]
+#else // FTOL_PTR
+ // call the library conversion function
+ EmitRexString(0x48, "BA"); // mov edx, Q_VMftol
+ EmitPtr((void*)Q_VMftol);
+ EmitRexString(0x48, "FF D2"); // call edx
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax
+#endif
+ break;
+ case OP_SEX8:
+ EmitString("0F BE 04 9F"); // movsx eax, byte ptr [edi + ebx * 4]
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax
+ break;
+ case OP_SEX16:
+ EmitString("0F BF 04 9F"); // movsx eax, word ptr [edi + ebx * 4]
+ EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax
+ break;
+
+ case OP_BLOCK_COPY:
+ EmitString("B8"); // mov eax, 0x12345678
+ Emit4(VM_BLOCK_COPY);
+ EmitString("B9"); // mov ecx, 0x12345678
+ Emit4(Constant4());
+
+ EmitCallRel(vm, callDoSyscallOfs);
+
+ EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2
+ break;
+
+ case OP_JUMP:
+ EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1
+ EmitString("8B 44 9F 04"); // mov eax, dword ptr 4[edi + ebx * 4]
+ EmitString("81 F8"); // cmp eax, vm->instructionCount
+ Emit4(vm->instructionCount);
+#if idx64
+ EmitString("73 04"); // jae +4
+ EmitRexString(0x49, "FF 24 C0"); // jmp qword ptr [r8 + eax * 8]
+#else
+ EmitString("73 07"); // jae +7
+ EmitString("FF 24 85"); // jmp dword ptr [instructionPointers + eax * 4]
+ Emit4((intptr_t) vm->instructionPointers);
+#endif
+ EmitCallErrJump(vm, callDoSyscallOfs);
+ break;
+ default:
+ VMFREE_BUFFERS();
+ Com_Error(ERR_DROP, "VM_CompileX86: bad opcode %i at offset %i", op, pc);
+ }
+ pop0 = pop1;
+ pop1 = op;
+ }
+ }
+
+ // copy to an exact sized buffer with the appropriate permission bits
+ vm->codeLength = compiledOfs;
+#ifdef VM_X86_MMAP
+ vm->codeBase = (byte*)mmap(NULL, compiledOfs, PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
+ if(vm->codeBase == MAP_FAILED)
+ Com_Error(ERR_FATAL, "VM_CompileX86: can't mmap memory");
+#elif _WIN32
+ // allocate memory with EXECUTE permissions under windows.
+ vm->codeBase = (byte*)VirtualAlloc(NULL, compiledOfs, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
+ if(!vm->codeBase)
+ Com_Error(ERR_FATAL, "VM_CompileX86: VirtualAlloc failed");
+#else
+ vm->codeBase = malloc(compiledOfs);
+ if(!vm->codeBase)
+ Com_Error(ERR_FATAL, "VM_CompileX86: malloc failed");
+#endif
+
+ ::memcpy( vm->codeBase, buf, compiledOfs );
+
+#ifdef VM_X86_MMAP
+ if(mprotect(vm->codeBase, compiledOfs, PROT_READ|PROT_EXEC))
+ Com_Error(ERR_FATAL, "VM_CompileX86: mprotect failed");
+#elif _WIN32
+ {
+ DWORD oldProtect = 0;
+
+ // remove write permissions.
+ if(!VirtualProtect(vm->codeBase, compiledOfs, PAGE_EXECUTE_READ, &oldProtect))
+ Com_Error(ERR_FATAL, "VM_CompileX86: VirtualProtect failed");
+ }
+#endif
+
+ Z_Free( code );
+ 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] += (intptr_t) vm->codeBase;
+ }
+}
+
+void VM_Destroy_Compiled(vm_t* self)
+{
+#ifdef VM_X86_MMAP
+ munmap(self->codeBase, self->codeLength);
+#elif _WIN32
+ VirtualFree(self->codeBase, 0, MEM_RELEASE);
+#else
+ free(self->codeBase);
+#endif
+}
+
+/*
+==============
+VM_CallCompiled
+
+This function is called directly by the generated code
+==============
+*/
+
+#if defined(_MSC_VER) && defined(idx64)
+extern uint8_t qvmcall64(int *programStack, int *opStack, intptr_t *instructionPointers, byte *dataBase);
+#endif
+
+int VM_CallCompiled(vm_t *vm, int *args)
+{
+ byte stack[OPSTACK_SIZE + 15];
+ void *entryPoint;
+ int programStack, stackOnEntry;
+ byte *image;
+ int *opStack;
+ int opStackOfs;
+ int arg;
+
+ currentVM = vm;
+
+ // interpret the code
+ vm->currentlyInterpreting = true;
+
+ // we might be called recursively, so this might not be the very top
+ programStack = stackOnEntry = vm->programStack;
+
+ // set up the stack frame
+ image = vm->dataBase;
+
+ programStack -= ( 8 + 4 * MAX_VMMAIN_ARGS );
+
+ for ( arg = 0; arg < MAX_VMMAIN_ARGS; arg++ )
+ *(int *)&image[ programStack + 8 + arg * 4 ] = args[ arg ];
+
+ *(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 + vm->entryOfs;
+ opStack = (int*)PADP(stack, 16);
+ *opStack = 0xDEADBEEF;
+ opStackOfs = 0;
+
+#ifdef _MSC_VER
+ #if idx64
+ opStackOfs = qvmcall64(&programStack, opStack, vm->instructionPointers, vm->dataBase);
+ #else
+ __asm
+ {
+ pushad
+
+ mov esi, dword ptr programStack
+ mov edi, dword ptr opStack
+ mov ebx, dword ptr opStackOfs
+
+ call entryPoint
+
+ mov dword ptr opStackOfs, ebx
+ mov dword ptr opStack, edi
+ mov dword ptr programStack, esi
+
+ popad
+ }
+ #endif
+#elif idx64
+ __asm__ volatile(
+ "movq %5, %%rax\n"
+ "movq %3, %%r8\n"
+ "movq %4, %%r9\n"
+ "push %%r15\n"
+ "push %%r14\n"
+ "push %%r13\n"
+ "push %%r12\n"
+ "callq *%%rax\n"
+ "pop %%r12\n"
+ "pop %%r13\n"
+ "pop %%r14\n"
+ "pop %%r15\n"
+ : "+S" (programStack), "+D" (opStack), "+b" (opStackOfs)
+ : "g" (vm->instructionPointers), "g" (vm->dataBase), "g" (entryPoint)
+ : "cc", "memory", "%rax", "%rcx", "%rdx", "%r8", "%r9", "%r10", "%r11"
+ );
+#else
+ __asm__ volatile(
+ "calll *%3\n"
+ : "+S" (programStack), "+D" (opStack), "+b" (opStackOfs)
+ : "g" (entryPoint)
+ : "cc", "memory", "%eax", "%ecx", "%edx"
+ );
+#endif
+
+ if(opStackOfs != 1 || *opStack != 0xDEADBEEF)
+ {
+ Com_Error(ERR_DROP, "opStack corrupted in compiled code");
+ }
+ if(programStack != stackOnEntry - (8 + 4 * MAX_VMMAIN_ARGS))
+ Com_Error(ERR_DROP, "programStack corrupted in compiled code");
+
+ vm->programStack = stackOnEntry;
+
+ return opStack[opStackOfs];
+}