1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 */
20 // console.c
21 
22 #if !defined(WIN32) || defined(__MINGW32__)
23 # include <unistd.h>
24 #endif
25 #include <time.h>
26 
27 #include "quakedef.h"
28 #include "thread.h"
29 
30 // for u8_encodech
31 #include "ft2.h"
32 
33 float con_cursorspeed = 4;
34 
35 // lines up from bottom to display
36 int con_backscroll;
37 
38 conbuffer_t con;
39 void *con_mutex = NULL;
40 
41 #define CON_LINES(i) CONBUFFER_LINES(&con, i)
42 #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con)
43 #define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con)
44 
45 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
46 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"};
47 cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
48 
49 cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"};
50 cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
51 cvar_t con_chatpos = {CVAR_SAVE, "con_chatpos","0", "where to put chat (negative: lines from bottom of screen, positive: lines below notify, 0: at top)"};
52 cvar_t con_chatrect = {CVAR_SAVE, "con_chatrect","0", "use con_chatrect_x and _y to position con_notify and con_chat freely instead of con_chatpos"};
53 cvar_t con_chatrect_x = {CVAR_SAVE, "con_chatrect_x","", "where to put chat, relative x coordinate of left edge on screen (use con_chatwidth for width)"};
54 cvar_t con_chatrect_y = {CVAR_SAVE, "con_chatrect_y","", "where to put chat, relative y coordinate of top edge on screen (use con_chat for line count)"};
55 cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"};
56 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
57 cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
58 cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
59 cvar_t con_chatsound = {CVAR_SAVE, "con_chatsound","1", "enables chat sound to play on message"};
60 
61 
62 cvar_t sys_specialcharactertranslation = {0, "sys_specialcharactertranslation", "1", "terminal console conchars to ASCII translation (set to 0 if your conchars.tga is for an 8bit character set or if you want raw output)"};
63 #ifdef WIN32
64 cvar_t sys_colortranslation = {0, "sys_colortranslation", "0", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
65 #else
66 cvar_t sys_colortranslation = {0, "sys_colortranslation", "1", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
67 #endif
68 
69 
70 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
71 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
72 				   "0: add nothing after completion. "
73 				   "1: add the last color after completion. "
74 				   "2: add a quote when starting a quote instead of the color. "
75 				   "4: will replace 1, will force color, even after a quote. "
76 				   "8: ignore non-alphanumerics. "
77 				   "16: ignore spaces. "};
78 #define NICKS_ADD_COLOR 1
79 #define NICKS_ADD_QUOTE 2
80 #define NICKS_FORCE_COLOR 4
81 #define NICKS_ALPHANUMERICS_ONLY 8
82 #define NICKS_NO_SPACES 16
83 
84 cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
85 cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
86 cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
87 
88 cvar_t condump_stripcolors = {CVAR_SAVE, "condump_stripcolors", "0", "strip color codes from console dumps"};
89 
90 int con_linewidth;
91 int con_vislines;
92 
93 qboolean con_initialized;
94 
95 // used for server replies to rcon command
96 lhnetsocket_t *rcon_redirect_sock = NULL;
97 lhnetaddress_t *rcon_redirect_dest = NULL;
98 int rcon_redirect_bufferpos = 0;
99 char rcon_redirect_buffer[1400];
100 qboolean rcon_redirect_proquakeprotocol = false;
101 
102 // generic functions for console buffers
103 
ConBuffer_Init(conbuffer_t * buf,int textsize,int maxlines,mempool_t * mempool)104 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
105 {
106 	buf->active = true;
107 	buf->textsize = textsize;
108 	buf->text = (char *) Mem_Alloc(mempool, textsize);
109 	buf->maxlines = maxlines;
110 	buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
111 	buf->lines_first = 0;
112 	buf->lines_count = 0;
113 }
114 
115 /*! The translation table between the graphical font and plain ASCII  --KB */
116 static char qfont_table[256] = {
117 	'\0', '#',  '#',  '#',  '#',  '.',  '#',  '#',
118 	'#',  9,    10,   '#',  ' ',  13,   '.',  '.',
119 	'[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
120 	'6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
121 	' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
122 	'(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
123 	'0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
124 	'8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
125 	'@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
126 	'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
127 	'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
128 	'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
129 	'`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
130 	'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
131 	'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
132 	'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<',
133 
134 	'<',  '=',  '>',  '#',  '#',  '.',  '#',  '#',
135 	'#',  '#',  ' ',  '#',  ' ',  '>',  '.',  '.',
136 	'[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
137 	'6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
138 	' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
139 	'(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
140 	'0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
141 	'8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
142 	'@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
143 	'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
144 	'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
145 	'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
146 	'`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
147 	'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
148 	'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
149 	'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<'
150 };
151 
152 /*
153 	SanitizeString strips color tags from the string in
154 	and writes the result on string out
155 */
SanitizeString(char * in,char * out)156 static void SanitizeString(char *in, char *out)
157 {
158 	while(*in)
159 	{
160 		if(*in == STRING_COLOR_TAG)
161 		{
162 			++in;
163 			if(!*in)
164 			{
165 				out[0] = STRING_COLOR_TAG;
166 				out[1] = 0;
167 				return;
168 			}
169 			else if (*in >= '0' && *in <= '9') // ^[0-9] found
170 			{
171 				++in;
172 				if(!*in)
173 				{
174 					*out = 0;
175 					return;
176 				} else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
177 					continue;
178 			}
179 			else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
180 			{
181 				if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
182 				{
183 					in+=4;
184 					if (!*in)
185 					{
186 						*out = 0;
187 						return;
188 					} else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
189 						continue;
190 				}
191 				else in--;
192 			}
193 			else if (*in != STRING_COLOR_TAG)
194 				--in;
195 		}
196 		*out = qfont_table[*(unsigned char*)in];
197 		++in;
198 		++out;
199 	}
200 	*out = 0;
201 }
202 
203 /*
204 ================
205 ConBuffer_Clear
206 ================
207 */
ConBuffer_Clear(conbuffer_t * buf)208 void ConBuffer_Clear (conbuffer_t *buf)
209 {
210 	buf->lines_count = 0;
211 }
212 
213 /*
214 ================
215 ConBuffer_Shutdown
216 ================
217 */
ConBuffer_Shutdown(conbuffer_t * buf)218 void ConBuffer_Shutdown(conbuffer_t *buf)
219 {
220 	buf->active = false;
221 	if (buf->text)
222 		Mem_Free(buf->text);
223 	if (buf->lines)
224 		Mem_Free(buf->lines);
225 	buf->text = NULL;
226 	buf->lines = NULL;
227 }
228 
229 /*
230 ================
231 ConBuffer_FixTimes
232 
233 Notifies the console code about the current time
234 (and shifts back times of other entries when the time
235 went backwards)
236 ================
237 */
ConBuffer_FixTimes(conbuffer_t * buf)238 void ConBuffer_FixTimes(conbuffer_t *buf)
239 {
240 	int i;
241 	if(buf->lines_count >= 1)
242 	{
243 		double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
244 		if(diff < 0)
245 		{
246 			for(i = 0; i < buf->lines_count; ++i)
247 				CONBUFFER_LINES(buf, i).addtime += diff;
248 		}
249 	}
250 }
251 
252 /*
253 ================
254 ConBuffer_DeleteLine
255 
256 Deletes the first line from the console history.
257 ================
258 */
ConBuffer_DeleteLine(conbuffer_t * buf)259 void ConBuffer_DeleteLine(conbuffer_t *buf)
260 {
261 	if(buf->lines_count == 0)
262 		return;
263 	--buf->lines_count;
264 	buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
265 }
266 
267 /*
268 ================
269 ConBuffer_DeleteLastLine
270 
271 Deletes the last line from the console history.
272 ================
273 */
ConBuffer_DeleteLastLine(conbuffer_t * buf)274 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
275 {
276 	if(buf->lines_count == 0)
277 		return;
278 	--buf->lines_count;
279 }
280 
281 /*
282 ================
283 ConBuffer_BytesLeft
284 
285 Checks if there is space for a line of the given length, and if yes, returns a
286 pointer to the start of such a space, and NULL otherwise.
287 ================
288 */
ConBuffer_BytesLeft(conbuffer_t * buf,int len)289 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
290 {
291 	if(len > buf->textsize)
292 		return NULL;
293 	if(buf->lines_count == 0)
294 		return buf->text;
295 	else
296 	{
297 		char *firstline_start = buf->lines[buf->lines_first].start;
298 		char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
299 		// the buffer is cyclic, so we first have two cases...
300 		if(firstline_start < lastline_onepastend) // buffer is contiguous
301 		{
302 			// put at end?
303 			if(len <= buf->text + buf->textsize - lastline_onepastend)
304 				return lastline_onepastend;
305 			// put at beginning?
306 			else if(len <= firstline_start - buf->text)
307 				return buf->text;
308 			else
309 				return NULL;
310 		}
311 		else // buffer has a contiguous hole
312 		{
313 			if(len <= firstline_start - lastline_onepastend)
314 				return lastline_onepastend;
315 			else
316 				return NULL;
317 		}
318 	}
319 }
320 
321 /*
322 ================
323 ConBuffer_AddLine
324 
325 Appends a given string as a new line to the console.
326 ================
327 */
ConBuffer_AddLine(conbuffer_t * buf,const char * line,int len,int mask)328 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
329 {
330 	char *putpos;
331 	con_lineinfo_t *p;
332 
333 	// developer_memory 1 during shutdown prints while conbuffer_t is being freed
334 	if (!buf->active)
335 		return;
336 
337 	ConBuffer_FixTimes(buf);
338 
339 	if(len >= buf->textsize)
340 	{
341 		// line too large?
342 		// only display end of line.
343 		line += len - buf->textsize + 1;
344 		len = buf->textsize - 1;
345 	}
346 	while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
347 		ConBuffer_DeleteLine(buf);
348 	memcpy(putpos, line, len);
349 	putpos[len] = 0;
350 	++buf->lines_count;
351 
352 	//fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
353 
354 	p = &CONBUFFER_LINES_LAST(buf);
355 	p->start = putpos;
356 	p->len = len;
357 	p->addtime = cl.time;
358 	p->mask = mask;
359 	p->height = -1; // calculate when needed
360 }
361 
ConBuffer_FindPrevLine(conbuffer_t * buf,int mask_must,int mask_mustnot,int start)362 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
363 {
364 	int i;
365 	if(start == -1)
366 		start = buf->lines_count;
367 	for(i = start - 1; i >= 0; --i)
368 	{
369 		con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
370 
371 		if((l->mask & mask_must) != mask_must)
372 			continue;
373 		if(l->mask & mask_mustnot)
374 			continue;
375 
376 		return i;
377 	}
378 
379 	return -1;
380 }
381 
ConBuffer_GetLine(conbuffer_t * buf,int i)382 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
383 {
384 	static char copybuf[MAX_INPUTLINE]; // client only
385 	con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
386 	size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
387 	strlcpy(copybuf, l->start, sz);
388 	return copybuf;
389 }
390 
391 /*
392 ==============================================================================
393 
394 LOGGING
395 
396 ==============================================================================
397 */
398 
399 /// \name Logging
400 //@{
401 cvar_t log_file = {0, "log_file", "", "filename to log messages to"};
402 cvar_t log_file_stripcolors = {0, "log_file_stripcolors", "0", "strip color codes from log messages"};
403 cvar_t log_dest_udp = {0, "log_dest_udp", "", "UDP address to log messages to (in QW rcon compatible format); multiple destinations can be separated by spaces; DO NOT SPECIFY DNS NAMES HERE"};
404 char log_dest_buffer[1400]; // UDP packet
405 size_t log_dest_buffer_pos;
406 unsigned int log_dest_buffer_appending;
407 char crt_log_file [MAX_OSPATH] = "";
408 qfile_t* logfile = NULL;
409 
410 unsigned char* logqueue = NULL;
411 size_t logq_ind = 0;
412 size_t logq_size = 0;
413 
414 void Log_ConPrint (const char *msg);
415 //@}
Log_DestBuffer_Init(void)416 static void Log_DestBuffer_Init(void)
417 {
418 	memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
419 	log_dest_buffer_pos = 5;
420 }
421 
Log_DestBuffer_Flush_NoLock(void)422 static void Log_DestBuffer_Flush_NoLock(void)
423 {
424 	lhnetaddress_t log_dest_addr;
425 	lhnetsocket_t *log_dest_socket;
426 	const char *s = log_dest_udp.string;
427 	qboolean have_opened_temp_sockets = false;
428 	if(s) if(log_dest_buffer_pos > 5)
429 	{
430 		++log_dest_buffer_appending;
431 		log_dest_buffer[log_dest_buffer_pos++] = 0;
432 
433 		if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
434  		{
435 			have_opened_temp_sockets = true;
436 			NetConn_OpenServerPorts(true);
437 		}
438 
439 		while(COM_ParseToken_Console(&s))
440 			if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
441 			{
442 				log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
443 				if(!log_dest_socket)
444 					log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
445 				if(log_dest_socket)
446 					NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
447 			}
448 
449 		if(have_opened_temp_sockets)
450 			NetConn_CloseServerPorts();
451 		--log_dest_buffer_appending;
452 	}
453 	log_dest_buffer_pos = 0;
454 }
455 
456 /*
457 ====================
458 Log_DestBuffer_Flush
459 ====================
460 */
Log_DestBuffer_Flush(void)461 void Log_DestBuffer_Flush(void)
462 {
463 	if (con_mutex)
464 		Thread_LockMutex(con_mutex);
465 	Log_DestBuffer_Flush_NoLock();
466 	if (con_mutex)
467 		Thread_UnlockMutex(con_mutex);
468 }
469 
Log_Timestamp(const char * desc)470 static const char* Log_Timestamp (const char *desc)
471 {
472 	static char timestamp [128]; // init/shutdown only
473 	time_t crt_time;
474 #if _MSC_VER >= 1400
475 	struct tm crt_tm;
476 #else
477 	struct tm *crt_tm;
478 #endif
479 	char timestring [64];
480 
481 	// Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
482 	time (&crt_time);
483 #if _MSC_VER >= 1400
484 	localtime_s (&crt_tm, &crt_time);
485 	strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
486 #else
487 	crt_tm = localtime (&crt_time);
488 	strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
489 #endif
490 
491 	if (desc != NULL)
492 		dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
493 	else
494 		dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
495 
496 	return timestamp;
497 }
498 
Log_Open(void)499 static void Log_Open (void)
500 {
501 	if (logfile != NULL || log_file.string[0] == '\0')
502 		return;
503 
504 	logfile = FS_OpenRealFile(log_file.string, "a", false);
505 	if (logfile != NULL)
506 	{
507 		strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
508 		FS_Print (logfile, Log_Timestamp ("Log started"));
509 	}
510 }
511 
512 /*
513 ====================
514 Log_Close
515 ====================
516 */
Log_Close(void)517 void Log_Close (void)
518 {
519 	if (logfile == NULL)
520 		return;
521 
522 	FS_Print (logfile, Log_Timestamp ("Log stopped"));
523 	FS_Print (logfile, "\n");
524 	FS_Close (logfile);
525 
526 	logfile = NULL;
527 	crt_log_file[0] = '\0';
528 }
529 
530 
531 /*
532 ====================
533 Log_Start
534 ====================
535 */
Log_Start(void)536 void Log_Start (void)
537 {
538 	size_t pos;
539 	size_t n;
540 	Log_Open ();
541 
542 	// Dump the contents of the log queue into the log file and free it
543 	if (logqueue != NULL)
544 	{
545 		unsigned char *temp = logqueue;
546 		logqueue = NULL;
547 		if(logq_ind != 0)
548 		{
549 			if (logfile != NULL)
550 				FS_Write (logfile, temp, logq_ind);
551 			if(*log_dest_udp.string)
552 			{
553 				for(pos = 0; pos < logq_ind; )
554 				{
555 					if(log_dest_buffer_pos == 0)
556 						Log_DestBuffer_Init();
557 					n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
558 					memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
559 					log_dest_buffer_pos += n;
560 					Log_DestBuffer_Flush_NoLock();
561 					pos += n;
562 				}
563 			}
564 		}
565 		Mem_Free (temp);
566 		logq_ind = 0;
567 		logq_size = 0;
568 	}
569 }
570 
571 
572 
573 /*
574 ================
575 Log_ConPrint
576 ================
577 */
Log_ConPrint(const char * msg)578 void Log_ConPrint (const char *msg)
579 {
580 	static qboolean inprogress = false;
581 
582 	// don't allow feedback loops with memory error reports
583 	if (inprogress)
584 		return;
585 	inprogress = true;
586 
587 	// Until the host is completely initialized, we maintain a log queue
588 	// to store the messages, since the log can't be started before
589 	if (logqueue != NULL)
590 	{
591 		size_t remain = logq_size - logq_ind;
592 		size_t len = strlen (msg);
593 
594 		// If we need to enlarge the log queue
595 		if (len > remain)
596 		{
597 			size_t factor = ((logq_ind + len) / logq_size) + 1;
598 			unsigned char* newqueue;
599 
600 			logq_size *= factor;
601 			newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
602 			memcpy (newqueue, logqueue, logq_ind);
603 			Mem_Free (logqueue);
604 			logqueue = newqueue;
605 			remain = logq_size - logq_ind;
606 		}
607 		memcpy (&logqueue[logq_ind], msg, len);
608 		logq_ind += len;
609 
610 		inprogress = false;
611 		return;
612 	}
613 
614 	// Check if log_file has changed
615 	if (strcmp (crt_log_file, log_file.string) != 0)
616 	{
617 		Log_Close ();
618 		Log_Open ();
619 	}
620 
621 	// If a log file is available
622 	if (logfile != NULL)
623 	{
624 		if (log_file_stripcolors.integer)
625 		{
626 			// sanitize msg
627 			size_t len = strlen(msg);
628 			char* sanitizedmsg = (char*)Mem_Alloc(tempmempool, len + 1);
629 			memcpy (sanitizedmsg, msg, len);
630 			SanitizeString(sanitizedmsg, sanitizedmsg); // SanitizeString's in pointer is always ahead of the out pointer, so this should work.
631 			FS_Print (logfile, sanitizedmsg);
632 			Mem_Free(sanitizedmsg);
633 		}
634 		else
635 		{
636 			FS_Print (logfile, msg);
637 		}
638 	}
639 
640 	inprogress = false;
641 }
642 
643 
644 /*
645 ================
646 Log_Printf
647 ================
648 */
Log_Printf(const char * logfilename,const char * fmt,...)649 void Log_Printf (const char *logfilename, const char *fmt, ...)
650 {
651 	qfile_t *file;
652 
653 	file = FS_OpenRealFile(logfilename, "a", true);
654 	if (file != NULL)
655 	{
656 		va_list argptr;
657 
658 		va_start (argptr, fmt);
659 		FS_VPrintf (file, fmt, argptr);
660 		va_end (argptr);
661 
662 		FS_Close (file);
663 	}
664 }
665 
666 
667 /*
668 ==============================================================================
669 
670 CONSOLE
671 
672 ==============================================================================
673 */
674 
675 /*
676 ================
677 Con_ToggleConsole_f
678 ================
679 */
Con_ToggleConsole_f(void)680 void Con_ToggleConsole_f (void)
681 {
682 	if (COM_CheckParm ("-noconsole"))
683 		if (!(key_consoleactive & KEY_CONSOLEACTIVE_USER))
684 			return; // only allow the key bind to turn off console
685 
686 	// toggle the 'user wants console' bit
687 	key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
688 	Con_ClearNotify();
689 }
690 
691 /*
692 ================
693 Con_ClearNotify
694 ================
695 */
Con_ClearNotify(void)696 void Con_ClearNotify (void)
697 {
698 	int i;
699 	for(i = 0; i < CON_LINES_COUNT; ++i)
700 		if(!(CON_LINES(i).mask & CON_MASK_CHAT))
701 			CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
702 }
703 
704 
705 /*
706 ================
707 Con_MessageMode_f
708 ================
709 */
Con_MessageMode_f(void)710 static void Con_MessageMode_f (void)
711 {
712 	key_dest = key_message;
713 	chat_mode = 0; // "say"
714 	if(Cmd_Argc() > 1)
715 	{
716 		dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
717 		chat_bufferlen = (unsigned int)strlen(chat_buffer);
718 	}
719 }
720 
721 
722 /*
723 ================
724 Con_MessageMode2_f
725 ================
726 */
Con_MessageMode2_f(void)727 static void Con_MessageMode2_f (void)
728 {
729 	key_dest = key_message;
730 	chat_mode = 1; // "say_team"
731 	if(Cmd_Argc() > 1)
732 	{
733 		dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
734 		chat_bufferlen = (unsigned int)strlen(chat_buffer);
735 	}
736 }
737 
738 /*
739 ================
740 Con_CommandMode_f
741 ================
742 */
Con_CommandMode_f(void)743 static void Con_CommandMode_f (void)
744 {
745 	key_dest = key_message;
746 	if(Cmd_Argc() > 1)
747 	{
748 		dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
749 		chat_bufferlen = (unsigned int)strlen(chat_buffer);
750 	}
751 	chat_mode = -1; // command
752 }
753 
754 /*
755 ================
756 Con_CheckResize
757 ================
758 */
Con_CheckResize(void)759 void Con_CheckResize (void)
760 {
761 	int i, width;
762 	float f;
763 
764 	f = bound(1, con_textsize.value, 128);
765 	if(f != con_textsize.value)
766 		Cvar_SetValueQuick(&con_textsize, f);
767 	width = (int)floor(vid_conwidth.value / con_textsize.value);
768 	width = bound(1, width, con.textsize/4);
769 		// FIXME uses con in a non abstracted way
770 
771 	if (width == con_linewidth)
772 		return;
773 
774 	con_linewidth = width;
775 
776 	for(i = 0; i < CON_LINES_COUNT; ++i)
777 		CON_LINES(i).height = -1; // recalculate when next needed
778 
779 	Con_ClearNotify();
780 	con_backscroll = 0;
781 }
782 
783 //[515]: the simplest command ever
784 //LordHavoc: not so simple after I made it print usage...
Con_Maps_f(void)785 static void Con_Maps_f (void)
786 {
787 	if (Cmd_Argc() > 2)
788 	{
789 		Con_Printf("usage: maps [mapnameprefix]\n");
790 		return;
791 	}
792 	else if (Cmd_Argc() == 2)
793 		GetMapList(Cmd_Argv(1), NULL, 0);
794 	else
795 		GetMapList("", NULL, 0);
796 }
797 
Con_ConDump_f(void)798 static void Con_ConDump_f (void)
799 {
800 	int i;
801 	qfile_t *file;
802 	if (Cmd_Argc() != 2)
803 	{
804 		Con_Printf("usage: condump <filename>\n");
805 		return;
806 	}
807 	file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
808 	if (!file)
809 	{
810 		Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
811 		return;
812 	}
813 	if (con_mutex) Thread_LockMutex(con_mutex);
814 	for(i = 0; i < CON_LINES_COUNT; ++i)
815 	{
816 		if (condump_stripcolors.integer)
817 		{
818 			// sanitize msg
819 			size_t len = CON_LINES(i).len;
820 			char* sanitizedmsg = (char*)Mem_Alloc(tempmempool, len + 1);
821 			memcpy (sanitizedmsg, CON_LINES(i).start, len);
822 			SanitizeString(sanitizedmsg, sanitizedmsg); // SanitizeString's in pointer is always ahead of the out pointer, so this should work.
823 			FS_Write(file, sanitizedmsg, strlen(sanitizedmsg));
824 			Mem_Free(sanitizedmsg);
825 		}
826 		else
827 		{
828 			FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
829 		}
830 		FS_Write(file, "\n", 1);
831 	}
832 	if (con_mutex) Thread_UnlockMutex(con_mutex);
833 	FS_Close(file);
834 }
835 
Con_Clear_f(void)836 void Con_Clear_f (void)
837 {
838 	if (con_mutex) Thread_LockMutex(con_mutex);
839 	ConBuffer_Clear(&con);
840 	if (con_mutex) Thread_UnlockMutex(con_mutex);
841 }
842 
843 /*
844 ================
845 Con_Init
846 ================
847 */
Con_Init(void)848 void Con_Init (void)
849 {
850 	con_linewidth = 80;
851 	ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
852 	if (Thread_HasThreads())
853 		con_mutex = Thread_CreateMutex();
854 
855 	// Allocate a log queue, this will be freed after configs are parsed
856 	logq_size = MAX_INPUTLINE;
857 	logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
858 	logq_ind = 0;
859 
860 	Cvar_RegisterVariable (&sys_colortranslation);
861 	Cvar_RegisterVariable (&sys_specialcharactertranslation);
862 
863 	Cvar_RegisterVariable (&log_file);
864 	Cvar_RegisterVariable (&log_file_stripcolors);
865 	Cvar_RegisterVariable (&log_dest_udp);
866 
867 	// support for the classic Quake option
868 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
869 	if (COM_CheckParm ("-condebug") != 0)
870 		Cvar_SetQuick (&log_file, "qconsole.log");
871 
872 	// register our cvars
873 	Cvar_RegisterVariable (&con_chat);
874 	Cvar_RegisterVariable (&con_chatpos);
875 	Cvar_RegisterVariable (&con_chatrect_x);
876 	Cvar_RegisterVariable (&con_chatrect_y);
877 	Cvar_RegisterVariable (&con_chatrect);
878 	Cvar_RegisterVariable (&con_chatsize);
879 	Cvar_RegisterVariable (&con_chattime);
880 	Cvar_RegisterVariable (&con_chatwidth);
881 	Cvar_RegisterVariable (&con_notify);
882 	Cvar_RegisterVariable (&con_notifyalign);
883 	Cvar_RegisterVariable (&con_notifysize);
884 	Cvar_RegisterVariable (&con_notifytime);
885 	Cvar_RegisterVariable (&con_textsize);
886 	Cvar_RegisterVariable (&con_chatsound);
887 
888 	// --blub
889 	Cvar_RegisterVariable (&con_nickcompletion);
890 	Cvar_RegisterVariable (&con_nickcompletion_flags);
891 
892 	Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
893 	Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
894 	Cvar_RegisterVariable (&con_completion_exec); // *.cfg
895 
896 	Cvar_RegisterVariable (&condump_stripcolors);
897 
898 	// register our commands
899 	Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
900 	Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
901 	Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
902 	Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
903 	Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
904 	Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
905 	Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
906 
907 	con_initialized = true;
908 	Con_DPrint("Console initialized.\n");
909 }
910 
Con_Shutdown(void)911 void Con_Shutdown (void)
912 {
913 	if (con_mutex) Thread_LockMutex(con_mutex);
914 	ConBuffer_Shutdown(&con);
915 	if (con_mutex) Thread_UnlockMutex(con_mutex);
916 	if (con_mutex) Thread_DestroyMutex(con_mutex);con_mutex = NULL;
917 }
918 
919 /*
920 ================
921 Con_PrintToHistory
922 
923 Handles cursor positioning, line wrapping, etc
924 All console printing must go through this in order to be displayed
925 If no console is visible, the notify window will pop up.
926 ================
927 */
Con_PrintToHistory(const char * txt,int mask)928 static void Con_PrintToHistory(const char *txt, int mask)
929 {
930 	// process:
931 	//   \n goes to next line
932 	//   \r deletes current line and makes a new one
933 
934 	static int cr_pending = 0;
935 	static char buf[CON_TEXTSIZE]; // con_mutex
936 	static int bufpos = 0;
937 
938 	if(!con.text) // FIXME uses a non-abstracted property of con
939 		return;
940 
941 	for(; *txt; ++txt)
942 	{
943 		if(cr_pending)
944 		{
945 			ConBuffer_DeleteLastLine(&con);
946 			cr_pending = 0;
947 		}
948 		switch(*txt)
949 		{
950 			case 0:
951 				break;
952 			case '\r':
953 				ConBuffer_AddLine(&con, buf, bufpos, mask);
954 				bufpos = 0;
955 				cr_pending = 1;
956 				break;
957 			case '\n':
958 				ConBuffer_AddLine(&con, buf, bufpos, mask);
959 				bufpos = 0;
960 				break;
961 			default:
962 				buf[bufpos++] = *txt;
963 				if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
964 				{
965 					ConBuffer_AddLine(&con, buf, bufpos, mask);
966 					bufpos = 0;
967 				}
968 				break;
969 		}
970 	}
971 }
972 
Con_Rcon_Redirect_Init(lhnetsocket_t * sock,lhnetaddress_t * dest,qboolean proquakeprotocol)973 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol)
974 {
975 	rcon_redirect_sock = sock;
976 	rcon_redirect_dest = dest;
977 	rcon_redirect_proquakeprotocol = proquakeprotocol;
978 	if (rcon_redirect_proquakeprotocol)
979 	{
980 		// reserve space for the packet header
981 		rcon_redirect_buffer[0] = 0;
982 		rcon_redirect_buffer[1] = 0;
983 		rcon_redirect_buffer[2] = 0;
984 		rcon_redirect_buffer[3] = 0;
985 		// this is a reply to a CCREQ_RCON
986 		rcon_redirect_buffer[4] = (unsigned char)CCREP_RCON;
987 	}
988 	else
989 		memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
990 	rcon_redirect_bufferpos = 5;
991 }
992 
Con_Rcon_Redirect_Flush(void)993 static void Con_Rcon_Redirect_Flush(void)
994 {
995 	if(rcon_redirect_sock)
996 	{
997 		rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
998 		if (rcon_redirect_proquakeprotocol)
999 		{
1000 			// update the length in the packet header
1001 			StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
1002 		}
1003 		NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
1004 	}
1005 	memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
1006 	rcon_redirect_bufferpos = 5;
1007 	rcon_redirect_proquakeprotocol = false;
1008 }
1009 
Con_Rcon_Redirect_End(void)1010 void Con_Rcon_Redirect_End(void)
1011 {
1012 	Con_Rcon_Redirect_Flush();
1013 	rcon_redirect_dest = NULL;
1014 	rcon_redirect_sock = NULL;
1015 }
1016 
Con_Rcon_Redirect_Abort(void)1017 void Con_Rcon_Redirect_Abort(void)
1018 {
1019 	rcon_redirect_dest = NULL;
1020 	rcon_redirect_sock = NULL;
1021 }
1022 
1023 /*
1024 ================
1025 Con_Rcon_AddChar
1026 ================
1027 */
1028 /// Adds a character to the rcon buffer.
Con_Rcon_AddChar(int c)1029 static void Con_Rcon_AddChar(int c)
1030 {
1031 	if(log_dest_buffer_appending)
1032 		return;
1033 	++log_dest_buffer_appending;
1034 
1035 	// if this print is in response to an rcon command, add the character
1036 	// to the rcon redirect buffer
1037 
1038 	if (rcon_redirect_dest)
1039 	{
1040 		rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
1041 		if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
1042 			Con_Rcon_Redirect_Flush();
1043 	}
1044 	else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
1045 	{
1046 		if(log_dest_buffer_pos == 0)
1047 			Log_DestBuffer_Init();
1048 		log_dest_buffer[log_dest_buffer_pos++] = c;
1049 		if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
1050 			Log_DestBuffer_Flush_NoLock();
1051 	}
1052 	else
1053 		log_dest_buffer_pos = 0;
1054 
1055 	--log_dest_buffer_appending;
1056 }
1057 
1058 /**
1059  * Convert an RGB color to its nearest quake color.
1060  * I'll cheat on this a bit by translating the colors to HSV first,
1061  * S and V decide if it's black or white, otherwise, H will decide the
1062  * actual color.
1063  * @param _r Red (0-255)
1064  * @param _g Green (0-255)
1065  * @param _b Blue (0-255)
1066  * @return A quake color character.
1067  */
Sys_Con_NearestColor(const unsigned char _r,const unsigned char _g,const unsigned char _b)1068 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
1069 {
1070 	float r = ((float)_r)/255.0;
1071 	float g = ((float)_g)/255.0;
1072 	float b = ((float)_b)/255.0;
1073 	float min = min(r, min(g, b));
1074 	float max = max(r, max(g, b));
1075 
1076 	int h; ///< Hue angle [0,360]
1077 	float s; ///< Saturation [0,1]
1078 	float v = max; ///< In HSV v == max [0,1]
1079 
1080 	if(max == min)
1081 		s = 0;
1082 	else
1083 		s = 1.0 - (min/max);
1084 
1085 	// Saturation threshold. We now say 0.2 is the minimum value for a color!
1086 	if(s < 0.2)
1087 	{
1088 		// If the value is less than half, return a black color code.
1089 		// Otherwise return a white one.
1090 		if(v < 0.5)
1091 			return '0';
1092 		return '7';
1093 	}
1094 
1095 	// Let's get the hue angle to define some colors:
1096 	if(max == min)
1097 		h = 0;
1098 	else if(max == r)
1099 		h = (int)(60.0 * (g-b)/(max-min))%360;
1100 	else if(max == g)
1101 		h = (int)(60.0 * (b-r)/(max-min) + 120);
1102 	else // if(max == b) redundant check
1103 		h = (int)(60.0 * (r-g)/(max-min) + 240);
1104 
1105 	if(h < 36) // *red* to orange
1106 		return '1';
1107 	else if(h < 80) // orange over *yellow* to evilish-bright-green
1108 		return '3';
1109 	else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1110 		return '2';
1111 	else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1112 		return '5';
1113 	else if(h < 270) // darkish blue over *dark blue* to cool purple
1114 		return '4';
1115 	else if(h < 330) // cool purple over *purple* to ugly swiny red
1116 		return '6';
1117 	else // ugly red to red closes the circly
1118 		return '1';
1119 }
1120 
1121 /*
1122 ================
1123 Con_MaskPrint
1124 ================
1125 */
1126 extern cvar_t timestamps;
1127 extern cvar_t timeformat;
1128 extern qboolean sys_nostdout;
Con_MaskPrint(int additionalmask,const char * msg)1129 void Con_MaskPrint(int additionalmask, const char *msg)
1130 {
1131 	static int mask = 0;
1132 	static int index = 0;
1133 	static char line[MAX_INPUTLINE];
1134 
1135 	if (con_mutex)
1136 		Thread_LockMutex(con_mutex);
1137 
1138 	for (;*msg;msg++)
1139 	{
1140 		Con_Rcon_AddChar(*msg);
1141 		// if this is the beginning of a new line, print timestamp
1142 		if (index == 0)
1143 		{
1144 			const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1145 			// reset the color
1146 			// FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1147 			line[index++] = STRING_COLOR_TAG;
1148 			// assert( STRING_COLOR_DEFAULT < 10 )
1149 			line[index++] = STRING_COLOR_DEFAULT + '0';
1150 			// special color codes for chat messages must always come first
1151 			// for Con_PrintToHistory to work properly
1152 			if (*msg == 1 || *msg == 2 || *msg == 3)
1153 			{
1154 				// play talk wav
1155 				if (*msg == 1)
1156 				{
1157 					if (con_chatsound.value)
1158 					{
1159 						if(IS_NEXUIZ_DERIVED(gamemode))
1160 						{
1161 							if(msg[1] == '\r' && cl.foundtalk2wav)
1162 								S_LocalSound ("sound/misc/talk2.wav");
1163 							else
1164 								S_LocalSound ("sound/misc/talk.wav");
1165 						}
1166 						else
1167 						{
1168 							if (msg[1] == '(' && cl.foundtalk2wav)
1169 								S_LocalSound ("sound/misc/talk2.wav");
1170 							else
1171 								S_LocalSound ("sound/misc/talk.wav");
1172 						}
1173 					}
1174 				}
1175 
1176 				// Send to chatbox for say/tell (1) and messages (3)
1177 				// 3 is just so that a message can be sent to the chatbox without a sound.
1178 				if (*msg == 1 || *msg == 3)
1179 					mask = CON_MASK_CHAT;
1180 
1181 				line[index++] = STRING_COLOR_TAG;
1182 				line[index++] = '3';
1183 				msg++;
1184 				Con_Rcon_AddChar(*msg);
1185 			}
1186 			// store timestamp
1187 			for (;*timestamp;index++, timestamp++)
1188 				if (index < (int)sizeof(line) - 2)
1189 					line[index] = *timestamp;
1190 			// add the mask
1191 			mask |= additionalmask;
1192 		}
1193 		// append the character
1194 		line[index++] = *msg;
1195 		// if this is a newline character, we have a complete line to print
1196 		if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1197 		{
1198 			// terminate the line
1199 			line[index] = 0;
1200 			// send to log file
1201 			Log_ConPrint(line);
1202 			// send to scrollable buffer
1203 			if (con_initialized && cls.state != ca_dedicated)
1204 			{
1205 				Con_PrintToHistory(line, mask);
1206 			}
1207 			// send to terminal or dedicated server window
1208 			if (!sys_nostdout)
1209 			if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1210 			{
1211 				if(sys_specialcharactertranslation.integer)
1212 				{
1213 					char *p;
1214 					const char *q;
1215 					p = line;
1216 					while(*p)
1217 					{
1218 						int ch = u8_getchar(p, &q);
1219 						if(ch >= 0xE000 && ch <= 0xE0FF && ((unsigned char) qfont_table[ch - 0xE000]) >= 0x20)
1220 						{
1221 							*p = qfont_table[ch - 0xE000];
1222 							if(q > p+1)
1223 								memmove(p+1, q, strlen(q)+1);
1224 							p = p + 1;
1225 						}
1226 						else
1227 							p = p + (q - p);
1228 					}
1229 				}
1230 
1231 				if(sys_colortranslation.integer == 1) // ANSI
1232 				{
1233 					static char printline[MAX_INPUTLINE * 4 + 3];
1234 						// 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1235 						// a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1236 					int lastcolor = 0;
1237 					const char *in;
1238 					char *out;
1239 					int color;
1240 					for(in = line, out = printline; *in; ++in)
1241 					{
1242 						switch(*in)
1243 						{
1244 							case STRING_COLOR_TAG:
1245 								if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1246 								{
1247 									char r = tolower(in[2]);
1248 									char g = tolower(in[3]);
1249 									char b = tolower(in[4]);
1250 									// it's a hex digit already, so the else part needs no check --blub
1251 									if(isdigit(r)) r -= '0';
1252 									else r -= 87;
1253 									if(isdigit(g)) g -= '0';
1254 									else g -= 87;
1255 									if(isdigit(b)) b -= '0';
1256 									else b -= 87;
1257 
1258 									color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1259 									in += 3; // 3 only, the switch down there does the fourth
1260 								}
1261 								else
1262 									color = in[1];
1263 
1264 								switch(color)
1265 								{
1266 									case STRING_COLOR_TAG:
1267 										++in;
1268 										*out++ = STRING_COLOR_TAG;
1269 										break;
1270 									case '0':
1271 									case '7':
1272 										// normal color
1273 										++in;
1274 										if(lastcolor == 0) break; else lastcolor = 0;
1275 										*out++ = 0x1B; *out++ = '['; *out++ = 'm';
1276 										break;
1277 									case '1':
1278 										// light red
1279 										++in;
1280 										if(lastcolor == 1) break; else lastcolor = 1;
1281 										*out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1282 										break;
1283 									case '2':
1284 										// light green
1285 										++in;
1286 										if(lastcolor == 2) break; else lastcolor = 2;
1287 										*out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1288 										break;
1289 									case '3':
1290 										// yellow
1291 										++in;
1292 										if(lastcolor == 3) break; else lastcolor = 3;
1293 										*out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1294 										break;
1295 									case '4':
1296 										// light blue
1297 										++in;
1298 										if(lastcolor == 4) break; else lastcolor = 4;
1299 										*out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1300 										break;
1301 									case '5':
1302 										// light cyan
1303 										++in;
1304 										if(lastcolor == 5) break; else lastcolor = 5;
1305 										*out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1306 										break;
1307 									case '6':
1308 										// light magenta
1309 										++in;
1310 										if(lastcolor == 6) break; else lastcolor = 6;
1311 										*out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1312 										break;
1313 									// 7 handled above
1314 									case '8':
1315 									case '9':
1316 										// bold normal color
1317 										++in;
1318 										if(lastcolor == 8) break; else lastcolor = 8;
1319 										*out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1320 										break;
1321 									default:
1322 										*out++ = STRING_COLOR_TAG;
1323 										break;
1324 								}
1325 								break;
1326 							case '\n':
1327 								if(lastcolor != 0)
1328 								{
1329 									*out++ = 0x1B; *out++ = '['; *out++ = 'm';
1330 									lastcolor = 0;
1331 								}
1332 								*out++ = *in;
1333 								break;
1334 							default:
1335 								*out++ = *in;
1336 								break;
1337 						}
1338 					}
1339 					if(lastcolor != 0)
1340 					{
1341 						*out++ = 0x1B;
1342 						*out++ = '[';
1343 						*out++ = 'm';
1344 					}
1345 					*out++ = 0;
1346 					Sys_PrintToTerminal(printline);
1347 				}
1348 				else if(sys_colortranslation.integer == 2) // Quake
1349 				{
1350 					Sys_PrintToTerminal(line);
1351 				}
1352 				else // strip
1353 				{
1354 					static char printline[MAX_INPUTLINE]; // it can only get shorter here
1355 					const char *in;
1356 					char *out;
1357 					for(in = line, out = printline; *in; ++in)
1358 					{
1359 						switch(*in)
1360 						{
1361 							case STRING_COLOR_TAG:
1362 								switch(in[1])
1363 								{
1364 									case STRING_COLOR_RGB_TAG_CHAR:
1365 										if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1366 										{
1367 											in+=4;
1368 											break;
1369 										}
1370 										*out++ = STRING_COLOR_TAG;
1371 										*out++ = STRING_COLOR_RGB_TAG_CHAR;
1372 										++in;
1373 										break;
1374 									case STRING_COLOR_TAG:
1375 										++in;
1376 										*out++ = STRING_COLOR_TAG;
1377 										break;
1378 									case '0':
1379 									case '1':
1380 									case '2':
1381 									case '3':
1382 									case '4':
1383 									case '5':
1384 									case '6':
1385 									case '7':
1386 									case '8':
1387 									case '9':
1388 										++in;
1389 										break;
1390 									default:
1391 										*out++ = STRING_COLOR_TAG;
1392 										break;
1393 								}
1394 								break;
1395 							default:
1396 								*out++ = *in;
1397 								break;
1398 						}
1399 					}
1400 					*out++ = 0;
1401 					Sys_PrintToTerminal(printline);
1402 				}
1403 			}
1404 			// empty the line buffer
1405 			index = 0;
1406 			mask = 0;
1407 		}
1408 	}
1409 
1410 	if (con_mutex)
1411 		Thread_UnlockMutex(con_mutex);
1412 }
1413 
1414 /*
1415 ================
1416 Con_MaskPrintf
1417 ================
1418 */
Con_MaskPrintf(int mask,const char * fmt,...)1419 void Con_MaskPrintf(int mask, const char *fmt, ...)
1420 {
1421 	va_list argptr;
1422 	char msg[MAX_INPUTLINE];
1423 
1424 	va_start(argptr,fmt);
1425 	dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1426 	va_end(argptr);
1427 
1428 	Con_MaskPrint(mask, msg);
1429 }
1430 
1431 /*
1432 ================
1433 Con_Print
1434 ================
1435 */
Con_Print(const char * msg)1436 void Con_Print(const char *msg)
1437 {
1438 	Con_MaskPrint(CON_MASK_PRINT, msg);
1439 }
1440 
1441 /*
1442 ================
1443 Con_Printf
1444 ================
1445 */
Con_Printf(const char * fmt,...)1446 void Con_Printf(const char *fmt, ...)
1447 {
1448 	va_list argptr;
1449 	char msg[MAX_INPUTLINE];
1450 
1451 	va_start(argptr,fmt);
1452 	dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1453 	va_end(argptr);
1454 
1455 	Con_MaskPrint(CON_MASK_PRINT, msg);
1456 }
1457 
1458 /*
1459 ================
1460 Con_DPrint
1461 ================
1462 */
Con_DPrint(const char * msg)1463 void Con_DPrint(const char *msg)
1464 {
1465 	if(developer.integer < 0) // at 0, we still add to the buffer but hide
1466 		return;
1467 
1468 	Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1469 }
1470 
1471 /*
1472 ================
1473 Con_DPrintf
1474 ================
1475 */
Con_DPrintf(const char * fmt,...)1476 void Con_DPrintf(const char *fmt, ...)
1477 {
1478 	va_list argptr;
1479 	char msg[MAX_INPUTLINE];
1480 
1481 	if(developer.integer < 0) // at 0, we still add to the buffer but hide
1482 		return;
1483 
1484 	va_start(argptr,fmt);
1485 	dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1486 	va_end(argptr);
1487 
1488 	Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1489 }
1490 
1491 
1492 /*
1493 ==============================================================================
1494 
1495 DRAWING
1496 
1497 ==============================================================================
1498 */
1499 
1500 /*
1501 ================
1502 Con_DrawInput
1503 
1504 The input line scrolls horizontally if typing goes beyond the right edge
1505 
1506 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1507 ================
1508 */
Con_DrawInput(void)1509 static void Con_DrawInput (void)
1510 {
1511 	int		y;
1512 	int		i;
1513 	char text[sizeof(key_line)+5+1]; // space for ^^xRGB too
1514 	float x, xo;
1515 	size_t len_out;
1516 	int col_out;
1517 
1518 	if (!key_consoleactive)
1519 		return;		// don't draw anything
1520 
1521 	strlcpy(text, key_line, sizeof(text));
1522 
1523 	// Advanced Console Editing by Radix radix@planetquake.com
1524 	// Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1525 
1526 	y = (int)strlen(text);
1527 
1528 	// make the color code visible when the cursor is inside it
1529 	if(text[key_linepos] != 0)
1530 	{
1531 		for(i=1; i < 5 && key_linepos - i > 0; ++i)
1532 			if(text[key_linepos-i] == STRING_COLOR_TAG)
1533 			{
1534 				int caret_pos, ofs = 0;
1535 				caret_pos = key_linepos - i;
1536 				if(i == 1 && text[caret_pos+1] == STRING_COLOR_TAG)
1537 					ofs = 1;
1538 				else if(i == 1 && isdigit(text[caret_pos+1]))
1539 					ofs = 2;
1540 				else if(text[caret_pos+1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(text[caret_pos+2]) && isxdigit(text[caret_pos+3]) && isxdigit(text[caret_pos+4]))
1541 					ofs = 5;
1542 				if(ofs && (size_t)(y + ofs + 1) < sizeof(text))
1543 				{
1544 					int carets = 1;
1545 					while(caret_pos - carets >= 1 && text[caret_pos - carets] == STRING_COLOR_TAG)
1546 						++carets;
1547 					if(carets & 1)
1548 					{
1549 						// str^2ing (displayed as string) --> str^2^^2ing (displayed as str^2ing)
1550 						// str^^ing (displayed as str^ing) --> str^^^^ing (displayed as str^^ing)
1551 						memmove(&text[caret_pos + ofs + 1], &text[caret_pos], y - caret_pos);
1552 						text[caret_pos + ofs] = STRING_COLOR_TAG;
1553 						y += ofs + 1;
1554 						text[y] = 0;
1555 					}
1556 				}
1557 				break;
1558 			}
1559 	}
1560 
1561 	len_out = key_linepos;
1562 	col_out = -1;
1563 	xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1564 	x = vid_conwidth.value * 0.95 - xo; // scroll
1565 	if(x >= 0)
1566 		x = 0;
1567 
1568 	// draw it
1569 	DrawQ_String(x, con_vislines - con_textsize.value*2, text, y + 3, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, FONT_CONSOLE );
1570 
1571 	// draw a cursor on top of this
1572 	if ((int)(realtime*con_cursorspeed) & 1)		// cursor is visible
1573 	{
1574 		if (!utf8_enable.integer)
1575 		{
1576 			text[0] = 11 + 130 * key_insert;	// either solid or triangle facing right
1577 			text[1] = 0;
1578 		}
1579 		else
1580 		{
1581 			size_t len;
1582 			const char *curbuf;
1583 			char charbuf16[16];
1584 			curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1585 			memcpy(text, curbuf, len);
1586 			text[len] = 0;
1587 		}
1588 		DrawQ_String(x + xo, con_vislines - con_textsize.value*2, text, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &col_out, false, FONT_CONSOLE);
1589 	}
1590 }
1591 
1592 typedef struct
1593 {
1594 	dp_font_t *font;
1595 	float alignment; // 0 = left, 0.5 = center, 1 = right
1596 	float fontsize;
1597 	float x;
1598 	float y;
1599 	float width;
1600 	float ymin, ymax;
1601 	const char *continuationString;
1602 
1603 	// PRIVATE:
1604 	int colorindex; // init to -1
1605 }
1606 con_text_info_t;
1607 
Con_WordWidthFunc(void * passthrough,const char * w,size_t * length,float maxWidth)1608 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1609 {
1610 	con_text_info_t *ti = (con_text_info_t *) passthrough;
1611 	if(w == NULL)
1612 	{
1613 		ti->colorindex = -1;
1614 		return ti->fontsize * ti->font->maxwidth;
1615 	}
1616 	if(maxWidth >= 0)
1617 		return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1618 	else if(maxWidth == -1)
1619 		return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1620 	else
1621 	{
1622 		Sys_PrintfToTerminal("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1623 		// Note: this is NOT a Con_Printf, as it could print recursively
1624 		return 0;
1625 	}
1626 }
1627 
Con_CountLineFunc(void * passthrough,const char * line,size_t length,float width,qboolean isContinuation)1628 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1629 {
1630 	(void) passthrough;
1631 	(void) line;
1632 	(void) length;
1633 	(void) width;
1634 	(void) isContinuation;
1635 	return 1;
1636 }
1637 
Con_DisplayLineFunc(void * passthrough,const char * line,size_t length,float width,qboolean isContinuation)1638 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1639 {
1640 	con_text_info_t *ti = (con_text_info_t *) passthrough;
1641 
1642 	if(ti->y < ti->ymin - 0.001)
1643 		(void) 0;
1644 	else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1645 		(void) 0;
1646 	else
1647 	{
1648 		int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1649 		if(isContinuation && *ti->continuationString)
1650 			x = (int) DrawQ_String(x, ti->y, ti->continuationString, strlen(ti->continuationString), ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, ti->font);
1651 		if(length > 0)
1652 			DrawQ_String(x, ti->y, line, length, ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, &(ti->colorindex), false, ti->font);
1653 	}
1654 
1655 	ti->y += ti->fontsize;
1656 	return 1;
1657 }
1658 
Con_DrawNotifyRect(int mask_must,int mask_mustnot,float maxage,float x,float y,float width,float height,float fontsize,float alignment_x,float alignment_y,const char * continuationString)1659 static int Con_DrawNotifyRect(int mask_must, int mask_mustnot, float maxage, float x, float y, float width, float height, float fontsize, float alignment_x, float alignment_y, const char *continuationString)
1660 {
1661 	int i;
1662 	int lines = 0;
1663 	int maxlines = (int) floor(height / fontsize + 0.01f);
1664 	int startidx;
1665 	int nskip = 0;
1666 	int continuationWidth = 0;
1667 	size_t len;
1668 	double t = cl.time; // saved so it won't change
1669 	con_text_info_t ti;
1670 
1671 	ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1672 	ti.fontsize = fontsize;
1673 	ti.alignment = alignment_x;
1674 	ti.width = width;
1675 	ti.ymin = y;
1676 	ti.ymax = y + height;
1677 	ti.continuationString = continuationString;
1678 
1679 	len = 0;
1680 	Con_WordWidthFunc(&ti, NULL, &len, -1);
1681 	len = strlen(continuationString);
1682 	continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &len, -1);
1683 
1684 	// first find the first line to draw by backwards iterating and word wrapping to find their length...
1685 	startidx = CON_LINES_COUNT;
1686 	for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1687 	{
1688 		con_lineinfo_t *l = &CON_LINES(i);
1689 		int mylines;
1690 
1691 		if((l->mask & mask_must) != mask_must)
1692 			continue;
1693 		if(l->mask & mask_mustnot)
1694 			continue;
1695 		if(maxage && (l->addtime < t - maxage))
1696 			continue;
1697 
1698 		// WE FOUND ONE!
1699 		// Calculate its actual height...
1700 		mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1701 		if(lines + mylines >= maxlines)
1702 		{
1703 			nskip = lines + mylines - maxlines;
1704 			lines = maxlines;
1705 			startidx = i;
1706 			break;
1707 		}
1708 		lines += mylines;
1709 		startidx = i;
1710 	}
1711 
1712 	// then center according to the calculated amount of lines...
1713 	ti.x = x;
1714 	ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1715 
1716 	// then actually draw
1717 	for(i = startidx; i < CON_LINES_COUNT; ++i)
1718 	{
1719 		con_lineinfo_t *l = &CON_LINES(i);
1720 
1721 		if((l->mask & mask_must) != mask_must)
1722 			continue;
1723 		if(l->mask & mask_mustnot)
1724 			continue;
1725 		if(maxage && (l->addtime < t - maxage))
1726 			continue;
1727 
1728 		COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1729 	}
1730 
1731 	return lines;
1732 }
1733 
1734 /*
1735 ================
1736 Con_DrawNotify
1737 
1738 Draws the last few lines of output transparently over the game top
1739 ================
1740 */
Con_DrawNotify(void)1741 void Con_DrawNotify (void)
1742 {
1743 	float	x, v, xr;
1744 	float chatstart, notifystart, inputsize, height;
1745 	float align;
1746 	char	temptext[MAX_INPUTLINE];
1747 	int numChatlines;
1748 	int chatpos;
1749 
1750 	if (con_mutex) Thread_LockMutex(con_mutex);
1751 	ConBuffer_FixTimes(&con);
1752 
1753 	numChatlines = con_chat.integer;
1754 
1755 	chatpos = con_chatpos.integer;
1756 
1757 	if (con_notify.integer < 0)
1758 		Cvar_SetValueQuick(&con_notify, 0);
1759 	if (gamemode == GAME_TRANSFUSION)
1760 		v = 8; // vertical offset
1761 	else
1762 		v = 0;
1763 
1764 	// GAME_NEXUIZ: center, otherwise left justify
1765 	align = con_notifyalign.value;
1766 	if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1767 	{
1768 		if(IS_OLDNEXUIZ_DERIVED(gamemode))
1769 			align = 0.5;
1770 	}
1771 
1772 	if(numChatlines || !con_chatrect.integer)
1773 	{
1774 		if(chatpos == 0)
1775 		{
1776 			// first chat, input line, then notify
1777 			chatstart = v;
1778 			notifystart = v + (numChatlines + 1) * con_chatsize.value;
1779 		}
1780 		else if(chatpos > 0)
1781 		{
1782 			// first notify, then (chatpos-1) empty lines, then chat, then input
1783 			notifystart = v;
1784 			chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1785 		}
1786 		else // if(chatpos < 0)
1787 		{
1788 			// first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1789 			notifystart = v;
1790 			chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1791 		}
1792 	}
1793 	else
1794 	{
1795 		// just notify and input
1796 		notifystart = v;
1797 		chatstart = 0; // shut off gcc warning
1798 	}
1799 
1800 	v = notifystart + con_notifysize.value * Con_DrawNotifyRect(0, CON_MASK_INPUT | CON_MASK_HIDENOTIFY | (numChatlines ? CON_MASK_CHAT : 0) | CON_MASK_DEVELOPER, con_notifytime.value, 0, notifystart, vid_conwidth.value, con_notify.value * con_notifysize.value, con_notifysize.value, align, 0.0, "");
1801 
1802 	if(con_chatrect.integer)
1803 	{
1804 		x = con_chatrect_x.value * vid_conwidth.value;
1805 		v = con_chatrect_y.value * vid_conheight.value;
1806 	}
1807 	else
1808 	{
1809 		x = 0;
1810 		if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1811 			v = chatstart;
1812 	}
1813 	height = numChatlines * con_chatsize.value;
1814 
1815 	if(numChatlines)
1816 	{
1817 		Con_DrawNotifyRect(CON_MASK_CHAT, CON_MASK_INPUT, con_chattime.value, x, v, vid_conwidth.value * con_chatwidth.value, height, con_chatsize.value, 0.0, 1.0, "^3 ... ");
1818 		v += height;
1819 	}
1820 	if (key_dest == key_message)
1821 	{
1822 		//static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1823 		int colorindex = -1;
1824 		const char *cursor;
1825 		char charbuf16[16];
1826 		cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL, charbuf16);
1827 
1828 		// LordHavoc: speedup, and other improvements
1829 		if (chat_mode < 0)
1830 			dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1831 		else if(chat_mode)
1832 			dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1833 		else
1834 			dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1835 
1836 		// FIXME word wrap
1837 		inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1838 		xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1839 		x = min(xr, x);
1840 		DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1841 	}
1842 	if (con_mutex) Thread_UnlockMutex(con_mutex);
1843 }
1844 
1845 /*
1846 ================
1847 Con_LineHeight
1848 
1849 Returns the height of a given console line; calculates it if necessary.
1850 ================
1851 */
Con_LineHeight(int lineno)1852 static int Con_LineHeight(int lineno)
1853 {
1854 	con_lineinfo_t *li = &CON_LINES(lineno);
1855 	if(li->height == -1)
1856 	{
1857 		float width = vid_conwidth.value;
1858 		con_text_info_t ti;
1859 		ti.fontsize = con_textsize.value;
1860 		ti.font = FONT_CONSOLE;
1861 		li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1862 	}
1863 	return li->height;
1864 }
1865 
1866 /*
1867 ================
1868 Con_DrawConsoleLine
1869 
1870 Draws a line of the console; returns its height in lines.
1871 If alpha is 0, the line is not drawn, but still wrapped and its height
1872 returned.
1873 ================
1874 */
Con_DrawConsoleLine(int mask_must,int mask_mustnot,float y,int lineno,float ymin,float ymax)1875 static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1876 {
1877 	float width = vid_conwidth.value;
1878 	con_text_info_t ti;
1879 	con_lineinfo_t *li = &CON_LINES(lineno);
1880 
1881 	if((li->mask & mask_must) != mask_must)
1882 		return 0;
1883 	if((li->mask & mask_mustnot) != 0)
1884 		return 0;
1885 
1886 	ti.continuationString = "";
1887 	ti.alignment = 0;
1888 	ti.fontsize = con_textsize.value;
1889 	ti.font = FONT_CONSOLE;
1890 	ti.x = 0;
1891 	ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1892 	ti.ymin = ymin;
1893 	ti.ymax = ymax;
1894 	ti.width = width;
1895 
1896 	return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1897 }
1898 
1899 /*
1900 ================
1901 Con_LastVisibleLine
1902 
1903 Calculates the last visible line index and how much to show of it based on
1904 con_backscroll.
1905 ================
1906 */
Con_LastVisibleLine(int mask_must,int mask_mustnot,int * last,int * limitlast)1907 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1908 {
1909 	int lines_seen = 0;
1910 	int i;
1911 
1912 	if(con_backscroll < 0)
1913 		con_backscroll = 0;
1914 
1915 	*last = 0;
1916 
1917 	// now count until we saw con_backscroll actual lines
1918 	for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1919 	if((CON_LINES(i).mask & mask_must) == mask_must)
1920 	if((CON_LINES(i).mask & mask_mustnot) == 0)
1921 	{
1922 		int h = Con_LineHeight(i);
1923 
1924 		// line is the last visible line?
1925 		*last = i;
1926 		if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1927 		{
1928 			*limitlast = lines_seen + h - con_backscroll;
1929 			return;
1930 		}
1931 
1932 		lines_seen += h;
1933 	}
1934 
1935 	// if we get here, no line was on screen - scroll so that one line is
1936 	// visible then.
1937 	con_backscroll = lines_seen - 1;
1938 	*limitlast = 1;
1939 }
1940 
1941 /*
1942 ================
1943 Con_DrawConsole
1944 
1945 Draws the console with the solid background
1946 The typing input line at the bottom should only be drawn if typing is allowed
1947 ================
1948 */
Con_DrawConsole(int lines)1949 void Con_DrawConsole (int lines)
1950 {
1951 	float alpha, alpha0;
1952 	double sx, sy;
1953 	int mask_must = 0;
1954 	int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1955 	cachepic_t *conbackpic;
1956 
1957 	if (lines <= 0)
1958 		return;
1959 
1960 	if (con_mutex) Thread_LockMutex(con_mutex);
1961 
1962 	if (con_backscroll < 0)
1963 		con_backscroll = 0;
1964 
1965 	con_vislines = lines;
1966 
1967 	r_draw2d_force = true;
1968 
1969 // draw the background
1970 	alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
1971 	if((alpha = alpha0 * scr_conalphafactor.value) > 0)
1972 	{
1973 		sx = scr_conscroll_x.value;
1974 		sy = scr_conscroll_y.value;
1975 		conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0) : NULL;
1976 		sx *= realtime; sy *= realtime;
1977 		sx -= floor(sx); sy -= floor(sy);
1978 		if (conbackpic && conbackpic->tex != r_texture_notexture)
1979 			DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1980 					0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1981 					1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1982 					0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1983 					1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1984 					0);
1985 		else
1986 			DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
1987 	}
1988 	if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
1989 	{
1990 		sx = scr_conscroll2_x.value;
1991 		sy = scr_conscroll2_y.value;
1992 		conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1993 		sx *= realtime; sy *= realtime;
1994 		sx -= floor(sx); sy -= floor(sy);
1995 		if(conbackpic && conbackpic->tex != r_texture_notexture)
1996 			DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1997 					0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1998 					1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1999 					0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2000 					1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2001 					0);
2002 	}
2003 	if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
2004 	{
2005 		sx = scr_conscroll3_x.value;
2006 		sy = scr_conscroll3_y.value;
2007 		conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
2008 		sx *= realtime; sy *= realtime;
2009 		sx -= floor(sx); sy -= floor(sy);
2010 		if(conbackpic && conbackpic->tex != r_texture_notexture)
2011 			DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
2012 					0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2013 					1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2014 					0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2015 					1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
2016 					0);
2017 	}
2018 	DrawQ_String(vid_conwidth.integer - DrawQ_TextWidth(engineversion, 0, con_textsize.value, con_textsize.value, false, FONT_CONSOLE), lines - con_textsize.value, engineversion, 0, con_textsize.value, con_textsize.value, 1, 0, 0, 1, 0, NULL, true, FONT_CONSOLE);
2019 
2020 // draw the text
2021 #if 0
2022 	{
2023 		int i;
2024 		int count = CON_LINES_COUNT;
2025 		float ymax = con_vislines - 2 * con_textsize.value;
2026 		float y = ymax + con_textsize.value * con_backscroll;
2027 		for (i = 0;i < count && y >= 0;i++)
2028 			y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
2029 		// fix any excessive scrollback for the next frame
2030 		if (i >= count && y >= 0)
2031 		{
2032 			con_backscroll -= (int)(y / con_textsize.value);
2033 			if (con_backscroll < 0)
2034 				con_backscroll = 0;
2035 		}
2036 	}
2037 #else
2038 	if(CON_LINES_COUNT > 0)
2039 	{
2040 		int i, last, limitlast;
2041 		float y;
2042 		float ymax = con_vislines - 2 * con_textsize.value;
2043 		Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2044 		//Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
2045 		y = ymax - con_textsize.value;
2046 
2047 		if(limitlast)
2048 			y += (CON_LINES(last).height - limitlast) * con_textsize.value;
2049 		i = last;
2050 
2051 		for(;;)
2052 		{
2053 			y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
2054 			if(i == 0)
2055 				break; // top of console buffer
2056 			if(y < 0)
2057 				break; // top of console window
2058 			limitlast = 0;
2059 			--i;
2060 		}
2061 	}
2062 #endif
2063 
2064 // draw the input prompt, user text, and cursor if desired
2065 	Con_DrawInput ();
2066 
2067 	r_draw2d_force = false;
2068 	if (con_mutex) Thread_UnlockMutex(con_mutex);
2069 }
2070 
2071 /*
2072 GetMapList
2073 
2074 Made by [515]
2075 Prints not only map filename, but also
2076 its format (q1/q2/q3/hl) and even its message
2077 */
2078 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2079 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2080 //LordHavoc: added .ent file loading, and redesigned error handling to still try the .ent file even if the map format is not recognized, this also eliminated one goto
2081 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
GetMapList(const char * s,char * completedname,int completednamebufferlength)2082 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
2083 {
2084 	fssearch_t	*t;
2085 	char		message[1024];
2086 	int			i, k, max, p, o, min;
2087 	unsigned char *len;
2088 	qfile_t		*f;
2089 	unsigned char buf[1024];
2090 
2091 	dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2092 	t = FS_Search(message, 1, true);
2093 	if(!t)
2094 		return false;
2095 	if (t->numfilenames > 1)
2096 		Con_Printf("^1 %i maps found :\n", t->numfilenames);
2097 	len = (unsigned char *)Z_Malloc(t->numfilenames);
2098 	min = 666;
2099 	for(max=i=0;i<t->numfilenames;i++)
2100 	{
2101 		k = (int)strlen(t->filenames[i]);
2102 		k -= 9;
2103 		if(max < k)
2104 			max = k;
2105 		else
2106 		if(min > k)
2107 			min = k;
2108 		len[i] = k;
2109 	}
2110 	o = (int)strlen(s);
2111 	for(i=0;i<t->numfilenames;i++)
2112 	{
2113 		int lumpofs = 0, lumplen = 0;
2114 		char *entities = NULL;
2115 		const char *data = NULL;
2116 		char keyname[64];
2117 		char entfilename[MAX_QPATH];
2118 		char desc[64];
2119 		desc[0] = 0;
2120 		strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
2121 		p = 0;
2122 		f = FS_OpenVirtualFile(t->filenames[i], true);
2123 		if(f)
2124 		{
2125 			strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message));
2126 			memset(buf, 0, 1024);
2127 			FS_Read(f, buf, 1024);
2128 			if (!memcmp(buf, "IBSP", 4))
2129 			{
2130 				p = LittleLong(((int *)buf)[1]);
2131 				if (p == Q3BSPVERSION)
2132 				{
2133 					q3dheader_t *header = (q3dheader_t *)buf;
2134 					lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2135 					lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2136 					dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p);
2137 				}
2138 				else if (p == Q2BSPVERSION)
2139 				{
2140 					q2dheader_t *header = (q2dheader_t *)buf;
2141 					lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2142 					lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2143 					dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p);
2144 				}
2145 				else
2146 					dpsnprintf(desc, sizeof(desc), "IBSP%i", p);
2147 			}
2148 			else if (BuffLittleLong(buf) == BSPVERSION)
2149 			{
2150 				lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2151 				lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2152 				dpsnprintf(desc, sizeof(desc), "BSP29");
2153 			}
2154 			else if (BuffLittleLong(buf) == 30)
2155 			{
2156 				lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2157 				lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2158 				dpsnprintf(desc, sizeof(desc), "BSPHL");
2159 			}
2160 			else if (!memcmp(buf, "BSP2", 4))
2161 			{
2162 				lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2163 				lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2164 				dpsnprintf(desc, sizeof(desc), "BSP2");
2165 			}
2166 			else if (!memcmp(buf, "2PSB", 4))
2167 			{
2168 				lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2169 				lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2170 				dpsnprintf(desc, sizeof(desc), "BSP2RMQe");
2171 			}
2172 			else
2173 			{
2174 				dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
2175 			}
2176 			strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2177 			memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2178 			entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2179 			if (!entities && lumplen >= 10)
2180 			{
2181 				FS_Seek(f, lumpofs, SEEK_SET);
2182 				entities = (char *)Z_Malloc(lumplen + 1);
2183 				FS_Read(f, entities, lumplen);
2184 			}
2185 			if (entities)
2186 			{
2187 				// if there are entities to parse, a missing message key just
2188 				// means there is no title, so clear the message string now
2189 				message[0] = 0;
2190 				data = entities;
2191 				for (;;)
2192 				{
2193 					int l;
2194 					if (!COM_ParseToken_Simple(&data, false, false, true))
2195 						break;
2196 					if (com_token[0] == '{')
2197 						continue;
2198 					if (com_token[0] == '}')
2199 						break;
2200 					// skip leading whitespace
2201 					for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2202 					for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2203 						keyname[l] = com_token[k+l];
2204 					keyname[l] = 0;
2205 					if (!COM_ParseToken_Simple(&data, false, false, true))
2206 						break;
2207 					if (developer_extra.integer)
2208 						Con_DPrintf("key: %s %s\n", keyname, com_token);
2209 					if (!strcmp(keyname, "message"))
2210 					{
2211 						// get the message contents
2212 						strlcpy(message, com_token, sizeof(message));
2213 						break;
2214 					}
2215 				}
2216 			}
2217 		}
2218 		if (entities)
2219 			Z_Free(entities);
2220 		if(f)
2221 			FS_Close(f);
2222 		*(t->filenames[i]+len[i]+5) = 0;
2223 		Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
2224 	}
2225 	Con_Print("\n");
2226 	for(p=o;p<min;p++)
2227 	{
2228 		k = *(t->filenames[0]+5+p);
2229 		if(k == 0)
2230 			goto endcomplete;
2231 		for(i=1;i<t->numfilenames;i++)
2232 			if(*(t->filenames[i]+5+p) != k)
2233 				goto endcomplete;
2234 	}
2235 endcomplete:
2236 	if(p > o && completedname && completednamebufferlength > 0)
2237 	{
2238 		memset(completedname, 0, completednamebufferlength);
2239 		memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2240 	}
2241 	Z_Free(len);
2242 	FS_FreeSearch(t);
2243 	return p > o;
2244 }
2245 
2246 /*
2247 	Con_DisplayList
2248 
2249 	New function for tab-completion system
2250 	Added by EvilTypeGuy
2251 	MEGA Thanks to Taniwha
2252 
2253 */
Con_DisplayList(const char ** list)2254 void Con_DisplayList(const char **list)
2255 {
2256 	int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2257 	const char **walk = list;
2258 
2259 	while (*walk) {
2260 		len = (int)strlen(*walk);
2261 		if (len > maxlen)
2262 			maxlen = len;
2263 		walk++;
2264 	}
2265 	maxlen += 1;
2266 
2267 	while (*list) {
2268 		len = (int)strlen(*list);
2269 		if (pos + maxlen >= width) {
2270 			Con_Print("\n");
2271 			pos = 0;
2272 		}
2273 
2274 		Con_Print(*list);
2275 		for (i = 0; i < (maxlen - len); i++)
2276 			Con_Print(" ");
2277 
2278 		pos += maxlen;
2279 		list++;
2280 	}
2281 
2282 	if (pos)
2283 		Con_Print("\n\n");
2284 }
2285 
2286 
2287 // Now it becomes TRICKY :D --blub
2288 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];	// contains the nicks with colors and all that
2289 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];	// sanitized list for completion when there are other possible matches.
2290 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2291 static int Nicks_offset[MAX_SCOREBOARD]; // when nicks use a space, we need this to move the completion list string starts to avoid invalid memcpys
2292 static int Nicks_matchpos;
2293 
2294 // co against <<:BLASTER:>> is true!?
Nicks_strncasecmp_nospaces(char * a,char * b,unsigned int a_len)2295 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2296 {
2297 	while(a_len)
2298 	{
2299 		if(tolower(*a) == tolower(*b))
2300 		{
2301 			if(*a == 0)
2302 				return 0;
2303 			--a_len;
2304 			++a;
2305 			++b;
2306 			continue;
2307 		}
2308 		if(!*a)
2309 			return -1;
2310 		if(!*b)
2311 			return 1;
2312 		if(*a == ' ')
2313 			return (*a < *b) ? -1 : 1;
2314 		if(*b == ' ')
2315 			++b;
2316 		else
2317 			return (*a < *b) ? -1 : 1;
2318 	}
2319 	return 0;
2320 }
Nicks_strncasecmp(char * a,char * b,unsigned int a_len)2321 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2322 {
2323 	char space_char;
2324 	if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2325 	{
2326 		if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2327 			return Nicks_strncasecmp_nospaces(a, b, a_len);
2328 		return strncasecmp(a, b, a_len);
2329 	}
2330 
2331 	space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2332 
2333 	// ignore non alphanumerics of B
2334 	// if A contains a non-alphanumeric, B must contain it as well though!
2335 	while(a_len)
2336 	{
2337 		qboolean alnum_a, alnum_b;
2338 
2339 		if(tolower(*a) == tolower(*b))
2340 		{
2341 			if(*a == 0) // end of both strings, they're equal
2342 				return 0;
2343 			--a_len;
2344 			++a;
2345 			++b;
2346 			continue;
2347 		}
2348 		// not equal, end of one string?
2349 		if(!*a)
2350 			return -1;
2351 		if(!*b)
2352 			return 1;
2353 		// ignore non alphanumerics
2354 		alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2355 		alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2356 		if(!alnum_a) // b must contain this
2357 			return (*a < *b) ? -1 : 1;
2358 		if(!alnum_b)
2359 			++b;
2360 		// otherwise, both are alnum, they're just not equal, return the appropriate number
2361 		else
2362 			return (*a < *b) ? -1 : 1;
2363 	}
2364 	return 0;
2365 }
2366 
2367 
2368 /* Nicks_CompleteCountPossible
2369 
2370    Count the number of possible nicks to complete
2371  */
Nicks_CompleteCountPossible(char * line,int pos,char * s,qboolean isCon)2372 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2373 {
2374 	char name[128];
2375 	int i, p;
2376 	int match;
2377 	int spos;
2378 	int count = 0;
2379 
2380 	if(!con_nickcompletion.integer)
2381 		return 0;
2382 
2383 	// changed that to 1
2384 	if(!line[0])// || !line[1]) // we want at least... 2 written characters
2385 		return 0;
2386 
2387 	for(i = 0; i < cl.maxclients; ++i)
2388 	{
2389 		p = i;
2390 		if(!cl.scores[p].name[0])
2391 			continue;
2392 
2393 		SanitizeString(cl.scores[p].name, name);
2394 		//Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2395 
2396 		if(!name[0])
2397 			continue;
2398 
2399 		match = -1;
2400 		spos = pos - 1; // no need for a minimum of characters :)
2401 
2402 		while(spos >= 0)
2403 		{
2404 			if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2405 			{
2406 				if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2407 				   !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2408 				{
2409 					--spos;
2410 					continue;
2411 				}
2412 			}
2413 			if(isCon && spos == 0)
2414 				break;
2415 			if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2416 				match = spos;
2417 			--spos;
2418 		}
2419 		if(match < 0)
2420 			continue;
2421 		//Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2422 		strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2423 
2424 		// the sanitized list
2425 		strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2426 		if(!count)
2427 		{
2428 			Nicks_matchpos = match;
2429 		}
2430 
2431 		Nicks_offset[count] = s - (&line[match]);
2432 		//Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2433 
2434 		++count;
2435 	}
2436 	return count;
2437 }
2438 
Cmd_CompleteNicksPrint(int count)2439 static void Cmd_CompleteNicksPrint(int count)
2440 {
2441 	int i;
2442 	for(i = 0; i < count; ++i)
2443 		Con_Printf("%s\n", Nicks_list[i]);
2444 }
2445 
Nicks_CutMatchesNormal(int count)2446 static void Nicks_CutMatchesNormal(int count)
2447 {
2448 	// cut match 0 down to the longest possible completion
2449 	int i;
2450 	unsigned int c, l;
2451 	c = (unsigned int)strlen(Nicks_sanlist[0]) - 1;
2452 	for(i = 1; i < count; ++i)
2453 	{
2454 		l = (unsigned int)strlen(Nicks_sanlist[i]) - 1;
2455 		if(l < c)
2456 			c = l;
2457 
2458 		for(l = 0; l <= c; ++l)
2459 			if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2460 			{
2461 				c = l-1;
2462 				break;
2463 			}
2464 	}
2465 	Nicks_sanlist[0][c+1] = 0;
2466 	//Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2467 }
2468 
Nicks_strcleanlen(const char * s)2469 static unsigned int Nicks_strcleanlen(const char *s)
2470 {
2471 	unsigned int l = 0;
2472 	while(*s)
2473 	{
2474 		if( (*s >= 'a' && *s <= 'z') ||
2475 		    (*s >= 'A' && *s <= 'Z') ||
2476 		    (*s >= '0' && *s <= '9') ||
2477 		    *s == ' ')
2478 			++l;
2479 		++s;
2480 	}
2481 	return l;
2482 }
2483 
Nicks_CutMatchesAlphaNumeric(int count)2484 static void Nicks_CutMatchesAlphaNumeric(int count)
2485 {
2486 	// cut match 0 down to the longest possible completion
2487 	int i;
2488 	unsigned int c, l;
2489 	char tempstr[sizeof(Nicks_sanlist[0])];
2490 	char *a, *b;
2491 	char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2492 
2493 	c = (unsigned int)strlen(Nicks_sanlist[0]);
2494 	for(i = 0, l = 0; i < (int)c; ++i)
2495 	{
2496 		if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2497 		    (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2498 		    (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2499 		{
2500 			tempstr[l++] = Nicks_sanlist[0][i];
2501 		}
2502 	}
2503 	tempstr[l] = 0;
2504 
2505 	for(i = 1; i < count; ++i)
2506 	{
2507 		a = tempstr;
2508 		b = Nicks_sanlist[i];
2509 		while(1)
2510 		{
2511 			if(!*a)
2512 				break;
2513 			if(!*b)
2514 			{
2515 				*a = 0;
2516 				break;
2517 			}
2518 			if(tolower(*a) == tolower(*b))
2519 			{
2520 				++a;
2521 				++b;
2522 				continue;
2523 			}
2524 			if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2525 			{
2526 				// b is alnum, so cut
2527 				*a = 0;
2528 				break;
2529 			}
2530 			++b;
2531 		}
2532 	}
2533 	// Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2534 	Nicks_CutMatchesNormal(count);
2535 	//if(!Nicks_sanlist[0][0])
2536 	if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2537 	{
2538 		// if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2539 		strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2540 	}
2541 }
2542 
Nicks_CutMatchesNoSpaces(int count)2543 static void Nicks_CutMatchesNoSpaces(int count)
2544 {
2545 	// cut match 0 down to the longest possible completion
2546 	int i;
2547 	unsigned int c, l;
2548 	char tempstr[sizeof(Nicks_sanlist[0])];
2549 	char *a, *b;
2550 
2551 	c = (unsigned int)strlen(Nicks_sanlist[0]);
2552 	for(i = 0, l = 0; i < (int)c; ++i)
2553 	{
2554 		if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2555 		{
2556 			tempstr[l++] = Nicks_sanlist[0][i];
2557 		}
2558 	}
2559 	tempstr[l] = 0;
2560 
2561 	for(i = 1; i < count; ++i)
2562 	{
2563 		a = tempstr;
2564 		b = Nicks_sanlist[i];
2565 		while(1)
2566 		{
2567 			if(!*a)
2568 				break;
2569 			if(!*b)
2570 			{
2571 				*a = 0;
2572 				break;
2573 			}
2574 			if(tolower(*a) == tolower(*b))
2575 			{
2576 				++a;
2577 				++b;
2578 				continue;
2579 			}
2580 			if(*b != ' ')
2581 			{
2582 				*a = 0;
2583 				break;
2584 			}
2585 			++b;
2586 		}
2587 	}
2588 	// Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2589 	Nicks_CutMatchesNormal(count);
2590 	//if(!Nicks_sanlist[0][0])
2591 	//Con_Printf("TS: %s\n", tempstr);
2592 	if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2593 	{
2594 		// if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2595 		strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2596 	}
2597 }
2598 
Nicks_CutMatches(int count)2599 static void Nicks_CutMatches(int count)
2600 {
2601 	if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2602 		Nicks_CutMatchesAlphaNumeric(count);
2603 	else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2604 		Nicks_CutMatchesNoSpaces(count);
2605 	else
2606 		Nicks_CutMatchesNormal(count);
2607 }
2608 
Nicks_CompleteBuildList(int count)2609 static const char **Nicks_CompleteBuildList(int count)
2610 {
2611 	const char **buf;
2612 	int bpos = 0;
2613 	// the list is freed by Con_CompleteCommandLine, so create a char**
2614 	buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2615 
2616 	for(; bpos < count; ++bpos)
2617 		buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2618 
2619 	Nicks_CutMatches(count);
2620 
2621 	buf[bpos] = NULL;
2622 	return buf;
2623 }
2624 
2625 /*
2626 	Nicks_AddLastColor
2627 	Restores the previous used color, after the autocompleted name.
2628 */
Nicks_AddLastColor(char * buffer,int pos)2629 static int Nicks_AddLastColor(char *buffer, int pos)
2630 {
2631 	qboolean quote_added = false;
2632 	int match;
2633 	int color = STRING_COLOR_DEFAULT + '0';
2634 	char r = 0, g = 0, b = 0;
2635 
2636 	if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2637 	{
2638 		// we'll have to add a quote :)
2639 		buffer[pos++] = '\"';
2640 		quote_added = true;
2641 	}
2642 
2643 	if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2644 	{
2645 		// add color when no quote was added, or when flags &4?
2646 		// find last color
2647 		for(match = Nicks_matchpos-1; match >= 0; --match)
2648 		{
2649 			if(buffer[match] == STRING_COLOR_TAG)
2650 			{
2651 				if( isdigit(buffer[match+1]) )
2652 				{
2653 					color = buffer[match+1];
2654 					break;
2655 				}
2656 				else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2657 				{
2658 					if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2659 					{
2660 						r = buffer[match+2];
2661 						g = buffer[match+3];
2662 						b = buffer[match+4];
2663 						color = -1;
2664 						break;
2665 					}
2666 				}
2667 			}
2668 		}
2669 		if(!quote_added)
2670 		{
2671 			if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2672 				pos -= 2;
2673 			else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2674 					 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2675 				pos -= 5;
2676 		}
2677 		buffer[pos++] = STRING_COLOR_TAG;
2678 		if (color == -1)
2679 		{
2680 			buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2681 			buffer[pos++] = r;
2682 			buffer[pos++] = g;
2683 			buffer[pos++] = b;
2684 		}
2685 		else
2686 			buffer[pos++] = color;
2687 	}
2688 	return pos;
2689 }
2690 
Nicks_CompleteChatLine(char * buffer,size_t size,unsigned int pos)2691 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2692 {
2693 	int n;
2694 	/*if(!con_nickcompletion.integer)
2695 	  return; is tested in Nicks_CompletionCountPossible */
2696 	n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2697 	if(n == 1)
2698 	{
2699 		size_t len;
2700 		char *msg;
2701 
2702 		msg = Nicks_list[0];
2703 		len = min(size - Nicks_matchpos - 3, strlen(msg));
2704 		memcpy(&buffer[Nicks_matchpos], msg, len);
2705 		if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2706 			len = (int)Nicks_AddLastColor(buffer, Nicks_matchpos+(int)len);
2707 		buffer[len++] = ' ';
2708 		buffer[len] = 0;
2709 		return (int)len;
2710 	} else if(n > 1)
2711 	{
2712 		int len;
2713 		char *msg;
2714 		Con_Printf("\n%i possible nicks:\n", n);
2715 		Cmd_CompleteNicksPrint(n);
2716 
2717 		Nicks_CutMatches(n);
2718 
2719 		msg = Nicks_sanlist[0];
2720 		len = (int)min(size - Nicks_matchpos, strlen(msg));
2721 		memcpy(&buffer[Nicks_matchpos], msg, len);
2722 		buffer[Nicks_matchpos + len] = 0;
2723 		//pos += len;
2724 		return Nicks_matchpos + len;
2725 	}
2726 	return pos;
2727 }
2728 
2729 
2730 /*
2731 	Con_CompleteCommandLine
2732 
2733 	New function for tab-completion system
2734 	Added by EvilTypeGuy
2735 	Thanks to Fett erich@heintz.com
2736 	Thanks to taniwha
2737 	Enhanced to tab-complete map names by [515]
2738 
2739 */
Con_CompleteCommandLine(void)2740 void Con_CompleteCommandLine (void)
2741 {
2742 	const char *cmd = "";
2743 	char *s;
2744 	const char **list[4] = {0, 0, 0, 0};
2745 	char s2[512];
2746 	char command[512];
2747 	int c, v, a, i, cmd_len, pos, k;
2748 	int n; // nicks --blub
2749 	const char *space, *patterns;
2750 	char vabuf[1024];
2751 
2752 	//find what we want to complete
2753 	pos = key_linepos;
2754 	while(--pos)
2755 	{
2756 		k = key_line[pos];
2757 		if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2758 			break;
2759 	}
2760 	pos++;
2761 
2762 	s = key_line + pos;
2763 	strlcpy(s2, key_line + key_linepos, sizeof(s2));	//save chars after cursor
2764 	key_line[key_linepos] = 0;					//hide them
2765 
2766 	space = strchr(key_line + 1, ' ');
2767 	if(space && pos == (space - key_line) + 1)
2768 	{
2769 		strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2770 
2771 		patterns = Cvar_VariableString(va(vabuf, sizeof(vabuf), "con_completion_%s", command)); // TODO maybe use a better place for this?
2772 		if(patterns && !*patterns)
2773 			patterns = NULL; // get rid of the empty string
2774 
2775 		if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2776 		{
2777 			//maps search
2778 			char t[MAX_QPATH];
2779 			if (GetMapList(s, t, sizeof(t)))
2780 			{
2781 				// first move the cursor
2782 				key_linepos += (int)strlen(t) - (int)strlen(s);
2783 
2784 				// and now do the actual work
2785 				*s = 0;
2786 				strlcat(key_line, t, MAX_INPUTLINE);
2787 				strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2788 
2789 				// and fix the cursor
2790 				if(key_linepos > (int) strlen(key_line))
2791 					key_linepos = (int) strlen(key_line);
2792 			}
2793 			return;
2794 		}
2795 		else
2796 		{
2797 			if(patterns)
2798 			{
2799 				char t[MAX_QPATH];
2800 				stringlist_t resultbuf, dirbuf;
2801 
2802 				// Usage:
2803 				//   // store completion patterns (space separated) for command foo in con_completion_foo
2804 				//   set con_completion_foo "foodata/*.foodefault *.foo"
2805 				//   foo <TAB>
2806 				//
2807 				// Note: patterns with slash are always treated as absolute
2808 				// patterns; patterns without slash search in the innermost
2809 				// directory the user specified. There is no way to "complete into"
2810 				// a directory as of now, as directories seem to be unknown to the
2811 				// FS subsystem.
2812 				//
2813 				// Examples:
2814 				//   set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2815 				//   set con_completion_playdemo "*.dem"
2816 				//   set con_completion_play "*.wav *.ogg"
2817 				//
2818 				// TODO somehow add support for directories; these shall complete
2819 				// to their name + an appended slash.
2820 
2821 				stringlistinit(&resultbuf);
2822 				stringlistinit(&dirbuf);
2823 				while(COM_ParseToken_Simple(&patterns, false, false, true))
2824 				{
2825 					fssearch_t *search;
2826 					if(strchr(com_token, '/'))
2827 					{
2828 						search = FS_Search(com_token, true, true);
2829 					}
2830 					else
2831 					{
2832 						const char *slash = strrchr(s, '/');
2833 						if(slash)
2834 						{
2835 							strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2836 							strlcat(t, com_token, sizeof(t));
2837 							search = FS_Search(t, true, true);
2838 						}
2839 						else
2840 							search = FS_Search(com_token, true, true);
2841 					}
2842 					if(search)
2843 					{
2844 						for(i = 0; i < search->numfilenames; ++i)
2845 							if(!strncmp(search->filenames[i], s, strlen(s)))
2846 								if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2847 									stringlistappend(&resultbuf, search->filenames[i]);
2848 						FS_FreeSearch(search);
2849 					}
2850 				}
2851 
2852 				// In any case, add directory names
2853 				{
2854 					fssearch_t *search;
2855 					const char *slash = strrchr(s, '/');
2856 					if(slash)
2857 					{
2858 						strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2859 						strlcat(t, "*", sizeof(t));
2860 						search = FS_Search(t, true, true);
2861 					}
2862 					else
2863 						search = FS_Search("*", true, true);
2864 					if(search)
2865 					{
2866 						for(i = 0; i < search->numfilenames; ++i)
2867 							if(!strncmp(search->filenames[i], s, strlen(s)))
2868 								if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2869 									stringlistappend(&dirbuf, search->filenames[i]);
2870 						FS_FreeSearch(search);
2871 					}
2872 				}
2873 
2874 				if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2875 				{
2876 					const char *p, *q;
2877 					unsigned int matchchars;
2878 					if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2879 					{
2880 						dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2881 					}
2882 					else
2883 					if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2884 					{
2885 						dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2886 					}
2887 					else
2888 					{
2889 						stringlistsort(&resultbuf, true); // dirbuf is already sorted
2890 						Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2891 						for(i = 0; i < dirbuf.numstrings; ++i)
2892 						{
2893 							Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2894 						}
2895 						for(i = 0; i < resultbuf.numstrings; ++i)
2896 						{
2897 							Con_Printf("%s\n", resultbuf.strings[i]);
2898 						}
2899 						matchchars = sizeof(t) - 1;
2900 						if(resultbuf.numstrings > 0)
2901 						{
2902 							p = resultbuf.strings[0];
2903 							q = resultbuf.strings[resultbuf.numstrings - 1];
2904 							for(; *p && *p == *q; ++p, ++q);
2905 							matchchars = (unsigned int)(p - resultbuf.strings[0]);
2906 						}
2907 						if(dirbuf.numstrings > 0)
2908 						{
2909 							p = dirbuf.strings[0];
2910 							q = dirbuf.strings[dirbuf.numstrings - 1];
2911 							for(; *p && *p == *q; ++p, ++q);
2912 							matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2913 						}
2914 						// now p points to the first non-equal character, or to the end
2915 						// of resultbuf.strings[0]. We want to append the characters
2916 						// from resultbuf.strings[0] to (not including) p as these are
2917 						// the unique prefix
2918 						strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2919 					}
2920 
2921 					// first move the cursor
2922 					key_linepos += (int)strlen(t) - (int)strlen(s);
2923 
2924 					// and now do the actual work
2925 					*s = 0;
2926 					strlcat(key_line, t, MAX_INPUTLINE);
2927 					strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2928 
2929 					// and fix the cursor
2930 					if(key_linepos > (int) strlen(key_line))
2931 						key_linepos = (int) strlen(key_line);
2932 				}
2933 				stringlistfreecontents(&resultbuf);
2934 				stringlistfreecontents(&dirbuf);
2935 
2936 				return; // bail out, when we complete for a command that wants a file name
2937 			}
2938 		}
2939 	}
2940 
2941 	// Count number of possible matches and print them
2942 	c = Cmd_CompleteCountPossible(s);
2943 	if (c)
2944 	{
2945 		Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2946 		Cmd_CompleteCommandPrint(s);
2947 	}
2948 	v = Cvar_CompleteCountPossible(s);
2949 	if (v)
2950 	{
2951 		Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2952 		Cvar_CompleteCvarPrint(s);
2953 	}
2954 	a = Cmd_CompleteAliasCountPossible(s);
2955 	if (a)
2956 	{
2957 		Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2958 		Cmd_CompleteAliasPrint(s);
2959 	}
2960 	n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2961 	if (n)
2962 	{
2963 		Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2964 		Cmd_CompleteNicksPrint(n);
2965 	}
2966 
2967 	if (!(c + v + a + n))	// No possible matches
2968 	{
2969 		if(s2[0])
2970 			strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2971 		return;
2972 	}
2973 
2974 	if (c)
2975 		cmd = *(list[0] = Cmd_CompleteBuildList(s));
2976 	if (v)
2977 		cmd = *(list[1] = Cvar_CompleteBuildList(s));
2978 	if (a)
2979 		cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2980 	if (n)
2981 		cmd = *(list[3] = Nicks_CompleteBuildList(n));
2982 
2983 	for (cmd_len = (int)strlen(s);;cmd_len++)
2984 	{
2985 		const char **l;
2986 		for (i = 0; i < 3; i++)
2987 			if (list[i])
2988 				for (l = list[i];*l;l++)
2989 					if ((*l)[cmd_len] != cmd[cmd_len])
2990 						goto done;
2991 		// all possible matches share this character, so we continue...
2992 		if (!cmd[cmd_len])
2993 		{
2994 			// if all matches ended at the same position, stop
2995 			// (this means there is only one match)
2996 			break;
2997 		}
2998 	}
2999 done:
3000 
3001 	// prevent a buffer overrun by limiting cmd_len according to remaining space
3002 	cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
3003 	if (cmd)
3004 	{
3005 		key_linepos = pos;
3006 		memcpy(&key_line[key_linepos], cmd, cmd_len);
3007 		key_linepos += cmd_len;
3008 		// if there is only one match, add a space after it
3009 		if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
3010 		{
3011 			if(n)
3012 			{ // was a nick, might have an offset, and needs colors ;) --blub
3013 				key_linepos = pos - Nicks_offset[0];
3014 				cmd_len = (int)strlen(Nicks_list[0]);
3015 				cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
3016 
3017 				memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
3018 				key_linepos += cmd_len;
3019 				if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
3020 					key_linepos = Nicks_AddLastColor(key_line, key_linepos);
3021 			}
3022 			key_line[key_linepos++] = ' ';
3023 		}
3024 	}
3025 
3026 	// use strlcat to avoid a buffer overrun
3027 	key_line[key_linepos] = 0;
3028 	strlcat(key_line, s2, sizeof(key_line));
3029 
3030 	// free the command, cvar, and alias lists
3031 	for (i = 0; i < 4; i++)
3032 		if (list[i])
3033 			Mem_Free((void *)list[i]);
3034 }
3035 
3036