diff options
author | IronClawTrem <louie.nutman@gmail.com> | 2020-02-16 03:40:06 +0000 |
---|---|---|
committer | IronClawTrem <louie.nutman@gmail.com> | 2020-02-16 03:40:06 +0000 |
commit | 425decdf7e9284d15aa726e3ae96b9942fb0e3ea (patch) | |
tree | 6c0dd7edfefff1be7b9e75fe0b3a0a85fe1595f3 /src/qcommon | |
parent | ccb0b2e4d6674a7a00c9bf491f08fc73b6898c54 (diff) |
create tremded branch
Diffstat (limited to 'src/qcommon')
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]; +} |