engine: reject mbf21 and shit24 wads. there is no way to know if it is safe to ignore...
[k8vavoom.git] / source / console.cpp
blobf58881615a66e4f93857be93fa0a5d73bfafdff3
1 //**************************************************************************
2 //**
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
9 //**
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
12 //**
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
16 //**
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
21 //**
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
24 //**
25 //**************************************************************************
26 #ifdef VAVOOM_CUSTOM_SPECIAL_SDL
27 # include <SDL.h>
28 #else
29 # include <SDL2/SDL.h>
30 #endif
31 #include "gamedefs.h"
32 #include "host.h" /* host_frametime */
33 #ifdef CLIENT
34 # include "drawer.h"
35 # include "screen.h"
36 # include "text.h"
37 # include "iline.h"
38 # include "menu.h"
39 #endif
41 #ifdef ANDROID
42 # include <android/log.h>
43 #endif
45 #define MAXHISTORY (64)
46 #define MAX_LINES (1024)
47 #define MAX_LINE_LENGTH (80)
50 extern const char *cli_LogFileName;
52 static bool GConTTYLogPrevious = false;
53 static bool GConTTYLogDisabled = false;
54 static bool GConSplashActive = false;
55 bool GConTTYLogForced = true;
58 //==========================================================================
60 // C_SplashActive
62 //==========================================================================
63 void C_SplashActive (bool v) {
64 GConSplashActive = v;
68 //==========================================================================
70 // C_DisableTTYLogs
72 //==========================================================================
73 void C_DisableTTYLogs () {
74 if (GConTTYLogDisabled || GConTTYLogForced) return;
75 #ifndef WIN32
76 if (GLogTTYLog) GCon->Logf(NAME_Warning, "tty logs disabled to avoid random slowdowns and disconnects.");
77 #endif
78 GConTTYLogDisabled = true;
79 GConTTYLogPrevious = GLogTTYLog;
80 GLogTTYLog = false;
84 //==========================================================================
86 // C_EnableTTYLogs
88 //==========================================================================
89 void C_EnableTTYLogs () {
90 if (!GConTTYLogDisabled || GConTTYLogForced) return;
91 GConTTYLogDisabled = false;
92 GLogTTYLog = GConTTYLogPrevious;
93 #ifndef WIN32
94 if (GLogTTYLog) GCon->Logf(NAME_Warning, "tty logs reenabled.");
95 #endif
99 enum cons_state_t {
100 cons_closed,
101 cons_opening,
102 cons_opening_imm,
103 cons_open,
104 cons_closing,
105 cons_closing_imm,
109 class FConsoleDevice : public FOutputDevice {
110 public:
111 FConsoleDevice () noexcept;
112 virtual void Serialise (const char *V, EName Event) noexcept override;
116 class FConsoleLog : public VLogListener {
117 public:
118 virtual void Serialise (const char *V, EName Event) noexcept override;
122 static mythread_mutex conLogLock;
123 FConsoleDevice Console;
124 FOutputDevice *GCon = &Console;
126 FConsoleDevice::FConsoleDevice () noexcept {
127 mythread_mutex_init(&conLogLock);
131 static TILine c_iline;
133 static cons_state_t consolestate = cons_closed;
135 struct ConLine {
136 char *str;
137 int len;
138 int alloced;
140 ConLine () : str(nullptr), len(0), alloced(0) {}
143 static ConLine clines[MAX_LINES];
144 static int num_lines = 0;
145 static int first_line = 0;
146 static int last_line = 0;
149 struct HistoryLine {
150 public:
151 char *str;
152 int bufsize; // including trailing zero; NOT line length!
154 public:
155 VV_DISABLE_COPY(HistoryLine)
157 HistoryLine () noexcept : str(nullptr), bufsize(0) {}
158 ~HistoryLine () noexcept { Z_Free(str); str = nullptr; bufsize = 0; }
160 inline void swap (HistoryLine &src) noexcept {
161 if (&src == this) return;
162 { char *tmp = str; str = src.str; src.str = tmp; }
163 { int tmp = bufsize; bufsize = src.bufsize; src.bufsize = tmp; }
166 inline const char *getCStr () const noexcept { return (str ? str : ""); }
168 inline bool strEqu (const char *s, bool doStrip) const noexcept {
169 if (!str) return false; // not initialized
170 if (!s || !s[0]) return (str[0] == 0);
171 if (doStrip) return strEqu(VStr(s), true);
172 return (VStr::Cmp(str, s) == 0);
175 inline bool strEqu (VStr s, bool doStrip) const noexcept {
176 if (!str) return false; // not initialized
177 if (s.isEmpty()) return (str[0] == 0);
178 if (!doStrip) return s.strEqu(str);
179 return s.xstrip().strEqu(VStr(str).xstrip());
182 inline void putStr (const char *s) noexcept {
183 if (!s) s = "";
184 int slen = (int)strlen(s)+1;
185 int newsz = (slen|0xff)+1;
186 if (bufsize < newsz) {
187 str = (char *)Z_Realloc(str, newsz);
188 bufsize = newsz;
190 strcpy(str, s);
194 static int c_history_size = 0;
195 static int c_history_current = -1;
196 static HistoryLine c_history[MAXHISTORY]; // 0 is oldest
199 static float cons_h = 0;
201 static VCvarF con_height("con_height", "240", "Console height.", CVAR_Archive|CVAR_NoShadow);
202 static VCvarF con_speed("con_speed", "6666", "Console sliding speed.", CVAR_Archive|CVAR_NoShadow);
203 static VCvarB con_clear_input_on_open("con_clear_input_on_open", true, "Clear input line when console opens?", CVAR_Archive|CVAR_NoShadow);
205 static FConsoleLog ConsoleLog;
207 static FILE *logfout = nullptr;
210 //==========================================================================
212 // onShowCompletionMatchCB
214 //==========================================================================
215 static void onShowCompletionMatchCB (bool isheader, VStr s) {
216 if (isheader) {
217 GCon->Logf("\034K%s", *s);
218 } else {
219 GCon->Logf("\034D %s", *s);
224 //==========================================================================
226 // C_SysErrorCallback
228 //==========================================================================
229 static void C_SysErrorCallback (const char *msg) noexcept {
230 if (logfout) {
231 fprintf(logfout, "%s\n", (msg ? msg : ""));
232 fclose(logfout);
233 logfout = nullptr;
238 //==========================================================================
240 // C_Init
242 // Console initialization
244 //==========================================================================
245 void C_Init () {
246 VCommand::onShowCompletionMatch = &onShowCompletionMatchCB;
248 if (cli_LogFileName && cli_LogFileName[0]) {
249 logfout = fopen(cli_LogFileName, "w");
252 #if defined(_WIN32) || (defined(__SWITCH__) && !defined(SWITCH_NXLINK))
253 if (!logfout) logfout = fopen("conlog.log", "w");
254 #endif
256 SysErrorCB = &C_SysErrorCallback;
258 memset((void *)&clines[0], 0, sizeof(clines));
259 c_history_size = 0;
260 c_history_current = -1;
261 for (int f = 0; f < MAXHISTORY; ++f) {
262 vassert(c_history[f].str == nullptr);
263 vassert(c_history[f].bufsize == 0);
265 GLog.AddListener(&ConsoleLog);
267 // prompt, cursor, and one char reserved
268 c_iline.SetVisChars(MAX_LINE_LENGTH-3);
269 c_iline.Init();
273 //==========================================================================
275 // C_Shutdown
277 //==========================================================================
278 void C_Shutdown () {
279 if (logfout) fclose(logfout);
280 logfout = nullptr;
284 //==========================================================================
286 // C_Start
288 // Open console
290 //==========================================================================
291 void C_Start (bool immediate) {
292 MN_DeactivateMenu();
293 MyThreadLocker lock(&conLogLock);
294 if (consolestate == cons_closed) {
295 if (con_clear_input_on_open) c_iline.Init();
296 last_line = num_lines;
298 consolestate = (immediate ? cons_opening_imm : cons_opening);
299 c_history_current = -1;
300 #ifdef ANDROID
301 SDL_StartTextInput();
302 #endif
306 //==========================================================================
308 // C_StartFull
310 //==========================================================================
311 void C_StartFull () {
312 MN_DeactivateMenu();
313 MyThreadLocker lock(&conLogLock);
314 c_iline.Init();
315 last_line = num_lines;
316 consolestate = cons_open;
317 c_history_current = -1;
318 //cons_h = 480.0f;
319 cons_h = fmax(con_height.asFloat(), 128.0f);
320 #ifdef ANDROID
321 SDL_StartTextInput();
322 #endif
326 //==========================================================================
328 // C_Stop
330 // Close console
332 //==========================================================================
333 void C_Stop (bool immediate) {
334 SDL_StopTextInput();
335 consolestate = (immediate ? cons_closing_imm : cons_closing);
339 //==========================================================================
341 // ToggleConsole
343 //==========================================================================
344 COMMAND(ToggleConsole) {
345 //C_Start();
346 if (consolestate == cons_closed) C_Start(); else C_Stop();
350 //==========================================================================
352 // ShowConsole
354 //==========================================================================
355 COMMAND(ShowConsole) {
356 C_Start(Args.length() > 1 && Args[1].startsWithCI("imm"));
360 //==========================================================================
362 // HideConsole
364 //==========================================================================
365 COMMAND(HideConsole) {
366 C_Stop(Args.length() > 1 && Args[1].startsWithCI("imm"));
370 //==========================================================================
372 // C_Active
374 //==========================================================================
375 bool C_Active () {
376 return (consolestate == cons_opening || consolestate == cons_open);
380 //==========================================================================
382 // DrawInputLine
384 // font and text mode should be already set
386 //==========================================================================
387 static void DrawInputLine (int y) {
388 // input line
389 c_iline.cursorChar = '\x0b';
390 T_DrawText(4, y, ">", CR_YELLOW);
391 c_iline.DrawAt(12, y, CR_ORANGE, CR_FIRE);
395 //==========================================================================
397 // C_Drawer
399 // Draws console
401 //==========================================================================
402 void C_Drawer () {
403 // scroll console up when closing
404 if (consolestate == cons_closing) {
405 cons_h -= con_speed*host_frametime;
406 if (cons_h <= 0) {
407 // closed
408 cons_h = 0;
409 consolestate = cons_closed;
411 } else if (consolestate == cons_closing_imm) {
412 cons_h = 0;
413 consolestate = cons_closed;
416 // scroll console down when opening
417 if (consolestate == cons_opening) {
418 cons_h += con_speed*host_frametime;
419 if (cons_h >= con_height) {
420 // open
421 cons_h = con_height;
422 consolestate = cons_open;
424 } else if (consolestate == cons_opening_imm) {
425 cons_h = con_height;
426 consolestate = cons_open;
429 if (!consolestate) return;
431 // background
432 Drawer->DrawConsoleBackground((int)(fScaleY*cons_h));
434 T_SetFont(ConFont);
435 T_SetAlign(hleft, vtop);
437 // input line
438 int y = (int)cons_h-10;
439 DrawInputLine(y);
440 y -= 10;
442 // lines
443 MyThreadLocker lock(&conLogLock);
444 int i = last_line;
445 while ((y+9 > 0) && i--) {
446 int lidx = (i+first_line)%MAX_LINES;
447 const ConLine &line = clines[lidx];
448 int trans = CR_UNTRANSLATED;
449 //if (line[0] == 1) { trans = line[1]; line += 2; }
450 T_DrawText(4, y, (line.str ? line.str : ""), trans);
451 y -= 9;
456 //==========================================================================
458 // C_Responder
460 // Handles the events
462 //==========================================================================
463 bool C_Responder (event_t *ev) {
464 const char *cp;
465 VStr str;
467 // respond to events only when console is active
468 if (!C_Active()) return false;
470 // we are iterested only in key down events
471 if (ev->type != ev_keydown) return false;
472 // k8: nope, eat all keyboard events
473 // oops, console (de)activation is processed down the chain
474 //if (ev->type != ev_keydown && ev->type != ev_keyup) return false;
476 // shit; i have to perform more fine-grained locking
477 switch (ev->keycode) {
478 // close console
479 case K_ESCAPE:
480 if (consolestate != cons_open) return false;
481 /* fallthrough */
482 case '`':
483 case K_BACKQUOTE:
484 //GCon->Logf("::: %d", (int)(ev->keycode == K_BACKQUOTE));
485 if (ev->isShiftDown()) {
486 c_iline.AddChar('~');
487 } else {
488 if (consolestate == cons_closing) C_Start(); else C_Stop();
490 return true;
492 // execute entered command
493 case K_ENTER:
494 case K_PADENTER:
496 VStr ccmds = VStr(c_iline.getCStr()).xstrip();
497 if (ccmds.length() != 0) {
498 VStr ccmdfull = VStr(c_iline.getCStr()).trimLeft();
499 // leave only one trailing space
500 if (!ccmdfull.isEmpty() && (vuint8)ccmdfull[ccmdfull.length()-1] <= ' ') ccmdfull = ccmdfull.xstrip()+" ";
501 // print it
502 GCon->Logf(">%s", *ccmds);
503 MyThreadLocker lock(&conLogLock);
505 // add to history (but if it is a duplicate, move it to history top)
506 int dupidx = -1;
507 for (int f = 0; f < c_history_size; ++f) {
508 if (c_history[f].strEqu(ccmds, true)) {
509 dupidx = f;
510 // if we have a space at the end, replace
511 if (ccmdfull.length() > ccmds.length()) c_history[f].putStr(*ccmdfull);
512 break;
515 if (dupidx >= 0) {
516 // duplicate found
517 // move to history bottom (or top, it depends of your PoV)
518 for (int f = dupidx+1; f <= c_history_size-1; ++f) c_history[f-1].swap(c_history[f]);
519 } else {
520 // no duplicate, append to history buffer
521 if (c_history_size == MAXHISTORY) {
522 // move oldest line to bottom, and reuse it
523 for (int f = 1; f < MAXHISTORY; ++f) c_history[f-1].swap(c_history[f]);
524 } else {
525 ++c_history_size;
527 c_history[c_history_size-1].putStr(*ccmdfull);
529 c_history_current = -1;
531 // add to command buffer
532 GCmdBuf << ccmds << "\n";
536 // clear line
537 c_iline.Init();
538 return true;
540 // scroll lines up
541 case K_PAGEUP:
543 MyThreadLocker lock(&conLogLock);
544 for (int i = 0; i < (ev->isShiftDown() ? 1 : max2(2, (int)con_height/9-2)); ++i) {
545 if (last_line > 1) --last_line;
548 return true;
550 // scroll lines down
551 case K_PAGEDOWN:
553 MyThreadLocker lock(&conLogLock);
554 for (int i = 0; i < (ev->isShiftDown() ? 1 : max2(2, (int)con_height/9-2)); ++i) {
555 if (last_line < num_lines) ++last_line;
558 return true;
560 // go to first line
561 case K_HOME:
562 if (ev->isShiftDown()) {
563 MyThreadLocker lock(&conLogLock);
564 last_line = 1;
565 return true;
567 return c_iline.Key(*ev);
569 // go to last line
570 case K_END:
571 if (ev->isShiftDown()) {
572 MyThreadLocker lock(&conLogLock);
573 last_line = num_lines;
574 return true;
576 return c_iline.Key(*ev);
578 // command history up
579 case K_UPARROW:
580 if (c_history_size > 0 && c_history_current < c_history_size-1) {
581 ++c_history_current;
582 c_iline.Init();
583 cp = c_history[c_history_size-c_history_current-1].getCStr();
584 while (*cp) c_iline.AddChar(*cp++);
586 return true;
588 // command history down
589 case K_DOWNARROW:
590 if (c_history_size > 0 && c_history_current >= 0) {
591 --c_history_current;
592 c_iline.Init();
593 if (c_history_current >= 0) {
594 cp = c_history[c_history_size-c_history_current-1].getCStr();
595 while (*cp) c_iline.AddChar(*cp++);
598 return true;
600 // auto complete
601 case K_TAB:
602 if (c_iline.length() != 0) {
603 VStr clineRest = c_iline.getCStr();
604 VStr cline = clineRest.left(c_iline.getCurPos());
605 clineRest.chopLeft(c_iline.getCurPos());
606 if (cline.length() && clineRest.length() && clineRest[0] == '"') {
607 cline += clineRest[0];
608 clineRest.chopLeft(1);
610 // find last command
611 int cmdstart = cline.findNextCommand();
612 for (;;) {
613 const int cmdstnext = cline.findNextCommand(cmdstart);
614 if (cmdstnext == cmdstart) break;
615 cmdstart = cmdstnext;
617 VStr oldpfx = cline;
618 oldpfx.chopLeft(cmdstart); // remove completed commands
619 VStr newpfx = VCommand::GetAutoComplete(oldpfx);
620 if (oldpfx != newpfx) {
621 c_iline.Init();
622 c_iline.AddString(cline.left(cmdstart));
623 c_iline.AddString(newpfx);
624 // append rest of cline
625 if (clineRest.length()) {
626 int cpos = c_iline.getCurPos();
627 c_iline.AddString(clineRest);
628 c_iline.setCurPos(cpos);
632 return true;
634 // add character to input line
635 default:
636 return c_iline.Key(*ev);
641 //==========================================================================
643 // Cls_f
645 //==========================================================================
646 COMMAND(Cls) {
647 MyThreadLocker lock(&conLogLock);
648 num_lines = 0;
649 first_line = 0;
650 last_line = 0;
654 //==========================================================================
656 // AddLine
658 // Ads a line to console strings
660 //==========================================================================
661 static void AddLine (const char *Data) noexcept {
662 if (!Data) Data = "";
663 //MyThreadLocker lock(&conLogLock);
664 if (num_lines >= MAX_LINES) {
665 --num_lines;
666 ++first_line;
668 int len = VStr::length(Data);
669 int lidx = (num_lines+first_line)%MAX_LINES;
670 ConLine &line = clines[lidx];
671 if (len+1 > line.alloced) {
672 int newsz = ((len+1)|0xff)+1;
673 line.str = (char *)Z_Realloc(line.str, newsz);
674 line.alloced = newsz;
676 line.len = len;
677 if (len) memcpy(line.str, Data, len);
678 line.str[len] = 0;
680 VStr::NCpy(clines[(num_lines+first_line)%MAX_LINES], Data, MAX_LINE_LENGTH);
681 clines[(num_lines+first_line)%MAX_LINES][MAX_LINE_LENGTH-1] = 0;
683 ++num_lines;
684 if (last_line == num_lines-1) last_line = num_lines;
685 #ifdef CLIENT
686 // update splash if necessary
687 if (GConSplashActive && Drawer) {
688 if (Drawer->IsLoadingSplashActive()) {
689 Drawer->DrawLoadingSplashText(Data, len);
690 } else {
691 GConSplashActive = false;
694 #endif
698 //==========================================================================
700 // C_FlushSplash
702 // can be called to force splash screen update
704 //==========================================================================
705 void C_FlushSplash () {
706 #ifdef CLIENT
707 if (GConSplashActive && Drawer) {
708 if (Drawer->IsLoadingSplashActive()) {
709 Drawer->DrawLoadingSplashText(nullptr, 0);
710 } else {
711 GConSplashActive = false;
714 #endif
718 //==========================================================================
720 // DoPrint
722 //==========================================================================
723 static ConLine cpbuf;
724 static int cpCurrLineLen = 0;
725 static VStr cpLastColor;
726 static bool cpLogFileNeedName = true;
729 static void cpAppendChar (char ch) noexcept {
730 if (ch == 0) ch = ' ';
731 int nlen = cpbuf.len+1;
732 if (nlen+1 > cpbuf.alloced) {
733 int newsz = ((nlen+1)|0xff)+1;
734 cpbuf.str = (char *)Z_Realloc(cpbuf.str, newsz);
735 cpbuf.alloced = newsz;
737 cpbuf.str[cpbuf.len++] = ch;
738 cpbuf.str[cpbuf.len] = 0;
742 static void cpPrintCurrColor () noexcept {
743 for (const char *s = cpLastColor.getCStr(); *s; ++s) cpAppendChar(*s);
747 static void cpFlushCurrent (bool asNewline) noexcept {
748 AddLine(cpbuf.str);
749 cpbuf.len = 0;
750 if (cpbuf.str) cpbuf.str[0] = 0;
751 cpCurrLineLen = 0;
752 if (asNewline) {
753 cpLastColor.clear();
754 } else {
755 cpPrintCurrColor();
760 // *ch should be TEXT_COLOR_ESCAPE
761 static const char *cpProcessColorEscape (const char *ch) noexcept {
762 vassert(*ch == TEXT_COLOR_ESCAPE);
763 cpLastColor.clear();
764 ++ch; // skip TEXT_COLOR_ESCAPE
765 if (!ch[0]) {
766 // reset
767 cpAppendChar(TEXT_COLOR_ESCAPE);
768 cpAppendChar('L'); // untranslated
769 return ch;
771 cpLastColor += TEXT_COLOR_ESCAPE;
772 if (*ch == '[') {
773 cpLastColor += *ch++;
774 while (*ch && *ch != ']') cpLastColor += *ch++;
775 if (*ch) cpLastColor += *ch++; else cpLastColor += ']';
776 } else {
777 cpLastColor += *ch++;
779 cpPrintCurrColor();
780 return ch;
784 // *ch should be TEXT_COLOR_ESCAPE
785 static const char *cpSkipColorEscape (const char *ch) noexcept {
786 vassert(*ch == TEXT_COLOR_ESCAPE);
787 ++ch; // skip TEXT_COLOR_ESCAPE
788 if (!ch[0]) return ch;
789 if (*ch++ == '[') {
790 while (*ch && *ch != ']') ++ch;
791 if (*ch) ++ch;
793 return ch;
797 static void DoPrint (const char *buf) noexcept {
798 const char *ch = buf;
799 while (*ch) {
800 if (*ch == '\n') {
801 cpFlushCurrent(true);
802 ++ch;
803 } else if (*ch == TEXT_COLOR_ESCAPE) {
804 // new color sequence
805 ch = cpProcessColorEscape(ch);
806 } else if (*(const vuint8 *)ch > ' ') {
807 // count word length
808 const char *p = ch;
809 int wlen = 0;
810 while (*(const vuint8 *)p > ' ') {
811 if (*p == TEXT_COLOR_ESCAPE) {
812 p = cpSkipColorEscape(p);
813 } else {
814 ++wlen;
815 ++p;
819 if (cpCurrLineLen+wlen >= MAX_LINE_LENGTH) {
820 if (cpCurrLineLen) {
821 // word too long and it is not a first word
822 // add current buffer and try again
823 cpFlushCurrent(false); // don't clear current color
824 } else {
825 // a very long first word, add partially
826 while (*(const vuint8 *)ch > ' ' && cpCurrLineLen < MAX_LINE_LENGTH) {
827 if (*ch == TEXT_COLOR_ESCAPE) {
828 ch = cpProcessColorEscape(ch);
829 } else {
830 cpAppendChar(*ch++);
831 ++cpCurrLineLen;
834 cpFlushCurrent(false); // don't clear current color
836 } else {
837 // add word to buffer
838 while (*(const vuint8 *)ch > ' ') {
839 if (*ch == TEXT_COLOR_ESCAPE) {
840 ch = cpProcessColorEscape(ch);
841 } else {
842 cpAppendChar(*ch++);
843 ++cpCurrLineLen;
847 } else {
848 // whitespace symbol
849 if (cpCurrLineLen < MAX_LINE_LENGTH) {
850 int count;
851 if (*ch == '\t') {
852 // tab
853 count = 8-cpCurrLineLen%8;
854 } else {
855 count = 1;
857 while (count--) {
858 cpAppendChar(' ');
859 ++cpCurrLineLen;
862 ++ch;
868 //==========================================================================
870 // ConSerialise
872 // tty output is done by standard logger
874 //==========================================================================
875 static void ConSerialise (const char *str, EName Event, bool fromGLog) noexcept {
876 if (Event == NAME_Dev && !developer) return;
877 if (!fromGLog) { GLog.WriteLine(Event, "%s", str); return; }
878 if (!str) str = "";
879 MyThreadLocker lock(&conLogLock);
880 //fprintf(stderr, "<<<%s>>>", str);
881 //HACK! if string starts with "Sys_Error:", print it, and close log file
882 if (VStr::NCmp(str, "Sys_Error:", 10) == 0) {
883 if (logfout) { fflush(logfout); fprintf(logfout, "*** %s\n", str); fclose(logfout); logfout = nullptr; }
885 bool resetColor = true;
886 const char *cs = VLog::GetColorInfoEngine(Event, resetColor);
887 if (cs) {
888 cpLastColor = VStr(cs);
889 cpPrintCurrColor();
891 DoPrint(str);
892 #ifdef ANDROID
894 VStr rc = VStr(str).RemoveColors();
895 const char *rstr = *rc;
896 __android_log_print(ANDROID_LOG_DEBUG, "K8VAVOOM", "%s", rstr);
898 #endif
899 // write to log
900 if (logfout) {
901 VStr rc = VStr(str).RemoveColors();
902 const char *rstr = *rc;
903 while (rstr && *rstr) {
904 const char *eol = strchr(rstr, '\n');
905 if (!eol) eol = rstr+strlen(rstr);
906 if (cpLogFileNeedName) {
907 char buf[64];
908 if (Event == NAME_DevNet) {
909 unsigned msecs = unsigned(Sys_Time()*1000);
910 snprintf(buf, sizeof(buf), "%u:", msecs);
911 fprintf(logfout, "%s:%s%s", VName::SafeString(Event), buf, (rstr == eol ? "" : " "));
912 } else {
913 buf[0] = 0;
915 fprintf(logfout, "%s:%s%s", VName::SafeString(Event), buf, (rstr == eol ? "" : " "));
916 cpLogFileNeedName = false;
918 if (eol != rstr) fwrite(rstr, (ptrdiff_t)(eol-rstr), 1, logfout);
919 rstr = eol;
920 if (!rstr[0]) break;
921 vassert(rstr[0] == '\n');
922 fprintf(logfout, "\n");
923 cpLogFileNeedName = true;
924 ++rstr;
926 //fprintf(logfout, "%s: %s", VName::SafeString(Event), *rc);
931 //==========================================================================
933 // FConsoleDevice::Serialise
935 //==========================================================================
936 void FConsoleDevice::Serialise (const char *V, EName Event) noexcept {
937 ConSerialise(V, Event, false);
941 //==========================================================================
943 // FConsoleLog::Serialise
945 //==========================================================================
946 void FConsoleLog::Serialise (const char *Text, EName Event) noexcept {
947 ConSerialise(Text, Event, true);