Fixed a potential buffer overflow in Con_DebugLog
[divverent/darkplaces.git] / console.c
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 #ifdef NeXT
23 #include <libc.h>
24 #endif
25 #ifndef _MSC_VER
26 #ifndef __BORLANDC__
27 #include <unistd.h>
28 #endif
29 #endif
30 #ifdef WIN32
31 #include <io.h>
32 #endif
33 #include <fcntl.h>
34 #include "quakedef.h"
35
36 int             con_linewidth;
37
38 float           con_cursorspeed = 4;
39
40 #define         CON_TEXTSIZE    16384
41
42 qboolean        con_forcedup;           // because no entities to refresh
43
44 int                     con_totallines;         // total lines in console scrollback
45 int                     con_backscroll;         // lines up from bottom to display
46 int                     con_current;            // where next message will be printed
47 int                     con_x;                          // offset in current line for next print
48 char            *con_text = 0;
49
50 cvar_t          con_notifytime = {CVAR_SAVE, "con_notifytime","3"};     //seconds
51 cvar_t          logfile = {0, "logfile","0"};
52
53 #define NUM_CON_TIMES 4
54 float           con_times[NUM_CON_TIMES];       // realtime time the line was generated
55                                                 // for transparent notify lines
56
57 int                     con_vislines;
58
59 qboolean        con_debuglog;
60
61 #define MAXCMDLINE      256
62 extern  char    key_lines[32][MAXCMDLINE];
63 extern  int             edit_line;
64 extern  int             key_linepos;
65 extern  int             key_insert;
66
67
68 qboolean        con_initialized;
69
70 mempool_t       *console_mempool;
71
72 int             con_notifylines;                // scan lines to clear for notify lines
73
74 extern void M_Menu_Main_f (void);
75
76 /*
77 ================
78 Con_ToggleConsole_f
79 ================
80 */
81 void Con_ToggleConsole_f (void)
82 {
83         if (key_dest == key_console)
84         {
85                 if (cls.state == ca_connected)
86                         key_dest = key_game;
87                 else
88                         M_Menu_Main_f ();
89         }
90         else
91                 key_dest = key_console;
92
93         memset (con_times, 0, sizeof(con_times));
94 }
95
96 /*
97 ================
98 Con_Clear_f
99 ================
100 */
101 void Con_Clear_f (void)
102 {
103         if (con_text)
104                 memset (con_text, ' ', CON_TEXTSIZE);
105 }
106
107
108 /*
109 ================
110 Con_ClearNotify
111 ================
112 */
113 void Con_ClearNotify (void)
114 {
115         int             i;
116
117         for (i=0 ; i<NUM_CON_TIMES ; i++)
118                 con_times[i] = 0;
119 }
120
121
122 /*
123 ================
124 Con_MessageMode_f
125 ================
126 */
127 extern qboolean team_message;
128
129 void Con_MessageMode_f (void)
130 {
131         key_dest = key_message;
132         team_message = false;
133 }
134
135
136 /*
137 ================
138 Con_MessageMode2_f
139 ================
140 */
141 void Con_MessageMode2_f (void)
142 {
143         key_dest = key_message;
144         team_message = true;
145 }
146
147
148 /*
149 ================
150 Con_CheckResize
151
152 If the line width has changed, reformat the buffer.
153 ================
154 */
155 void Con_CheckResize (void)
156 {
157         int             i, j, width, oldwidth, oldtotallines, numlines, numchars;
158         char    tbuf[CON_TEXTSIZE];
159
160         width = (vid.conwidth >> 3) - 2;
161
162         if (width == con_linewidth)
163                 return;
164
165         if (width < 1)                  // video hasn't been initialized yet
166         {
167                 width = 78; // LordHavoc: changed from 38 to 78 (320 -> 640 conversion)
168                 con_linewidth = width;
169                 con_totallines = CON_TEXTSIZE / con_linewidth;
170                 memset (con_text, ' ', CON_TEXTSIZE);
171         }
172         else
173         {
174                 oldwidth = con_linewidth;
175                 con_linewidth = width;
176                 oldtotallines = con_totallines;
177                 con_totallines = CON_TEXTSIZE / con_linewidth;
178                 numlines = oldtotallines;
179
180                 if (con_totallines < numlines)
181                         numlines = con_totallines;
182
183                 numchars = oldwidth;
184
185                 if (con_linewidth < numchars)
186                         numchars = con_linewidth;
187
188                 memcpy (tbuf, con_text, CON_TEXTSIZE);
189                 memset (con_text, ' ', CON_TEXTSIZE);
190
191                 for (i=0 ; i<numlines ; i++)
192                 {
193                         for (j=0 ; j<numchars ; j++)
194                         {
195                                 con_text[(con_totallines - 1 - i) * con_linewidth + j] =
196                                                 tbuf[((con_current - i + oldtotallines) %
197                                                           oldtotallines) * oldwidth + j];
198                         }
199                 }
200
201                 Con_ClearNotify ();
202         }
203
204         con_backscroll = 0;
205         con_current = con_totallines - 1;
206 }
207
208
209 /*
210 ================
211 Con_Init
212 ================
213 */
214 void Con_Init (void)
215 {
216 #define MAXGAMEDIRLEN   1000
217         char    temp[MAXGAMEDIRLEN+1];
218         char    *t2 = "/qconsole.log";
219
220         Cvar_RegisterVariable(&logfile);
221         con_debuglog = COM_CheckParm("-condebug");
222
223         if (con_debuglog)
224         {
225                 if (strlen (com_gamedir) < (MAXGAMEDIRLEN - strlen (t2)))
226                 {
227                         sprintf (temp, "%s%s", com_gamedir, t2);
228                         unlink (temp);
229                 }
230                 logfile.integer = 1;
231         }
232
233         console_mempool = Mem_AllocPool("console");
234         con_text = Mem_Alloc(console_mempool, CON_TEXTSIZE);
235         memset (con_text, ' ', CON_TEXTSIZE);
236         con_linewidth = -1;
237         Con_CheckResize ();
238
239         Con_Printf ("Console initialized.\n");
240
241 //
242 // register our commands
243 //
244         Cvar_RegisterVariable (&con_notifytime);
245
246         Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
247         Cmd_AddCommand ("messagemode", Con_MessageMode_f);
248         Cmd_AddCommand ("messagemode2", Con_MessageMode2_f);
249         Cmd_AddCommand ("clear", Con_Clear_f);
250         con_initialized = true;
251 }
252
253
254 /*
255 ===============
256 Con_Linefeed
257 ===============
258 */
259 void Con_Linefeed (void)
260 {
261         con_x = 0;
262         con_current++;
263         memset (&con_text[(con_current%con_totallines)*con_linewidth]
264         , ' ', con_linewidth);
265 }
266
267 /*
268 ================
269 Con_Print
270
271 Handles cursor positioning, line wrapping, etc
272 All console printing must go through this in order to be logged to disk
273 If no console is visible, the notify window will pop up.
274 ================
275 */
276 void Con_Print (char *txt)
277 {
278         int             y;
279         int             c, l;
280         static int      cr;
281         int             mask;
282
283         con_backscroll = 0;
284
285         if (txt[0] == 1)
286         {
287                 mask = 128;             // go to colored text
288                 S_LocalSound ("misc/talk.wav");
289         // play talk wav
290                 txt++;
291         }
292         else if (txt[0] == 2)
293         {
294                 mask = 128;             // go to colored text
295                 txt++;
296         }
297         else
298                 mask = 0;
299
300
301         while ( (c = *txt) )
302         {
303         // count word length
304                 for (l=0 ; l< con_linewidth ; l++)
305                         if ( txt[l] <= ' ')
306                                 break;
307
308         // word wrap
309                 if (l != con_linewidth && (con_x + l > con_linewidth) )
310                         con_x = 0;
311
312                 txt++;
313
314                 if (cr)
315                 {
316                         con_current--;
317                         cr = false;
318                 }
319
320
321                 if (!con_x)
322                 {
323                         Con_Linefeed ();
324                 // mark time for transparent overlay
325                         if (con_current >= 0)
326                                 con_times[con_current % NUM_CON_TIMES] = realtime;
327                 }
328
329                 switch (c)
330                 {
331                 case '\n':
332                         con_x = 0;
333                         break;
334
335                 case '\r':
336                         con_x = 0;
337                         cr = 1;
338                         break;
339
340                 default:        // display character and advance
341                         y = con_current % con_totallines;
342                         con_text[y*con_linewidth+con_x] = c | mask;
343                         con_x++;
344                         if (con_x >= con_linewidth)
345                                 con_x = 0;
346                         break;
347                 }
348
349         }
350 }
351
352
353 /*
354 ================
355 Con_DebugLog
356 ================
357 */
358 void Con_DebugLog(char *file, char *fmt, ...)
359 {
360     va_list argptr;
361     FILE* fd;
362
363     fd = fopen(file, "at");
364     va_start(argptr, fmt);
365     vfprintf (fd, fmt, argptr);
366     va_end(argptr);
367     fclose(fd);
368 }
369
370
371 /*
372 ================
373 Con_Printf
374
375 Handles cursor positioning, line wrapping, etc
376 ================
377 */
378 // LordHavoc: increased from 4096 to 16384
379 #define MAXPRINTMSG     16384
380 // FIXME: make a buffer size safe vsprintf?
381 void Con_Printf (char *fmt, ...)
382 {
383         va_list         argptr;
384         char            msg[MAXPRINTMSG];
385
386         va_start (argptr,fmt);
387         vsprintf (msg,fmt,argptr);
388         va_end (argptr);
389
390 // also echo to debugging console
391         Sys_Printf ("%s", msg);
392
393 // log all messages to file
394         if (con_debuglog)
395         {
396                 // can't use va() here because it might overwrite other important things
397                 char logname[MAX_OSPATH];
398                 sprintf(logname, "%s/qconsole.log", com_gamedir);
399                 Con_DebugLog(logname, "%s", msg);
400         }
401
402         if (!con_initialized)
403                 return;
404
405         if (cls.state == ca_dedicated)
406                 return;         // no graphics mode
407
408 // write it to the scrollable buffer
409         Con_Print (msg);
410 }
411
412 /*
413 ================
414 Con_DPrintf
415
416 A Con_Printf that only shows up if the "developer" cvar is set
417 ================
418 */
419 void Con_DPrintf (char *fmt, ...)
420 {
421         va_list         argptr;
422         char            msg[MAXPRINTMSG];
423
424         if (!developer.integer)
425                 return;                 // don't confuse non-developers with techie stuff...
426
427         va_start (argptr,fmt);
428         vsprintf (msg,fmt,argptr);
429         va_end (argptr);
430
431         Con_Printf ("%s", msg);
432 }
433
434
435 /*
436 ==================
437 Con_SafePrintf
438
439 Okay to call even when the screen can't be updated
440 ==================
441 */
442 void Con_SafePrintf (char *fmt, ...)
443 {
444         va_list         argptr;
445         char            msg[1024];
446
447         va_start (argptr,fmt);
448         vsprintf (msg,fmt,argptr);
449         va_end (argptr);
450
451         Con_Printf ("%s", msg);
452 }
453
454
455 /*
456 ==============================================================================
457
458 DRAWING
459
460 ==============================================================================
461 */
462
463
464 /*
465 ================
466 Con_DrawInput
467
468 The input line scrolls horizontally if typing goes beyond the right edge
469
470 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
471 ================
472 */
473 void Con_DrawInput (void)
474 {
475         char editlinecopy[256], *text;
476
477         if (key_dest != key_console && !con_forcedup)
478                 return;         // don't draw anything
479
480         text = strcpy(editlinecopy, key_lines[edit_line]);
481
482         // Advanced Console Editing by Radix radix@planetquake.com
483         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
484         // use strlen of edit_line instead of key_linepos to allow editing
485         // of early characters w/o erasing
486
487         // add the cursor frame
488         if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
489                 text[key_linepos] = 11 + 130 * key_insert;      // either solid or triangle facing right
490
491         text[key_linepos + 1] = 0;
492
493         // prestep if horizontally scrolling
494         if (key_linepos >= con_linewidth)
495                 text += 1 + key_linepos - con_linewidth;
496
497         // draw it
498         DrawQ_String(8, con_vislines - 16, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
499
500         // remove cursor
501         key_lines[edit_line][key_linepos] = 0;
502 }
503
504
505 /*
506 ================
507 Con_DrawNotify
508
509 Draws the last few lines of output transparently over the game top
510 ================
511 */
512 void Con_DrawNotify (void)
513 {
514         int             x, v;
515         char    *text;
516         int             i;
517         float   time;
518         extern char chat_buffer[];
519         char    temptext[256];
520
521         v = 0;
522         for (i= con_current-NUM_CON_TIMES+1 ; i<=con_current ; i++)
523         {
524                 if (i < 0)
525                         continue;
526                 time = con_times[i % NUM_CON_TIMES];
527                 if (time == 0)
528                         continue;
529                 time = realtime - time;
530                 if (time > con_notifytime.value)
531                         continue;
532                 text = con_text + (i % con_totallines)*con_linewidth;
533
534                 clearnotify = 0;
535
536                 DrawQ_String(8, v, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
537
538                 v += 8;
539         }
540
541
542         if (key_dest == key_message)
543         {
544                 clearnotify = 0;
545
546                 x = 0;
547
548                 // LordHavoc: speedup, and other improvements
549                 if (team_message)
550                         sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
551                 else
552                         sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
553                 while (strlen(temptext) >= con_linewidth)
554                 {
555                         DrawQ_String (8, v, temptext, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
556                         strcpy(temptext, &temptext[con_linewidth]);
557                         v += 8;
558                 }
559                 if (strlen(temptext) > 0)
560                 {
561                         DrawQ_String (8, v, temptext, 0, 8, 8, 1, 1, 1, 1, 0);
562                         v += 8;
563                 }
564         }
565
566         if (v > con_notifylines)
567                 con_notifylines = v;
568 }
569
570 /*
571 ================
572 Con_DrawConsole
573
574 Draws the console with the solid background
575 The typing input line at the bottom should only be drawn if typing is allowed
576 ================
577 */
578 extern cvar_t scr_conalpha;
579 extern char engineversion[40];
580 void Con_DrawConsole (int lines)
581 {
582         int                             i, y;
583         int                             rows;
584         char                    *text;
585         int                             j;
586
587         if (lines <= 0)
588                 return;
589
590 // draw the background
591         DrawQ_Pic(0, lines - vid.conheight, "gfx/conback", vid.conwidth, vid.conheight, 1, 1, 1, scr_conalpha.value * lines / vid.conheight, 0);
592         DrawQ_String(vid.conwidth - strlen(engineversion) * 8 - 8, lines - 8, engineversion, 0, 8, 8, 1, 0, 0, 1, 0);
593
594 // draw the text
595         con_vislines = lines;
596
597         rows = (lines-16)>>3;           // rows of text to draw
598         y = lines - 16 - (rows<<3);     // may start slightly negative
599
600         for (i = con_current - rows + 1;i <= con_current;i++, y += 8)
601         {
602                 j = max(i - con_backscroll, 0);
603                 text = con_text + (j % con_totallines)*con_linewidth;
604
605                 DrawQ_String(8, y, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
606         }
607
608 // draw the input prompt, user text, and cursor if desired
609         Con_DrawInput ();
610 }
611
612 /*
613         Con_DisplayList
614
615         New function for tab-completion system
616         Added by EvilTypeGuy
617         MEGA Thanks to Taniwha
618
619 */
620 void
621 Con_DisplayList(char **list)
622 {
623         int     i = 0;
624         int     pos = 0;
625         int     len = 0;
626         int     maxlen = 0;
627         int     width = (con_linewidth - 4);
628         char    **walk = list;
629
630         while (*walk) {
631                 len = strlen(*walk);
632                 if (len > maxlen)
633                         maxlen = len;
634                 walk++;
635         }
636         maxlen += 1;
637
638         while (*list) {
639                 len = strlen(*list);
640                 if (pos + maxlen >= width) {
641                         Con_Printf("\n");
642                         pos = 0;
643                 }
644
645                 Con_Printf("%s", *list);
646                 for (i = 0; i < (maxlen - len); i++)
647                         Con_Printf(" ");
648
649                 pos += maxlen;
650                 list++;
651         }
652
653         if (pos)
654                 Con_Printf("\n\n");
655 }
656
657 /*
658         Con_CompleteCommandLine
659
660         New function for tab-completion system
661         Added by EvilTypeGuy
662         Thanks to Fett erich@heintz.com
663         Thanks to taniwha
664
665 */
666 void
667 Con_CompleteCommandLine (void)
668 {
669         char    *cmd = "";
670         char    *s;
671         int             c, v, a, i;
672         int             cmd_len;
673         char    **list[3] = {0, 0, 0};
674
675         s = key_lines[edit_line] + 1;
676         // Count number of possible matches
677         c = Cmd_CompleteCountPossible(s);
678         v = Cvar_CompleteCountPossible(s);
679         a = Cmd_CompleteAliasCountPossible(s);
680
681         if (!(c + v + a))       // No possible matches
682                 return;
683
684         if (c + v + a == 1) {
685                 if (c)
686                         list[0] = Cmd_CompleteBuildList(s);
687                 else if (v)
688                         list[0] = Cvar_CompleteBuildList(s);
689                 else
690                         list[0] = Cmd_CompleteAliasBuildList(s);
691                 cmd = *list[0];
692                 cmd_len = strlen (cmd);
693         } else {
694                 if (c)
695                         cmd = *(list[0] = Cmd_CompleteBuildList(s));
696                 if (v)
697                         cmd = *(list[1] = Cvar_CompleteBuildList(s));
698                 if (a)
699                         cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
700
701                 cmd_len = strlen (s);
702                 do {
703                         for (i = 0; i < 3; i++) {
704                                 char ch = cmd[cmd_len];
705                                 char **l = list[i];
706                                 if (l) {
707                                         while (*l && (*l)[cmd_len] == ch)
708                                                 l++;
709                                         if (*l)
710                                                 break;
711                                 }
712                         }
713                         if (i == 3)
714                                 cmd_len++;
715                 } while (i == 3);
716                 // 'quakebar'
717                 Con_Printf("\n\35");
718                 for (i = 0; i < con_linewidth - 4; i++)
719                         Con_Printf("\36");
720                 Con_Printf("\37\n");
721
722                 // Print Possible Commands
723                 if (c) {
724                         Con_Printf("%i possible command%s\n", c, (c > 1) ? "s: " : ":");
725                         Con_DisplayList(list[0]);
726                 }
727
728                 if (v) {
729                         Con_Printf("%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
730                         Con_DisplayList(list[1]);
731                 }
732
733                 if (a) {
734                         Con_Printf("%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
735                         Con_DisplayList(list[2]);
736                 }
737         }
738
739         if (cmd) {
740                 strncpy(key_lines[edit_line] + 1, cmd, cmd_len);
741                 key_linepos = cmd_len + 1;
742                 if (c + v + a == 1) {
743                         key_lines[edit_line][key_linepos] = ' ';
744                         key_linepos++;
745                 }
746                 key_lines[edit_line][key_linepos] = 0;
747         }
748         for (i = 0; i < 3; i++)
749                 if (list[i])
750                         Mem_Free(list[i]);
751 }
752