mirror of
https://github.com/FWGS/xash3d-fwgs
synced 2024-11-26 03:39:16 +01:00
883 lines
20 KiB
C
883 lines
20 KiB
C
/*
|
|
net_buffer.c - network bitbuffer io functions
|
|
Copyright (C) 2010 Uncle Mike
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "protocol.h"
|
|
#include "net_buffer.h"
|
|
#include "xash3d_mathlib.h"
|
|
|
|
// precalculated bit masks for WriteUBitLong.
|
|
// Using these tables instead of doing the calculations
|
|
// gives a 33% speedup in WriteUBitLong.
|
|
static uint32_t BitWriteMasks[32][33];
|
|
static uint32_t ExtraMasks[32];
|
|
const char *const svc_strings[svc_lastmsg+1] =
|
|
{
|
|
"svc_bad",
|
|
"svc_nop",
|
|
"svc_disconnect",
|
|
"svc_event",
|
|
"svc_changing",
|
|
"svc_setview",
|
|
"svc_sound",
|
|
"svc_time",
|
|
"svc_print",
|
|
"svc_stufftext",
|
|
"svc_setangle",
|
|
"svc_serverdata",
|
|
"svc_lightstyle",
|
|
"svc_updateuserinfo",
|
|
"svc_deltatable",
|
|
"svc_clientdata",
|
|
"svc_resource",
|
|
"svc_pings",
|
|
"svc_particle",
|
|
"svc_restoresound",
|
|
"svc_spawnstatic",
|
|
"svc_event_reliable",
|
|
"svc_spawnbaseline",
|
|
"svc_temp_entity",
|
|
"svc_setpause",
|
|
"svc_signonnum",
|
|
"svc_centerprint",
|
|
"svc_unused27",
|
|
"svc_unused28",
|
|
"svc_unused29",
|
|
"svc_intermission",
|
|
"svc_finale",
|
|
"svc_cdtrack",
|
|
"svc_restore",
|
|
"svc_cutscene",
|
|
"svc_weaponanim",
|
|
"svc_bspdecal",
|
|
"svc_roomtype",
|
|
"svc_addangle",
|
|
"svc_usermessage",
|
|
"svc_packetentities",
|
|
"svc_deltapacketentities",
|
|
"svc_choke",
|
|
"svc_resourcelist",
|
|
"svc_deltamovevars",
|
|
"svc_resourcerequest",
|
|
"svc_customization",
|
|
"svc_crosshairangle",
|
|
"svc_soundfade",
|
|
"svc_filetxferfailed",
|
|
"svc_hltv",
|
|
"svc_director",
|
|
"svc_voiceinit",
|
|
"svc_voicedata",
|
|
"svc_unused54",
|
|
"svc_unused55",
|
|
"svc_resourcelocation",
|
|
"svc_querycvarvalue",
|
|
"svc_querycvarvalue2",
|
|
"svc_exec",
|
|
};
|
|
|
|
const char *const svc_legacy_strings[svc_lastmsg+1] =
|
|
{
|
|
[svc_legacy_changing] = "svc_legacy_changing",
|
|
[svc_legacy_ambientsound] = "svc_legacy_ambientsound",
|
|
[svc_legacy_soundindex] = "svc_legacy_soundindex",
|
|
[svc_legacy_ambientsound] = "svc_legacy_ambientsound",
|
|
[svc_legacy_modelindex] = "svc_legacy_modelindex",
|
|
[svc_legacy_eventindex] = "svc_legacy_eventindex",
|
|
[svc_legacy_chokecount] = "svc_legacy_chokecount",
|
|
};
|
|
|
|
const char *const svc_goldsrc_strings[svc_lastmsg+1] =
|
|
{
|
|
[svc_goldsrc_version] = "svc_goldsrc_version",
|
|
[svc_goldsrc_serverinfo] = "svc_goldsrc_serverinfo",
|
|
[svc_goldsrc_deltadescription] = "svc_goldsrc_deltadescription",
|
|
[svc_goldsrc_stopsound] = "svc_goldsrc_stopsound",
|
|
[svc_goldsrc_damage] = "svc_goldsrc_damage",
|
|
[svc_goldsrc_killedmonster] = "svc_goldsrc_killedmonster",
|
|
[svc_goldsrc_foundsecret] = "svc_goldsrc_foundsecret",
|
|
[svc_goldsrc_spawnstaticsound] = "svc_goldsrc_spawnstaticsound",
|
|
[svc_goldsrc_decalname] = "svc_goldsrc_decalname",
|
|
[svc_goldsrc_newusermsg] = "svc_goldsrc_newusermsg",
|
|
[svc_goldsrc_newmovevars] = "svc_goldsrc_newmovevars",
|
|
[svc_goldsrc_sendextrainfo] = "svc_goldsrc_sendextrainfo",
|
|
[svc_goldsrc_timescale] = "svc_goldsrc_timescale",
|
|
[svc_goldsrc_sendcvarvalue] = "svc_goldsrc_sendcvarvalue",
|
|
[svc_goldsrc_sendcvarvalue2] = "svc_goldsrc_sendcvarvalue2",
|
|
};
|
|
|
|
const char *const svc_quake_strings[svc_lastmsg+1] =
|
|
{
|
|
[svc_updatestat] = "svc_quake_updatestat",
|
|
[svc_version] = "svc_quake_version",
|
|
[svc_updatename] = "svc_quake_updatename",
|
|
[svc_updatefrags] = "svc_quake_updatefrags",
|
|
[svc_stopsound] = "svc_quake_stopsound",
|
|
[svc_updatecolors] = "svc_quake_updatecolors",
|
|
[svc_damage] = "svc_quake_damage",
|
|
[svc_spawnbinary] = "svc_quake_spawnbinary",
|
|
[svc_killedmonster] = "svc_quake_killedmonster",
|
|
[svc_foundsecret] = "svc_quake_foundsecret",
|
|
[svc_spawnstaticsound] = "svc_quake_spawnstaticsound",
|
|
[svc_sellscreen] = "svc_quake_sellscreen",
|
|
[svc_showlmp] = "svc_quake_showlmp",
|
|
[svc_hidelmp] = "svc_quake_hidelmp",
|
|
[svc_skybox] = "svc_quake_skybox",
|
|
[svc_skyboxsize] = "svc_quake_skyboxsize",
|
|
[svc_fog] = "svc_quake_fog",
|
|
};
|
|
|
|
void MSG_InitMasks( void )
|
|
{
|
|
uint startbit, endbit;
|
|
uint maskBit, nBitsLeft;
|
|
|
|
for( startbit = 0; startbit < 32; startbit++ )
|
|
{
|
|
for( nBitsLeft = 0; nBitsLeft < 33; nBitsLeft++ )
|
|
{
|
|
endbit = startbit + nBitsLeft;
|
|
|
|
BitWriteMasks[startbit][nBitsLeft] = (uint)BIT( startbit ) - 1;
|
|
if( endbit < 32 ) BitWriteMasks[startbit][nBitsLeft] |= ~((uint)BIT( endbit ) - 1 );
|
|
}
|
|
}
|
|
|
|
for( maskBit = 0; maskBit < 32; maskBit++ )
|
|
ExtraMasks[maskBit] = (uint)BIT( maskBit ) - 1;
|
|
}
|
|
|
|
void MSG_WriteUBitLong( sizebuf_t *sb, uint curData, int numbits )
|
|
{
|
|
Assert( numbits >= 0 && numbits <= 32 );
|
|
|
|
// bounds checking..
|
|
if(( sb->iCurBit + numbits ) > sb->nDataBits )
|
|
{
|
|
sb->bOverflow = true;
|
|
sb->iCurBit = sb->nDataBits;
|
|
}
|
|
else
|
|
{
|
|
int nBitsLeft = numbits;
|
|
int iCurBit = sb->iCurBit;
|
|
uint iDWord = iCurBit >> 5; // Mask in a dword.
|
|
uint32_t iCurBitMasked;
|
|
int nBitsWritten;
|
|
|
|
Assert(( iDWord * 4 + sizeof( int )) <= (uint)MSG_GetMaxBytes( sb ));
|
|
|
|
iCurBitMasked = iCurBit & 31;
|
|
((uint32_t *)sb->pData)[iDWord] &= BitWriteMasks[iCurBitMasked][nBitsLeft];
|
|
((uint32_t *)sb->pData)[iDWord] |= curData << iCurBitMasked;
|
|
|
|
// did it span a dword?
|
|
nBitsWritten = 32 - iCurBitMasked;
|
|
|
|
if( nBitsWritten < nBitsLeft )
|
|
{
|
|
nBitsLeft -= nBitsWritten;
|
|
iCurBit += nBitsWritten;
|
|
curData >>= nBitsWritten;
|
|
|
|
iCurBitMasked = iCurBit & 31;
|
|
((uint32_t *)sb->pData)[iDWord+1] &= BitWriteMasks[iCurBitMasked][nBitsLeft];
|
|
((uint32_t *)sb->pData)[iDWord+1] |= curData << iCurBitMasked;
|
|
}
|
|
sb->iCurBit += numbits;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=======================
|
|
MSG_WriteSBitLong
|
|
|
|
sign bit comes first
|
|
=======================
|
|
*/
|
|
void MSG_WriteSBitLong( sizebuf_t *sb, int data, int numbits )
|
|
{
|
|
// do we have a valid # of bits to encode with?
|
|
Assert( numbits >= 1 && numbits <= 32 );
|
|
|
|
if( sb->iAlternateSign )
|
|
{
|
|
MSG_WriteOneBit( sb, data < 0 ? 1 : 0 );
|
|
MSG_WriteUBitLong( sb, (uint)abs( data ), numbits - 1 );
|
|
}
|
|
else
|
|
{
|
|
if( data < 0 )
|
|
{
|
|
MSG_WriteUBitLong( sb, (uint)( 0x80000000 + data ), numbits - 1 );
|
|
MSG_WriteOneBit( sb, 1 );
|
|
}
|
|
else
|
|
{
|
|
MSG_WriteUBitLong( sb, (uint)data, numbits - 1 );
|
|
MSG_WriteOneBit( sb, 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
void MSG_WriteBitLong( sizebuf_t *sb, uint data, int numbits, qboolean bSigned )
|
|
{
|
|
if( bSigned )
|
|
MSG_WriteSBitLong( sb, (int)data, numbits );
|
|
else MSG_WriteUBitLong( sb, data, numbits );
|
|
}
|
|
|
|
qboolean MSG_WriteBits( sizebuf_t *sb, const void *pData, int nBits )
|
|
{
|
|
byte *pOut = (byte *)pData;
|
|
int nBitsLeft = nBits;
|
|
|
|
// get output dword-aligned.
|
|
while((( uint32_t )pOut & 3 ) != 0 && nBitsLeft >= 8 )
|
|
{
|
|
MSG_WriteUBitLong( sb, *pOut, 8 );
|
|
|
|
nBitsLeft -= 8;
|
|
++pOut;
|
|
}
|
|
|
|
// read dwords.
|
|
while( nBitsLeft >= 32 )
|
|
{
|
|
MSG_WriteUBitLong( sb, *(( uint32_t *)pOut ), 32 );
|
|
|
|
pOut += sizeof( uint32_t );
|
|
nBitsLeft -= 32;
|
|
}
|
|
|
|
// read the remaining bytes.
|
|
while( nBitsLeft >= 8 )
|
|
{
|
|
MSG_WriteUBitLong( sb, *pOut, 8 );
|
|
|
|
nBitsLeft -= 8;
|
|
++pOut;
|
|
}
|
|
|
|
// Read the remaining bits.
|
|
if( nBitsLeft )
|
|
{
|
|
MSG_WriteUBitLong( sb, *pOut, nBitsLeft );
|
|
}
|
|
|
|
return !sb->bOverflow;
|
|
}
|
|
|
|
void MSG_WriteBitAngle( sizebuf_t *sb, float fAngle, int numbits )
|
|
{
|
|
uint mask, shift;
|
|
int d;
|
|
|
|
// clamp the angle before receiving
|
|
fAngle = fmod( fAngle, 360.0f );
|
|
if( fAngle < 0 ) fAngle += 360.0f;
|
|
|
|
shift = ( 1 << numbits );
|
|
mask = shift - 1;
|
|
|
|
d = (int)(( fAngle * shift ) / 360.0f );
|
|
d &= mask;
|
|
|
|
MSG_WriteUBitLong( sb, (uint)d, numbits );
|
|
}
|
|
|
|
void MSG_WriteCoord( sizebuf_t *sb, float val )
|
|
{
|
|
// g-cont. we loose precision here but keep old size of coord variable!
|
|
if( FBitSet( host.features, ENGINE_WRITE_LARGE_COORD ))
|
|
MSG_WriteShort( sb, Q_rint( val ));
|
|
else MSG_WriteShort( sb, (int)( val * 8.0f ));
|
|
}
|
|
|
|
void MSG_WriteVec3Coord( sizebuf_t *sb, const float *fa )
|
|
{
|
|
MSG_WriteCoord( sb, fa[0] );
|
|
MSG_WriteCoord( sb, fa[1] );
|
|
MSG_WriteCoord( sb, fa[2] );
|
|
}
|
|
|
|
void MSG_WriteVec3Angles( sizebuf_t *sb, const float *fa )
|
|
{
|
|
MSG_WriteBitAngle( sb, fa[0], 16 );
|
|
MSG_WriteBitAngle( sb, fa[1], 16 );
|
|
MSG_WriteBitAngle( sb, fa[2], 16 );
|
|
}
|
|
|
|
void MSG_WriteCmdExt( sizebuf_t *sb, int cmd, netsrc_t type, const char *name )
|
|
{
|
|
if( unlikely( net_send_debug.value ))
|
|
{
|
|
if( name != NULL )
|
|
{
|
|
// get custom name
|
|
Con_Printf( "^1sv^7 (%d) write: %s\n", sb->iCurBit, name );
|
|
}
|
|
else if( type == NS_SERVER )
|
|
{
|
|
if( cmd >= 0 && cmd <= svc_lastmsg )
|
|
{
|
|
// get engine message name
|
|
Con_Printf( "^1sv^7 (%d) write: %s\n", sb->iCurBit, svc_strings[cmd] );
|
|
}
|
|
}
|
|
else if( type == NS_CLIENT )
|
|
{
|
|
if( cmd >= 0 && cmd <= clc_lastmsg && cmd != clc_nop )
|
|
{
|
|
Con_Printf( "^1cl^7 (%d) write: %s\n", sb->iCurBit, clc_strings[cmd] );
|
|
}
|
|
}
|
|
}
|
|
|
|
MSG_WriteUBitLong( sb, cmd, sizeof( uint8_t ) << 3 );
|
|
}
|
|
|
|
void MSG_WriteChar( sizebuf_t *sb, int val )
|
|
{
|
|
MSG_WriteSBitLong( sb, val, sizeof( int8_t ) << 3 );
|
|
}
|
|
|
|
void MSG_WriteByte( sizebuf_t *sb, int val )
|
|
{
|
|
MSG_WriteUBitLong( sb, val, sizeof( uint8_t ) << 3 );
|
|
}
|
|
|
|
void MSG_WriteShort( sizebuf_t *sb, int val )
|
|
{
|
|
MSG_WriteSBitLong( sb, val, sizeof( int16_t ) << 3 );
|
|
}
|
|
|
|
void MSG_WriteWord( sizebuf_t *sb, int val )
|
|
{
|
|
MSG_WriteUBitLong( sb, val, sizeof( uint16_t ) << 3 );
|
|
}
|
|
|
|
void MSG_WriteLong( sizebuf_t *sb, int val )
|
|
{
|
|
MSG_WriteSBitLong( sb, val, sizeof( int32_t ) << 3 );
|
|
}
|
|
|
|
void MSG_WriteDword( sizebuf_t *sb, uint val )
|
|
{
|
|
MSG_WriteUBitLong( sb, val, sizeof( uint32_t ) << 3 );
|
|
}
|
|
|
|
void MSG_WriteFloat( sizebuf_t *sb, float val )
|
|
{
|
|
MSG_WriteBits( sb, &val, sizeof( val ) << 3 );
|
|
}
|
|
|
|
qboolean MSG_WriteBytes( sizebuf_t *sb, const void *pBuf, int nBytes )
|
|
{
|
|
return MSG_WriteBits( sb, pBuf, nBytes << 3 );
|
|
}
|
|
|
|
qboolean MSG_WriteString( sizebuf_t *sb, const char *pStr )
|
|
{
|
|
int len = Q_strlen( pStr );
|
|
|
|
if( len )
|
|
MSG_WriteBytes( sb, pStr, len + 1 );
|
|
else MSG_WriteByte( sb, 0 );
|
|
|
|
return !sb->bOverflow;
|
|
}
|
|
|
|
qboolean MSG_WriteStringf( sizebuf_t *sb, const char *format, ... )
|
|
{
|
|
va_list va;
|
|
int len;
|
|
char buf[MAX_VA_STRING];
|
|
|
|
va_start( va, format );
|
|
len = Q_vsnprintf( buf, sizeof( buf ), format, va );
|
|
va_end( va );
|
|
|
|
if( len < 0 )
|
|
{
|
|
Host_Error( "%s: snprintf overflow!\n", __func__ );
|
|
return false;
|
|
}
|
|
|
|
MSG_WriteBytes( sb, buf, len + 1 );
|
|
|
|
return !sb->bOverflow;
|
|
}
|
|
|
|
int MSG_ReadOneBit( sizebuf_t *sb )
|
|
{
|
|
if( !MSG_Overflow( sb, 1 ))
|
|
{
|
|
int value = sb->pData[sb->iCurBit >> 3] & (1 << ( sb->iCurBit & 7 ));
|
|
sb->iCurBit++;
|
|
return !!value;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint MSG_ReadUBitLong( sizebuf_t *sb, int numbits )
|
|
{
|
|
int idword1;
|
|
uint dword1, ret;
|
|
|
|
if( numbits == 8 )
|
|
{
|
|
int leftBits = MSG_GetNumBitsLeft( sb );
|
|
|
|
if( leftBits >= 0 && leftBits < 8 )
|
|
return 0; // end of message
|
|
}
|
|
|
|
if(( sb->iCurBit + numbits ) > sb->nDataBits )
|
|
{
|
|
sb->bOverflow = true;
|
|
sb->iCurBit = sb->nDataBits;
|
|
return 0;
|
|
}
|
|
|
|
Assert( numbits > 0 && numbits <= 32 );
|
|
|
|
// Read the current dword.
|
|
idword1 = sb->iCurBit >> 5;
|
|
dword1 = ((uint *)sb->pData)[idword1];
|
|
dword1 >>= ( sb->iCurBit & 31 ); // get the bits we're interested in.
|
|
|
|
sb->iCurBit += numbits;
|
|
ret = dword1;
|
|
|
|
// Does it span this dword?
|
|
if(( sb->iCurBit - 1 ) >> 5 == idword1 )
|
|
{
|
|
if( numbits != 32 )
|
|
ret &= ExtraMasks[numbits];
|
|
}
|
|
else
|
|
{
|
|
int nExtraBits = sb->iCurBit & 31;
|
|
uint dword2 = ((uint *)sb->pData)[idword1+1] & ExtraMasks[nExtraBits];
|
|
|
|
// no need to mask since we hit the end of the dword.
|
|
// shift the second dword's part into the high bits.
|
|
ret |= (dword2 << ( numbits - nExtraBits ));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
qboolean MSG_ReadBits( sizebuf_t *sb, void *pOutData, int nBits )
|
|
{
|
|
byte *pOut = (byte *)pOutData;
|
|
int nBitsLeft = nBits;
|
|
|
|
// get output dword-aligned.
|
|
while((( uint32_t )pOut & 3) != 0 && nBitsLeft >= 8 )
|
|
{
|
|
*pOut = (byte)MSG_ReadUBitLong( sb, 8 );
|
|
++pOut;
|
|
nBitsLeft -= 8;
|
|
}
|
|
|
|
// read dwords.
|
|
while( nBitsLeft >= 32 )
|
|
{
|
|
*((uint32_t *)pOut) = MSG_ReadUBitLong( sb, 32 );
|
|
pOut += sizeof( uint32_t );
|
|
nBitsLeft -= 32;
|
|
}
|
|
|
|
// read the remaining bytes.
|
|
while( nBitsLeft >= 8 )
|
|
{
|
|
*pOut = MSG_ReadUBitLong( sb, 8 );
|
|
++pOut;
|
|
nBitsLeft -= 8;
|
|
}
|
|
|
|
// read the remaining bits.
|
|
if( nBitsLeft )
|
|
{
|
|
*pOut = MSG_ReadUBitLong( sb, nBitsLeft );
|
|
}
|
|
|
|
return !sb->bOverflow;
|
|
}
|
|
|
|
float MSG_ReadBitAngle( sizebuf_t *sb, int numbits )
|
|
{
|
|
float fReturn, shift;
|
|
int i;
|
|
|
|
shift = (float)( 1 << numbits );
|
|
|
|
i = MSG_ReadUBitLong( sb, numbits );
|
|
fReturn = (float)i * ( 360.0f / shift );
|
|
|
|
// clamp the finale angle
|
|
if( fReturn < -180.0f ) fReturn += 360.0f;
|
|
else if( fReturn > 180.0f ) fReturn -= 360.0f;
|
|
|
|
return fReturn;
|
|
}
|
|
|
|
// Append numbits least significant bits from data to the current bit stream
|
|
int MSG_ReadSBitLong( sizebuf_t *sb, int numbits )
|
|
{
|
|
int r, sign;
|
|
|
|
if( sb->iAlternateSign )
|
|
{
|
|
sign = MSG_ReadOneBit( sb );
|
|
r = MSG_ReadUBitLong( sb, numbits - 1 );
|
|
|
|
if( sign )
|
|
r = -r;
|
|
}
|
|
else
|
|
{
|
|
r = MSG_ReadUBitLong( sb, numbits - 1 );
|
|
sign = MSG_ReadOneBit( sb );
|
|
|
|
if( sign )
|
|
r = -( BIT( numbits - 1 ) - r );
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
uint MSG_ReadBitLong( sizebuf_t *sb, int numbits, qboolean bSigned )
|
|
{
|
|
if( bSigned )
|
|
return (uint)MSG_ReadSBitLong( sb, numbits );
|
|
return MSG_ReadUBitLong( sb, numbits );
|
|
}
|
|
|
|
int MSG_ReadCmd( sizebuf_t *sb, netsrc_t type )
|
|
{
|
|
int cmd = MSG_ReadUBitLong( sb, sizeof( uint8_t ) << 3 );
|
|
|
|
if( unlikely( net_recv_debug.value ))
|
|
{
|
|
if( type == NS_SERVER )
|
|
{
|
|
if( cmd != svc_nop )
|
|
Con_Printf( "^1cl^7 read: %s\n", CL_MsgInfo( cmd ));
|
|
}
|
|
else if( cmd >= 0 && cmd <= clc_lastmsg )
|
|
{
|
|
Con_Printf( "^1sv^7 read: %s\n", clc_strings[cmd] );
|
|
}
|
|
}
|
|
|
|
return cmd;
|
|
}
|
|
|
|
int MSG_ReadChar( sizebuf_t *sb )
|
|
{
|
|
int alt = sb->iAlternateSign, ret;
|
|
|
|
sb->iAlternateSign = 0;
|
|
ret = MSG_ReadSBitLong( sb, sizeof( int8_t ) << 3 );
|
|
sb->iAlternateSign = alt;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int MSG_ReadByte( sizebuf_t *sb )
|
|
{
|
|
return MSG_ReadUBitLong( sb, sizeof( uint8_t ) << 3 );
|
|
}
|
|
|
|
int MSG_ReadShort( sizebuf_t *sb )
|
|
{
|
|
int alt = sb->iAlternateSign, ret;
|
|
|
|
sb->iAlternateSign = 0;
|
|
ret = MSG_ReadSBitLong( sb, sizeof( int16_t ) << 3 );
|
|
sb->iAlternateSign = alt;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int MSG_ReadWord( sizebuf_t *sb )
|
|
{
|
|
return MSG_ReadUBitLong( sb, sizeof( uint16_t ) << 3 );
|
|
}
|
|
|
|
float MSG_ReadCoord( sizebuf_t *sb )
|
|
{
|
|
// g-cont. we loose precision here but keep old size of coord variable!
|
|
if( FBitSet( host.features, ENGINE_WRITE_LARGE_COORD ))
|
|
return (float)(MSG_ReadShort( sb ));
|
|
return (float)(MSG_ReadShort( sb ) * ( 1.0f / 8.0f ));
|
|
}
|
|
|
|
void MSG_ReadVec3Coord( sizebuf_t *sb, vec3_t fa )
|
|
{
|
|
fa[0] = MSG_ReadCoord( sb );
|
|
fa[1] = MSG_ReadCoord( sb );
|
|
fa[2] = MSG_ReadCoord( sb );
|
|
}
|
|
|
|
void MSG_ReadVec3Angles( sizebuf_t *sb, vec3_t fa )
|
|
{
|
|
fa[0] = MSG_ReadBitAngle( sb, 16 );
|
|
fa[1] = MSG_ReadBitAngle( sb, 16 );
|
|
fa[2] = MSG_ReadBitAngle( sb, 16 );
|
|
}
|
|
|
|
int MSG_ReadLong( sizebuf_t *sb )
|
|
{
|
|
int alt = sb->iAlternateSign, ret;
|
|
|
|
sb->iAlternateSign = 0;
|
|
ret = MSG_ReadSBitLong( sb, sizeof( int32_t ) << 3 );
|
|
sb->iAlternateSign = alt;
|
|
|
|
return ret;
|
|
}
|
|
|
|
uint MSG_ReadDword( sizebuf_t *sb )
|
|
{
|
|
return MSG_ReadUBitLong( sb, sizeof( uint32_t ) << 3 );
|
|
}
|
|
|
|
float MSG_ReadFloat( sizebuf_t *sb )
|
|
{
|
|
float ret;
|
|
|
|
MSG_ReadBits( sb, &ret, sizeof( ret ) << 3 );
|
|
|
|
return ret;
|
|
}
|
|
|
|
qboolean MSG_ReadBytes( sizebuf_t *sb, void *pOut, int nBytes )
|
|
{
|
|
return MSG_ReadBits( sb, pOut, nBytes << 3 );
|
|
}
|
|
|
|
static char *MSG_ReadStringExt( sizebuf_t *sb, qboolean bLine )
|
|
{
|
|
static char string[4096];
|
|
int l = 0, c;
|
|
|
|
do
|
|
{
|
|
// use MSG_ReadByte so -1 is out of bounds
|
|
c = MSG_ReadByte( sb );
|
|
|
|
if( c == 0 ) break;
|
|
else if( bLine && c == '\n' )
|
|
break;
|
|
|
|
// translate all fmt spec to avoid crash bugs
|
|
// NOTE: but game strings leave unchanged. see pfnWriteString for details
|
|
if( c == '%' ) c = '.';
|
|
|
|
string[l] = c;
|
|
l++;
|
|
} while( l < sizeof( string ) - 1 );
|
|
string[l] = 0; // terminator
|
|
|
|
return string;
|
|
}
|
|
|
|
char *MSG_ReadString( sizebuf_t *sb )
|
|
{
|
|
return MSG_ReadStringExt( sb, false );
|
|
}
|
|
|
|
char *MSG_ReadStringLine( sizebuf_t *sb )
|
|
{
|
|
return MSG_ReadStringExt( sb, true );
|
|
}
|
|
|
|
void MSG_ExciseBits( sizebuf_t *sb, int startbit, int bitstoremove )
|
|
{
|
|
int i, endbit = startbit + bitstoremove;
|
|
int remaining_to_end = sb->nDataBits - endbit;
|
|
sizebuf_t temp;
|
|
|
|
MSG_StartWriting( &temp, sb->pData, MSG_GetMaxBytes( sb ), startbit, -1 );
|
|
MSG_SeekToBit( sb, endbit, SEEK_SET );
|
|
|
|
for( i = 0; i < remaining_to_end; i++ )
|
|
{
|
|
MSG_WriteOneBit( &temp, MSG_ReadOneBit( sb ));
|
|
}
|
|
|
|
MSG_SeekToBit( sb, startbit, SEEK_SET );
|
|
sb->nDataBits -= bitstoremove;
|
|
}
|
|
|
|
#ifdef XASH_ENGINE_TESTS
|
|
#include "tests.h"
|
|
|
|
static const void *g_testbuf = "asdf\xba\xa1\xba\xa1\xed\xc8\x15\x7a";
|
|
static const size_t g_testbuf_bits = (( 4 + 4 + 2 + 1 ) << 3 ) + 4;
|
|
|
|
static void Test_Buffer_BitByte( void )
|
|
{
|
|
TASSERT_EQi( BitByte( 0 ), 0 );
|
|
TASSERT_EQi( BitByte( 1 ), 1 );
|
|
TASSERT_EQi( BitByte( 8 ), 1 );
|
|
TASSERT_EQi( BitByte( 9 ), 2 );
|
|
}
|
|
|
|
static void Test_Buffer_Write( void )
|
|
{
|
|
sizebuf_t sb;
|
|
char testdata[0x100] = { 0 };
|
|
|
|
MSG_Init( &sb, __func__, testdata, sizeof( testdata ));
|
|
TASSERT_EQi( sb.iCurBit, 0 );
|
|
TASSERT_EQi( sb.nDataBits, sizeof( testdata ) << 3 );
|
|
TASSERT_EQi( sb.pData, testdata );
|
|
TASSERT_EQi( sb.bOverflow, false );
|
|
|
|
MSG_WriteBytes( &sb, "asdf", 4 );
|
|
TASSERT_EQi( sb.bOverflow, false );
|
|
TASSERT_EQi( sb.iCurBit, 32 );
|
|
|
|
MSG_WriteDword( &sb, 0xa1baa1ba );
|
|
TASSERT_EQi( sb.bOverflow, false );
|
|
TASSERT_EQi( sb.iCurBit, 64 );
|
|
|
|
MSG_WriteShort( &sb, -0x3713 );
|
|
TASSERT_EQi( sb.bOverflow, false );
|
|
TASSERT_EQi( sb.iCurBit, 80 );
|
|
|
|
MSG_WriteOneBit( &sb, 1 );
|
|
TASSERT_EQi( sb.bOverflow, false );
|
|
TASSERT_EQi( sb.iCurBit, 81 );
|
|
|
|
MSG_WriteOneBit( &sb, 0 );
|
|
TASSERT_EQi( sb.bOverflow, false );
|
|
TASSERT_EQi( sb.iCurBit, 82 );
|
|
|
|
MSG_WriteOneBit( &sb, 1 );
|
|
TASSERT_EQi( sb.bOverflow, false );
|
|
TASSERT_EQi( sb.iCurBit, 83 );
|
|
|
|
MSG_WriteOneBit( &sb, 0 );
|
|
TASSERT_EQi( sb.bOverflow, false );
|
|
TASSERT_EQi( sb.iCurBit, 84 );
|
|
|
|
MSG_WriteByte( &sb, 0xa1 );
|
|
TASSERT_EQi( sb.bOverflow, false );
|
|
TASSERT_EQi( sb.iCurBit, 92 );
|
|
|
|
TASSERT_EQi( MSG_GetNumBitsWritten( &sb ), g_testbuf_bits );
|
|
TASSERT_EQi( MSG_GetNumBytesWritten( &sb ), BitByte( g_testbuf_bits ));
|
|
TASSERT_EQi( MSG_GetRealBytesWritten( &sb ), g_testbuf_bits >> 3 );
|
|
|
|
// if tests fails here on big endian, it's possible due to endian issues
|
|
TASSERT( !memcmp( sb.pData, g_testbuf, g_testbuf_bits >> 3 ));
|
|
|
|
// must check last 4 bits separately because we never care about uninitialized bits
|
|
MSG_SeekToBit( &sb, g_testbuf_bits & ~7, SEEK_SET );
|
|
TASSERT_EQi( sb.iCurBit, 88 );
|
|
TASSERT_EQi( MSG_ReadUBitLong( &sb, 4 ), 0xa );
|
|
}
|
|
|
|
static void Test_Buffer_Read( void )
|
|
{
|
|
sizebuf_t sb;
|
|
char buf[4];
|
|
|
|
MSG_StartReading( &sb, (void *)g_testbuf, -1, 0, g_testbuf_bits );
|
|
TASSERT_EQi( sb.iCurBit, 0 );
|
|
TASSERT_EQi( sb.nDataBits, g_testbuf_bits );
|
|
TASSERT_EQi( sb.pData, g_testbuf );
|
|
TASSERT_EQi( sb.bOverflow, false );
|
|
|
|
MSG_ReadBytes( &sb, buf, 4 );
|
|
TASSERT( !memcmp( buf, "asdf", 4 ));
|
|
TASSERT_EQi( sb.iCurBit, 32 );
|
|
TASSERT_EQi( sb.bOverflow, false );
|
|
|
|
TASSERT_EQi( MSG_ReadWord( &sb ), 0xa1ba );
|
|
TASSERT_EQi( sb.iCurBit, 48 );
|
|
TASSERT_EQi( sb.bOverflow, false );
|
|
|
|
TASSERT_EQi( MSG_ReadDword( &sb ), 0xc8eda1baU );
|
|
TASSERT_EQi( sb.iCurBit, 80 );
|
|
TASSERT_EQi( sb.bOverflow, false );
|
|
|
|
TASSERT_EQi( MSG_ReadOneBit( &sb ), 1 );
|
|
TASSERT_EQi( sb.iCurBit, 81 );
|
|
TASSERT_EQi( sb.bOverflow, false );
|
|
|
|
TASSERT_EQi( MSG_ReadOneBit( &sb ), 0 );
|
|
TASSERT_EQi( sb.iCurBit, 82 );
|
|
TASSERT_EQi( sb.bOverflow, false );
|
|
|
|
TASSERT_EQi( MSG_ReadOneBit( &sb ), 1 );
|
|
TASSERT_EQi( sb.iCurBit, 83 );
|
|
TASSERT_EQi( sb.bOverflow, false );
|
|
|
|
TASSERT_EQi( MSG_ReadOneBit( &sb ), 0 );
|
|
TASSERT_EQi( sb.iCurBit, 84 );
|
|
TASSERT_EQi( sb.bOverflow, false );
|
|
|
|
TASSERT_EQi( MSG_ReadByte( &sb ), 0xa1 );
|
|
TASSERT_EQi( sb.iCurBit, 92 );
|
|
TASSERT_EQi( sb.bOverflow, false );
|
|
|
|
TASSERT_EQi( MSG_Overflow( &sb, 1 ), true );
|
|
TASSERT_EQi( sb.bOverflow, true );
|
|
}
|
|
|
|
static void Test_Buffer_ExciseBits( void )
|
|
{
|
|
sizebuf_t sb;
|
|
char testdata[0x100];
|
|
|
|
memcpy( testdata, g_testbuf, BitByte( g_testbuf_bits ));
|
|
|
|
MSG_StartWriting( &sb, testdata, 0, 0, g_testbuf_bits );
|
|
MSG_ExciseBits( &sb, 8, 28 );
|
|
|
|
TASSERT_EQi( MSG_CheckOverflow( &sb ), false );
|
|
TASSERT_EQi( MSG_GetMaxBits( &sb ), 64 );
|
|
TASSERT( !memcmp( MSG_GetData( &sb ), "a\x1b\xaa\x1b\xda\x8e\x5c\xa1", 8 ));
|
|
|
|
memcpy( testdata, g_testbuf, BitByte( g_testbuf_bits ));
|
|
|
|
MSG_StartWriting( &sb, testdata, 0, 0, g_testbuf_bits );
|
|
MSG_ExciseBits( &sb, 16, 32 );
|
|
|
|
TASSERT_EQi( MSG_CheckOverflow( &sb ), false );
|
|
TASSERT_EQi( MSG_GetMaxBits( &sb ), g_testbuf_bits - 32 );
|
|
TASSERT( !memcmp( MSG_GetData( &sb ), "as\xba\xa1\xed\xc8\x15", 7 ));
|
|
|
|
MSG_SeekToBit( &sb, 7 << 3, SEEK_SET );
|
|
TASSERT_EQi( MSG_ReadUBitLong( &sb, 4 ), 0xa );
|
|
}
|
|
|
|
void Test_RunBuffer( void )
|
|
{
|
|
MSG_InitMasks();
|
|
|
|
TRUN( Test_Buffer_BitByte( ));
|
|
TRUN( Test_Buffer_Write( ));
|
|
TRUN( Test_Buffer_Read( ));
|
|
TRUN( Test_Buffer_ExciseBits( ));
|
|
}
|
|
|
|
#endif // XASH_ENGINE_TESTS
|