UFO: Alien Invasion
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
cl_irc.cpp
Go to the documentation of this file.
1 
6 /*
7 All original material Copyright (C) 2002-2020 UFO: Alien Invasion.
8 
9 Most of this stuff comes from Warsow
10 
11 This program is free software; you can redistribute it and/or
12 modify it under the terms of the GNU General Public License
13 as published by the Free Software Foundation; either version 2
14 of the License, or (at your option) any later version.
15 
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19 
20 See the GNU General Public License for more details.
21 
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 
26 */
27 
28 #include "cl_irc.h"
29 #include "client.h"
30 #include "cl_language.h"
31 #include "ui/ui_main.h"
32 #include "ui/ui_nodes.h"
33 #include "ui/ui_popup.h"
34 #include "battlescape/cl_hud.h"
35 #include "cgame/cl_game.h"
36 
37 #ifdef _WIN32
38 # include <winerror.h>
39 #else
40 # include <netinet/in.h>
41 # include <arpa/inet.h>
42 # include <netdb.h>
43 # include <fcntl.h>
44 #endif
45 
47 static cvar_t* irc_port;
49 static cvar_t* irc_nick;
50 static cvar_t* irc_user;
52 static cvar_t* irc_topic;
56 /* menu cvar */
59 
60 static bool irc_connected;
61 
62 #define IRC_SEND_BUF_SIZE 512
63 #define IRC_RECV_BUF_SIZE 1024
64 
65 typedef struct irc_user_s {
66  char key[MAX_VAR];
67  struct irc_user_s* next;
68 } irc_user_t;
69 
70 typedef struct irc_channel_s {
71  char name[MAX_VAR];
72  char topic[256];
73  int users;
76 
77 /* numeric commands as specified by RFC 1459 - Internet Relay Chat Protocol */
78 typedef enum irc_numeric_e {
79  /* command replies */
80  RPL_WELCOME = 1, /* ":Welcome to the Internet Relay Network <nick>!<user>@<host>" */
81  RPL_YOURHOST = 2, /* ":Your host is <servername>, running version <ver>" */
82  RPL_CREATED = 3, /* ":This server was created <date>" */
83  RPL_MYINFO = 4, /* "<servername> <version> <available user modes> <available channel modes>" */
84  RPL_ISUPPORT = 5, /* "<nick> <parameter> * :are supported by this server" */
85  RPL_HELLO = 20, /* ":Please wait while we process your connection" */
86  RPL_NONE = 300,
87  RPL_USERHOST = 302, /* ":[<reply>{<space><reply>}]" */
88  RPL_ISON = 303, /* ":[<nick> {<space><nick>}]" */
89  RPL_AWAY = 301, /* "<nick> :<away message>" */
90  RPL_UNAWAY = 305, /* ":You are no longer marked as being away" */
91  RPL_NOWAWAY = 306, /* ":You have been marked as being away" */
92  RPL_WHOISUSER = 311, /* "<nick> <user> <host> * :<real name>" */
93  RPL_WHOISSERVER = 312, /* "<nick> <server> :<server info>" */
94  RPL_WHOISOPERATOR = 313, /* "<nick> :is an IRC operator" */
95  RPL_WHOISIDLE = 317, /* "<nick> <integer> :seconds idle" */
96  RPL_ENDOFWHOIS = 318, /* "<nick> :End of /WHOIS list" */
97  RPL_WHOISCHANNELS = 319, /* "<nick> :{[@|+]<channel><space>}" */
98  RPL_WHOWASUSER = 314, /* "<nick> <user> <host> * :<real name>" */
99  RPL_ENDOFWHOWAS = 369, /* "<nick> :End of WHOWAS" */
100  RPL_WHOISACCOUNT = 330, /* "<nick> <account> :is logged in as" */
101 
102  RPL_LISTSTART = 321, /* "Channel :Users Name" */
103  RPL_LIST = 322, /* "<channel> <# visible> :<topic>" */
104  RPL_LISTEND = 323, /* ":End of /LIST" */
105  RPL_CHANNELMODEIS = 324, /* "<channel> <mode> <mode params>" */
106  RPL_NOTOPIC = 331, /* "<channel> :No topic is set" */
107  RPL_TOPIC = 332, /* "<channel> :<topic>" */
108  RPL_TOPICWHOTIME = 333, /* "<channel> <nick> <time>" */
109  RPL_INVITING = 341, /* "<channel> <nick>" */
110  RPL_SUMMONING = 342, /* "<user> :Summoning user to IRC" */
111  RPL_VERSION = 351, /* "<version>.<debuglevel> <server> :<comments>" */
112  RPL_WHOREPLY = 352, /* "<channel> <user> <host> <server> <nick> <H|G>[*][@|+] :<hopcount> <real name>" */
113  RPL_ENDOFWHO = 315, /* "<name> :End of /WHO list" */
114  RPL_NAMREPLY = 353, /* "<channel> :[[@|+]<nick> [[@|+]<nick> [...]]]" */
115  RPL_ENDOFNAMES = 366, /* "<channel> :End of /NAMES list" */
116  RPL_LINKS = 364, /* "<mask> <server> :<hopcount> <server info>" */
117  RPL_ENDOFLINKS = 365, /* "<mask> :End of /LINKS list" */
118  RPL_BANLIST = 367, /* "<channel> <banid>" */
119  RPL_ENDOFBANLIST = 368, /* "<channel> :End of channel ban list" */
120  RPL_INFO = 371, /* ":<string>" */
121  RPL_ENDOFINFO = 374, /* ":End of /INFO list" */
122  RPL_MOTDSTART = 375, /* ":- <server> Message of the day - " */
123  RPL_MOTD = 372, /* ":- <text>" */
124  RPL_ENDOFMOTD = 376, /* ":End of /MOTD command" */
125  RPL_YOUREOPER = 381, /* ":You are now an IRC operator" */
126  RPL_REHASHING = 382, /* "<config file> :Rehashing" */
127  RPL_TIME = 391, /* "<server> :<string showing server's local time>" */
128  RPL_USERSSTART = 392, /* ":UserID Terminal Host" */
129  RPL_USERS = 393, /* ":%-8s %-9s %-8s" */
130  RPL_ENDOFUSERS = 394, /* ":End of users" */
131  RPL_NOUSERS = 395, /* ":Nobody logged in" */
132  RPL_TRACELINK = 200, /* "Link <version & debug level> <destination> <next server>" */
133  RPL_TRACECONNECTING = 201, /* "Try. <class> <server>" */
134  RPL_TRACEHANDSHAKE = 202, /* "H.S. <class> <server>" */
135  RPL_TRACEUNKNOWN = 203, /* "???? <class> [<client IP address in dot form>]" */
136  RPL_TRACEOPERATOR = 204, /* "Oper <class> <nick>" */
137  RPL_TRACEUSER = 205, /* "User <class> <nick>" */
138  RPL_TRACESERVER = 206, /* "Serv <class> <int>S <int>C <server> <nick!user|*!*>@<host|server>" */
139  RPL_TRACENEWTYPE = 208, /* "<newtype> 0 <client name>" */
140  RPL_TRACELOG = 261, /* "File <logfile> <debug level>" */
141  RPL_STATSLINKINFO = 211, /* "<linkname> <sendq> <sent messages> <sent bytes> <received messages> <received bytes> <time open>" */
142  RPL_STATSCOMMANDS = 212, /* "<command> <count>" */
143  RPL_STATSCLINE = 213, /* "C <host> * <name> <port> <class>" */
144  RPL_STATSNLINE = 214, /* "N <host> * <name> <port> <class>" */
145  RPL_STATSILINE = 215, /* "I <host> * <host> <port> <class>" */
146  RPL_STATSKLINE = 216, /* "K <host> * <username> <port> <class>" */
147  RPL_STATSYLINE = 218, /* "Y <class> <ping frequency> <connectfrequency> <max sendq>" */
148  RPL_ENDOFSTATS = 219, /* "<stats letter> :End of /STATS report" */
149  RPL_STATSLLINE = 241, /* "L <hostmask> * <servername> <maxdepth>" */
150  RPL_STATSUPTIME = 242, /* ":Server Up %d days %d:%02d:%02d" */
151  RPL_STATSOLINE = 243, /* "O <hostmask> * <name>" */
152  RPL_STATSHLINE = 244, /* "H <hostmask> * <servername>" */
153  RPL_UMODEIS = 221, /* "<user mode string>" */
154  RPL_LUSERCLIENT = 251, /* ":There are <integer> users and <integer> invisible on <integer> servers" */
155  RPL_LUSEROP = 252, /* "<integer> :operator(s) online" */
156  RPL_LUSERUNKNOWN = 253, /* "<integer> :unknown connection(s)" */
157  RPL_LUSERCHANNELS = 254, /* "<integer> :channels formed" */
158  RPL_LUSERME = 255, /* ":I have <integer> clients and <integer> servers" */
159  RPL_ADMINME = 256, /* "<server> :Administrative info" */
160  RPL_ADMINLOC1 = 257, /* ":<admin info>" */
161  RPL_ADMINLOC2 = 258, /* ":<admin info>" */
162  RPL_ADMINEMAIL = 259, /* ":<admin info>" */
165 
166  /* error codes */
167  ERR_NOSUCHNICK = 401, /* "<nickname> :No such nick/channel" */
168  ERR_NOSUCHSERVER = 402, /* "<server name> :No such server" */
169  ERR_NOSUCHCHANNEL = 403, /* "<channel name> :No such channel" */
170  ERR_CANNOTSENDTOCHAN = 404, /* "<channel name> :Cannot send to channel" */
171  ERR_TOOMANYCHANNELS = 405, /* "<channel name> :You have joined too many channels" */
172  ERR_WASNOSUCHNICK = 406, /* "<nickname> :There was no such nickname" */
173  ERR_TOOMANYTARGETS = 407, /* "<target> :Duplicate recipients. No message delivered" */
174  ERR_NOORIGIN = 409, /* ":No origin specified" */
175  ERR_NORECIPIENT = 411, /* ":No recipient given (<command>)" */
176  ERR_NOTEXTTOSEND = 412, /* ":No text to send" */
177  ERR_NOTOPLEVEL = 413, /* "<mask> :No toplevel domain specified" */
178  ERR_WILDTOPLEVEL = 414, /* "<mask> :Wildcard in toplevel domain" */
179  ERR_UNKNOWNCOMMAND = 421, /* "<command> :Unknown command" */
180  ERR_NOMOTD = 422, /* ":MOTD File is missing" */
181  ERR_NOADMININFO = 423, /* "<server> :No administrative info available" */
182  ERR_FILEERROR = 424, /* ":File error doing <file op> on <file>" */
183  ERR_NONICKNAMEGIVEN = 431, /* ":No nickname given" */
184  ERR_ERRONEUSNICKNAME = 432, /* "<nick> :Erroneus nickname" */
185  ERR_NICKNAMEINUSE = 433, /* "<nick> :Nickname is already in use" */
186  ERR_NICKCOLLISION = 436, /* "<nick> :Nickname collision KILL" */
189  ERR_USERNOTINCHANNEL = 441, /* "<nick> <channel> :They aren't on that channel" */
190  ERR_NOTONCHANNEL = 442, /* "<channel> :You're not on that channel" */
191  ERR_USERONCHANNEL = 443, /* "<user> <channel> :is already on channel" */
192  ERR_NOLOGIN = 444, /* "<user> :User not logged in" */
193  ERR_SUMMONDISABLED = 445, /* ":SUMMON has been disabled" */
194  ERR_USERSDISABLED = 446, /* ":USERS has been disabled" */
195  ERR_NOTREGISTERED = 451, /* ":You have not registered" */
196  ERR_NEEDMOREPARAMS = 461, /* "<command> :Not enough parameters" */
197  ERR_ALREADYREGISTRED = 462, /* ":You may not reregister" */
198  ERR_NOPERMFORHOST = 463, /* ":Your host isn't among the privileged" */
199  ERR_PASSWDMISMATCH = 464, /* ":Password incorrect" */
200  ERR_YOUREBANNEDCREEP = 465, /* ":You are banned from this server" */
201  ERR_BADNAME = 468, /* ":Your username is invalid" */
202  ERR_KEYSET = 467, /* "<channel> :Channel key already set" */
203  ERR_CHANNELISFULL = 471, /* "<channel> :Cannot join channel (+l)" */
204  ERR_UNKNOWNMODE = 472, /* "<char> :is unknown mode char to me" */
205  ERR_INVITEONLYCHAN = 473, /* "<channel> :Cannot join channel (+i)" */
206  ERR_BANNEDFROMCHAN = 474, /* "<channel> :Cannot join channel (+b)" */
207  ERR_BADCHANNELKEY = 475, /* "<channel> :Cannot join channel (+k)" */
208  ERR_NOPRIVILEGES = 481, /* ":Permission Denied- You're not an IRC operator" */
209  ERR_CHANOPRIVSNEEDED = 482, /* "<channel> :You're not channel operator" */
210  ERR_CANTKILLSERVER = 483, /* ":You cant kill a server!" */
211  ERR_NOOPERHOST = 491, /* ":No O-lines for your host" */
212  ERR_UMODEUNKNOWNFLAG = 501, /* ":Unknown MODE flag" */
213  ERR_USERSDONTMATCH = 502, /* ":Cant change mode for other users" */
218 /* ERR_TOOMANYWATCH = 512,*/
219  ERR_BADPING = 513,
224 } irc_numeric_t;
225 
226 typedef enum irc_command_type_e {
230 
231 typedef enum irc_nick_prefix_e {
236 
237 /* commands can be numeric's or string */
238 typedef struct irc_command_s {
239  union {
240  /* tagged union */
242  const char* string;
243  } id;
245 } irc_command_t;
246 
247 /* server <- client messages */
248 typedef struct irc_server_msg_s {
249  union {
250  char string[IRC_SEND_BUF_SIZE];
252  } id;
258 
259 static struct net_stream* irc_stream;
260 
261 static const char IRC_INVITE_FOR_A_GAME[] = "UFOAIINVITE;";
262 
265 
266 static char irc_buffer[4096];
267 
268 static void Irc_Logic_RemoveChannelName(irc_channel_t* channel, const char* nick);
269 static void Irc_Logic_AddChannelName(irc_channel_t* channel, irc_nick_prefix_t prefix, const char* nick);
270 static void Irc_Client_Names_f(void);
271 static bool Irc_Client_Join(const char* channel, const char* password);
272 static void Irc_Logic_Disconnect(const char* reason);
273 
274 static bool Irc_AppendToBuffer(const char* const msg, ...) __attribute__((format(__printf__, 1, 2)));
275 static bool Irc_Proto_ParseServerMsg(const char* txt, size_t txt_len, irc_server_msg_t* msg);
276 static bool Irc_Proto_Enqueue(const char* msg, size_t msg_len);
277 
278 static bool Irc_Net_Connect(const char* host, const char* port);
279 static bool Irc_Net_Disconnect(void);
280 static void Irc_Net_Send(const char* msg, size_t msg_len);
281 
282 static void Irc_Connect_f(void);
283 static void Irc_Disconnect_f(void);
284 static void Irc_Input_Deactivate_f(void);
285 
286 /*
287 ===============================================================
288 Common functions
289 ===============================================================
290 */
291 
292 static inline bool Irc_IsChannel (const char* target)
293 {
294  assert(target);
295  return (target[0] == '#' || target[0] == '&');
296 }
297 
298 static void Irc_ParseName (const char* mask, char* nick, size_t size, irc_nick_prefix_t* prefix)
299 {
300  if (mask[0] == IRC_NICK_PREFIX_OP || mask[0] == IRC_NICK_PREFIX_VOICE) {
301  *prefix = (irc_nick_prefix_t) *mask; /* read prefix */
302  ++mask; /* crop prefix from mask */
303  } else {
304  *prefix = IRC_NICK_PREFIX_NONE;
305  }
306  const char* emph = strchr(mask, '!');
307  if (emph) {
308  size_t length = emph - mask;
309  if (length >= size - 1)
310  length = size - 1;
311  /* complete hostmask, crop anything after ! */
312  memcpy(nick, mask, length);
313  nick[length] = '\0';
314  } else {
315  /* just the nickname, use as is */
316  Q_strncpyz(nick, mask, size);
317  }
318 }
319 
320 /*
321 ===============================================================
322 Protocol functions
323 ===============================================================
324 */
325 
331 
332 typedef struct irc_bucket_message_s {
333  char* msg;
334  size_t msg_len;
337 
338 typedef struct irc_bucket_s {
340  unsigned int message_size;
341  unsigned int character_size;
344 } irc_bucket_t;
345 
347 
351 static bool Irc_Proto_Connect (const char* host, const char* port)
352 {
353  const bool status = Irc_Net_Connect(host, port);
354  if (!status) {
355  irc_bucket.first_msg = nullptr;
356  irc_bucket.message_size = 0;
357  irc_bucket.character_size = 0;
358  irc_bucket.last_refill = CL_Milliseconds();
359  irc_bucket.character_token = (double)irc_characterBucketBurst->value;
360  }
361  return status;
362 }
363 
367 static bool Irc_Proto_Disconnect (void)
368 {
369  const bool status = Irc_Net_Disconnect();
370  if (!status) {
371  irc_bucket_message_t* msg = irc_bucket.first_msg;
372  while (msg) {
373  irc_bucket_message_t* prev = msg;
374  msg = msg->next;
375  Mem_Free(prev->msg);
376  Mem_Free(prev);
377  }
378  irc_bucket.first_msg = nullptr;
379  irc_bucket.message_size = 0;
380  irc_bucket.character_size = 0;
381  }
382  return status;
383 }
384 
388 static bool Irc_Proto_Quit (const char* quitmsg)
389 {
390  char msg[IRC_SEND_BUF_SIZE];
391  const int msg_len = snprintf(msg, sizeof(msg) - 1, "QUIT %s\r\n", quitmsg);
392  msg[sizeof(msg) - 1] = '\0';
393  Irc_Net_Send(msg, msg_len); /* send immediately */
394  return false;
395 }
396 
400 static bool Irc_Proto_Nick (const char* nick)
401 {
402  char msg[IRC_SEND_BUF_SIZE];
403  const int msg_len = snprintf(msg, sizeof(msg) - 1, "NICK %s\r\n", nick);
404  msg[sizeof(msg) - 1] = '\0';
405  return Irc_Proto_Enqueue(msg, msg_len);
406 }
407 
411 static bool Irc_Proto_User (const char* user, bool invisible, const char* name)
412 {
413  char msg[IRC_SEND_BUF_SIZE];
414  const int msg_len = snprintf(msg, sizeof(msg) - 1, "USER %s %c * :%s\r\n", user, invisible ? '8' : '0', name);
415  msg[sizeof(msg) - 1] = '\0';
416  return Irc_Proto_Enqueue(msg, msg_len);
417 }
418 
422 static bool Irc_Proto_Password (const char* password)
423 {
424  char msg[IRC_SEND_BUF_SIZE];
425  const int msg_len = snprintf(msg, sizeof(msg) - 1, "PASS %s\r\n", password);
426  msg[sizeof(msg) - 1] = '\0';
427  return Irc_Proto_Enqueue(msg, msg_len);
428 }
429 
433 static bool Irc_Proto_Join (const char* channel, const char* password)
434 {
435  char msg[IRC_SEND_BUF_SIZE];
436  const int msg_len = password
437  ? snprintf(msg, sizeof(msg) - 1, "JOIN %s %s\r\n", channel, password)
438  : snprintf(msg, sizeof(msg) - 1, "JOIN %s\r\n", channel);
439  msg[sizeof(msg) - 1] = '\0';
440 
441  /* only one channel allowed */
442  if (chan) {
443  Com_Printf("Already in a channel\n");
444  return false;
445  }
446 
447  chan = &ircChan;
448  OBJZERO(*chan);
449  Q_strncpyz(chan->name, channel, sizeof(chan->name));
450  return Irc_Proto_Enqueue(msg, msg_len);
451 }
452 
456 static bool Irc_Proto_Part (const char* channel)
457 {
458  char msg[IRC_SEND_BUF_SIZE];
459  const int msg_len = snprintf(msg, sizeof(msg) - 1, "PART %s\r\n", channel);
460  msg[sizeof(msg) - 1] = '\0';
461  return Irc_Proto_Enqueue(msg, msg_len);
462 }
463 
467 static bool Irc_Proto_Mode (const char* target, const char* modes, const char* params)
468 {
469  char msg[IRC_SEND_BUF_SIZE];
470  const int msg_len = params
471  ? snprintf(msg, sizeof(msg) - 1, "MODE %s %s %s\r\n", target, modes, params)
472  : snprintf(msg, sizeof(msg) - 1, "MODE %s %s\r\n", target, modes);
473  msg[sizeof(msg) - 1] = '\0';
474  return Irc_Proto_Enqueue(msg, msg_len);
475 }
476 
480 static bool Irc_Proto_Topic (const char* channel, const char* topic)
481 {
482  char msg[IRC_SEND_BUF_SIZE];
483  const int msg_len = topic
484  ? snprintf(msg, sizeof(msg) - 1, "TOPIC %s :%s\r\n", channel, topic)
485  : snprintf(msg, sizeof(msg) - 1, "TOPIC %s\r\n", channel);
486  msg[sizeof(msg) - 1] = '\0';
487  return Irc_Proto_Enqueue(msg, msg_len);
488 }
489 
495 static bool Irc_Proto_Msg (const char* target, const char* text)
496 {
497  if (*text == '/') {
498  Com_DPrintf(DEBUG_CLIENT, "Don't send irc commands as PRIVMSG\n");
499  Cbuf_AddText("%s\n", &text[1]);
500  return true;
501  } else {
502  char msg[IRC_SEND_BUF_SIZE];
503  const int msg_len = snprintf(msg, sizeof(msg) - 1, "PRIVMSG %s :%s\r\n", target, text);
504  msg[sizeof(msg) - 1] = '\0';
505  return Irc_Proto_Enqueue(msg, msg_len);
506  }
507 }
508 
512 static bool Irc_Proto_Notice (const char* target, const char* text)
513 {
514  char msg[IRC_SEND_BUF_SIZE];
515  const int msg_len = snprintf(msg, sizeof(msg) - 1, "NOTICE %s :%s\r\n", target, text);
516  msg[sizeof(msg) - 1] = '\0';
517  return Irc_Proto_Enqueue(msg, msg_len);
518 }
519 
523 static void Irc_Proto_Pong (const char* nick, const char* server, const char* cookie)
524 {
525  char msg[IRC_SEND_BUF_SIZE];
526  const int msg_len = cookie
527  ? snprintf(msg, sizeof(msg) - 1, "PONG %s %s :%s\r\n", nick, server, cookie)
528  : snprintf(msg, sizeof(msg) - 1, "PONG %s %s\r\n", nick, server);
529  msg[sizeof(msg) - 1] = '\0';
530  Irc_Net_Send(msg, msg_len); /* send immediately */
531 }
532 
536 static bool Irc_Proto_Kick (const char* channel, const char* nick, const char* reason)
537 {
538  char msg[IRC_SEND_BUF_SIZE];
539  const int msg_len = reason
540  ? snprintf(msg, sizeof(msg) - 1, "KICK %s %s :%s\r\n", channel, nick, reason)
541  : snprintf(msg, sizeof(msg) - 1, "KICK %s %s :%s\r\n", channel, nick, nick);
542  msg[sizeof(msg) - 1] = '\0';
543  return Irc_Proto_Enqueue(msg, msg_len);
544 }
545 
549 static bool Irc_Proto_Who (const char* nick)
550 {
551  char msg[IRC_SEND_BUF_SIZE];
552  const int msg_len = snprintf(msg, sizeof(msg) - 1, "WHO %s\r\n", nick);
553  msg[sizeof(msg) - 1] = '\0';
554  return Irc_Proto_Enqueue(msg, msg_len);
555 }
556 
560 static bool Irc_Proto_Whois (const char* nick)
561 {
562  char msg[IRC_SEND_BUF_SIZE];
563  const int msg_len = snprintf(msg, sizeof(msg) - 1, "WHOIS %s\r\n", nick);
564  msg[sizeof(msg) - 1] = '\0';
565  return Irc_Proto_Enqueue(msg, msg_len);
566 }
567 
571 static bool Irc_Proto_Whowas (const char* nick)
572 {
573  char msg[IRC_SEND_BUF_SIZE];
574  const int msg_len = snprintf(msg, sizeof(msg) - 1, "WHOWAS %s\r\n", nick);
575  msg[sizeof(msg) - 1] = '\0';
576  return Irc_Proto_Enqueue(msg, msg_len);
577 }
578 
582 static bool Irc_Proto_PollServerMsg (irc_server_msg_t* msg, bool* msg_complete)
583 {
584  static char buf[IRC_RECV_BUF_SIZE];
585  static char* last = buf;
586  *msg_complete = false;
587  /* recv packet */
588  const int recvd = NET_StreamDequeue(irc_stream, last, sizeof(buf) - (last - buf) - 1);
589  if (recvd < 0)
590  return true;
591 
592  /* terminate buf string */
593  const char* const begin = buf;
594  last += recvd;
595  *last = '\0';
596  if (last != begin) {
597  /* buffer not empty; */
598  const char* const end = strstr(begin, "\r\n");
599  if (end) {
600  /* complete command in buffer, parse */
601  const size_t cmd_len = end + 2 - begin;
602  if (!Irc_Proto_ParseServerMsg(begin, cmd_len, msg)) {
603  /* parsing successful */
604  /* move succeeding commands to begin of buffer */
605  memmove(buf, end + 2, sizeof(buf) - cmd_len);
606  last -= cmd_len;
607  *msg_complete = true;
608  } else {
609  /* parsing failure, fatal */
610  Com_Printf("Received invalid packet from server\n");
611  return true;
612  }
613  }
614  } else {
615  *msg_complete = false;
616  }
617  return false;
618 }
619 
626 static bool Irc_AppendToBuffer (const char* const msg, ...)
627 {
628  va_list ap;
629  char appendString[2048];
630 
631  va_start(ap, msg);
632  Q_vsnprintf(appendString, sizeof(appendString), msg, ap);
633  va_end(ap);
634 
635  while (strlen(irc_buffer) + strlen(appendString) + 1 >= sizeof(irc_buffer)) {
636  const char* n;
637  if (!(n = strchr(irc_buffer, '\n'))) {
638  irc_buffer[0] = '\0';
639  break;
640  }
641  memmove(irc_buffer, n + 1, strlen(n));
642  }
643 
644  Q_strcat(irc_buffer, sizeof(irc_buffer), "%s\n", appendString);
645  if (irc_logConsole->integer) {
646  char appendStringCut[1000];
647  Q_strncpyz(appendStringCut, appendString, lengthof(appendStringCut));
648  Com_Printf("IRC: %s\n", appendStringCut);
649  }
650 
652  UI_TextScrollEnd("irc.chat.mainBody.irc_data");
653 
654  if (irc_showIfNotInMenu->integer && !Q_streq(UI_GetActiveWindowName(), "irc")) {
656  GAME_AddChatMessage(appendString);
657  return true;
658  }
659  return false;
660 }
661 
662 static void Irc_Client_CmdRplWhowasuser (const char* params, const char* trailing)
663 {
664  char buf[IRC_SEND_BUF_SIZE];
665  const char* nick = "", *user = "", *host = "", *real_name = trailing;
666  unsigned int i = 0;
667 
668  /* parse params "<nick> <user> <host> * :<real name>" */
669  Q_strncpyz(buf, params, sizeof(buf));
670  for (char* p = strtok(buf, " "); p; p = strtok(nullptr, " "), ++i) {
671  switch (i) {
672  case 1:
673  nick = p;
674  break;
675  case 2:
676  user = p;
677  break;
678  case 3:
679  host = p;
680  break;
681  }
682  }
683  Irc_AppendToBuffer("^B%s was %s@%s : %s", nick, user, host, real_name);
684 }
685 
686 static inline void Irc_Client_CmdTopic (const char* prefix, const char* trailing)
687 {
688  Cvar_ForceSet("irc_topic", trailing);
689 }
690 
691 static void Irc_Client_CmdRplTopic (const char* params, const char* trailing)
692 {
693  const char* channel = strchr(params, ' ');
694  if (channel) {
695  ++channel;
696  Irc_Client_CmdTopic(params, trailing);
697  }
698 }
699 
700 static void Irc_Client_CmdRplWhoisuser (const char* params, const char* trailing)
701 {
702  char buf[IRC_SEND_BUF_SIZE];
703  const char* nick = "", *user = "", *host = "", *real_name = trailing;
704  unsigned int i = 0;
705 
706  /* parse params "<nick> <user> <host> * :<real name>" */
707  Q_strncpyz(buf, params, IRC_SEND_BUF_SIZE);
708  for (char* p = strtok(buf, " "); p; p = strtok(nullptr, " "), ++i) {
709  switch (i) {
710  case 1:
711  nick = p;
712  break;
713  case 2:
714  user = p;
715  break;
716  case 3:
717  host = p;
718  break;
719  }
720  }
721  Irc_AppendToBuffer("^B%s is %s@%s : %s", nick, user, host, real_name);
722 }
723 
724 static void Irc_Client_CmdRplWhoisserver (const char* params, const char* trailing)
725 {
726  char buf[IRC_SEND_BUF_SIZE];
727  const char* nick = "", *server = "", *server_info = trailing;
728  unsigned int i = 0;
729 
730  /* parse params "<nick> <server> :<server info>" */
731  Q_strncpyz(buf, params, IRC_SEND_BUF_SIZE);
732  for (char* p = strtok(buf, " "); p; p = strtok(nullptr, " "), ++i) {
733  switch (i) {
734  case 1:
735  nick = p;
736  break;
737  case 2:
738  server = p;
739  break;
740  }
741  }
742  Irc_AppendToBuffer("^B%s using %s : %s", nick, server, server_info);
743 }
744 
745 static void Irc_Client_CmdRplWhoisaccount (const char* params, const char* trailing)
746 {
747  char buf[IRC_SEND_BUF_SIZE];
748  const char* nick = "", *account = "";
749  unsigned int i = 0;
750 
751  /* parse params "<nick> <account> :is logged in as" */
752  Q_strncpyz(buf, params, IRC_SEND_BUF_SIZE);
753  for (char* p = strtok(buf, " "); p; p = strtok(nullptr, " "), ++i) {
754  switch (i) {
755  case 1:
756  nick = p;
757  break;
758  case 2:
759  account = p;
760  break;
761  }
762  }
763  Irc_AppendToBuffer("^B%s %s %s", nick, trailing, account);
764 }
765 
766 static void Irc_Client_CmdRplWhoisidle (const char* params, const char* trailing)
767 {
768  char buf[IRC_SEND_BUF_SIZE];
769  const char* nick = "", *idle = "";
770  unsigned int i = 0;
771 
772  /* parse params "<nick> <integer> :seconds idle" */
773  Q_strncpyz(buf, params, IRC_SEND_BUF_SIZE);
774  for (char* p = strtok(buf, " "); p; p = strtok(nullptr, " "), ++i) {
775  switch (i) {
776  case 1:
777  nick = p;
778  break;
779  case 2:
780  idle = p;
781  break;
782  }
783  }
784  Irc_AppendToBuffer("^B%s is %s %s", nick, idle, trailing);
785 }
786 
787 static void Irc_Client_CmdRplWhoreply (const char* params, const char* trailing)
788 {
789  char buf[IRC_SEND_BUF_SIZE];
790  const char* channel = "", *user = "", *host = "", *server = "", *nick = "", *hg = "";
791  unsigned int i = 0;
792 
793  /* parse params "<channel> <user> <host> <server> <nick> <H|G>[*][@|+] :<hopcount> <real name>" */
794  Q_strncpyz(buf, params, IRC_SEND_BUF_SIZE);
795  for (char* p = strtok(buf, " "); p; p = strtok(nullptr, " "), ++i) {
796  switch (i) {
797  case 0:
798  channel = p;
799  break;
800  case 1:
801  user = p;
802  break;
803  case 2:
804  host = p;
805  break;
806  case 3:
807  server = p;
808  break;
809  case 4:
810  nick = p;
811  break;
812  case 5:
813  hg = p;
814  break;
815  }
816  }
817  Irc_AppendToBuffer("%s %s %s %s %s %s : %s", channel, user, host, server, nick, hg, trailing);
818 }
819 
820 static void Irc_Client_CmdMode (const char* prefix, const char* params, const char* trailing)
821 {
822  char nick[MAX_VAR];
824  Irc_ParseName(prefix, nick, sizeof(nick), &p);
825  Irc_AppendToBuffer("^B%s sets mode %s", nick, params);
826 }
827 
828 static void Irc_Client_CmdJoin (const char* prefix, const char* params, const char* trailing)
829 {
830  char nick[MAX_VAR];
832  Irc_ParseName(prefix, nick, sizeof(nick), &p);
833  Irc_AppendToBuffer("^BJoined: %s", nick);
834  Irc_Logic_AddChannelName(chan, p, nick);
835 }
836 
837 static void Irc_Client_CmdPart (const char* prefix, const char* trailing)
838 {
839  char nick[MAX_VAR];
841  Irc_ParseName(prefix, nick, sizeof(nick), &p);
842  Irc_AppendToBuffer("^BLeft: %s (%s)", nick, prefix);
843  Irc_Logic_RemoveChannelName(chan, nick);
844 }
845 
846 static void Irc_Client_CmdQuit (const char* prefix, const char* params, const char* trailing)
847 {
848  char nick[MAX_VAR];
850  Irc_ParseName(prefix, nick, sizeof(nick), &p);
851  Irc_AppendToBuffer("^BQuits: %s (%s)", nick, trailing);
852  Irc_Logic_RemoveChannelName(chan, nick);
853 }
854 
855 static void Irc_Client_CmdKill (const char* prefix, const char* params, const char* trailing)
856 {
857  char nick[MAX_VAR];
859  Irc_ParseName(prefix, nick, sizeof(nick), &p);
860  Irc_AppendToBuffer("^BKilled: %s (%s)", nick, trailing);
861  Irc_Logic_RemoveChannelName(chan, nick);
862 }
863 
864 static void Irc_Client_CmdKick (const char* prefix, const char* params, const char* trailing)
865 {
866  char buf[IRC_SEND_BUF_SIZE];
867  char nick[MAX_VAR];
869  Irc_ParseName(prefix, nick, sizeof(nick), &p);
870  Q_strncpyz(buf, params, IRC_SEND_BUF_SIZE);
871  const char* channel = strtok(buf, " ");
872  const char* victim = strtok(nullptr, " ");
873  if (Q_streq(victim, irc_nick->string)) {
874  /* we have been kicked */
875  Irc_AppendToBuffer("^BYou were kicked from %s by %s (%s)", channel, nick, trailing);
876  } else {
877  /* someone else was kicked */
878  Irc_AppendToBuffer("^B%s kicked %s (%s)", nick, victim, trailing);
879  }
880  Irc_Logic_RemoveChannelName(chan, nick);
881 }
882 
886 static void Irc_Client_CmdNick (const char* prefix, const char* params, const char* trailing)
887 {
888  char nick[MAX_VAR];
890 
891  /* not connected */
892  if (!chan)
893  return;
894 
895  Irc_ParseName(prefix, nick, sizeof(nick), &p);
896  if (Q_streq(irc_nick->string, nick))
897  Cvar_ForceSet("cl_name", trailing);
898  Irc_AppendToBuffer("%s is now known as %s", nick, trailing);
899  Irc_Logic_RemoveChannelName(chan, nick);
900  Irc_Logic_AddChannelName(chan, p, trailing);
901 }
902 
903 #define IRC_CTCP_MARKER_CHR '\001'
904 #define IRC_CTCP_MARKER_STR "\001"
905 
909 static void Irc_Client_CmdPrivmsg (const char* prefix, const char* params, const char* trailing)
910 {
911  char nick[MAX_VAR];
912  const char* const emph = strchr(prefix, '!');
913  const char* ctcp = strchr(trailing, IRC_CTCP_MARKER_CHR);
914  OBJZERO(nick);
915  if (emph)
916  memcpy(nick, prefix, emph - prefix);
917  else
918  Q_strncpyz(nick, prefix, MAX_VAR);
919 
920  if (ctcp) {
921  if (Q_streq(trailing + 1, "VERSION" IRC_CTCP_MARKER_STR)) {
922  /* response with the game version */
923  Irc_Proto_Msg(irc_defaultChannel->string, Cvar_GetString("version"));
924  /*Irc_Proto_Notice(nick, IRC_CTCP_MARKER_STR "VERSION " UFO_VERSION " " CPUSTRING " " __DATE__ " " BUILDSTRING);*/
925  Com_DPrintf(DEBUG_CLIENT, "Irc_Client_CmdPrivmsg: Response to version query\n");
926  } else if (!strncmp(trailing + 1, "PING", 4)) {
927  /* Used to measure the delay of the IRC network between clients. */
928  char response[IRC_SEND_BUF_SIZE];
929  strcpy(response, trailing);
930  response[2] = 'O'; /* PING => PONG */
931  Irc_Proto_Notice(nick, response);
932  } else if (Q_streq(trailing + 1, "TIME" IRC_CTCP_MARKER_STR)) {
933  const time_t t = time(nullptr);
934  char response[IRC_SEND_BUF_SIZE];
935  const size_t response_len = sprintf(response, IRC_CTCP_MARKER_STR "TIME :%s" IRC_CTCP_MARKER_STR, ctime(&t));
936  response[response_len - 1] = '\0'; /* remove trailing \n */
937  Irc_Proto_Notice(nick, response);
938  } else {
939  Com_Printf("Irc_Client_CmdPrivmsg: Unknown ctcp command: '%s'\n", trailing);
940  }
941  } else {
942  if (!strncmp(trailing, IRC_INVITE_FOR_A_GAME, strlen(IRC_INVITE_FOR_A_GAME))) {
943  char serverIPAndPort[128];
944  Q_strncpyz(serverIPAndPort, trailing + strlen(IRC_INVITE_FOR_A_GAME), sizeof(serverIPAndPort));
945  /* values are splitted by ; */
946  char* port = strstr(serverIPAndPort, ";");
947  if (port == nullptr) {
948  Com_DPrintf(DEBUG_CLIENT, "Invalid irc invite message received\n");
949  return;
950  }
951 
952  /* split ip and port */
953  *port++ = '\0';
954 
955  /* the version is optional */
956  char* version = strstr(port, ";");
957  if (version != nullptr) {
958  /* split port and version */
959  *version++ = '\0';
960  if (!Q_streq(version, UFO_VERSION)) {
961  Com_DPrintf(DEBUG_CLIENT, "irc invite message from different game version received: %s (versus our version: " UFO_VERSION ")\n",
962  version);
963  return;
964  }
965  }
966 
968  UI_ExecuteConfunc("multiplayer_invite_server_info %s %s", serverIPAndPort, port);
969 
970  UI_PushWindow("multiplayer_invite");
971  } else if (!Irc_AppendToBuffer("<%s> %s", nick, trailing)) {
972  /* check whether this is no message to the channel - but to the user */
973  if (params && !Q_streq(params, irc_defaultChannel->string)) {
974  S_StartLocalSample("misc/lobbyprivmsg", SND_VOLUME_DEFAULT);
975  GAME_AddChatMessage(va("<%s> %s\n", nick, trailing));
976  } else if (strstr(trailing, irc_nick->string)) {
977  S_StartLocalSample("misc/lobbyprivmsg", SND_VOLUME_DEFAULT);
978  GAME_AddChatMessage(va("<%s> %s\n", nick, trailing));
979  }
980  }
981 
982  if (UI_GetActiveWindow() && !Q_streq(UI_GetActiveWindowName(), "irc")) {
983  Com_Printf(S_COLOR_GREEN "<%s@lobby> %s\n", nick, trailing);
984  }
985  }
986 }
987 
988 static void Irc_Client_CmdRplNamreply (const char* params, const char* trailing)
989 {
990  char* space;
991  char nick[MAX_VAR];
992  const size_t len = strlen(trailing) + 1;
994 
995  if (!chan)
996  return;
997 
998  char* const parseBuf = Mem_PoolAllocTypeN(char, len, cl_ircSysPool);
999  if (!parseBuf)
1000  return;
1001 
1002  Q_strncpyz(parseBuf, trailing, len);
1003  char* pos = parseBuf;
1004 
1005  do {
1006  /* names are space separated */
1007  space = strstr(pos, " ");
1008  if (space)
1009  *space = '\0';
1010  Irc_ParseName(pos, nick, sizeof(nick), &p);
1011  Irc_Logic_AddChannelName(chan, p, nick);
1012  if (space)
1013  pos = space + 1;
1014  } while (space && *pos);
1015 
1016  Mem_Free(parseBuf);
1017 }
1018 
1022 static void Irc_Client_CmdRplEndofnames (const char* params, const char* trailing)
1023 {
1024 }
1025 
1030 {
1031  irc_command_t cmd;
1032  const char* p = nullptr;
1033  cmd.type = msg->type;
1034 
1037  switch (cmd.type) {
1038  case IRC_COMMAND_NUMERIC:
1039  cmd.id.numeric = msg->id.numeric;
1040  switch (cmd.id.numeric) {
1041  case RPL_HELLO:
1042  case RPL_WELCOME:
1043  case RPL_YOURHOST:
1044  case RPL_CREATED:
1045  case RPL_MYINFO:
1046  case RPL_MOTDSTART:
1047  case RPL_MOTD:
1048  case RPL_LOCALUSERS:
1049  case RPL_GLOBALUSERS:
1050  case RPL_ISUPPORT:
1051  case RPL_LUSEROP:
1052  case RPL_LUSERUNKNOWN:
1053  case RPL_LUSERCHANNELS:
1054  case RPL_LUSERCLIENT:
1055  case RPL_LUSERME:
1056  return true;
1057 
1058  /* read our own motd */
1059  case RPL_ENDOFMOTD:
1060  Irc_AppendToBuffer("%s", CL_Translate("*msgid:irc_motd"));
1061  return true;
1062  case RPL_NAMREPLY:
1064  return true;
1065  case RPL_ENDOFNAMES:
1067  return true;
1068  case RPL_TOPIC:
1070  return true;
1071  case RPL_NOTOPIC:
1072  return true;
1073  case RPL_WHOISUSER:
1075  return true;
1076  case RPL_WHOISSERVER:
1078  return true;
1079  case RPL_WHOISIDLE:
1081  return true;
1082  case RPL_WHOISACCOUNT:
1084  return true;
1085  case RPL_WHOREPLY:
1087  return true;
1088  case RPL_WHOWASUSER:
1090  return true;
1091  case RPL_ENDOFWHO:
1092  case RPL_WHOISCHANNELS:
1093  case RPL_WHOISOPERATOR:
1094  case RPL_ENDOFWHOIS:
1095  case RPL_ENDOFWHOWAS:
1096  p = strchr(msg->params, ' ');
1097  if (p) {
1098  ++p;
1099  Irc_AppendToBuffer("%s %s", p, msg->trailing);
1100  }
1101  return true;
1102 
1103  case ERR_NICKNAMEINUSE:
1104  case ERR_NOSUCHNICK:
1105  case ERR_NONICKNAMEGIVEN:
1106  case ERR_ERRONEUSNICKNAME:
1107  case ERR_NICKCOLLISION:
1108  Irc_AppendToBuffer("%s : %s", msg->params, msg->trailing);
1109  UI_PushWindow("irc_changename");
1110  break;
1111  /* error codes */
1112  case ERR_NOSUCHSERVER:
1113  case ERR_NOSUCHCHANNEL:
1114  case ERR_CANNOTSENDTOCHAN:
1115  case ERR_TOOMANYCHANNELS:
1116  case ERR_WASNOSUCHNICK:
1117  case ERR_TOOMANYTARGETS:
1118  case ERR_NOORIGIN:
1119  case ERR_NORECIPIENT:
1120  case ERR_NOTEXTTOSEND:
1121  case ERR_NOTOPLEVEL:
1122  case ERR_WILDTOPLEVEL:
1123  case ERR_UNKNOWNCOMMAND:
1124  case ERR_NOMOTD:
1125  case ERR_NOADMININFO:
1126  case ERR_FILEERROR:
1127  case ERR_BANNICKCHANGE:
1128  case ERR_NCHANGETOOFAST:
1129  case ERR_USERNOTINCHANNEL:
1130  case ERR_NOTONCHANNEL:
1131  case ERR_USERONCHANNEL:
1132  case ERR_NOLOGIN:
1133  case ERR_SUMMONDISABLED:
1134  case ERR_USERSDISABLED:
1135  case ERR_NOTREGISTERED:
1136  case ERR_NEEDMOREPARAMS:
1137  case ERR_ALREADYREGISTRED:
1138  case ERR_NOPERMFORHOST:
1139  case ERR_PASSWDMISMATCH:
1140  case ERR_YOUREBANNEDCREEP:
1141  case ERR_BADNAME:
1142  case ERR_KEYSET:
1143  case ERR_CHANNELISFULL:
1144  case ERR_UNKNOWNMODE:
1145  case ERR_INVITEONLYCHAN:
1146  case ERR_BANNEDFROMCHAN:
1147  case ERR_BADCHANNELKEY:
1148  case ERR_NOPRIVILEGES:
1149  case ERR_CHANOPRIVSNEEDED:
1150  case ERR_CANTKILLSERVER:
1151  case ERR_NOOPERHOST:
1152  case ERR_UMODEUNKNOWNFLAG:
1153  case ERR_USERSDONTMATCH:
1154  case ERR_GHOSTEDCLIENT:
1155  case ERR_LAST_ERR_MSG:
1156  case ERR_SILELISTFULL:
1157  case ERR_NOSUCHGLINE:
1158  case ERR_BADPING:
1159  case ERR_TOOMANYDCC:
1160  case ERR_LISTSYNTAX:
1161  case ERR_WHOSYNTAX:
1162  case ERR_WHOLIMEXCEED:
1163  Irc_AppendToBuffer("%s : %s", msg->params, msg->trailing);
1164  return true;
1165  default:
1166  Com_DPrintf(DEBUG_CLIENT, "<%s> [%i] %s : %s\n", msg->prefix, cmd.id.numeric, msg->params, msg->trailing);
1167  return true;
1168  } /* switch */
1169  break;
1170  case IRC_COMMAND_STRING:
1171  cmd.id.string = msg->id.string;
1172 #ifdef DEBUG
1173  Com_DPrintf(DEBUG_CLIENT, "<%s> [%s] %s : %s\n", msg->prefix, cmd.id.string, msg->params, msg->trailing);
1174 #endif
1175  if (!strncmp(cmd.id.string, "NICK", 4))
1176  Irc_Client_CmdNick(msg->prefix, msg->params, msg->trailing);
1177  else if (!strncmp(cmd.id.string, "QUIT", 4))
1178  Irc_Client_CmdQuit(msg->prefix, msg->params, msg->trailing);
1179  else if (!strncmp(cmd.id.string, "NOTICE", 6)) {
1180  if (irc_logConsole->integer)
1181  Com_Printf("%s\n", msg->trailing);
1182  } else if (!strncmp(cmd.id.string, "PRIVMSG", 7))
1183  Irc_Client_CmdPrivmsg(msg->prefix, msg->params, msg->trailing);
1184  else if (!strncmp(cmd.id.string, "MODE", 4))
1185  Irc_Client_CmdMode(msg->prefix, msg->params, msg->trailing);
1186  else if (!strncmp(cmd.id.string, "JOIN", 4))
1187  Irc_Client_CmdJoin(msg->prefix, msg->params, msg->trailing);
1188  else if (!strncmp(cmd.id.string, "PART", 4))
1189  Irc_Client_CmdPart(msg->prefix, msg->trailing);
1190  else if (!strncmp(cmd.id.string, "TOPIC", 5))
1191  Irc_Client_CmdTopic(msg->prefix, msg->trailing);
1192  else if (!strncmp(cmd.id.string, "KILL", 4))
1193  Irc_Client_CmdKill(msg->prefix, msg->params, msg->trailing);
1194  else if (!strncmp(cmd.id.string, "KICK", 4))
1195  Irc_Client_CmdKick(msg->prefix, msg->params, msg->trailing);
1196  else if (!strncmp(cmd.id.string, "PING", 4))
1197  Irc_Proto_Pong(irc_nick->string, msg->params, msg->trailing[0] ? msg->trailing : nullptr);
1198  else if (!strncmp(cmd.id.string, "ERROR", 5)) {
1200  Q_strncpyz(popupText, msg->trailing, sizeof(popupText));
1201  UI_Popup(_("Error"), popupText);
1202  } else
1203  Irc_AppendToBuffer("%s", msg->trailing);
1204  break;
1205  } /* switch */
1206  return false;
1207 }
1208 
1209 static bool Irc_Proto_ParseServerMsg (const char* txt, size_t txt_len, irc_server_msg_t* msg)
1210 {
1211  const char* c = txt;
1212  const char* end = txt + txt_len;
1213  msg->prefix[0] = '\0';
1214  msg->params[0] = '\0';
1215  msg->trailing[0] = '\0';
1216  if (c < end && *c == ':') {
1217  /* parse prefix */
1218  char* prefix = msg->prefix;
1219  int i = 1;
1220  const size_t size = sizeof(msg->prefix);
1221  ++c;
1222  while (c < end && *c != '\r' && *c != ' ') {
1223  if (i++ >= size)
1224  return true;
1225  *prefix++ = *c++;
1226  }
1227  *prefix = '\0';
1228  ++c;
1229  }
1230  if (c < end && *c != '\r') {
1231  /* parse command */
1232  if (c < end && *c >= '0' && *c <= '9') {
1233  /* numeric command */
1234  char command[4];
1235  for (int i = 0; i < 3; ++i) {
1236  if (c < end && *c >= '0' && *c <= '9')
1237  command[i] = *c++;
1238  else
1239  return true;
1240  }
1241  command[3] = '\0';
1242  msg->type = IRC_COMMAND_NUMERIC;
1243  msg->id.numeric = (irc_numeric_t)atoi(command);
1244  } else if (c < end && *c != '\r') {
1245  /* string command */
1246  int i = 1;
1247  const size_t size = sizeof(msg->id.string);
1248  char* command = msg->id.string;
1249  while (c < end && *c != '\r' && *c != ' ') {
1250  if (i++ >= size)
1251  return true;
1252  *command++ = *c++;
1253  }
1254  *command = '\0';
1255  msg->type = IRC_COMMAND_STRING;
1256  } else
1257  return true;
1258  if (c < end && *c == ' ') {
1259  /* parse params and trailing */
1260  char* params = msg->params;
1261  int i = 1;
1262  const size_t size = sizeof(msg->params);
1263  ++c;
1264 
1265  /* parse params */
1266  while (c < end && *c != '\r' && *c != ':') {
1267  /* parse single param */
1268  while (c < end && *c != '\r' && *c != ' ') {
1269  if (i++ >= size)
1270  return true;
1271  *params++ = *c++;
1272  }
1273  /* more params */
1274  if (c + 1 < end && *c == ' ' && *(c + 1) != ':') {
1275  if (i++ >= size)
1276  return true;
1277  *params++ = ' ';
1278  }
1279  if (*c == ' ')
1280  ++c;
1281  }
1282  *params = '\0';
1283  /* parse trailing */
1284  if (c < end && *c == ':') {
1285  char* trailing = msg->trailing;
1286  int i = 1;
1287  const size_t size = sizeof(msg->trailing);
1288  ++c;
1289  while (c < end && *c != '\r') {
1290  if (i++ >= size)
1291  return true;
1292  *trailing++ = *c++;
1293  }
1294  *trailing = '\0';
1295  }
1296  }
1297  }
1298  return false;
1299 }
1300 
1305 static bool Irc_Proto_Enqueue (const char* msg, size_t msg_len)
1306 {
1307  const int messageBucketSize = irc_messageBucketSize->integer;
1308  const int characterBucketSize = irc_characterBucketSize->integer;
1309 
1310  if (!irc_connected) {
1311  Com_Printf("Irc_Proto_Enqueue: not connected\n");
1312  return true;
1313  }
1314 
1315  /* create message node */
1316  if (irc_bucket.message_size + 1 <= messageBucketSize && irc_bucket.character_size + msg_len <= characterBucketSize) {
1319  m->msg = Mem_AllocTypeN(char, msg_len);
1320  memcpy(m->msg, msg, msg_len);
1321  m->msg_len = msg_len;
1322  m->next = nullptr;
1323  /* append message node */
1324  irc_bucket_message_t** anchor = &irc_bucket.first_msg;
1325  while (*anchor) anchor = &(*anchor)->next;
1326  *anchor = m;
1327  /* update bucket sizes */
1328  ++irc_bucket.message_size;
1329  irc_bucket.character_size += msg_len;
1330  return false;
1331  } else {
1332  Com_Printf("Bucket(s) full. Could not enqueue message\n");
1333  return true;
1334  }
1335 }
1336 
1341 static void Irc_Proto_RefillBucket (void)
1342 {
1343  /* calculate token refill */
1344  const double characterBucketSize = irc_characterBucketSize->value;
1345  const double characterBucketRate = irc_characterBucketRate->value;
1346  const int ms = CL_Milliseconds();
1347  const int ms_delta = ms - irc_bucket.last_refill;
1348  const double char_delta = (ms_delta * characterBucketRate) / 1000;
1349  const double char_new = irc_bucket.character_token + char_delta;
1350  /* refill token (but do not exceed maximum) */
1351  irc_bucket.character_token = std::min(char_new, characterBucketSize);
1352  /* set timestamp so next refill can calculate delta */
1353  irc_bucket.last_refill = ms;
1354 }
1355 
1361 static void Irc_Proto_DrainBucket (void)
1362 {
1363  const double characterBucketBurst = irc_characterBucketBurst->value;
1364 
1365  /* remove messages whose size exceed our burst size (we can not send them) */
1366  for (irc_bucket_message_t* msg = irc_bucket.first_msg; msg && msg->msg_len > characterBucketBurst; msg = irc_bucket.first_msg) {
1367  irc_bucket_message_t* const next = msg->next;
1368  /* update bucket sizes */
1369  --irc_bucket.message_size;
1370  irc_bucket.character_size -= msg->msg_len;
1371  /* free message */
1372  Mem_Free(msg->msg);
1373  /* dequeue message */
1374  irc_bucket.first_msg = next;
1375  }
1376  /* send burst of remaining messages */
1377  for (irc_bucket_message_t* msg = irc_bucket.first_msg; msg; msg = irc_bucket.first_msg) {
1378  /* send message */
1379  Irc_Net_Send(msg->msg, msg->msg_len);
1380  irc_bucket.character_token -= msg->msg_len;
1381  /* dequeue message */
1382  irc_bucket.first_msg = msg->next;
1383  /* update bucket sizes */
1384  --irc_bucket.message_size;
1385  irc_bucket.character_size -= msg->msg_len;
1386  /* free message */
1387  Mem_Free(msg->msg);
1388  Mem_Free(msg);
1389  }
1390 }
1391 
1392 /*
1393 ===============================================================
1394 Logic functions
1395 ===============================================================
1396 */
1397 
1401 static void Irc_Logic_SendMessages (void)
1402 {
1403  Irc_Proto_RefillBucket(); /* first refill token */
1404  Irc_Proto_DrainBucket(); /* then send messages (if allowed) */
1405 }
1406 
1413 static void Irc_Logic_ReadMessages (void)
1414 {
1415  bool msg_complete;
1416  do {
1417  irc_server_msg_t msg;
1418  if (!Irc_Proto_PollServerMsg(&msg, &msg_complete)) {
1419  /* success */
1420  if (msg_complete)
1422  } else {
1423  /* failure */
1424  UI_Popup(_("Error"), _("Server closed connection"));
1425  Irc_Logic_Disconnect("Server closed connection");
1426  }
1427  } while (msg_complete);
1428 }
1429 
1430 static void Irc_Logic_Connect (const char* server, const char* port)
1431 {
1432  if (!Irc_Proto_Connect(server, port)) {
1433  /* connected to server, send NICK and USER commands */
1434  const char* const pass = irc_password->string;
1435  const char* const user = irc_user->string;
1436  irc_connected = true;
1437  if (pass[0] != '\0')
1438  Irc_Proto_Password(pass);
1439  Irc_Proto_Nick(irc_nick->string);
1440  Irc_Proto_User(user, true, user);
1441  } else {
1442  Com_Printf("Could not connect to the irc server %s:%s\n", server, port);
1443  }
1444 }
1445 
1446 static void Irc_Logic_Disconnect (const char* reason)
1447 {
1448  if (!irc_connected) {
1449  Com_Printf("Irc_Disconnect: not connected\n");
1450  return;
1451  }
1452 
1453  Com_Printf("Irc_Disconnect: %s\n", reason);
1454  Irc_Proto_Quit(reason);
1456  irc_connected = false;
1457  chan = nullptr;
1458  Cvar_ForceSet("irc_defaultChannel", "");
1459  Cvar_ForceSet("irc_topic", "Connecting (please wait)...");
1462 }
1463 
1468 void Irc_Logic_Frame (void)
1469 {
1470  if (irc_connected) {
1471  if (irc_channel->modified) {
1473  Irc_Logic_Disconnect("Switched to another channel");
1474  Irc_Logic_Connect(irc_server->string, irc_port->string);
1475  if (irc_connected)
1476  Irc_Client_Join(irc_channel->string, nullptr);
1477  }
1478  /* could have changed in the meantime */
1479  if (irc_connected) {
1482  }
1483  }
1484  irc_channel->modified = false;
1485 }
1486 
1487 static const char* Irc_Logic_GetChannelTopic (const irc_channel_t* channel)
1488 {
1489  assert(channel);
1490  return channel->topic;
1491 }
1492 
1497 static void Irc_Logic_AddChannelName (irc_channel_t* channel, irc_nick_prefix_t prefix, const char* nick)
1498 {
1499  /* first one */
1500  irc_user_t* user = channel->user;
1501  for (int i = 0; user && i < channel->users; i++, user = user->next) {
1502  if (!strncmp(&(user->key[1]), nick, MAX_VAR - 1))
1503  return;
1504  }
1505  user = Mem_PoolAllocType(irc_user_t, cl_ircSysPool);
1506  user->next = channel->user;
1507  channel->user = user;
1508 
1509  Com_sprintf(user->key, sizeof(user->key), "%c%s", prefix, nick);
1510  channel->users++;
1511  Com_DPrintf(DEBUG_CLIENT, "Add user '%s' to userlist at pos %i\n", user->key, channel->users);
1513 }
1514 
1519 static void Irc_Logic_RemoveChannelName (irc_channel_t* channel, const char* nick)
1520 {
1521  /* first one */
1522  irc_user_t* user = channel->user;
1523  irc_user_t* predecessor = nullptr;
1524  for (int i = 0; user && i < channel->users; i++, user = user->next) {
1525  if (!strncmp(&(user->key[1]), nick, sizeof(user->key))) {
1526  /* delete the first user from the list */
1527  if (!predecessor)
1528  channel->user = user->next;
1529  /* point to the descendant */
1530  else
1531  predecessor->next = user->next;
1532  Mem_Free(user);
1533  chan->users--;
1535  return;
1536  }
1537  predecessor = user;
1538  }
1539 }
1540 
1541 /*
1542 ===============================================================
1543 Network functions
1544 ===============================================================
1545 */
1546 
1547 static void Irc_Net_StreamClose (void)
1548 {
1549  irc_stream = nullptr;
1550 }
1551 
1556 static bool Irc_Net_Connect (const char* host, const char* port)
1557 {
1558  if (irc_stream)
1559  NET_StreamFree(irc_stream);
1560  irc_stream = NET_Connect(host, port, Irc_Net_StreamClose);
1561  return irc_stream ? false : true;
1562 }
1563 
1567 static bool Irc_Net_Disconnect (void)
1568 {
1569  NET_StreamFree(irc_stream);
1570  return true;
1571 }
1572 
1573 static void Irc_Net_Send (const char* msg, size_t msg_len)
1574 {
1575  assert(msg);
1576  NET_StreamEnqueue(irc_stream, msg, msg_len);
1577 }
1578 
1579 /*
1580 ===============================================================
1581 Bindings
1582 ===============================================================
1583 */
1584 
1585 static void Irc_Connect_f (void)
1586 {
1587  const int argc = Cmd_Argc();
1588  if (!irc_port || !irc_server)
1589  return;
1590  if (argc >= 2 && argc <= 4) {
1591 #if 0
1592  if (irc_connected)
1593  Irc_Logic_Disconnect("reconnect");
1594 #endif
1595  if (!irc_connected) {
1596  /* not connected yet */
1597  if (argc >= 2)
1598  Cvar_Set("irc_server", "%s", Cmd_Argv(1));
1599  if (argc >= 3)
1600  Cvar_Set("irc_port", "%s", Cmd_Argv(2));
1601  Com_Printf("Connecting to %s:%s\n", irc_server->string, irc_port->string);
1602  Irc_Logic_Connect(irc_server->string, irc_port->string);
1603  if (irc_connected && argc >= 4)
1604  Irc_Client_Join(Cmd_Argv(3), nullptr);
1605  } else {
1606  Com_Printf("Already connected.\n");
1607  }
1608  } else if (irc_server->string[0] != '\0' && irc_port->integer) {
1609  if (!irc_connected)
1610  Cbuf_AddText("irc_connect %s %i %s\n", irc_server->string, irc_port->integer, irc_channel->string);
1611  else
1612  Com_Printf("Already connected.\n");
1613  } else {
1614  Com_Printf("Usage: %s [<server>] [<port>] [<channel>]\n", Cmd_Argv(0));
1615  }
1616 }
1617 
1618 static void Irc_Disconnect_f (void)
1619 {
1620  Irc_Logic_Disconnect("normal exit");
1621 }
1622 
1623 static bool Irc_Client_Join (const char* channel, const char* password)
1624 {
1625  if (!Irc_IsChannel(channel)) {
1626  Com_Printf("No valid channel name\n");
1627  return false;
1628  }
1629  /* join desired channel */
1630  if (!Irc_Proto_Join(channel, password)) {
1631  Cvar_ForceSet("irc_defaultChannel", channel);
1632  Com_Printf("Joined channel: '%s'\n", channel);
1633  return true;
1634  } else {
1635  Com_Printf("Could not join channel: '%s'\n", channel);
1636  return false;
1637  }
1638 }
1639 
1640 static void Irc_Client_Join_f (void)
1641 {
1642  const int argc = Cmd_Argc();
1643  if (argc >= 2 && argc <= 3) {
1644  const char* const channel = Cmd_Argv(1);
1645  /* password is optional */
1646  const char* const channel_pass = (argc == 3) ? Cmd_Argv(2) : nullptr;
1647  Irc_Client_Join(channel, channel_pass);
1648  } else {
1649  Com_Printf("Usage: %s <channel> [<password>]\n", Cmd_Argv(0));
1650  }
1651 }
1652 
1653 static void Irc_Client_Part_f (void)
1654 {
1655  const int argc = Cmd_Argc();
1656  if (argc == 2) {
1657  const char* const channel = Cmd_Argv(1);
1658  Irc_Proto_Part(channel);
1659  } else {
1660  Com_Printf("Usage: %s <channel>\n", Cmd_Argv(0));
1661  }
1662 }
1663 
1669 static void Irc_Client_Msg_f (void)
1670 {
1671  char cropped_msg[IRC_SEND_BUF_SIZE];
1672  const char* msg;
1673 
1674  if (Cmd_Argc() >= 2)
1675  msg = Cmd_Args();
1676  else if (irc_send_buffer->string[0] != '\0')
1677  msg = irc_send_buffer->string;
1678  else
1679  return;
1680 
1681  if (msg[0] != '\0') {
1682  if (irc_defaultChannel->string[0] != '\0') {
1683  if (msg[0] == '"') {
1684  const size_t msg_len = strlen(msg);
1685  memcpy(cropped_msg, msg + 1, msg_len - 2);
1686  cropped_msg[msg_len - 2] = '\0';
1687  msg = cropped_msg;
1688  }
1689  if (!Irc_Proto_Msg(irc_defaultChannel->string, msg)) {
1690  /* local echo */
1691  Irc_AppendToBuffer("<%s> %s", irc_nick->string, msg);
1692  }
1693  Cvar_ForceSet("irc_send_buffer", "");
1694  } else
1695  Com_Printf("Join a channel first.\n");
1696  }
1697 }
1698 
1699 static void Irc_Client_PrivMsg_f (void)
1700 {
1701  if (Cmd_Argc() >= 3) {
1702  char cropped_msg[IRC_SEND_BUF_SIZE];
1703  const char* const target = Cmd_Argv(1);
1704  const char* msg = Cmd_Args() + strlen(target) + 1;
1705  if (*msg == '"') {
1706  const size_t msg_len = strlen(msg);
1707  memcpy(cropped_msg, msg + 1, msg_len - 2);
1708  cropped_msg[msg_len - 2] = '\0';
1709  msg = cropped_msg;
1710  }
1711  Irc_Proto_Msg(target, msg);
1712  } else {
1713  Com_Printf("Usage: %s <target> {<msg>}\n", Cmd_Argv(0));
1714  }
1715 }
1716 
1717 static void Irc_Client_Mode_f (void)
1718 {
1719  const int argc = Cmd_Argc();
1720  if (argc >= 3) {
1721  const char* const target = Cmd_Argv(1);
1722  const char* const modes = Cmd_Argv(2);
1723  const char* const params = argc >= 4
1724  ? Cmd_Args() + strlen(target) + strlen(modes) + 2
1725  : nullptr;
1726  Irc_Proto_Mode(target, modes, params);
1727  } else
1728  Com_Printf("Usage: %s <target> <modes> {<param>}\n", Cmd_Argv(0));
1729 }
1730 
1731 static void Irc_Client_Topic_f (void)
1732 {
1733  const int argc = Cmd_Argc();
1734  if (argc >= 2) {
1735  const char* const channel = Cmd_Argv(1);
1736  if (chan) {
1737  if (argc >= 3) {
1738  char buf[1024];
1739  const char* in = Cmd_Args();
1740  char* out = buf;
1741  if (*in == '"')
1742  in += 2;
1743  in += strlen(channel) + 1;
1744  Q_strncpyz(out, in, sizeof(out));
1745  if (*out == '"') {
1746  ++out;
1747  const size_t out_len = strlen(out);
1748  assert(out_len >= 1);
1749  out[out_len - 1] = '\0';
1750  }
1751  Irc_Proto_Topic(channel, out);
1752  } else {
1753  Com_Printf("%s topic: \"%s\"\n", channel, Irc_Logic_GetChannelTopic(chan));
1754  }
1755  } else {
1756  Com_Printf("Not joined: %s\n", channel);
1757  }
1758  } else {
1759  Com_Printf("Usage: %s <channel> [<topic>]\n", Cmd_Argv(0));
1760  }
1761 }
1762 
1763 #define IRC_MAX_USERLIST 512
1765 
1766 static void Irc_Client_Names_f (void)
1767 {
1768  if (!chan) {
1769  Com_Printf("Not joined\n");
1770  return;
1771  }
1772  linkedList_t* irc_names_buffer = nullptr;
1773  int i;
1774  irc_user_t* user = chan->user;
1775  for (i = 0; i < chan->users; i++) {
1776  if (i >= IRC_MAX_USERLIST)
1777  break;
1779  user = user->next;
1780  }
1781  if (i > 0) {
1782  qsort((void*)irc_userListOrdered, i, MAX_VAR, Q_StringSort);
1783  while (i--)
1784  LIST_AddString(&irc_names_buffer, irc_userListOrdered[i]);
1785  }
1786  UI_RegisterLinkedListText(TEXT_IRCUSERS, irc_names_buffer);
1787 }
1788 
1789 static void Irc_Client_Kick_f (void)
1790 {
1791  const int argc = Cmd_Argc();
1792  if (argc < 3) {
1793  Com_Printf("Usage: %s <channel> <nick> [<reason>]\n", Cmd_Argv(0));
1794  return;
1795  }
1796  const char* channel = Cmd_Argv(1);
1797  if (!chan) {
1798  Com_Printf("Not joined: %s.\n", channel);
1799  return;
1800  }
1801  const char* const nick = Cmd_Argv(2);
1802  const char* reason;
1803  if (argc >= 4)
1804  reason = Cmd_Args() + strlen(nick) + strlen(channel) + 2;
1805  else
1806  reason = nullptr;
1807  Irc_Proto_Kick(channel, nick, reason);
1808 }
1809 
1810 static void Irc_GetExternalIP (const char* externalIP, void* userdata)
1811 {
1812  char buf[128];
1813 
1814  if (!externalIP) {
1815  Com_Printf("Could not query masterserver\n");
1816  return;
1817  }
1818  Com_sprintf(buf, sizeof(buf), "%s%s;%s;%s", IRC_INVITE_FOR_A_GAME, externalIP, port->string, UFO_VERSION);
1819 
1820  const irc_user_t* user = chan->user;
1821  while (user) {
1824  /* the first character is a prefix, skip it when checking
1825  * against our own nickname */
1826  const char* name = &(user->key[1]);
1827  if (!Q_streq(name, irc_nick->string))
1828  Irc_Proto_Msg(name, buf);
1829  user = user->next;
1830  }
1831 }
1832 
1836 static void Irc_Client_Invite_f (void)
1837 {
1838  if (!CL_OnBattlescape()) {
1839  Com_Printf("You must be connected to a running server to invite others\n");
1840  return;
1841  }
1842 
1843  if (!chan) {
1844  UI_PushWindow("irc_popup");
1845  return;
1846  }
1847 
1848  HTTP_GetURL(va("%s/ufo/masterserver.php?ip", masterserver_url->string), Irc_GetExternalIP);
1849 }
1850 
1851 static void Irc_Client_Who_f (void)
1852 {
1853  if (Cmd_Argc() != 2) {
1854  Com_Printf("Usage: %s <usermask>\n", Cmd_Argv(0));
1855  return;
1856  }
1857  Irc_Proto_Who(Cmd_Argv(1));
1858 }
1859 
1860 static void Irc_Client_Whois_f (void)
1861 {
1862  if (Cmd_Argc() != 2) {
1863  Com_Printf("Usage: %s <nick>\n", Cmd_Argv(0));
1864  return;
1865  }
1867 }
1868 
1869 static void Irc_Client_Whowas_f (void)
1870 {
1871  if (Cmd_Argc() != 2) {
1872  Com_Printf("Usage: %s <nick>\n", Cmd_Argv(0));
1873  return;
1874  }
1876 }
1877 
1878 /*
1879 ===============================================================
1880 Menu functions
1881 ===============================================================
1882 */
1883 
1888 static void Irc_UserClick_f (void)
1889 {
1890  if (Cmd_Argc() != 2)
1891  return;
1892 
1893  if (!chan)
1894  return;
1895 
1896  const int num = atoi(Cmd_Argv(1));
1897  if (num < 0 || num >= chan->users || num >= IRC_MAX_USERLIST)
1898  return;
1899 
1900  const int cnt = std::min(chan->users, IRC_MAX_USERLIST) - (num + 1);
1901 
1902  const char* name = irc_userListOrdered[cnt];
1903  Cvar_Set("irc_send_buffer", "%s%s: ", irc_send_buffer->string, &name[1]);
1904 }
1905 
1910 static void Irc_UserRightClick_f (void)
1911 {
1912  if (Cmd_Argc() != 2)
1913  return;
1914 
1915  if (!chan)
1916  return;
1917 
1918  const int num = atoi(Cmd_Argv(1));
1919  if (num < 0 || num >= chan->users || num >= IRC_MAX_USERLIST)
1920  return;
1921 
1922  const int cnt = std::min(chan->users, IRC_MAX_USERLIST) - (num + 1);
1923 
1924  const char* name = irc_userListOrdered[cnt];
1925  Irc_Proto_Whois(&name[1]);
1926 }
1927 
1931 static void Irc_Input_Activate_f (void)
1932 {
1933  /* in case of a failure we need this in UI_PopWindow */
1934  if (irc_connected && irc_defaultChannel->string[0] != '\0') {
1936  } else {
1937  Com_DPrintf(DEBUG_CLIENT, "Irc_Input_Activate: Warning - IRC not connected\n");
1938  UI_PopWindow();
1939  UI_PushWindow("irc_popup");
1940  }
1941 }
1942 
1946 static void Irc_Input_Deactivate_f (void)
1947 {
1948  irc_send_buffer->modified = false;
1949 
1951 }
1952 
1953 /*
1954 ===============================================================
1955 Init and Shutdown functions
1956 ===============================================================
1957 */
1958 
1959 void Irc_Init (void)
1960 {
1961  cl_ircSysPool = Mem_CreatePool("Client: IRC system");
1962 
1963  /* commands */
1964  Cmd_AddCommand("irc_join", Irc_Client_Join_f, "Join an irc channel");
1965  Cmd_AddCommand("irc_connect", Irc_Connect_f, "Connect to the irc network");
1966  Cmd_AddCommand("irc_disconnect", Irc_Disconnect_f, "Disconnect from the irc network");
1967  Cmd_AddCommand("irc_part", Irc_Client_Part_f);
1968  Cmd_AddCommand("irc_privmsg", Irc_Client_PrivMsg_f);
1969  Cmd_AddCommand("irc_mode", Irc_Client_Mode_f);
1970  Cmd_AddCommand("irc_who", Irc_Client_Who_f);
1971  Cmd_AddCommand("irc_whois", Irc_Client_Whois_f);
1972  Cmd_AddCommand("irc_whowas", Irc_Client_Whowas_f);
1973  Cmd_AddCommand("irc_chanmsg", Irc_Client_Msg_f);
1974  Cmd_AddCommand("irc_topic", Irc_Client_Topic_f);
1975  Cmd_AddCommand("irc_names", Irc_Client_Names_f);
1976  Cmd_AddCommand("irc_kick", Irc_Client_Kick_f);
1977  Cmd_AddCommand("irc_invite", Irc_Client_Invite_f, "Invite other players for a game");
1978 
1979  Cmd_AddCommand("irc_userlist_click", Irc_UserClick_f, "Menu function for clicking a user from the list");
1980  Cmd_AddCommand("irc_userlist_rclick", Irc_UserRightClick_f, "Menu function for clicking a user from the list");
1981 
1982  Cmd_AddCommand("irc_activate", Irc_Input_Activate_f, "IRC init when entering the menu");
1983  Cmd_AddCommand("irc_deactivate", Irc_Input_Deactivate_f, "IRC deactivate when leaving the irc menu");
1984 
1985  /* cvars */
1986  irc_server = Cvar_Get("irc_server", "irc.freenode.org", CVAR_ARCHIVE, "IRC server to connect to");
1987  irc_channel = Cvar_Get("irc_channel", "#ufoai-gamer", CVAR_ARCHIVE, "IRC channel to join into");
1988  irc_channel->modified = false;
1989  irc_port = Cvar_Get("irc_port", "6667", CVAR_ARCHIVE, "IRC port to connect to");
1990  irc_user = Cvar_Get("irc_user", "UFOAIPlayer", CVAR_ARCHIVE);
1991  irc_password = Cvar_Get("irc_password", "", CVAR_ARCHIVE);
1992  irc_topic = Cvar_Get("irc_topic", "Connecting (please wait)...", CVAR_NOSET);
1993  irc_defaultChannel = Cvar_Get("irc_defaultChannel", "", CVAR_NOSET);
1994  irc_logConsole = Cvar_Get("irc_logConsole", "0", CVAR_ARCHIVE, "Log all irc conversations to game console, too");
1995  irc_showIfNotInMenu = Cvar_Get("irc_showIfNotInMenu", "0", CVAR_ARCHIVE, "Show chat messages on top of the menu stack if we are not in the irc menu");
1996  irc_send_buffer = Cvar_Get("irc_send_buffer");
1997  irc_nick = Cvar_Get("cl_name");
1998 
1999  irc_messageBucketSize = Cvar_Get("irc_messageBucketSize", "100", CVAR_ARCHIVE, "max 100 messages in bucket");
2000  irc_messageBucketBurst = Cvar_Get("irc_messageBucketBurst", "5", CVAR_ARCHIVE, "max burst size is 5 messages");
2001  irc_characterBucketSize = Cvar_Get("irc_characterBucketSize", "2500", CVAR_ARCHIVE, "max 2,500 characters in bucket");
2002  irc_characterBucketBurst = Cvar_Get("irc_characterBucketBurst", "250", CVAR_ARCHIVE, "max burst size is 250 characters");
2003  irc_characterBucketRate = Cvar_Get("irc_characterBucketRate", "10", CVAR_ARCHIVE, "per second (100 characters in 10 seconds)");
2004 }
2005 
2006 void Irc_Shutdown (void)
2007 {
2008  if (irc_connected)
2009  Irc_Logic_Disconnect("shutdown");
2010 
2011  Mem_DeletePool(cl_ircSysPool);
2012 }
int NET_StreamDequeue(struct net_stream *s, char *data, int len)
Definition: net.cpp:760
const char * Cmd_Argv(int arg)
Returns a given argument.
Definition: cmd.cpp:516
static bool Irc_Proto_Msg(const char *target, const char *text)
Definition: cl_irc.cpp:495
void Cmd_AddCommand(const char *cmdName, xcommand_t function, const char *desc)
Add a new command to the script interface.
Definition: cmd.cpp:744
static bool Irc_AppendToBuffer(const char *const msg,...) __attribute__((format(__printf__
Append the irc message to the buffer.
Definition: cl_irc.cpp:626
enum irc_command_type_e irc_command_type_t
void GAME_AddChatMessage(const char *format,...)
Definition: cl_game.cpp:370
#define Mem_AllocTypeN(type, n)
Definition: mem.h:38
union irc_command_s::@6 id
static bool Irc_Proto_Part(const char *channel)
Definition: cl_irc.cpp:456
unsigned int message_size
Definition: cl_irc.cpp:340
static bool Irc_Proto_PollServerMsg(irc_server_msg_t *msg, bool *msg_complete)
Definition: cl_irc.cpp:582
int Q_vsnprintf(char *str, size_t size, const char *format, va_list ap)
Safe (null terminating) vsnprintf implementation.
Definition: shared.cpp:535
char popupText[UI_MAX_SMALLTEXTLEN]
strings to be used for popup when text is not static
Definition: ui_popup.cpp:37
irc_nick_prefix_e
Definition: cl_irc.cpp:231
enum irc_nick_prefix_e irc_nick_prefix_t
irc_command_type_t type
Definition: cl_irc.cpp:253
struct net_stream * NET_Connect(const char *node, const char *service, stream_onclose_func *onclose)
Try to connect to a given host on a given port.
Definition: net.cpp:644
void UI_RegisterLinkedListText(int dataId, linkedList_t *text)
share a linked list of text with a data id
Definition: ui_data.cpp:131
static bool Irc_Proto_Connect(const char *host, const char *port)
Definition: cl_irc.cpp:351
struct irc_bucket_message_s * next
Definition: cl_irc.cpp:335
static void Irc_Client_CmdJoin(const char *prefix, const char *params, const char *trailing)
Definition: cl_irc.cpp:828
static char irc_userListOrdered[IRC_MAX_USERLIST][MAX_VAR]
Definition: cl_irc.cpp:1764
static void Irc_Logic_RemoveChannelName(irc_channel_t *channel, const char *nick)
Removes a username from the channel username list.
Definition: cl_irc.cpp:1519
void UI_RegisterText(int dataId, const char *text)
share a text with a data id
Definition: ui_data.cpp:115
bool CL_OnBattlescape(void)
Check whether we are in a tactical mission as server or as client. But this only means that we are ab...
const char * va(const char *format,...)
does a varargs printf into a temp buffer, so I don't need to have varargs versions of all text functi...
Definition: shared.cpp:410
irc_bucket_message_t * first_msg
Definition: cl_irc.cpp:339
static void Irc_Client_CmdQuit(const char *prefix, const char *params, const char *trailing)
Definition: cl_irc.cpp:846
static void Irc_Logic_Disconnect(const char *reason)
Definition: cl_irc.cpp:1446
#define IRC_RECV_BUF_SIZE
Definition: cl_irc.cpp:63
static void Irc_Client_Whois_f(void)
Definition: cl_irc.cpp:1860
This is a cvar definition. Cvars can be user modified and used in our menus e.g.
Definition: cvar.h:71
#define _(String)
Definition: cl_shared.h:43
static cvar_t * irc_characterBucketBurst
Definition: cl_irc.cpp:329
irc_command_type_e
Definition: cl_irc.cpp:226
struct irc_command_s irc_command_t
static bool Irc_Proto_Disconnect(void)
Definition: cl_irc.cpp:367
struct irc_user_s * next
Definition: cl_irc.cpp:67
static void Irc_Client_Mode_f(void)
Definition: cl_irc.cpp:1717
bool Com_sprintf(char *dest, size_t size, const char *fmt,...)
copies formatted string with buffer-size checking
Definition: shared.cpp:494
const char * UI_GetActiveWindowName(void)
Returns the name of the current window.
Definition: ui_windows.cpp:526
static void Irc_Client_CmdNick(const char *prefix, const char *params, const char *trailing)
Changes the cvar 'name' with the new name you set.
Definition: cl_irc.cpp:886
static cvar_t * irc_channel
Definition: cl_irc.cpp:48
char name[MAX_VAR]
Definition: cl_irc.cpp:71
#define IRC_CTCP_MARKER_CHR
Definition: cl_irc.cpp:903
uiNode_t * UI_GetActiveWindow(void)
Returns the current active window from the window stack or nullptr if there is none.
Definition: ui_windows.cpp:516
struct irc_user_s irc_user_t
irc_user_t * user
Definition: cl_irc.cpp:74
struct irc_server_msg_s irc_server_msg_t
static cvar_t * irc_characterBucketRate
Definition: cl_irc.cpp:330
static cvar_t * irc_send_buffer
Definition: cl_irc.cpp:57
void Cbuf_AddText(const char *format,...)
Adds command text at the end of the buffer.
Definition: cmd.cpp:126
union irc_server_msg_s::@7 id
static cvar_t * irc_password
Definition: cl_irc.cpp:51
static void Irc_Client_CmdRplWhoisaccount(const char *params, const char *trailing)
Definition: cl_irc.cpp:745
static const char * user
Definition: test_webapi.cpp:35
Shared game type headers.
static void Irc_Input_Activate_f(void)
Definition: cl_irc.cpp:1931
float value
Definition: cvar.h:80
static bool Irc_Net_Disconnect(void)
Definition: cl_irc.cpp:1567
char topic[256]
Definition: cl_irc.cpp:72
void NET_StreamFree(struct net_stream *s)
Call NET_StreamFree to dump the whole thing right now.
Definition: net.cpp:817
static bool Irc_Client_Join(const char *channel, const char *password)
Definition: cl_irc.cpp:1623
static cvar_t * irc_nick
Definition: cl_irc.cpp:49
void Com_Printf(const char *const fmt,...)
Definition: common.cpp:386
static void Irc_Client_Who_f(void)
Definition: cl_irc.cpp:1851
char trailing[IRC_SEND_BUF_SIZE]
Definition: cl_irc.cpp:256
char params[IRC_SEND_BUF_SIZE]
Definition: cl_irc.cpp:255
struct irc_bucket_s irc_bucket_t
#define __attribute__(x)
Definition: cxx.h:37
int integer
Definition: cvar.h:81
cvar_t * masterserver_url
Definition: common.cpp:57
static void Irc_Client_CmdRplNamreply(const char *params, const char *trailing)
Definition: cl_irc.cpp:988
enum irc_numeric_e irc_numeric_t
voidpf void * buf
Definition: ioapi.h:42
#define CVAR_ARCHIVE
Definition: cvar.h:40
irc_command_type_t type
Definition: cl_irc.cpp:244
double character_token
Definition: cl_irc.cpp:343
static bool Irc_Proto_Enqueue(const char *msg, size_t msg_len)
Definition: cl_irc.cpp:1305
struct irc_bucket_message_s irc_bucket_message_t
static void Irc_Net_StreamClose(void)
Definition: cl_irc.cpp:1547
static void Irc_Client_CmdRplWhoisuser(const char *params, const char *trailing)
Definition: cl_irc.cpp:700
static void Irc_UserClick_f(void)
Adds the username you clicked to your input buffer.
Definition: cl_irc.cpp:1888
#define IRC_MAX_USERLIST
Definition: cl_irc.cpp:1763
static bool Irc_Proto_Who(const char *nick)
Definition: cl_irc.cpp:549
unsigned int character_size
Definition: cl_irc.cpp:341
static cvar_t * irc_messageBucketBurst
Definition: cl_irc.cpp:327
void Q_strncpyz(char *dest, const char *src, size_t destsize)
Safe strncpy that ensures a trailing zero.
Definition: shared.cpp:457
static void Irc_Client_Names_f(void)
Definition: cl_irc.cpp:1766
static void Irc_Net_Send(const char *msg, size_t msg_len)
Definition: cl_irc.cpp:1573
static void Irc_Disconnect_f(void)
Definition: cl_irc.cpp:1618
static bool Irc_Proto_Whowas(const char *nick)
Definition: cl_irc.cpp:571
static bool static bool Irc_Proto_ParseServerMsg(const char *txt, size_t txt_len, irc_server_msg_t *msg)
Definition: cl_irc.cpp:1209
#define UFO_VERSION
Definition: common.h:36
uiNode_t * UI_PushWindow(const char *name, const char *parentName, linkedList_t *params)
Push a window onto the window stack.
Definition: ui_windows.cpp:170
static void Irc_Client_Part_f(void)
Definition: cl_irc.cpp:1653
#define DEBUG_CLIENT
Definition: defines.h:59
const char * string
Definition: cl_irc.cpp:242
void UI_ResetData(int dataId)
Reset a shared data. Type became NONE and value became nullptr.
Definition: ui_data.cpp:212
cvar_t * Cvar_Get(const char *var_name, const char *var_value, int flags, const char *desc)
Init or return a cvar.
Definition: cvar.cpp:342
GLsizei size
Definition: r_gl.h:152
void NET_StreamEnqueue(struct net_stream *s, const char *data, int len)
Enqueue a network message into a stream.
Definition: net.cpp:719
void LIST_AddString(linkedList_t **listDest, const char *data)
Adds an string to a new or to an already existing linked list. The string is copied here...
Definition: list.cpp:139
#define OBJZERO(obj)
Definition: shared.h:178
#define MAX_VAR
Definition: shared.h:36
static bool Irc_Proto_Quit(const char *quitmsg)
Definition: cl_irc.cpp:388
QGL_EXTERN GLuint GLsizei GLsizei * length
Definition: r_gl.h:110
static cvar_t * irc_user
Definition: cl_irc.cpp:50
static char irc_buffer[4096]
Definition: cl_irc.cpp:266
int Cmd_Argc(void)
Return the number of arguments of the current command. "command parameter" will result in a argc of 2...
Definition: cmd.cpp:505
irc_numeric_t numeric
Definition: cl_irc.cpp:251
static void Irc_Client_CmdRplWhowasuser(const char *params, const char *trailing)
Definition: cl_irc.cpp:662
static bool Irc_Proto_Nick(const char *nick)
Definition: cl_irc.cpp:400
#define inline
Definition: shared.h:85
static void Irc_Logic_ReadMessages(void)
Definition: cl_irc.cpp:1413
#define CVAR_NOSET
Definition: cvar.h:43
static void Irc_Proto_RefillBucket(void)
Definition: cl_irc.cpp:1341
static void Irc_Client_Whowas_f(void)
Definition: cl_irc.cpp:1869
#define Mem_CreatePool(name)
Definition: mem.h:32
static irc_channel_t ircChan
Definition: cl_irc.cpp:263
#define Mem_DeletePool(pool)
Definition: mem.h:33
void UI_TextScrollEnd(const char *nodePath)
Scroll to the bottom.
static cvar_t * irc_server
Definition: cl_irc.cpp:46
static void Irc_Connect_f(void)
Definition: cl_irc.cpp:1585
char string[IRC_SEND_BUF_SIZE]
Definition: cl_irc.cpp:250
static void Irc_Logic_SendMessages(void)
Definition: cl_irc.cpp:1401
static void Irc_Client_PrivMsg_f(void)
Definition: cl_irc.cpp:1699
#define S_COLOR_GREEN
Definition: common.h:219
static void Irc_Proto_Pong(const char *nick, const char *server, const char *cookie)
Definition: cl_irc.cpp:523
void Com_DPrintf(int level, const char *fmt,...)
A Com_Printf that only shows up if the "developer" cvar is set.
Definition: common.cpp:398
static void Irc_Logic_Connect(const char *server, const char *port)
Definition: cl_irc.cpp:1430
static struct net_stream * irc_stream
Definition: cl_irc.cpp:259
static void Irc_Client_CmdRplEndofnames(const char *params, const char *trailing)
Definition: cl_irc.cpp:1022
char key[MAX_VAR]
Definition: cl_irc.cpp:66
static irc_bucket_t irc_bucket
Definition: cl_irc.cpp:346
#define IRC_CTCP_MARKER_STR
Definition: cl_irc.cpp:904
static void Irc_GetExternalIP(const char *externalIP, void *userdata)
Definition: cl_irc.cpp:1810
static bool Irc_Proto_Whois(const char *nick)
Definition: cl_irc.cpp:560
static void Irc_Client_Msg_f(void)
Send a message from menu or commandline.
Definition: cl_irc.cpp:1669
static cvar_t * irc_characterBucketSize
Definition: cl_irc.cpp:328
void S_StartLocalSample(const char *s, float volume)
Plays a sample without spatialization.
Definition: s_mix.cpp:184
static bool Irc_IsChannel(const char *target)
Definition: cl_irc.cpp:292
static void Irc_ParseName(const char *mask, char *nick, size_t size, irc_nick_prefix_t *prefix)
Definition: cl_irc.cpp:298
static const char * Irc_Logic_GetChannelTopic(const irc_channel_t *channel)
Definition: cl_irc.cpp:1487
#define Mem_PoolAllocTypeN(type, n, pool)
Definition: mem.h:42
void Irc_Init(void)
Definition: cl_irc.cpp:1959
static void Irc_Proto_DrainBucket(void)
Send all enqueued packets.
Definition: cl_irc.cpp:1361
static void Irc_Logic_AddChannelName(irc_channel_t *channel, irc_nick_prefix_t prefix, const char *nick)
Adds a new username to the channel username list.
Definition: cl_irc.cpp:1497
#define IRC_SEND_BUF_SIZE
Definition: cl_irc.cpp:62
cvar_t * password
Definition: g_main.cpp:67
static bool Irc_Proto_User(const char *user, bool invisible, const char *name)
Definition: cl_irc.cpp:411
static void Irc_Client_CmdRplWhoisidle(const char *params, const char *trailing)
Definition: cl_irc.cpp:766
static void Irc_Client_CmdPart(const char *prefix, const char *trailing)
Definition: cl_irc.cpp:837
static bool Irc_Proto_Topic(const char *channel, const char *topic)
Definition: cl_irc.cpp:480
static bool Irc_Net_Connect(const char *host, const char *port)
Definition: cl_irc.cpp:1556
static void Irc_Client_CmdKill(const char *prefix, const char *params, const char *trailing)
Definition: cl_irc.cpp:855
QGL_EXTERN GLint i
Definition: r_gl.h:113
static cvar_t * irc_defaultChannel
Definition: cl_irc.cpp:53
QGL_EXTERN GLuint GLchar GLuint * len
Definition: r_gl.h:99
const char * CL_Translate(const char *t)
static cvar_t * irc_logConsole
Definition: cl_irc.cpp:54
char * string
Definition: cvar.h:73
static bool irc_connected
Definition: cl_irc.cpp:60
static cvar_t * irc_messageBucketSize
Definition: cl_irc.cpp:326
static bool Irc_Proto_Password(const char *password)
Definition: cl_irc.cpp:422
QGL_EXTERN GLuint GLsizei GLsizei GLint GLenum GLchar * name
Definition: r_gl.h:110
static void Irc_UserRightClick_f(void)
Performs a whois query for the username you clicked.
Definition: cl_irc.cpp:1910
static void Irc_Client_Kick_f(void)
Definition: cl_irc.cpp:1789
#define Mem_Free(ptr)
Definition: mem.h:35
static void Irc_Client_Join_f(void)
Definition: cl_irc.cpp:1640
cvar_t * port
Definition: common.cpp:58
void Q_strcat(char *dest, size_t destsize, const char *format,...)
Safely (without overflowing the destination buffer) concatenates two strings.
Definition: shared.cpp:475
static void Irc_Client_Topic_f(void)
Definition: cl_irc.cpp:1731
void Irc_Logic_Frame(void)
Definition: cl_irc.cpp:1468
HUD related routines.
void UI_PopWindow(bool all)
Pops a window from the window stack.
Definition: ui_windows.cpp:452
static cvar_t * irc_showIfNotInMenu
Definition: cl_irc.cpp:55
static const char IRC_INVITE_FOR_A_GAME[]
Definition: cl_irc.cpp:261
static cvar_t * irc_topic
Definition: cl_irc.cpp:52
irc_numeric_e
Definition: cl_irc.cpp:78
int Q_StringSort(const void *string1, const void *string2)
Compare two strings.
Definition: shared.cpp:385
#define Mem_AllocType(type)
Definition: mem.h:39
cvar_t * Cvar_ForceSet(const char *varName, const char *value)
Will set the variable even if NOSET or LATCH.
Definition: cvar.cpp:604
bool HTTP_GetURL(const char *url, http_callback_t callback, void *userdata, const char *postfields)
Downloads the given url and return the data to the callback (optional)
Definition: http.cpp:374
struct irc_channel_s irc_channel_t
#define lengthof(x)
Definition: shared.h:105
static bool Irc_Proto_ProcessServerMsg(const irc_server_msg_t *msg)
Definition: cl_irc.cpp:1029
void Irc_Shutdown(void)
Definition: cl_irc.cpp:2006
cvar_t * Cvar_Set(const char *varName, const char *value,...)
Sets a cvar value.
Definition: cvar.cpp:615
Primary header for client.
static memPool_t * cl_ircSysPool
Definition: cl_irc.cpp:58
IRC client header for UFO:AI.
const char * Cvar_GetString(const char *varName)
Returns the value of cvar as string.
Definition: cvar.cpp:210
static bool Irc_Proto_Join(const char *channel, const char *password)
Definition: cl_irc.cpp:433
#define Q_streq(a, b)
Definition: shared.h:136
bool modified
Definition: cvar.h:79
#define Mem_PoolAllocType(type, pool)
Definition: mem.h:43
char prefix[IRC_SEND_BUF_SIZE]
Definition: cl_irc.cpp:254
irc_numeric_t numeric
Definition: cl_irc.cpp:241
static void Irc_Client_CmdRplTopic(const char *params, const char *trailing)
Definition: cl_irc.cpp:691
static irc_channel_t * chan
Definition: cl_irc.cpp:264
static bool Irc_Proto_Mode(const char *target, const char *modes, const char *params)
Definition: cl_irc.cpp:467
void UI_Popup(const char *title, const char *text)
Popup on geoscape.
Definition: ui_popup.cpp:47
static void Irc_Client_Invite_f(void)
Definition: cl_irc.cpp:1836
static bool Irc_Proto_Kick(const char *channel, const char *nick, const char *reason)
Definition: cl_irc.cpp:536
static cvar_t * irc_port
Definition: cl_irc.cpp:47
static void Irc_Input_Deactivate_f(void)
Definition: cl_irc.cpp:1946
static bool Irc_Proto_Notice(const char *target, const char *text)
Definition: cl_irc.cpp:512
static void Irc_Client_CmdRplWhoreply(const char *params, const char *trailing)
Definition: cl_irc.cpp:787
static struct mdfour * m
Definition: md4.cpp:35
static void Irc_Client_CmdRplWhoisserver(const char *params, const char *trailing)
Definition: cl_irc.cpp:724
static void Irc_Client_CmdTopic(const char *prefix, const char *trailing)
Definition: cl_irc.cpp:686
const char * Cmd_Args(void)
Returns a single string containing argv(1) to argv(argc()-1)
Definition: cmd.cpp:526
static void Irc_Client_CmdKick(const char *prefix, const char *params, const char *trailing)
Definition: cl_irc.cpp:864
void format(__printf__, 1, 2)))
static void Irc_Client_CmdMode(const char *prefix, const char *params, const char *trailing)
Definition: cl_irc.cpp:820
int last_refill
Definition: cl_irc.cpp:342
int CL_Milliseconds(void)
Definition: cl_main.cpp:1208
#define SND_VOLUME_DEFAULT
Definition: s_main.h:42
void UI_ExecuteConfunc(const char *fmt,...)
Executes confunc - just to identify those confuncs in the code - in this frame.
Definition: ui_main.cpp:110
static void Irc_Client_CmdPrivmsg(const char *prefix, const char *params, const char *trailing)
Definition: cl_irc.cpp:909