Rewrote lobby to unify common and Doom-specific packet structure, allowing for saner handling of in-game joining. Added a new per-client stage system that allows individual clients to be handled at a time when gathering and sharing info. Reworked lobby UI to display user info and added kick/ban functionalities. Bans are only a temporary per-game IP ban (use passwords to keep unwanted users out). Increased max player count to 64 and unified engine constant.
500 lines
12 KiB
C++
500 lines
12 KiB
C++
/*
|
|
** d_protocol.cpp
|
|
** Basic network packet creation routines and simple IFF parsing
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
** Copyright 1998-2006 Randy Heit
|
|
** All rights reserved.
|
|
**
|
|
** Redistribution and use in source and binary forms, with or without
|
|
** modification, are permitted provided that the following conditions
|
|
** are met:
|
|
**
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
** notice, this list of conditions and the following disclaimer.
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
** documentation and/or other materials provided with the distribution.
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
** derived from this software without specific prior written permission.
|
|
**
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
*/
|
|
|
|
#include "d_protocol.h"
|
|
#include "d_net.h"
|
|
#include "doomstat.h"
|
|
#include "cmdlib.h"
|
|
#include "serializer.h"
|
|
|
|
char* ReadString(uint8_t **stream)
|
|
{
|
|
char *string = *((char **)stream);
|
|
|
|
*stream += strlen(string) + 1;
|
|
return copystring(string);
|
|
}
|
|
|
|
const char* ReadStringConst(uint8_t **stream)
|
|
{
|
|
const char *string = *((const char **)stream);
|
|
*stream += strlen(string) + 1;
|
|
return string;
|
|
}
|
|
|
|
uint8_t ReadInt8(uint8_t **stream)
|
|
{
|
|
uint8_t v = **stream;
|
|
*stream += 1;
|
|
return v;
|
|
}
|
|
|
|
int16_t ReadInt16(uint8_t **stream)
|
|
{
|
|
int16_t v = (((*stream)[0]) << 8) | (((*stream)[1]));
|
|
*stream += 2;
|
|
return v;
|
|
}
|
|
|
|
int32_t ReadInt32(uint8_t **stream)
|
|
{
|
|
int32_t v = (((*stream)[0]) << 24) | (((*stream)[1]) << 16) | (((*stream)[2]) << 8) | (((*stream)[3]));
|
|
*stream += 4;
|
|
return v;
|
|
}
|
|
|
|
int64_t ReadInt64(uint8_t** stream)
|
|
{
|
|
int64_t v = (int64_t((*stream)[0]) << 56) | (int64_t((*stream)[1]) << 48) | (int64_t((*stream)[2]) << 40) | (int64_t((*stream)[3]) << 32)
|
|
| (int64_t((*stream)[4]) << 24) | (int64_t((*stream)[5]) << 16) | (int64_t((*stream)[6]) << 8) | (int64_t((*stream)[7]));
|
|
*stream += 8;
|
|
return v;
|
|
}
|
|
|
|
float ReadFloat(uint8_t **stream)
|
|
{
|
|
union
|
|
{
|
|
int32_t i;
|
|
float f;
|
|
} fakeint;
|
|
fakeint.i = ReadInt32 (stream);
|
|
return fakeint.f;
|
|
}
|
|
|
|
double ReadDouble(uint8_t** stream)
|
|
{
|
|
union
|
|
{
|
|
int64_t i;
|
|
double f;
|
|
} fakeint;
|
|
fakeint.i = ReadInt64(stream);
|
|
return fakeint.f;
|
|
}
|
|
|
|
void WriteString(const char *string, uint8_t **stream)
|
|
{
|
|
char *p = *((char **)stream);
|
|
|
|
while (*string) {
|
|
*p++ = *string++;
|
|
}
|
|
|
|
*p++ = 0;
|
|
*stream = (uint8_t *)p;
|
|
}
|
|
|
|
void WriteInt8(uint8_t v, uint8_t **stream)
|
|
{
|
|
**stream = v;
|
|
*stream += 1;
|
|
}
|
|
|
|
void WriteInt16(int16_t v, uint8_t **stream)
|
|
{
|
|
(*stream)[0] = v >> 8;
|
|
(*stream)[1] = v & 255;
|
|
*stream += 2;
|
|
}
|
|
|
|
void WriteInt32(int32_t v, uint8_t **stream)
|
|
{
|
|
(*stream)[0] = v >> 24;
|
|
(*stream)[1] = (v >> 16) & 255;
|
|
(*stream)[2] = (v >> 8) & 255;
|
|
(*stream)[3] = v & 255;
|
|
*stream += 4;
|
|
}
|
|
|
|
void WriteInt64(int64_t v, uint8_t** stream)
|
|
{
|
|
(*stream)[0] = v >> 56;
|
|
(*stream)[1] = (v >> 48) & 255;
|
|
(*stream)[2] = (v >> 40) & 255;
|
|
(*stream)[3] = (v >> 32) & 255;
|
|
(*stream)[4] = (v >> 24) & 255;
|
|
(*stream)[5] = (v >> 16) & 255;
|
|
(*stream)[6] = (v >> 8) & 255;
|
|
(*stream)[7] = v & 255;
|
|
*stream += 8;
|
|
}
|
|
|
|
void WriteFloat(float v, uint8_t **stream)
|
|
{
|
|
union
|
|
{
|
|
int32_t i;
|
|
float f;
|
|
} fakeint;
|
|
fakeint.f = v;
|
|
WriteInt32 (fakeint.i, stream);
|
|
}
|
|
|
|
void WriteDouble(double v, uint8_t** stream)
|
|
{
|
|
union
|
|
{
|
|
int64_t i;
|
|
double f;
|
|
} fakeint;
|
|
fakeint.f = v;
|
|
WriteInt64(fakeint.i, stream);
|
|
}
|
|
|
|
FSerializer& Serialize(FSerializer& arc, const char* key, usercmd_t& cmd, usercmd_t* def)
|
|
{
|
|
// This used packed data with the old serializer but that's totally counterproductive when
|
|
// having a text format that is human-readable. So this compression has been undone here.
|
|
// The few bytes of file size it saves are not worth the obfuscation.
|
|
|
|
if (arc.BeginObject(key))
|
|
{
|
|
arc("buttons", cmd.buttons)
|
|
("pitch", cmd.pitch)
|
|
("yaw", cmd.yaw)
|
|
("roll", cmd.roll)
|
|
("forwardmove", cmd.forwardmove)
|
|
("sidemove", cmd.sidemove)
|
|
("upmove", cmd.upmove)
|
|
.EndObject();
|
|
}
|
|
return arc;
|
|
}
|
|
|
|
// Returns the number of bytes read
|
|
int UnpackUserCmd(usercmd_t& cmd, const usercmd_t* basis, uint8_t*& stream)
|
|
{
|
|
const uint8_t* start = stream;
|
|
|
|
if (basis != nullptr)
|
|
{
|
|
if (basis != &cmd)
|
|
memcpy(&cmd, basis, sizeof(usercmd_t));
|
|
}
|
|
else
|
|
{
|
|
memset(&cmd, 0, sizeof(usercmd_t));
|
|
}
|
|
|
|
uint8_t flags = ReadInt8(&stream);
|
|
if (flags)
|
|
{
|
|
// We can support up to 29 buttons using 1 to 4 bytes to store them. The most
|
|
// significant bit of each button byte is a flag to indicate whether or not more buttons
|
|
// should be read in excluding the last one which supports all 8 bits.
|
|
if (flags & UCMDF_BUTTONS)
|
|
{
|
|
uint8_t in = ReadInt8(&stream);
|
|
uint32_t buttons = (cmd.buttons & ~0x7F) | (in & 0x7F);
|
|
if (in & MoreButtons)
|
|
{
|
|
in = ReadInt8(&stream);
|
|
buttons = (buttons & ~(0x7F << 7)) | ((in & 0x7F) << 7);
|
|
if (in & MoreButtons)
|
|
{
|
|
in = ReadInt8(&stream);
|
|
buttons = (buttons & ~(0x7F << 14)) | ((in & 0x7F) << 14);
|
|
if (in & MoreButtons)
|
|
{
|
|
in = ReadInt8(&stream);
|
|
buttons = (buttons & ~(0xFF << 21)) | (in << 21);
|
|
}
|
|
}
|
|
}
|
|
cmd.buttons = buttons;
|
|
}
|
|
if (flags & UCMDF_PITCH)
|
|
cmd.pitch = ReadInt16(&stream);
|
|
if (flags & UCMDF_YAW)
|
|
cmd.yaw = ReadInt16(&stream);
|
|
if (flags & UCMDF_FORWARDMOVE)
|
|
cmd.forwardmove = ReadInt16(&stream);
|
|
if (flags & UCMDF_SIDEMOVE)
|
|
cmd.sidemove = ReadInt16(&stream);
|
|
if (flags & UCMDF_UPMOVE)
|
|
cmd.upmove = ReadInt16(&stream);
|
|
if (flags & UCMDF_ROLL)
|
|
cmd.roll = ReadInt16(&stream);
|
|
}
|
|
|
|
return int(stream - start);
|
|
}
|
|
|
|
int PackUserCmd(const usercmd_t& cmd, const usercmd_t* basis, uint8_t*& stream)
|
|
{
|
|
uint8_t flags = 0;
|
|
uint8_t* start = stream;
|
|
|
|
usercmd_t blank;
|
|
if (basis == nullptr)
|
|
{
|
|
memset(&blank, 0, sizeof(blank));
|
|
basis = ␣
|
|
}
|
|
|
|
++stream; // Make room for the flags.
|
|
uint32_t buttons_changed = cmd.buttons ^ basis->buttons;
|
|
if (buttons_changed != 0)
|
|
{
|
|
uint8_t bytes[4] = { uint8_t(cmd.buttons & 0x7F),
|
|
uint8_t((cmd.buttons >> 7) & 0x7F),
|
|
uint8_t((cmd.buttons >> 14) & 0x7F),
|
|
uint8_t((cmd.buttons >> 21) & 0xFF) };
|
|
|
|
flags |= UCMDF_BUTTONS;
|
|
if (buttons_changed & 0xFFFFFF80)
|
|
{
|
|
bytes[0] |= MoreButtons;
|
|
if (buttons_changed & 0xFFFFC000)
|
|
{
|
|
bytes[1] |= MoreButtons;
|
|
if (buttons_changed & 0xFFE00000)
|
|
bytes[2] |= MoreButtons;
|
|
}
|
|
}
|
|
WriteInt8(bytes[0], &stream);
|
|
if (bytes[0] & MoreButtons)
|
|
{
|
|
WriteInt8(bytes[1], &stream);
|
|
if (bytes[1] & MoreButtons)
|
|
{
|
|
WriteInt8(bytes[2], &stream);
|
|
if (bytes[2] & MoreButtons)
|
|
WriteInt8(bytes[3], &stream);
|
|
}
|
|
}
|
|
}
|
|
if (cmd.pitch != basis->pitch)
|
|
{
|
|
flags |= UCMDF_PITCH;
|
|
WriteInt16(cmd.pitch, &stream);
|
|
}
|
|
if (cmd.yaw != basis->yaw)
|
|
{
|
|
flags |= UCMDF_YAW;
|
|
WriteInt16 (cmd.yaw, &stream);
|
|
}
|
|
if (cmd.forwardmove != basis->forwardmove)
|
|
{
|
|
flags |= UCMDF_FORWARDMOVE;
|
|
WriteInt16 (cmd.forwardmove, &stream);
|
|
}
|
|
if (cmd.sidemove != basis->sidemove)
|
|
{
|
|
flags |= UCMDF_SIDEMOVE;
|
|
WriteInt16(cmd.sidemove, &stream);
|
|
}
|
|
if (cmd.upmove != basis->upmove)
|
|
{
|
|
flags |= UCMDF_UPMOVE;
|
|
WriteInt16(cmd.upmove, &stream);
|
|
}
|
|
if (cmd.roll != basis->roll)
|
|
{
|
|
flags |= UCMDF_ROLL;
|
|
WriteInt16(cmd.roll, &stream);
|
|
}
|
|
|
|
// Write the packing bits
|
|
WriteInt8(flags, &start);
|
|
|
|
return int(stream - start);
|
|
}
|
|
|
|
int WriteUserCmdMessage(const usercmd_t& cmd, const usercmd_t* basis, uint8_t*& stream)
|
|
{
|
|
if (basis == nullptr)
|
|
{
|
|
if (cmd.buttons
|
|
|| cmd.pitch || cmd.yaw || cmd.roll
|
|
|| cmd.forwardmove || cmd.sidemove || cmd.upmove)
|
|
{
|
|
WriteInt8(DEM_USERCMD, &stream);
|
|
return PackUserCmd(cmd, basis, stream) + 1;
|
|
}
|
|
}
|
|
else if (cmd.buttons != basis->buttons
|
|
|| cmd.yaw != basis->yaw || cmd.pitch != basis->pitch || cmd.roll != basis->roll
|
|
|| cmd.forwardmove != basis->forwardmove || cmd.sidemove != basis->sidemove || cmd.upmove != basis->upmove)
|
|
{
|
|
WriteInt8(DEM_USERCMD, &stream);
|
|
return PackUserCmd(cmd, basis, stream) + 1;
|
|
}
|
|
|
|
WriteInt8(DEM_EMPTYUSERCMD, &stream);
|
|
return 1;
|
|
}
|
|
|
|
// Reads through the user command without actually setting any of its info. Used to get the size
|
|
// of the command when getting the length of the stream.
|
|
int SkipUserCmdMessage(uint8_t*& stream)
|
|
{
|
|
const uint8_t* start = stream;
|
|
|
|
while (true)
|
|
{
|
|
const uint8_t type = *stream++;
|
|
if (type == DEM_USERCMD)
|
|
{
|
|
int skip = 1;
|
|
if (*stream & UCMDF_PITCH)
|
|
skip += 2;
|
|
if (*stream & UCMDF_YAW)
|
|
skip += 2;
|
|
if (*stream & UCMDF_FORWARDMOVE)
|
|
skip += 2;
|
|
if (*stream & UCMDF_SIDEMOVE)
|
|
skip += 2;
|
|
if (*stream & UCMDF_UPMOVE)
|
|
skip += 2;
|
|
if (*stream & UCMDF_ROLL)
|
|
skip += 2;
|
|
if (*stream & UCMDF_BUTTONS)
|
|
{
|
|
if (*++stream & MoreButtons)
|
|
{
|
|
if (*++stream & MoreButtons)
|
|
{
|
|
if (*++stream & MoreButtons)
|
|
++stream;
|
|
}
|
|
}
|
|
}
|
|
stream += skip;
|
|
break;
|
|
}
|
|
else if (type == DEM_EMPTYUSERCMD)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
Net_SkipCommand(type, &stream);
|
|
}
|
|
}
|
|
|
|
return int(stream - start);
|
|
}
|
|
|
|
int ReadUserCmdMessage(uint8_t*& stream, int player, int tic)
|
|
{
|
|
const int ticMod = tic % BACKUPTICS;
|
|
|
|
auto& curTic = ClientStates[player].Tics[ticMod];
|
|
usercmd_t& ticCmd = curTic.Command;
|
|
|
|
const uint8_t* start = stream;
|
|
|
|
// Skip until we reach the player command. Event data will get read off once the
|
|
// tick is actually executed.
|
|
int type;
|
|
while ((type = ReadInt8(&stream)) != DEM_USERCMD && type != DEM_EMPTYUSERCMD)
|
|
Net_SkipCommand(type, &stream);
|
|
|
|
// Subtract a byte to account for the fact the stream head is now sitting on the
|
|
// user command.
|
|
curTic.Data.SetData(start, int(stream - start - 1));
|
|
|
|
if (type == DEM_USERCMD)
|
|
{
|
|
UnpackUserCmd(ticCmd,
|
|
tic > 0 ? &ClientStates[player].Tics[(tic - 1) % BACKUPTICS].Command : nullptr, stream);
|
|
}
|
|
else
|
|
{
|
|
if (tic > 0)
|
|
memcpy(&ticCmd, &ClientStates[player].Tics[(tic - 1) % BACKUPTICS].Command, sizeof(ticCmd));
|
|
else
|
|
memset(&ticCmd, 0, sizeof(ticCmd));
|
|
}
|
|
|
|
return int(stream - start);
|
|
}
|
|
|
|
void RunPlayerCommands(int player, int tic)
|
|
{
|
|
// We don't have the full command yet, so don't run it.
|
|
if (gametic % TicDup)
|
|
return;
|
|
|
|
int len;
|
|
auto& data = ClientStates[player].Tics[tic % BACKUPTICS].Data;
|
|
uint8_t* stream = data.GetData(&len);
|
|
if (stream != nullptr)
|
|
{
|
|
uint8_t* end = stream + len;
|
|
while (stream < end)
|
|
Net_DoCommand(ReadInt8(&stream), &stream, player);
|
|
|
|
if (!demorecording)
|
|
data.SetData(nullptr, 0);
|
|
}
|
|
}
|
|
|
|
// Demo related functionality
|
|
|
|
uint8_t* streamPos = nullptr;
|
|
|
|
// Write the header of an IFF chunk and leave space
|
|
// for the length field.
|
|
void StartChunk(int id, uint8_t **stream)
|
|
{
|
|
WriteInt32(id, stream);
|
|
streamPos = *stream;
|
|
*stream += 4;
|
|
}
|
|
|
|
// Write the length field for the chunk and insert
|
|
// pad byte if the chunk is odd-sized.
|
|
void FinishChunk(uint8_t **stream)
|
|
{
|
|
if (streamPos == nullptr)
|
|
return;
|
|
|
|
int len = int(*stream - streamPos - 4);
|
|
WriteInt32(len, &streamPos);
|
|
if (len & 1)
|
|
WriteInt8(0, stream);
|
|
|
|
streamPos = nullptr;
|
|
}
|
|
|
|
// Skip past an unknown chunk. *stream should be
|
|
// pointing to the chunk's length field.
|
|
void SkipChunk(uint8_t **stream)
|
|
{
|
|
int len = ReadInt32(stream);
|
|
*stream += len + (len & 1);
|
|
}
|