fteqw/plugins/quake3/clq3_parse.c
Shpoike 8dadfb4878 Added sys_openfile console command(and menu option) to web and flatpak(via cmake+dbus) builds, to 'install' packages on sandboxed systems a bit more easily.
Cmake: Add FTE_WERROR option, defaults to true in debug builds and off in release builds (in case future compilers have issues).
Cmake: Pull in libXscreensaver so we don't get interrupted by screensavers when playing demos.
Make: Added `make webcl-rel` for a web build without server bloat (eg for sites focused on demo playback. Yes, this means you XantoM).
fteqcc: Include the decompiler in fteqcc (non-gui) builds ('-d' arg).
fteqcc: Decompiler can now mostly handle hexen2 mods without any unknown opcodes.
Allow ezHud and OpenSSL to be compiled as in-engine plugins, potentially for web and windows ports respectively.
Web: Fix support for ogg vorbis. Add support for voip.
Web: Added basic support for WebXR.
QTV: Don't try seeking on unseekable qtv streams. Don't spam when developer 1 is set.
QTV: add support for some eztv extensions.
MVD: added hack to use ktx's vweps in mvd where mvdsv doesn't bother to record the info.
qwfwd: hack around a hack in qwfwd, allowing it to work again.
recording: favour qwd in single player, instead of mvd.
Protocol: reduce client memory used for precache names. Bump maximum precache counts - some people are just abusive, yes you Orl.
hexen2: add enough clientside protocol compat to play the demo included with h2mp. lacks effects.
in_xflip: restored this setting.
fs_hidesyspaths: new cvar, defaults to enabled so you won't find your username or whatever turning up in screenshots or the like. change it to 0 before debuging stuff eg via 'path'.
gl_overbright_models: Added cvar to match QS.
netchan: Added MTU determination, we'll no longer fail to connect when routers stupidly drop icmp packets.
Win: try a few other versions of xinput too.
CSQC: Added a CSQC_GenerateMaterial function, to give the csqc a chance to generate custom materials.
MenuQC: Added support for the skeletal objects API.
2024-07-14 19:58:24 +01:00

1053 lines
26 KiB
C

#include "q3common.h"
#include "shader.h"
#include "glquake.h"
#include "shader.h"
#include "cl_master.h"
//urm, yeah, this is more than just parse.
#ifdef Q3CLIENT
#include "clq3defs.h"
#define SHOWSTRING(s) if(cl_shownet_ptr->value==2)Con_Printf ("%s\n", s);
#define SHOWNET(x) if(cl_shownet_ptr->value==2)Con_Printf ("%3i:%s\n", msg->currentbit-8, x);
#define SHOWNET2(x, y) if(cl_shownet_ptr->value==2)Con_Printf ("%3i:%3i:%s\n", msg->currentbit-8, y, x);
void MSG_WriteBits(sizebuf_t *msg, int value, int bits);
static qboolean CLQ3_Netchan_Process(sizebuf_t *msg);
ClientConnectionState_t ccs;
qboolean CG_FillQ3Snapshot(int snapnum, snapshot_t *snapshot)
{
int i;
clientSnap_t *snap;
if (snapnum > ccs.serverMessageNum)
{
plugfuncs->EndGame("CG_FillQ3Snapshot: snapshotNumber > cl.snap.serverMessageNum");
}
if (ccs.serverMessageNum - snapnum >= Q3UPDATE_BACKUP)
{
return false; // too old
}
snap = &ccs.snapshots[snapnum & Q3UPDATE_MASK];
if(!snap->valid || snap->serverMessageNum != snapnum)
{
return false; // invalid
}
memcpy(&snapshot->ps, &snap->playerstate, sizeof(snapshot->ps));
snapshot->numEntities = snap->numEntities;
for (i=0; i<snapshot->numEntities; i++)
{
memcpy(&snapshot->entities[i], &ccs.parseEntities[(snap->firstEntity+i) & Q3PARSE_ENTITIES_MASK], sizeof(snapshot->entities[0]));
}
memcpy( &snapshot->areamask, snap->areabits, sizeof( snapshot->areamask ) );
snapshot->snapFlags = snap->snapFlags;
snapshot->ping = snap->ping;
snapshot->serverTime = snap->serverTime;
snapshot->numServerCommands = snap->serverCommandNum;
snapshot->serverCommandSequence = ccs.lastServerCommandNum;
return true;
}
/*
=====================
CLQ3_ParseServerCommand
=====================
*/
void CLQ3_ParseServerCommand(void)
{
int number;
char *string;
number = msgfuncs->ReadLong();
// SHOWNET(va("%i", number));
string = msgfuncs->ReadString();
SHOWSTRING(string);
if( number <= ccs.lastServerCommandNum )
{
return; // we have already received this command
}
ccs.lastServerCommandNum++;
if( number > ccs.lastServerCommandNum+Q3TEXTCMD_MASK-1 )
{
Con_Printf("Warning: Lost %i reliable serverCommands\n",
number - ccs.lastServerCommandNum );
}
// archive the command to be processed by cgame later
Q_strncpyz( ccs.serverCommands[number & Q3TEXTCMD_MASK], string, sizeof( ccs.serverCommands[0] ) );
}
/*
==================
CL_DeltaEntity
Parses deltas from the given base and adds the resulting entity
to the current frame
==================
*/
static void CLQ3_DeltaEntity( clientSnap_t *frame, int newnum, q3entityState_t *old, qboolean unchanged )
{
q3entityState_t *state;
state = &ccs.parseEntities[ccs.firstParseEntity & Q3PARSE_ENTITIES_MASK];
if( unchanged )
{
memcpy( state, old, sizeof(*state) ); // don't read any bits
}
else
{
if (!MSG_Q3_ReadDeltaEntity(old, state, newnum)) // the entity present in oldframe is not in the current frame
return;
}
ccs.firstParseEntity++;
frame->numEntities++;
}
/*
==================
CL_ParsePacketEntities
An svc_packetentities has just been parsed, deal with the
rest of the data stream.
==================
*/
static void CLQ3_ParsePacketEntities( clientSnap_t *oldframe, clientSnap_t *newframe )
{
int numentities;
int oldnum;
int newnum;
q3entityState_t *oldstate;
oldstate = NULL;
newframe->firstEntity = ccs.firstParseEntity;
newframe->numEntities = 0;
// delta from the entities present in oldframe
numentities = 0;
if( !oldframe )
{
oldnum = 99999;
}
else if( oldframe->numEntities <= 0 )
{
oldnum = 99999;
}
else
{
oldstate = &ccs.parseEntities[oldframe->firstEntity & Q3PARSE_ENTITIES_MASK];
oldnum = oldstate->number;
}
while( 1 )
{
newnum = msgfuncs->ReadBits( GENTITYNUM_BITS );
if (newnum < 0)
plugfuncs->EndGame("CLQ3_ParsePacketEntities: end of message");
else if (newnum >= MAX_GENTITIES )
plugfuncs->EndGame("CLQ3_ParsePacketEntities: bad number %i", newnum);
// end of packetentities
if( newnum == ENTITYNUM_NONE )
{
break;
}
while( oldnum < newnum )
{
// one or more entities from the old packet are unchanged
SHOWSTRING( va( "unchanged: %i", oldnum ) );
CLQ3_DeltaEntity( newframe, oldnum, oldstate, true );
numentities++;
if( numentities >= oldframe->numEntities )
{
oldnum = 99999;
}
else
{
oldstate = &ccs.parseEntities[(oldframe->firstEntity + numentities) & Q3PARSE_ENTITIES_MASK];
oldnum = oldstate->number;
}
}
if( oldnum == newnum )
{
// delta from previous state
SHOWSTRING( va( "delta: %i", newnum ) );
CLQ3_DeltaEntity( newframe, newnum, oldstate, false );
numentities++;
if( numentities >= oldframe->numEntities )
{
oldnum = 99999;
}
else
{
oldstate = &ccs.parseEntities[(oldframe->firstEntity + numentities) & Q3PARSE_ENTITIES_MASK];
oldnum = oldstate->number;
}
continue;
}
if( oldnum > newnum )
{
// delta from baseline
SHOWSTRING( va( "baseline: %i", newnum ) );
CLQ3_DeltaEntity( newframe, newnum, &ccs.baselines[newnum], false );
}
}
// any remaining entities in the old frame are copied over
while( oldnum != 99999 )
{
// one or more entities from the old packet are unchanged
SHOWSTRING( va( "unchanged: %i", oldnum ) );
CLQ3_DeltaEntity( newframe, oldnum, oldstate, true );
numentities++;
if( numentities >= oldframe->numEntities )
{
oldnum = 99999;
}
else
{
oldstate = &ccs.parseEntities[(oldframe->firstEntity + numentities) & Q3PARSE_ENTITIES_MASK];
oldnum = oldstate->number;
}
}
}
void CLQ3_ParseSnapshot(void)
{
clientSnap_t snap, *oldsnap;
int delta;
int len;
// int i;
// outframe_t *frame;
// usercmd_t *ucmd;
// int commandTime;
memset(&snap, 0, sizeof(snap));
snap.serverMessageNum = ccs.serverMessageNum;
snap.serverCommandNum = ccs.lastServerCommandNum;
snap.serverTime = msgfuncs->ReadLong();
//so we can delta to it properly.
clientfuncs->UpdateGameTime(snap.serverTime / 1000.0f);
// If the frame is delta compressed from data that we
// no longer have available, we must suck up the rest of
// the frame, but not use it, then ask for a non-compressed message
delta = msgfuncs->ReadByte();
if(delta)
{
snap.deltaFrame = ccs.serverMessageNum - delta;
oldsnap = &ccs.snapshots[snap.deltaFrame & Q3UPDATE_MASK];
if(!oldsnap->valid)
{
// should never happen
Con_Printf( "Delta from invalid frame (not supposed to happen!).\n");
}
else if( oldsnap->serverMessageNum != snap.deltaFrame )
{
// The frame that the server did the delta from
// is too old, so we can't reconstruct it properly.
Con_DPrintf( "Delta frame too old.\n" );
}
else if(ccs.firstParseEntity - oldsnap->firstEntity >
Q3MAX_PARSE_ENTITIES - MAX_ENTITIES_IN_SNAPSHOT)
{
Con_DPrintf( "Delta parse_entities too old.\n" );
}
else
{
snap.valid = true; // valid delta parse
}
}
else
{
oldsnap = NULL;
snap.deltaFrame = -1;
snap.valid = true; // uncompressed frame
}
// read snapFlags
snap.snapFlags = msgfuncs->ReadByte();
// read areabits
len = msgfuncs->ReadByte();
msgfuncs->ReadData(snap.areabits, len );
// read playerinfo
SHOWSTRING("playerstate");
MSG_Q3_ReadDeltaPlayerstate(oldsnap ? &oldsnap->playerstate : NULL, &snap.playerstate);
// read packet entities
SHOWSTRING("packet entities");
CLQ3_ParsePacketEntities(oldsnap, &snap);
if (!snap.valid)
{
return;
}
// cl.adjustTimeDelta = true;
// Find last usercmd server has processed and calculate snap.ping
snap.ping = 3;
/* for (i=ccs.netchan.outgoing_sequence-1 ; i>ccs.netchan.outgoing_sequence-Q3CMD_BACKUP ; i--)
{
frame = &cl.outframes[i & Q3CMD_MASK];
if (frame->server_message_num == snap.deltaFrame)
{
snap.ping = plugfuncs->GetMilliseconds() - frame->client_time;
break;
}
}*/
memcpy(&ccs.snap, &snap, sizeof(snap));
memcpy(&ccs.snapshots[ccs.serverMessageNum & Q3UPDATE_MASK], &snap, sizeof(snap));
SHOWSTRING(va("snapshot:%i delta:%i ping:%i", snap.serverMessageNum, snap.deltaFrame, snap.ping));
}
#define MAXCHUNKSIZE 65536
void CLQ3_ParseDownload(void)
{
qdownload_t *dl = ccs.download;
unsigned int chunknum;
unsigned int chunksize;
unsigned char chunkdata[MAXCHUNKSIZE];
int i;
char *s;
chunknum = (unsigned short) msgfuncs->ReadShort();
chunknum |= ccs.downloadchunknum&~0xffff; //add the chunk number, truncated by the network protocol.
if (!chunknum)
{
dl->size = (unsigned int)msgfuncs->ReadLong();
cvarfuncs->SetFloat( cvarfuncs->GetNVFDG("cl_downloadSize", "0", 0, NULL, "Q3 Compat")->name, dl->size ); //so the gamecode knows download progress.
}
if (dl->size == (unsigned int)-1)
{ //the only downloads we should be getting is pk3s.
//if they're advertised-but-failing then its probably due to permissions rather than file-not-found
s = msgfuncs->ReadString();
clientfuncs->DownloadFailed(dl->remotename, dl, DLFAIL_SERVERCVAR);
plugfuncs->EndGame("%s", s);
return;
}
chunksize = (unsigned short)msgfuncs->ReadShort();
if (chunksize > MAXCHUNKSIZE)
plugfuncs->EndGame("Server sent a download chunk of size %i (it's too damn big!)\n", chunksize);
for (i = 0; i < chunksize; i++)
chunkdata[i] = msgfuncs->ReadByte();
if (ccs.downloadchunknum != chunknum) //the q3 client is rather lame.
{ //ccs.downloadchunknum holds the chunk number.
Con_DPrintf("PACKETLOSS WHEN DOWNLOADING!!!!\n");
return; //let the server try again some time
}
ccs.downloadchunknum++;
if (!dl || dl->method != DL_Q3)
{
Con_Printf("Server sending download, but no download was requested\n");
CLQ3_SendClientCommand("stopdl");
return;
}
if (!dl->file)
{
if (!clientfuncs->DownloadBegun(dl))
{
clientfuncs->DownloadFailed(dl->remotename, dl, DLFAIL_CLIENTFILE);
return;
}
}
Con_DPrintf("dl: chnk %u, size %u, csize %u\n", (unsigned int)chunknum, (unsigned int)dl->size, (unsigned int)chunksize);
if (!chunksize)
{
clientfuncs->DownloadFinished(dl);
ccs.servercount = -1; //make sure the server resends us that vital gamestate.
ccs.downloadchunknum = -1;
return;
}
else
{
VFS_WRITE(dl->file, chunkdata, chunksize);
dl->ratebytes += chunksize;
chunksize=VFS_TELL(dl->file);
// Con_Printf("Recieved %i\n", chunksize);
dl->percent = (100.0 * chunksize) / dl->size;
}
CLQ3_SendClientCommand("nextdl %u", chunknum);
}
static qboolean CLQ3_SendDownloads(char *rc, char *rn)
{
while(rn)
{
qdownload_t *dl;
char localname[MAX_QPATH];
char tempname[MAX_QPATH];
char filename[MAX_QPATH];
char crc[64];
vfsfile_t *f;
rc = cmdfuncs->ParseToken(rc, crc, sizeof(crc), NULL);
rn = cmdfuncs->ParseToken(rn, filename, sizeof(filename), NULL);
if (!*filename)
break;
if (!strchr(filename, '/')) //don't let some muppet tell us to download quake3.exe
break;
//as much as I'd like to use COM_FCheckExists, this stuf is relative to root, not the gamedir.
f = fsfuncs->OpenVFS(va("%s.pk3", filename), "rb", FS_ROOT);
if (f)
{
VFS_CLOSE(f);
continue;
}
if (!fsfuncs->GenCachedPakName(va("%s.pk3", filename), crc, localname, sizeof(localname)))
continue;
f = fsfuncs->OpenVFS(localname, "rb", FS_ROOT);
if (f)
{
VFS_CLOSE(f);
continue;
}
if (!fsfuncs->GenCachedPakName(va("%s.tmp", filename), crc, tempname, sizeof(tempname)))
continue;
if (!cvarfuncs->GetFloat("cl_downloads"))
{
Con_Printf(CON_WARNING "Need to download %s.pk3, but downloads are disabled\n", filename);
continue;
}
//fixme: request to download it
Con_Printf("Sending request to download %s.pk3\n", filename);
CLQ3_SendClientCommand("download %s.pk3", filename);
ccs.downloadchunknum = 0;
dl = Z_Malloc(sizeof(*dl));
//q3's downloads are relative to root, but they do at least force a pk3 extension.
Q_snprintfz(dl->localname, sizeof(dl->localname), "package/%s", localname);
Q_snprintfz(dl->tempname, sizeof(dl->tempname), "package/%s", tempname);
dl->prefixbytes = 8;
dl->fsroot = FS_ROOT;
Q_snprintfz(dl->remotename, sizeof(dl->remotename), "%s.pk3", filename);
dl->method = DL_Q3;
dl->percent = 0;
ccs.download = dl;
return true;
}
return false;
}
qboolean CLQ3_SystemInfoChanged(const char *str)
{
qboolean usingpure, usingcheats;
char *value;
char *pc, *pn;
char *rc, *rn;
Con_Printf("Server's sv_pure: \"%s\"\n", worldfuncs->GetInfoKey(str, "sv_pure"));
usingpure = atoi(worldfuncs->GetInfoKey(str, "sv_pure"));
usingcheats = atoi(worldfuncs->GetInfoKey(str, "sv_cheats"));
clientfuncs->ForceCheatVars(usingpure||usingcheats, usingcheats);
// if (atoi(value))
// Host_EndGame("Unable to connect to Q3 Pure Servers\n");
value = worldfuncs->GetInfoKey(str, "fs_game");
if (!*value)
{
value = "baseq3";
}
rc = worldfuncs->GetInfoKey(str, "sv_referencedPaks"); //the ones that we should download.
rn = worldfuncs->GetInfoKey(str, "sv_referencedPakNames");
if (CLQ3_SendDownloads(rc, rn))
return false;
pc = worldfuncs->GetInfoKey(str, "sv_paks"); //the ones that we are allowed to use (in order!)
pn = worldfuncs->GetInfoKey(str, "sv_pakNames");
fsfuncs->PureMode(value, usingpure?2:0, pn, pc, rn, rc, ccs.fs_key);
return true; //yay, we're in
}
void CLQ3_ParseGameState(sizebuf_t *msg)
{
int c;
int index;
char *configString;
//
// wipe the client_state_t struct
//
clientfuncs->ClearClientState();
CG_ClearGameState();
ccs.firstParseEntity = 0;
memset(ccs.parseEntities, 0, sizeof(ccs.parseEntities));
memset(ccs.baselines, 0, sizeof(ccs.baselines));
ccs.lastServerCommandNum = msgfuncs->ReadLong();
for(;;)
{
c = msgfuncs->ReadByte();
if(c < 0)
{
plugfuncs->EndGame("CLQ3_ParseGameState: read past end of server message");
break;
}
if(c == svcq3_eom)
{
break;
}
SHOWNET(va("%i", c));
switch(c)
{
default:
plugfuncs->EndGame("CLQ3_ParseGameState: bad command byte %i", c);
break;
case svcq3_configstring:
index = msgfuncs->ReadBits(16);
if (index < 0 || index >= MAX_Q3_CONFIGSTRINGS)
{
plugfuncs->EndGame("CLQ3_ParseGameState: configString index %i out of range", index);
}
configString = msgfuncs->ReadString();
CG_InsertIntoGameState(index, configString);
break;
case svcq3_baseline:
index = msgfuncs->ReadBits(GENTITYNUM_BITS);
if (index < 0 || index >= MAX_GENTITIES)
{
plugfuncs->EndGame("CLQ3_ParseGameState: baseline index %i out of range", index);
}
MSG_Q3_ReadDeltaEntity(NULL, &ccs.baselines[index], index);
break;
}
}
ccs.playernum = msgfuncs->ReadLong();
ccs.fs_key = msgfuncs->ReadLong();
if (!CLQ3_SystemInfoChanged(CG_GetConfigString(CFGSTR_SYSINFO)))
{
UI_Restart_f();
return;
}
scenefuncs->NewMap(NULL);
CG_Restart();
UI_Restart_f();
if (!ccs.worldmodel)
plugfuncs->EndGame("CGame didn't set a map.\n");
clientfuncs->SetLoadingState(false);
ccs.state = ca_active;
{
char buffer[2048];
strcpy(buffer, va("cp %i ", ccs.servercount));
fsfuncs->GenerateClientPacksList(buffer, sizeof(buffer), ccs.fs_key);
CLQ3_SendClientCommand("%s", buffer); // warning: format not a string literal and no format arguments
}
// load cgame, etc
// CL_ChangeLevel();
cvarfuncs->ForceSetString("cl_paused", "0");
}
int CLQ3_ParseServerMessage (sizebuf_t *msg)
{
int cmd;
if (!CLQ3_Netchan_Process(msg))
return ccs.state; //was a fragment.
if (cl_shownet_ptr->value == 1)
Con_Printf ("%i ", msg->cursize);
else if (cl_shownet_ptr->value == 2)
Con_Printf ("------------------\n");
msgfuncs->BeginReading(msg, msg_nullnetprim);
ccs.serverMessageNum = msgfuncs->ReadLong();
msg->packing = SZ_HUFFMAN; //the rest is huffman compressed.
// read last client command number server received
ccs.lastClientCommandNum = msgfuncs->ReadLong();
if( ccs.lastClientCommandNum <= ccs.numClientCommands - Q3TEXTCMD_BACKUP )
{
ccs.lastClientCommandNum = ccs.numClientCommands - Q3TEXTCMD_BACKUP + 1;
}
else if( ccs.lastClientCommandNum > ccs.numClientCommands )
{
ccs.lastClientCommandNum = ccs.numClientCommands;
}
//
// parse the message
//
for(;;)
{
cmd = msgfuncs->ReadByte();
if(cmd < 0) //hm, we have an eom, so only stop when the message is bad.
{
plugfuncs->EndGame("CLQ3_ParseServerMessage: read past end of server message");
break;
}
if(cmd == svcq3_eom)
{
SHOWNET2("END OF MESSAGE", 2);
break;
}
SHOWNET(va("%i", cmd));
// other commands
switch(cmd)
{
default:
plugfuncs->EndGame("CLQ3_ParseServerMessage: Illegible server message");
break;
case svcq3_nop:
break;
case svcq3_gamestate:
CLQ3_ParseGameState(msg);
break;
case svcq3_serverCommand:
CLQ3_ParseServerCommand();
break;
case svcq3_download:
CLQ3_ParseDownload();
break;
case svcq3_snapshot:
CLQ3_ParseSnapshot();
break;
}
}
return ccs.state;
}
static qboolean CLQ3_Netchan_Process(sizebuf_t *msg)
{
if(Netchan_ProcessQ3(&ccs.netchan, msg))
{
#ifndef Q3_NOENCRYPT
int sequence;
int lastClientCommandNum;
qbyte bitmask;
qbyte c;
int i, j;
char *string;
int readcount;
msgfuncs->BeginReading(msg, msg_nullnetprim);
sequence = msgfuncs->ReadLong();
msg->packing = SZ_HUFFMAN;
readcount = msg->currentbit>>3;
lastClientCommandNum = msgfuncs->ReadLong();
// calculate bitmask
bitmask = (sequence ^ ccs.challenge) & 0xff;
string = ccs.clientCommands[lastClientCommandNum & Q3TEXTCMD_MASK];
// decrypt the packet
for(i=readcount+4,j=0 ; i<msg->cursize ; i++,j++)
{
if(!string[j])
j = 0; // another way around
c = string[j];
if(c > 127 || c == '%')
c = '.';
bitmask ^= c << ((i-readcount) & 1);
msg->data[i] ^= bitmask;
}
msg->packing = SZ_RAWBITS; //first bit was plain...
#endif
return true; //all good
}
return false; //its bad dude, bad.
}
void CL_Netchan_Transmit(struct ftenet_connections_s *socket, int length, const qbyte *data )
{
#ifndef Q3_NOENCRYPT
sizebuf_t msg;
char msgdata[MAX_OVERALLMSGLEN];
int serverid;
int lastSequence;
int lastServerCommandNum;
qbyte bitmask;
qbyte c;
int i, j;
char *string;
msgfuncs->BeginWriting(&msg, msg_nullnetprim, msgdata, sizeof(msgdata));
msgfuncs->WriteData(&msg, data, length);
if (msg.overflowed)
{
plugfuncs->EndGame("Client message overflowed");
}
msgfuncs->BeginReading(&msg, msg_nullnetprim);
msg.packing = SZ_HUFFMAN;
serverid = msgfuncs->ReadLong();
lastSequence = msgfuncs->ReadLong();
lastServerCommandNum = msgfuncs->ReadLong();
// calculate bitmask
bitmask = (lastSequence ^ serverid ^ ccs.challenge) & 0xff;
string = ccs.serverCommands[lastServerCommandNum & Q3TEXTCMD_MASK];
// encrypt the packet
for (i=12,j=0 ; i<msg.cursize ; i++,j++)
{
if (!string[j])
{
j = 0; // another way around
}
c = string[j];
if (c > 127 || c == '%')
{
c = '.';
}
bitmask ^= c << (i & 1);
msg.data[i] ^= bitmask;
}
data = msg.data;
length = msg.cursize;
#endif
Netchan_TransmitQ3(socket, &ccs.netchan, length, data);
}
static void MSG_WriteDeltaKey( sizebuf_t *msg, int key, int from, int to, int bits )
{
if( from == to )
{
msgfuncs->WriteBits( msg, 0, 1 );
return; // unchanged
}
msgfuncs->WriteBits( msg, 1, 1 );
msgfuncs->WriteBits( msg, to ^ key, bits );
}
void MSG_Q3_WriteDeltaUsercmd( sizebuf_t *msg, int key, const usercmd_t *from, const usercmd_t *to )
{
// figure out how to pack serverTime
if( to->servertime - from->servertime < 255 )
{
msgfuncs->WriteBits(msg, 1, 1);
msgfuncs->WriteBits(msg, to->servertime - from->servertime, 8);
}
else
{
msgfuncs->WriteBits( msg, 0, 1 );
msgfuncs->WriteBits( msg, to->servertime, 32);
}
if( !memcmp( (qbyte *)from + 4, (qbyte *)to + 4, sizeof( usercmd_t ) - 4 ) )
{
msgfuncs->WriteBits(msg, 0, 1);
return; // nothing changed
}
key ^= to->servertime;
msgfuncs->WriteBits(msg, 1, 1);
MSG_WriteDeltaKey(msg, key, from->angles[0], to->angles[0], 16);
MSG_WriteDeltaKey(msg, key, from->angles[1], to->angles[1], 16);
MSG_WriteDeltaKey(msg, key, from->angles[2], to->angles[2], 16);
MSG_WriteDeltaKey(msg, key, from->forwardmove, to->forwardmove, 8);
MSG_WriteDeltaKey(msg, key, from->sidemove, to->sidemove, 8);
MSG_WriteDeltaKey(msg, key, from->upmove, to->upmove, 8);
MSG_WriteDeltaKey(msg, key, from->buttons, to->buttons, 16);
MSG_WriteDeltaKey(msg, key, from->weapon, to->weapon, 8);
}
void VARGS CLQ3_SendClientCommand(const char *fmt, ...)
{
va_list argptr;
char command[MAX_STRING_CHARS];
va_start( argptr, fmt );
vsnprintf( command, sizeof(command), fmt, argptr );
va_end( argptr );
// create new clientCommand
ccs.numClientCommands++;
// check if server will lose some of our clientCommands
if(ccs.numClientCommands - ccs.lastClientCommandNum >= Q3TEXTCMD_BACKUP)
plugfuncs->EndGame("Client command overflow");
Q_strncpyz(ccs.clientCommands[ccs.numClientCommands & Q3TEXTCMD_MASK], command, sizeof(ccs.clientCommands[0]));
Con_DPrintf("Sending %s\n", command);
}
void CLQ3_SendCmd(struct ftenet_connections_s *socket, usercmd_t *cmd, unsigned int movesequence, double gametime)
{
char *string;
int i;
char data[MAX_OVERALLMSGLEN];
sizebuf_t msg;
// outframe_t *frame;
unsigned int oldsequence;
int cmdcount, key;
const usercmd_t *to, *from;
static usercmd_t nullcmd;
//reuse the q1 array
cmd->servertime = gametime*1000;
cmd->weapon = ccs.selected_weapon;
cmd->forwardmove *= 127/400.0f;
cmd->sidemove *= 127/400.0f;
cmd->upmove *= 127/400.0f;
if (cmd->forwardmove > 127)
cmd->forwardmove = 127;
if (cmd->forwardmove < -127)
cmd->forwardmove = -127;
if (cmd->sidemove > 127)
cmd->sidemove = 127;
if (cmd->sidemove < -127)
cmd->sidemove = -127;
if (cmd->upmove > 127)
cmd->upmove = 127;
if (cmd->upmove < -127)
cmd->upmove = -127;
if (cmd->buttons & 2) //jump
{
cmd->upmove = 100;
cmd->buttons &= ~2;
}
if (inputfuncs->GetKeyDest() & ~kdm_game)
cmd->buttons |= 2; //add in the 'at console' button
//FIXME: q3 generates a new command every video frame, but a new packet at a more limited rate.
//FIXME: we should return here if its not yet time for a network frame.
/* frame = &cl.outframes[ccs.netchan.outgoing_sequence & Q3CMD_MASK];
frame->cmd_sequence = movesequence;
frame->server_message_num = ccs.serverMessageNum;
frame->server_time = gametime;
frame->client_time = plugfuncs->GetMilliseconds();
*/
memset(&msg, 0, sizeof(msg));
msg.maxsize = sizeof(data);
msg.data = data;
msg.packing = SZ_HUFFMAN;
msgfuncs->WriteBits(&msg, ccs.servercount, 32);
msgfuncs->WriteBits(&msg, ccs.serverMessageNum, 32);
msgfuncs->WriteBits(&msg, ccs.lastServerCommandNum, 32);
// write clientCommands not acknowledged by server yet
for (i=ccs.lastClientCommandNum+1; i<=ccs.numClientCommands; i++)
{
msgfuncs->WriteBits(&msg, clcq3_clientCommand, 8);
msgfuncs->WriteBits(&msg, i, 32);
string = ccs.clientCommands[i & Q3TEXTCMD_MASK];
while(*string)
msgfuncs->WriteBits(&msg, *string++, 8);
msgfuncs->WriteBits(&msg, 0, 8);
}
i = ccs.netchan.outgoing_sequence;
i -= bound(0, cl_c2sdupe_ptr->ival, 5); //extra age, if desired
i--;
if (i < ccs.netchan.outgoing_sequence-Q3CMD_MASK)
i = ccs.netchan.outgoing_sequence-Q3CMD_MASK;
oldsequence = movesequence-1;//cl.outframes[i & Q3CMD_MASK].cmd_sequence;
cmdcount = movesequence - oldsequence;
if (cmdcount > Q3CMD_MASK)
cmdcount = Q3CMD_MASK;
// begin a client move command, if any
if (cmdcount)
{
if(cl_nodelta_ptr->value || !ccs.snap.valid || ccs.snap.serverMessageNum != ccs.serverMessageNum)
msgfuncs->WriteBits(&msg, clcq3_nodeltaMove, 8); // no compression
else
msgfuncs->WriteBits(&msg, clcq3_move, 8);
// write cmdcount
msgfuncs->WriteBits(&msg, cmdcount, 8);
// calculate key
string = ccs.serverCommands[ccs.lastServerCommandNum & Q3TEXTCMD_MASK];
key = ccs.fs_key ^ ccs.serverMessageNum ^ StringKey(string, 32);
//note that q3 uses timestamps so sequences are not important
//we can also send dupes without issue.
from = &nullcmd;
for (i = movesequence-cmdcount; i < movesequence; i++)
{
to = inputfuncs->GetMoveEntry(i);
if (!to)
to = from;
MSG_Q3_WriteDeltaUsercmd( &msg, key, from, to );
from = to;
}
}
msgfuncs->WriteBits(&msg, clcq3_eom, 8);
CL_Netchan_Transmit(socket, msg.cursize, msg.data );
while(ccs.netchan.reliable_length)
Netchan_TransmitNextFragment(socket, &ccs.netchan);
}
void CLQ3_SendAuthPacket(struct ftenet_connections_s *socket, netadr_t *gameserver)
{
#ifdef HAVE_PACKET
char data[2048];
sizebuf_t msg;
//send the auth packet
//this should be the right code, but it doesn't work.
if (gameserver->type == NA_IP)
{
char *key = cvarfuncs->GetNVFDG("cl_cdkey", "", CVAR_ARCHIVE, "Quake3 auth", "Q3 Compat")->string;
netadr_t authaddr;
#define Q3_AUTHORIZE_SERVER_NAME "authorize.quake3arena.com:27952"
if (*key)
{
Con_Printf("Resolving %s\n", Q3_AUTHORIZE_SERVER_NAME);
if (masterfuncs->StringToAdr(Q3_AUTHORIZE_SERVER_NAME, 0, &authaddr, 1, NULL))
{
msgfuncs->BeginWriting(&msg, msg_nullnetprim, data, sizeof(data));
msgfuncs->WriteLong(&msg, -1);
msgfuncs->WriteString(&msg, "getKeyAuthorize 0 ");
msg.cursize--;
while(*key)
{
if ((*key >= 'a' && *key <= 'z') || (*key >= 'A' && *key <= 'Z') || (*key >= '0' && *key <= '9'))
msgfuncs->WriteByte(&msg, *key);
key++;
}
msgfuncs->WriteByte(&msg, 0);
msgfuncs->SendPacket(socket, msg.cursize, msg.data, &authaddr);
}
else
Con_Printf(" failed\n");
}
}
#endif
}
void CLQ3_SendConnectPacket(struct ftenet_connections_s *socket, netadr_t *to, int challenge, int qport, infobuf_t *userinfo)
{
char infostr[1024];
char data[2048];
sizebuf_t msg;
static const char *priorityq3[] = {"*", "name", NULL};
static const char *nonq3[] = {"challenge", "qport", "protocol", "ip", "chat", NULL};
int protocol = cvarfuncs->GetFloat("com_protocolversion");
memset(&ccs, 0, sizeof(ccs));
ccs.servercount = -1;
ccs.challenge = challenge;
Netchan_SetupQ3(NCF_CLIENT, &ccs.netchan, to, qport);
worldfuncs->IBufToInfo(userinfo, infostr, sizeof(infostr), priorityq3, nonq3, NULL, NULL,/*&cls.userinfosync,*/ userinfo);
msgfuncs->BeginWriting(&msg, msg_nullnetprim, data, sizeof(data));
msgfuncs->WriteLong(&msg, -1);
msgfuncs->WriteString(&msg, va("connect \"\\challenge\\%i\\qport\\%i\\protocol\\%i%s\"", challenge, qport, protocol, infostr));
#ifdef HUFFNETWORK
if (msgfuncs->Huff_EncryptPacket)
msgfuncs->Huff_EncryptPacket(&msg, 12);
if (!msgfuncs->Huff_CompressionCRC || !msgfuncs->Huff_CompressionCRC(HUFFCRC_QUAKE3))
{
Con_Printf("Huffman compression error\n");
return;
}
#endif
msgfuncs->SendPacket (socket, msg.cursize, msg.data, to);
}
void CLQ3_Established(void)
{
ccs.state = ca_connected;
}
void CLQ3_Disconnect(struct ftenet_connections_s *socket)
{
ccs.state = ca_disconnected;
}
#endif