/ Games / Quake 3 / Demo Format

Quake 3 Demo Format

This document describes the demo format that later releases of quake 3 use. It is not my work, but that of Andrey Nazarov. I am mirroring it here as it is hard to find and makes it easier for me to reference and append to if required. Here is the original Quake 3 Demo Specification and a mirror of the Quake 3 Demo Specification.

Quake III Arena Demo File Specifications

File formats: *.dm_66, *.dm_67, *.dm_68

 

Document Version 4

 

 

1. About

2. Sizebuf_t structure

3. Demofile format

4. MSG_ReadBits and MSG_WriteBits

5. Common MSG_* functions

6. Delta encoding of game structures

7. Huffman encoding

8. Server to Client commands

9. Operations with gameState_t

10. Parsing whole demo file

11. Thanks to

 

 

1. About

 

This document contains complete Quake III Arena Demo File specifications, useful for programmers working in C or C++ developing demo players/editors. It introduces demo message encoding/decoding routines that allow parsing and writing Quake III Arena Demo Files.

 

Note this code doesn’t provide full Quake III Arena network protocol support, as it uses much more encryption. All network-only stuff was removed from this document.

 

This code is redistributed under the terms of GNU General Public License:

 

/*

Quake III *.dm_6? Demo Specifications

 

Copyright (C) 2003 Andrey '[SkulleR]' Nazarov

Based on Argus and Quake II source code

Also contains some stuff from Q3A SDK

 

Argus is Copyright (C) 2000 Martin Otten

Quake II and Quake III are Copyright (C) 1997-2001 ID Software, Inc

 

This program is free software; you can redistribute it and/or

modify it under the terms of the GNU General Public License

as published by the Free Software Foundation; either version 2

of the License, or (at your option) any later version.

 

This program is distributed in the hope that it will be useful,

but WITHOUT ANY WARRANTY; without even the implied warranty of

MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 

See the GNU General Public License for more details.

 

You should have received a copy of the GNU General Public License

along with this program; if not, write to the Free Software

Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

*/

 

You can also download this example application (source code + binary):

http://skuller-vidnoe.narod.ru/downloads/dm_68.zip

It contains all source code present in this document and all necessary headers. The only thing this app does is dumping obituaries and some other info from a *.dm_68 file.

 

2. Sizebuf_t structure

 

All MSG_* functions operate with the sizebuf_t structure, which holds all necessary information about current buffer state and is used both when reading and writing bits to the buffer. Normally nothing outside MSG_* functions should modify this structure.

 

typedef struct sizebuf_s {

qboolean allowoverflow; // if false, do a Com_Error

qboolean overflowed; // set to true if the buffer size failed

qboolean uncompressed; // don't do Huffman encoding, write raw bytes

byte *data; // pointer to message buffer, set by MSG_Init

int maxsize; // size in bytes of message buffer, set by MSG_Init

int cursize; // number of bytes written to the buffer, set by MSG_WriteBits

int readcount; // number of bytes read from the buffer, set by MSG_ReadBits

int bit; // number of bits written to or read from the buffer

} sizebuf_t;

 

 

 

3. Demofile format

 

Actually Quake III Demo File is a version of server-to-client communication protocol, written to a disk in the following format:

<demo block> <...> <demo block> <end block>

 

Demo block structure:

Size, bytes

Description

4

Message sequence, <seq>

4

Message length, <msglen>

<msglen>

Message data

 

Demo is parsed until <seq> and <msglen> are not equal to -1 (end block), which indicates demofile EOF. <msglen> should be always less than MAX_MSGLEN.

 

#define MAX_MSGLEN 0x4000 // max length of demo message

 

Sample routine for demo file parsing:

 

FILE *demofile;

int demoMessageSequence; // used for delta decoding

 

/*

=====================

Parse_NextDemoMessage

 

Read next message from demo file and parse it

Return qfalse if demo EOF reached or error occured

=====================

*/

qboolean Parse_NextDemoMessage( void ) {

sizebuf_t msg;

byte buffer[MAX_MSGLEN];

int len;

int seq;

 

if( fread( &seq, 1, 4, demofile ) != 4 ) {

Com_Printf( "Demo file was truncated\n" );

return qfalse;

}

if( fread( &len, 1, 4, demofile ) != 4 ) {

Com_Printf( "Demo file was truncated\n" );

return qfalse;

}

 

if( seq == -1 || len == -1 ) {

return qfalse; // demo EOF reached

}

 

MSG_Init( &msg, buffer, sizeof( buffer ) );

 

demoMessageSequence = LittleLong( seq );

msg.cursize = LittleLong( len );

 

if( msg.cursize <= 0 || msg.cursize >= msg.maxsize ) {

Com_Error( ERR_DROP, "Illegal demo message length" );

}

 

if( fread( msg.data, 1, msg.cursize, demofile ) != msg.cursize ) {

Com_Printf( "Demo file was truncated\n" );

return qfalse;

}

 

Parse_DemoMessage( &msg ); // parse the message

 

return qtrue;

}

 

 

4. MSG_ReadBits and MSG_WriteBits

 

The basic functions for performing i/o operations on message data are MSG_WriteBits and MSG_ReadBits, which do Huffman encoding/decoding of the data bitstream using Huff_* code. If uncompressed flag is set on the sizebuf, then raw bytes are used, i.e. bits value is rounded to a full byte (q2-style). Currently this is not used when working with demo files.

 

/*

============

MSG_WriteBits

============

*/

void MSG_WriteBits( sizebuf_t *msg, int value, int bits ) {

int remaining;

int i;

byte *buf;

 

if( msg->maxsize - msg->cursize < 4 ) {

msg->overflowed = qtrue;

return;

}

 

if( !bits || bits < -31 || bits > 32 ) {

Com_Error( ERR_DROP, "MSG_WriteBits: bad bits %i", bits );

}

 

if( bits < 0 ) {

bits = -bits;

}

 

if( msg->uncompressed ) {

if( bits <= 8 ) {

buf = MSG_GetSpace( msg, 1 );

buf[0] = value;

} else if( bits <= 16 ) {

buf = MSG_GetSpace( msg, 2 );

buf[0] = value & 0xFF;

buf[1] = value >> 8;

} else if( bits <= 32 ) {

buf = MSG_GetSpace( msg, 4 );

buf[0] = value & 0xFF;

buf[1] = (value >> 8) & 0xFF;

buf[2] = (value >> 16) & 0xFF;

buf[3] = value >> 24;

}

return;

}

 

value &= 0xFFFFFFFFU >> (32 - bits);

remaining = bits & 7;

 

for( i=0 ; i<remaining ; i++ ) {

if( !(msg->bit & 7) ) {

msg->data[msg->bit >> 3] = 0;

}

msg->data[msg->bit >> 3] |= (value & 1) << (msg->bit & 7);

msg->bit++;

value >>= 1;

}

bits -= remaining;

 

if( bits > 0 ) {

for( i=0 ; i<(bits+7)>>3 ; i++ ) {

Huff_EmitByte( value & 255, msg->data, &msg->bit );

value >>= 8;

}

}

 

msg->cursize = (msg->bit >> 3) + 1;

}

 

 

/*

============

MSG_ReadBits

============

*/

int MSG_ReadBits( sizebuf_t *msg, int bits ) {

int i;

int val;

int bitmask = 0;

int remaining;

qboolean extend = qfalse;

 

if( !bits || bits < -31 || bits > 32 ) {

Com_Error( ERR_DROP, "MSG_ReadBits: bad bits %i", bits );

}

 

if( bits < 0 ) {

bits = -bits;

extend = qtrue;

}

 

if( msg->uncompressed ) {

if( bits <= 8 ) {

bitmask = (unsigned char)msg->data[msg->readcount];

msg->readcount++;

msg->bit += 8;

} else if( bits <= 16 ) {

bitmask = (unsigned short)(msg->data[msg->readcount]

+ (msg->data[msg->readcount+1] << 8));

msg->readcount += 2;

msg->bit += 16;

} else if( bits <= 32 ) {

bitmask = msg->data[msg->readcount]

+ (msg->data[msg->readcount+1] << 8)

+ (msg->data[msg->readcount+2] << 16)

+ (msg->data[msg->readcount+3] << 24);

msg->readcount += 4;

msg->bit += 32;

}

} else {

remaining = bits & 7;

 

for( i=0 ; i<remaining ; i++ ) {

val = msg->data[msg->bit >> 3] >> (msg->bit & 7);

msg->bit++;

bitmask |= (val & 1) << i;

}

for( i=0 ; i<bits-remaining ; i+=8 ) {

val = Huff_GetByteEx( msg->data, &msg->bit );

bitmask |= val << (i + remaining);

}

msg->readcount = (msg->bit >> 3) + 1;

}

 

if( extend ) {

if( bitmask & (1 << (bits - 1)) ) {

bitmask |= ~((1 << bits) - 1);

}

}

 

return bitmask;

}

 

There is a wrapper interface for writing/reading bytes, shorts and ints. It is implemented as macros and inline functions.

 

#define MSG_WriteByte(msg,c) MSG_WriteBits(msg,c,8)

#define MSG_WriteShort(msg,c) MSG_WriteBits(msg,c,16)

#define MSG_WriteSignedShort(msg,c) MSG_WriteBits(msg,c,-16)

#define MSG_WriteLong(msg,c) MSG_WriteBits(msg,c,32)

 

static ID_INLINE int MSG_ReadByte( sizebuf_t *msg ) {

int c = MSG_ReadBits( msg, 8 ) & 0xFF;

 

if( msg->readcount > msg->cursize ) {

return -1;

}

 

return c;

}

 

static ID_INLINE int MSG_ReadShort( sizebuf_t *msg ) {

int c = MSG_ReadBits( msg, 16 );

 

if( msg->readcount > msg->cursize ) {

return -1;

}

 

return c;

}

 

static ID_INLINE int MSG_ReadSignedShort( sizebuf_t *msg ) {

int c = MSG_ReadBits( msg, -16 );

 

if( msg->readcount > msg->cursize ) {

return -1;

}

 

return c;

}

 

static ID_INLINE int MSG_ReadLong( sizebuf_t *msg ) {

int c = MSG_ReadBits( msg, 32 );

 

if( msg->readcount > msg->cursize ) {

return -1;

}

 

return c;

}

 

5. Common MSG_* functions

 

// initialize sizebuf_t and associate it with specified buffer

void MSG_Init( sizebuf_t *msg, byte *buffer, int size );

 

// initialize sizebuf_t, associate it with specified buffer and set uncompressed flag

void MSG_InitRaw( sizebuf_t *msg, byte *buffer, int size );

 

// clear sizebuf_t, prepare it for writing and unset uncompressed flag

void MSG_Clear( sizebuf_t *msg );

 

// unset uncompressed flag for sizebuf_t

void MSG_SetBitstream( sizebuf_t *msg );

 

// strcat raw data to the sizebuf_t

void MSG_WriteRawData( sizebuf_t *msg, const void *data, int length );

 

// prepare sizebuf_t for writing and set uncompressed flag

void MSG_BeginWriting( sizebuf_t *msg );

 

// write data as bytes

void MSG_WriteData( sizebuf_t *msg, const void *data, int length );

 

// write string as bytes up to MAX_STRING_CHARS

void MSG_WriteString( sizebuf_t *msg, const char *string );

 

// write string as bytes up to BIG_INFO_STRING

void MSG_WriteBigString( sizebuf_t *msg, const char *string );

 

// prepare sizebuf_t for reading and set uncompressed flag

void MSG_BeginReading( sizebuf_t *msg );

 

// read data as bytes

void MSG_ReadData( sizebuf_t *msg, void *data, int len );

 

// read string as bytes up to MAX_STRING_CHARS

char *MSG_ReadString( sizebuf_t *msg );

 

// read string as bytes up to BIG_INFO_STRING

char *MSG_ReadBigString( sizebuf_t *msg );

 

/*

============

MSG_GetSpace

============

*/

static void *MSG_GetSpace( sizebuf_t *msg, int length ) {

void *data;

if( msg->cursize + length > msg->maxsize ) {

if( !msg->allowoverflow ) {

Com_Error( ERR_FATAL, "MSG_GetSpace: overflow without allowoverflow set" );

}

if( length > msg->maxsize ) {

Com_Error( ERR_FATAL, "MSG_GetSpace: %i is > full buffer size", length );

}

MSG_Clear( msg );

msg->overflowed = qtrue;

}

 

data = msg->data + msg->cursize;

msg->cursize += length;

msg->bit += length << 3;

 

return data;

}

 

/*

============

MSG_Init

============

*/

void MSG_Init( sizebuf_t *msg, byte *buffer, int size ) {

memset( msg, 0, sizeof( *msg ) );

msg->data = buffer;

msg->maxsize = size;

}

 

/*

============

MSG_InitRaw

============

*/

void MSG_InitRaw( sizebuf_t *msg, byte *buffer, int size ) {

memset( msg, 0, sizeof( *msg ) );

msg->data = buffer;

msg->maxsize = size;

msg->uncompressed = qtrue;

}

 

/*

============

MSG_Clear

============

*/

void MSG_Clear( sizebuf_t *msg ) {

msg->cursize = 0;

msg->overflowed = qfalse;

msg->uncompressed = qfalse;

msg->bit = 0;

}

 

/*

============

MSG_SetBitstream

============

*/

void MSG_SetBitstream( sizebuf_t *msg ) {

msg->uncompressed = qfalse;

}

 

/*

============

MSG_WriteRawData

============

*/

void MSG_WriteRawData( sizebuf_t *msg, const void *data, int length ) {

if( length > 0 ) {

memcpy( MSG_GetSpace( msg, length ), data, length );

}

}

 

/*

============

MSG_BeginWriting

============

*/

void MSG_BeginWriting( sizebuf_t *msg ) {

msg->uncompressed = qtrue;

msg->overflowed = 0;

msg->cursize = 0;

msg->bit = 0;

}

 

/*

============

MSG_WriteData

============

*/

void MSG_WriteData( sizebuf_t *msg, const void *data, int length ) {

int i;

 

for( i=0 ; i<length ; i++ ) {

MSG_WriteByte( msg, ((byte *)data)[i] );

}

}

 

/*

============

MSG_WriteString

============

*/

void MSG_WriteString( sizebuf_t *msg, const char *string ) {

char buffer[MAX_STRING_CHARS];

int i;

int len;

 

if( !string ) {

MSG_WriteByte( msg, 0 );

return;

}

 

len = strlen( string );

 

if( len >= sizeof( buffer ) ) {

Com_Printf( "MSG_WriteString: MAX_STRING_CHARS\n" );

MSG_WriteByte( msg, 0 );

return;

}

 

Q_strncpyz( buffer, string, sizeof( buffer ) );

 

for( i=0 ; i<len ; i++ ) {

if( buffer[i] > 127 ) {

buffer[i] = '.';

}

}

 

for( i=0 ; i<=len ; i++ ) {

MSG_WriteByte( msg, buffer[i] );

}

}

 

/*

============

MSG_WriteString

============

*/

void MSG_WriteBigString( sizebuf_t *msg, const char *string ) {

char buffer[BIG_INFO_STRING];

int i;

int len;

 

if( !string ) {

MSG_WriteByte( msg, 0 );

return;

}

 

len = strlen( string );

 

if( len >= sizeof( buffer ) ) {

Com_Printf( "MSG_WriteString: BIG_INFO_STRING\n" );

MSG_WriteByte( msg, 0 );

return;

}

 

Q_strncpyz( buffer, string, sizeof( buffer ) );

 

for( i=0 ; i<len ; i++ ) {

if( buffer[i] > 127 ) {

buffer[i] = '.';

}

}

 

for( i=0 ; i<=len ; i++ ) {

MSG_WriteByte( msg, buffer[i] );

}

}

 

/*

============

MSG_BeginReading

============

*/

void MSG_BeginReading( sizebuf_t *msg ) {

msg->readcount = 0;

msg->bit = 0;

msg->uncompressed = qtrue;

}

 

/*

============

MSG_ReadData

============

*/

void MSG_ReadData( sizebuf_t *msg, void *data, int len ) {

int i;

int c;

 

for( i=0 ; i<len ; i++ ) {

c = MSG_ReadByte( msg );

if( c == -1 ) {

break;

}

if( data ) {

((byte *)data)[i] = c;

}

}

}

 

/*

============

MSG_ReadString

============

*/

char *MSG_ReadString( sizebuf_t *msg ) {

static char string[MAX_STRING_CHARS];

int i;

int c;

 

for( i=0 ; i<sizeof( string )-1; i++ ) {

c = MSG_ReadByte( msg );

if( c == -1 || c == 0 ) {

break;

}

if( c == '%' || c > 127 ) {

c = '.';

}

string[i] = c;

}

 

string[i] = 0;

 

return string;

}

 

/*

============

MSG_ReadBigString

============

*/

char *MSG_ReadBigString( sizebuf_t *msg ) {

static char string[BIG_INFO_STRING];

int i;

int c;

 

for( i=0 ; i<sizeof( string )-1; i++ ) {

c = MSG_ReadByte( msg );

if( c == -1 || c == 0 ) {

break;

}

if( c == '%' || c > 127 ) {

c = '.';

}

string[i] = c;

}

 

string[i] = 0;

 

return string;

}

 

6. Delta encoding of game structures

 

For network bandwidth saving the following entityState_t and playerState_t structures are delta compressed when transmitted over the network:

 

// playerState_t is the information needed by both the client and server

// to predict player motion and actions

// nothing outside of pmove should modify these, or some degree of prediction error

// will occur

 

// you can't add anything to this without modifying the code in msg.c

 

// playerState_t is a full superset of entityState_t as it is used by players,

// so if a playerState_t is transmitted, the entityState_t can be fully derived

// from it.

typedef struct playerState_s {

int commandTime; // cmd->serverTime of last executed command

int pm_type;

int bobCycle; // for view bobbing and footstep generation

int pm_flags; // ducked, jump_held, etc

int pm_time;

 

vec3_t origin;

vec3_t velocity;

int weaponTime;

int gravity;

int speed;

int delta_angles[3]; // add to command angles to get view direction

// changed by spawns, rotating objects, and teleporters

 

int groundEntityNum; // ENTITYNUM_NONE = in air

 

int legsTimer; // don't change low priority animations until this runs out

int legsAnim; // mask off ANIM_TOGGLEBIT

 

int torsoTimer; // don't change low priority animations until this runs out

int torsoAnim; // mask off ANIM_TOGGLEBIT

 

int movementDir; // a number 0 to 7 that represents the reletive angle

// of movement to the view angle (axial and diagonals)

// when at rest, the value will remain unchanged

// used to twist the legs during strafing

 

vec3_t grapplePoint; // location of grapple to pull towards if PMF_GRAPPLE_PULL

 

int eFlags; // copied to entityState_t->eFlags

 

int eventSequence; // pmove generated events

int events[MAX_PS_EVENTS];

int eventParms[MAX_PS_EVENTS];

 

int externalEvent; // events set on player from another source

int externalEventParm;

int externalEventTime;

 

int clientNum; // ranges from 0 to MAX_CLIENTS-1

int weapon; // copied to entityState_t->weapon

int weaponstate;

 

vec3_t viewangles; // for fixed views

int viewheight;

 

// damage feedback

int damageEvent; // when it changes, latch the other parms

int damageYaw;

int damagePitch;

int damageCount;

 

int stats[MAX_STATS];

int persistant[MAX_PERSISTANT]; // stats that aren't cleared on death

int powerups[MAX_POWERUPS]; // level.time that the powerup runs out

int ammo[MAX_WEAPONS];

 

int generic1;

int loopSound;

int jumppad_ent; // jumppad entity hit this frame

 

// not communicated over the net at all

int ping; // server to game info for scoreboard

int pmove_framecount; // FIXME: don't transmit over the network

int jumppad_frame;

int entityEventSequence;

} playerState_t;

 

// if entityState->solid == SOLID_BMODEL, modelindex is an inline model number

#define SOLID_BMODEL 0xffffff

 

typedef enum {

TR_STATIONARY,

TR_INTERPOLATE, // non-parametric, but interpolate between snapshots

TR_LINEAR,

TR_LINEAR_STOP,

TR_SINE, // value = base + sin( time / duration ) * delta

TR_GRAVITY

} trType_t;

 

typedef struct {

trType_t trType;

int trTime;

int trDuration; // if non 0, trTime + trDuration = stop time

vec3_t trBase;

vec3_t trDelta; // velocity, etc

} trajectory_t;

 

// entityState_t is the information conveyed from the server

// in an update message about entities that the client will

// need to render in some way

// Different eTypes may use the information in different ways

// The messages are delta compressed, so it doesn't really matter if

// the structure size is fairly large

 

typedef struct entityState_s {

int number; // entity index

int eType; // entityType_t

int eFlags;

 

trajectory_t pos; // for calculating position

trajectory_t apos; // for calculating angles

 

int time;

int time2;

 

vec3_t origin;

vec3_t origin2;

 

vec3_t angles;

vec3_t angles2;

 

int otherEntityNum; // shotgun sources, etc

int otherEntityNum2;

 

int groundEntityNum; // -1 = in air

 

int constantLight; // r + (g<<8) + (b<<16) + (intensity<<24)

int loopSound; // constantly loop this sound

 

int modelindex;

int modelindex2;

int clientNum; // 0 to (MAX_CLIENTS - 1), for players and corpses

int frame;

 

int solid; // for client side prediction, trap_linkentity sets this properly

 

int event; // impulse events -- muzzle flashes, footsteps, etc

int eventParm;

 

// for players

int powerups; // bit flags

int weapon; // determines weapon and flash model, etc

int legsAnim; // mask off ANIM_TOGGLEBIT

int torsoAnim; // mask off ANIM_TOGGLEBIT

 

int generic1;

} entityState_t;

 

The following tables define offsets of each field in these structures and size of it (in bits):

 

/*

======================================================================================

 

OFFSET TABLES FOR MAIN GAME STRUCTURES

 

If you want something from playerState_t or entityState structures to be

transmitted on the network, just insert a field into one of the following tables.

 

For network bandwidth saving, all fields are sorted in order from highest

modification frequency (during active gameplay) to lowest.

 

======================================================================================

*/

 

typedef struct {

int offset;

int bits; // bits > 0 --> unsigned integer

// bits = 0 --> float value

// bits < 0 --> signed integer

} field_t;

 

// field declarations

#define PS_FIELD(n,b) { ((int)&(((playerState_t *)0)->n)), b }

#define ES_FIELD(n,b) { ((int)&(((entityState_t *)0)->n)), b }

 

 

// field data accessing

#define FIELD_INTEGER(s) (*(int *)((byte *)(s)+field->offset))

#define FIELD_FLOAT(s) (*(float *)((byte *)(s)+field->offset))

 

//

// playerState_t

//

static field_t psTable[] = {

PS_FIELD( commandTime, 32 ),

PS_FIELD( origin[0], 0 ),

PS_FIELD( origin[1], 0 ),

PS_FIELD( bobCycle, 8 ),

PS_FIELD( velocity[0], 0 ),

PS_FIELD( velocity[1], 0 ),

PS_FIELD( viewangles[1], 0 ),

PS_FIELD( viewangles[0], 0 ),

PS_FIELD( weaponTime, -16 ),

PS_FIELD( origin[2], 0 ),

PS_FIELD( velocity[2], 0 ),

PS_FIELD( legsTimer, 8 ),

PS_FIELD( pm_time, -16 ),

PS_FIELD( eventSequence, 16 ),

PS_FIELD( torsoAnim, 8 ),

PS_FIELD( movementDir, 4 ),

PS_FIELD( events[0], 8 ),

PS_FIELD( legsAnim, 8 ),

PS_FIELD( events[1], 8 ),

PS_FIELD( pm_flags, 16 ),

PS_FIELD( groundEntityNum, 10 ),

PS_FIELD( weaponstate, 4 ),

PS_FIELD( eFlags, 16 ),

PS_FIELD( externalEvent, 10 ),

PS_FIELD( gravity, 16 ),

PS_FIELD( speed, 16 ),

PS_FIELD( delta_angles[1], 16 ),

PS_FIELD( externalEventParm, 8 ),

PS_FIELD( viewheight, -8 ),

PS_FIELD( damageEvent, 8 ),

PS_FIELD( damageYaw, 8 ),

PS_FIELD( damagePitch, 8 ),

PS_FIELD( damageCount, 8 ),

PS_FIELD( generic1, 8 ),

PS_FIELD( pm_type, 8 ),

PS_FIELD( delta_angles[0], 16 ),

PS_FIELD( delta_angles[2], 16 ),

PS_FIELD( torsoTimer, 12 ),

PS_FIELD( eventParms[0], 8 ),

PS_FIELD( eventParms[1], 8 ),

PS_FIELD( clientNum, 8 ),

PS_FIELD( weapon, 5 ),

PS_FIELD( viewangles[2], 0 ),

PS_FIELD( grapplePoint[0], 0 ),

PS_FIELD( grapplePoint[1], 0 ),

PS_FIELD( grapplePoint[2], 0 ),

PS_FIELD( jumppad_ent, 10 ),

PS_FIELD( loopSound, 16 )

};

 

//

// entityState_t

//

static field_t esTable[] = {

ES_FIELD( pos.trTime, 32 ),

ES_FIELD( pos.trBase[0], 0 ),

ES_FIELD( pos.trBase[1], 0 ),

ES_FIELD( pos.trDelta[0], 0 ),

ES_FIELD( pos.trDelta[1], 0 ),

ES_FIELD( pos.trBase[2], 0 ),

ES_FIELD( apos.trBase[1], 0 ),

ES_FIELD( pos.trDelta[2], 0 ),

ES_FIELD( apos.trBase[0], 0 ),

ES_FIELD( event, 10 ),

ES_FIELD( angles2[1], 0 ),

ES_FIELD( eType, 8 ),

ES_FIELD( torsoAnim, 8 ),

ES_FIELD( eventParm, 8 ),

ES_FIELD( legsAnim, 8 ),

ES_FIELD( groundEntityNum, 10 ),

ES_FIELD( pos.trType, 8 ),

ES_FIELD( eFlags, 19 ),

ES_FIELD( otherEntityNum, 10 ),

ES_FIELD( weapon, 8 ),

ES_FIELD( clientNum, 8 ),

ES_FIELD( angles[1], 0 ),

ES_FIELD( pos.trDuration, 32 ),

ES_FIELD( apos.trType, 8 ),

ES_FIELD( origin[0], 0 ),

ES_FIELD( origin[1], 0 ),

ES_FIELD( origin[2], 0 ),

ES_FIELD( solid, 24 ),

ES_FIELD( powerups, 16 ),

ES_FIELD( modelindex, 8 ),

ES_FIELD( otherEntityNum2, 10 ),

ES_FIELD( loopSound, 8 ),

ES_FIELD( generic1, 8 ),

ES_FIELD( origin2[2], 0 ),

ES_FIELD( origin2[0], 0 ),

ES_FIELD( origin2[1], 0 ),

ES_FIELD( modelindex2, 8 ),

ES_FIELD( angles[0], 0 ),

ES_FIELD( time, 32 ),

ES_FIELD( apos.trTime, 32 ),

ES_FIELD( apos.trDuration, 32 ),

ES_FIELD( apos.trBase[2], 0 ),

ES_FIELD( apos.trDelta[0], 0 ),

ES_FIELD( apos.trDelta[1], 0 ),

ES_FIELD( apos.trDelta[2], 0 ),

ES_FIELD( time2, 32 ),

ES_FIELD( angles[2], 0 ),

ES_FIELD( angles2[0], 0 ),

ES_FIELD( angles2[2], 0 ),

ES_FIELD( constantLight, 32 ),

ES_FIELD( frame, 16 )

};

 

static const int psTableSize = sizeof( psTable ) / sizeof( psTable[0] );

static const int esTableSize = sizeof( esTable ) / sizeof( esTable[0] );

 

static const entityState_t nullEntityState;

static const playerState_t nullPlayerState;

 

Float values (or ‘vectors’) can be written as 32 bits or 13 bits. ‘Snapped’ vectors, i.e. floats without fractional part, are written as unsigned 13-bit integers for network bandwidth saving, if their values don’t exceed MAX_SNAPPED.

 

// snapped vectors are packed in 13 bits instead of 32

#define SNAPPED_BITS 13

#define MAX_SNAPPED (1<<SNAPPED_BITS)

 

Delta compressed structures are read/write using the following functions:

 

/*

============

MSG_WriteDeltaEntity

 

If 'force' parm is false, this won't result any bits

emitted if entity didn't changed at all

 

'from' == NULL --> nodelta update

'to' == NULL --> entity removed

============

*/

void MSG_WriteDeltaEntity( sizebuf_t *msg, const entityState_t *from, const entityState_t *to, qboolean force ) {

field_t *field;

int to_value;

int to_integer;

float to_float;

int maxFieldNum;

int i;

 

if( !to ) {

if( from ) {

MSG_WriteBits( msg, from->number, GENTITYNUM_BITS );

MSG_WriteBits( msg, 1, 1 );

}

return; // removed

}

 

if( to->number < 0 || to->number > MAX_GENTITIES ) {

Com_Error( ERR_DROP, "MSG_WriteDeltaEntity: Bad entity number: %i", to->number );

}

 

if( !from ) {

from = &nullEntityState; // nodelta update

}

 

//

// find last modified field in table

//

maxFieldNum = 0;

for( i=0, field=esTable ; i<esTableSize ; i++, field++ ) {

if( FIELD_INTEGER( from ) != FIELD_INTEGER( to ) ) {

maxFieldNum = i + 1;

}

}

 

if( !maxFieldNum ) {

if( !force ) {

return; // don't emit any bits at all

}

 

MSG_WriteBits( msg, to->number, GENTITYNUM_BITS );

MSG_WriteBits( msg, 0, 1 );

MSG_WriteBits( msg, 0, 1 );

return; // unchanged

}

 

MSG_WriteBits( msg, to->number, GENTITYNUM_BITS );

MSG_WriteBits( msg, 0, 1 );

MSG_WriteBits( msg, 1, 1 );

MSG_WriteByte( msg, maxFieldNum );

 

//

// write all modified fields

//

for( i=0, field=esTable ; i<maxFieldNum ; i++, field++ ) {

to_value = FIELD_INTEGER( to );

if( FIELD_INTEGER( from ) == to_value ) {

MSG_WriteBits( msg, 0, 1 );

continue; // field unchanged

}

MSG_WriteBits( msg, 1, 1 );

 

if( !to_value ) {

MSG_WriteBits( msg, 0, 1 );

continue; // field set to zero

}

MSG_WriteBits( msg, 1, 1 );

 

if( field->bits ) {

MSG_WriteBits( msg, to_value, field->bits );

continue; // integer value

}

 

//

// figure out how to pack float value

//

to_float = FIELD_FLOAT( to );

to_integer = (int)to_float;

 

if( (float)to_integer == to_float

&& to_integer + MAX_SNAPPED/2 >= 0

&& to_integer + MAX_SNAPPED/2 < MAX_SNAPPED )

{

MSG_WriteBits( msg, 0, 1 ); // pack in 13 bits

MSG_WriteBits( msg, to_integer + MAX_SNAPPED/2, SNAPPED_BITS );

} else {

MSG_WriteBits( msg, 1, 1 ); // pack in 32 bits

MSG_WriteLong( msg, to_value );

}

}

 

}

 

/*

============

MSG_ReadDeltaEntity

 

'from' == NULL --> nodelta update

'to' == NULL --> do nothing

============

*/

void MSG_ReadDeltaEntity( sizebuf_t *msg, const entityState_t *from, entityState_t *to, int number ) {

field_t *field;

int maxFieldNum;

int i;

 

if( number < 0 || number >= MAX_GENTITIES ) {

Com_Error( ERR_DROP, "MSG_ReadDeltaEntity: Bad delta entity number: %i\n", number );

}

 

if( !to ) {

return;

}

 

if( MSG_ReadBits( msg, 1 ) ) {

memset( to, 0, sizeof( *to ) );

to->number = ENTITYNUM_NONE;

return; // removed

}

 

if( !from ) {

memset( to, 0, sizeof( *to ) ); // nodelta update

} else {

memcpy( to, from, sizeof( *to ) );

}

to->number = number;

 

if( !MSG_ReadBits( msg, 1 ) ) {

return; // unchanged

}

 

maxFieldNum = MSG_ReadByte( msg );

if( maxFieldNum > esTableSize ) {

Com_Error( ERR_DROP, "MSG_ReadDeltaEntity: maxFieldNum > esTableSize" );

}

 

//

// read all modified fields

//

for( i=0, field=esTable ; i<maxFieldNum ; i++, field++ ) {

if( !MSG_ReadBits( msg, 1 ) ) {

continue; // field unchanged

}

if( !MSG_ReadBits( msg, 1 ) ) {

FIELD_INTEGER( to ) = 0;

continue; // field set to zero

}

 

if( field->bits ) {

FIELD_INTEGER( to ) = MSG_ReadBits( msg, field->bits );

continue; // integer value

}

 

//

// read packed float value

//

if( !MSG_ReadBits( msg, 1 ) ) {

FIELD_FLOAT( to ) = (float)(MSG_ReadBits( msg, SNAPPED_BITS ) - MAX_SNAPPED/2);

} else {

FIELD_INTEGER( to ) = MSG_ReadLong( msg );

}

}

}

 

/*

============

MSG_WriteDeltaPlayerstate

 

'from' == NULL --> nodelta update

'to' == NULL --> do nothing

============

*/

void MSG_WriteDeltaPlayerstate( sizebuf_t *msg, const playerState_t *from, const playerState_t *to ) {

field_t *field;

int to_value;

float to_float;

int to_integer;

int maxFieldNum;

int statsMask;

int persistantMask;

int ammoMask;

int powerupsMask;

int i;

 

if( !to ) {

return;

}

 

if( !from ) {

from = &nullPlayerState; // nodelta update

}

 

//

// find last modified field in table

//

maxFieldNum = 0;

for( i=0, field=psTable ; i<psTableSize ; i++, field++ ) {

if( FIELD_INTEGER( from ) != FIELD_INTEGER( to ) ) {

maxFieldNum = i + 1;

}

}

 

MSG_WriteByte( msg, maxFieldNum );

 

//

// write all modified fields

//

for( i=0, field=psTable ; i<maxFieldNum ; i++, field++ ) {

to_value = FIELD_INTEGER( to );

if( FIELD_INTEGER( from ) == to_value ) {

MSG_WriteBits( msg, 0, 1 );

continue; // field unchanged

}

MSG_WriteBits( msg, 1, 1 );

 

if( field->bits ) {

MSG_WriteBits( msg, to_value, field->bits );

continue; // integer value

}

 

//

// figure out how to pack float value

//

to_float = FIELD_FLOAT( to );

to_integer = (int)to_float;

 

if( (float)to_integer == to_float

&& to_integer + MAX_SNAPPED/2 >= 0

&& to_integer + MAX_SNAPPED/2 < MAX_SNAPPED )

{

MSG_WriteBits( msg, 0, 1 ); // pack in 13 bits

MSG_WriteBits( msg, to_integer + MAX_SNAPPED/2, SNAPPED_BITS );

} else {

MSG_WriteBits( msg, 1, 1 ); // pack in 32 bits

MSG_WriteLong( msg, to_value );

}

}

 

//

// find modified arrays

//

statsMask = 0;

for( i=0 ; i<MAX_STATS ; i++ ) {

if( from->stats[i] != to->stats[i] ) {

statsMask |= (1 << i);

}

}

 

persistantMask = 0;

for( i=0 ; i<MAX_PERSISTANT ; i++ ) {

if( from->persistant[i] != to->persistant[i] ) {

persistantMask |= (1 << i);

}

}

 

ammoMask = 0;

for( i=0 ; i<MAX_WEAPONS ; i++ ) {

if( from->ammo[i] != to->ammo[i] ) {

ammoMask |= (1 << i);

}

}

 

powerupsMask = 0;

for( i=0 ; i<MAX_POWERUPS ; i++ ) {

if( from->powerups[i] != to->powerups[i] ) {

powerupsMask |= (1 << i);

}

}

 

if( !statsMask && !persistantMask && !ammoMask && !powerupsMask ) {

MSG_WriteBits( msg, 0, 1 );

return; // no arrays modified

}

 

//

// write all modified arrays

//

MSG_WriteBits( msg, 1, 1 );

 

// PS_STATS

if( statsMask ) {

MSG_WriteBits( msg, 1, 1 );

MSG_WriteShort( msg, statsMask );

for( i=0 ; i<MAX_STATS ; i++ ) {

if( statsMask & (1 << i) ) {

MSG_WriteSignedShort( msg, to->stats[i] );

}

}

} else {

MSG_WriteBits( msg, 0, 1 ); // unchanged

}

 

// PS_PERSISTANT

if( persistantMask ) {

MSG_WriteBits( msg, 1, 1 );

MSG_WriteShort( msg, persistantMask );

for( i=0 ; i<MAX_PERSISTANT ; i++ ) {

if( persistantMask & (1 << i) ) {

MSG_WriteSignedShort( msg, to->persistant[i] );

}

}

} else {

MSG_WriteBits( msg, 0, 1 ); // unchanged

}

 

 

// PS_AMMO

if( ammoMask ) {

MSG_WriteBits( msg, 1, 1 );

MSG_WriteShort( msg, ammoMask );

for( i=0 ; i<MAX_WEAPONS ; i++ ) {

if( ammoMask & (1 << i) ) {

MSG_WriteShort( msg, to->ammo[i] );

}

}

} else {

MSG_WriteBits( msg, 0, 1 ); // unchanged

}

 

// PS_POWERUPS

if( powerupsMask ) {

MSG_WriteBits( msg, 1, 1 );

MSG_WriteShort( msg, powerupsMask );

for( i=0 ; i<MAX_POWERUPS ; i++ ) {

if( powerupsMask & (1 << i) ) {

MSG_WriteLong( msg, to->powerups[i] ); // WARNING: powerups use 32 bits, not 16

}

}

} else {

MSG_WriteBits( msg, 0, 1 ); // unchanged

}

 

}

 

 

/*

============

MSG_ReadDeltaPlayerstate

 

'from' == NULL --> nodelta update

'to' == NULL --> do nothing

============

*/

void MSG_ReadDeltaPlayerstate( sizebuf_t *msg, const playerState_t *from, playerState_t *to ) {

field_t *field;

int maxFieldNum;

int bitmask;

int i;

 

if( !to ) {

return;

}

 

if( !from ) {

memset( to, 0, sizeof( *to ) ); // nodelta update

} else {

memcpy( to, from, sizeof( *to ) );

}

 

maxFieldNum = MSG_ReadByte( msg );

if( maxFieldNum > psTableSize ) {

Com_Error( ERR_DROP, "MSG_ReadDeltaPlayerstate: maxFieldNum > psTableSize" );

}

 

//

// read all modified fields

//

for( i=0, field=psTable ; i<maxFieldNum ; i++, field++ ) {

if( !MSG_ReadBits( msg, 1 ) ) {

continue; // field unchanged

}

 

if( field->bits ) {

FIELD_INTEGER( to ) = MSG_ReadBits( msg, field->bits );

continue; // integer value

}

 

//

// read packed float value

//

if( !MSG_ReadBits( msg, 1 ) ) {

FIELD_FLOAT( to ) = (float)(MSG_ReadBits( msg, SNAPPED_BITS ) - MAX_SNAPPED/2);

} else {

FIELD_INTEGER( to ) = MSG_ReadLong( msg );

}

}

 

//

// read all modified arrays

//

if( !MSG_ReadBits( msg, 1 ) ) {

return; // no arrays modified

}

 

// PS_STATS

if( MSG_ReadBits( msg, 1 ) ) {

bitmask = MSG_ReadShort( msg );

for( i=0 ; i<MAX_STATS ; i++ ) {

if( bitmask & (1 << i) ) {

to->stats[i] = MSG_ReadSignedShort( msg ); // PS_STATS can be negative

}

}

}

 

// PS_PERSISTANT

if( MSG_ReadBits( msg, 1 ) ) {

bitmask = MSG_ReadShort( msg );

for( i=0 ; i<MAX_PERSISTANT ; i++ ) {

if( bitmask & (1 << i) ) {

to->persistant[i] = MSG_ReadSignedShort( msg ); // PS_PERSISTANT can be negative

}

}

}

 

// PS_AMMO

if( MSG_ReadBits( msg, 1 ) ) {

bitmask = MSG_ReadShort( msg );

for( i=0 ; i<MAX_WEAPONS ; i++ ) {

if( bitmask & (1 << i) ) {

to->ammo[i] = MSG_ReadShort( msg );

}

}

}

 

// PS_POWERUPS

if( MSG_ReadBits( msg, 1 ) ) {

bitmask = MSG_ReadShort( msg );

for( i=0 ; i<MAX_POWERUPS ; i++ ) {

if( bitmask & (1 << i) ) {

to->powerups[i] = MSG_ReadLong( msg ); // WARNING: powerups use 32 bits, not 16

}

}

}

}

 

7. Huffman encoding

 

The following routines do Huffman encoding/decoding. Note that you have to do a Huff_Init call before using any MSG_* functions.

 

//

// huff.c - Huffman compression routines for data bitstream

//

 

#define VALUE(a) ((int )(a))

#define NODE(a) ((void *)(a))

 

#define NODE_START NODE( 1)

#define NODE_NONE NODE(256)

#define NODE_NEXT NODE(257)

 

#define HUFF_TREE_SIZE 7175

typedef void *tree_t[HUFF_TREE_SIZE];

 

//

// pre-defined frequency counts for all bytes [0..255]

//

static int huffCounts[256] = {

0x3D1CB, 0x0A0E9, 0x01894, 0x01BC2, 0x00E92, 0x00EA6, 0x017DE, 0x05AF3,

0x08225, 0x01B26, 0x01E9E, 0x025F2, 0x02429, 0x0436B, 0x00F6D, 0x006F2,

0x02060, 0x00644, 0x00636, 0x0067F, 0x0044C, 0x004BD, 0x004D6, 0x0046E,

0x006D5, 0x00423, 0x004DE, 0x0047D, 0x004F9, 0x01186, 0x00AF5, 0x00D90,

0x0553B, 0x00487, 0x00686, 0x0042A, 0x00413, 0x003F4, 0x0041D, 0x0042E,

0x006BE, 0x00378, 0x0049C, 0x00352, 0x003C0, 0x0030C, 0x006D8, 0x00CE0,

0x02986, 0x011A2, 0x016F9, 0x00A7D, 0x0122A, 0x00EFD, 0x0082D, 0x0074B,

0x00A18, 0x0079D, 0x007B4, 0x003AC, 0x0046E, 0x006FC, 0x00686, 0x004B6,

0x01657, 0x017F0, 0x01C36, 0x019FE, 0x00E7E, 0x00ED3, 0x005D4, 0x005F4,

0x008A7, 0x00474, 0x0054B, 0x003CB, 0x00884, 0x004E0, 0x00530, 0x004AB,

0x006EA, 0x00436, 0x004F0, 0x004F2, 0x00490, 0x003C5, 0x00483, 0x004A2,

0x00543, 0x004CC, 0x005F9, 0x00640, 0x00A39, 0x00800, 0x009F2, 0x00CCB,

0x0096A, 0x00E01, 0x009C8, 0x00AF0, 0x00A73, 0x01802, 0x00E4F, 0x00B18,

0x037AD, 0x00C5C, 0x008AD, 0x00697, 0x00C88, 0x00AB3, 0x00DB8, 0x012BC,

0x00FFB, 0x00DBB, 0x014A8, 0x00FB0, 0x01F01, 0x0178F, 0x014F0, 0x00F54,

0x0131C, 0x00E9F, 0x011D6, 0x012C7, 0x016DC, 0x01900, 0x01851, 0x02063,

0x05ACB, 0x01E9E, 0x01BA1, 0x022E7, 0x0153D, 0x01183, 0x00E39, 0x01488,

0x014C0, 0x014D0, 0x014FA, 0x00DA4, 0x0099A, 0x0069E, 0x0071D, 0x00849,

0x0077C, 0x0047D, 0x005EC, 0x00557, 0x004D4, 0x00405, 0x004EA, 0x00450,

0x004DD, 0x003EE, 0x0047D, 0x00401, 0x004D9, 0x003B8, 0x00507, 0x003E5,

0x006B1, 0x003F1, 0x004A3, 0x0036F, 0x0044B, 0x003A1, 0x00436, 0x003B7,

0x00678, 0x003A2, 0x00481, 0x00406, 0x004EE, 0x00426, 0x004BE, 0x00424,

0x00655, 0x003A2, 0x00452, 0x00390, 0x0040A, 0x0037C, 0x00486, 0x003DE,

0x00497, 0x00352, 0x00461, 0x00387, 0x0043F, 0x00398, 0x00478, 0x00420,

0x00D86, 0x008C0, 0x0112D, 0x02F68, 0x01E4E, 0x00541, 0x0051B, 0x00CCE,

0x0079E, 0x00376, 0x003FF, 0x00458, 0x00435, 0x00412, 0x00425, 0x0042F,

0x005CC, 0x003E9, 0x00448, 0x00393, 0x0041C, 0x003E3, 0x0042E, 0x0036C,

0x00457, 0x00353, 0x00423, 0x00325, 0x00458, 0x0039B, 0x0044F, 0x00331,

0x0076B, 0x00750, 0x003D0, 0x00349, 0x00467, 0x003BC, 0x00487, 0x003B6,

0x01E6F, 0x003BA, 0x00509, 0x003A5, 0x00467, 0x00C87, 0x003FC, 0x0039F,

0x0054B, 0x00300, 0x00410, 0x002E9, 0x003B8, 0x00325, 0x00431, 0x002E4,

0x003F5, 0x00325, 0x003F0, 0x0031C, 0x003E4, 0x00421, 0x02CC1, 0x034C0

};

 

 

//

// static Huffman tree

//

static tree_t huffTree;

 

//

// received from MSG_* code

//

static int huffBitPos;

 

 

/*

======================================================================================

 

HUFFMAN TREE CONSTRUCTION

 

======================================================================================

*/

 

/*

============

Huff_PrepareTree

============

*/

static ID_INLINE void Huff_PrepareTree( tree_t tree ) {

void **node;

memset( tree, 0, sizeof( tree_t ) );

// create first node

node = &tree[263];

VALUE( tree[0] )++;

 

node[7] = NODE_NONE;

tree[2] = node;

tree[3] = node;

tree[4] = node;

tree[261] = node;

}

 

/*

============

Huff_GetNode

============

*/

static void **Huff_GetNode( void **tree ) {

void **node;

int value;

 

node = tree[262];

if( !node ) {

value = VALUE( tree[1] )++;

node = &tree[value + 6407];

return node;

}

 

tree[262] = node[0];

return node;

}

 

/*

============

Huff_Swap

============

*/

static void Huff_Swap( void **tree1, void **tree2, void **tree3 ) {

void **a, **b;

 

a = tree2[2];

if( a ) {

if( a[0] == tree2 ) {

a[0] = tree3;

} else {

a[1] = tree3;

}

} else {

tree1[2] = tree3;

}

 

b = tree3[2];

 

if( b ) {

if( b[0] == tree3 ) {

b[0] = tree2;

tree2[2] = b;

tree3[2] = a;

return;

}

 

b[1] = tree2;

tree2[2] = b;

tree3[2] = a;

return;

}

 

tree1[2] = tree2;

tree2[2] = NULL;

tree3[2] = a;

}

 

/*

============

Huff_SwapTrees

============

*/

static void Huff_SwapTrees( void **tree1, void **tree2 ) {

void **temp;

 

temp = tree1[3];

tree1[3] = tree2[3];

tree2[3] = temp;

 

temp = tree1[4];

tree1[4] = tree2[4];

tree2[4] = temp;

 

if( tree1[3] == tree1 ) {

tree1[3] = tree2;

}

 

if( tree2[3] == tree2 ) {

tree2[3] = tree1;

}

 

temp = tree1[3];

if( temp ) {

temp[4] = tree1;

}

 

temp = tree2[3];

if( temp ) {

temp[4] = tree2;

}

 

temp = tree1[4];

if( temp ) {

temp[3] = tree1;

}

 

temp = tree2[4];

if( temp ) {

temp[3] = tree2;

}

 

}

 

/*

============

Huff_DeleteNode

============

*/

static void Huff_DeleteNode( void **tree1, void **tree2 ) {

tree2[0] = tree1[262];

tree1[262] = tree2;

}

 

/*

============

Huff_IncrementFreq_r

============

*/

static void Huff_IncrementFreq_r( void **tree1, void **tree2 ) {

void **a, **b;

 

if( !tree2 ) {

return;

}

 

a = tree2[3];

if( a && a[6] == tree2[6] ) {

b = tree2[5];

if( b[0] != tree2[2] ) {

Huff_Swap( tree1, b[0], tree2 );

}

Huff_SwapTrees( b[0], tree2 );

}

 

a = tree2[4];

if( a && a[6] == tree2[6] ) {

b = tree2[5];

b[0] = a;

} else {

a = tree2[5];

a[0] = 0;

Huff_DeleteNode( tree1, tree2[5] );

}

 

VALUE( tree2[6] )++;

a = tree2[3];

if( a && a[6] == tree2[6] ) {

tree2[5] = a[5];

} else {

a = Huff_GetNode( tree1 );

tree2[5] = a;

a[0] = tree2;

}

 

if( tree2[2] ) {

Huff_IncrementFreq_r( tree1, tree2[2] );

if( tree2[4] == tree2[2] ) {

Huff_SwapTrees( tree2, tree2[2] );

a = tree2[5];

 

if( a[0] == tree2 ) {

a[0] = tree2[2];

}

}

}

}

 

/*

============

Huff_AddReference

 

Insert 'ch' into the tree or increment it's frequency

============

*/

static void Huff_AddReference( void **tree, int ch ) {

void **a, **b, **c, **d;

int value;

 

ch &= 255;

if( tree[ch + 5] ) {

Huff_IncrementFreq_r( tree, tree[ch + 5] );

return; // already added

}

 

value = VALUE( tree[0] )++;

b = &tree[value * 8 + 263];

 

value = VALUE( tree[0] )++;

a = &tree[value * 8 + 263];

 

a[7] = NODE_NEXT;

a[6] = NODE_START;

d = tree[3];

a[3] = d[3];

if( a[3] ) {

d = a[3];

d[4] = a;

d = a[3];

if( d[6] == NODE_START ) {

a[5] = d[5];

} else {

d = Huff_GetNode( tree );

a[5] = d;

d[0] = a;

}

} else {

d = Huff_GetNode( tree );

a[5] = d;

d[0] = a;

 

}

d = tree[3];

d[3] = a;

a[4] = tree[3];

b[7] = NODE( ch );

b[6] = NODE_START;

d = tree[3];

b[3] = d[3];

if( b[3] ) {

d = b[3];

d[4] = b;

if( d[6] == NODE_START ) {

b[5] = d[5];

} else {

d = Huff_GetNode( tree );

b[5] = d;

d[0] = a;

}

} else {

d = Huff_GetNode( tree );

b[5] = d;

d[0] = b;

}

 

d = tree[3];

d[3] = b;

b[4] = tree[3];

b[1] = NULL;

b[0] = NULL;

d = tree[3];

c = d[2];

if( c ) {

if( c[0] == tree[3] ) {

c[0] = a;

} else {

c[1] = a;

}

} else {

tree[2] = a;

}

 

a[1] = b;

d = tree[3];

a[0] = d;

a[2] = d[2];

b[2] = a;

d = tree[3];

d[2] = a;

tree[ch + 5] = b;

 

Huff_IncrementFreq_r( tree, a[2] );

}

 

/*

======================================================================================

 

BITSTREAM I/O

 

======================================================================================

*/

 

/*

============

Huff_EmitBit

 

Put one bit into buffer

============

*/

static ID_INLINE void Huff_EmitBit( int bit, byte *buffer ) {

if( !(huffBitPos & 7) ) {

buffer[huffBitPos >> 3] = 0;

}

 

buffer[huffBitPos >> 3] |= bit << (huffBitPos & 7);

huffBitPos++;

}

 

/*

============

Huff_GetBit

 

Read one bit from buffer

============

*/

static ID_INLINE int Huff_GetBit( byte *buffer ) {

int bit;

 

bit = buffer[huffBitPos >> 3] >> (huffBitPos & 7);

huffBitPos++;

 

return (bit & 1);

}

 

/*

============

Huff_EmitPathToByte

============

*/

static ID_INLINE void Huff_EmitPathToByte( void **tree, void **subtree, byte *buffer ) {

if( tree[2] ) {

Huff_EmitPathToByte( tree[2], tree, buffer );

}

 

if( !subtree ) {

return;

}

 

//

// emit tree walking control bits

//

if( tree[1] == subtree ) {

Huff_EmitBit( 1, buffer );

} else {

Huff_EmitBit( 0, buffer );

}

}

 

/*

============

Huff_GetByteFromTree

 

Get one byte using dynamic or static tree

============

*/

static ID_INLINE int Huff_GetByteFromTree( void **tree, byte *buffer ) {

if( !tree ) {

return 0;

}

 

//

// walk through the tree until we get a value

//