Add support for Q2E's lan networking layer (the annoying lobby bit).

This is separate from protocol 2023 stuff.
This commit is contained in:
Shpoike 2023-08-19 03:14:09 +01:00
parent 3357338ab7
commit 969134d9fd
8 changed files with 1145 additions and 158 deletions

View file

@ -310,6 +310,7 @@ static struct
CIM_DEFAULT, //sends both a qw getchallenge and nq connect (also with postfixed getchallenge so modified servers can force getchallenge)
CIM_NQONLY, //disables getchallenge (so fte servers treat us as an nq client). should not be used for dpp7 servers.
CIM_QEONLY, //forces dtls and uses a different nq netchan version
CIM_Q2EONLY, //forces dtls and uses a different nq netchan version
} mode;
enum coninfospec_e
{
@ -825,6 +826,12 @@ char *CL_TryingToConnect(void)
if (!connectinfo.trying)
return NULL;
if (connectinfo.numadr >= 1 && connectinfo.adr[0].prot == NP_KEXLAN)
{
char status[1024];
if (NET_GetConnectionCertificate(cls.sockets, &connectinfo.adr[0], QCERT_LOBBYSTATUS, status, sizeof(status))>0)
return va("%s\n%s", cls.servername, status);
}
return cls.servername;
}
@ -870,6 +877,15 @@ static void CL_ResolvedServer(void *vctx, void *data, size_t a, size_t b)
}
#endif
if (connectinfo.mode == CIM_Q2EONLY)
{
for (i = 0; i < ctx->found; i++)
{ //if we've already established a dtls connection, stick with it
if (ctx->adr[i].prot == NP_DGRAM)
ctx->adr[i].prot = NP_KEXLAN;
}
}
connectinfo.numadr = ctx->found;
connectinfo.nextadr = 0;
connectinfo.resolving = false;
@ -889,7 +905,7 @@ static void CL_ResolveServer(void *vctx, void *data, size_t a, size_t b)
COM_AddWork(WG_MAIN, CL_ResolvedServer, ctx, data, a, b);
}
static qboolean CL_IsPendingServerAddress(netadr_t *adr)
qboolean CL_IsPendingServerAddress(netadr_t *adr)
{
size_t i;
for (i = 0; i < connectinfo.numadr; i++)
@ -1313,7 +1329,9 @@ void CL_CheckForResend (void)
if (to->prot == NP_DGRAM)
connectinfo.nextadr++; //cycle hosts with each ping (if we got multiple).
if (connectinfo.mode==CIM_QEONLY || connectinfo.mode==CIM_NQONLY)
if (connectinfo.mode==CIM_Q2EONLY)
contype |= 1; //don't ever try nq packets here.
else if (connectinfo.mode==CIM_QEONLY || connectinfo.mode==CIM_NQONLY)
contype |= 2;
else
{
@ -1321,7 +1339,16 @@ void CL_CheckForResend (void)
#ifdef VM_UI
if (!(q3&&q3->ui.IsRunning())) //don't try to connect to nq servers when running a q3ui. I was getting annoying error messages from q3 servers due to this.
#endif
contype |= 2; /*try nq connections periodically (or if its the default nq port)*/
{
COM_Parse(com_protocolname.string);
if (!strcmp(com_token, "Quake2"))
{
if (connectinfo.nextadr>3) //don't create an extra channel until we know our preferred one has failed.
contype |= 4; /*q2e's kex lan layer*/
}
else
contype |= 2; /*try nq connections periodically (or if its the default nq port)*/
}
}
/*DP, QW, Q2, Q3*/
@ -1401,6 +1428,15 @@ void CL_CheckForResend (void)
}
}
#endif
#ifdef Q2CLIENT
if ((contype & 4) && !connectinfo.clogged)
{
#define KEXLAN_SHAMELESSSELFPROMOMAGIC "\x08""CRANTIME" //hey, if you can't shove your own nick in your network protocols then you're doing it wrong.
#define KEXLAN_SUBPROTOCOL "\x08""Quake II" //this should be cvar-ised at some point, if its to ever be useful for anything but q2.
static char pkt[] = "\x01\x60\x80"KEXLAN_SHAMELESSSELFPROMOMAGIC KEXLAN_SUBPROTOCOL"\x01";
NET_SendPacket (cls.sockets, strlen(pkt), pkt, to);
}
#endif
connectinfo.tries++;
@ -1429,30 +1465,25 @@ static void CL_BeginServerConnect(char *host, int port, qboolean noproxy, enum c
if (schemeend)
{
//"qw:tcp://host/observe"
const char *schemestart = strchr(host, ':');
int schemelen;
//if its one of our explicit protocols then use the url as-is
const char *netschemes[] = {"udp", "udp4", "udp6", "ipx", "tcp", "tcp4", "tcp6", /*ipx*/"spx", "ws", "wss", "tls", "dtls", "ice", "rtc", "ices", "rtcs", "irc", "udg", "unix"};
int i;
size_t slen;
const struct urischeme_s *scheme;
if (!schemestart || schemestart==schemeend)
schemestart = host;
else
schemestart++;
schemelen = schemeend-schemestart;
Q_strncpyz (cls.servername, "", sizeof(cls.servername));
for (i = 0; i < countof(netschemes); i++)
//the scheme is either a network scheme in which case we use it directly, or a game-specific scheme.
scheme = NET_IsURIScheme(schemestart);
if (scheme->prot == NP_INVALID)
scheme = NULL; //qw:// or q3:// something that's just noise here.
if (scheme->flags&URISCHEME_NEEDSRESOURCE)
{
slen = strlen(netschemes[i]);
if (schemelen == slen && !strncmp(schemestart, netschemes[i], slen))
{
Q_strncpyz (cls.servername, host, sizeof(cls.servername)); //oh. will probably be okay then
break;
}
Q_strncpyz (cls.servername, schemestart, sizeof(cls.servername)); //oh. will probably be okay then
arglist = NULL;
}
if (!*cls.servername)
else
{ //not some '/foo' name, not rtc:// either...
char *sl = strchr(schemeend+3, '/');
if (sl)
@ -1479,7 +1510,11 @@ static void CL_BeginServerConnect(char *host, int port, qboolean noproxy, enum c
memmove(sl, sl+1, strlen(sl+1)+1);
}
}
Q_strncpyz (cls.servername, schemeend+3, sizeof(cls.servername)); //probably some game-specific mess that we don't know
if (scheme) //preserve the scheme, the netchan cares.
Q_strncpyz (cls.servername, schemestart, sizeof(cls.servername)); //probably some game-specific mess that we don't know
else
Q_strncpyz (cls.servername, schemeend+3, sizeof(cls.servername)); //probably some game-specific mess that we don't know
arglist = strchr(cls.servername, '?');
}
}
else
@ -1491,10 +1526,9 @@ static void CL_BeginServerConnect(char *host, int port, qboolean noproxy, enum c
Q_strncpyz (cls.servername, host, sizeof(cls.servername));
else
Q_snprintfz(cls.servername, sizeof(cls.servername), "%s@%s", host, cl_proxyaddr.string);
arglist = strchr(cls.servername, '?');
}
arglist = strchr(cls.servername, '?');
if (!port)
port = cl_defaultport.value;
@ -1748,6 +1782,7 @@ void CLNQ_Connect_f (void)
{
char *server;
enum coninfomode_e mode;
int port = 26000;
if (Cmd_Argc() != 2)
{
@ -1765,7 +1800,26 @@ void CLNQ_Connect_f (void)
CL_Disconnect_f ();
CL_BeginServerConnect(server, 26000, true, mode, CIS_DEFAULT/*doesn't really do spec/join stuff, but if the server asks for our info later...*/);
CL_BeginServerConnect(server, port, true, mode, CIS_DEFAULT/*doesn't really do spec/join stuff, but if the server asks for our info later...*/);
}
#endif
#ifdef Q2CLIENT
void CLQ2E_Connect_f (void)
{
char *server;
if (Cmd_Argc() != 2)
{
Con_TPrintf ("usage: connect <server>\n");
return;
}
server = Cmd_Argv (1);
server = strcpy(alloca(strlen(server)+1), server);
CL_Disconnect_f ();
CL_BeginServerConnect(server, PORT_Q2EXSERVER/*q2e servers ignore their own port cvar, so don't use the standard q2 port number here*/, true, CIM_Q2EONLY, CIS_DEFAULT);
}
#endif
@ -3564,6 +3618,7 @@ void CL_Reconnect_f (void)
static void CL_ConnectionlessPacket_Connection(char *tokens)
{
int qportsize = -1;
if (net_from.type == NA_INVALID)
return; //I've found a qizmo demo that contains one of these. its best left ignored.
@ -3601,8 +3656,10 @@ static void CL_ConnectionlessPacket_Connection(char *tokens)
}
#if defined(Q2CLIENT)
if (tokens)
if (tokens && cls.protocol == CP_QUAKE2)
{
if (cls.protocol_q2 == PROTOCOL_VERSION_R1Q2 || cls.protocol_q2 == PROTOCOL_VERSION_Q2PRO)
qportsize = 1;
tokens = COM_Parse(tokens); //skip the client_connect bit
while((tokens = COM_Parse(tokens)))
{
@ -3615,16 +3672,9 @@ static void CL_ConnectionlessPacket_Connection(char *tokens)
}
}
else if (!strncmp(com_token, "nc=", 3))
{
int type = (cls.protocol_q2 == PROTOCOL_VERSION_R1Q2 || cls.protocol_q2 == PROTOCOL_VERSION_Q2PRO)?1:0;
if (atoi(com_token+3) != type)
{
CL_ConnectAbort("server's netchan type differs from expected.");
return;
}
}
qportsize = atoi(com_token+3)?1:2;
else if (!strncmp(com_token, "map=", 4))
;
SCR_ImageName(com_token+4);
else if (!strncmp(com_token, "dlserver=", 9))
Q_strncpyz(cls.downloadurl, com_token+9, sizeof(cls.downloadurl));
else
@ -3642,12 +3692,9 @@ static void CL_ConnectionlessPacket_Connection(char *tokens)
cls.ezprotocolextensions1 = connectinfo.ext.ez1;
cls.challenge = connectinfo.challenge;
Netchan_Setup (NS_CLIENT, &cls.netchan, &net_from, connectinfo.qport);
if (cls.protocol == CP_QUAKE2)
{
cls.protocol_q2 = connectinfo.subprotocol;
if (cls.protocol_q2 == PROTOCOL_VERSION_R1Q2 || cls.protocol_q2 == PROTOCOL_VERSION_Q2PRO)
cls.netchan.qportsize = 1;
}
cls.protocol_q2 = (cls.protocol == CP_QUAKE2)?connectinfo.subprotocol:0;
if (qportsize>=0)
cls.netchan.qportsize = qportsize;
cls.netchan.pext_fragmentation = connectinfo.ext.mtu?true:false;
cls.netchan.pext_stunaware = !!(connectinfo.ext.fte2&PEXT2_STUNAWARE);
if (connectinfo.ext.mtu >= 64)
@ -5685,6 +5732,9 @@ void CL_Init (void)
#ifdef NQPROT
Cmd_AddCommandD ("connectnq", CLNQ_Connect_f, "Connects to the specified server, defaulting to port "STRINGIFY(PORT_NQSERVER)". Also disables QW/Q2/Q3/DP handshakes preventing them from being favoured, so should only be used when you actually want NQ protocols specifically.");
Cmd_AddCommandD ("connectqe", CLNQ_Connect_f, "Connects to the specified server, defaulting to port "STRINGIFY(PORT_NQSERVER)". Also forces the use of DTLS and QE-specific handshakes. You will also need to ensure the dtls_psk_* cvars are set properly or the server will refuse the connection.");
#endif
#ifdef Q2CLIENT
Cmd_AddCommandD ("connectq2e", CLQ2E_Connect_f, "Connects to the specified server, defaulting to port "STRINGIFY(PORT_Q2ESERVER)".");
#endif
Cmd_AddCommand ("reconnect", CL_Reconnect_f);
Cmd_AddCommandAD ("join", CL_Join_f, CL_Connect_c, "Switches away from spectator mode, optionally connecting to a different server.");

View file

@ -1671,6 +1671,8 @@ int CLNQ_GetMessage (void);
#endif
void CL_BeginServerReconnect(void);
qboolean CL_IsPendingServerAddress(netadr_t *adr);
void CL_Transfer(netadr_t *adr);
void SV_User_f (void); //called by client version of the function
void SV_Serverinfo_f (void);

View file

@ -3871,6 +3871,11 @@ void CL_Say (qboolean team, char *extra)
strlcat (sendtext, va("\x7f!%c", 'A'+pv->playernum), sizeof(sendtext));
}
#ifdef Q2CLIENT
if (cls.netchan.remote_address.prot == NP_KEXLAN && NET_GetConnectionCertificate(cls.sockets, &cls.netchan.remote_address, QCERT_LOBBYSENDCHAT, sendtext, strlen(sendtext))>0)
return;
#endif
#ifdef Q3CLIENT
if (cls.protocol == CP_QUAKE3)
q3->cl.SendClientCommand("%s %s%s", team ? "say_team" : "say", extra?extra:"", sendtext);

View file

@ -397,7 +397,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#undef USE_EGL
#endif
#if defined(HAVE_GNUTLS) || defined(HAVE_WINSSPI)
#if defined(HAVE_GNUTLS) || defined(HAVE_WINSSPI) || defined(HAVE_PLUGINS)
#define HAVE_SSL
#endif
#if defined(HAVE_GNUTLS) || defined(HAVE_WINSSPI) || defined(HAVE_PLUGINS)

View file

@ -54,6 +54,9 @@ typedef enum {
typedef enum {
NP_DGRAM,
NP_DTLS, //connected via ICE/WebRTC
NP_KEXLAN, //layered over some silly lobby mess
#define NP_ISLAYERED(np) (np>=NP_DTLS && np<=NP_KEXLAN)
NP_STREAM,
NP_TLS,
NP_WS,
@ -166,6 +169,18 @@ enum addressscope_e
};
enum addressscope_e NET_ClassifyAddress(netadr_t *adr, const char **outdesc);
struct urischeme_s
{
const char *name;
netproto_t prot; //NP_INVALID means its a game-specific one that should really be handled elsewhere but is silently ignored by the netcode.
netadrtype_t family; //usually NA_INVALID, unless ipv4/ipv6-specific
enum
{
URISCHEME_NEEDSRESOURCE = (1<<0), //forwards it on to the server
} flags;
};
const struct urischeme_s *NET_IsURIScheme(const char *possible);
qboolean NET_AddrIsReliable(netadr_t *adr); //hints that the protocol is reliable. if so, we don't need to wait for acks
qboolean NET_IsEncrypted(netadr_t *adr);
qboolean NET_CompareAdr (netadr_t *a, netadr_t *b);
@ -198,6 +213,9 @@ enum certprops_e
QCERT_PEERCERTIFICATE, //should be the primary cert, ignoring chain. no fixed maximum size required, mostly 2k but probably best to allow at leasy 5k.. or 8k.
QCERT_LOCALCERTIFICATE, //the cert we're using/advertising. may have no context. to tell people what fp to expect.
QCERT_LOBBYSTATUS, //for special-case lobby wrappers.
QCERT_LOBBYSENDCHAT, //to send chat via the stupid lobby instead of the game itself.
};
int NET_GetConnectionCertificate(struct ftenet_connections_s *col, netadr_t *a, enum certprops_e prop, char *out, size_t outsize);

View file

@ -387,7 +387,10 @@ void Netchan_Setup (netsrc_t sock, netchan_t *chan, netadr_t *adr, int qport)
chan->qport = qport;
chan->qportsize = 2;
if (adr->prot == NP_KEXLAN)
chan->qportsize = 0;
else
chan->qportsize = 2;
}
@ -790,6 +793,28 @@ int Netchan_Transmit (netchan_t *chan, int length, qbyte *data, int rate)
send_reliable = true;
}
if (send_reliable && chan->remote_address.prot == NP_KEXLAN)
#ifndef SERVERONLY
if (!cls.demoplayback)
#endif
{
if (chan->reliable_length)
{
send.data = send_buf;
send.maxsize = (chan->mtu?chan->mtu:MAX_QWMSGLEN) + PACKET_HEADER;
send.cursize = 0;
MSG_WriteLong (&send, 1u<<31);
MSG_WriteLong (&send, 1u<<31);
SZ_Write (&send, chan->reliable_buf, chan->reliable_length);
if (NETERR_SENT == NET_SendPacket (chan->sock, send.cursize, send.data, &chan->remote_address))
chan->reliable_length = 0; //the lower layer will handle any retransmission for us.
}
send_reliable = 0;
chan->incoming_reliable_sequence = 0;
}
// write the packet header
send.data = send_buf;
send.maxsize = (chan->mtu?chan->mtu:MAX_QWMSGLEN) + PACKET_HEADER;
@ -950,6 +975,9 @@ int Netchan_Transmit (netchan_t *chan, int length, qbyte *data, int rate)
if (e == NETERR_SENT)
{
if (send_reliable && NET_AddrIsReliable(&chan->remote_address))
chan->reliable_length = 0; //we know the peer will receive it. don't worry about waiting for their acks.
chan->bytesout += send.cursize;
Netchan_Block(chan, send.cursize, rate);
}
@ -1025,7 +1053,7 @@ qboolean Netchan_Process (netchan_t *chan)
// skip over the qport if we are a server (its handled elsewhere)
#ifndef CLIENTONLY
if (chan->sock == NS_SERVER)
MSG_ReadShort ();
MSG_ReadSkip (chan->qportsize);
#endif
if (chan->pext_fragmentation)
@ -1083,7 +1111,8 @@ qboolean Netchan_Process (netchan_t *chan)
//
// discard stale or duplicated packets
//
if (sequence <= (unsigned)chan->incoming_sequence)
if (sequence <= (unsigned)chan->incoming_sequence &&
!(reliable_message && chan->remote_address.prot == NP_KEXLAN)) //*sigh* reliables don't work properly here.
{
if (showdrop.value)
Con_TPrintf ("%s:Out of order packet %i at %i\n"
@ -1180,11 +1209,16 @@ qboolean Netchan_Process (netchan_t *chan)
//
// if this message contains a reliable message, bump incoming_reliable_sequence
//
chan->incoming_sequence = sequence;
chan->incoming_acknowledged = sequence_ack;
chan->incoming_reliable_acknowledged = reliable_ack;
if (reliable_message)
chan->incoming_reliable_sequence ^= 1;
if (reliable_message && chan->remote_address.prot == NP_KEXLAN) //*sigh* reliables don't work properly here.
; //don't corrupt sequences/acks/etc.
else
{
chan->incoming_sequence = sequence;
chan->incoming_acknowledged = sequence_ack;
chan->incoming_reliable_acknowledged = reliable_ack;
if (reliable_message)
chan->incoming_reliable_sequence ^= 1;
}
//
// the message can now be read from the current message pointer

File diff suppressed because it is too large Load diff

View file

@ -1943,7 +1943,7 @@ qboolean SV_ChallengePasses(int challenge)
//this means that DP clients tend to connect as generic NQ clients.
//and because DP _REQUIRES_ sv_bigcoords, they tend to end up being given fitz/rmq protocols
//thus we don't respond to the connect if sv_listen_dp is 1, and we had a recent getchallenge request. recent is 2 secs.
static qboolean SV_ChallengeRecent(void)
qboolean SV_ChallengeRecent(void)
{
int curtime = realtime; //yeah, evil. sue me. consitent with challenges.
int i;
@ -3264,13 +3264,13 @@ void SV_DoDirectConnect(svconnectinfo_t *fte_restrict info)
else //measure this guy in minuites.
s = va(langtext("Welcome back %s. You have previously spent %i mins connected\n", newcl->language), newcl->name, (int)(rs.timeonserver/60));
SV_OutOfBandPrintf (info->protocol == SCP_QUAKE2, &info->adr, s);
SV_OutOfBandPrintf (ISQ2CLIENT(info), &info->adr, s);
}
else if (!preserveparms)
{
SV_GetNewSpawnParms(newcl);
SV_OutOfBandTPrintf (info->protocol == SCP_QUAKE2, &info->adr, newcl->language, "Welcome %s. Your time on this server is being logged and ranked\n", newcl->name, (int)rs.timeonserver);
SV_OutOfBandTPrintf (ISQ2CLIENT(info), &info->adr, newcl->language, "Welcome %s. Your time on this server is being logged and ranked\n", newcl->name, (int)rs.timeonserver);
}
//else loaded players already have their initial parms set
}
@ -3570,7 +3570,7 @@ void SVC_DirectConnect(int expectedreliablesequence)
}*/
version = atoi(Cmd_Argv(1));
if (version >= 31 && version <= 34)
if (version >= PROTOCOL_VERSION_Q2_MIN && version <= PROTOCOL_VERSION_Q2)
info.protocol = SCP_QUAKE2;
#ifdef NQPROT
else if (version == NQ_NETCHAN_VERSION)
@ -3650,11 +3650,13 @@ void SVC_DirectConnect(int expectedreliablesequence)
}
}
// see if the challenge is valid.
if (net_from.type == NA_LOOPBACK) //normal rules don't apply
;
else if (net_from.prot != NP_DGRAM)
; //challenge checks are irrelevant when we've alread passed a challenge in a lower network layer.
else
{
// see if the challenge is valid
if (!SV_ChallengePasses(info.challenge))
{
if (sv_listen_dp.ival && !info.challenge && info.protocol == SCP_QUAKEWORLD)
@ -4940,8 +4942,16 @@ void SV_ReadPacket(void)
continue;
#endif
if (cl->netchan.qport != qport)
continue;
if (cl->netchan.qportsize == 0)
{ //no qports... use the actual port.
if (cl->netchan.remote_address.port != net_from.port)
continue;
}
else
{
if (cl->netchan.qport != qport)
continue;
}
if (cl->netchan.remote_address.port != net_from.port)
{
Con_DPrintf ("SV_ReadPackets: fixing up a translated port\n");
@ -4994,7 +5004,7 @@ dominping:
cl->send_message = true; // reply at end of frame
#ifdef Q2SERVER
if (cl->protocol == SCP_QUAKE2)
if (ISQ2CLIENT(cl))
SVQ2_ExecuteClientMessage(cl);
else
#endif
@ -5107,7 +5117,7 @@ qboolean SV_ReadPackets (float *delay)
cl->send_message = true; // reply at end of frame
#ifdef Q2SERVER
if (cl->protocol == SCP_QUAKE2)
if (ISQ2CLIENT(cl))
SVQ2_ExecuteClientMessage(cl);
else
#endif