The Quake III Arena sources as originally released under the GPL license on August 20, 2005.
This commit is contained in:
commit
dbe4ddb103
1409 changed files with 806066 additions and 0 deletions
839
code/qcommon/cm_load.c
Normal file
839
code/qcommon/cm_load.c
Normal file
|
@ -0,0 +1,839 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code 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 Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
// cmodel.c -- model loading
|
||||
|
||||
#include "cm_local.h"
|
||||
|
||||
#ifdef BSPC
|
||||
|
||||
#include "../bspc/l_qfiles.h"
|
||||
|
||||
void SetPlaneSignbits (cplane_t *out) {
|
||||
int bits, j;
|
||||
|
||||
// for fast box on planeside test
|
||||
bits = 0;
|
||||
for (j=0 ; j<3 ; j++) {
|
||||
if (out->normal[j] < 0) {
|
||||
bits |= 1<<j;
|
||||
}
|
||||
}
|
||||
out->signbits = bits;
|
||||
}
|
||||
#endif //BSPC
|
||||
|
||||
// to allow boxes to be treated as brush models, we allocate
|
||||
// some extra indexes along with those needed by the map
|
||||
#define BOX_BRUSHES 1
|
||||
#define BOX_SIDES 6
|
||||
#define BOX_LEAFS 2
|
||||
#define BOX_PLANES 12
|
||||
|
||||
#define LL(x) x=LittleLong(x)
|
||||
|
||||
|
||||
clipMap_t cm;
|
||||
int c_pointcontents;
|
||||
int c_traces, c_brush_traces, c_patch_traces;
|
||||
|
||||
|
||||
byte *cmod_base;
|
||||
|
||||
#ifndef BSPC
|
||||
cvar_t *cm_noAreas;
|
||||
cvar_t *cm_noCurves;
|
||||
cvar_t *cm_playerCurveClip;
|
||||
#endif
|
||||
|
||||
cmodel_t box_model;
|
||||
cplane_t *box_planes;
|
||||
cbrush_t *box_brush;
|
||||
|
||||
|
||||
|
||||
void CM_InitBoxHull (void);
|
||||
void CM_FloodAreaConnections (void);
|
||||
|
||||
|
||||
/*
|
||||
===============================================================================
|
||||
|
||||
MAP LOADING
|
||||
|
||||
===============================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
=================
|
||||
CMod_LoadShaders
|
||||
=================
|
||||
*/
|
||||
void CMod_LoadShaders( lump_t *l ) {
|
||||
dshader_t *in, *out;
|
||||
int i, count;
|
||||
|
||||
in = (void *)(cmod_base + l->fileofs);
|
||||
if (l->filelen % sizeof(*in)) {
|
||||
Com_Error (ERR_DROP, "CMod_LoadShaders: funny lump size");
|
||||
}
|
||||
count = l->filelen / sizeof(*in);
|
||||
|
||||
if (count < 1) {
|
||||
Com_Error (ERR_DROP, "Map with no shaders");
|
||||
}
|
||||
cm.shaders = Hunk_Alloc( count * sizeof( *cm.shaders ), h_high );
|
||||
cm.numShaders = count;
|
||||
|
||||
Com_Memcpy( cm.shaders, in, count * sizeof( *cm.shaders ) );
|
||||
|
||||
out = cm.shaders;
|
||||
for ( i=0 ; i<count ; i++, in++, out++ ) {
|
||||
out->contentFlags = LittleLong( out->contentFlags );
|
||||
out->surfaceFlags = LittleLong( out->surfaceFlags );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
CMod_LoadSubmodels
|
||||
=================
|
||||
*/
|
||||
void CMod_LoadSubmodels( lump_t *l ) {
|
||||
dmodel_t *in;
|
||||
cmodel_t *out;
|
||||
int i, j, count;
|
||||
int *indexes;
|
||||
|
||||
in = (void *)(cmod_base + l->fileofs);
|
||||
if (l->filelen % sizeof(*in))
|
||||
Com_Error (ERR_DROP, "CMod_LoadSubmodels: funny lump size");
|
||||
count = l->filelen / sizeof(*in);
|
||||
|
||||
if (count < 1)
|
||||
Com_Error (ERR_DROP, "Map with no models");
|
||||
cm.cmodels = Hunk_Alloc( count * sizeof( *cm.cmodels ), h_high );
|
||||
cm.numSubModels = count;
|
||||
|
||||
if ( count > MAX_SUBMODELS ) {
|
||||
Com_Error( ERR_DROP, "MAX_SUBMODELS exceeded" );
|
||||
}
|
||||
|
||||
for ( i=0 ; i<count ; i++, in++, out++)
|
||||
{
|
||||
out = &cm.cmodels[i];
|
||||
|
||||
for (j=0 ; j<3 ; j++)
|
||||
{ // spread the mins / maxs by a pixel
|
||||
out->mins[j] = LittleFloat (in->mins[j]) - 1;
|
||||
out->maxs[j] = LittleFloat (in->maxs[j]) + 1;
|
||||
}
|
||||
|
||||
if ( i == 0 ) {
|
||||
continue; // world model doesn't need other info
|
||||
}
|
||||
|
||||
// make a "leaf" just to hold the model's brushes and surfaces
|
||||
out->leaf.numLeafBrushes = LittleLong( in->numBrushes );
|
||||
indexes = Hunk_Alloc( out->leaf.numLeafBrushes * 4, h_high );
|
||||
out->leaf.firstLeafBrush = indexes - cm.leafbrushes;
|
||||
for ( j = 0 ; j < out->leaf.numLeafBrushes ; j++ ) {
|
||||
indexes[j] = LittleLong( in->firstBrush ) + j;
|
||||
}
|
||||
|
||||
out->leaf.numLeafSurfaces = LittleLong( in->numSurfaces );
|
||||
indexes = Hunk_Alloc( out->leaf.numLeafSurfaces * 4, h_high );
|
||||
out->leaf.firstLeafSurface = indexes - cm.leafsurfaces;
|
||||
for ( j = 0 ; j < out->leaf.numLeafSurfaces ; j++ ) {
|
||||
indexes[j] = LittleLong( in->firstSurface ) + j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
CMod_LoadNodes
|
||||
|
||||
=================
|
||||
*/
|
||||
void CMod_LoadNodes( lump_t *l ) {
|
||||
dnode_t *in;
|
||||
int child;
|
||||
cNode_t *out;
|
||||
int i, j, count;
|
||||
|
||||
in = (void *)(cmod_base + l->fileofs);
|
||||
if (l->filelen % sizeof(*in))
|
||||
Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size");
|
||||
count = l->filelen / sizeof(*in);
|
||||
|
||||
if (count < 1)
|
||||
Com_Error (ERR_DROP, "Map has no nodes");
|
||||
cm.nodes = Hunk_Alloc( count * sizeof( *cm.nodes ), h_high );
|
||||
cm.numNodes = count;
|
||||
|
||||
out = cm.nodes;
|
||||
|
||||
for (i=0 ; i<count ; i++, out++, in++)
|
||||
{
|
||||
out->plane = cm.planes + LittleLong( in->planeNum );
|
||||
for (j=0 ; j<2 ; j++)
|
||||
{
|
||||
child = LittleLong (in->children[j]);
|
||||
out->children[j] = child;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
CM_BoundBrush
|
||||
|
||||
=================
|
||||
*/
|
||||
void CM_BoundBrush( cbrush_t *b ) {
|
||||
b->bounds[0][0] = -b->sides[0].plane->dist;
|
||||
b->bounds[1][0] = b->sides[1].plane->dist;
|
||||
|
||||
b->bounds[0][1] = -b->sides[2].plane->dist;
|
||||
b->bounds[1][1] = b->sides[3].plane->dist;
|
||||
|
||||
b->bounds[0][2] = -b->sides[4].plane->dist;
|
||||
b->bounds[1][2] = b->sides[5].plane->dist;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
CMod_LoadBrushes
|
||||
|
||||
=================
|
||||
*/
|
||||
void CMod_LoadBrushes( lump_t *l ) {
|
||||
dbrush_t *in;
|
||||
cbrush_t *out;
|
||||
int i, count;
|
||||
|
||||
in = (void *)(cmod_base + l->fileofs);
|
||||
if (l->filelen % sizeof(*in)) {
|
||||
Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size");
|
||||
}
|
||||
count = l->filelen / sizeof(*in);
|
||||
|
||||
cm.brushes = Hunk_Alloc( ( BOX_BRUSHES + count ) * sizeof( *cm.brushes ), h_high );
|
||||
cm.numBrushes = count;
|
||||
|
||||
out = cm.brushes;
|
||||
|
||||
for ( i=0 ; i<count ; i++, out++, in++ ) {
|
||||
out->sides = cm.brushsides + LittleLong(in->firstSide);
|
||||
out->numsides = LittleLong(in->numSides);
|
||||
|
||||
out->shaderNum = LittleLong( in->shaderNum );
|
||||
if ( out->shaderNum < 0 || out->shaderNum >= cm.numShaders ) {
|
||||
Com_Error( ERR_DROP, "CMod_LoadBrushes: bad shaderNum: %i", out->shaderNum );
|
||||
}
|
||||
out->contents = cm.shaders[out->shaderNum].contentFlags;
|
||||
|
||||
CM_BoundBrush( out );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
CMod_LoadLeafs
|
||||
=================
|
||||
*/
|
||||
void CMod_LoadLeafs (lump_t *l)
|
||||
{
|
||||
int i;
|
||||
cLeaf_t *out;
|
||||
dleaf_t *in;
|
||||
int count;
|
||||
|
||||
in = (void *)(cmod_base + l->fileofs);
|
||||
if (l->filelen % sizeof(*in))
|
||||
Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size");
|
||||
count = l->filelen / sizeof(*in);
|
||||
|
||||
if (count < 1)
|
||||
Com_Error (ERR_DROP, "Map with no leafs");
|
||||
|
||||
cm.leafs = Hunk_Alloc( ( BOX_LEAFS + count ) * sizeof( *cm.leafs ), h_high );
|
||||
cm.numLeafs = count;
|
||||
|
||||
out = cm.leafs;
|
||||
for ( i=0 ; i<count ; i++, in++, out++)
|
||||
{
|
||||
out->cluster = LittleLong (in->cluster);
|
||||
out->area = LittleLong (in->area);
|
||||
out->firstLeafBrush = LittleLong (in->firstLeafBrush);
|
||||
out->numLeafBrushes = LittleLong (in->numLeafBrushes);
|
||||
out->firstLeafSurface = LittleLong (in->firstLeafSurface);
|
||||
out->numLeafSurfaces = LittleLong (in->numLeafSurfaces);
|
||||
|
||||
if (out->cluster >= cm.numClusters)
|
||||
cm.numClusters = out->cluster + 1;
|
||||
if (out->area >= cm.numAreas)
|
||||
cm.numAreas = out->area + 1;
|
||||
}
|
||||
|
||||
cm.areas = Hunk_Alloc( cm.numAreas * sizeof( *cm.areas ), h_high );
|
||||
cm.areaPortals = Hunk_Alloc( cm.numAreas * cm.numAreas * sizeof( *cm.areaPortals ), h_high );
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
CMod_LoadPlanes
|
||||
=================
|
||||
*/
|
||||
void CMod_LoadPlanes (lump_t *l)
|
||||
{
|
||||
int i, j;
|
||||
cplane_t *out;
|
||||
dplane_t *in;
|
||||
int count;
|
||||
int bits;
|
||||
|
||||
in = (void *)(cmod_base + l->fileofs);
|
||||
if (l->filelen % sizeof(*in))
|
||||
Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size");
|
||||
count = l->filelen / sizeof(*in);
|
||||
|
||||
if (count < 1)
|
||||
Com_Error (ERR_DROP, "Map with no planes");
|
||||
cm.planes = Hunk_Alloc( ( BOX_PLANES + count ) * sizeof( *cm.planes ), h_high );
|
||||
cm.numPlanes = count;
|
||||
|
||||
out = cm.planes;
|
||||
|
||||
for ( i=0 ; i<count ; i++, in++, out++)
|
||||
{
|
||||
bits = 0;
|
||||
for (j=0 ; j<3 ; j++)
|
||||
{
|
||||
out->normal[j] = LittleFloat (in->normal[j]);
|
||||
if (out->normal[j] < 0)
|
||||
bits |= 1<<j;
|
||||
}
|
||||
|
||||
out->dist = LittleFloat (in->dist);
|
||||
out->type = PlaneTypeForNormal( out->normal );
|
||||
out->signbits = bits;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
CMod_LoadLeafBrushes
|
||||
=================
|
||||
*/
|
||||
void CMod_LoadLeafBrushes (lump_t *l)
|
||||
{
|
||||
int i;
|
||||
int *out;
|
||||
int *in;
|
||||
int count;
|
||||
|
||||
in = (void *)(cmod_base + l->fileofs);
|
||||
if (l->filelen % sizeof(*in))
|
||||
Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size");
|
||||
count = l->filelen / sizeof(*in);
|
||||
|
||||
cm.leafbrushes = Hunk_Alloc( (count + BOX_BRUSHES) * sizeof( *cm.leafbrushes ), h_high );
|
||||
cm.numLeafBrushes = count;
|
||||
|
||||
out = cm.leafbrushes;
|
||||
|
||||
for ( i=0 ; i<count ; i++, in++, out++) {
|
||||
*out = LittleLong (*in);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
CMod_LoadLeafSurfaces
|
||||
=================
|
||||
*/
|
||||
void CMod_LoadLeafSurfaces( lump_t *l )
|
||||
{
|
||||
int i;
|
||||
int *out;
|
||||
int *in;
|
||||
int count;
|
||||
|
||||
in = (void *)(cmod_base + l->fileofs);
|
||||
if (l->filelen % sizeof(*in))
|
||||
Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size");
|
||||
count = l->filelen / sizeof(*in);
|
||||
|
||||
cm.leafsurfaces = Hunk_Alloc( count * sizeof( *cm.leafsurfaces ), h_high );
|
||||
cm.numLeafSurfaces = count;
|
||||
|
||||
out = cm.leafsurfaces;
|
||||
|
||||
for ( i=0 ; i<count ; i++, in++, out++) {
|
||||
*out = LittleLong (*in);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
CMod_LoadBrushSides
|
||||
=================
|
||||
*/
|
||||
void CMod_LoadBrushSides (lump_t *l)
|
||||
{
|
||||
int i;
|
||||
cbrushside_t *out;
|
||||
dbrushside_t *in;
|
||||
int count;
|
||||
int num;
|
||||
|
||||
in = (void *)(cmod_base + l->fileofs);
|
||||
if ( l->filelen % sizeof(*in) ) {
|
||||
Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size");
|
||||
}
|
||||
count = l->filelen / sizeof(*in);
|
||||
|
||||
cm.brushsides = Hunk_Alloc( ( BOX_SIDES + count ) * sizeof( *cm.brushsides ), h_high );
|
||||
cm.numBrushSides = count;
|
||||
|
||||
out = cm.brushsides;
|
||||
|
||||
for ( i=0 ; i<count ; i++, in++, out++) {
|
||||
num = LittleLong( in->planeNum );
|
||||
out->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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
CMod_LoadEntityString
|
||||
=================
|
||||
*/
|
||||
void CMod_LoadEntityString( lump_t *l ) {
|
||||
cm.entityString = Hunk_Alloc( l->filelen, h_high );
|
||||
cm.numEntityChars = l->filelen;
|
||||
Com_Memcpy (cm.entityString, cmod_base + l->fileofs, l->filelen);
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
CMod_LoadVisibility
|
||||
=================
|
||||
*/
|
||||
#define VIS_HEADER 8
|
||||
void CMod_LoadVisibility( lump_t *l ) {
|
||||
int len;
|
||||
byte *buf;
|
||||
|
||||
len = l->filelen;
|
||||
if ( !len ) {
|
||||
cm.clusterBytes = ( cm.numClusters + 31 ) & ~31;
|
||||
cm.visibility = Hunk_Alloc( cm.clusterBytes, h_high );
|
||||
Com_Memset( cm.visibility, 255, cm.clusterBytes );
|
||||
return;
|
||||
}
|
||||
buf = cmod_base + l->fileofs;
|
||||
|
||||
cm.vised = qtrue;
|
||||
cm.visibility = Hunk_Alloc( len, h_high );
|
||||
cm.numClusters = LittleLong( ((int *)buf)[0] );
|
||||
cm.clusterBytes = LittleLong( ((int *)buf)[1] );
|
||||
Com_Memcpy (cm.visibility, buf + VIS_HEADER, len - VIS_HEADER );
|
||||
}
|
||||
|
||||
//==================================================================
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
CMod_LoadPatches
|
||||
=================
|
||||
*/
|
||||
#define MAX_PATCH_VERTS 1024
|
||||
void CMod_LoadPatches( lump_t *surfs, lump_t *verts ) {
|
||||
drawVert_t *dv, *dv_p;
|
||||
dsurface_t *in;
|
||||
int count;
|
||||
int i, j;
|
||||
int c;
|
||||
cPatch_t *patch;
|
||||
vec3_t points[MAX_PATCH_VERTS];
|
||||
int width, height;
|
||||
int shaderNum;
|
||||
|
||||
in = (void *)(cmod_base + surfs->fileofs);
|
||||
if (surfs->filelen % sizeof(*in))
|
||||
Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size");
|
||||
cm.numSurfaces = count = surfs->filelen / sizeof(*in);
|
||||
cm.surfaces = Hunk_Alloc( cm.numSurfaces * sizeof( cm.surfaces[0] ), h_high );
|
||||
|
||||
dv = (void *)(cmod_base + verts->fileofs);
|
||||
if (verts->filelen % sizeof(*dv))
|
||||
Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size");
|
||||
|
||||
// scan through all the surfaces, but only load patches,
|
||||
// not planar faces
|
||||
for ( i = 0 ; i < count ; i++, in++ ) {
|
||||
if ( LittleLong( in->surfaceType ) != MST_PATCH ) {
|
||||
continue; // ignore other surfaces
|
||||
}
|
||||
// FIXME: check for non-colliding patches
|
||||
|
||||
cm.surfaces[ i ] = patch = Hunk_Alloc( sizeof( *patch ), h_high );
|
||||
|
||||
// load the full drawverts onto the stack
|
||||
width = LittleLong( in->patchWidth );
|
||||
height = LittleLong( in->patchHeight );
|
||||
c = width * height;
|
||||
if ( c > MAX_PATCH_VERTS ) {
|
||||
Com_Error( ERR_DROP, "ParseMesh: MAX_PATCH_VERTS" );
|
||||
}
|
||||
|
||||
dv_p = dv + LittleLong( in->firstVert );
|
||||
for ( j = 0 ; j < c ; j++, dv_p++ ) {
|
||||
points[j][0] = LittleFloat( dv_p->xyz[0] );
|
||||
points[j][1] = LittleFloat( dv_p->xyz[1] );
|
||||
points[j][2] = LittleFloat( dv_p->xyz[2] );
|
||||
}
|
||||
|
||||
shaderNum = LittleLong( in->shaderNum );
|
||||
patch->contents = cm.shaders[shaderNum].contentFlags;
|
||||
patch->surfaceFlags = cm.shaders[shaderNum].surfaceFlags;
|
||||
|
||||
// create the internal facet structure
|
||||
patch->pc = CM_GeneratePatchCollide( width, height, points );
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================
|
||||
|
||||
unsigned CM_LumpChecksum(lump_t *lump) {
|
||||
return LittleLong (Com_BlockChecksum (cmod_base + lump->fileofs, lump->filelen));
|
||||
}
|
||||
|
||||
unsigned CM_Checksum(dheader_t *header) {
|
||||
unsigned checksums[16];
|
||||
checksums[0] = CM_LumpChecksum(&header->lumps[LUMP_SHADERS]);
|
||||
checksums[1] = CM_LumpChecksum(&header->lumps[LUMP_LEAFS]);
|
||||
checksums[2] = CM_LumpChecksum(&header->lumps[LUMP_LEAFBRUSHES]);
|
||||
checksums[3] = CM_LumpChecksum(&header->lumps[LUMP_LEAFSURFACES]);
|
||||
checksums[4] = CM_LumpChecksum(&header->lumps[LUMP_PLANES]);
|
||||
checksums[5] = CM_LumpChecksum(&header->lumps[LUMP_BRUSHSIDES]);
|
||||
checksums[6] = CM_LumpChecksum(&header->lumps[LUMP_BRUSHES]);
|
||||
checksums[7] = CM_LumpChecksum(&header->lumps[LUMP_MODELS]);
|
||||
checksums[8] = CM_LumpChecksum(&header->lumps[LUMP_NODES]);
|
||||
checksums[9] = CM_LumpChecksum(&header->lumps[LUMP_SURFACES]);
|
||||
checksums[10] = CM_LumpChecksum(&header->lumps[LUMP_DRAWVERTS]);
|
||||
|
||||
return LittleLong(Com_BlockChecksum(checksums, 11 * 4));
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
CM_LoadMap
|
||||
|
||||
Loads in the map and all submodels
|
||||
==================
|
||||
*/
|
||||
void CM_LoadMap( const char *name, qboolean clientload, int *checksum ) {
|
||||
int *buf;
|
||||
int i;
|
||||
dheader_t header;
|
||||
int length;
|
||||
static unsigned last_checksum;
|
||||
|
||||
if ( !name || !name[0] ) {
|
||||
Com_Error( ERR_DROP, "CM_LoadMap: NULL name" );
|
||||
}
|
||||
|
||||
#ifndef BSPC
|
||||
cm_noAreas = Cvar_Get ("cm_noAreas", "0", CVAR_CHEAT);
|
||||
cm_noCurves = Cvar_Get ("cm_noCurves", "0", CVAR_CHEAT);
|
||||
cm_playerCurveClip = Cvar_Get ("cm_playerCurveClip", "1", CVAR_ARCHIVE|CVAR_CHEAT );
|
||||
#endif
|
||||
Com_DPrintf( "CM_LoadMap( %s, %i )\n", name, clientload );
|
||||
|
||||
if ( !strcmp( cm.name, name ) && clientload ) {
|
||||
*checksum = last_checksum;
|
||||
return;
|
||||
}
|
||||
|
||||
// free old stuff
|
||||
Com_Memset( &cm, 0, sizeof( cm ) );
|
||||
CM_ClearLevelPatches();
|
||||
|
||||
if ( !name[0] ) {
|
||||
cm.numLeafs = 1;
|
||||
cm.numClusters = 1;
|
||||
cm.numAreas = 1;
|
||||
cm.cmodels = Hunk_Alloc( sizeof( *cm.cmodels ), h_high );
|
||||
*checksum = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// load the file
|
||||
//
|
||||
#ifndef BSPC
|
||||
length = FS_ReadFile( name, (void **)&buf );
|
||||
#else
|
||||
length = LoadQuakeFile((quakefile_t *) name, (void **)&buf);
|
||||
#endif
|
||||
|
||||
if ( !buf ) {
|
||||
Com_Error (ERR_DROP, "Couldn't load %s", name);
|
||||
}
|
||||
|
||||
last_checksum = LittleLong (Com_BlockChecksum (buf, length));
|
||||
*checksum = last_checksum;
|
||||
|
||||
header = *(dheader_t *)buf;
|
||||
for (i=0 ; i<sizeof(dheader_t)/4 ; i++) {
|
||||
((int *)&header)[i] = LittleLong ( ((int *)&header)[i]);
|
||||
}
|
||||
|
||||
if ( header.version != BSP_VERSION ) {
|
||||
Com_Error (ERR_DROP, "CM_LoadMap: %s has wrong version number (%i should be %i)"
|
||||
, name, header.version, BSP_VERSION );
|
||||
}
|
||||
|
||||
cmod_base = (byte *)buf;
|
||||
|
||||
// load into heap
|
||||
CMod_LoadShaders( &header.lumps[LUMP_SHADERS] );
|
||||
CMod_LoadLeafs (&header.lumps[LUMP_LEAFS]);
|
||||
CMod_LoadLeafBrushes (&header.lumps[LUMP_LEAFBRUSHES]);
|
||||
CMod_LoadLeafSurfaces (&header.lumps[LUMP_LEAFSURFACES]);
|
||||
CMod_LoadPlanes (&header.lumps[LUMP_PLANES]);
|
||||
CMod_LoadBrushSides (&header.lumps[LUMP_BRUSHSIDES]);
|
||||
CMod_LoadBrushes (&header.lumps[LUMP_BRUSHES]);
|
||||
CMod_LoadSubmodels (&header.lumps[LUMP_MODELS]);
|
||||
CMod_LoadNodes (&header.lumps[LUMP_NODES]);
|
||||
CMod_LoadEntityString (&header.lumps[LUMP_ENTITIES]);
|
||||
CMod_LoadVisibility( &header.lumps[LUMP_VISIBILITY] );
|
||||
CMod_LoadPatches( &header.lumps[LUMP_SURFACES], &header.lumps[LUMP_DRAWVERTS] );
|
||||
|
||||
// we are NOT freeing the file, because it is cached for the ref
|
||||
FS_FreeFile (buf);
|
||||
|
||||
CM_InitBoxHull ();
|
||||
|
||||
CM_FloodAreaConnections ();
|
||||
|
||||
// allow this to be cached if it is loaded by the server
|
||||
if ( !clientload ) {
|
||||
Q_strncpyz( cm.name, name, sizeof( cm.name ) );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
CM_ClearMap
|
||||
==================
|
||||
*/
|
||||
void CM_ClearMap( void ) {
|
||||
Com_Memset( &cm, 0, sizeof( cm ) );
|
||||
CM_ClearLevelPatches();
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
CM_ClipHandleToModel
|
||||
==================
|
||||
*/
|
||||
cmodel_t *CM_ClipHandleToModel( clipHandle_t handle ) {
|
||||
if ( handle < 0 ) {
|
||||
Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i", handle );
|
||||
}
|
||||
if ( handle < cm.numSubModels ) {
|
||||
return &cm.cmodels[handle];
|
||||
}
|
||||
if ( handle == BOX_MODEL_HANDLE ) {
|
||||
return &box_model;
|
||||
}
|
||||
if ( handle < MAX_SUBMODELS ) {
|
||||
Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i < %i < %i",
|
||||
cm.numSubModels, handle, MAX_SUBMODELS );
|
||||
}
|
||||
Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i", handle + MAX_SUBMODELS );
|
||||
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
CM_InlineModel
|
||||
==================
|
||||
*/
|
||||
clipHandle_t CM_InlineModel( int index ) {
|
||||
if ( index < 0 || index >= cm.numSubModels ) {
|
||||
Com_Error (ERR_DROP, "CM_InlineModel: bad number");
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
int CM_NumClusters( void ) {
|
||||
return cm.numClusters;
|
||||
}
|
||||
|
||||
int CM_NumInlineModels( void ) {
|
||||
return cm.numSubModels;
|
||||
}
|
||||
|
||||
char *CM_EntityString( void ) {
|
||||
return cm.entityString;
|
||||
}
|
||||
|
||||
int CM_LeafCluster( int leafnum ) {
|
||||
if (leafnum < 0 || leafnum >= cm.numLeafs) {
|
||||
Com_Error (ERR_DROP, "CM_LeafCluster: bad number");
|
||||
}
|
||||
return cm.leafs[leafnum].cluster;
|
||||
}
|
||||
|
||||
int CM_LeafArea( int leafnum ) {
|
||||
if ( leafnum < 0 || leafnum >= cm.numLeafs ) {
|
||||
Com_Error (ERR_DROP, "CM_LeafArea: bad number");
|
||||
}
|
||||
return cm.leafs[leafnum].area;
|
||||
}
|
||||
|
||||
//=======================================================================
|
||||
|
||||
|
||||
/*
|
||||
===================
|
||||
CM_InitBoxHull
|
||||
|
||||
Set up the planes and nodes so that the six floats of a bounding box
|
||||
can just be stored out and get a proper clipping hull structure.
|
||||
===================
|
||||
*/
|
||||
void CM_InitBoxHull (void)
|
||||
{
|
||||
int i;
|
||||
int side;
|
||||
cplane_t *p;
|
||||
cbrushside_t *s;
|
||||
|
||||
box_planes = &cm.planes[cm.numPlanes];
|
||||
|
||||
box_brush = &cm.brushes[cm.numBrushes];
|
||||
box_brush->numsides = 6;
|
||||
box_brush->sides = cm.brushsides + cm.numBrushSides;
|
||||
box_brush->contents = CONTENTS_BODY;
|
||||
|
||||
box_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];
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
|
194
code/qcommon/cm_local.h
Normal file
194
code/qcommon/cm_local.h
Normal file
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code 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 Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
#include "../game/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 {
|
||||
cplane_t *plane;
|
||||
int surfaceFlags;
|
||||
int shaderNum;
|
||||
} 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
|
||||
} cbrush_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
int checkcount; // to avoid repeated testings
|
||||
int surfaceFlags;
|
||||
int contents;
|
||||
struct patchCollide_s *pc;
|
||||
} cPatch_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
int floodnum;
|
||||
int floodvalid;
|
||||
} cArea_t;
|
||||
|
||||
typedef struct {
|
||||
char name[MAX_QPATH];
|
||||
|
||||
int numShaders;
|
||||
dshader_t *shaders;
|
||||
|
||||
int numBrushSides;
|
||||
cbrushside_t *brushsides;
|
||||
|
||||
int numPlanes;
|
||||
cplane_t *planes;
|
||||
|
||||
int numNodes;
|
||||
cNode_t *nodes;
|
||||
|
||||
int numLeafs;
|
||||
cLeaf_t *leafs;
|
||||
|
||||
int numLeafBrushes;
|
||||
int *leafbrushes;
|
||||
|
||||
int numLeafSurfaces;
|
||||
int *leafsurfaces;
|
||||
|
||||
int numSubModels;
|
||||
cmodel_t *cmodels;
|
||||
|
||||
int numBrushes;
|
||||
cbrush_t *brushes;
|
||||
|
||||
int numClusters;
|
||||
int clusterBytes;
|
||||
byte *visibility;
|
||||
qboolean vised; // if false, visibility is just a single cluster of ffs
|
||||
|
||||
int numEntityChars;
|
||||
char *entityString;
|
||||
|
||||
int numAreas;
|
||||
cArea_t *areas;
|
||||
int *areaPortals; // [ numAreas*numAreas ] reference counts
|
||||
|
||||
int numSurfaces;
|
||||
cPatch_t **surfaces; // non-patches will be NULL
|
||||
|
||||
int floodvalid;
|
||||
int checkcount; // incremented on each trace
|
||||
} clipMap_t;
|
||||
|
||||
|
||||
// keep 1/8 unit away to keep the position valid before network snapping
|
||||
// and to avoid various numeric issues
|
||||
#define SURFACE_CLIP_EPSILON (0.125)
|
||||
|
||||
extern clipMap_t cm;
|
||||
extern int c_pointcontents;
|
||||
extern int c_traces, c_brush_traces, c_patch_traces;
|
||||
extern cvar_t *cm_noAreas;
|
||||
extern cvar_t *cm_noCurves;
|
||||
extern cvar_t *cm_playerCurveClip;
|
||||
|
||||
// cm_test.c
|
||||
|
||||
// Used for oriented capsule collision detection
|
||||
typedef struct
|
||||
{
|
||||
qboolean use;
|
||||
float radius;
|
||||
float halfheight;
|
||||
vec3_t offset;
|
||||
} sphere_t;
|
||||
|
||||
typedef struct {
|
||||
vec3_t start;
|
||||
vec3_t end;
|
||||
vec3_t size[2]; // size of the box being swept through the model
|
||||
vec3_t offsets[8]; // [signbits][x] = either size[0][x] or size[1][x]
|
||||
float maxOffset; // longest corner length from origin
|
||||
vec3_t extents; // greatest of abs(size[0]) and abs(size[1])
|
||||
vec3_t bounds[2]; // enclosing box of start and end surrounding by size
|
||||
vec3_t modelOrigin;// origin of the model tracing through
|
||||
int contents; // ored contents of the model tracing through
|
||||
qboolean isPoint; // optimized case
|
||||
trace_t trace; // returned from trace call
|
||||
sphere_t sphere; // sphere for oriendted capsule collision
|
||||
} traceWork_t;
|
||||
|
||||
typedef struct leafList_s {
|
||||
int count;
|
||||
int maxcount;
|
||||
qboolean overflowed;
|
||||
int *list;
|
||||
vec3_t bounds[2];
|
||||
int lastLeaf; // for overflows where each leaf can't be stored individually
|
||||
void (*storeLeafs)( struct leafList_s *ll, int nodenum );
|
||||
} leafList_t;
|
||||
|
||||
|
||||
int CM_BoxBrushes( const vec3_t mins, const vec3_t maxs, cbrush_t **list, int listsize );
|
||||
|
||||
void CM_StoreLeafs( leafList_t *ll, int nodenum );
|
||||
void CM_StoreBrushes( leafList_t *ll, int nodenum );
|
||||
|
||||
void CM_BoxLeafnums_r( leafList_t *ll, int nodenum );
|
||||
|
||||
cmodel_t *CM_ClipHandleToModel( clipHandle_t handle );
|
||||
|
||||
// cm_patch.c
|
||||
|
||||
struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points );
|
||||
void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc );
|
||||
qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc );
|
||||
void CM_ClearLevelPatches( void );
|
1771
code/qcommon/cm_patch.c
Normal file
1771
code/qcommon/cm_patch.c
Normal file
File diff suppressed because it is too large
Load diff
103
code/qcommon/cm_patch.h
Normal file
103
code/qcommon/cm_patch.h
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code 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 Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
//#define CULL_BBOX
|
||||
|
||||
/*
|
||||
|
||||
This file does not reference any globals, and has these entry points:
|
||||
|
||||
void CM_ClearLevelPatches( void );
|
||||
struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, const vec3_t *points );
|
||||
void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc );
|
||||
qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc );
|
||||
void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, flaot *points) );
|
||||
|
||||
|
||||
Issues for collision against curved surfaces:
|
||||
|
||||
Surface edges need to be handled differently than surface planes
|
||||
|
||||
Plane expansion causes raw surfaces to expand past expanded bounding box
|
||||
|
||||
Position test of a volume against a surface is tricky.
|
||||
|
||||
Position test of a point against a surface is not well defined, because the surface has no volume.
|
||||
|
||||
|
||||
Tracing leading edge points instead of volumes?
|
||||
Position test by tracing corner to corner? (8*7 traces -- ouch)
|
||||
|
||||
coplanar edges
|
||||
triangulated patches
|
||||
degenerate patches
|
||||
|
||||
endcaps
|
||||
degenerate
|
||||
|
||||
WARNING: this may misbehave with meshes that have rows or columns that only
|
||||
degenerate a few triangles. Completely degenerate rows and columns are handled
|
||||
properly.
|
||||
*/
|
||||
|
||||
|
||||
#define MAX_FACETS 1024
|
||||
#define MAX_PATCH_PLANES 2048
|
||||
|
||||
typedef struct {
|
||||
float plane[4];
|
||||
int signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision
|
||||
} patchPlane_t;
|
||||
|
||||
typedef struct {
|
||||
int surfacePlane;
|
||||
int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels
|
||||
int borderPlanes[4+6+16];
|
||||
int borderInward[4+6+16];
|
||||
qboolean borderNoAdjust[4+6+16];
|
||||
} facet_t;
|
||||
|
||||
typedef struct patchCollide_s {
|
||||
vec3_t bounds[2];
|
||||
int numPlanes; // surface planes plus edge planes
|
||||
patchPlane_t *planes;
|
||||
int numFacets;
|
||||
facet_t *facets;
|
||||
} patchCollide_t;
|
||||
|
||||
|
||||
#define MAX_GRID_SIZE 129
|
||||
|
||||
typedef struct {
|
||||
int width;
|
||||
int height;
|
||||
qboolean wrapWidth;
|
||||
qboolean wrapHeight;
|
||||
vec3_t points[MAX_GRID_SIZE][MAX_GRID_SIZE]; // [width][height]
|
||||
} cGrid_t;
|
||||
|
||||
#define SUBDIVIDE_DISTANCE 16 //4 // never more than this units away from curve
|
||||
#define PLANE_TRI_EPSILON 0.1
|
||||
#define WRAP_POINT_EPSILON 0.1
|
||||
|
||||
|
||||
struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points );
|
737
code/qcommon/cm_polylib.c
Normal file
737
code/qcommon/cm_polylib.c
Normal file
|
@ -0,0 +1,737 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code 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 Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
// this is only used for visualization tools in cm_ debug functions
|
||||
|
||||
|
||||
#include "cm_local.h"
|
||||
|
||||
|
||||
// counters are only bumped when running single threaded,
|
||||
// because they are an awefull coherence problem
|
||||
int c_active_windings;
|
||||
int c_peak_windings;
|
||||
int c_winding_allocs;
|
||||
int c_winding_points;
|
||||
|
||||
void pw(winding_t *w)
|
||||
{
|
||||
int i;
|
||||
for (i=0 ; i<w->numpoints ; i++)
|
||||
printf ("(%5.1f, %5.1f, %5.1f)\n",w->p[i][0], w->p[i][1],w->p[i][2]);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============
|
||||
AllocWinding
|
||||
=============
|
||||
*/
|
||||
winding_t *AllocWinding (int points)
|
||||
{
|
||||
winding_t *w;
|
||||
int s;
|
||||
|
||||
c_winding_allocs++;
|
||||
c_winding_points += points;
|
||||
c_active_windings++;
|
||||
if (c_active_windings > c_peak_windings)
|
||||
c_peak_windings = c_active_windings;
|
||||
|
||||
s = sizeof(vec_t)*3*points + sizeof(int);
|
||||
w = Z_Malloc (s);
|
||||
Com_Memset (w, 0, s);
|
||||
return w;
|
||||
}
|
||||
|
||||
void FreeWinding (winding_t *w)
|
||||
{
|
||||
if (*(unsigned *)w == 0xdeaddead)
|
||||
Com_Error (ERR_FATAL, "FreeWinding: freed a freed winding");
|
||||
*(unsigned *)w = 0xdeaddead;
|
||||
|
||||
c_active_windings--;
|
||||
Z_Free (w);
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
RemoveColinearPoints
|
||||
============
|
||||
*/
|
||||
int c_removed;
|
||||
|
||||
void RemoveColinearPoints (winding_t *w)
|
||||
{
|
||||
int i, j, k;
|
||||
vec3_t v1, v2;
|
||||
int nump;
|
||||
vec3_t p[MAX_POINTS_ON_WINDING];
|
||||
|
||||
nump = 0;
|
||||
for (i=0 ; i<w->numpoints ; i++)
|
||||
{
|
||||
j = (i+1)%w->numpoints;
|
||||
k = (i+w->numpoints-1)%w->numpoints;
|
||||
VectorSubtract (w->p[j], w->p[i], v1);
|
||||
VectorSubtract (w->p[i], w->p[k], v2);
|
||||
VectorNormalize2(v1,v1);
|
||||
VectorNormalize2(v2,v2);
|
||||
if (DotProduct(v1, v2) < 0.999)
|
||||
{
|
||||
VectorCopy (w->p[i], p[nump]);
|
||||
nump++;
|
||||
}
|
||||
}
|
||||
|
||||
if (nump == w->numpoints)
|
||||
return;
|
||||
|
||||
c_removed += w->numpoints - nump;
|
||||
w->numpoints = nump;
|
||||
Com_Memcpy (w->p, p, nump*sizeof(p[0]));
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
WindingPlane
|
||||
============
|
||||
*/
|
||||
void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist)
|
||||
{
|
||||
vec3_t v1, v2;
|
||||
|
||||
VectorSubtract (w->p[1], w->p[0], v1);
|
||||
VectorSubtract (w->p[2], w->p[0], v2);
|
||||
CrossProduct (v2, v1, normal);
|
||||
VectorNormalize2(normal, normal);
|
||||
*dist = DotProduct (w->p[0], normal);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
WindingArea
|
||||
=============
|
||||
*/
|
||||
vec_t WindingArea (winding_t *w)
|
||||
{
|
||||
int i;
|
||||
vec3_t d1, d2, cross;
|
||||
vec_t total;
|
||||
|
||||
total = 0;
|
||||
for (i=2 ; i<w->numpoints ; i++)
|
||||
{
|
||||
VectorSubtract (w->p[i-1], w->p[0], d1);
|
||||
VectorSubtract (w->p[i], w->p[0], d2);
|
||||
CrossProduct (d1, d2, cross);
|
||||
total += 0.5 * VectorLength ( cross );
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
WindingBounds
|
||||
=============
|
||||
*/
|
||||
void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs)
|
||||
{
|
||||
vec_t v;
|
||||
int i,j;
|
||||
|
||||
mins[0] = mins[1] = mins[2] = MAX_MAP_BOUNDS;
|
||||
maxs[0] = maxs[1] = maxs[2] = -MAX_MAP_BOUNDS;
|
||||
|
||||
for (i=0 ; i<w->numpoints ; i++)
|
||||
{
|
||||
for (j=0 ; j<3 ; j++)
|
||||
{
|
||||
v = w->p[i][j];
|
||||
if (v < mins[j])
|
||||
mins[j] = v;
|
||||
if (v > maxs[j])
|
||||
maxs[j] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
WindingCenter
|
||||
=============
|
||||
*/
|
||||
void WindingCenter (winding_t *w, vec3_t center)
|
||||
{
|
||||
int i;
|
||||
float scale;
|
||||
|
||||
VectorCopy (vec3_origin, center);
|
||||
for (i=0 ; i<w->numpoints ; i++)
|
||||
VectorAdd (w->p[i], center, center);
|
||||
|
||||
scale = 1.0/w->numpoints;
|
||||
VectorScale (center, scale, center);
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
BaseWindingForPlane
|
||||
=================
|
||||
*/
|
||||
winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist)
|
||||
{
|
||||
int i, x;
|
||||
vec_t max, v;
|
||||
vec3_t org, vright, vup;
|
||||
winding_t *w;
|
||||
|
||||
// find the major axis
|
||||
|
||||
max = -MAX_MAP_BOUNDS;
|
||||
x = -1;
|
||||
for (i=0 ; i<3; i++)
|
||||
{
|
||||
v = fabs(normal[i]);
|
||||
if (v > max)
|
||||
{
|
||||
x = i;
|
||||
max = v;
|
||||
}
|
||||
}
|
||||
if (x==-1)
|
||||
Com_Error (ERR_DROP, "BaseWindingForPlane: no axis found");
|
||||
|
||||
VectorCopy (vec3_origin, vup);
|
||||
switch (x)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
vup[2] = 1;
|
||||
break;
|
||||
case 2:
|
||||
vup[0] = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
v = DotProduct (vup, normal);
|
||||
VectorMA (vup, -v, normal, vup);
|
||||
VectorNormalize2(vup, vup);
|
||||
|
||||
VectorScale (normal, dist, org);
|
||||
|
||||
CrossProduct (vup, normal, vright);
|
||||
|
||||
VectorScale (vup, MAX_MAP_BOUNDS, vup);
|
||||
VectorScale (vright, MAX_MAP_BOUNDS, vright);
|
||||
|
||||
// project a really big axis aligned box onto the plane
|
||||
w = AllocWinding (4);
|
||||
|
||||
VectorSubtract (org, vright, w->p[0]);
|
||||
VectorAdd (w->p[0], vup, w->p[0]);
|
||||
|
||||
VectorAdd (org, vright, w->p[1]);
|
||||
VectorAdd (w->p[1], vup, w->p[1]);
|
||||
|
||||
VectorAdd (org, vright, w->p[2]);
|
||||
VectorSubtract (w->p[2], vup, w->p[2]);
|
||||
|
||||
VectorSubtract (org, vright, w->p[3]);
|
||||
VectorSubtract (w->p[3], vup, w->p[3]);
|
||||
|
||||
w->numpoints = 4;
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
CopyWinding
|
||||
==================
|
||||
*/
|
||||
winding_t *CopyWinding (winding_t *w)
|
||||
{
|
||||
int size;
|
||||
winding_t *c;
|
||||
|
||||
c = AllocWinding (w->numpoints);
|
||||
size = (int)((winding_t *)0)->p[w->numpoints];
|
||||
Com_Memcpy (c, w, size);
|
||||
return c;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
ReverseWinding
|
||||
==================
|
||||
*/
|
||||
winding_t *ReverseWinding (winding_t *w)
|
||||
{
|
||||
int i;
|
||||
winding_t *c;
|
||||
|
||||
c = AllocWinding (w->numpoints);
|
||||
for (i=0 ; i<w->numpoints ; i++)
|
||||
{
|
||||
VectorCopy (w->p[w->numpoints-1-i], c->p[i]);
|
||||
}
|
||||
c->numpoints = w->numpoints;
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============
|
||||
ClipWindingEpsilon
|
||||
=============
|
||||
*/
|
||||
void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist,
|
||||
vec_t epsilon, winding_t **front, winding_t **back)
|
||||
{
|
||||
vec_t dists[MAX_POINTS_ON_WINDING+4];
|
||||
int sides[MAX_POINTS_ON_WINDING+4];
|
||||
int counts[3];
|
||||
static vec_t dot; // VC 4.2 optimizer bug if not static
|
||||
int i, j;
|
||||
vec_t *p1, *p2;
|
||||
vec3_t mid;
|
||||
winding_t *f, *b;
|
||||
int maxpts;
|
||||
|
||||
counts[0] = counts[1] = counts[2] = 0;
|
||||
|
||||
// determine sides for each point
|
||||
for (i=0 ; i<in->numpoints ; i++)
|
||||
{
|
||||
dot = DotProduct (in->p[i], normal);
|
||||
dot -= dist;
|
||||
dists[i] = dot;
|
||||
if (dot > epsilon)
|
||||
sides[i] = SIDE_FRONT;
|
||||
else if (dot < -epsilon)
|
||||
sides[i] = SIDE_BACK;
|
||||
else
|
||||
{
|
||||
sides[i] = SIDE_ON;
|
||||
}
|
||||
counts[sides[i]]++;
|
||||
}
|
||||
sides[i] = sides[0];
|
||||
dists[i] = dists[0];
|
||||
|
||||
*front = *back = NULL;
|
||||
|
||||
if (!counts[0])
|
||||
{
|
||||
*back = CopyWinding (in);
|
||||
return;
|
||||
}
|
||||
if (!counts[1])
|
||||
{
|
||||
*front = CopyWinding (in);
|
||||
return;
|
||||
}
|
||||
|
||||
maxpts = in->numpoints+4; // cant use counts[0]+2 because
|
||||
// of fp grouping errors
|
||||
|
||||
*front = f = AllocWinding (maxpts);
|
||||
*back = b = AllocWinding (maxpts);
|
||||
|
||||
for (i=0 ; i<in->numpoints ; i++)
|
||||
{
|
||||
p1 = in->p[i];
|
||||
|
||||
if (sides[i] == SIDE_ON)
|
||||
{
|
||||
VectorCopy (p1, f->p[f->numpoints]);
|
||||
f->numpoints++;
|
||||
VectorCopy (p1, b->p[b->numpoints]);
|
||||
b->numpoints++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sides[i] == SIDE_FRONT)
|
||||
{
|
||||
VectorCopy (p1, f->p[f->numpoints]);
|
||||
f->numpoints++;
|
||||
}
|
||||
if (sides[i] == SIDE_BACK)
|
||||
{
|
||||
VectorCopy (p1, b->p[b->numpoints]);
|
||||
b->numpoints++;
|
||||
}
|
||||
|
||||
if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
|
||||
continue;
|
||||
|
||||
// generate a split point
|
||||
p2 = in->p[(i+1)%in->numpoints];
|
||||
|
||||
dot = dists[i] / (dists[i]-dists[i+1]);
|
||||
for (j=0 ; j<3 ; j++)
|
||||
{ // avoid round off error when possible
|
||||
if (normal[j] == 1)
|
||||
mid[j] = dist;
|
||||
else if (normal[j] == -1)
|
||||
mid[j] = -dist;
|
||||
else
|
||||
mid[j] = p1[j] + dot*(p2[j]-p1[j]);
|
||||
}
|
||||
|
||||
VectorCopy (mid, f->p[f->numpoints]);
|
||||
f->numpoints++;
|
||||
VectorCopy (mid, b->p[b->numpoints]);
|
||||
b->numpoints++;
|
||||
}
|
||||
|
||||
if (f->numpoints > maxpts || b->numpoints > maxpts)
|
||||
Com_Error (ERR_DROP, "ClipWinding: points exceeded estimate");
|
||||
if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING)
|
||||
Com_Error (ERR_DROP, "ClipWinding: MAX_POINTS_ON_WINDING");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============
|
||||
ChopWindingInPlace
|
||||
=============
|
||||
*/
|
||||
void ChopWindingInPlace (winding_t **inout, vec3_t normal, vec_t dist, vec_t epsilon)
|
||||
{
|
||||
winding_t *in;
|
||||
vec_t dists[MAX_POINTS_ON_WINDING+4];
|
||||
int sides[MAX_POINTS_ON_WINDING+4];
|
||||
int counts[3];
|
||||
static vec_t dot; // VC 4.2 optimizer bug if not static
|
||||
int i, j;
|
||||
vec_t *p1, *p2;
|
||||
vec3_t mid;
|
||||
winding_t *f;
|
||||
int maxpts;
|
||||
|
||||
in = *inout;
|
||||
counts[0] = counts[1] = counts[2] = 0;
|
||||
|
||||
// determine sides for each point
|
||||
for (i=0 ; i<in->numpoints ; i++)
|
||||
{
|
||||
dot = DotProduct (in->p[i], normal);
|
||||
dot -= dist;
|
||||
dists[i] = dot;
|
||||
if (dot > epsilon)
|
||||
sides[i] = SIDE_FRONT;
|
||||
else if (dot < -epsilon)
|
||||
sides[i] = SIDE_BACK;
|
||||
else
|
||||
{
|
||||
sides[i] = SIDE_ON;
|
||||
}
|
||||
counts[sides[i]]++;
|
||||
}
|
||||
sides[i] = sides[0];
|
||||
dists[i] = dists[0];
|
||||
|
||||
if (!counts[0])
|
||||
{
|
||||
FreeWinding (in);
|
||||
*inout = NULL;
|
||||
return;
|
||||
}
|
||||
if (!counts[1])
|
||||
return; // inout stays the same
|
||||
|
||||
maxpts = in->numpoints+4; // cant use counts[0]+2 because
|
||||
// of fp grouping errors
|
||||
|
||||
f = AllocWinding (maxpts);
|
||||
|
||||
for (i=0 ; i<in->numpoints ; i++)
|
||||
{
|
||||
p1 = in->p[i];
|
||||
|
||||
if (sides[i] == SIDE_ON)
|
||||
{
|
||||
VectorCopy (p1, f->p[f->numpoints]);
|
||||
f->numpoints++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sides[i] == SIDE_FRONT)
|
||||
{
|
||||
VectorCopy (p1, f->p[f->numpoints]);
|
||||
f->numpoints++;
|
||||
}
|
||||
|
||||
if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
|
||||
continue;
|
||||
|
||||
// generate a split point
|
||||
p2 = in->p[(i+1)%in->numpoints];
|
||||
|
||||
dot = dists[i] / (dists[i]-dists[i+1]);
|
||||
for (j=0 ; j<3 ; j++)
|
||||
{ // avoid round off error when possible
|
||||
if (normal[j] == 1)
|
||||
mid[j] = dist;
|
||||
else if (normal[j] == -1)
|
||||
mid[j] = -dist;
|
||||
else
|
||||
mid[j] = p1[j] + dot*(p2[j]-p1[j]);
|
||||
}
|
||||
|
||||
VectorCopy (mid, f->p[f->numpoints]);
|
||||
f->numpoints++;
|
||||
}
|
||||
|
||||
if (f->numpoints > maxpts)
|
||||
Com_Error (ERR_DROP, "ClipWinding: points exceeded estimate");
|
||||
if (f->numpoints > MAX_POINTS_ON_WINDING)
|
||||
Com_Error (ERR_DROP, "ClipWinding: MAX_POINTS_ON_WINDING");
|
||||
|
||||
FreeWinding (in);
|
||||
*inout = f;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
ChopWinding
|
||||
|
||||
Returns the fragment of in that is on the front side
|
||||
of the cliping plane. The original is freed.
|
||||
=================
|
||||
*/
|
||||
winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist)
|
||||
{
|
||||
winding_t *f, *b;
|
||||
|
||||
ClipWindingEpsilon (in, normal, dist, ON_EPSILON, &f, &b);
|
||||
FreeWinding (in);
|
||||
if (b)
|
||||
FreeWinding (b);
|
||||
return f;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
CheckWinding
|
||||
|
||||
=================
|
||||
*/
|
||||
void CheckWinding (winding_t *w)
|
||||
{
|
||||
int i, j;
|
||||
vec_t *p1, *p2;
|
||||
vec_t d, edgedist;
|
||||
vec3_t dir, edgenormal, facenormal;
|
||||
vec_t area;
|
||||
vec_t facedist;
|
||||
|
||||
if (w->numpoints < 3)
|
||||
Com_Error (ERR_DROP, "CheckWinding: %i points",w->numpoints);
|
||||
|
||||
area = WindingArea(w);
|
||||
if (area < 1)
|
||||
Com_Error (ERR_DROP, "CheckWinding: %f area", area);
|
||||
|
||||
WindingPlane (w, facenormal, &facedist);
|
||||
|
||||
for (i=0 ; i<w->numpoints ; i++)
|
||||
{
|
||||
p1 = w->p[i];
|
||||
|
||||
for (j=0 ; j<3 ; j++)
|
||||
if (p1[j] > MAX_MAP_BOUNDS || p1[j] < -MAX_MAP_BOUNDS)
|
||||
Com_Error (ERR_DROP, "CheckFace: BUGUS_RANGE: %f",p1[j]);
|
||||
|
||||
j = i+1 == w->numpoints ? 0 : i+1;
|
||||
|
||||
// check the point is on the face plane
|
||||
d = DotProduct (p1, facenormal) - facedist;
|
||||
if (d < -ON_EPSILON || d > ON_EPSILON)
|
||||
Com_Error (ERR_DROP, "CheckWinding: point off plane");
|
||||
|
||||
// check the edge isnt degenerate
|
||||
p2 = w->p[j];
|
||||
VectorSubtract (p2, p1, dir);
|
||||
|
||||
if (VectorLength (dir) < ON_EPSILON)
|
||||
Com_Error (ERR_DROP, "CheckWinding: degenerate edge");
|
||||
|
||||
CrossProduct (facenormal, dir, edgenormal);
|
||||
VectorNormalize2 (edgenormal, edgenormal);
|
||||
edgedist = DotProduct (p1, edgenormal);
|
||||
edgedist += ON_EPSILON;
|
||||
|
||||
// all other points must be on front side
|
||||
for (j=0 ; j<w->numpoints ; j++)
|
||||
{
|
||||
if (j == i)
|
||||
continue;
|
||||
d = DotProduct (w->p[j], edgenormal);
|
||||
if (d > edgedist)
|
||||
Com_Error (ERR_DROP, "CheckWinding: non-convex");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
WindingOnPlaneSide
|
||||
============
|
||||
*/
|
||||
int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist)
|
||||
{
|
||||
qboolean front, back;
|
||||
int i;
|
||||
vec_t d;
|
||||
|
||||
front = qfalse;
|
||||
back = qfalse;
|
||||
for (i=0 ; i<w->numpoints ; i++)
|
||||
{
|
||||
d = DotProduct (w->p[i], normal) - dist;
|
||||
if (d < -ON_EPSILON)
|
||||
{
|
||||
if (front)
|
||||
return SIDE_CROSS;
|
||||
back = qtrue;
|
||||
continue;
|
||||
}
|
||||
if (d > ON_EPSILON)
|
||||
{
|
||||
if (back)
|
||||
return SIDE_CROSS;
|
||||
front = qtrue;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (back)
|
||||
return SIDE_BACK;
|
||||
if (front)
|
||||
return SIDE_FRONT;
|
||||
return SIDE_ON;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
AddWindingToConvexHull
|
||||
|
||||
Both w and *hull are on the same plane
|
||||
=================
|
||||
*/
|
||||
#define MAX_HULL_POINTS 128
|
||||
void AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal ) {
|
||||
int i, j, k;
|
||||
float *p, *copy;
|
||||
vec3_t dir;
|
||||
float d;
|
||||
int numHullPoints, numNew;
|
||||
vec3_t hullPoints[MAX_HULL_POINTS];
|
||||
vec3_t newHullPoints[MAX_HULL_POINTS];
|
||||
vec3_t hullDirs[MAX_HULL_POINTS];
|
||||
qboolean hullSide[MAX_HULL_POINTS];
|
||||
qboolean outside;
|
||||
|
||||
if ( !*hull ) {
|
||||
*hull = CopyWinding( w );
|
||||
return;
|
||||
}
|
||||
|
||||
numHullPoints = (*hull)->numpoints;
|
||||
Com_Memcpy( hullPoints, (*hull)->p, numHullPoints * sizeof(vec3_t) );
|
||||
|
||||
for ( i = 0 ; i < w->numpoints ; i++ ) {
|
||||
p = w->p[i];
|
||||
|
||||
// calculate hull side vectors
|
||||
for ( j = 0 ; j < numHullPoints ; j++ ) {
|
||||
k = ( j + 1 ) % numHullPoints;
|
||||
|
||||
VectorSubtract( hullPoints[k], hullPoints[j], dir );
|
||||
VectorNormalize2( dir, dir );
|
||||
CrossProduct( normal, dir, hullDirs[j] );
|
||||
}
|
||||
|
||||
outside = qfalse;
|
||||
for ( j = 0 ; j < numHullPoints ; j++ ) {
|
||||
VectorSubtract( p, hullPoints[j], dir );
|
||||
d = DotProduct( dir, hullDirs[j] );
|
||||
if ( d >= ON_EPSILON ) {
|
||||
outside = qtrue;
|
||||
}
|
||||
if ( d >= -ON_EPSILON ) {
|
||||
hullSide[j] = qtrue;
|
||||
} else {
|
||||
hullSide[j] = qfalse;
|
||||
}
|
||||
}
|
||||
|
||||
// if the point is effectively inside, do nothing
|
||||
if ( !outside ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// find the back side to front side transition
|
||||
for ( j = 0 ; j < numHullPoints ; j++ ) {
|
||||
if ( !hullSide[ j % numHullPoints ] && hullSide[ (j + 1) % numHullPoints ] ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( j == numHullPoints ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// insert the point here
|
||||
VectorCopy( p, newHullPoints[0] );
|
||||
numNew = 1;
|
||||
|
||||
// copy over all points that aren't double fronts
|
||||
j = (j+1)%numHullPoints;
|
||||
for ( k = 0 ; k < numHullPoints ; k++ ) {
|
||||
if ( hullSide[ (j+k) % numHullPoints ] && hullSide[ (j+k+1) % numHullPoints ] ) {
|
||||
continue;
|
||||
}
|
||||
copy = hullPoints[ (j+k+1) % numHullPoints ];
|
||||
VectorCopy( copy, newHullPoints[numNew] );
|
||||
numNew++;
|
||||
}
|
||||
|
||||
numHullPoints = numNew;
|
||||
Com_Memcpy( hullPoints, newHullPoints, numHullPoints * sizeof(vec3_t) );
|
||||
}
|
||||
|
||||
FreeWinding( *hull );
|
||||
w = AllocWinding( numHullPoints );
|
||||
w->numpoints = numHullPoints;
|
||||
*hull = w;
|
||||
Com_Memcpy( w->p, hullPoints, numHullPoints * sizeof(vec3_t) );
|
||||
}
|
||||
|
||||
|
68
code/qcommon/cm_polylib.h
Normal file
68
code/qcommon/cm_polylib.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code 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 Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
// this is only used for visualization tools in cm_ debug functions
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int numpoints;
|
||||
vec3_t p[4]; // variable sized
|
||||
} winding_t;
|
||||
|
||||
#define MAX_POINTS_ON_WINDING 64
|
||||
|
||||
#define SIDE_FRONT 0
|
||||
#define SIDE_BACK 1
|
||||
#define SIDE_ON 2
|
||||
#define SIDE_CROSS 3
|
||||
|
||||
#define CLIP_EPSILON 0.1f
|
||||
|
||||
#define MAX_MAP_BOUNDS 65535
|
||||
|
||||
// you can define on_epsilon in the makefile as tighter
|
||||
#ifndef ON_EPSILON
|
||||
#define ON_EPSILON 0.1f
|
||||
#endif
|
||||
|
||||
winding_t *AllocWinding (int points);
|
||||
vec_t WindingArea (winding_t *w);
|
||||
void WindingCenter (winding_t *w, vec3_t center);
|
||||
void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist,
|
||||
vec_t epsilon, winding_t **front, winding_t **back);
|
||||
winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist);
|
||||
winding_t *CopyWinding (winding_t *w);
|
||||
winding_t *ReverseWinding (winding_t *w);
|
||||
winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist);
|
||||
void CheckWinding (winding_t *w);
|
||||
void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist);
|
||||
void RemoveColinearPoints (winding_t *w);
|
||||
int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist);
|
||||
void FreeWinding (winding_t *w);
|
||||
void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs);
|
||||
|
||||
void AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal );
|
||||
|
||||
void ChopWindingInPlace (winding_t **w, vec3_t normal, vec_t dist, vec_t epsilon);
|
||||
// frees the original if clipped
|
||||
|
||||
void pw(winding_t *w);
|
76
code/qcommon/cm_public.h
Normal file
76
code/qcommon/cm_public.h
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code 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 Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
#include "qfiles.h"
|
||||
|
||||
|
||||
void CM_LoadMap( const char *name, qboolean clientload, int *checksum);
|
||||
void CM_ClearMap( void );
|
||||
clipHandle_t CM_InlineModel( int index ); // 0 = world, 1 + are bmodels
|
||||
clipHandle_t CM_TempBoxModel( const vec3_t mins, const vec3_t maxs, int capsule );
|
||||
|
||||
void CM_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs );
|
||||
|
||||
int CM_NumClusters (void);
|
||||
int CM_NumInlineModels( void );
|
||||
char *CM_EntityString (void);
|
||||
|
||||
// returns an ORed contents mask
|
||||
int CM_PointContents( const vec3_t p, clipHandle_t model );
|
||||
int CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles );
|
||||
|
||||
void CM_BoxTrace ( trace_t *results, const vec3_t start, const vec3_t end,
|
||||
vec3_t mins, vec3_t maxs,
|
||||
clipHandle_t model, int brushmask, int capsule );
|
||||
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, int capsule );
|
||||
|
||||
byte *CM_ClusterPVS (int cluster);
|
||||
|
||||
int CM_PointLeafnum( const vec3_t p );
|
||||
|
||||
// only returns non-solid leafs
|
||||
// overflow if return listsize and if *lastLeaf != list[listsize-1]
|
||||
int CM_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *list,
|
||||
int listsize, int *lastLeaf );
|
||||
|
||||
int CM_LeafCluster (int leafnum);
|
||||
int CM_LeafArea (int leafnum);
|
||||
|
||||
void CM_AdjustAreaPortalState( int area1, int area2, qboolean open );
|
||||
qboolean CM_AreasConnected( int area1, int area2 );
|
||||
|
||||
int CM_WriteAreaBits( byte *buffer, int area );
|
||||
|
||||
// cm_tag.c
|
||||
int CM_LerpTag( orientation_t *tag, clipHandle_t model, int startFrame, int endFrame,
|
||||
float frac, const char *tagName );
|
||||
|
||||
|
||||
// cm_marks.c
|
||||
int CM_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection,
|
||||
int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer );
|
||||
|
||||
// cm_patch.c
|
||||
void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, float *points) );
|
478
code/qcommon/cm_test.c
Normal file
478
code/qcommon/cm_test.c
Normal file
|
@ -0,0 +1,478 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code 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 Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
#include "cm_local.h"
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
CM_PointLeafnum_r
|
||||
|
||||
==================
|
||||
*/
|
||||
int CM_PointLeafnum_r( const vec3_t p, int num ) {
|
||||
float d;
|
||||
cNode_t *node;
|
||||
cplane_t *plane;
|
||||
|
||||
while (num >= 0)
|
||||
{
|
||||
node = cm.nodes + num;
|
||||
plane = node->plane;
|
||||
|
||||
if (plane->type < 3)
|
||||
d = p[plane->type] - plane->dist;
|
||||
else
|
||||
d = DotProduct (plane->normal, p) - plane->dist;
|
||||
if (d < 0)
|
||||
num = node->children[1];
|
||||
else
|
||||
num = node->children[0];
|
||||
}
|
||||
|
||||
c_pointcontents++; // optimize counter
|
||||
|
||||
return -1 - num;
|
||||
}
|
||||
|
||||
int CM_PointLeafnum( const vec3_t p ) {
|
||||
if ( !cm.numNodes ) { // map not loaded
|
||||
return 0;
|
||||
}
|
||||
return CM_PointLeafnum_r (p, 0);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
======================================================================
|
||||
|
||||
LEAF LISTING
|
||||
|
||||
======================================================================
|
||||
*/
|
||||
|
||||
|
||||
void CM_StoreLeafs( leafList_t *ll, int nodenum ) {
|
||||
int leafNum;
|
||||
|
||||
leafNum = -1 - nodenum;
|
||||
|
||||
// store the lastLeaf even if the list is overflowed
|
||||
if ( cm.leafs[ leafNum ].cluster != -1 ) {
|
||||
ll->lastLeaf = leafNum;
|
||||
}
|
||||
|
||||
if ( ll->count >= ll->maxcount) {
|
||||
ll->overflowed = qtrue;
|
||||
return;
|
||||
}
|
||||
ll->list[ ll->count++ ] = leafNum;
|
||||
}
|
||||
|
||||
void CM_StoreBrushes( leafList_t *ll, int nodenum ) {
|
||||
int i, k;
|
||||
int leafnum;
|
||||
int brushnum;
|
||||
cLeaf_t *leaf;
|
||||
cbrush_t *b;
|
||||
|
||||
leafnum = -1 - nodenum;
|
||||
|
||||
leaf = &cm.leafs[leafnum];
|
||||
|
||||
for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) {
|
||||
brushnum = cm.leafbrushes[leaf->firstLeafBrush+k];
|
||||
b = &cm.brushes[brushnum];
|
||||
if ( b->checkcount == cm.checkcount ) {
|
||||
continue; // already checked this brush in another leaf
|
||||
}
|
||||
b->checkcount = cm.checkcount;
|
||||
for ( i = 0 ; i < 3 ; i++ ) {
|
||||
if ( b->bounds[0][i] >= ll->bounds[1][i] || b->bounds[1][i] <= ll->bounds[0][i] ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( i != 3 ) {
|
||||
continue;
|
||||
}
|
||||
if ( ll->count >= ll->maxcount) {
|
||||
ll->overflowed = qtrue;
|
||||
return;
|
||||
}
|
||||
((cbrush_t **)ll->list)[ ll->count++ ] = b;
|
||||
}
|
||||
#if 0
|
||||
// store patches?
|
||||
for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) {
|
||||
patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstleafsurface + k ] ];
|
||||
if ( !patch ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
CM_BoxLeafnums
|
||||
|
||||
Fills in a list of all the leafs touched
|
||||
=============
|
||||
*/
|
||||
void CM_BoxLeafnums_r( leafList_t *ll, int nodenum ) {
|
||||
cplane_t *plane;
|
||||
cNode_t *node;
|
||||
int s;
|
||||
|
||||
while (1) {
|
||||
if (nodenum < 0) {
|
||||
ll->storeLeafs( ll, nodenum );
|
||||
return;
|
||||
}
|
||||
|
||||
node = &cm.nodes[nodenum];
|
||||
plane = node->plane;
|
||||
s = BoxOnPlaneSide( ll->bounds[0], ll->bounds[1], plane );
|
||||
if (s == 1) {
|
||||
nodenum = node->children[0];
|
||||
} else if (s == 2) {
|
||||
nodenum = node->children[1];
|
||||
} else {
|
||||
// go down both
|
||||
CM_BoxLeafnums_r( ll, node->children[0] );
|
||||
nodenum = node->children[1];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
CM_BoxLeafnums
|
||||
==================
|
||||
*/
|
||||
int CM_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *list, int listsize, int *lastLeaf) {
|
||||
leafList_t ll;
|
||||
|
||||
cm.checkcount++;
|
||||
|
||||
VectorCopy( mins, ll.bounds[0] );
|
||||
VectorCopy( maxs, ll.bounds[1] );
|
||||
ll.count = 0;
|
||||
ll.maxcount = listsize;
|
||||
ll.list = list;
|
||||
ll.storeLeafs = CM_StoreLeafs;
|
||||
ll.lastLeaf = 0;
|
||||
ll.overflowed = qfalse;
|
||||
|
||||
CM_BoxLeafnums_r( &ll, 0 );
|
||||
|
||||
*lastLeaf = ll.lastLeaf;
|
||||
return ll.count;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
CM_BoxBrushes
|
||||
==================
|
||||
*/
|
||||
int CM_BoxBrushes( const vec3_t mins, const vec3_t maxs, cbrush_t **list, int listsize ) {
|
||||
leafList_t ll;
|
||||
|
||||
cm.checkcount++;
|
||||
|
||||
VectorCopy( mins, ll.bounds[0] );
|
||||
VectorCopy( maxs, ll.bounds[1] );
|
||||
ll.count = 0;
|
||||
ll.maxcount = listsize;
|
||||
ll.list = (void *)list;
|
||||
ll.storeLeafs = CM_StoreBrushes;
|
||||
ll.lastLeaf = 0;
|
||||
ll.overflowed = qfalse;
|
||||
|
||||
CM_BoxLeafnums_r( &ll, 0 );
|
||||
|
||||
return ll.count;
|
||||
}
|
||||
|
||||
|
||||
//====================================================================
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
CM_PointContents
|
||||
|
||||
==================
|
||||
*/
|
||||
int CM_PointContents( const vec3_t p, clipHandle_t model ) {
|
||||
int leafnum;
|
||||
int i, k;
|
||||
int brushnum;
|
||||
cLeaf_t *leaf;
|
||||
cbrush_t *b;
|
||||
int contents;
|
||||
float d;
|
||||
cmodel_t *clipm;
|
||||
|
||||
if (!cm.numNodes) { // map not loaded
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( model ) {
|
||||
clipm = CM_ClipHandleToModel( model );
|
||||
leaf = &clipm->leaf;
|
||||
} else {
|
||||
leafnum = CM_PointLeafnum_r (p, 0);
|
||||
leaf = &cm.leafs[leafnum];
|
||||
}
|
||||
|
||||
contents = 0;
|
||||
for (k=0 ; k<leaf->numLeafBrushes ; k++) {
|
||||
brushnum = cm.leafbrushes[leaf->firstLeafBrush+k];
|
||||
b = &cm.brushes[brushnum];
|
||||
|
||||
// see if the point is in the brush
|
||||
for ( i = 0 ; i < b->numsides ; i++ ) {
|
||||
d = DotProduct( p, b->sides[i].plane->normal );
|
||||
// FIXME test for Cash
|
||||
// if ( d >= b->sides[i].plane->dist ) {
|
||||
if ( d > b->sides[i].plane->dist ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( i == b->numsides ) {
|
||||
contents |= b->contents;
|
||||
}
|
||||
}
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
CM_TransformedPointContents
|
||||
|
||||
Handles offseting and rotation of the end points for moving and
|
||||
rotating entities
|
||||
==================
|
||||
*/
|
||||
int CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles) {
|
||||
vec3_t p_l;
|
||||
vec3_t temp;
|
||||
vec3_t forward, right, up;
|
||||
|
||||
// subtract origin offset
|
||||
VectorSubtract (p, origin, p_l);
|
||||
|
||||
// rotate start and end into the models frame of reference
|
||||
if ( model != BOX_MODEL_HANDLE &&
|
||||
(angles[0] || angles[1] || angles[2]) )
|
||||
{
|
||||
AngleVectors (angles, forward, right, up);
|
||||
|
||||
VectorCopy (p_l, temp);
|
||||
p_l[0] = DotProduct (temp, forward);
|
||||
p_l[1] = -DotProduct (temp, right);
|
||||
p_l[2] = DotProduct (temp, up);
|
||||
}
|
||||
|
||||
return CM_PointContents( p_l, model );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
===============================================================================
|
||||
|
||||
PVS
|
||||
|
||||
===============================================================================
|
||||
*/
|
||||
|
||||
byte *CM_ClusterPVS (int cluster) {
|
||||
if (cluster < 0 || cluster >= cm.numClusters || !cm.vised ) {
|
||||
return cm.visibility;
|
||||
}
|
||||
|
||||
return cm.visibility + cluster * cm.clusterBytes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
===============================================================================
|
||||
|
||||
AREAPORTALS
|
||||
|
||||
===============================================================================
|
||||
*/
|
||||
|
||||
void CM_FloodArea_r( int areaNum, int floodnum) {
|
||||
int i;
|
||||
cArea_t *area;
|
||||
int *con;
|
||||
|
||||
area = &cm.areas[ areaNum ];
|
||||
|
||||
if ( area->floodvalid == cm.floodvalid ) {
|
||||
if (area->floodnum == floodnum)
|
||||
return;
|
||||
Com_Error (ERR_DROP, "FloodArea_r: reflooded");
|
||||
}
|
||||
|
||||
area->floodnum = floodnum;
|
||||
area->floodvalid = cm.floodvalid;
|
||||
con = cm.areaPortals + areaNum * cm.numAreas;
|
||||
for ( i=0 ; i < cm.numAreas ; i++ ) {
|
||||
if ( con[i] > 0 ) {
|
||||
CM_FloodArea_r( i, floodnum );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
====================
|
||||
CM_FloodAreaConnections
|
||||
|
||||
====================
|
||||
*/
|
||||
void CM_FloodAreaConnections( void ) {
|
||||
int i;
|
||||
cArea_t *area;
|
||||
int floodnum;
|
||||
|
||||
// all current floods are now invalid
|
||||
cm.floodvalid++;
|
||||
floodnum = 0;
|
||||
|
||||
for (i = 0 ; i < cm.numAreas ; i++) {
|
||||
area = &cm.areas[i];
|
||||
if (area->floodvalid == cm.floodvalid) {
|
||||
continue; // already flooded into
|
||||
}
|
||||
floodnum++;
|
||||
CM_FloodArea_r (i, floodnum);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
====================
|
||||
CM_AdjustAreaPortalState
|
||||
|
||||
====================
|
||||
*/
|
||||
void CM_AdjustAreaPortalState( int area1, int area2, qboolean open ) {
|
||||
if ( area1 < 0 || area2 < 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( area1 >= cm.numAreas || area2 >= cm.numAreas ) {
|
||||
Com_Error (ERR_DROP, "CM_ChangeAreaPortalState: bad area number");
|
||||
}
|
||||
|
||||
if ( open ) {
|
||||
cm.areaPortals[ area1 * cm.numAreas + area2 ]++;
|
||||
cm.areaPortals[ area2 * cm.numAreas + area1 ]++;
|
||||
} else {
|
||||
cm.areaPortals[ area1 * cm.numAreas + area2 ]--;
|
||||
cm.areaPortals[ area2 * cm.numAreas + area1 ]--;
|
||||
if ( cm.areaPortals[ area2 * cm.numAreas + area1 ] < 0 ) {
|
||||
Com_Error (ERR_DROP, "CM_AdjustAreaPortalState: negative reference count");
|
||||
}
|
||||
}
|
||||
|
||||
CM_FloodAreaConnections ();
|
||||
}
|
||||
|
||||
/*
|
||||
====================
|
||||
CM_AreasConnected
|
||||
|
||||
====================
|
||||
*/
|
||||
qboolean CM_AreasConnected( int area1, int area2 ) {
|
||||
#ifndef BSPC
|
||||
if ( cm_noAreas->integer ) {
|
||||
return qtrue;
|
||||
}
|
||||
#endif
|
||||
|
||||
if ( area1 < 0 || area2 < 0 ) {
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
if (area1 >= cm.numAreas || area2 >= cm.numAreas) {
|
||||
Com_Error (ERR_DROP, "area >= cm.numAreas");
|
||||
}
|
||||
|
||||
if (cm.areas[area1].floodnum == cm.areas[area2].floodnum) {
|
||||
return qtrue;
|
||||
}
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=================
|
||||
CM_WriteAreaBits
|
||||
|
||||
Writes a bit vector of all the areas
|
||||
that are in the same flood as the area parameter
|
||||
Returns the number of bytes needed to hold all the bits.
|
||||
|
||||
The bits are OR'd in, so you can CM_WriteAreaBits from multiple
|
||||
viewpoints and get the union of all visible areas.
|
||||
|
||||
This is used to cull non-visible entities from snapshots
|
||||
=================
|
||||
*/
|
||||
int CM_WriteAreaBits (byte *buffer, int area)
|
||||
{
|
||||
int i;
|
||||
int floodnum;
|
||||
int bytes;
|
||||
|
||||
bytes = (cm.numAreas+7)>>3;
|
||||
|
||||
#ifndef BSPC
|
||||
if (cm_noAreas->integer || area == -1)
|
||||
#else
|
||||
if ( area == -1)
|
||||
#endif
|
||||
{ // for debugging, send everything
|
||||
Com_Memset (buffer, 255, bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
floodnum = cm.areas[area].floodnum;
|
||||
for (i=0 ; i<cm.numAreas ; i++)
|
||||
{
|
||||
if (cm.areas[i].floodnum == floodnum || area == -1)
|
||||
buffer[i>>3] |= 1<<(i&7);
|
||||
}
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
1471
code/qcommon/cm_trace.c
Normal file
1471
code/qcommon/cm_trace.c
Normal file
File diff suppressed because it is too large
Load diff
715
code/qcommon/cmd.c
Normal file
715
code/qcommon/cmd.c
Normal file
|
@ -0,0 +1,715 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code 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 Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
// cmd.c -- Quake script command processing module
|
||||
|
||||
#include "../game/q_shared.h"
|
||||
#include "qcommon.h"
|
||||
|
||||
#define MAX_CMD_BUFFER 16384
|
||||
#define MAX_CMD_LINE 1024
|
||||
|
||||
typedef struct {
|
||||
byte *data;
|
||||
int maxsize;
|
||||
int cursize;
|
||||
} cmd_t;
|
||||
|
||||
int cmd_wait;
|
||||
cmd_t cmd_text;
|
||||
byte cmd_text_buf[MAX_CMD_BUFFER];
|
||||
|
||||
|
||||
//=============================================================================
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_Wait_f
|
||||
|
||||
Causes execution of the remainder of the command buffer to be delayed until
|
||||
next frame. This allows commands like:
|
||||
bind g "cmd use rocket ; +attack ; wait ; -attack ; cmd use blaster"
|
||||
============
|
||||
*/
|
||||
void Cmd_Wait_f( void ) {
|
||||
if ( Cmd_Argc() == 2 ) {
|
||||
cmd_wait = atoi( Cmd_Argv( 1 ) );
|
||||
} else {
|
||||
cmd_wait = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============================================================================
|
||||
|
||||
COMMAND BUFFER
|
||||
|
||||
=============================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
============
|
||||
Cbuf_Init
|
||||
============
|
||||
*/
|
||||
void Cbuf_Init (void)
|
||||
{
|
||||
cmd_text.data = cmd_text_buf;
|
||||
cmd_text.maxsize = MAX_CMD_BUFFER;
|
||||
cmd_text.cursize = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cbuf_AddText
|
||||
|
||||
Adds command text at the end of the buffer, does NOT add a final \n
|
||||
============
|
||||
*/
|
||||
void Cbuf_AddText( const char *text ) {
|
||||
int l;
|
||||
|
||||
l = strlen (text);
|
||||
|
||||
if (cmd_text.cursize + l >= cmd_text.maxsize)
|
||||
{
|
||||
Com_Printf ("Cbuf_AddText: overflow\n");
|
||||
return;
|
||||
}
|
||||
Com_Memcpy(&cmd_text.data[cmd_text.cursize], text, l);
|
||||
cmd_text.cursize += l;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cbuf_InsertText
|
||||
|
||||
Adds command text immediately after the current command
|
||||
Adds a \n to the text
|
||||
============
|
||||
*/
|
||||
void Cbuf_InsertText( const char *text ) {
|
||||
int len;
|
||||
int i;
|
||||
|
||||
len = strlen( text ) + 1;
|
||||
if ( len + cmd_text.cursize > cmd_text.maxsize ) {
|
||||
Com_Printf( "Cbuf_InsertText overflowed\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
// move the existing command text
|
||||
for ( i = cmd_text.cursize - 1 ; i >= 0 ; i-- ) {
|
||||
cmd_text.data[ i + len ] = cmd_text.data[ i ];
|
||||
}
|
||||
|
||||
// copy the new text in
|
||||
Com_Memcpy( cmd_text.data, text, len - 1 );
|
||||
|
||||
// add a \n
|
||||
cmd_text.data[ len - 1 ] = '\n';
|
||||
|
||||
cmd_text.cursize += len;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cbuf_ExecuteText
|
||||
============
|
||||
*/
|
||||
void Cbuf_ExecuteText (int exec_when, const char *text)
|
||||
{
|
||||
switch (exec_when)
|
||||
{
|
||||
case EXEC_NOW:
|
||||
if (text && strlen(text) > 0) {
|
||||
Cmd_ExecuteString (text);
|
||||
} else {
|
||||
Cbuf_Execute();
|
||||
}
|
||||
break;
|
||||
case EXEC_INSERT:
|
||||
Cbuf_InsertText (text);
|
||||
break;
|
||||
case EXEC_APPEND:
|
||||
Cbuf_AddText (text);
|
||||
break;
|
||||
default:
|
||||
Com_Error (ERR_FATAL, "Cbuf_ExecuteText: bad exec_when");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cbuf_Execute
|
||||
============
|
||||
*/
|
||||
void Cbuf_Execute (void)
|
||||
{
|
||||
int i;
|
||||
char *text;
|
||||
char line[MAX_CMD_LINE];
|
||||
int quotes;
|
||||
|
||||
while (cmd_text.cursize)
|
||||
{
|
||||
if ( cmd_wait ) {
|
||||
// skip out while text still remains in buffer, leaving it
|
||||
// for next frame
|
||||
cmd_wait--;
|
||||
break;
|
||||
}
|
||||
|
||||
// find a \n or ; line break
|
||||
text = (char *)cmd_text.data;
|
||||
|
||||
quotes = 0;
|
||||
for (i=0 ; i< cmd_text.cursize ; i++)
|
||||
{
|
||||
if (text[i] == '"')
|
||||
quotes++;
|
||||
if ( !(quotes&1) && text[i] == ';')
|
||||
break; // don't break if inside a quoted string
|
||||
if (text[i] == '\n' || text[i] == '\r' )
|
||||
break;
|
||||
}
|
||||
|
||||
if( i >= (MAX_CMD_LINE - 1)) {
|
||||
i = MAX_CMD_LINE - 1;
|
||||
}
|
||||
|
||||
Com_Memcpy (line, text, i);
|
||||
line[i] = 0;
|
||||
|
||||
// delete the text from the command buffer and move remaining commands down
|
||||
// this is necessary because commands (exec) can insert data at the
|
||||
// beginning of the text buffer
|
||||
|
||||
if (i == cmd_text.cursize)
|
||||
cmd_text.cursize = 0;
|
||||
else
|
||||
{
|
||||
i++;
|
||||
cmd_text.cursize -= i;
|
||||
memmove (text, text+i, cmd_text.cursize);
|
||||
}
|
||||
|
||||
// execute the command line
|
||||
|
||||
Cmd_ExecuteString (line);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
SCRIPT COMMANDS
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
Cmd_Exec_f
|
||||
===============
|
||||
*/
|
||||
void Cmd_Exec_f( void ) {
|
||||
char *f;
|
||||
int len;
|
||||
char filename[MAX_QPATH];
|
||||
|
||||
if (Cmd_Argc () != 2) {
|
||||
Com_Printf ("exec <filename> : execute a script file\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) );
|
||||
COM_DefaultExtension( filename, sizeof( filename ), ".cfg" );
|
||||
len = FS_ReadFile( filename, (void **)&f);
|
||||
if (!f) {
|
||||
Com_Printf ("couldn't exec %s\n",Cmd_Argv(1));
|
||||
return;
|
||||
}
|
||||
Com_Printf ("execing %s\n",Cmd_Argv(1));
|
||||
|
||||
Cbuf_InsertText (f);
|
||||
|
||||
FS_FreeFile (f);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
Cmd_Vstr_f
|
||||
|
||||
Inserts the current value of a variable as command text
|
||||
===============
|
||||
*/
|
||||
void Cmd_Vstr_f( void ) {
|
||||
char *v;
|
||||
|
||||
if (Cmd_Argc () != 2) {
|
||||
Com_Printf ("vstr <variablename> : execute a variable command\n");
|
||||
return;
|
||||
}
|
||||
|
||||
v = Cvar_VariableString( Cmd_Argv( 1 ) );
|
||||
Cbuf_InsertText( va("%s\n", v ) );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
Cmd_Echo_f
|
||||
|
||||
Just prints the rest of the line to the console
|
||||
===============
|
||||
*/
|
||||
void Cmd_Echo_f (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=1 ; i<Cmd_Argc() ; i++)
|
||||
Com_Printf ("%s ",Cmd_Argv(i));
|
||||
Com_Printf ("\n");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============================================================================
|
||||
|
||||
COMMAND EXECUTION
|
||||
|
||||
=============================================================================
|
||||
*/
|
||||
|
||||
typedef struct cmd_function_s
|
||||
{
|
||||
struct cmd_function_s *next;
|
||||
char *name;
|
||||
xcommand_t function;
|
||||
} cmd_function_t;
|
||||
|
||||
|
||||
static int cmd_argc;
|
||||
static char *cmd_argv[MAX_STRING_TOKENS]; // points into cmd_tokenized
|
||||
static char cmd_tokenized[BIG_INFO_STRING+MAX_STRING_TOKENS]; // will have 0 bytes inserted
|
||||
static char cmd_cmd[BIG_INFO_STRING]; // the original command we received (no token processing)
|
||||
|
||||
static cmd_function_t *cmd_functions; // possible commands to execute
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_Argc
|
||||
============
|
||||
*/
|
||||
int Cmd_Argc( void ) {
|
||||
return cmd_argc;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_Argv
|
||||
============
|
||||
*/
|
||||
char *Cmd_Argv( int arg ) {
|
||||
if ( (unsigned)arg >= cmd_argc ) {
|
||||
return "";
|
||||
}
|
||||
return cmd_argv[arg];
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_ArgvBuffer
|
||||
|
||||
The interpreted versions use this because
|
||||
they can't have pointers returned to them
|
||||
============
|
||||
*/
|
||||
void Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength ) {
|
||||
Q_strncpyz( buffer, Cmd_Argv( arg ), bufferLength );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_Args
|
||||
|
||||
Returns a single string containing argv(1) to argv(argc()-1)
|
||||
============
|
||||
*/
|
||||
char *Cmd_Args( void ) {
|
||||
static char cmd_args[MAX_STRING_CHARS];
|
||||
int i;
|
||||
|
||||
cmd_args[0] = 0;
|
||||
for ( i = 1 ; i < cmd_argc ; i++ ) {
|
||||
strcat( cmd_args, cmd_argv[i] );
|
||||
if ( i != cmd_argc-1 ) {
|
||||
strcat( cmd_args, " " );
|
||||
}
|
||||
}
|
||||
|
||||
return cmd_args;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_Args
|
||||
|
||||
Returns a single string containing argv(arg) to argv(argc()-1)
|
||||
============
|
||||
*/
|
||||
char *Cmd_ArgsFrom( int arg ) {
|
||||
static char cmd_args[BIG_INFO_STRING];
|
||||
int i;
|
||||
|
||||
cmd_args[0] = 0;
|
||||
if (arg < 0)
|
||||
arg = 0;
|
||||
for ( i = arg ; i < cmd_argc ; i++ ) {
|
||||
strcat( cmd_args, cmd_argv[i] );
|
||||
if ( i != cmd_argc-1 ) {
|
||||
strcat( cmd_args, " " );
|
||||
}
|
||||
}
|
||||
|
||||
return cmd_args;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_ArgsBuffer
|
||||
|
||||
The interpreted versions use this because
|
||||
they can't have pointers returned to them
|
||||
============
|
||||
*/
|
||||
void Cmd_ArgsBuffer( char *buffer, int bufferLength ) {
|
||||
Q_strncpyz( buffer, Cmd_Args(), bufferLength );
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_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()
|
||||
{
|
||||
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
|
||||
void Cmd_TokenizeString( const char *text_in ) {
|
||||
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;
|
||||
|
||||
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 ( *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 ( 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_AddCommand
|
||||
============
|
||||
*/
|
||||
void Cmd_AddCommand( const char *cmd_name, xcommand_t function ) {
|
||||
cmd_function_t *cmd;
|
||||
|
||||
// fail if the command already exists
|
||||
for ( cmd = cmd_functions ; cmd ; cmd=cmd->next ) {
|
||||
if ( !strcmp( cmd_name, cmd->name ) ) {
|
||||
// allow completion-only commands to be silently doubled
|
||||
if ( function != NULL ) {
|
||||
Com_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// use a small malloc to avoid zone fragmentation
|
||||
cmd = S_Malloc (sizeof(cmd_function_t));
|
||||
cmd->name = CopyString( cmd_name );
|
||||
cmd->function = function;
|
||||
cmd->next = cmd_functions;
|
||||
cmd_functions = cmd;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_RemoveCommand
|
||||
============
|
||||
*/
|
||||
void Cmd_RemoveCommand( const char *cmd_name ) {
|
||||
cmd_function_t *cmd, **back;
|
||||
|
||||
back = &cmd_functions;
|
||||
while( 1 ) {
|
||||
cmd = *back;
|
||||
if ( !cmd ) {
|
||||
// command wasn't active
|
||||
return;
|
||||
}
|
||||
if ( !strcmp( cmd_name, cmd->name ) ) {
|
||||
*back = cmd->next;
|
||||
if (cmd->name) {
|
||||
Z_Free(cmd->name);
|
||||
}
|
||||
Z_Free (cmd);
|
||||
return;
|
||||
}
|
||||
back = &cmd->next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_CommandCompletion
|
||||
============
|
||||
*/
|
||||
void Cmd_CommandCompletion( void(*callback)(const char *s) ) {
|
||||
cmd_function_t *cmd;
|
||||
|
||||
for (cmd=cmd_functions ; cmd ; cmd=cmd->next) {
|
||||
callback( cmd->name );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_ExecuteString
|
||||
|
||||
A complete command line has been parsed, so try to execute it
|
||||
============
|
||||
*/
|
||||
void Cmd_ExecuteString( const char *text ) {
|
||||
cmd_function_t *cmd, **prev;
|
||||
|
||||
// execute the command line
|
||||
Cmd_TokenizeString( text );
|
||||
if ( !Cmd_Argc() ) {
|
||||
return; // no tokens
|
||||
}
|
||||
|
||||
// check registered command functions
|
||||
for ( prev = &cmd_functions ; *prev ; prev = &cmd->next ) {
|
||||
cmd = *prev;
|
||||
if ( !Q_stricmp( cmd_argv[0],cmd->name ) ) {
|
||||
// rearrange the links so that the command will be
|
||||
// near the head of the list next time it is used
|
||||
*prev = cmd->next;
|
||||
cmd->next = cmd_functions;
|
||||
cmd_functions = cmd;
|
||||
|
||||
// perform the action
|
||||
if ( !cmd->function ) {
|
||||
// let the cgame or game handle it
|
||||
break;
|
||||
} else {
|
||||
cmd->function ();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check cvars
|
||||
if ( Cvar_Command() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check client game commands
|
||||
if ( com_cl_running && com_cl_running->integer && CL_GameCommand() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check server game commands
|
||||
if ( com_sv_running && com_sv_running->integer && SV_GameCommand() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check ui commands
|
||||
if ( com_cl_running && com_cl_running->integer && UI_GameCommand() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// send it as a server command if we are connected
|
||||
// this will usually result in a chat message
|
||||
CL_ForwardCommandToServer ( text );
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_List_f
|
||||
============
|
||||
*/
|
||||
void Cmd_List_f (void)
|
||||
{
|
||||
cmd_function_t *cmd;
|
||||
int i;
|
||||
char *match;
|
||||
|
||||
if ( Cmd_Argc() > 1 ) {
|
||||
match = Cmd_Argv( 1 );
|
||||
} else {
|
||||
match = NULL;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
for (cmd=cmd_functions ; cmd ; cmd=cmd->next) {
|
||||
if (match && !Com_Filter(match, cmd->name, qfalse)) continue;
|
||||
|
||||
Com_Printf ("%s\n", cmd->name);
|
||||
i++;
|
||||
}
|
||||
Com_Printf ("%i commands\n", i);
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_Init
|
||||
============
|
||||
*/
|
||||
void Cmd_Init (void) {
|
||||
Cmd_AddCommand ("cmdlist",Cmd_List_f);
|
||||
Cmd_AddCommand ("exec",Cmd_Exec_f);
|
||||
Cmd_AddCommand ("vstr",Cmd_Vstr_f);
|
||||
Cmd_AddCommand ("echo",Cmd_Echo_f);
|
||||
Cmd_AddCommand ("wait", Cmd_Wait_f);
|
||||
}
|
||||
|
636
code/qcommon/cmd.c.save
Normal file
636
code/qcommon/cmd.c.save
Normal file
|
@ -0,0 +1,636 @@
|
|||
// cmd.c -- Quake script command processing module
|
||||
|
||||
#include "../game/q_shared.h"
|
||||
#include "qcommon.h"
|
||||
|
||||
#define MAX_CMD_BUFFER 8192
|
||||
int cmd_wait;
|
||||
msg_t cmd_text;
|
||||
byte cmd_text_buf[MAX_CMD_BUFFER];
|
||||
char cmd_defer_text_buf[MAX_CMD_BUFFER];
|
||||
|
||||
|
||||
//=============================================================================
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_Wait_f
|
||||
|
||||
Causes execution of the remainder of the command buffer to be delayed until
|
||||
next frame. This allows commands like:
|
||||
bind g "cmd use rocket ; +attack ; wait ; -attack ; cmd use blaster"
|
||||
============
|
||||
*/
|
||||
void Cmd_Wait_f( void ) {
|
||||
if ( Cmd_Argc() == 2 ) {
|
||||
cmd_wait = atoi( Cmd_Argv( 1 ) );
|
||||
} else {
|
||||
cmd_wait = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============================================================================
|
||||
|
||||
COMMAND BUFFER
|
||||
|
||||
=============================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
============
|
||||
Cbuf_Init
|
||||
============
|
||||
*/
|
||||
void Cbuf_Init (void)
|
||||
{
|
||||
MSG_Init (&cmd_text, cmd_text_buf, sizeof(cmd_text_buf));
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
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;
|
||||
}
|
||||
MSG_WriteData (&cmd_text, text, strlen (text));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cbuf_InsertText
|
||||
|
||||
Adds command text immediately after the current command
|
||||
Adds a \n to the text
|
||||
============
|
||||
*/
|
||||
void Cbuf_InsertText( const char *text ) {
|
||||
int len;
|
||||
int i;
|
||||
|
||||
len = strlen( text ) + 1;
|
||||
if ( len + cmd_text.cursize > cmd_text.maxsize ) {
|
||||
Com_Printf( "Cbuf_InsertText overflowed\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
// move the existing command text
|
||||
for ( i = cmd_text.cursize - 1 ; i >= 0 ; i-- ) {
|
||||
cmd_text.data[ i + len ] = cmd_text.data[ i ];
|
||||
}
|
||||
|
||||
// copy the new text in
|
||||
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:
|
||||
Cmd_ExecuteString (text);
|
||||
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_BUFFER];
|
||||
int quotes;
|
||||
|
||||
while (cmd_text.cursize)
|
||||
{
|
||||
if ( cmd_wait ) {
|
||||
// skip out while text still remains in buffer, leaving it
|
||||
// for next frame
|
||||
cmd_wait--;
|
||||
break;
|
||||
}
|
||||
|
||||
// find a \n or ; line break
|
||||
text = (char *)cmd_text.data;
|
||||
|
||||
quotes = 0;
|
||||
for (i=0 ; i< cmd_text.cursize ; i++)
|
||||
{
|
||||
if (text[i] == '"')
|
||||
quotes++;
|
||||
if ( !(quotes&1) && text[i] == ';')
|
||||
break; // don't break if inside a quoted string
|
||||
if (text[i] == '\n' || text[i] == '\r' )
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
text[cmd_text.cursize] = 0;
|
||||
}
|
||||
|
||||
// execute the command line
|
||||
Cmd_ExecuteString (line);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
SCRIPT COMMANDS
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
Cmd_Exec_f
|
||||
===============
|
||||
*/
|
||||
void Cmd_Exec_f( void ) {
|
||||
fileHandle_t f;
|
||||
int len;
|
||||
char filename[MAX_QPATH];
|
||||
char *buffer;
|
||||
|
||||
if (Cmd_Argc () != 2) {
|
||||
Com_Printf ("exec <filename> : execute a script file\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) );
|
||||
COM_DefaultExtension( filename, sizeof( filename ), ".cfg" );
|
||||
len = FS_FOpenFileRead( filename, &f, qfalse );
|
||||
if ( len <= 0 ) {
|
||||
Com_Printf ("Couldn't read %s.\n", filename );
|
||||
return;
|
||||
}
|
||||
|
||||
buffer = Z_Malloc(len+1);
|
||||
|
||||
if (FS_Read(buffer, len, f) == len) {
|
||||
FS_FCloseFile( f );
|
||||
buffer[len] = 0;
|
||||
Com_Printf ("execing %s\n",Cmd_Argv(1));
|
||||
Cbuf_InsertText (buffer);
|
||||
}
|
||||
else {
|
||||
Com_Printf ("couldn't exec %s\n",Cmd_Argv(1));
|
||||
}
|
||||
|
||||
Z_Free(buffer);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
Cmd_Vstr_f
|
||||
|
||||
Inserts the current value of a variable as command text
|
||||
===============
|
||||
*/
|
||||
void Cmd_Vstr_f( void ) {
|
||||
char *v;
|
||||
|
||||
if (Cmd_Argc () != 2) {
|
||||
Com_Printf ("vstr <variablename> : execute a variable command\n");
|
||||
return;
|
||||
}
|
||||
|
||||
v = Cvar_VariableString( Cmd_Argv( 1 ) );
|
||||
Cbuf_InsertText( va("%s\n", v ) );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
Cmd_Echo_f
|
||||
|
||||
Just prints the rest of the line to the console
|
||||
===============
|
||||
*/
|
||||
void Cmd_Echo_f (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=1 ; i<Cmd_Argc() ; i++)
|
||||
Com_Printf ("%s ",Cmd_Argv(i));
|
||||
Com_Printf ("\n");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=============================================================================
|
||||
|
||||
COMMAND EXECUTION
|
||||
|
||||
=============================================================================
|
||||
*/
|
||||
|
||||
typedef struct cmd_function_s
|
||||
{
|
||||
struct cmd_function_s *next;
|
||||
char *name;
|
||||
xcommand_t function;
|
||||
} cmd_function_t;
|
||||
|
||||
|
||||
static int cmd_argc;
|
||||
static char *cmd_argv[MAX_STRING_TOKENS]; // points into cmd_tokenized
|
||||
static char cmd_tokenized[MAX_STRING_CHARS+MAX_STRING_TOKENS]; // will have 0 bytes inserted
|
||||
|
||||
static cmd_function_t *cmd_functions; // possible commands to execute
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_Argc
|
||||
============
|
||||
*/
|
||||
int Cmd_Argc( void ) {
|
||||
return cmd_argc;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_Argv
|
||||
============
|
||||
*/
|
||||
char *Cmd_Argv( int arg ) {
|
||||
if ( (unsigned)arg >= cmd_argc ) {
|
||||
return "";
|
||||
}
|
||||
return cmd_argv[arg];
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_ArgvBuffer
|
||||
|
||||
The interpreted versions use this because
|
||||
they can't have pointers returned to them
|
||||
============
|
||||
*/
|
||||
void Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength ) {
|
||||
Q_strncpyz( buffer, Cmd_Argv( arg ), bufferLength );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_Args
|
||||
|
||||
Returns a single string containing argv(1) to argv(argc()-1)
|
||||
============
|
||||
*/
|
||||
char *Cmd_Args( void ) {
|
||||
static char cmd_args[MAX_STRING_CHARS];
|
||||
int i;
|
||||
|
||||
cmd_args[0] = 0;
|
||||
for ( i = 1 ; i < cmd_argc ; i++ ) {
|
||||
strcat( cmd_args, cmd_argv[i] );
|
||||
if ( i != cmd_argc ) {
|
||||
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_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.
|
||||
============
|
||||
*/
|
||||
void Cmd_TokenizeString( const char *text_in ) {
|
||||
const char *text;
|
||||
char *textOut;
|
||||
|
||||
// clear previous args
|
||||
cmd_argc = 0;
|
||||
|
||||
if ( !text_in ) {
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
if ( *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 ( 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_AddCommand
|
||||
============
|
||||
*/
|
||||
void Cmd_AddCommand( const char *cmd_name, xcommand_t function ) {
|
||||
cmd_function_t *cmd;
|
||||
|
||||
// fail if the command already exists
|
||||
for ( cmd = cmd_functions ; cmd ; cmd=cmd->next ) {
|
||||
if ( !strcmp( cmd_name, cmd->name ) ) {
|
||||
// allow completion-only commands to be silently doubled
|
||||
if ( function != NULL ) {
|
||||
Com_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cmd = Z_Malloc (sizeof(cmd_function_t));
|
||||
cmd->name = CopyString( cmd_name );
|
||||
cmd->function = function;
|
||||
cmd->next = cmd_functions;
|
||||
cmd_functions = cmd;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_RemoveCommand
|
||||
============
|
||||
*/
|
||||
void Cmd_RemoveCommand( const char *cmd_name ) {
|
||||
cmd_function_t *cmd, **back;
|
||||
|
||||
back = &cmd_functions;
|
||||
while( 1 ) {
|
||||
cmd = *back;
|
||||
if ( !cmd ) {
|
||||
// command wasn't active
|
||||
return;
|
||||
}
|
||||
if ( !strcmp( cmd_name, cmd->name ) ) {
|
||||
*back = cmd->next;
|
||||
Z_Free (cmd);
|
||||
return;
|
||||
}
|
||||
back = &cmd->next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_CommandCompletion
|
||||
============
|
||||
*/
|
||||
void Cmd_CommandCompletion( void(*callback)(const char *s) ) {
|
||||
cmd_function_t *cmd;
|
||||
|
||||
for (cmd=cmd_functions ; cmd ; cmd=cmd->next) {
|
||||
callback( cmd->name );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_ExecuteString
|
||||
|
||||
A complete command line has been parsed, so try to execute it
|
||||
============
|
||||
*/
|
||||
void Cmd_ExecuteString( const char *text ) {
|
||||
cmd_function_t *cmd, **prev;
|
||||
|
||||
// execute the command line
|
||||
Cmd_TokenizeString( text );
|
||||
if ( !Cmd_Argc() ) {
|
||||
return; // no tokens
|
||||
}
|
||||
|
||||
// check registered command functions
|
||||
for ( prev = &cmd_functions ; *prev ; prev = &cmd->next ) {
|
||||
cmd = *prev;
|
||||
if ( !Q_stricmp( cmd_argv[0],cmd->name ) ) {
|
||||
// rearrange the links so that the command will be
|
||||
// near the head of the list next time it is used
|
||||
*prev = cmd->next;
|
||||
cmd->next = cmd_functions;
|
||||
cmd_functions = cmd;
|
||||
|
||||
// perform the action
|
||||
if ( !cmd->function ) {
|
||||
// let the cgame or game handle it
|
||||
break;
|
||||
} else {
|
||||
cmd->function ();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check cvars
|
||||
if ( Cvar_Command() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check client game commands
|
||||
if ( com_cl_running && com_cl_running->integer && CL_GameCommand() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check server game commands
|
||||
if ( com_sv_running && com_sv_running->integer && SV_GameCommand() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check ui commands
|
||||
if ( com_cl_running && com_cl_running->integer && UI_GameCommand() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// send it as a server command if we are connected
|
||||
// this will usually result in a chat message
|
||||
CL_ForwardCommandToServer ();
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_List_f
|
||||
============
|
||||
*/
|
||||
void Cmd_List_f (void)
|
||||
{
|
||||
cmd_function_t *cmd;
|
||||
int i;
|
||||
char *match;
|
||||
|
||||
if ( Cmd_Argc() > 1 ) {
|
||||
match = Cmd_Argv( 1 );
|
||||
} else {
|
||||
match = NULL;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
for (cmd=cmd_functions ; cmd ; cmd=cmd->next) {
|
||||
if (match && !Com_Filter(match, cmd->name, qfalse)) continue;
|
||||
|
||||
Com_Printf ("%s\n", cmd->name);
|
||||
i++;
|
||||
}
|
||||
Com_Printf ("%i commands\n", i);
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cmd_Init
|
||||
============
|
||||
*/
|
||||
void Cmd_Init (void) {
|
||||
Cmd_AddCommand ("cmdlist",Cmd_List_f);
|
||||
Cmd_AddCommand ("exec",Cmd_Exec_f);
|
||||
Cmd_AddCommand ("vstr",Cmd_Vstr_f);
|
||||
Cmd_AddCommand ("echo",Cmd_Echo_f);
|
||||
Cmd_AddCommand ("wait", Cmd_Wait_f);
|
||||
}
|
||||
|
3317
code/qcommon/common.c
Normal file
3317
code/qcommon/common.c
Normal file
File diff suppressed because it is too large
Load diff
906
code/qcommon/cvar.c
Normal file
906
code/qcommon/cvar.c
Normal file
|
@ -0,0 +1,906 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code 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 Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
// cvar.c -- dynamic variable tracking
|
||||
|
||||
#include "../game/q_shared.h"
|
||||
#include "qcommon.h"
|
||||
|
||||
cvar_t *cvar_vars;
|
||||
cvar_t *cvar_cheats;
|
||||
int cvar_modifiedFlags;
|
||||
|
||||
#define MAX_CVARS 1024
|
||||
cvar_t cvar_indexes[MAX_CVARS];
|
||||
int cvar_numIndexes;
|
||||
|
||||
#define FILE_HASH_SIZE 256
|
||||
static cvar_t* hashTable[FILE_HASH_SIZE];
|
||||
|
||||
cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force);
|
||||
|
||||
/*
|
||||
================
|
||||
return a hash value for the filename
|
||||
================
|
||||
*/
|
||||
static long generateHashValue( const char *fname ) {
|
||||
int i;
|
||||
long hash;
|
||||
char letter;
|
||||
|
||||
hash = 0;
|
||||
i = 0;
|
||||
while (fname[i] != '\0') {
|
||||
letter = tolower(fname[i]);
|
||||
hash+=(long)(letter)*(i+119);
|
||||
i++;
|
||||
}
|
||||
hash &= (FILE_HASH_SIZE-1);
|
||||
return hash;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_ValidateString
|
||||
============
|
||||
*/
|
||||
static qboolean Cvar_ValidateString( const char *s ) {
|
||||
if ( !s ) {
|
||||
return qfalse;
|
||||
}
|
||||
if ( strchr( s, '\\' ) ) {
|
||||
return qfalse;
|
||||
}
|
||||
if ( strchr( s, '\"' ) ) {
|
||||
return qfalse;
|
||||
}
|
||||
if ( strchr( s, ';' ) ) {
|
||||
return qfalse;
|
||||
}
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_FindVar
|
||||
============
|
||||
*/
|
||||
static cvar_t *Cvar_FindVar( const char *var_name ) {
|
||||
cvar_t *var;
|
||||
long hash;
|
||||
|
||||
hash = generateHashValue(var_name);
|
||||
|
||||
for (var=hashTable[hash] ; var ; var=var->hashNext) {
|
||||
if (!Q_stricmp(var_name, var->name)) {
|
||||
return var;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_VariableValue
|
||||
============
|
||||
*/
|
||||
float Cvar_VariableValue( const char *var_name ) {
|
||||
cvar_t *var;
|
||||
|
||||
var = Cvar_FindVar (var_name);
|
||||
if (!var)
|
||||
return 0;
|
||||
return var->value;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_VariableIntegerValue
|
||||
============
|
||||
*/
|
||||
int Cvar_VariableIntegerValue( const char *var_name ) {
|
||||
cvar_t *var;
|
||||
|
||||
var = Cvar_FindVar (var_name);
|
||||
if (!var)
|
||||
return 0;
|
||||
return var->integer;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_VariableString
|
||||
============
|
||||
*/
|
||||
char *Cvar_VariableString( const char *var_name ) {
|
||||
cvar_t *var;
|
||||
|
||||
var = Cvar_FindVar (var_name);
|
||||
if (!var)
|
||||
return "";
|
||||
return var->string;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_VariableStringBuffer
|
||||
============
|
||||
*/
|
||||
void Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) {
|
||||
cvar_t *var;
|
||||
|
||||
var = Cvar_FindVar (var_name);
|
||||
if (!var) {
|
||||
*buffer = 0;
|
||||
}
|
||||
else {
|
||||
Q_strncpyz( buffer, var->string, bufsize );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_CommandCompletion
|
||||
============
|
||||
*/
|
||||
void Cvar_CommandCompletion( void(*callback)(const char *s) ) {
|
||||
cvar_t *cvar;
|
||||
|
||||
for ( cvar = cvar_vars ; cvar ; cvar = cvar->next ) {
|
||||
callback( cvar->name );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_Get
|
||||
|
||||
If the variable already exists, the value will not be set unless CVAR_ROM
|
||||
The flags will be or'ed in if the variable exists.
|
||||
============
|
||||
*/
|
||||
cvar_t *Cvar_Get( const char *var_name, const char *var_value, int flags ) {
|
||||
cvar_t *var;
|
||||
long hash;
|
||||
|
||||
if ( !var_name || ! var_value ) {
|
||||
Com_Error( ERR_FATAL, "Cvar_Get: NULL parameter" );
|
||||
}
|
||||
|
||||
if ( !Cvar_ValidateString( var_name ) ) {
|
||||
Com_Printf("invalid cvar name string: %s\n", var_name );
|
||||
var_name = "BADNAME";
|
||||
}
|
||||
|
||||
#if 0 // FIXME: values with backslash happen
|
||||
if ( !Cvar_ValidateString( var_value ) ) {
|
||||
Com_Printf("invalid cvar value string: %s\n", var_value );
|
||||
var_value = "BADVALUE";
|
||||
}
|
||||
#endif
|
||||
|
||||
var = Cvar_FindVar (var_name);
|
||||
if ( var ) {
|
||||
// if the C code is now specifying a variable that the user already
|
||||
// set a value for, take the new value as the reset value
|
||||
if ( ( var->flags & CVAR_USER_CREATED ) && !( flags & CVAR_USER_CREATED )
|
||||
&& var_value[0] ) {
|
||||
var->flags &= ~CVAR_USER_CREATED;
|
||||
Z_Free( var->resetString );
|
||||
var->resetString = CopyString( var_value );
|
||||
|
||||
// ZOID--needs to be set so that cvars the game sets as
|
||||
// SERVERINFO get sent to clients
|
||||
cvar_modifiedFlags |= flags;
|
||||
}
|
||||
|
||||
var->flags |= flags;
|
||||
// only allow one non-empty reset string without a warning
|
||||
if ( !var->resetString[0] ) {
|
||||
// we don't have a reset string yet
|
||||
Z_Free( var->resetString );
|
||||
var->resetString = CopyString( var_value );
|
||||
} else if ( var_value[0] && strcmp( var->resetString, var_value ) ) {
|
||||
Com_DPrintf( "Warning: cvar \"%s\" given initial values: \"%s\" and \"%s\"\n",
|
||||
var_name, var->resetString, var_value );
|
||||
}
|
||||
// if we have a latched string, take that value now
|
||||
if ( var->latchedString ) {
|
||||
char *s;
|
||||
|
||||
s = var->latchedString;
|
||||
var->latchedString = NULL; // otherwise cvar_set2 would free it
|
||||
Cvar_Set2( var_name, s, qtrue );
|
||||
Z_Free( s );
|
||||
}
|
||||
|
||||
// use a CVAR_SET for rom sets, get won't override
|
||||
#if 0
|
||||
// CVAR_ROM always overrides
|
||||
if ( flags & CVAR_ROM ) {
|
||||
Cvar_Set2( var_name, var_value, qtrue );
|
||||
}
|
||||
#endif
|
||||
return var;
|
||||
}
|
||||
|
||||
//
|
||||
// allocate a new cvar
|
||||
//
|
||||
if ( cvar_numIndexes >= MAX_CVARS ) {
|
||||
Com_Error( ERR_FATAL, "MAX_CVARS" );
|
||||
}
|
||||
var = &cvar_indexes[cvar_numIndexes];
|
||||
cvar_numIndexes++;
|
||||
var->name = CopyString (var_name);
|
||||
var->string = CopyString (var_value);
|
||||
var->modified = qtrue;
|
||||
var->modificationCount = 1;
|
||||
var->value = atof (var->string);
|
||||
var->integer = atoi(var->string);
|
||||
var->resetString = CopyString( var_value );
|
||||
|
||||
// link the variable in
|
||||
var->next = cvar_vars;
|
||||
cvar_vars = var;
|
||||
|
||||
var->flags = flags;
|
||||
|
||||
hash = generateHashValue(var_name);
|
||||
var->hashNext = hashTable[hash];
|
||||
hashTable[hash] = var;
|
||||
|
||||
return var;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_Set2
|
||||
============
|
||||
*/
|
||||
cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force ) {
|
||||
cvar_t *var;
|
||||
|
||||
Com_DPrintf( "Cvar_Set2: %s %s\n", var_name, value );
|
||||
|
||||
if ( !Cvar_ValidateString( var_name ) ) {
|
||||
Com_Printf("invalid cvar name string: %s\n", var_name );
|
||||
var_name = "BADNAME";
|
||||
}
|
||||
|
||||
#if 0 // FIXME
|
||||
if ( value && !Cvar_ValidateString( value ) ) {
|
||||
Com_Printf("invalid cvar value string: %s\n", value );
|
||||
var_value = "BADVALUE";
|
||||
}
|
||||
#endif
|
||||
|
||||
var = Cvar_FindVar (var_name);
|
||||
if (!var) {
|
||||
if ( !value ) {
|
||||
return NULL;
|
||||
}
|
||||
// create it
|
||||
if ( !force ) {
|
||||
return Cvar_Get( var_name, value, CVAR_USER_CREATED );
|
||||
} else {
|
||||
return Cvar_Get (var_name, value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (!value ) {
|
||||
value = var->resetString;
|
||||
}
|
||||
|
||||
if (!strcmp(value,var->string)) {
|
||||
return var;
|
||||
}
|
||||
// note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo)
|
||||
cvar_modifiedFlags |= var->flags;
|
||||
|
||||
if (!force)
|
||||
{
|
||||
if (var->flags & CVAR_ROM)
|
||||
{
|
||||
Com_Printf ("%s is read only.\n", var_name);
|
||||
return var;
|
||||
}
|
||||
|
||||
if (var->flags & CVAR_INIT)
|
||||
{
|
||||
Com_Printf ("%s is write protected.\n", var_name);
|
||||
return var;
|
||||
}
|
||||
|
||||
if (var->flags & CVAR_LATCH)
|
||||
{
|
||||
if (var->latchedString)
|
||||
{
|
||||
if (strcmp(value, var->latchedString) == 0)
|
||||
return var;
|
||||
Z_Free (var->latchedString);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (strcmp(value, var->string) == 0)
|
||||
return var;
|
||||
}
|
||||
|
||||
Com_Printf ("%s will be changed upon restarting.\n", var_name);
|
||||
var->latchedString = CopyString(value);
|
||||
var->modified = qtrue;
|
||||
var->modificationCount++;
|
||||
return var;
|
||||
}
|
||||
|
||||
if ( (var->flags & CVAR_CHEAT) && !cvar_cheats->integer )
|
||||
{
|
||||
Com_Printf ("%s is cheat protected.\n", var_name);
|
||||
return var;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (var->latchedString)
|
||||
{
|
||||
Z_Free (var->latchedString);
|
||||
var->latchedString = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!strcmp(value, var->string))
|
||||
return var; // not changed
|
||||
|
||||
var->modified = qtrue;
|
||||
var->modificationCount++;
|
||||
|
||||
Z_Free (var->string); // free the old value string
|
||||
|
||||
var->string = CopyString(value);
|
||||
var->value = atof (var->string);
|
||||
var->integer = atoi (var->string);
|
||||
|
||||
return var;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_Set
|
||||
============
|
||||
*/
|
||||
void Cvar_Set( const char *var_name, const char *value) {
|
||||
Cvar_Set2 (var_name, value, qtrue);
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_SetLatched
|
||||
============
|
||||
*/
|
||||
void Cvar_SetLatched( const char *var_name, const char *value) {
|
||||
Cvar_Set2 (var_name, value, qfalse);
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_SetValue
|
||||
============
|
||||
*/
|
||||
void Cvar_SetValue( const char *var_name, float value) {
|
||||
char val[32];
|
||||
|
||||
if ( value == (int)value ) {
|
||||
Com_sprintf (val, sizeof(val), "%i",(int)value);
|
||||
} else {
|
||||
Com_sprintf (val, sizeof(val), "%f",value);
|
||||
}
|
||||
Cvar_Set (var_name, val);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_Reset
|
||||
============
|
||||
*/
|
||||
void Cvar_Reset( const char *var_name ) {
|
||||
Cvar_Set2( var_name, NULL, qfalse );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_SetCheatState
|
||||
|
||||
Any testing variables will be reset to the safe values
|
||||
============
|
||||
*/
|
||||
void Cvar_SetCheatState( void ) {
|
||||
cvar_t *var;
|
||||
|
||||
// set all default vars to the safe value
|
||||
for ( var = cvar_vars ; var ; var = var->next ) {
|
||||
if ( var->flags & CVAR_CHEAT ) {
|
||||
// the CVAR_LATCHED|CVAR_CHEAT vars might escape the reset here
|
||||
// because of a different var->latchedString
|
||||
if (var->latchedString)
|
||||
{
|
||||
Z_Free(var->latchedString);
|
||||
var->latchedString = NULL;
|
||||
}
|
||||
if (strcmp(var->resetString,var->string)) {
|
||||
Cvar_Set( var->name, var->resetString );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_Command
|
||||
|
||||
Handles variable inspection and changing from the console
|
||||
============
|
||||
*/
|
||||
qboolean Cvar_Command( void ) {
|
||||
cvar_t *v;
|
||||
|
||||
// check variables
|
||||
v = Cvar_FindVar (Cmd_Argv(0));
|
||||
if (!v) {
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
// perform a variable print or set
|
||||
if ( Cmd_Argc() == 1 ) {
|
||||
Com_Printf ("\"%s\" is:\"%s" S_COLOR_WHITE "\" default:\"%s" S_COLOR_WHITE "\"\n", v->name, v->string, v->resetString );
|
||||
if ( v->latchedString ) {
|
||||
Com_Printf( "latched: \"%s\"\n", v->latchedString );
|
||||
}
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
// set the value if forcing isn't required
|
||||
Cvar_Set2 (v->name, Cmd_Argv(1), qfalse);
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_Toggle_f
|
||||
|
||||
Toggles a cvar for easy single key binding
|
||||
============
|
||||
*/
|
||||
void Cvar_Toggle_f( void ) {
|
||||
int v;
|
||||
|
||||
if ( Cmd_Argc() != 2 ) {
|
||||
Com_Printf ("usage: toggle <variable>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
v = Cvar_VariableValue( Cmd_Argv( 1 ) );
|
||||
v = !v;
|
||||
|
||||
Cvar_Set2 (Cmd_Argv(1), va("%i", v), qfalse);
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_Set_f
|
||||
|
||||
Allows setting and defining of arbitrary cvars from console, even if they
|
||||
weren't declared in C code.
|
||||
============
|
||||
*/
|
||||
void Cvar_Set_f( void ) {
|
||||
int i, c, l, len;
|
||||
char combined[MAX_STRING_TOKENS];
|
||||
|
||||
c = Cmd_Argc();
|
||||
if ( c < 3 ) {
|
||||
Com_Printf ("usage: set <variable> <value>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
combined[0] = 0;
|
||||
l = 0;
|
||||
for ( i = 2 ; i < c ; i++ ) {
|
||||
len = strlen ( Cmd_Argv( i ) + 1 );
|
||||
if ( l + len >= MAX_STRING_TOKENS - 2 ) {
|
||||
break;
|
||||
}
|
||||
strcat( combined, Cmd_Argv( i ) );
|
||||
if ( i != c-1 ) {
|
||||
strcat( combined, " " );
|
||||
}
|
||||
l += len;
|
||||
}
|
||||
Cvar_Set2 (Cmd_Argv(1), combined, qfalse);
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_SetU_f
|
||||
|
||||
As Cvar_Set, but also flags it as userinfo
|
||||
============
|
||||
*/
|
||||
void Cvar_SetU_f( void ) {
|
||||
cvar_t *v;
|
||||
|
||||
if ( Cmd_Argc() != 3 ) {
|
||||
Com_Printf ("usage: setu <variable> <value>\n");
|
||||
return;
|
||||
}
|
||||
Cvar_Set_f();
|
||||
v = Cvar_FindVar( Cmd_Argv( 1 ) );
|
||||
if ( !v ) {
|
||||
return;
|
||||
}
|
||||
v->flags |= CVAR_USERINFO;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_SetS_f
|
||||
|
||||
As Cvar_Set, but also flags it as userinfo
|
||||
============
|
||||
*/
|
||||
void Cvar_SetS_f( void ) {
|
||||
cvar_t *v;
|
||||
|
||||
if ( Cmd_Argc() != 3 ) {
|
||||
Com_Printf ("usage: sets <variable> <value>\n");
|
||||
return;
|
||||
}
|
||||
Cvar_Set_f();
|
||||
v = Cvar_FindVar( Cmd_Argv( 1 ) );
|
||||
if ( !v ) {
|
||||
return;
|
||||
}
|
||||
v->flags |= CVAR_SERVERINFO;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_SetA_f
|
||||
|
||||
As Cvar_Set, but also flags it as archived
|
||||
============
|
||||
*/
|
||||
void Cvar_SetA_f( void ) {
|
||||
cvar_t *v;
|
||||
|
||||
if ( Cmd_Argc() != 3 ) {
|
||||
Com_Printf ("usage: seta <variable> <value>\n");
|
||||
return;
|
||||
}
|
||||
Cvar_Set_f();
|
||||
v = Cvar_FindVar( Cmd_Argv( 1 ) );
|
||||
if ( !v ) {
|
||||
return;
|
||||
}
|
||||
v->flags |= CVAR_ARCHIVE;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_Reset_f
|
||||
============
|
||||
*/
|
||||
void Cvar_Reset_f( void ) {
|
||||
if ( Cmd_Argc() != 2 ) {
|
||||
Com_Printf ("usage: reset <variable>\n");
|
||||
return;
|
||||
}
|
||||
Cvar_Reset( Cmd_Argv( 1 ) );
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_WriteVariables
|
||||
|
||||
Appends lines containing "set variable value" for all variables
|
||||
with the archive flag set to qtrue.
|
||||
============
|
||||
*/
|
||||
void Cvar_WriteVariables( fileHandle_t f ) {
|
||||
cvar_t *var;
|
||||
char buffer[1024];
|
||||
|
||||
for (var = cvar_vars ; var ; var = var->next) {
|
||||
if( Q_stricmp( var->name, "cl_cdkey" ) == 0 ) {
|
||||
continue;
|
||||
}
|
||||
if( var->flags & CVAR_ARCHIVE ) {
|
||||
// write the latched value, even if it hasn't taken effect yet
|
||||
if ( var->latchedString ) {
|
||||
Com_sprintf (buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->latchedString);
|
||||
} else {
|
||||
Com_sprintf (buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->string);
|
||||
}
|
||||
FS_Printf (f, "%s", buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_List_f
|
||||
============
|
||||
*/
|
||||
void Cvar_List_f( void ) {
|
||||
cvar_t *var;
|
||||
int i;
|
||||
char *match;
|
||||
|
||||
if ( Cmd_Argc() > 1 ) {
|
||||
match = Cmd_Argv( 1 );
|
||||
} else {
|
||||
match = NULL;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
for (var = cvar_vars ; var ; var = var->next, i++)
|
||||
{
|
||||
if (match && !Com_Filter(match, var->name, qfalse)) continue;
|
||||
|
||||
if (var->flags & CVAR_SERVERINFO) {
|
||||
Com_Printf("S");
|
||||
} else {
|
||||
Com_Printf(" ");
|
||||
}
|
||||
if (var->flags & CVAR_USERINFO) {
|
||||
Com_Printf("U");
|
||||
} else {
|
||||
Com_Printf(" ");
|
||||
}
|
||||
if (var->flags & CVAR_ROM) {
|
||||
Com_Printf("R");
|
||||
} else {
|
||||
Com_Printf(" ");
|
||||
}
|
||||
if (var->flags & CVAR_INIT) {
|
||||
Com_Printf("I");
|
||||
} else {
|
||||
Com_Printf(" ");
|
||||
}
|
||||
if (var->flags & CVAR_ARCHIVE) {
|
||||
Com_Printf("A");
|
||||
} else {
|
||||
Com_Printf(" ");
|
||||
}
|
||||
if (var->flags & CVAR_LATCH) {
|
||||
Com_Printf("L");
|
||||
} else {
|
||||
Com_Printf(" ");
|
||||
}
|
||||
if (var->flags & CVAR_CHEAT) {
|
||||
Com_Printf("C");
|
||||
} else {
|
||||
Com_Printf(" ");
|
||||
}
|
||||
|
||||
Com_Printf (" %s \"%s\"\n", var->name, var->string);
|
||||
}
|
||||
|
||||
Com_Printf ("\n%i total cvars\n", i);
|
||||
Com_Printf ("%i cvar indexes\n", cvar_numIndexes);
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_Restart_f
|
||||
|
||||
Resets all cvars to their hardcoded values
|
||||
============
|
||||
*/
|
||||
void Cvar_Restart_f( void ) {
|
||||
cvar_t *var;
|
||||
cvar_t **prev;
|
||||
|
||||
prev = &cvar_vars;
|
||||
while ( 1 ) {
|
||||
var = *prev;
|
||||
if ( !var ) {
|
||||
break;
|
||||
}
|
||||
|
||||
// don't mess with rom values, or some inter-module
|
||||
// communication will get broken (com_cl_running, etc)
|
||||
if ( var->flags & ( CVAR_ROM | CVAR_INIT | CVAR_NORESTART ) ) {
|
||||
prev = &var->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
// throw out any variables the user created
|
||||
if ( var->flags & CVAR_USER_CREATED ) {
|
||||
*prev = var->next;
|
||||
if ( var->name ) {
|
||||
Z_Free( var->name );
|
||||
}
|
||||
if ( var->string ) {
|
||||
Z_Free( var->string );
|
||||
}
|
||||
if ( var->latchedString ) {
|
||||
Z_Free( var->latchedString );
|
||||
}
|
||||
if ( var->resetString ) {
|
||||
Z_Free( var->resetString );
|
||||
}
|
||||
// clear the var completely, since we
|
||||
// can't remove the index from the list
|
||||
Com_Memset( var, 0, sizeof( var ) );
|
||||
continue;
|
||||
}
|
||||
|
||||
Cvar_Set( var->name, var->resetString );
|
||||
|
||||
prev = &var->next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
=====================
|
||||
Cvar_InfoString
|
||||
=====================
|
||||
*/
|
||||
char *Cvar_InfoString( int bit ) {
|
||||
static char info[MAX_INFO_STRING];
|
||||
cvar_t *var;
|
||||
|
||||
info[0] = 0;
|
||||
|
||||
for (var = cvar_vars ; var ; var = var->next) {
|
||||
if (var->flags & bit) {
|
||||
Info_SetValueForKey (info, var->name, var->string);
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
/*
|
||||
=====================
|
||||
Cvar_InfoString_Big
|
||||
|
||||
handles large info strings ( CS_SYSTEMINFO )
|
||||
=====================
|
||||
*/
|
||||
char *Cvar_InfoString_Big( int bit ) {
|
||||
static char info[BIG_INFO_STRING];
|
||||
cvar_t *var;
|
||||
|
||||
info[0] = 0;
|
||||
|
||||
for (var = cvar_vars ; var ; var = var->next) {
|
||||
if (var->flags & bit) {
|
||||
Info_SetValueForKey_Big (info, var->name, var->string);
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
=====================
|
||||
Cvar_InfoStringBuffer
|
||||
=====================
|
||||
*/
|
||||
void Cvar_InfoStringBuffer( int bit, char* buff, int buffsize ) {
|
||||
Q_strncpyz(buff,Cvar_InfoString(bit),buffsize);
|
||||
}
|
||||
|
||||
/*
|
||||
=====================
|
||||
Cvar_Register
|
||||
|
||||
basically a slightly modified Cvar_Get for the interpreted modules
|
||||
=====================
|
||||
*/
|
||||
void Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ) {
|
||||
cvar_t *cv;
|
||||
|
||||
cv = Cvar_Get( varName, defaultValue, flags );
|
||||
if ( !vmCvar ) {
|
||||
return;
|
||||
}
|
||||
vmCvar->handle = cv - cvar_indexes;
|
||||
vmCvar->modificationCount = -1;
|
||||
Cvar_Update( vmCvar );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=====================
|
||||
Cvar_Register
|
||||
|
||||
updates an interpreted modules' version of a cvar
|
||||
=====================
|
||||
*/
|
||||
void Cvar_Update( vmCvar_t *vmCvar ) {
|
||||
cvar_t *cv = NULL; // bk001129
|
||||
assert(vmCvar); // bk
|
||||
|
||||
if ( (unsigned)vmCvar->handle >= cvar_numIndexes ) {
|
||||
Com_Error( ERR_DROP, "Cvar_Update: handle out of range" );
|
||||
}
|
||||
|
||||
cv = cvar_indexes + vmCvar->handle;
|
||||
|
||||
if ( cv->modificationCount == vmCvar->modificationCount ) {
|
||||
return;
|
||||
}
|
||||
if ( !cv->string ) {
|
||||
return; // variable might have been cleared by a cvar_restart
|
||||
}
|
||||
vmCvar->modificationCount = cv->modificationCount;
|
||||
// bk001129 - mismatches.
|
||||
if ( strlen(cv->string)+1 > MAX_CVAR_VALUE_STRING )
|
||||
Com_Error( ERR_DROP, "Cvar_Update: src %s length %d exceeds MAX_CVAR_VALUE_STRING",
|
||||
cv->string,
|
||||
strlen(cv->string),
|
||||
sizeof(vmCvar->string) );
|
||||
// bk001212 - Q_strncpyz guarantees zero padding and dest[MAX_CVAR_VALUE_STRING-1]==0
|
||||
// bk001129 - paranoia. Never trust the destination string.
|
||||
// bk001129 - beware, sizeof(char*) is always 4 (for cv->string).
|
||||
// sizeof(vmCvar->string) always MAX_CVAR_VALUE_STRING
|
||||
//Q_strncpyz( vmCvar->string, cv->string, sizeof( vmCvar->string ) ); // id
|
||||
Q_strncpyz( vmCvar->string, cv->string, MAX_CVAR_VALUE_STRING );
|
||||
|
||||
vmCvar->value = cv->value;
|
||||
vmCvar->integer = cv->integer;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============
|
||||
Cvar_Init
|
||||
|
||||
Reads in all archived cvars
|
||||
============
|
||||
*/
|
||||
void Cvar_Init (void) {
|
||||
cvar_cheats = Cvar_Get("sv_cheats", "1", CVAR_ROM | CVAR_SYSTEMINFO );
|
||||
|
||||
Cmd_AddCommand ("toggle", Cvar_Toggle_f);
|
||||
Cmd_AddCommand ("set", Cvar_Set_f);
|
||||
Cmd_AddCommand ("sets", Cvar_SetS_f);
|
||||
Cmd_AddCommand ("setu", Cvar_SetU_f);
|
||||
Cmd_AddCommand ("seta", Cvar_SetA_f);
|
||||
Cmd_AddCommand ("reset", Cvar_Reset_f);
|
||||
Cmd_AddCommand ("cvarlist", Cvar_List_f);
|
||||
Cmd_AddCommand ("cvar_restart", Cvar_Restart_f);
|
||||
}
|
3419
code/qcommon/files.c
Normal file
3419
code/qcommon/files.c
Normal file
File diff suppressed because it is too large
Load diff
437
code/qcommon/huffman.c
Normal file
437
code/qcommon/huffman.c
Normal file
|
@ -0,0 +1,437 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code 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 Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
/* This is based on the Adaptive Huffman algorithm described in Sayood's Data
|
||||
* Compression book. The ranks are not actually stored, but implicitly defined
|
||||
* by the location of a node within a doubly-linked list */
|
||||
|
||||
#include "../game/q_shared.h"
|
||||
#include "qcommon.h"
|
||||
|
||||
static int bloc = 0;
|
||||
|
||||
void Huff_putBit( int bit, byte *fout, int *offset) {
|
||||
bloc = *offset;
|
||||
if ((bloc&7) == 0) {
|
||||
fout[(bloc>>3)] = 0;
|
||||
}
|
||||
fout[(bloc>>3)] |= bit << (bloc&7);
|
||||
bloc++;
|
||||
*offset = bloc;
|
||||
}
|
||||
|
||||
int Huff_getBit( byte *fin, int *offset) {
|
||||
int t;
|
||||
bloc = *offset;
|
||||
t = (fin[(bloc>>3)] >> (bloc&7)) & 0x1;
|
||||
bloc++;
|
||||
*offset = bloc;
|
||||
return t;
|
||||
}
|
||||
|
||||
/* Add a bit to the output file (buffered) */
|
||||
static void add_bit (char bit, byte *fout) {
|
||||
if ((bloc&7) == 0) {
|
||||
fout[(bloc>>3)] = 0;
|
||||
}
|
||||
fout[(bloc>>3)] |= bit << (bloc&7);
|
||||
bloc++;
|
||||
}
|
||||
|
||||
/* Receive one bit from the input file (buffered) */
|
||||
static int get_bit (byte *fin) {
|
||||
int t;
|
||||
t = (fin[(bloc>>3)] >> (bloc&7)) & 0x1;
|
||||
bloc++;
|
||||
return t;
|
||||
}
|
||||
|
||||
static node_t **get_ppnode(huff_t* huff) {
|
||||
node_t **tppnode;
|
||||
if (!huff->freelist) {
|
||||
return &(huff->nodePtrs[huff->blocPtrs++]);
|
||||
} else {
|
||||
tppnode = huff->freelist;
|
||||
huff->freelist = (node_t **)*tppnode;
|
||||
return tppnode;
|
||||
}
|
||||
}
|
||||
|
||||
static void free_ppnode(huff_t* huff, node_t **ppnode) {
|
||||
*ppnode = (node_t *)huff->freelist;
|
||||
huff->freelist = ppnode;
|
||||
}
|
||||
|
||||
/* Swap the location of these two nodes in the tree */
|
||||
static void swap (huff_t* huff, node_t *node1, node_t *node2) {
|
||||
node_t *par1, *par2;
|
||||
|
||||
par1 = node1->parent;
|
||||
par2 = node2->parent;
|
||||
|
||||
if (par1) {
|
||||
if (par1->left == node1) {
|
||||
par1->left = node2;
|
||||
} else {
|
||||
par1->right = node2;
|
||||
}
|
||||
} else {
|
||||
huff->tree = node2;
|
||||
}
|
||||
|
||||
if (par2) {
|
||||
if (par2->left == node2) {
|
||||
par2->left = node1;
|
||||
} else {
|
||||
par2->right = node1;
|
||||
}
|
||||
} else {
|
||||
huff->tree = node1;
|
||||
}
|
||||
|
||||
node1->parent = par2;
|
||||
node2->parent = par1;
|
||||
}
|
||||
|
||||
/* Swap these two nodes in the linked list (update ranks) */
|
||||
static void swaplist(node_t *node1, node_t *node2) {
|
||||
node_t *par1;
|
||||
|
||||
par1 = node1->next;
|
||||
node1->next = node2->next;
|
||||
node2->next = par1;
|
||||
|
||||
par1 = node1->prev;
|
||||
node1->prev = node2->prev;
|
||||
node2->prev = par1;
|
||||
|
||||
if (node1->next == node1) {
|
||||
node1->next = node2;
|
||||
}
|
||||
if (node2->next == node2) {
|
||||
node2->next = node1;
|
||||
}
|
||||
if (node1->next) {
|
||||
node1->next->prev = node1;
|
||||
}
|
||||
if (node2->next) {
|
||||
node2->next->prev = node2;
|
||||
}
|
||||
if (node1->prev) {
|
||||
node1->prev->next = node1;
|
||||
}
|
||||
if (node2->prev) {
|
||||
node2->prev->next = node2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Do the increments */
|
||||
static void increment(huff_t* huff, node_t *node) {
|
||||
node_t *lnode;
|
||||
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node->next != NULL && node->next->weight == node->weight) {
|
||||
lnode = *node->head;
|
||||
if (lnode != node->parent) {
|
||||
swap(huff, lnode, node);
|
||||
}
|
||||
swaplist(lnode, node);
|
||||
}
|
||||
if (node->prev && node->prev->weight == node->weight) {
|
||||
*node->head = node->prev;
|
||||
} else {
|
||||
*node->head = NULL;
|
||||
free_ppnode(huff, node->head);
|
||||
}
|
||||
node->weight++;
|
||||
if (node->next && node->next->weight == node->weight) {
|
||||
node->head = node->next->head;
|
||||
} else {
|
||||
node->head = get_ppnode(huff);
|
||||
*node->head = node;
|
||||
}
|
||||
if (node->parent) {
|
||||
increment(huff, node->parent);
|
||||
if (node->prev == node->parent) {
|
||||
swaplist(node, node->parent);
|
||||
if (*node->head == node) {
|
||||
*node->head = node->parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Huff_addRef(huff_t* huff, byte ch) {
|
||||
node_t *tnode, *tnode2;
|
||||
if (huff->loc[ch] == NULL) { /* if this is the first transmission of this node */
|
||||
tnode = &(huff->nodeList[huff->blocNode++]);
|
||||
tnode2 = &(huff->nodeList[huff->blocNode++]);
|
||||
|
||||
tnode2->symbol = INTERNAL_NODE;
|
||||
tnode2->weight = 1;
|
||||
tnode2->next = huff->lhead->next;
|
||||
if (huff->lhead->next) {
|
||||
huff->lhead->next->prev = tnode2;
|
||||
if (huff->lhead->next->weight == 1) {
|
||||
tnode2->head = huff->lhead->next->head;
|
||||
} else {
|
||||
tnode2->head = get_ppnode(huff);
|
||||
*tnode2->head = tnode2;
|
||||
}
|
||||
} else {
|
||||
tnode2->head = get_ppnode(huff);
|
||||
*tnode2->head = tnode2;
|
||||
}
|
||||
huff->lhead->next = tnode2;
|
||||
tnode2->prev = huff->lhead;
|
||||
|
||||
tnode->symbol = ch;
|
||||
tnode->weight = 1;
|
||||
tnode->next = huff->lhead->next;
|
||||
if (huff->lhead->next) {
|
||||
huff->lhead->next->prev = tnode;
|
||||
if (huff->lhead->next->weight == 1) {
|
||||
tnode->head = huff->lhead->next->head;
|
||||
} else {
|
||||
/* this should never happen */
|
||||
tnode->head = get_ppnode(huff);
|
||||
*tnode->head = tnode2;
|
||||
}
|
||||
} else {
|
||||
/* this should never happen */
|
||||
tnode->head = get_ppnode(huff);
|
||||
*tnode->head = tnode;
|
||||
}
|
||||
huff->lhead->next = tnode;
|
||||
tnode->prev = huff->lhead;
|
||||
tnode->left = tnode->right = NULL;
|
||||
|
||||
if (huff->lhead->parent) {
|
||||
if (huff->lhead->parent->left == huff->lhead) { /* lhead is guaranteed to by the NYT */
|
||||
huff->lhead->parent->left = tnode2;
|
||||
} else {
|
||||
huff->lhead->parent->right = tnode2;
|
||||
}
|
||||
} else {
|
||||
huff->tree = tnode2;
|
||||
}
|
||||
|
||||
tnode2->right = tnode;
|
||||
tnode2->left = huff->lhead;
|
||||
|
||||
tnode2->parent = huff->lhead->parent;
|
||||
huff->lhead->parent = tnode->parent = tnode2;
|
||||
|
||||
huff->loc[ch] = tnode;
|
||||
|
||||
increment(huff, tnode2->parent);
|
||||
} else {
|
||||
increment(huff, huff->loc[ch]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Get a symbol */
|
||||
int Huff_Receive (node_t *node, int *ch, byte *fin) {
|
||||
while (node && node->symbol == INTERNAL_NODE) {
|
||||
if (get_bit(fin)) {
|
||||
node = node->right;
|
||||
} else {
|
||||
node = node->left;
|
||||
}
|
||||
}
|
||||
if (!node) {
|
||||
return 0;
|
||||
// Com_Error(ERR_DROP, "Illegal tree!\n");
|
||||
}
|
||||
return (*ch = node->symbol);
|
||||
}
|
||||
|
||||
/* Get a symbol */
|
||||
void Huff_offsetReceive (node_t *node, int *ch, byte *fin, int *offset) {
|
||||
bloc = *offset;
|
||||
while (node && node->symbol == INTERNAL_NODE) {
|
||||
if (get_bit(fin)) {
|
||||
node = node->right;
|
||||
} else {
|
||||
node = node->left;
|
||||
}
|
||||
}
|
||||
if (!node) {
|
||||
*ch = 0;
|
||||
return;
|
||||
// Com_Error(ERR_DROP, "Illegal tree!\n");
|
||||
}
|
||||
*ch = node->symbol;
|
||||
*offset = bloc;
|
||||
}
|
||||
|
||||
/* Send the prefix code for this node */
|
||||
static void send(node_t *node, node_t *child, byte *fout) {
|
||||
if (node->parent) {
|
||||
send(node->parent, node, fout);
|
||||
}
|
||||
if (child) {
|
||||
if (node->right == child) {
|
||||
add_bit(1, fout);
|
||||
} else {
|
||||
add_bit(0, fout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Send a symbol */
|
||||
void Huff_transmit (huff_t *huff, int ch, byte *fout) {
|
||||
int i;
|
||||
if (huff->loc[ch] == NULL) {
|
||||
/* node_t hasn't been transmitted, send a NYT, then the symbol */
|
||||
Huff_transmit(huff, NYT, fout);
|
||||
for (i = 7; i >= 0; i--) {
|
||||
add_bit((char)((ch >> i) & 0x1), fout);
|
||||
}
|
||||
} else {
|
||||
send(huff->loc[ch], NULL, fout);
|
||||
}
|
||||
}
|
||||
|
||||
void Huff_offsetTransmit (huff_t *huff, int ch, byte *fout, int *offset) {
|
||||
bloc = *offset;
|
||||
send(huff->loc[ch], NULL, fout);
|
||||
*offset = bloc;
|
||||
}
|
||||
|
||||
void Huff_Decompress(msg_t *mbuf, int offset) {
|
||||
int ch, cch, i, j, size;
|
||||
byte seq[65536];
|
||||
byte* buffer;
|
||||
huff_t huff;
|
||||
|
||||
size = mbuf->cursize - offset;
|
||||
buffer = mbuf->data + offset;
|
||||
|
||||
if ( size <= 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Com_Memset(&huff, 0, sizeof(huff_t));
|
||||
// Initialize the tree & list with the NYT node
|
||||
huff.tree = huff.lhead = huff.ltail = huff.loc[NYT] = &(huff.nodeList[huff.blocNode++]);
|
||||
huff.tree->symbol = NYT;
|
||||
huff.tree->weight = 0;
|
||||
huff.lhead->next = huff.lhead->prev = NULL;
|
||||
huff.tree->parent = huff.tree->left = huff.tree->right = NULL;
|
||||
|
||||
cch = buffer[0]*256 + buffer[1];
|
||||
// don't overflow with bad messages
|
||||
if ( cch > mbuf->maxsize - offset ) {
|
||||
cch = mbuf->maxsize - offset;
|
||||
}
|
||||
bloc = 16;
|
||||
|
||||
for ( j = 0; j < cch; j++ ) {
|
||||
ch = 0;
|
||||
// don't overflow reading from the messages
|
||||
// FIXME: would it be better to have a overflow check in get_bit ?
|
||||
if ( (bloc >> 3) > size ) {
|
||||
seq[j] = 0;
|
||||
break;
|
||||
}
|
||||
Huff_Receive(huff.tree, &ch, buffer); /* Get a character */
|
||||
if ( ch == NYT ) { /* We got a NYT, get the symbol associated with it */
|
||||
ch = 0;
|
||||
for ( i = 0; i < 8; i++ ) {
|
||||
ch = (ch<<1) + get_bit(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
seq[j] = ch; /* Write symbol */
|
||||
|
||||
Huff_addRef(&huff, (byte)ch); /* Increment node */
|
||||
}
|
||||
mbuf->cursize = cch + offset;
|
||||
Com_Memcpy(mbuf->data + offset, seq, cch);
|
||||
}
|
||||
|
||||
extern int oldsize;
|
||||
|
||||
void Huff_Compress(msg_t *mbuf, int offset) {
|
||||
int i, ch, size;
|
||||
byte seq[65536];
|
||||
byte* buffer;
|
||||
huff_t huff;
|
||||
|
||||
size = mbuf->cursize - offset;
|
||||
buffer = mbuf->data+ + offset;
|
||||
|
||||
if (size<=0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Com_Memset(&huff, 0, sizeof(huff_t));
|
||||
// Add the NYT (not yet transmitted) node into the tree/list */
|
||||
huff.tree = huff.lhead = huff.loc[NYT] = &(huff.nodeList[huff.blocNode++]);
|
||||
huff.tree->symbol = NYT;
|
||||
huff.tree->weight = 0;
|
||||
huff.lhead->next = huff.lhead->prev = NULL;
|
||||
huff.tree->parent = huff.tree->left = huff.tree->right = NULL;
|
||||
huff.loc[NYT] = huff.tree;
|
||||
|
||||
seq[0] = (size>>8);
|
||||
seq[1] = size&0xff;
|
||||
|
||||
bloc = 16;
|
||||
|
||||
for (i=0; i<size; i++ ) {
|
||||
ch = buffer[i];
|
||||
Huff_transmit(&huff, ch, seq); /* Transmit symbol */
|
||||
Huff_addRef(&huff, (byte)ch); /* Do update */
|
||||
}
|
||||
|
||||
bloc += 8; // next byte
|
||||
|
||||
mbuf->cursize = (bloc>>3) + offset;
|
||||
Com_Memcpy(mbuf->data+offset, seq, (bloc>>3));
|
||||
}
|
||||
|
||||
void Huff_Init(huffman_t *huff) {
|
||||
|
||||
Com_Memset(&huff->compressor, 0, sizeof(huff_t));
|
||||
Com_Memset(&huff->decompressor, 0, sizeof(huff_t));
|
||||
|
||||
// Initialize the tree & list with the NYT node
|
||||
huff->decompressor.tree = huff->decompressor.lhead = huff->decompressor.ltail = huff->decompressor.loc[NYT] = &(huff->decompressor.nodeList[huff->decompressor.blocNode++]);
|
||||
huff->decompressor.tree->symbol = NYT;
|
||||
huff->decompressor.tree->weight = 0;
|
||||
huff->decompressor.lhead->next = huff->decompressor.lhead->prev = NULL;
|
||||
huff->decompressor.tree->parent = huff->decompressor.tree->left = huff->decompressor.tree->right = NULL;
|
||||
|
||||
// Add the NYT (not yet transmitted) node into the tree/list */
|
||||
huff->compressor.tree = huff->compressor.lhead = huff->compressor.loc[NYT] = &(huff->compressor.nodeList[huff->compressor.blocNode++]);
|
||||
huff->compressor.tree->symbol = NYT;
|
||||
huff->compressor.tree->weight = 0;
|
||||
huff->compressor.lhead->next = huff->compressor.lhead->prev = NULL;
|
||||
huff->compressor.tree->parent = huff->compressor.tree->left = huff->compressor.tree->right = NULL;
|
||||
huff->compressor.loc[NYT] = huff->compressor.tree;
|
||||
}
|
||||
|
299
code/qcommon/md4.c
Normal file
299
code/qcommon/md4.c
Normal file
|
@ -0,0 +1,299 @@
|
|||
/* GLOBAL.H - RSAREF types and constants */
|
||||
|
||||
#include <string.h>
|
||||
#if defined(_WIN32)
|
||||
#pragma warning(disable : 4711) // selected for automatic inline expansion
|
||||
#endif
|
||||
|
||||
/* POINTER defines a generic pointer type */
|
||||
typedef unsigned char *POINTER;
|
||||
|
||||
/* UINT2 defines a two byte word */
|
||||
typedef unsigned short int UINT2;
|
||||
|
||||
/* UINT4 defines a four byte word */
|
||||
typedef unsigned long int UINT4;
|
||||
|
||||
|
||||
/* MD4.H - header file for MD4C.C */
|
||||
|
||||
/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991.
|
||||
|
||||
All rights reserved.
|
||||
|
||||
License to copy and use this software is granted provided that it is identified as the “RSA Data Security, Inc. MD4 Message-Digest Algorithm” in all material mentioning or referencing this software or this function.
|
||||
License is also granted to make and use derivative works provided that such works are identified as “derived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm” in all material mentioning or referencing the derived work.
|
||||
RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided “as is” without express or implied warranty of any kind.
|
||||
|
||||
These notices must be retained in any copies of any part of this documentation and/or software. */
|
||||
|
||||
/* MD4 context. */
|
||||
typedef struct {
|
||||
UINT4 state[4]; /* state (ABCD) */
|
||||
UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */
|
||||
unsigned char buffer[64]; /* input buffer */
|
||||
} MD4_CTX;
|
||||
|
||||
void MD4Init (MD4_CTX *);
|
||||
void MD4Update (MD4_CTX *, const unsigned char *, unsigned int);
|
||||
void MD4Final (unsigned char [16], MD4_CTX *);
|
||||
|
||||
#ifndef __VECTORC
|
||||
void Com_Memset (void* dest, const int val, const size_t count);
|
||||
void Com_Memcpy (void* dest, const void* src, const size_t count);
|
||||
#else
|
||||
#define Com_Memset memset
|
||||
#define Com_Memcpy memcpy
|
||||
#endif
|
||||
|
||||
/* MD4C.C - RSA Data Security, Inc., MD4 message-digest algorithm */
|
||||
/* Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved.
|
||||
|
||||
License to copy and use this software is granted provided that it is identified as the
|
||||
RSA Data Security, Inc. MD4 Message-Digest Algorithm
|
||||
in all material mentioning or referencing this software or this function.
|
||||
License is also granted to make and use derivative works provided that such works are identified as
|
||||
derived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm
|
||||
in all material mentioning or referencing the derived work.
|
||||
RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided
|
||||
as is without express or implied warranty of any kind.
|
||||
|
||||
These notices must be retained in any copies of any part of this documentation and/or software. */
|
||||
|
||||
/* Constants for MD4Transform routine. */
|
||||
#define S11 3
|
||||
#define S12 7
|
||||
#define S13 11
|
||||
#define S14 19
|
||||
#define S21 3
|
||||
#define S22 5
|
||||
#define S23 9
|
||||
#define S24 13
|
||||
#define S31 3
|
||||
#define S32 9
|
||||
#define S33 11
|
||||
#define S34 15
|
||||
|
||||
static void MD4Transform (UINT4 [4], const unsigned char [64]);
|
||||
static void Encode (unsigned char *, UINT4 *, unsigned int);
|
||||
static void Decode (UINT4 *, const unsigned char *, unsigned int);
|
||||
|
||||
static unsigned char PADDING[64] = {
|
||||
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
/* F, G and H are basic MD4 functions. */
|
||||
#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))
|
||||
|
||||
/* ROTATE_LEFT rotates x left n bits. */
|
||||
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
|
||||
|
||||
/* FF, GG and HH are transformations for rounds 1, 2 and 3 */
|
||||
/* Rotation is separate from addition to prevent recomputation */
|
||||
#define FF(a, b, c, d, x, s) {(a) += F ((b), (c), (d)) + (x); (a) = ROTATE_LEFT ((a), (s));}
|
||||
|
||||
#define GG(a, b, c, d, x, s) {(a) += G ((b), (c), (d)) + (x) + (UINT4)0x5a827999; (a) = ROTATE_LEFT ((a), (s));}
|
||||
|
||||
#define HH(a, b, c, d, x, s) {(a) += H ((b), (c), (d)) + (x) + (UINT4)0x6ed9eba1; (a) = ROTATE_LEFT ((a), (s));}
|
||||
|
||||
|
||||
/* MD4 initialization. Begins an MD4 operation, writing a new context. */
|
||||
void MD4Init (MD4_CTX *context)
|
||||
{
|
||||
context->count[0] = context->count[1] = 0;
|
||||
|
||||
/* Load magic initialization constants.*/
|
||||
context->state[0] = 0x67452301;
|
||||
context->state[1] = 0xefcdab89;
|
||||
context->state[2] = 0x98badcfe;
|
||||
context->state[3] = 0x10325476;
|
||||
}
|
||||
|
||||
/* MD4 block update operation. Continues an MD4 message-digest operation, processing another message block, and updating the context. */
|
||||
void MD4Update (MD4_CTX *context, const unsigned char *input, unsigned int inputLen)
|
||||
{
|
||||
unsigned int i, index, partLen;
|
||||
|
||||
/* Compute number of bytes mod 64 */
|
||||
index = (unsigned int)((context->count[0] >> 3) & 0x3F);
|
||||
|
||||
/* Update number of bits */
|
||||
if ((context->count[0] += ((UINT4)inputLen << 3))< ((UINT4)inputLen << 3))
|
||||
context->count[1]++;
|
||||
|
||||
context->count[1] += ((UINT4)inputLen >> 29);
|
||||
|
||||
partLen = 64 - index;
|
||||
|
||||
/* Transform as many times as possible.*/
|
||||
if (inputLen >= partLen)
|
||||
{
|
||||
Com_Memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen);
|
||||
MD4Transform (context->state, context->buffer);
|
||||
|
||||
for (i = partLen; i + 63 < inputLen; i += 64)
|
||||
MD4Transform (context->state, &input[i]);
|
||||
|
||||
index = 0;
|
||||
}
|
||||
else
|
||||
i = 0;
|
||||
|
||||
/* Buffer remaining input */
|
||||
Com_Memcpy ((POINTER)&context->buffer[index], (POINTER)&input[i], inputLen-i);
|
||||
}
|
||||
|
||||
|
||||
/* MD4 finalization. Ends an MD4 message-digest operation, writing the the message digest and zeroizing the context. */
|
||||
void MD4Final (unsigned char digest[16], MD4_CTX *context)
|
||||
{
|
||||
unsigned char bits[8];
|
||||
unsigned int index, padLen;
|
||||
|
||||
/* Save number of bits */
|
||||
Encode (bits, context->count, 8);
|
||||
|
||||
/* Pad out to 56 mod 64.*/
|
||||
index = (unsigned int)((context->count[0] >> 3) & 0x3f);
|
||||
padLen = (index < 56) ? (56 - index) : (120 - index);
|
||||
MD4Update (context, PADDING, padLen);
|
||||
|
||||
/* Append length (before padding) */
|
||||
MD4Update (context, bits, 8);
|
||||
|
||||
/* Store state in digest */
|
||||
Encode (digest, context->state, 16);
|
||||
|
||||
/* Zeroize sensitive information.*/
|
||||
Com_Memset ((POINTER)context, 0, sizeof (*context));
|
||||
}
|
||||
|
||||
|
||||
/* MD4 basic transformation. Transforms state based on block. */
|
||||
static void MD4Transform (UINT4 state[4], const unsigned char block[64])
|
||||
{
|
||||
UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
|
||||
|
||||
Decode (x, block, 64);
|
||||
|
||||
/* Round 1 */
|
||||
FF (a, b, c, d, x[ 0], S11); /* 1 */
|
||||
FF (d, a, b, c, x[ 1], S12); /* 2 */
|
||||
FF (c, d, a, b, x[ 2], S13); /* 3 */
|
||||
FF (b, c, d, a, x[ 3], S14); /* 4 */
|
||||
FF (a, b, c, d, x[ 4], S11); /* 5 */
|
||||
FF (d, a, b, c, x[ 5], S12); /* 6 */
|
||||
FF (c, d, a, b, x[ 6], S13); /* 7 */
|
||||
FF (b, c, d, a, x[ 7], S14); /* 8 */
|
||||
FF (a, b, c, d, x[ 8], S11); /* 9 */
|
||||
FF (d, a, b, c, x[ 9], S12); /* 10 */
|
||||
FF (c, d, a, b, x[10], S13); /* 11 */
|
||||
FF (b, c, d, a, x[11], S14); /* 12 */
|
||||
FF (a, b, c, d, x[12], S11); /* 13 */
|
||||
FF (d, a, b, c, x[13], S12); /* 14 */
|
||||
FF (c, d, a, b, x[14], S13); /* 15 */
|
||||
FF (b, c, d, a, x[15], S14); /* 16 */
|
||||
|
||||
/* Round 2 */
|
||||
GG (a, b, c, d, x[ 0], S21); /* 17 */
|
||||
GG (d, a, b, c, x[ 4], S22); /* 18 */
|
||||
GG (c, d, a, b, x[ 8], S23); /* 19 */
|
||||
GG (b, c, d, a, x[12], S24); /* 20 */
|
||||
GG (a, b, c, d, x[ 1], S21); /* 21 */
|
||||
GG (d, a, b, c, x[ 5], S22); /* 22 */
|
||||
GG (c, d, a, b, x[ 9], S23); /* 23 */
|
||||
GG (b, c, d, a, x[13], S24); /* 24 */
|
||||
GG (a, b, c, d, x[ 2], S21); /* 25 */
|
||||
GG (d, a, b, c, x[ 6], S22); /* 26 */
|
||||
GG (c, d, a, b, x[10], S23); /* 27 */
|
||||
GG (b, c, d, a, x[14], S24); /* 28 */
|
||||
GG (a, b, c, d, x[ 3], S21); /* 29 */
|
||||
GG (d, a, b, c, x[ 7], S22); /* 30 */
|
||||
GG (c, d, a, b, x[11], S23); /* 31 */
|
||||
GG (b, c, d, a, x[15], S24); /* 32 */
|
||||
|
||||
/* Round 3 */
|
||||
HH (a, b, c, d, x[ 0], S31); /* 33 */
|
||||
HH (d, a, b, c, x[ 8], S32); /* 34 */
|
||||
HH (c, d, a, b, x[ 4], S33); /* 35 */
|
||||
HH (b, c, d, a, x[12], S34); /* 36 */
|
||||
HH (a, b, c, d, x[ 2], S31); /* 37 */
|
||||
HH (d, a, b, c, x[10], S32); /* 38 */
|
||||
HH (c, d, a, b, x[ 6], S33); /* 39 */
|
||||
HH (b, c, d, a, x[14], S34); /* 40 */
|
||||
HH (a, b, c, d, x[ 1], S31); /* 41 */
|
||||
HH (d, a, b, c, x[ 9], S32); /* 42 */
|
||||
HH (c, d, a, b, x[ 5], S33); /* 43 */
|
||||
HH (b, c, d, a, x[13], S34); /* 44 */
|
||||
HH (a, b, c, d, x[ 3], S31); /* 45 */
|
||||
HH (d, a, b, c, x[11], S32); /* 46 */
|
||||
HH (c, d, a, b, x[ 7], S33); /* 47 */
|
||||
HH (b, c, d, a, x[15], S34); /* 48 */
|
||||
|
||||
state[0] += a;
|
||||
state[1] += b;
|
||||
state[2] += c;
|
||||
state[3] += d;
|
||||
|
||||
/* Zeroize sensitive information.*/
|
||||
Com_Memset ((POINTER)x, 0, sizeof (x));
|
||||
}
|
||||
|
||||
|
||||
/* Encodes input (UINT4) into output (unsigned char). Assumes len is a multiple of 4. */
|
||||
static void Encode (unsigned char *output, UINT4 *input, unsigned int len)
|
||||
{
|
||||
unsigned int i, j;
|
||||
|
||||
for (i = 0, j = 0; j < len; i++, j += 4) {
|
||||
output[j] = (unsigned char)(input[i] & 0xff);
|
||||
output[j+1] = (unsigned char)((input[i] >> 8) & 0xff);
|
||||
output[j+2] = (unsigned char)((input[i] >> 16) & 0xff);
|
||||
output[j+3] = (unsigned char)((input[i] >> 24) & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Decodes input (unsigned char) into output (UINT4). Assumes len is a multiple of 4. */
|
||||
static void Decode (UINT4 *output, const unsigned char *input, unsigned int len)
|
||||
{
|
||||
unsigned int i, j;
|
||||
|
||||
for (i = 0, j = 0; j < len; i++, j += 4)
|
||||
output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24);
|
||||
}
|
||||
|
||||
//===================================================================
|
||||
|
||||
unsigned Com_BlockChecksum (void *buffer, int length)
|
||||
{
|
||||
int digest[4];
|
||||
unsigned val;
|
||||
MD4_CTX ctx;
|
||||
|
||||
MD4Init (&ctx);
|
||||
MD4Update (&ctx, (unsigned char *)buffer, length);
|
||||
MD4Final ( (unsigned char *)digest, &ctx);
|
||||
|
||||
val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3];
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
unsigned Com_BlockChecksumKey (void *buffer, int length, int key)
|
||||
{
|
||||
int digest[4];
|
||||
unsigned val;
|
||||
MD4_CTX ctx;
|
||||
|
||||
MD4Init (&ctx);
|
||||
MD4Update (&ctx, (unsigned char *)&key, 4);
|
||||
MD4Update (&ctx, (unsigned char *)buffer, length);
|
||||
MD4Final ( (unsigned char *)digest, &ctx);
|
||||
|
||||
val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3];
|
||||
|
||||
return val;
|
||||
}
|
1757
code/qcommon/msg.c
Normal file
1757
code/qcommon/msg.c
Normal file
File diff suppressed because it is too large
Load diff
742
code/qcommon/net_chan.c
Normal file
742
code/qcommon/net_chan.c
Normal file
|
@ -0,0 +1,742 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code 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 Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
#include "../game/q_shared.h"
|
||||
#include "qcommon.h"
|
||||
|
||||
/*
|
||||
|
||||
packet header
|
||||
-------------
|
||||
4 outgoing sequence. high bit will be set if this is a fragmented message
|
||||
[2 qport (only for client to server)]
|
||||
[2 fragment start byte]
|
||||
[2 fragment length. if < FRAGMENT_SIZE, this is the last fragment]
|
||||
|
||||
if the sequence number is -1, the packet should be handled as an out-of-band
|
||||
message instead of as part of a netcon.
|
||||
|
||||
All fragments will have the same sequence numbers.
|
||||
|
||||
The qport field is a workaround for bad address translating routers that
|
||||
sometimes remap the client's source port on a packet during gameplay.
|
||||
|
||||
If the base part of the net address matches and the qport matches, then the
|
||||
channel matches even if the IP port differs. The IP port should be updated
|
||||
to the new value before sending out any replies.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#define MAX_PACKETLEN 1400 // max size of a network packet
|
||||
|
||||
#define FRAGMENT_SIZE (MAX_PACKETLEN - 100)
|
||||
#define PACKET_HEADER 10 // two ints and a short
|
||||
|
||||
#define FRAGMENT_BIT (1<<31)
|
||||
|
||||
cvar_t *showpackets;
|
||||
cvar_t *showdrop;
|
||||
cvar_t *qport;
|
||||
|
||||
static char *netsrcString[2] = {
|
||||
"client",
|
||||
"server"
|
||||
};
|
||||
|
||||
/*
|
||||
===============
|
||||
Netchan_Init
|
||||
|
||||
===============
|
||||
*/
|
||||
void Netchan_Init( int port ) {
|
||||
port &= 0xffff;
|
||||
showpackets = Cvar_Get ("showpackets", "0", CVAR_TEMP );
|
||||
showdrop = Cvar_Get ("showdrop", "0", CVAR_TEMP );
|
||||
qport = Cvar_Get ("net_qport", va("%i", port), CVAR_INIT );
|
||||
}
|
||||
|
||||
/*
|
||||
==============
|
||||
Netchan_Setup
|
||||
|
||||
called to open a channel to a remote system
|
||||
==============
|
||||
*/
|
||||
void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport ) {
|
||||
Com_Memset (chan, 0, sizeof(*chan));
|
||||
|
||||
chan->sock = sock;
|
||||
chan->remoteAddress = adr;
|
||||
chan->qport = qport;
|
||||
chan->incomingSequence = 0;
|
||||
chan->outgoingSequence = 1;
|
||||
}
|
||||
|
||||
// TTimo: unused, commenting out to make gcc happy
|
||||
#if 0
|
||||
/*
|
||||
==============
|
||||
Netchan_ScramblePacket
|
||||
|
||||
A probably futile attempt to make proxy hacking somewhat
|
||||
more difficult.
|
||||
==============
|
||||
*/
|
||||
#define SCRAMBLE_START 6
|
||||
static void Netchan_ScramblePacket( msg_t *buf ) {
|
||||
unsigned seed;
|
||||
int i, j, c, mask, temp;
|
||||
int seq[MAX_PACKETLEN];
|
||||
|
||||
seed = ( LittleLong( *(unsigned *)buf->data ) * 3 ) ^ ( buf->cursize * 123 );
|
||||
c = buf->cursize;
|
||||
if ( c <= SCRAMBLE_START ) {
|
||||
return;
|
||||
}
|
||||
if ( c > MAX_PACKETLEN ) {
|
||||
Com_Error( ERR_DROP, "MAX_PACKETLEN" );
|
||||
}
|
||||
|
||||
// generate a sequence of "random" numbers
|
||||
for (i = 0 ; i < c ; i++) {
|
||||
seed = (119 * seed + 1);
|
||||
seq[i] = seed;
|
||||
}
|
||||
|
||||
// transpose each character
|
||||
for ( mask = 1 ; mask < c-SCRAMBLE_START ; mask = ( mask << 1 ) + 1 ) {
|
||||
}
|
||||
mask >>= 1;
|
||||
for (i = SCRAMBLE_START ; i < c ; i++) {
|
||||
j = SCRAMBLE_START + ( seq[i] & mask );
|
||||
temp = buf->data[j];
|
||||
buf->data[j] = buf->data[i];
|
||||
buf->data[i] = temp;
|
||||
}
|
||||
|
||||
// byte xor the data after the header
|
||||
for (i = SCRAMBLE_START ; i < c ; i++) {
|
||||
buf->data[i] ^= seq[i];
|
||||
}
|
||||
}
|
||||
|
||||
static void Netchan_UnScramblePacket( msg_t *buf ) {
|
||||
unsigned seed;
|
||||
int i, j, c, mask, temp;
|
||||
int seq[MAX_PACKETLEN];
|
||||
|
||||
seed = ( LittleLong( *(unsigned *)buf->data ) * 3 ) ^ ( buf->cursize * 123 );
|
||||
c = buf->cursize;
|
||||
if ( c <= SCRAMBLE_START ) {
|
||||
return;
|
||||
}
|
||||
if ( c > MAX_PACKETLEN ) {
|
||||
Com_Error( ERR_DROP, "MAX_PACKETLEN" );
|
||||
}
|
||||
|
||||
// generate a sequence of "random" numbers
|
||||
for (i = 0 ; i < c ; i++) {
|
||||
seed = (119 * seed + 1);
|
||||
seq[i] = seed;
|
||||
}
|
||||
|
||||
// byte xor the data after the header
|
||||
for (i = SCRAMBLE_START ; i < c ; i++) {
|
||||
buf->data[i] ^= seq[i];
|
||||
}
|
||||
|
||||
// transpose each character in reverse order
|
||||
for ( mask = 1 ; mask < c-SCRAMBLE_START ; mask = ( mask << 1 ) + 1 ) {
|
||||
}
|
||||
mask >>= 1;
|
||||
for (i = c-1 ; i >= SCRAMBLE_START ; i--) {
|
||||
j = SCRAMBLE_START + ( seq[i] & mask );
|
||||
temp = buf->data[j];
|
||||
buf->data[j] = buf->data[i];
|
||||
buf->data[i] = temp;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
=================
|
||||
Netchan_TransmitNextFragment
|
||||
|
||||
Send one fragment of the current message
|
||||
=================
|
||||
*/
|
||||
void Netchan_TransmitNextFragment( netchan_t *chan ) {
|
||||
msg_t send;
|
||||
byte send_buf[MAX_PACKETLEN];
|
||||
int fragmentLength;
|
||||
|
||||
// write the packet header
|
||||
MSG_InitOOB (&send, send_buf, sizeof(send_buf)); // <-- only do the oob here
|
||||
|
||||
MSG_WriteLong( &send, chan->outgoingSequence | FRAGMENT_BIT );
|
||||
|
||||
// send the qport if we are a client
|
||||
if ( chan->sock == NS_CLIENT ) {
|
||||
MSG_WriteShort( &send, qport->integer );
|
||||
}
|
||||
|
||||
// copy the reliable message to the packet first
|
||||
fragmentLength = FRAGMENT_SIZE;
|
||||
if ( chan->unsentFragmentStart + fragmentLength > chan->unsentLength ) {
|
||||
fragmentLength = chan->unsentLength - chan->unsentFragmentStart;
|
||||
}
|
||||
|
||||
MSG_WriteShort( &send, chan->unsentFragmentStart );
|
||||
MSG_WriteShort( &send, fragmentLength );
|
||||
MSG_WriteData( &send, chan->unsentBuffer + chan->unsentFragmentStart, fragmentLength );
|
||||
|
||||
// send the datagram
|
||||
NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress );
|
||||
|
||||
if ( showpackets->integer ) {
|
||||
Com_Printf ("%s send %4i : s=%i fragment=%i,%i\n"
|
||||
, netsrcString[ chan->sock ]
|
||||
, send.cursize
|
||||
, chan->outgoingSequence
|
||||
, chan->unsentFragmentStart, fragmentLength);
|
||||
}
|
||||
|
||||
chan->unsentFragmentStart += fragmentLength;
|
||||
|
||||
// this exit condition is a little tricky, because a packet
|
||||
// that is exactly the fragment length still needs to send
|
||||
// a second packet of zero length so that the other side
|
||||
// can tell there aren't more to follow
|
||||
if ( chan->unsentFragmentStart == chan->unsentLength && fragmentLength != FRAGMENT_SIZE ) {
|
||||
chan->outgoingSequence++;
|
||||
chan->unsentFragments = qfalse;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
Netchan_Transmit
|
||||
|
||||
Sends a message to a connection, fragmenting if necessary
|
||||
A 0 length will still generate a packet.
|
||||
================
|
||||
*/
|
||||
void Netchan_Transmit( netchan_t *chan, int length, const byte *data ) {
|
||||
msg_t send;
|
||||
byte send_buf[MAX_PACKETLEN];
|
||||
|
||||
if ( length > MAX_MSGLEN ) {
|
||||
Com_Error( ERR_DROP, "Netchan_Transmit: length = %i", length );
|
||||
}
|
||||
chan->unsentFragmentStart = 0;
|
||||
|
||||
// fragment large reliable messages
|
||||
if ( length >= FRAGMENT_SIZE ) {
|
||||
chan->unsentFragments = qtrue;
|
||||
chan->unsentLength = length;
|
||||
Com_Memcpy( chan->unsentBuffer, data, length );
|
||||
|
||||
// only send the first fragment now
|
||||
Netchan_TransmitNextFragment( chan );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// write the packet header
|
||||
MSG_InitOOB (&send, send_buf, sizeof(send_buf));
|
||||
|
||||
MSG_WriteLong( &send, chan->outgoingSequence );
|
||||
chan->outgoingSequence++;
|
||||
|
||||
// send the qport if we are a client
|
||||
if ( chan->sock == NS_CLIENT ) {
|
||||
MSG_WriteShort( &send, qport->integer );
|
||||
}
|
||||
|
||||
MSG_WriteData( &send, data, length );
|
||||
|
||||
// send the datagram
|
||||
NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress );
|
||||
|
||||
if ( showpackets->integer ) {
|
||||
Com_Printf( "%s send %4i : s=%i ack=%i\n"
|
||||
, netsrcString[ chan->sock ]
|
||||
, send.cursize
|
||||
, chan->outgoingSequence - 1
|
||||
, chan->incomingSequence );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Netchan_Process
|
||||
|
||||
Returns qfalse if the message should not be processed due to being
|
||||
out of order or a fragment.
|
||||
|
||||
Msg must be large enough to hold MAX_MSGLEN, because if this is the
|
||||
final fragment of a multi-part message, the entire thing will be
|
||||
copied out.
|
||||
=================
|
||||
*/
|
||||
qboolean Netchan_Process( netchan_t *chan, msg_t *msg ) {
|
||||
int sequence;
|
||||
int qport;
|
||||
int fragmentStart, fragmentLength;
|
||||
qboolean fragmented;
|
||||
|
||||
// XOR unscramble all data in the packet after the header
|
||||
// Netchan_UnScramblePacket( msg );
|
||||
|
||||
// get sequence numbers
|
||||
MSG_BeginReadingOOB( msg );
|
||||
sequence = MSG_ReadLong( msg );
|
||||
|
||||
// check for fragment information
|
||||
if ( sequence & FRAGMENT_BIT ) {
|
||||
sequence &= ~FRAGMENT_BIT;
|
||||
fragmented = qtrue;
|
||||
} else {
|
||||
fragmented = qfalse;
|
||||
}
|
||||
|
||||
// read the qport if we are a server
|
||||
if ( chan->sock == NS_SERVER ) {
|
||||
qport = MSG_ReadShort( msg );
|
||||
}
|
||||
|
||||
// read the fragment information
|
||||
if ( fragmented ) {
|
||||
fragmentStart = MSG_ReadShort( msg );
|
||||
fragmentLength = MSG_ReadShort( msg );
|
||||
} else {
|
||||
fragmentStart = 0; // stop warning message
|
||||
fragmentLength = 0;
|
||||
}
|
||||
|
||||
if ( showpackets->integer ) {
|
||||
if ( fragmented ) {
|
||||
Com_Printf( "%s recv %4i : s=%i fragment=%i,%i\n"
|
||||
, netsrcString[ chan->sock ]
|
||||
, msg->cursize
|
||||
, sequence
|
||||
, fragmentStart, fragmentLength );
|
||||
} else {
|
||||
Com_Printf( "%s recv %4i : s=%i\n"
|
||||
, netsrcString[ chan->sock ]
|
||||
, msg->cursize
|
||||
, sequence );
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// discard out of order or duplicated packets
|
||||
//
|
||||
if ( sequence <= chan->incomingSequence ) {
|
||||
if ( showdrop->integer || showpackets->integer ) {
|
||||
Com_Printf( "%s:Out of order packet %i at %i\n"
|
||||
, NET_AdrToString( chan->remoteAddress )
|
||||
, sequence
|
||||
, chan->incomingSequence );
|
||||
}
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
//
|
||||
// dropped packets don't keep the message from being used
|
||||
//
|
||||
chan->dropped = sequence - (chan->incomingSequence+1);
|
||||
if ( chan->dropped > 0 ) {
|
||||
if ( showdrop->integer || showpackets->integer ) {
|
||||
Com_Printf( "%s:Dropped %i packets at %i\n"
|
||||
, NET_AdrToString( chan->remoteAddress )
|
||||
, chan->dropped
|
||||
, sequence );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// if this is the final framgent of a reliable message,
|
||||
// bump incoming_reliable_sequence
|
||||
//
|
||||
if ( fragmented ) {
|
||||
// TTimo
|
||||
// make sure we add the fragments in correct order
|
||||
// either a packet was dropped, or we received this one too soon
|
||||
// we don't reconstruct the fragments. we will wait till this fragment gets to us again
|
||||
// (NOTE: we could probably try to rebuild by out of order chunks if needed)
|
||||
if ( sequence != chan->fragmentSequence ) {
|
||||
chan->fragmentSequence = sequence;
|
||||
chan->fragmentLength = 0;
|
||||
}
|
||||
|
||||
// if we missed a fragment, dump the message
|
||||
if ( fragmentStart != chan->fragmentLength ) {
|
||||
if ( showdrop->integer || showpackets->integer ) {
|
||||
Com_Printf( "%s:Dropped a message fragment\n"
|
||||
, NET_AdrToString( chan->remoteAddress )
|
||||
, sequence);
|
||||
}
|
||||
// we can still keep the part that we have so far,
|
||||
// so we don't need to clear chan->fragmentLength
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
// copy the fragment to the fragment buffer
|
||||
if ( fragmentLength < 0 || msg->readcount + fragmentLength > msg->cursize ||
|
||||
chan->fragmentLength + fragmentLength > sizeof( chan->fragmentBuffer ) ) {
|
||||
if ( showdrop->integer || showpackets->integer ) {
|
||||
Com_Printf ("%s:illegal fragment length\n"
|
||||
, NET_AdrToString (chan->remoteAddress ) );
|
||||
}
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
Com_Memcpy( chan->fragmentBuffer + chan->fragmentLength,
|
||||
msg->data + msg->readcount, fragmentLength );
|
||||
|
||||
chan->fragmentLength += fragmentLength;
|
||||
|
||||
// if this wasn't the last fragment, don't process anything
|
||||
if ( fragmentLength == FRAGMENT_SIZE ) {
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
if ( chan->fragmentLength > msg->maxsize ) {
|
||||
Com_Printf( "%s:fragmentLength %i > msg->maxsize\n"
|
||||
, NET_AdrToString (chan->remoteAddress ),
|
||||
chan->fragmentLength );
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
// copy the full message over the partial fragment
|
||||
|
||||
// make sure the sequence number is still there
|
||||
*(int *)msg->data = LittleLong( sequence );
|
||||
|
||||
Com_Memcpy( msg->data + 4, chan->fragmentBuffer, chan->fragmentLength );
|
||||
msg->cursize = chan->fragmentLength + 4;
|
||||
chan->fragmentLength = 0;
|
||||
msg->readcount = 4; // past the sequence number
|
||||
msg->bit = 32; // past the sequence number
|
||||
|
||||
// TTimo
|
||||
// clients were not acking fragmented messages
|
||||
chan->incomingSequence = sequence;
|
||||
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
//
|
||||
// the message can now be read from the current message pointer
|
||||
//
|
||||
chan->incomingSequence = sequence;
|
||||
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
|
||||
/*
|
||||
===================
|
||||
NET_CompareBaseAdr
|
||||
|
||||
Compares without the port
|
||||
===================
|
||||
*/
|
||||
qboolean NET_CompareBaseAdr (netadr_t a, netadr_t b)
|
||||
{
|
||||
if (a.type != b.type)
|
||||
return qfalse;
|
||||
|
||||
if (a.type == NA_LOOPBACK)
|
||||
return qtrue;
|
||||
|
||||
if (a.type == NA_IP)
|
||||
{
|
||||
if (a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3])
|
||||
return qtrue;
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
if (a.type == NA_IPX)
|
||||
{
|
||||
if ((memcmp(a.ipx, b.ipx, 10) == 0))
|
||||
return qtrue;
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
|
||||
Com_Printf ("NET_CompareBaseAdr: bad address type\n");
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
const char *NET_AdrToString (netadr_t a)
|
||||
{
|
||||
static char s[64];
|
||||
|
||||
if (a.type == NA_LOOPBACK) {
|
||||
Com_sprintf (s, sizeof(s), "loopback");
|
||||
} else if (a.type == NA_BOT) {
|
||||
Com_sprintf (s, sizeof(s), "bot");
|
||||
} else if (a.type == NA_IP) {
|
||||
Com_sprintf (s, sizeof(s), "%i.%i.%i.%i:%hu",
|
||||
a.ip[0], a.ip[1], a.ip[2], a.ip[3], BigShort(a.port));
|
||||
} else {
|
||||
Com_sprintf (s, sizeof(s), "%02x%02x%02x%02x.%02x%02x%02x%02x%02x%02x:%hu",
|
||||
a.ipx[0], a.ipx[1], a.ipx[2], a.ipx[3], a.ipx[4], a.ipx[5], a.ipx[6], a.ipx[7], a.ipx[8], a.ipx[9],
|
||||
BigShort(a.port));
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
qboolean NET_CompareAdr (netadr_t a, netadr_t b)
|
||||
{
|
||||
if (a.type != b.type)
|
||||
return qfalse;
|
||||
|
||||
if (a.type == NA_LOOPBACK)
|
||||
return qtrue;
|
||||
|
||||
if (a.type == NA_IP)
|
||||
{
|
||||
if (a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3] && a.port == b.port)
|
||||
return qtrue;
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
if (a.type == NA_IPX)
|
||||
{
|
||||
if ((memcmp(a.ipx, b.ipx, 10) == 0) && a.port == b.port)
|
||||
return qtrue;
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
Com_Printf ("NET_CompareAdr: bad address type\n");
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
|
||||
qboolean NET_IsLocalAddress( netadr_t adr ) {
|
||||
return adr.type == NA_LOOPBACK;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
=============================================================================
|
||||
|
||||
LOOPBACK BUFFERS FOR LOCAL PLAYER
|
||||
|
||||
=============================================================================
|
||||
*/
|
||||
|
||||
// there needs to be enough loopback messages to hold a complete
|
||||
// gamestate of maximum size
|
||||
#define MAX_LOOPBACK 16
|
||||
|
||||
typedef struct {
|
||||
byte data[MAX_PACKETLEN];
|
||||
int datalen;
|
||||
} loopmsg_t;
|
||||
|
||||
typedef struct {
|
||||
loopmsg_t msgs[MAX_LOOPBACK];
|
||||
int get, send;
|
||||
} loopback_t;
|
||||
|
||||
loopback_t loopbacks[2];
|
||||
|
||||
|
||||
qboolean NET_GetLoopPacket (netsrc_t sock, netadr_t *net_from, msg_t *net_message)
|
||||
{
|
||||
int i;
|
||||
loopback_t *loop;
|
||||
|
||||
loop = &loopbacks[sock];
|
||||
|
||||
if (loop->send - loop->get > MAX_LOOPBACK)
|
||||
loop->get = loop->send - MAX_LOOPBACK;
|
||||
|
||||
if (loop->get >= loop->send)
|
||||
return qfalse;
|
||||
|
||||
i = loop->get & (MAX_LOOPBACK-1);
|
||||
loop->get++;
|
||||
|
||||
Com_Memcpy (net_message->data, loop->msgs[i].data, loop->msgs[i].datalen);
|
||||
net_message->cursize = loop->msgs[i].datalen;
|
||||
Com_Memset (net_from, 0, sizeof(*net_from));
|
||||
net_from->type = NA_LOOPBACK;
|
||||
return qtrue;
|
||||
|
||||
}
|
||||
|
||||
|
||||
void NET_SendLoopPacket (netsrc_t sock, int length, const void *data, netadr_t to)
|
||||
{
|
||||
int i;
|
||||
loopback_t *loop;
|
||||
|
||||
loop = &loopbacks[sock^1];
|
||||
|
||||
i = loop->send & (MAX_LOOPBACK-1);
|
||||
loop->send++;
|
||||
|
||||
Com_Memcpy (loop->msgs[i].data, data, length);
|
||||
loop->msgs[i].datalen = length;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
|
||||
|
||||
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_BOT ) {
|
||||
return;
|
||||
}
|
||||
if ( to.type == NA_BAD ) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 );
|
||||
vsprintf( string+4, format, argptr );
|
||||
va_end( argptr );
|
||||
|
||||
// send the datagram
|
||||
NET_SendPacket( sock, strlen( string ), string, adr );
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
NET_OutOfBandPrint
|
||||
|
||||
Sends a data message in an out-of-band datagram (only used for "connect")
|
||||
================
|
||||
*/
|
||||
void QDECL NET_OutOfBandData( netsrc_t sock, netadr_t adr, byte *format, int len ) {
|
||||
byte string[MAX_MSGLEN*2];
|
||||
int i;
|
||||
msg_t mbuf;
|
||||
|
||||
// set the header
|
||||
string[0] = 0xff;
|
||||
string[1] = 0xff;
|
||||
string[2] = 0xff;
|
||||
string[3] = 0xff;
|
||||
|
||||
for(i=0;i<len;i++) {
|
||||
string[i+4] = format[i];
|
||||
}
|
||||
|
||||
mbuf.data = string;
|
||||
mbuf.cursize = len+4;
|
||||
Huff_Compress( &mbuf, 12);
|
||||
// send the datagram
|
||||
NET_SendPacket( sock, mbuf.cursize, mbuf.data, adr );
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
NET_StringToAdr
|
||||
|
||||
Traps "localhost" for loopback, passes everything else to system
|
||||
=============
|
||||
*/
|
||||
qboolean NET_StringToAdr( const char *s, netadr_t *a ) {
|
||||
qboolean r;
|
||||
char base[MAX_STRING_CHARS];
|
||||
char *port;
|
||||
|
||||
if (!strcmp (s, "localhost")) {
|
||||
Com_Memset (a, 0, sizeof(*a));
|
||||
a->type = NA_LOOPBACK;
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
// look for a port number
|
||||
Q_strncpyz( base, s, sizeof( base ) );
|
||||
port = strstr( base, ":" );
|
||||
if ( port ) {
|
||||
*port = 0;
|
||||
port++;
|
||||
}
|
||||
|
||||
r = Sys_StringToAdr( base, a );
|
||||
|
||||
if ( !r ) {
|
||||
a->type = NA_BAD;
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
// inet_addr returns this if out of range
|
||||
if ( a->ip[0] == 255 && a->ip[1] == 255 && a->ip[2] == 255 && a->ip[3] == 255 ) {
|
||||
a->type = NA_BAD;
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
if ( port ) {
|
||||
a->port = BigShort( (short)atoi( port ) );
|
||||
} else {
|
||||
a->port = BigShort( PORT_SERVER );
|
||||
}
|
||||
|
||||
return qtrue;
|
||||
}
|
||||
|
1067
code/qcommon/qcommon.h
Normal file
1067
code/qcommon/qcommon.h
Normal file
File diff suppressed because it is too large
Load diff
488
code/qcommon/qfiles.h
Normal file
488
code/qcommon/qfiles.h
Normal file
|
@ -0,0 +1,488 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code 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 Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
#ifndef __QFILES_H__
|
||||
#define __QFILES_H__
|
||||
|
||||
//
|
||||
// qfiles.h: quake file formats
|
||||
// This file must be identical in the quake and utils directories
|
||||
//
|
||||
|
||||
// surface geometry should not exceed these limits
|
||||
#define SHADER_MAX_VERTEXES 1000
|
||||
#define SHADER_MAX_INDEXES (6*SHADER_MAX_VERTEXES)
|
||||
|
||||
|
||||
// the maximum size of game relative pathnames
|
||||
#define MAX_QPATH 64
|
||||
|
||||
/*
|
||||
========================================================================
|
||||
|
||||
QVM files
|
||||
|
||||
========================================================================
|
||||
*/
|
||||
|
||||
#define VM_MAGIC 0x12721444
|
||||
typedef struct {
|
||||
int vmMagic;
|
||||
|
||||
int instructionCount;
|
||||
|
||||
int codeOffset;
|
||||
int codeLength;
|
||||
|
||||
int dataOffset;
|
||||
int dataLength;
|
||||
int litLength; // ( dataLength - litLength ) should be byteswapped on load
|
||||
int bssLength; // zero filled memory appended to datalength
|
||||
} vmHeader_t;
|
||||
|
||||
|
||||
/*
|
||||
========================================================================
|
||||
|
||||
PCX files are used for 8 bit images
|
||||
|
||||
========================================================================
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
char manufacturer;
|
||||
char version;
|
||||
char encoding;
|
||||
char bits_per_pixel;
|
||||
unsigned short xmin,ymin,xmax,ymax;
|
||||
unsigned short hres,vres;
|
||||
unsigned char palette[48];
|
||||
char reserved;
|
||||
char color_planes;
|
||||
unsigned short bytes_per_line;
|
||||
unsigned short palette_type;
|
||||
char filler[58];
|
||||
unsigned char data; // unbounded
|
||||
} pcx_t;
|
||||
|
||||
|
||||
/*
|
||||
========================================================================
|
||||
|
||||
TGA files are used for 24/32 bit images
|
||||
|
||||
========================================================================
|
||||
*/
|
||||
|
||||
typedef struct _TargaHeader {
|
||||
unsigned char id_length, colormap_type, image_type;
|
||||
unsigned short colormap_index, colormap_length;
|
||||
unsigned char colormap_size;
|
||||
unsigned short x_origin, y_origin, width, height;
|
||||
unsigned char pixel_size, attributes;
|
||||
} TargaHeader;
|
||||
|
||||
|
||||
|
||||
/*
|
||||
========================================================================
|
||||
|
||||
.MD3 triangle model file format
|
||||
|
||||
========================================================================
|
||||
*/
|
||||
|
||||
#define MD3_IDENT (('3'<<24)+('P'<<16)+('D'<<8)+'I')
|
||||
#define MD3_VERSION 15
|
||||
|
||||
// limits
|
||||
#define MD3_MAX_LODS 3
|
||||
#define MD3_MAX_TRIANGLES 8192 // per surface
|
||||
#define MD3_MAX_VERTS 4096 // per surface
|
||||
#define MD3_MAX_SHADERS 256 // per surface
|
||||
#define MD3_MAX_FRAMES 1024 // per model
|
||||
#define MD3_MAX_SURFACES 32 // per model
|
||||
#define MD3_MAX_TAGS 16 // per frame
|
||||
|
||||
// vertex scales
|
||||
#define MD3_XYZ_SCALE (1.0/64)
|
||||
|
||||
typedef struct md3Frame_s {
|
||||
vec3_t bounds[2];
|
||||
vec3_t localOrigin;
|
||||
float radius;
|
||||
char name[16];
|
||||
} md3Frame_t;
|
||||
|
||||
typedef struct md3Tag_s {
|
||||
char name[MAX_QPATH]; // tag name
|
||||
vec3_t origin;
|
||||
vec3_t axis[3];
|
||||
} md3Tag_t;
|
||||
|
||||
/*
|
||||
** md3Surface_t
|
||||
**
|
||||
** CHUNK SIZE
|
||||
** header sizeof( md3Surface_t )
|
||||
** shaders sizeof( md3Shader_t ) * numShaders
|
||||
** triangles[0] sizeof( md3Triangle_t ) * numTriangles
|
||||
** st sizeof( md3St_t ) * numVerts
|
||||
** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames
|
||||
*/
|
||||
typedef struct {
|
||||
int ident; //
|
||||
|
||||
char name[MAX_QPATH]; // polyset name
|
||||
|
||||
int flags;
|
||||
int numFrames; // all surfaces in a model should have the same
|
||||
|
||||
int numShaders; // all surfaces in a model should have the same
|
||||
int numVerts;
|
||||
|
||||
int numTriangles;
|
||||
int ofsTriangles;
|
||||
|
||||
int ofsShaders; // offset from start of md3Surface_t
|
||||
int ofsSt; // texture coords are common for all frames
|
||||
int ofsXyzNormals; // numVerts * numFrames
|
||||
|
||||
int ofsEnd; // next surface follows
|
||||
} md3Surface_t;
|
||||
|
||||
typedef struct {
|
||||
char name[MAX_QPATH];
|
||||
int shaderIndex; // for in-game use
|
||||
} md3Shader_t;
|
||||
|
||||
typedef struct {
|
||||
int indexes[3];
|
||||
} md3Triangle_t;
|
||||
|
||||
typedef struct {
|
||||
float st[2];
|
||||
} md3St_t;
|
||||
|
||||
typedef struct {
|
||||
short xyz[3];
|
||||
short normal;
|
||||
} md3XyzNormal_t;
|
||||
|
||||
typedef struct {
|
||||
int ident;
|
||||
int version;
|
||||
|
||||
char name[MAX_QPATH]; // model name
|
||||
|
||||
int flags;
|
||||
|
||||
int numFrames;
|
||||
int numTags;
|
||||
int numSurfaces;
|
||||
|
||||
int numSkins;
|
||||
|
||||
int ofsFrames; // offset for first frame
|
||||
int ofsTags; // numFrames * numTags
|
||||
int ofsSurfaces; // first surface, others follow
|
||||
|
||||
int ofsEnd; // end of file
|
||||
} md3Header_t;
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
MD4 file format
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#define MD4_IDENT (('4'<<24)+('P'<<16)+('D'<<8)+'I')
|
||||
#define MD4_VERSION 1
|
||||
#define MD4_MAX_BONES 128
|
||||
|
||||
typedef struct {
|
||||
int boneIndex; // these are indexes into the boneReferences,
|
||||
float boneWeight; // not the global per-frame bone list
|
||||
vec3_t offset;
|
||||
} md4Weight_t;
|
||||
|
||||
typedef struct {
|
||||
vec3_t normal;
|
||||
vec2_t texCoords;
|
||||
int numWeights;
|
||||
md4Weight_t weights[1]; // variable sized
|
||||
} md4Vertex_t;
|
||||
|
||||
typedef struct {
|
||||
int indexes[3];
|
||||
} md4Triangle_t;
|
||||
|
||||
typedef struct {
|
||||
int ident;
|
||||
|
||||
char name[MAX_QPATH]; // polyset name
|
||||
char shader[MAX_QPATH];
|
||||
int shaderIndex; // for in-game use
|
||||
|
||||
int ofsHeader; // this will be a negative number
|
||||
|
||||
int numVerts;
|
||||
int ofsVerts;
|
||||
|
||||
int numTriangles;
|
||||
int ofsTriangles;
|
||||
|
||||
// Bone references are a set of ints representing all the bones
|
||||
// present in any vertex weights for this surface. This is
|
||||
// needed because a model may have surfaces that need to be
|
||||
// drawn at different sort times, and we don't want to have
|
||||
// to re-interpolate all the bones for each surface.
|
||||
int numBoneReferences;
|
||||
int ofsBoneReferences;
|
||||
|
||||
int ofsEnd; // next surface follows
|
||||
} md4Surface_t;
|
||||
|
||||
typedef struct {
|
||||
float matrix[3][4];
|
||||
} md4Bone_t;
|
||||
|
||||
typedef struct {
|
||||
vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame
|
||||
vec3_t localOrigin; // midpoint of bounds, used for sphere cull
|
||||
float radius; // dist from localOrigin to corner
|
||||
md4Bone_t bones[1]; // [numBones]
|
||||
} md4Frame_t;
|
||||
|
||||
typedef struct {
|
||||
int numSurfaces;
|
||||
int ofsSurfaces; // first surface, others follow
|
||||
int ofsEnd; // next lod follows
|
||||
} md4LOD_t;
|
||||
|
||||
typedef struct {
|
||||
int ident;
|
||||
int version;
|
||||
|
||||
char name[MAX_QPATH]; // model name
|
||||
|
||||
// frames and bones are shared by all levels of detail
|
||||
int numFrames;
|
||||
int numBones;
|
||||
int ofsBoneNames; // char name[ MAX_QPATH ]
|
||||
int ofsFrames; // md4Frame_t[numFrames]
|
||||
|
||||
// each level of detail has completely separate sets of surfaces
|
||||
int numLODs;
|
||||
int ofsLODs;
|
||||
|
||||
int ofsEnd; // end of file
|
||||
} md4Header_t;
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
.BSP file format
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
|
||||
#define BSP_IDENT (('P'<<24)+('S'<<16)+('B'<<8)+'I')
|
||||
// little-endian "IBSP"
|
||||
|
||||
#define BSP_VERSION 46
|
||||
|
||||
|
||||
// there shouldn't be any problem with increasing these values at the
|
||||
// expense of more memory allocation in the utilities
|
||||
#define MAX_MAP_MODELS 0x400
|
||||
#define MAX_MAP_BRUSHES 0x8000
|
||||
#define MAX_MAP_ENTITIES 0x800
|
||||
#define MAX_MAP_ENTSTRING 0x40000
|
||||
#define MAX_MAP_SHADERS 0x400
|
||||
|
||||
#define MAX_MAP_AREAS 0x100 // MAX_MAP_AREA_BYTES in q_shared must match!
|
||||
#define MAX_MAP_FOGS 0x100
|
||||
#define MAX_MAP_PLANES 0x20000
|
||||
#define MAX_MAP_NODES 0x20000
|
||||
#define MAX_MAP_BRUSHSIDES 0x20000
|
||||
#define MAX_MAP_LEAFS 0x20000
|
||||
#define MAX_MAP_LEAFFACES 0x20000
|
||||
#define MAX_MAP_LEAFBRUSHES 0x40000
|
||||
#define MAX_MAP_PORTALS 0x20000
|
||||
#define MAX_MAP_LIGHTING 0x800000
|
||||
#define MAX_MAP_LIGHTGRID 0x800000
|
||||
#define MAX_MAP_VISIBILITY 0x200000
|
||||
|
||||
#define MAX_MAP_DRAW_SURFS 0x20000
|
||||
#define MAX_MAP_DRAW_VERTS 0x80000
|
||||
#define MAX_MAP_DRAW_INDEXES 0x80000
|
||||
|
||||
|
||||
// key / value pair sizes in the entities lump
|
||||
#define MAX_KEY 32
|
||||
#define MAX_VALUE 1024
|
||||
|
||||
// the editor uses these predefined yaw angles to orient entities up or down
|
||||
#define ANGLE_UP -1
|
||||
#define ANGLE_DOWN -2
|
||||
|
||||
#define LIGHTMAP_WIDTH 128
|
||||
#define LIGHTMAP_HEIGHT 128
|
||||
|
||||
#define MAX_WORLD_COORD ( 128*1024 )
|
||||
#define MIN_WORLD_COORD ( -128*1024 )
|
||||
#define WORLD_SIZE ( MAX_WORLD_COORD - MIN_WORLD_COORD )
|
||||
|
||||
//=============================================================================
|
||||
|
||||
|
||||
typedef struct {
|
||||
int fileofs, filelen;
|
||||
} lump_t;
|
||||
|
||||
#define LUMP_ENTITIES 0
|
||||
#define LUMP_SHADERS 1
|
||||
#define LUMP_PLANES 2
|
||||
#define LUMP_NODES 3
|
||||
#define LUMP_LEAFS 4
|
||||
#define LUMP_LEAFSURFACES 5
|
||||
#define LUMP_LEAFBRUSHES 6
|
||||
#define LUMP_MODELS 7
|
||||
#define LUMP_BRUSHES 8
|
||||
#define LUMP_BRUSHSIDES 9
|
||||
#define LUMP_DRAWVERTS 10
|
||||
#define LUMP_DRAWINDEXES 11
|
||||
#define LUMP_FOGS 12
|
||||
#define LUMP_SURFACES 13
|
||||
#define LUMP_LIGHTMAPS 14
|
||||
#define LUMP_LIGHTGRID 15
|
||||
#define LUMP_VISIBILITY 16
|
||||
#define HEADER_LUMPS 17
|
||||
|
||||
typedef struct {
|
||||
int ident;
|
||||
int version;
|
||||
|
||||
lump_t lumps[HEADER_LUMPS];
|
||||
} dheader_t;
|
||||
|
||||
typedef struct {
|
||||
float mins[3], maxs[3];
|
||||
int firstSurface, numSurfaces;
|
||||
int firstBrush, numBrushes;
|
||||
} dmodel_t;
|
||||
|
||||
typedef struct {
|
||||
char shader[MAX_QPATH];
|
||||
int surfaceFlags;
|
||||
int contentFlags;
|
||||
} dshader_t;
|
||||
|
||||
// planes x^1 is allways the opposite of plane x
|
||||
|
||||
typedef struct {
|
||||
float normal[3];
|
||||
float dist;
|
||||
} dplane_t;
|
||||
|
||||
typedef struct {
|
||||
int planeNum;
|
||||
int children[2]; // negative numbers are -(leafs+1), not nodes
|
||||
int mins[3]; // for frustom culling
|
||||
int maxs[3];
|
||||
} dnode_t;
|
||||
|
||||
typedef struct {
|
||||
int cluster; // -1 = opaque cluster (do I still store these?)
|
||||
int area;
|
||||
|
||||
int mins[3]; // for frustum culling
|
||||
int maxs[3];
|
||||
|
||||
int firstLeafSurface;
|
||||
int numLeafSurfaces;
|
||||
|
||||
int firstLeafBrush;
|
||||
int numLeafBrushes;
|
||||
} dleaf_t;
|
||||
|
||||
typedef struct {
|
||||
int planeNum; // positive plane side faces out of the leaf
|
||||
int shaderNum;
|
||||
} dbrushside_t;
|
||||
|
||||
typedef struct {
|
||||
int firstSide;
|
||||
int numSides;
|
||||
int shaderNum; // the shader that determines the contents flags
|
||||
} dbrush_t;
|
||||
|
||||
typedef struct {
|
||||
char shader[MAX_QPATH];
|
||||
int brushNum;
|
||||
int visibleSide; // the brush side that ray tests need to clip against (-1 == none)
|
||||
} dfog_t;
|
||||
|
||||
typedef struct {
|
||||
vec3_t xyz;
|
||||
float st[2];
|
||||
float lightmap[2];
|
||||
vec3_t normal;
|
||||
byte color[4];
|
||||
} drawVert_t;
|
||||
|
||||
typedef enum {
|
||||
MST_BAD,
|
||||
MST_PLANAR,
|
||||
MST_PATCH,
|
||||
MST_TRIANGLE_SOUP,
|
||||
MST_FLARE
|
||||
} mapSurfaceType_t;
|
||||
|
||||
typedef struct {
|
||||
int shaderNum;
|
||||
int fogNum;
|
||||
int surfaceType;
|
||||
|
||||
int firstVert;
|
||||
int numVerts;
|
||||
|
||||
int firstIndex;
|
||||
int numIndexes;
|
||||
|
||||
int lightmapNum;
|
||||
int lightmapX, lightmapY;
|
||||
int lightmapWidth, lightmapHeight;
|
||||
|
||||
vec3_t lightmapOrigin;
|
||||
vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds
|
||||
|
||||
int patchWidth;
|
||||
int patchHeight;
|
||||
} dsurface_t;
|
||||
|
||||
|
||||
#endif
|
4299
code/qcommon/unzip.c
Normal file
4299
code/qcommon/unzip.c
Normal file
File diff suppressed because it is too large
Load diff
336
code/qcommon/unzip.h
Normal file
336
code/qcommon/unzip.h
Normal file
|
@ -0,0 +1,336 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code 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 Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP)
|
||||
/* like the STRICT of WIN32, we define a pointer that cannot be converted
|
||||
from (void*) without cast */
|
||||
typedef struct TagunzFile__ { int unused; } unzFile__;
|
||||
typedef unzFile__ *unzFile;
|
||||
#else
|
||||
typedef void* unzFile;
|
||||
#endif
|
||||
|
||||
/* tm_unz contain date/time info */
|
||||
typedef struct tm_unz_s
|
||||
{
|
||||
unsigned int tm_sec; /* seconds after the minute - [0,59] */
|
||||
unsigned int tm_min; /* minutes after the hour - [0,59] */
|
||||
unsigned int tm_hour; /* hours since midnight - [0,23] */
|
||||
unsigned int tm_mday; /* day of the month - [1,31] */
|
||||
unsigned int tm_mon; /* months since January - [0,11] */
|
||||
unsigned int tm_year; /* years - [1980..2044] */
|
||||
} tm_unz;
|
||||
|
||||
/* unz_global_info structure contain global data about the ZIPfile
|
||||
These data comes from the end of central dir */
|
||||
typedef struct unz_global_info_s
|
||||
{
|
||||
unsigned long number_entry; /* total number of entries in the central dir on this disk */
|
||||
unsigned long size_comment; /* size of the global comment of the zipfile */
|
||||
} unz_global_info;
|
||||
|
||||
|
||||
/* unz_file_info contain information about a file in the zipfile */
|
||||
typedef struct unz_file_info_s
|
||||
{
|
||||
unsigned long version; /* version made by 2 unsigned chars */
|
||||
unsigned long version_needed; /* version needed to extract 2 unsigned chars */
|
||||
unsigned long flag; /* general purpose bit flag 2 unsigned chars */
|
||||
unsigned long compression_method; /* compression method 2 unsigned chars */
|
||||
unsigned long dosDate; /* last mod file date in Dos fmt 4 unsigned chars */
|
||||
unsigned long crc; /* crc-32 4 unsigned chars */
|
||||
unsigned long compressed_size; /* compressed size 4 unsigned chars */
|
||||
unsigned long uncompressed_size; /* uncompressed size 4 unsigned chars */
|
||||
unsigned long size_filename; /* filename length 2 unsigned chars */
|
||||
unsigned long size_file_extra; /* extra field length 2 unsigned chars */
|
||||
unsigned long size_file_comment; /* file comment length 2 unsigned chars */
|
||||
|
||||
unsigned long disk_num_start; /* disk number start 2 unsigned chars */
|
||||
unsigned long internal_fa; /* internal file attributes 2 unsigned chars */
|
||||
unsigned long external_fa; /* external file attributes 4 unsigned chars */
|
||||
|
||||
tm_unz tmu_date;
|
||||
} unz_file_info;
|
||||
|
||||
/* unz_file_info_interntal contain internal info about a file in zipfile*/
|
||||
typedef struct unz_file_info_internal_s
|
||||
{
|
||||
unsigned long offset_curfile;/* relative offset of static header 4 unsigned chars */
|
||||
} unz_file_info_internal;
|
||||
|
||||
typedef void* (*alloc_func) (void* opaque, unsigned int items, unsigned int size);
|
||||
typedef void (*free_func) (void* opaque, void* address);
|
||||
|
||||
struct internal_state;
|
||||
|
||||
typedef struct z_stream_s {
|
||||
unsigned char *next_in; /* next input unsigned char */
|
||||
unsigned int avail_in; /* number of unsigned chars available at next_in */
|
||||
unsigned long total_in; /* total nb of input unsigned chars read so */
|
||||
|
||||
unsigned char *next_out; /* next output unsigned char should be put there */
|
||||
unsigned int avail_out; /* remaining free space at next_out */
|
||||
unsigned long total_out; /* total nb of unsigned chars output so */
|
||||
|
||||
char *msg; /* last error message, NULL if no error */
|
||||
struct internal_state *state; /* not visible by applications */
|
||||
|
||||
alloc_func zalloc; /* used to allocate the internal state */
|
||||
free_func zfree; /* used to free the internal state */
|
||||
unsigned char* opaque; /* private data object passed to zalloc and zfree */
|
||||
|
||||
int data_type; /* best guess about the data type: ascii or binary */
|
||||
unsigned long adler; /* adler32 value of the uncompressed data */
|
||||
unsigned long reserved; /* reserved for future use */
|
||||
} z_stream;
|
||||
|
||||
typedef z_stream *z_streamp;
|
||||
|
||||
|
||||
/* file_in_zip_read_info_s contain internal information about a file in zipfile,
|
||||
when reading and decompress it */
|
||||
typedef struct
|
||||
{
|
||||
char *read_buffer; /* internal buffer for compressed data */
|
||||
z_stream stream; /* zLib stream structure for inflate */
|
||||
|
||||
unsigned long pos_in_zipfile; /* position in unsigned char on the zipfile, for fseek*/
|
||||
unsigned long stream_initialised; /* flag set if stream structure is initialised*/
|
||||
|
||||
unsigned long offset_local_extrafield;/* offset of the static extra field */
|
||||
unsigned int size_local_extrafield;/* size of the static extra field */
|
||||
unsigned long pos_local_extrafield; /* position in the static extra field in read*/
|
||||
|
||||
unsigned long crc32; /* crc32 of all data uncompressed */
|
||||
unsigned long crc32_wait; /* crc32 we must obtain after decompress all */
|
||||
unsigned long rest_read_compressed; /* number of unsigned char to be decompressed */
|
||||
unsigned long rest_read_uncompressed;/*number of unsigned char to be obtained after decomp*/
|
||||
FILE* file; /* io structore of the zipfile */
|
||||
unsigned long compression_method; /* compression method (0==store) */
|
||||
unsigned long byte_before_the_zipfile;/* unsigned char before the zipfile, (>0 for sfx)*/
|
||||
} file_in_zip_read_info_s;
|
||||
|
||||
|
||||
/* unz_s contain internal information about the zipfile
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
FILE* file; /* io structore of the zipfile */
|
||||
unz_global_info gi; /* public global information */
|
||||
unsigned long byte_before_the_zipfile;/* unsigned char before the zipfile, (>0 for sfx)*/
|
||||
unsigned long num_file; /* number of the current file in the zipfile*/
|
||||
unsigned long pos_in_central_dir; /* pos of the current file in the central dir*/
|
||||
unsigned long current_file_ok; /* flag about the usability of the current file*/
|
||||
unsigned long central_pos; /* position of the beginning of the central dir*/
|
||||
|
||||
unsigned long size_central_dir; /* size of the central directory */
|
||||
unsigned long offset_central_dir; /* offset of start of central directory with
|
||||
respect to the starting disk number */
|
||||
|
||||
unz_file_info cur_file_info; /* public info about the current file in zip*/
|
||||
unz_file_info_internal cur_file_info_internal; /* private info about it*/
|
||||
file_in_zip_read_info_s* pfile_in_zip_read; /* structure about the current
|
||||
file if we are decompressing it */
|
||||
unsigned char* tmpFile;
|
||||
int tmpPos,tmpSize;
|
||||
} unz_s;
|
||||
|
||||
#define UNZ_OK (0)
|
||||
#define UNZ_END_OF_LIST_OF_FILE (-100)
|
||||
#define UNZ_ERRNO (Z_ERRNO)
|
||||
#define UNZ_EOF (0)
|
||||
#define UNZ_PARAMERROR (-102)
|
||||
#define UNZ_BADZIPFILE (-103)
|
||||
#define UNZ_INTERNALERROR (-104)
|
||||
#define UNZ_CRCERROR (-105)
|
||||
|
||||
#define UNZ_CASESENSITIVE 1
|
||||
#define UNZ_NOTCASESENSITIVE 2
|
||||
#define UNZ_OSDEFAULTCASE 0
|
||||
|
||||
extern int unzStringFileNameCompare (const char* fileName1, const char* fileName2, int iCaseSensitivity);
|
||||
|
||||
/*
|
||||
Compare two filename (fileName1,fileName2).
|
||||
If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp)
|
||||
If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi
|
||||
or strcasecmp)
|
||||
If iCaseSenisivity = 0, case sensitivity is defaut of your operating system
|
||||
(like 1 on Unix, 2 on Windows)
|
||||
*/
|
||||
|
||||
extern unzFile unzOpen (const char *path);
|
||||
extern unzFile unzReOpen (const char* path, unzFile file);
|
||||
|
||||
/*
|
||||
Open a Zip file. path contain the full pathname (by example,
|
||||
on a Windows NT computer "c:\\zlib\\zlib111.zip" or on an Unix computer
|
||||
"zlib/zlib111.zip".
|
||||
If the zipfile cannot be opened (file don't exist or in not valid), the
|
||||
return value is NULL.
|
||||
Else, the return value is a unzFile Handle, usable with other function
|
||||
of this unzip package.
|
||||
*/
|
||||
|
||||
extern int unzClose (unzFile file);
|
||||
|
||||
/*
|
||||
Close a ZipFile opened with unzipOpen.
|
||||
If there is files inside the .Zip opened with unzOpenCurrentFile (see later),
|
||||
these files MUST be closed with unzipCloseCurrentFile before call unzipClose.
|
||||
return UNZ_OK if there is no problem. */
|
||||
|
||||
extern int unzGetGlobalInfo (unzFile file, unz_global_info *pglobal_info);
|
||||
|
||||
/*
|
||||
Write info about the ZipFile in the *pglobal_info structure.
|
||||
No preparation of the structure is needed
|
||||
return UNZ_OK if there is no problem. */
|
||||
|
||||
|
||||
extern int unzGetGlobalComment (unzFile file, char *szComment, unsigned long uSizeBuf);
|
||||
|
||||
/*
|
||||
Get the global comment string of the ZipFile, in the szComment buffer.
|
||||
uSizeBuf is the size of the szComment buffer.
|
||||
return the number of unsigned char copied or an error code <0
|
||||
*/
|
||||
|
||||
|
||||
/***************************************************************************/
|
||||
/* Unzip package allow you browse the directory of the zipfile */
|
||||
|
||||
extern int unzGoToFirstFile (unzFile file);
|
||||
|
||||
/*
|
||||
Set the current file of the zipfile to the first file.
|
||||
return UNZ_OK if there is no problem
|
||||
*/
|
||||
|
||||
extern int unzGoToNextFile (unzFile file);
|
||||
|
||||
/*
|
||||
Set the current file of the zipfile to the next file.
|
||||
return UNZ_OK if there is no problem
|
||||
return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest.
|
||||
*/
|
||||
|
||||
extern int unzGetCurrentFileInfoPosition (unzFile file, unsigned long *pos );
|
||||
|
||||
/*
|
||||
Get the position of the info of the current file in the zip.
|
||||
return UNZ_OK if there is no problem
|
||||
*/
|
||||
|
||||
extern int unzSetCurrentFileInfoPosition (unzFile file, unsigned long pos );
|
||||
|
||||
/*
|
||||
Set the position of the info of the current file in the zip.
|
||||
return UNZ_OK if there is no problem
|
||||
*/
|
||||
|
||||
extern int unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity);
|
||||
|
||||
/*
|
||||
Try locate the file szFileName in the zipfile.
|
||||
For the iCaseSensitivity signification, see unzStringFileNameCompare
|
||||
|
||||
return value :
|
||||
UNZ_OK if the file is found. It becomes the current file.
|
||||
UNZ_END_OF_LIST_OF_FILE if the file is not found
|
||||
*/
|
||||
|
||||
|
||||
extern int unzGetCurrentFileInfo (unzFile file, unz_file_info *pfile_info, char *szFileName, unsigned long fileNameBufferSize, void *extraField, unsigned long extraFieldBufferSize, char *szComment, unsigned long commentBufferSize);
|
||||
|
||||
/*
|
||||
Get Info about the current file
|
||||
if pfile_info!=NULL, the *pfile_info structure will contain somes info about
|
||||
the current file
|
||||
if szFileName!=NULL, the filemane string will be copied in szFileName
|
||||
(fileNameBufferSize is the size of the buffer)
|
||||
if extraField!=NULL, the extra field information will be copied in extraField
|
||||
(extraFieldBufferSize is the size of the buffer).
|
||||
This is the Central-header version of the extra field
|
||||
if szComment!=NULL, the comment string of the file will be copied in szComment
|
||||
(commentBufferSize is the size of the buffer)
|
||||
*/
|
||||
|
||||
/***************************************************************************/
|
||||
/* for reading the content of the current zipfile, you can open it, read data
|
||||
from it, and close it (you can close it before reading all the file)
|
||||
*/
|
||||
|
||||
extern int unzOpenCurrentFile (unzFile file);
|
||||
|
||||
/*
|
||||
Open for reading data the current file in the zipfile.
|
||||
If there is no error, the return value is UNZ_OK.
|
||||
*/
|
||||
|
||||
extern int unzCloseCurrentFile (unzFile file);
|
||||
|
||||
/*
|
||||
Close the file in zip opened with unzOpenCurrentFile
|
||||
Return UNZ_CRCERROR if all the file was read but the CRC is not good
|
||||
*/
|
||||
|
||||
|
||||
extern int unzReadCurrentFile (unzFile file, void* buf, unsigned len);
|
||||
|
||||
/*
|
||||
Read unsigned chars from the current file (opened by unzOpenCurrentFile)
|
||||
buf contain buffer where data must be copied
|
||||
len the size of buf.
|
||||
|
||||
return the number of unsigned char copied if somes unsigned chars are copied
|
||||
return 0 if the end of file was reached
|
||||
return <0 with error code if there is an error
|
||||
(UNZ_ERRNO for IO error, or zLib error for uncompress error)
|
||||
*/
|
||||
|
||||
extern long unztell(unzFile file);
|
||||
|
||||
/*
|
||||
Give the current position in uncompressed data
|
||||
*/
|
||||
|
||||
extern int unzeof (unzFile file);
|
||||
|
||||
/*
|
||||
return 1 if the end of file was reached, 0 elsewhere
|
||||
*/
|
||||
|
||||
extern int unzGetLocalExtrafield (unzFile file, void* buf, unsigned len);
|
||||
|
||||
/*
|
||||
Read extra field from the current file (opened by unzOpenCurrentFile)
|
||||
This is the local-header version of the extra field (sometimes, there is
|
||||
more info in the local-header version than in the central-header)
|
||||
|
||||
if buf==NULL, it return the size of the local extra field
|
||||
|
||||
if buf!=NULL, len is the size of the buffer, the extra header is copied in
|
||||
buf.
|
||||
the return value is the number of unsigned chars copied in buf, or (if <0)
|
||||
the error code
|
||||
*/
|
835
code/qcommon/vm.c
Normal file
835
code/qcommon/vm.c
Normal file
|
@ -0,0 +1,835 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code 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 Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
// vm.c -- virtual machine
|
||||
|
||||
/*
|
||||
|
||||
|
||||
intermix code and data
|
||||
symbol table
|
||||
|
||||
a dll has one imported function: VM_SystemCall
|
||||
and one exported function: Perform
|
||||
|
||||
|
||||
*/
|
||||
|
||||
#include "vm_local.h"
|
||||
|
||||
|
||||
vm_t *currentVM = NULL; // bk001212
|
||||
vm_t *lastVM = NULL; // bk001212
|
||||
int vm_debugLevel;
|
||||
|
||||
#define MAX_VM 3
|
||||
vm_t vmTable[MAX_VM];
|
||||
|
||||
|
||||
void VM_VmInfo_f( void );
|
||||
void VM_VmProfile_f( void );
|
||||
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
void VM_Debug( int level ) {
|
||||
vm_debugLevel = level;
|
||||
}
|
||||
|
||||
/*
|
||||
==============
|
||||
VM_Init
|
||||
==============
|
||||
*/
|
||||
void VM_Init( void ) {
|
||||
Cvar_Get( "vm_cgame", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2
|
||||
Cvar_Get( "vm_game", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2
|
||||
Cvar_Get( "vm_ui", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2
|
||||
|
||||
Cmd_AddCommand ("vmprofile", VM_VmProfile_f );
|
||||
Cmd_AddCommand ("vminfo", VM_VmInfo_f );
|
||||
|
||||
Com_Memset( vmTable, 0, sizeof( vmTable ) );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
VM_ValueToSymbol
|
||||
|
||||
Assumes a program counter value
|
||||
===============
|
||||
*/
|
||||
const char *VM_ValueToSymbol( vm_t *vm, int value ) {
|
||||
vmSymbol_t *sym;
|
||||
static char text[MAX_TOKEN_CHARS];
|
||||
|
||||
sym = vm->symbols;
|
||||
if ( !sym ) {
|
||||
return "NO SYMBOLS";
|
||||
}
|
||||
|
||||
// find the symbol
|
||||
while ( sym->next && sym->next->symValue <= value ) {
|
||||
sym = sym->next;
|
||||
}
|
||||
|
||||
if ( value == sym->symValue ) {
|
||||
return sym->symName;
|
||||
}
|
||||
|
||||
Com_sprintf( text, sizeof( text ), "%s+%i", sym->symName, value - sym->symValue );
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
VM_ValueToFunctionSymbol
|
||||
|
||||
For profiling, find the symbol behind this value
|
||||
===============
|
||||
*/
|
||||
vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ) {
|
||||
vmSymbol_t *sym;
|
||||
static vmSymbol_t nullSym;
|
||||
|
||||
sym = vm->symbols;
|
||||
if ( !sym ) {
|
||||
return &nullSym;
|
||||
}
|
||||
|
||||
while ( sym->next && sym->next->symValue <= value ) {
|
||||
sym = sym->next;
|
||||
}
|
||||
|
||||
return sym;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
VM_SymbolToValue
|
||||
===============
|
||||
*/
|
||||
int VM_SymbolToValue( vm_t *vm, const char *symbol ) {
|
||||
vmSymbol_t *sym;
|
||||
|
||||
for ( sym = vm->symbols ; sym ; sym = sym->next ) {
|
||||
if ( !strcmp( symbol, sym->symName ) ) {
|
||||
return sym->symValue;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=====================
|
||||
VM_SymbolForCompiledPointer
|
||||
=====================
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
ParseHex
|
||||
===============
|
||||
*/
|
||||
int ParseHex( const char *text ) {
|
||||
int value;
|
||||
int c;
|
||||
|
||||
value = 0;
|
||||
while ( ( c = *text++ ) != 0 ) {
|
||||
if ( c >= '0' && c <= '9' ) {
|
||||
value = value * 16 + c - '0';
|
||||
continue;
|
||||
}
|
||||
if ( c >= 'a' && c <= 'f' ) {
|
||||
value = value * 16 + 10 + c - 'a';
|
||||
continue;
|
||||
}
|
||||
if ( c >= 'A' && c <= 'F' ) {
|
||||
value = value * 16 + 10 + c - 'A';
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
VM_LoadSymbols
|
||||
===============
|
||||
*/
|
||||
void VM_LoadSymbols( vm_t *vm ) {
|
||||
int len;
|
||||
char *mapfile, *text_p, *token;
|
||||
char name[MAX_QPATH];
|
||||
char symbols[MAX_QPATH];
|
||||
vmSymbol_t **prev, *sym;
|
||||
int count;
|
||||
int value;
|
||||
int chars;
|
||||
int segment;
|
||||
int numInstructions;
|
||||
|
||||
// don't load symbols if not developer
|
||||
if ( !com_developer->integer ) {
|
||||
return;
|
||||
}
|
||||
|
||||
COM_StripExtension( vm->name, name );
|
||||
Com_sprintf( symbols, sizeof( symbols ), "vm/%s.map", name );
|
||||
len = FS_ReadFile( symbols, (void **)&mapfile );
|
||||
if ( !mapfile ) {
|
||||
Com_Printf( "Couldn't load symbol file: %s\n", symbols );
|
||||
return;
|
||||
}
|
||||
|
||||
numInstructions = vm->instructionPointersLength >> 2;
|
||||
|
||||
// parse the symbols
|
||||
text_p = mapfile;
|
||||
prev = &vm->symbols;
|
||||
count = 0;
|
||||
|
||||
while ( 1 ) {
|
||||
token = COM_Parse( &text_p );
|
||||
if ( !token[0] ) {
|
||||
break;
|
||||
}
|
||||
segment = ParseHex( token );
|
||||
if ( segment ) {
|
||||
COM_Parse( &text_p );
|
||||
COM_Parse( &text_p );
|
||||
continue; // only load code segment values
|
||||
}
|
||||
|
||||
token = COM_Parse( &text_p );
|
||||
if ( !token[0] ) {
|
||||
Com_Printf( "WARNING: incomplete line at end of file\n" );
|
||||
break;
|
||||
}
|
||||
value = ParseHex( token );
|
||||
|
||||
token = COM_Parse( &text_p );
|
||||
if ( !token[0] ) {
|
||||
Com_Printf( "WARNING: incomplete line at end of file\n" );
|
||||
break;
|
||||
}
|
||||
chars = strlen( token );
|
||||
sym = Hunk_Alloc( sizeof( *sym ) + chars, h_high );
|
||||
*prev = sym;
|
||||
prev = &sym->next;
|
||||
sym->next = NULL;
|
||||
|
||||
// convert value from an instruction number to a code offset
|
||||
if ( value >= 0 && value < numInstructions ) {
|
||||
value = vm->instructionPointers[value];
|
||||
}
|
||||
|
||||
sym->symValue = value;
|
||||
Q_strncpyz( sym->symName, token, chars + 1 );
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
vm->numSymbols = count;
|
||||
Com_Printf( "%i symbols parsed from %s\n", count, symbols );
|
||||
FS_FreeFile( mapfile );
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
VM_DllSyscall
|
||||
|
||||
Dlls will call this directly
|
||||
|
||||
rcg010206 The horror; the horror.
|
||||
|
||||
The syscall mechanism relies on stack manipulation to get it's args.
|
||||
This is likely due to C's inability to pass "..." parameters to
|
||||
a function in one clean chunk. On PowerPC Linux, these parameters
|
||||
are not necessarily passed on the stack, so while (&arg[0] == arg)
|
||||
is true, (&arg[1] == 2nd function parameter) is not necessarily
|
||||
accurate, as arg's value might have been stored to the stack or
|
||||
other piece of scratch memory to give it a valid address, but the
|
||||
next parameter might still be sitting in a register.
|
||||
|
||||
Quake's syscall system also assumes that the stack grows downward,
|
||||
and that any needed types can be squeezed, safely, into a signed int.
|
||||
|
||||
This hack below copies all needed values for an argument to a
|
||||
array in memory, so that Quake can get the correct values. This can
|
||||
also be used on systems where the stack grows upwards, as the
|
||||
presumably standard and safe stdargs.h macros are used.
|
||||
|
||||
As for having enough space in a signed int for your datatypes, well,
|
||||
it might be better to wait for DOOM 3 before you start porting. :)
|
||||
|
||||
The original code, while probably still inherently dangerous, seems
|
||||
to work well enough for the platforms it already works on. Rather
|
||||
than add the performance hit for those platforms, the original code
|
||||
is still in use there.
|
||||
|
||||
For speed, we just grab 15 arguments, and don't worry about exactly
|
||||
how many the syscall actually needs; the extra is thrown away.
|
||||
|
||||
============
|
||||
*/
|
||||
int QDECL VM_DllSyscall( int arg, ... ) {
|
||||
#if ((defined __linux__) && (defined __powerpc__))
|
||||
// rcg010206 - see commentary above
|
||||
int args[16];
|
||||
int i;
|
||||
va_list ap;
|
||||
|
||||
args[0] = arg;
|
||||
|
||||
va_start(ap, arg);
|
||||
for (i = 1; i < sizeof (args) / sizeof (args[i]); i++)
|
||||
args[i] = va_arg(ap, int);
|
||||
va_end(ap);
|
||||
|
||||
return currentVM->systemCall( args );
|
||||
#else // original id code
|
||||
return currentVM->systemCall( &arg );
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
VM_Restart
|
||||
|
||||
Reload the data, but leave everything else in place
|
||||
This allows a server to do a map_restart without changing memory allocation
|
||||
=================
|
||||
*/
|
||||
vm_t *VM_Restart( vm_t *vm ) {
|
||||
vmHeader_t *header;
|
||||
int length;
|
||||
int dataLength;
|
||||
int i;
|
||||
char filename[MAX_QPATH];
|
||||
|
||||
// DLL's can't be restarted in place
|
||||
if ( vm->dllHandle ) {
|
||||
char name[MAX_QPATH];
|
||||
int (*systemCall)( int *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", filename );
|
||||
Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", vm->name );
|
||||
Com_Printf( "Loading vm file %s.\n", filename );
|
||||
length = FS_ReadFile( filename, (void **)&header );
|
||||
if ( !header ) {
|
||||
Com_Error( ERR_DROP, "VM_Restart failed.\n" );
|
||||
}
|
||||
|
||||
// byte swap the header
|
||||
for ( i = 0 ; i < sizeof( *header ) / 4 ; i++ ) {
|
||||
((int *)header)[i] = LittleLong( ((int *)header)[i] );
|
||||
}
|
||||
|
||||
// validate
|
||||
if ( header->vmMagic != VM_MAGIC
|
||||
|| header->bssLength < 0
|
||||
|| header->dataLength < 0
|
||||
|| header->litLength < 0
|
||||
|| header->codeLength <= 0 ) {
|
||||
VM_Free( vm );
|
||||
Com_Error( ERR_FATAL, "%s has bad header", filename );
|
||||
}
|
||||
|
||||
// round up to next power of 2 so all data operations can
|
||||
// be mask protected
|
||||
dataLength = header->dataLength + header->litLength + header->bssLength;
|
||||
for ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) {
|
||||
}
|
||||
dataLength = 1 << i;
|
||||
|
||||
// clear the data
|
||||
Com_Memset( vm->dataBase, 0, dataLength );
|
||||
|
||||
// copy the intialized data
|
||||
Com_Memcpy( vm->dataBase, (byte *)header + header->dataOffset, header->dataLength + header->litLength );
|
||||
|
||||
// byte swap the longs
|
||||
for ( i = 0 ; i < header->dataLength ; i += 4 ) {
|
||||
*(int *)(vm->dataBase + i) = LittleLong( *(int *)(vm->dataBase + i ) );
|
||||
}
|
||||
|
||||
// free the original file
|
||||
FS_FreeFile( header );
|
||||
|
||||
return vm;
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
VM_Create
|
||||
|
||||
If image ends in .qvm it will be interpreted, otherwise
|
||||
it will attempt to load as a system dll
|
||||
================
|
||||
*/
|
||||
|
||||
#define STACK_SIZE 0x20000
|
||||
|
||||
vm_t *VM_Create( const char *module, int (*systemCalls)(int *),
|
||||
vmInterpret_t interpret ) {
|
||||
vm_t *vm;
|
||||
vmHeader_t *header;
|
||||
int length;
|
||||
int dataLength;
|
||||
int i, remaining;
|
||||
char filename[MAX_QPATH];
|
||||
|
||||
if ( !module || !module[0] || !systemCalls ) {
|
||||
Com_Error( ERR_FATAL, "VM_Create: bad parms" );
|
||||
}
|
||||
|
||||
remaining = Hunk_MemoryRemaining();
|
||||
|
||||
// see if we already have the VM
|
||||
for ( i = 0 ; i < MAX_VM ; i++ ) {
|
||||
if (!Q_stricmp(vmTable[i].name, module)) {
|
||||
vm = &vmTable[i];
|
||||
return vm;
|
||||
}
|
||||
}
|
||||
|
||||
// find a free vm
|
||||
for ( i = 0 ; i < MAX_VM ; i++ ) {
|
||||
if ( !vmTable[i].name[0] ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( i == MAX_VM ) {
|
||||
Com_Error( ERR_FATAL, "VM_Create: no free vm_t" );
|
||||
}
|
||||
|
||||
vm = &vmTable[i];
|
||||
|
||||
Q_strncpyz( vm->name, module, sizeof( vm->name ) );
|
||||
vm->systemCall = systemCalls;
|
||||
|
||||
// never allow dll loading with a demo
|
||||
if ( interpret == VMI_NATIVE ) {
|
||||
if ( Cvar_VariableValue( "fs_restrict" ) ) {
|
||||
interpret = VMI_COMPILED;
|
||||
}
|
||||
}
|
||||
|
||||
if ( interpret == VMI_NATIVE ) {
|
||||
// try to load as a system dll
|
||||
Com_Printf( "Loading dll file %s.\n", vm->name );
|
||||
vm->dllHandle = Sys_LoadDll( module, vm->fqpath , &vm->entryPoint, VM_DllSyscall );
|
||||
if ( vm->dllHandle ) {
|
||||
return vm;
|
||||
}
|
||||
|
||||
Com_Printf( "Failed to load dll, looking for qvm.\n" );
|
||||
interpret = VMI_COMPILED;
|
||||
}
|
||||
|
||||
// load the image
|
||||
Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", vm->name );
|
||||
Com_Printf( "Loading vm file %s.\n", filename );
|
||||
length = FS_ReadFile( filename, (void **)&header );
|
||||
if ( !header ) {
|
||||
Com_Printf( "Failed.\n" );
|
||||
VM_Free( vm );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// byte swap the header
|
||||
for ( i = 0 ; i < sizeof( *header ) / 4 ; i++ ) {
|
||||
((int *)header)[i] = LittleLong( ((int *)header)[i] );
|
||||
}
|
||||
|
||||
// validate
|
||||
if ( header->vmMagic != VM_MAGIC
|
||||
|| header->bssLength < 0
|
||||
|| header->dataLength < 0
|
||||
|| header->litLength < 0
|
||||
|| header->codeLength <= 0 ) {
|
||||
VM_Free( vm );
|
||||
Com_Error( ERR_FATAL, "%s has bad header", filename );
|
||||
}
|
||||
|
||||
// round up to next power of 2 so all data operations can
|
||||
// be mask protected
|
||||
dataLength = header->dataLength + header->litLength + header->bssLength;
|
||||
for ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) {
|
||||
}
|
||||
dataLength = 1 << i;
|
||||
|
||||
// allocate zero filled space for initialized and uninitialized data
|
||||
vm->dataBase = Hunk_Alloc( dataLength, h_high );
|
||||
vm->dataMask = dataLength - 1;
|
||||
|
||||
// copy the intialized data
|
||||
Com_Memcpy( vm->dataBase, (byte *)header + header->dataOffset, header->dataLength + header->litLength );
|
||||
|
||||
// byte swap the longs
|
||||
for ( i = 0 ; i < header->dataLength ; i += 4 ) {
|
||||
*(int *)(vm->dataBase + i) = LittleLong( *(int *)(vm->dataBase + i ) );
|
||||
}
|
||||
|
||||
// allocate space for the jump targets, which will be filled in by the compile/prep functions
|
||||
vm->instructionPointersLength = header->instructionCount * 4;
|
||||
vm->instructionPointers = Hunk_Alloc( vm->instructionPointersLength, h_high );
|
||||
|
||||
// copy or compile the instructions
|
||||
vm->codeLength = header->codeLength;
|
||||
|
||||
if ( interpret >= VMI_COMPILED ) {
|
||||
vm->compiled = qtrue;
|
||||
VM_Compile( vm, header );
|
||||
} else {
|
||||
vm->compiled = qfalse;
|
||||
VM_PrepareInterpreter( vm, header );
|
||||
}
|
||||
|
||||
// free the original file
|
||||
FS_FreeFile( header );
|
||||
|
||||
// load the map file
|
||||
VM_LoadSymbols( vm );
|
||||
|
||||
// the stack is implicitly at the end of the image
|
||||
vm->programStack = vm->dataMask + 1;
|
||||
vm->stackBottom = vm->programStack - STACK_SIZE;
|
||||
|
||||
Com_Printf("%s loaded in %d bytes on the hunk\n", module, remaining - Hunk_MemoryRemaining());
|
||||
|
||||
return vm;
|
||||
}
|
||||
|
||||
/*
|
||||
==============
|
||||
VM_Free
|
||||
==============
|
||||
*/
|
||||
void VM_Free( vm_t *vm ) {
|
||||
|
||||
if ( vm->dllHandle ) {
|
||||
Sys_UnloadDll( vm->dllHandle );
|
||||
Com_Memset( vm, 0, sizeof( *vm ) );
|
||||
}
|
||||
#if 0 // now automatically freed by hunk
|
||||
if ( vm->codeBase ) {
|
||||
Z_Free( vm->codeBase );
|
||||
}
|
||||
if ( vm->dataBase ) {
|
||||
Z_Free( vm->dataBase );
|
||||
}
|
||||
if ( vm->instructionPointers ) {
|
||||
Z_Free( vm->instructionPointers );
|
||||
}
|
||||
#endif
|
||||
Com_Memset( vm, 0, sizeof( *vm ) );
|
||||
|
||||
currentVM = NULL;
|
||||
lastVM = NULL;
|
||||
}
|
||||
|
||||
void VM_Clear(void) {
|
||||
int i;
|
||||
for (i=0;i<MAX_VM; i++) {
|
||||
if ( vmTable[i].dllHandle ) {
|
||||
Sys_UnloadDll( vmTable[i].dllHandle );
|
||||
}
|
||||
Com_Memset( &vmTable[i], 0, sizeof( vm_t ) );
|
||||
}
|
||||
currentVM = NULL;
|
||||
lastVM = NULL;
|
||||
}
|
||||
|
||||
void *VM_ArgPtr( int intValue ) {
|
||||
if ( !intValue ) {
|
||||
return NULL;
|
||||
}
|
||||
// bk001220 - currentVM is missing on reconnect
|
||||
if ( currentVM==NULL )
|
||||
return NULL;
|
||||
|
||||
if ( currentVM->entryPoint ) {
|
||||
return (void *)(currentVM->dataBase + intValue);
|
||||
}
|
||||
else {
|
||||
return (void *)(currentVM->dataBase + (intValue & currentVM->dataMask));
|
||||
}
|
||||
}
|
||||
|
||||
void *VM_ExplicitArgPtr( vm_t *vm, int intValue ) {
|
||||
if ( !intValue ) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// bk010124 - currentVM is missing on reconnect here as well?
|
||||
if ( currentVM==NULL )
|
||||
return NULL;
|
||||
|
||||
//
|
||||
if ( vm->entryPoint ) {
|
||||
return (void *)(vm->dataBase + intValue);
|
||||
}
|
||||
else {
|
||||
return (void *)(vm->dataBase + (intValue & vm->dataMask));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==============
|
||||
VM_Call
|
||||
|
||||
|
||||
Upon a system call, the stack will look like:
|
||||
|
||||
sp+32 parm1
|
||||
sp+28 parm0
|
||||
sp+24 return value
|
||||
sp+20 return address
|
||||
sp+16 local1
|
||||
sp+14 local0
|
||||
sp+12 arg1
|
||||
sp+8 arg0
|
||||
sp+4 return stack
|
||||
sp return address
|
||||
|
||||
An interpreted function will immediately execute
|
||||
an OP_ENTER instruction, which will subtract space for
|
||||
locals from sp
|
||||
==============
|
||||
*/
|
||||
#define MAX_STACK 256
|
||||
#define STACK_MASK (MAX_STACK-1)
|
||||
|
||||
int QDECL VM_Call( vm_t *vm, int callnum, ... ) {
|
||||
vm_t *oldVM;
|
||||
int r;
|
||||
int i;
|
||||
int args[16];
|
||||
va_list ap;
|
||||
|
||||
|
||||
if ( !vm ) {
|
||||
Com_Error( ERR_FATAL, "VM_Call with NULL vm" );
|
||||
}
|
||||
|
||||
oldVM = currentVM;
|
||||
currentVM = vm;
|
||||
lastVM = vm;
|
||||
|
||||
if ( vm_debugLevel ) {
|
||||
Com_Printf( "VM_Call( %i )\n", callnum );
|
||||
}
|
||||
|
||||
// if we have a dll loaded, call it directly
|
||||
if ( vm->entryPoint ) {
|
||||
//rcg010207 - see dissertation at top of VM_DllSyscall() in this file.
|
||||
va_start(ap, callnum);
|
||||
for (i = 0; i < sizeof (args) / sizeof (args[i]); i++) {
|
||||
args[i] = va_arg(ap, int);
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
r = vm->entryPoint( callnum, args[0], args[1], args[2], args[3],
|
||||
args[4], args[5], args[6], args[7],
|
||||
args[8], args[9], args[10], args[11],
|
||||
args[12], args[13], args[14], args[15]);
|
||||
} else if ( vm->compiled ) {
|
||||
r = VM_CallCompiled( vm, &callnum );
|
||||
} else {
|
||||
r = VM_CallInterpreted( vm, &callnum );
|
||||
}
|
||||
|
||||
if ( oldVM != NULL ) // bk001220 - assert(currentVM!=NULL) for oldVM==NULL
|
||||
currentVM = oldVM;
|
||||
return r;
|
||||
}
|
||||
|
||||
//=================================================================
|
||||
|
||||
static int QDECL VM_ProfileSort( const void *a, const void *b ) {
|
||||
vmSymbol_t *sa, *sb;
|
||||
|
||||
sa = *(vmSymbol_t **)a;
|
||||
sb = *(vmSymbol_t **)b;
|
||||
|
||||
if ( sa->profileCount < sb->profileCount ) {
|
||||
return -1;
|
||||
}
|
||||
if ( sa->profileCount > sb->profileCount ) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
==============
|
||||
VM_VmProfile_f
|
||||
|
||||
==============
|
||||
*/
|
||||
void VM_VmProfile_f( void ) {
|
||||
vm_t *vm;
|
||||
vmSymbol_t **sorted, *sym;
|
||||
int i;
|
||||
double total;
|
||||
|
||||
if ( !lastVM ) {
|
||||
return;
|
||||
}
|
||||
|
||||
vm = lastVM;
|
||||
|
||||
if ( !vm->numSymbols ) {
|
||||
return;
|
||||
}
|
||||
|
||||
sorted = Z_Malloc( vm->numSymbols * sizeof( *sorted ) );
|
||||
sorted[0] = vm->symbols;
|
||||
total = sorted[0]->profileCount;
|
||||
for ( i = 1 ; i < vm->numSymbols ; i++ ) {
|
||||
sorted[i] = sorted[i-1]->next;
|
||||
total += sorted[i]->profileCount;
|
||||
}
|
||||
|
||||
qsort( sorted, vm->numSymbols, sizeof( *sorted ), VM_ProfileSort );
|
||||
|
||||
for ( i = 0 ; i < vm->numSymbols ; i++ ) {
|
||||
int perc;
|
||||
|
||||
sym = sorted[i];
|
||||
|
||||
perc = 100 * (float) sym->profileCount / total;
|
||||
Com_Printf( "%2i%% %9i %s\n", perc, sym->profileCount, sym->symName );
|
||||
sym->profileCount = 0;
|
||||
}
|
||||
|
||||
Com_Printf(" %9.0f total\n", total );
|
||||
|
||||
Z_Free( sorted );
|
||||
}
|
||||
|
||||
/*
|
||||
==============
|
||||
VM_VmInfo_f
|
||||
|
||||
==============
|
||||
*/
|
||||
void VM_VmInfo_f( void ) {
|
||||
vm_t *vm;
|
||||
int i;
|
||||
|
||||
Com_Printf( "Registered virtual machines:\n" );
|
||||
for ( i = 0 ; i < MAX_VM ; i++ ) {
|
||||
vm = &vmTable[i];
|
||||
if ( !vm->name[0] ) {
|
||||
break;
|
||||
}
|
||||
Com_Printf( "%s : ", vm->name );
|
||||
if ( vm->dllHandle ) {
|
||||
Com_Printf( "native\n" );
|
||||
continue;
|
||||
}
|
||||
if ( vm->compiled ) {
|
||||
Com_Printf( "compiled on load\n" );
|
||||
} else {
|
||||
Com_Printf( "interpreted\n" );
|
||||
}
|
||||
Com_Printf( " code length : %7i\n", vm->codeLength );
|
||||
Com_Printf( " table length: %7i\n", vm->instructionPointersLength );
|
||||
Com_Printf( " data length : %7i\n", vm->dataMask + 1 );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
VM_LogSyscalls
|
||||
|
||||
Insert calls to this while debugging the vm compiler
|
||||
===============
|
||||
*/
|
||||
void VM_LogSyscalls( int *args ) {
|
||||
static int callnum;
|
||||
static FILE *f;
|
||||
|
||||
if ( !f ) {
|
||||
f = fopen("syscalls.log", "w" );
|
||||
}
|
||||
callnum++;
|
||||
fprintf(f, "%i: %i (%i) = %i %i %i %i\n", callnum, args - (int *)currentVM->dataBase,
|
||||
args[0], args[1], args[2], args[3], args[4] );
|
||||
}
|
||||
|
||||
|
||||
|
||||
#ifdef oDLL_ONLY // bk010215 - for DLL_ONLY dedicated servers/builds w/o VM
|
||||
int VM_CallCompiled( vm_t *vm, int *args ) {
|
||||
return(0);
|
||||
}
|
||||
|
||||
void VM_Compile( vm_t *vm, vmHeader_t *header ) {}
|
||||
#endif // DLL_ONLY
|
889
code/qcommon/vm_interpreted.c
Normal file
889
code/qcommon/vm_interpreted.c
Normal file
|
@ -0,0 +1,889 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code 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 Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
#include "vm_local.h"
|
||||
|
||||
#ifdef DEBUG_VM // bk001204
|
||||
static char *opnames[256] = {
|
||||
"OP_UNDEF",
|
||||
|
||||
"OP_IGNORE",
|
||||
|
||||
"OP_BREAK",
|
||||
|
||||
"OP_ENTER",
|
||||
"OP_LEAVE",
|
||||
"OP_CALL",
|
||||
"OP_PUSH",
|
||||
"OP_POP",
|
||||
|
||||
"OP_CONST",
|
||||
|
||||
"OP_LOCAL",
|
||||
|
||||
"OP_JUMP",
|
||||
|
||||
//-------------------
|
||||
|
||||
"OP_EQ",
|
||||
"OP_NE",
|
||||
|
||||
"OP_LTI",
|
||||
"OP_LEI",
|
||||
"OP_GTI",
|
||||
"OP_GEI",
|
||||
|
||||
"OP_LTU",
|
||||
"OP_LEU",
|
||||
"OP_GTU",
|
||||
"OP_GEU",
|
||||
|
||||
"OP_EQF",
|
||||
"OP_NEF",
|
||||
|
||||
"OP_LTF",
|
||||
"OP_LEF",
|
||||
"OP_GTF",
|
||||
"OP_GEF",
|
||||
|
||||
//-------------------
|
||||
|
||||
"OP_LOAD1",
|
||||
"OP_LOAD2",
|
||||
"OP_LOAD4",
|
||||
"OP_STORE1",
|
||||
"OP_STORE2",
|
||||
"OP_STORE4",
|
||||
"OP_ARG",
|
||||
|
||||
"OP_BLOCK_COPY",
|
||||
|
||||
//-------------------
|
||||
|
||||
"OP_SEX8",
|
||||
"OP_SEX16",
|
||||
|
||||
"OP_NEGI",
|
||||
"OP_ADD",
|
||||
"OP_SUB",
|
||||
"OP_DIVI",
|
||||
"OP_DIVU",
|
||||
"OP_MODI",
|
||||
"OP_MODU",
|
||||
"OP_MULI",
|
||||
"OP_MULU",
|
||||
|
||||
"OP_BAND",
|
||||
"OP_BOR",
|
||||
"OP_BXOR",
|
||||
"OP_BCOM",
|
||||
|
||||
"OP_LSH",
|
||||
"OP_RSHI",
|
||||
"OP_RSHU",
|
||||
|
||||
"OP_NEGF",
|
||||
"OP_ADDF",
|
||||
"OP_SUBF",
|
||||
"OP_DIVF",
|
||||
"OP_MULF",
|
||||
|
||||
"OP_CVIF",
|
||||
"OP_CVFI"
|
||||
};
|
||||
#endif
|
||||
|
||||
#if idppc
|
||||
#if defined(__GNUC__)
|
||||
static inline unsigned int loadWord(void *addr) {
|
||||
unsigned int word;
|
||||
|
||||
asm("lwbrx %0,0,%1" : "=r" (word) : "r" (addr));
|
||||
return word;
|
||||
}
|
||||
#else
|
||||
#define loadWord(addr) __lwbrx(addr,0)
|
||||
#endif
|
||||
#else
|
||||
#define loadWord(addr) *((int *)addr)
|
||||
#endif
|
||||
|
||||
char *VM_Indent( vm_t *vm ) {
|
||||
static char *string = " ";
|
||||
if ( vm->callLevel > 20 ) {
|
||||
return string;
|
||||
}
|
||||
return string + 2 * ( 20 - vm->callLevel );
|
||||
}
|
||||
|
||||
void VM_StackTrace( vm_t *vm, int programCounter, int programStack ) {
|
||||
int count;
|
||||
|
||||
count = 0;
|
||||
do {
|
||||
Com_Printf( "%s\n", VM_ValueToSymbol( vm, programCounter ) );
|
||||
programStack = *(int *)&vm->dataBase[programStack+4];
|
||||
programCounter = *(int *)&vm->dataBase[programStack];
|
||||
} while ( programCounter != -1 && ++count < 32 );
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
====================
|
||||
VM_PrepareInterpreter
|
||||
====================
|
||||
*/
|
||||
void VM_PrepareInterpreter( vm_t *vm, vmHeader_t *header ) {
|
||||
int op;
|
||||
int pc;
|
||||
byte *code;
|
||||
int instruction;
|
||||
int *codeBase;
|
||||
|
||||
vm->codeBase = Hunk_Alloc( vm->codeLength*4, h_high ); // we're now int aligned
|
||||
// memcpy( vm->codeBase, (byte *)header + header->codeOffset, vm->codeLength );
|
||||
|
||||
// we don't need to translate the instructions, but we still need
|
||||
// to find each instructions starting point for jumps
|
||||
pc = 0;
|
||||
instruction = 0;
|
||||
code = (byte *)header + header->codeOffset;
|
||||
codeBase = (int *)vm->codeBase;
|
||||
|
||||
while ( instruction < header->instructionCount ) {
|
||||
vm->instructionPointers[ instruction ] = pc;
|
||||
instruction++;
|
||||
|
||||
op = code[ pc ];
|
||||
codeBase[pc] = op;
|
||||
if ( pc > header->codeLength ) {
|
||||
Com_Error( ERR_FATAL, "VM_PrepareInterpreter: pc > header->codeLength" );
|
||||
}
|
||||
|
||||
pc++;
|
||||
|
||||
// these are the only opcodes that aren't a single byte
|
||||
switch ( op ) {
|
||||
case OP_ENTER:
|
||||
case OP_CONST:
|
||||
case OP_LOCAL:
|
||||
case OP_LEAVE:
|
||||
case OP_EQ:
|
||||
case OP_NE:
|
||||
case OP_LTI:
|
||||
case OP_LEI:
|
||||
case OP_GTI:
|
||||
case OP_GEI:
|
||||
case OP_LTU:
|
||||
case OP_LEU:
|
||||
case OP_GTU:
|
||||
case OP_GEU:
|
||||
case OP_EQF:
|
||||
case OP_NEF:
|
||||
case OP_LTF:
|
||||
case OP_LEF:
|
||||
case OP_GTF:
|
||||
case OP_GEF:
|
||||
case OP_BLOCK_COPY:
|
||||
codeBase[pc+0] = loadWord(&code[pc]);
|
||||
pc += 4;
|
||||
break;
|
||||
case OP_ARG:
|
||||
codeBase[pc+0] = code[pc];
|
||||
pc += 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
pc = 0;
|
||||
instruction = 0;
|
||||
code = (byte *)header + header->codeOffset;
|
||||
codeBase = (int *)vm->codeBase;
|
||||
|
||||
while ( instruction < header->instructionCount ) {
|
||||
op = code[ pc ];
|
||||
instruction++;
|
||||
pc++;
|
||||
switch ( op ) {
|
||||
case OP_ENTER:
|
||||
case OP_CONST:
|
||||
case OP_LOCAL:
|
||||
case OP_LEAVE:
|
||||
case OP_EQ:
|
||||
case OP_NE:
|
||||
case OP_LTI:
|
||||
case OP_LEI:
|
||||
case OP_GTI:
|
||||
case OP_GEI:
|
||||
case OP_LTU:
|
||||
case OP_LEU:
|
||||
case OP_GTU:
|
||||
case OP_GEU:
|
||||
case OP_EQF:
|
||||
case OP_NEF:
|
||||
case OP_LTF:
|
||||
case OP_LEF:
|
||||
case OP_GTF:
|
||||
case OP_GEF:
|
||||
case OP_BLOCK_COPY:
|
||||
switch(op) {
|
||||
case OP_EQ:
|
||||
case OP_NE:
|
||||
case OP_LTI:
|
||||
case OP_LEI:
|
||||
case OP_GTI:
|
||||
case OP_GEI:
|
||||
case OP_LTU:
|
||||
case OP_LEU:
|
||||
case OP_GTU:
|
||||
case OP_GEU:
|
||||
case OP_EQF:
|
||||
case OP_NEF:
|
||||
case OP_LTF:
|
||||
case OP_LEF:
|
||||
case OP_GTF:
|
||||
case OP_GEF:
|
||||
codeBase[pc] = vm->instructionPointers[codeBase[pc]];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
pc += 4;
|
||||
break;
|
||||
case OP_ARG:
|
||||
pc += 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==============
|
||||
VM_Call
|
||||
|
||||
|
||||
Upon a system call, the stack will look like:
|
||||
|
||||
sp+32 parm1
|
||||
sp+28 parm0
|
||||
sp+24 return stack
|
||||
sp+20 return address
|
||||
sp+16 local1
|
||||
sp+14 local0
|
||||
sp+12 arg1
|
||||
sp+8 arg0
|
||||
sp+4 return stack
|
||||
sp return address
|
||||
|
||||
An interpreted function will immediately execute
|
||||
an OP_ENTER instruction, which will subtract space for
|
||||
locals from sp
|
||||
==============
|
||||
*/
|
||||
#define MAX_STACK 256
|
||||
#define STACK_MASK (MAX_STACK-1)
|
||||
//#define DEBUG_VM
|
||||
|
||||
#define DEBUGSTR va("%s%i", VM_Indent(vm), opStack-stack )
|
||||
|
||||
int VM_CallInterpreted( vm_t *vm, int *args ) {
|
||||
int stack[MAX_STACK];
|
||||
int *opStack;
|
||||
int programCounter;
|
||||
int programStack;
|
||||
int stackOnEntry;
|
||||
byte *image;
|
||||
int *codeImage;
|
||||
int v1;
|
||||
int dataMask;
|
||||
#ifdef DEBUG_VM
|
||||
vmSymbol_t *profileSymbol;
|
||||
#endif
|
||||
|
||||
// interpret the code
|
||||
vm->currentlyInterpreting = qtrue;
|
||||
|
||||
// we might be called recursively, so this might not be the very top
|
||||
programStack = stackOnEntry = vm->programStack;
|
||||
|
||||
#ifdef DEBUG_VM
|
||||
profileSymbol = VM_ValueToFunctionSymbol( vm, 0 );
|
||||
// uncomment this for debugging breakpoints
|
||||
vm->breakFunction = 0;
|
||||
#endif
|
||||
// set up the stack frame
|
||||
|
||||
image = vm->dataBase;
|
||||
codeImage = (int *)vm->codeBase;
|
||||
dataMask = vm->dataMask;
|
||||
|
||||
// leave a free spot at start of stack so
|
||||
// that as long as opStack is valid, opStack-1 will
|
||||
// not corrupt anything
|
||||
opStack = stack;
|
||||
programCounter = 0;
|
||||
|
||||
programStack -= 48;
|
||||
|
||||
*(int *)&image[ programStack + 44] = args[9];
|
||||
*(int *)&image[ programStack + 40] = args[8];
|
||||
*(int *)&image[ programStack + 36] = args[7];
|
||||
*(int *)&image[ programStack + 32] = args[6];
|
||||
*(int *)&image[ programStack + 28] = args[5];
|
||||
*(int *)&image[ programStack + 24] = args[4];
|
||||
*(int *)&image[ programStack + 20] = args[3];
|
||||
*(int *)&image[ programStack + 16] = args[2];
|
||||
*(int *)&image[ programStack + 12] = args[1];
|
||||
*(int *)&image[ programStack + 8 ] = args[0];
|
||||
*(int *)&image[ programStack + 4 ] = 0; // return stack
|
||||
*(int *)&image[ programStack ] = -1; // will terminate the loop on return
|
||||
|
||||
vm->callLevel = 0;
|
||||
|
||||
VM_Debug(0);
|
||||
|
||||
// vm_debugLevel=2;
|
||||
// main interpreter loop, will exit when a LEAVE instruction
|
||||
// grabs the -1 program counter
|
||||
|
||||
#define r2 codeImage[programCounter]
|
||||
|
||||
while ( 1 ) {
|
||||
int opcode, r0, r1;
|
||||
// unsigned int r2;
|
||||
|
||||
nextInstruction:
|
||||
r0 = ((int *)opStack)[0];
|
||||
r1 = ((int *)opStack)[-1];
|
||||
nextInstruction2:
|
||||
opcode = codeImage[ programCounter++ ];
|
||||
#ifdef DEBUG_VM
|
||||
if ( (unsigned)programCounter > vm->codeLength ) {
|
||||
Com_Error( ERR_DROP, "VM pc out of range" );
|
||||
}
|
||||
|
||||
if ( opStack < stack ) {
|
||||
Com_Error( ERR_DROP, "VM opStack underflow" );
|
||||
}
|
||||
if ( opStack >= stack+MAX_STACK ) {
|
||||
Com_Error( ERR_DROP, "VM opStack overflow" );
|
||||
}
|
||||
|
||||
if ( programStack <= vm->stackBottom ) {
|
||||
Com_Error( ERR_DROP, "VM stack overflow" );
|
||||
}
|
||||
|
||||
if ( programStack & 3 ) {
|
||||
Com_Error( ERR_DROP, "VM program stack misaligned" );
|
||||
}
|
||||
|
||||
if ( vm_debugLevel > 1 ) {
|
||||
Com_Printf( "%s %s\n", DEBUGSTR, opnames[opcode] );
|
||||
}
|
||||
profileSymbol->profileCount++;
|
||||
#endif
|
||||
|
||||
switch ( opcode ) {
|
||||
#ifdef DEBUG_VM
|
||||
default:
|
||||
Com_Error( ERR_DROP, "Bad VM instruction" ); // this should be scanned on load!
|
||||
#endif
|
||||
case OP_BREAK:
|
||||
vm->breakCount++;
|
||||
goto nextInstruction2;
|
||||
case OP_CONST:
|
||||
opStack++;
|
||||
r1 = r0;
|
||||
r0 = *opStack = r2;
|
||||
|
||||
programCounter += 4;
|
||||
goto nextInstruction2;
|
||||
case OP_LOCAL:
|
||||
opStack++;
|
||||
r1 = r0;
|
||||
r0 = *opStack = r2+programStack;
|
||||
|
||||
programCounter += 4;
|
||||
goto nextInstruction2;
|
||||
|
||||
case OP_LOAD4:
|
||||
#ifdef DEBUG_VM
|
||||
if ( *opStack & 3 ) {
|
||||
Com_Error( ERR_DROP, "OP_LOAD4 misaligned" );
|
||||
}
|
||||
#endif
|
||||
r0 = *opStack = *(int *)&image[ r0&dataMask ];
|
||||
goto nextInstruction2;
|
||||
case OP_LOAD2:
|
||||
r0 = *opStack = *(unsigned short *)&image[ r0&dataMask ];
|
||||
goto nextInstruction2;
|
||||
case OP_LOAD1:
|
||||
r0 = *opStack = image[ r0&dataMask ];
|
||||
goto nextInstruction2;
|
||||
|
||||
case OP_STORE4:
|
||||
*(int *)&image[ r1&(dataMask & ~3) ] = r0;
|
||||
opStack -= 2;
|
||||
goto nextInstruction;
|
||||
case OP_STORE2:
|
||||
*(short *)&image[ r1&(dataMask & ~1) ] = r0;
|
||||
opStack -= 2;
|
||||
goto nextInstruction;
|
||||
case OP_STORE1:
|
||||
image[ r1&dataMask ] = r0;
|
||||
opStack -= 2;
|
||||
goto nextInstruction;
|
||||
|
||||
case OP_ARG:
|
||||
// single byte offset from programStack
|
||||
*(int *)&image[ codeImage[programCounter] + programStack ] = r0;
|
||||
opStack--;
|
||||
programCounter += 1;
|
||||
goto nextInstruction;
|
||||
|
||||
case OP_BLOCK_COPY:
|
||||
{
|
||||
int *src, *dest;
|
||||
int i, count, srci, desti;
|
||||
|
||||
count = r2;
|
||||
// MrE: copy range check
|
||||
srci = r0 & dataMask;
|
||||
desti = r1 & dataMask;
|
||||
count = ((srci + count) & dataMask) - srci;
|
||||
count = ((desti + count) & dataMask) - desti;
|
||||
|
||||
src = (int *)&image[ r0&dataMask ];
|
||||
dest = (int *)&image[ r1&dataMask ];
|
||||
if ( ( (int)src | (int)dest | count ) & 3 ) {
|
||||
Com_Error( ERR_DROP, "OP_BLOCK_COPY not dword aligned" );
|
||||
}
|
||||
count >>= 2;
|
||||
for ( i = count-1 ; i>= 0 ; i-- ) {
|
||||
dest[i] = src[i];
|
||||
}
|
||||
programCounter += 4;
|
||||
opStack -= 2;
|
||||
}
|
||||
goto nextInstruction;
|
||||
|
||||
case OP_CALL:
|
||||
// save current program counter
|
||||
*(int *)&image[ programStack ] = programCounter;
|
||||
|
||||
// jump to the location on the stack
|
||||
programCounter = r0;
|
||||
opStack--;
|
||||
if ( programCounter < 0 ) {
|
||||
// system call
|
||||
int r;
|
||||
int temp;
|
||||
#ifdef DEBUG_VM
|
||||
int stomped;
|
||||
|
||||
if ( vm_debugLevel ) {
|
||||
Com_Printf( "%s---> systemcall(%i)\n", DEBUGSTR, -1 - programCounter );
|
||||
}
|
||||
#endif
|
||||
// save the stack to allow recursive VM entry
|
||||
temp = vm->callLevel;
|
||||
vm->programStack = programStack - 4;
|
||||
#ifdef DEBUG_VM
|
||||
stomped = *(int *)&image[ programStack + 4 ];
|
||||
#endif
|
||||
*(int *)&image[ programStack + 4 ] = -1 - programCounter;
|
||||
|
||||
//VM_LogSyscalls( (int *)&image[ programStack + 4 ] );
|
||||
r = vm->systemCall( (int *)&image[ programStack + 4 ] );
|
||||
|
||||
#ifdef DEBUG_VM
|
||||
// this is just our stack frame pointer, only needed
|
||||
// for debugging
|
||||
*(int *)&image[ programStack + 4 ] = stomped;
|
||||
#endif
|
||||
|
||||
// save return value
|
||||
opStack++;
|
||||
*opStack = r;
|
||||
programCounter = *(int *)&image[ programStack ];
|
||||
vm->callLevel = temp;
|
||||
#ifdef DEBUG_VM
|
||||
if ( vm_debugLevel ) {
|
||||
Com_Printf( "%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter ) );
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
programCounter = vm->instructionPointers[ programCounter ];
|
||||
}
|
||||
goto nextInstruction;
|
||||
|
||||
// push and pop are only needed for discarded or bad function return values
|
||||
case OP_PUSH:
|
||||
opStack++;
|
||||
goto nextInstruction;
|
||||
case OP_POP:
|
||||
opStack--;
|
||||
goto nextInstruction;
|
||||
|
||||
case OP_ENTER:
|
||||
#ifdef DEBUG_VM
|
||||
profileSymbol = VM_ValueToFunctionSymbol( vm, programCounter );
|
||||
#endif
|
||||
// get size of stack frame
|
||||
v1 = r2;
|
||||
|
||||
programCounter += 4;
|
||||
programStack -= v1;
|
||||
#ifdef DEBUG_VM
|
||||
// save old stack frame for debugging traces
|
||||
*(int *)&image[programStack+4] = programStack + v1;
|
||||
if ( vm_debugLevel ) {
|
||||
Com_Printf( "%s---> %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter - 5 ) );
|
||||
if ( vm->breakFunction && programCounter - 5 == vm->breakFunction ) {
|
||||
// this is to allow setting breakpoints here in the debugger
|
||||
vm->breakCount++;
|
||||
// vm_debugLevel = 2;
|
||||
// VM_StackTrace( vm, programCounter, programStack );
|
||||
}
|
||||
vm->callLevel++;
|
||||
}
|
||||
#endif
|
||||
goto nextInstruction;
|
||||
case OP_LEAVE:
|
||||
// remove our stack frame
|
||||
v1 = r2;
|
||||
|
||||
programStack += v1;
|
||||
|
||||
// grab the saved program counter
|
||||
programCounter = *(int *)&image[ programStack ];
|
||||
#ifdef DEBUG_VM
|
||||
profileSymbol = VM_ValueToFunctionSymbol( vm, programCounter );
|
||||
if ( vm_debugLevel ) {
|
||||
vm->callLevel--;
|
||||
Com_Printf( "%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter ) );
|
||||
}
|
||||
#endif
|
||||
// check for leaving the VM
|
||||
if ( programCounter == -1 ) {
|
||||
goto done;
|
||||
}
|
||||
goto nextInstruction;
|
||||
|
||||
/*
|
||||
===================================================================
|
||||
BRANCHES
|
||||
===================================================================
|
||||
*/
|
||||
|
||||
case OP_JUMP:
|
||||
programCounter = r0;
|
||||
programCounter = vm->instructionPointers[ programCounter ];
|
||||
opStack--;
|
||||
goto nextInstruction;
|
||||
|
||||
case OP_EQ:
|
||||
opStack -= 2;
|
||||
if ( r1 == r0 ) {
|
||||
programCounter = r2; //vm->instructionPointers[r2];
|
||||
goto nextInstruction;
|
||||
} else {
|
||||
programCounter += 4;
|
||||
goto nextInstruction;
|
||||
}
|
||||
|
||||
case OP_NE:
|
||||
opStack -= 2;
|
||||
if ( r1 != r0 ) {
|
||||
programCounter = r2; //vm->instructionPointers[r2];
|
||||
goto nextInstruction;
|
||||
} else {
|
||||
programCounter += 4;
|
||||
goto nextInstruction;
|
||||
}
|
||||
|
||||
case OP_LTI:
|
||||
opStack -= 2;
|
||||
if ( r1 < r0 ) {
|
||||
programCounter = r2; //vm->instructionPointers[r2];
|
||||
goto nextInstruction;
|
||||
} else {
|
||||
programCounter += 4;
|
||||
goto nextInstruction;
|
||||
}
|
||||
|
||||
case OP_LEI:
|
||||
opStack -= 2;
|
||||
if ( r1 <= r0 ) {
|
||||
programCounter = r2; //vm->instructionPointers[r2];
|
||||
goto nextInstruction;
|
||||
} else {
|
||||
programCounter += 4;
|
||||
goto nextInstruction;
|
||||
}
|
||||
|
||||
case OP_GTI:
|
||||
opStack -= 2;
|
||||
if ( r1 > r0 ) {
|
||||
programCounter = r2; //vm->instructionPointers[r2];
|
||||
goto nextInstruction;
|
||||
} else {
|
||||
programCounter += 4;
|
||||
goto nextInstruction;
|
||||
}
|
||||
|
||||
case OP_GEI:
|
||||
opStack -= 2;
|
||||
if ( r1 >= r0 ) {
|
||||
programCounter = r2; //vm->instructionPointers[r2];
|
||||
goto nextInstruction;
|
||||
} else {
|
||||
programCounter += 4;
|
||||
goto nextInstruction;
|
||||
}
|
||||
|
||||
case OP_LTU:
|
||||
opStack -= 2;
|
||||
if ( ((unsigned)r1) < ((unsigned)r0) ) {
|
||||
programCounter = r2; //vm->instructionPointers[r2];
|
||||
goto nextInstruction;
|
||||
} else {
|
||||
programCounter += 4;
|
||||
goto nextInstruction;
|
||||
}
|
||||
|
||||
case OP_LEU:
|
||||
opStack -= 2;
|
||||
if ( ((unsigned)r1) <= ((unsigned)r0) ) {
|
||||
programCounter = r2; //vm->instructionPointers[r2];
|
||||
goto nextInstruction;
|
||||
} else {
|
||||
programCounter += 4;
|
||||
goto nextInstruction;
|
||||
}
|
||||
|
||||
case OP_GTU:
|
||||
opStack -= 2;
|
||||
if ( ((unsigned)r1) > ((unsigned)r0) ) {
|
||||
programCounter = r2; //vm->instructionPointers[r2];
|
||||
goto nextInstruction;
|
||||
} else {
|
||||
programCounter += 4;
|
||||
goto nextInstruction;
|
||||
}
|
||||
|
||||
case OP_GEU:
|
||||
opStack -= 2;
|
||||
if ( ((unsigned)r1) >= ((unsigned)r0) ) {
|
||||
programCounter = r2; //vm->instructionPointers[r2];
|
||||
goto nextInstruction;
|
||||
} else {
|
||||
programCounter += 4;
|
||||
goto nextInstruction;
|
||||
}
|
||||
|
||||
case OP_EQF:
|
||||
if ( ((float *)opStack)[-1] == *(float *)opStack ) {
|
||||
programCounter = r2; //vm->instructionPointers[r2];
|
||||
opStack -= 2;
|
||||
goto nextInstruction;
|
||||
} else {
|
||||
programCounter += 4;
|
||||
opStack -= 2;
|
||||
goto nextInstruction;
|
||||
}
|
||||
|
||||
case OP_NEF:
|
||||
if ( ((float *)opStack)[-1] != *(float *)opStack ) {
|
||||
programCounter = r2; //vm->instructionPointers[r2];
|
||||
opStack -= 2;
|
||||
goto nextInstruction;
|
||||
} else {
|
||||
programCounter += 4;
|
||||
opStack -= 2;
|
||||
goto nextInstruction;
|
||||
}
|
||||
|
||||
case OP_LTF:
|
||||
if ( ((float *)opStack)[-1] < *(float *)opStack ) {
|
||||
programCounter = r2; //vm->instructionPointers[r2];
|
||||
opStack -= 2;
|
||||
goto nextInstruction;
|
||||
} else {
|
||||
programCounter += 4;
|
||||
opStack -= 2;
|
||||
goto nextInstruction;
|
||||
}
|
||||
|
||||
case OP_LEF:
|
||||
if ( ((float *)opStack)[-1] <= *(float *)opStack ) {
|
||||
programCounter = r2; //vm->instructionPointers[r2];
|
||||
opStack -= 2;
|
||||
goto nextInstruction;
|
||||
} else {
|
||||
programCounter += 4;
|
||||
opStack -= 2;
|
||||
goto nextInstruction;
|
||||
}
|
||||
|
||||
case OP_GTF:
|
||||
if ( ((float *)opStack)[-1] > *(float *)opStack ) {
|
||||
programCounter = r2; //vm->instructionPointers[r2];
|
||||
opStack -= 2;
|
||||
goto nextInstruction;
|
||||
} else {
|
||||
programCounter += 4;
|
||||
opStack -= 2;
|
||||
goto nextInstruction;
|
||||
}
|
||||
|
||||
case OP_GEF:
|
||||
if ( ((float *)opStack)[-1] >= *(float *)opStack ) {
|
||||
programCounter = r2; //vm->instructionPointers[r2];
|
||||
opStack -= 2;
|
||||
goto nextInstruction;
|
||||
} else {
|
||||
programCounter += 4;
|
||||
opStack -= 2;
|
||||
goto nextInstruction;
|
||||
}
|
||||
|
||||
|
||||
//===================================================================
|
||||
|
||||
case OP_NEGI:
|
||||
*opStack = -r0;
|
||||
goto nextInstruction;
|
||||
case OP_ADD:
|
||||
opStack[-1] = r1 + r0;
|
||||
opStack--;
|
||||
goto nextInstruction;
|
||||
case OP_SUB:
|
||||
opStack[-1] = r1 - r0;
|
||||
opStack--;
|
||||
goto nextInstruction;
|
||||
case OP_DIVI:
|
||||
opStack[-1] = r1 / r0;
|
||||
opStack--;
|
||||
goto nextInstruction;
|
||||
case OP_DIVU:
|
||||
opStack[-1] = ((unsigned)r1) / ((unsigned)r0);
|
||||
opStack--;
|
||||
goto nextInstruction;
|
||||
case OP_MODI:
|
||||
opStack[-1] = r1 % r0;
|
||||
opStack--;
|
||||
goto nextInstruction;
|
||||
case OP_MODU:
|
||||
opStack[-1] = ((unsigned)r1) % (unsigned)r0;
|
||||
opStack--;
|
||||
goto nextInstruction;
|
||||
case OP_MULI:
|
||||
opStack[-1] = r1 * r0;
|
||||
opStack--;
|
||||
goto nextInstruction;
|
||||
case OP_MULU:
|
||||
opStack[-1] = ((unsigned)r1) * ((unsigned)r0);
|
||||
opStack--;
|
||||
goto nextInstruction;
|
||||
|
||||
case OP_BAND:
|
||||
opStack[-1] = ((unsigned)r1) & ((unsigned)r0);
|
||||
opStack--;
|
||||
goto nextInstruction;
|
||||
case OP_BOR:
|
||||
opStack[-1] = ((unsigned)r1) | ((unsigned)r0);
|
||||
opStack--;
|
||||
goto nextInstruction;
|
||||
case OP_BXOR:
|
||||
opStack[-1] = ((unsigned)r1) ^ ((unsigned)r0);
|
||||
opStack--;
|
||||
goto nextInstruction;
|
||||
case OP_BCOM:
|
||||
opStack[-1] = ~ ((unsigned)r0);
|
||||
goto nextInstruction;
|
||||
|
||||
case OP_LSH:
|
||||
opStack[-1] = r1 << r0;
|
||||
opStack--;
|
||||
goto nextInstruction;
|
||||
case OP_RSHI:
|
||||
opStack[-1] = r1 >> r0;
|
||||
opStack--;
|
||||
goto nextInstruction;
|
||||
case OP_RSHU:
|
||||
opStack[-1] = ((unsigned)r1) >> r0;
|
||||
opStack--;
|
||||
goto nextInstruction;
|
||||
|
||||
case OP_NEGF:
|
||||
*(float *)opStack = -*(float *)opStack;
|
||||
goto nextInstruction;
|
||||
case OP_ADDF:
|
||||
*(float *)(opStack-1) = *(float *)(opStack-1) + *(float *)opStack;
|
||||
opStack--;
|
||||
goto nextInstruction;
|
||||
case OP_SUBF:
|
||||
*(float *)(opStack-1) = *(float *)(opStack-1) - *(float *)opStack;
|
||||
opStack--;
|
||||
goto nextInstruction;
|
||||
case OP_DIVF:
|
||||
*(float *)(opStack-1) = *(float *)(opStack-1) / *(float *)opStack;
|
||||
opStack--;
|
||||
goto nextInstruction;
|
||||
case OP_MULF:
|
||||
*(float *)(opStack-1) = *(float *)(opStack-1) * *(float *)opStack;
|
||||
opStack--;
|
||||
goto nextInstruction;
|
||||
|
||||
case OP_CVIF:
|
||||
*(float *)opStack = (float)*opStack;
|
||||
goto nextInstruction;
|
||||
case OP_CVFI:
|
||||
*opStack = (int) *(float *)opStack;
|
||||
goto nextInstruction;
|
||||
case OP_SEX8:
|
||||
*opStack = (signed char)*opStack;
|
||||
goto nextInstruction;
|
||||
case OP_SEX16:
|
||||
*opStack = (short)*opStack;
|
||||
goto nextInstruction;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
vm->currentlyInterpreting = qfalse;
|
||||
|
||||
if ( opStack != &stack[1] ) {
|
||||
Com_Error( ERR_DROP, "Interpreter error: opStack = %i", opStack - stack );
|
||||
}
|
||||
|
||||
vm->programStack = stackOnEntry;
|
||||
|
||||
// return the result
|
||||
return *opStack;
|
||||
}
|
180
code/qcommon/vm_local.h
Normal file
180
code/qcommon/vm_local.h
Normal file
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code 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 Foobar; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
#include "../game/q_shared.h"
|
||||
#include "qcommon.h"
|
||||
|
||||
typedef enum {
|
||||
OP_UNDEF,
|
||||
|
||||
OP_IGNORE,
|
||||
|
||||
OP_BREAK,
|
||||
|
||||
OP_ENTER,
|
||||
OP_LEAVE,
|
||||
OP_CALL,
|
||||
OP_PUSH,
|
||||
OP_POP,
|
||||
|
||||
OP_CONST,
|
||||
OP_LOCAL,
|
||||
|
||||
OP_JUMP,
|
||||
|
||||
//-------------------
|
||||
|
||||
OP_EQ,
|
||||
OP_NE,
|
||||
|
||||
OP_LTI,
|
||||
OP_LEI,
|
||||
OP_GTI,
|
||||
OP_GEI,
|
||||
|
||||
OP_LTU,
|
||||
OP_LEU,
|
||||
OP_GTU,
|
||||
OP_GEU,
|
||||
|
||||
OP_EQF,
|
||||
OP_NEF,
|
||||
|
||||
OP_LTF,
|
||||
OP_LEF,
|
||||
OP_GTF,
|
||||
OP_GEF,
|
||||
|
||||
//-------------------
|
||||
|
||||
OP_LOAD1,
|
||||
OP_LOAD2,
|
||||
OP_LOAD4,
|
||||
OP_STORE1,
|
||||
OP_STORE2,
|
||||
OP_STORE4, // *(stack[top-1]) = stack[top]
|
||||
OP_ARG,
|
||||
|
||||
OP_BLOCK_COPY,
|
||||
|
||||
//-------------------
|
||||
|
||||
OP_SEX8,
|
||||
OP_SEX16,
|
||||
|
||||
OP_NEGI,
|
||||
OP_ADD,
|
||||
OP_SUB,
|
||||
OP_DIVI,
|
||||
OP_DIVU,
|
||||
OP_MODI,
|
||||
OP_MODU,
|
||||
OP_MULI,
|
||||
OP_MULU,
|
||||
|
||||
OP_BAND,
|
||||
OP_BOR,
|
||||
OP_BXOR,
|
||||
OP_BCOM,
|
||||
|
||||
OP_LSH,
|
||||
OP_RSHI,
|
||||
OP_RSHU,
|
||||
|
||||
OP_NEGF,
|
||||
OP_ADDF,
|
||||
OP_SUBF,
|
||||
OP_DIVF,
|
||||
OP_MULF,
|
||||
|
||||
OP_CVIF,
|
||||
OP_CVFI
|
||||
} opcode_t;
|
||||
|
||||
|
||||
|
||||
typedef int vmptr_t;
|
||||
|
||||
typedef struct vmSymbol_s {
|
||||
struct vmSymbol_s *next;
|
||||
int symValue;
|
||||
int profileCount;
|
||||
char symName[1]; // variable sized
|
||||
} vmSymbol_t;
|
||||
|
||||
#define VM_OFFSET_PROGRAM_STACK 0
|
||||
#define VM_OFFSET_SYSTEM_CALL 4
|
||||
|
||||
struct vm_s {
|
||||
// DO NOT MOVE OR CHANGE THESE WITHOUT CHANGING THE VM_OFFSET_* DEFINES
|
||||
// USED BY THE ASM CODE
|
||||
int programStack; // the vm may be recursively entered
|
||||
int (*systemCall)( int *parms );
|
||||
|
||||
//------------------------------------
|
||||
|
||||
char name[MAX_QPATH];
|
||||
|
||||
// for dynamic linked modules
|
||||
void *dllHandle;
|
||||
int (QDECL *entryPoint)( int callNum, ... );
|
||||
|
||||
// for interpreted modules
|
||||
qboolean currentlyInterpreting;
|
||||
|
||||
qboolean compiled;
|
||||
byte *codeBase;
|
||||
int codeLength;
|
||||
|
||||
int *instructionPointers;
|
||||
int instructionPointersLength;
|
||||
|
||||
byte *dataBase;
|
||||
int dataMask;
|
||||
|
||||
int stackBottom; // if programStack < stackBottom, error
|
||||
|
||||
int numSymbols;
|
||||
struct vmSymbol_s *symbols;
|
||||
|
||||
int callLevel; // for debug indenting
|
||||
int breakFunction; // increment breakCount on function entry to this
|
||||
int breakCount;
|
||||
|
||||
// fqpath member added 7/20/02 by T.Ray
|
||||
char fqpath[MAX_QPATH+1] ;
|
||||
};
|
||||
|
||||
|
||||
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 );
|
||||
|
1479
code/qcommon/vm_ppc.c
Normal file
1479
code/qcommon/vm_ppc.c
Normal file
File diff suppressed because it is too large
Load diff
2119
code/qcommon/vm_ppc_new.c
Normal file
2119
code/qcommon/vm_ppc_new.c
Normal file
File diff suppressed because it is too large
Load diff
1196
code/qcommon/vm_x86.c
Normal file
1196
code/qcommon/vm_x86.c
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue