/* #FILENAME# #DESCRIPTION# Copyright (C) 2004 #AUTHOR# Author: #AUTHOR# Date: #DATE# 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: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif static __attribute__ ((unused)) const char rcsid[] = "$Id$"; #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #include #include #include "QF/cmd.h" #include "QF/console.h" #include "QF/dstring.h" #include "QF/hash.h" #include "QF/idparse.h" #include "QF/info.h" #include "QF/msg.h" #include "QF/qendian.h" #include "QF/sys.h" #include "QF/va.h" #include "qw/protocol.h" #include "connection.h" #include "qtv.h" #include "server.h" static hashtab_t *server_hash; static server_t *servers; static const char * server_get_key (void *sv, void *unused) { return ((server_t *) sv)->name; } static void server_free (void *_sv, void *unused) { server_t *sv = (server_t *) _sv; server_t **s; static byte final[] = {qtv_stringcmd, 'd', 'r', 'o', 'p', 0}; if (sv->connected) Netchan_Transmit (&sv->netchan, sizeof (final), final); Connection_Del (sv->con); Info_Destroy (sv->info); free ((char *) sv->name); free ((char *) sv->address); if (sv->gamedir) free ((char *) sv->gamedir); if (sv->message) free ((char *) sv->message); for (s = &servers; *s; s = &(*s)->next) { if (*s == sv) { *s = sv->next; break; } } free (sv); } static int server_compare (const void *a, const void *b) { return strcmp ((*(server_t **) a)->name, (*(server_t **) b)->name); } static void setup_sub_message (qmsg_t *msg, qmsg_t *sub, sizebuf_t *buf, int len) { memset (sub, 0, sizeof (qmsg_t)); memset (buf, 0, sizeof (sizebuf_t)); if (len > msg->message->cursize - msg->readcount) { len = msg->message->cursize - msg->readcount; msg->badread = 1; } sub->message = buf; buf->cursize = buf->maxsize = len; buf->data = msg->message->data + msg->readcount; msg->readcount += len; } static void setup_player_list (server_t *sv, unsigned mask) { int i; player_t **p, *cl; if (sv->player_mask == mask) return; sv->player_mask = mask; p = &sv->player_list; *p = 0; for (i = 0, cl = sv->players; i < MAX_SV_PLAYERS; i++, cl++) { if (mask & (1 << i)) { *p = cl; p = &(*p)->next; } } *p = 0; } static void qtv_parse_updates (server_t *sv, qmsg_t *msg, int reliable) { byte type; int msec; sizebuf_t sub_message_buf; qmsg_t sub_message; while (1) { msec = MSG_ReadByte (msg); //qtv_printf ("msec = %d\n", msec); if (msec == -1) break; type = MSG_ReadByte (msg); switch (type & 7) { case dem_cmd: case dem_set: qtv_printf ("qtv_parse_updates: unexpected dem: %d\n", type & 7); return; case dem_read: break; case dem_multiple: setup_player_list (sv, MSG_ReadLong (msg)); break; case dem_stats: case dem_single: setup_player_list (sv, 1 << (type >> 3)); break; case dem_all: setup_player_list (sv, ~0); break; } setup_sub_message (msg, &sub_message, &sub_message_buf, MSG_ReadLong (msg)); sv_parse (sv, &sub_message, reliable); } } static void qtv_packet_f (server_t *sv) { int len, type; int reliable = 0; sizebuf_t sub_message_buf; qmsg_t sub_message; len = MSG_ReadShort (net_message); type = len & 0xf000; len &= 0x0fff; setup_sub_message (net_message, &sub_message, &sub_message_buf, len); //qtv_printf ("qtv_packet: %x %d\n", type, len); switch (type) { case qtv_p_signon: setup_player_list (sv, ~0); sv_parse (sv, &sub_message, 1); break; case qtv_p_print: qtv_printf ("%s\n", MSG_ReadString (net_message)); break; case qtv_p_reliable: reliable = 1; case qtv_p_unreliable: qtv_parse_updates (sv, &sub_message, reliable); break; default: qtv_printf ("unknown packet type %x (%d bytes)\n", type, len); break; } } static void server_handler (connection_t *con, void *object) { server_t *sv = (server_t *) object; if (*(int *) net_message->message->data == -1) return; if (!Netchan_Process (&sv->netchan)) return; while (1) { int cmd; if (net_message->badread) { break; } cmd = MSG_ReadByte (net_message); if (cmd == -1) { net_message->readcount++; break; } //qtv_printf ("cmd: %d\n", cmd); switch (cmd) { default: qtv_printf ("Illegible server message: %d [%d]\n", cmd, net_message->readcount); while ((cmd = MSG_ReadByte (net_message)) != -1) { qtv_printf ("%02x (%c) ", cmd, cmd ? sys_char_map[cmd] : '#'); } qtv_printf ("\n"); goto bail; case qtv_disconnect: qtv_printf ("%s: disconnected\n", sv->name); break; case qtv_stringcmd: sv_stringcmd (sv, net_message); break; case qtv_packet: qtv_packet_f (sv); break; } } bail: return; } static inline const char * expect_packet (qmsg_t *msg, int type) { const char *str; int seq; MSG_BeginReading (net_message); seq = MSG_ReadLong (net_message); if (seq != -1) { qtv_printf ("unexpected connected packet\n"); return 0; } str = MSG_ReadString (net_message); if (str[0] == A2C_PRINT) { qtv_printf ("%s", str + 1); return 0; } if (str[0] != type) { qtv_printf ("unexpected connectionless packet type: %s\n", str); return 0; } return str; } static void server_connect (connection_t *con, void *object) { server_t *sv = (server_t *) object; sizebuf_t *msg = &sv->netchan.message; const char *str; if (!(str = expect_packet (net_message, S2C_CONNECTION))) return; qtv_printf ("connection from %s\n", sv->name); Netchan_Setup (&sv->netchan, con->address, sv->qport, NC_SEND_QPORT); sv->netchan.outgoing_sequence = 1; sv->connected = 1; MSG_WriteByte (msg, qtv_stringcmd); MSG_WriteString (msg, "new"); sv->next_run = realtime; con->handler = server_handler; } static void server_challenge (connection_t *con, void *object) { server_t *sv = (server_t *) object; const char *str, *qtv = 0; int challenge, i; dstring_t *data; if (!(str = expect_packet (net_message, S2C_CHALLENGE))) return; COM_TokenizeString (str + 1, qtv_args); cmd_args = qtv_args; challenge = atoi (Cmd_Argv (0)); for (i = 1; i < Cmd_Argc (); i++) { str = Cmd_Argv (i); if (!strcmp ("QF", str)) { qtv_printf ("QuakeForge server detected\n"); } else if (!strcmp ("qtv", str)) { qtv_printf ("QTV capable server\n"); qtv = str; } else { qtv_printf ("%s\n", str); } } if (!qtv) { qtv_printf ("%s can't handle qtv.\n", sv->name); Hash_Del (server_hash, sv->name); Hash_Free (server_hash, sv); return; } data = dstring_new (); dsprintf (data, "%c%c%c%cconnect %s %i %i \"%s\"\n", 255, 255, 255, 255, qtv, sv->qport, challenge, Info_MakeString (sv->info, 0)); Netchan_SendPacket (strlen (data->str), data->str, net_from); dstring_delete (data); con->handler = server_connect; } static void server_getchallenge (connection_t *con, server_t *sv) { static const char *getchallenge = "\377\377\377\377getchallenge\n"; Netchan_SendPacket (strlen (getchallenge), (void *) getchallenge, sv->adr); } static void sv_new_f (void) { const char *name; const char *address; server_t *sv; netadr_t adr; if (Cmd_Argc () < 2 || Cmd_Argc () > 3) { qtv_printf ("Usage: sv_new
\n"); return; } address = name = Cmd_Argv (1); if (Hash_Find (server_hash, name)) { qtv_printf ("sv_new: %s already exists\n", name); return; } if (Cmd_Argc () == 3) address = Cmd_Argv (2); if (!NET_StringToAdr (address, &adr)) { qtv_printf ("Bad server address\n"); return; } if (!adr.port) adr.port = BigShort (27500); sv = calloc (1, sizeof (server_t)); sv->next = servers; servers = sv; sv->name = strdup (name); sv->address = strdup (address); sv->adr = adr; sv->qport = qport->int_val; sv->info = Info_ParseString ("", MAX_INFO_STRING, 0); Info_SetValueForStarKey (sv->info, "*ver", va ("%s QTV %s", QW_VERSION, VERSION), 0); Info_SetValueForStarKey (sv->info, "*qsg_version", QW_QSG_VERSION, 0); Info_SetValueForKey (sv->info, "name", "QTV Proxy", 0); Hash_Add (server_hash, sv); sv->con = Connection_Add (&adr, sv, server_challenge); server_getchallenge (sv->con, sv); } static void sv_del_f (void) { const char *name; server_t *sv; if (Cmd_Argc () != 2) { qtv_printf ("Usage: sv_del \n"); return; } name = Cmd_Argv (1); if (!(sv = Hash_Del (server_hash, name))) { qtv_printf ("sv_new: %s unknown\n", name); return; } Hash_Free (server_hash, sv); } static void sv_list_f (void) { server_t **list, **l, *sv; int count; for (l = &servers, count = 0; *l; l = &(*l)->next) count++; if (!count) { qtv_printf ("no servers\n"); return; } list = malloc (count * sizeof (server_t **)); for (l = &servers, count = 0; *l; l = &(*l)->next) list[count] = *l; qsort (list, count, sizeof (*list), server_compare); for (l = list; *l; l++) { sv = *l; qtv_printf ("%-20s %s(%s)\n", sv->name, sv->address, NET_AdrToString (sv->adr)); } free (list); } static void server_shutdown (void) { Hash_FlushTable (server_hash); Hash_DelTable (server_hash); } static void server_run (server_t *sv) { static byte delta_msg[2] = {qtv_delta}; static byte nop_msg[1] = {qtv_nop}; if (sv->connected > 1) { int frame = (sv->netchan.outgoing_sequence) & UPDATE_MASK; sv->next_run = realtime + 0.03; sv->frames[frame].delta_sequence = sv->delta; if (sv->delta != -1) { delta_msg[1] = sv->delta; Netchan_Transmit (&sv->netchan, sizeof (delta_msg), delta_msg); return; } else if (!sv->netchan.message.cursize) { Netchan_Transmit (&sv->netchan, sizeof (nop_msg), nop_msg); return; } } Netchan_Transmit (&sv->netchan, 0, 0); } void Server_Init (void) { Sys_RegisterShutdown (server_shutdown); server_hash = Hash_NewTable (61, server_get_key, server_free, 0); Cmd_AddCommand ("sv_new", sv_new_f, "Add a new server"); Cmd_AddCommand ("sv_del", sv_del_f, "Remove an existing server"); Cmd_AddCommand ("sv_list", sv_list_f, "List available servers"); } void Server_Frame (void) { server_t *sv; for (sv = servers; sv; sv = sv->next) { if (sv->next_run && sv->next_run <= realtime) { sv->next_run = 0; server_run (sv); } } }