1 /*
2  *  Debug.cpp
3  *  OpenLieroX
4  *
5  *  Created by Albert Zeyer on 01.01.09.
6  *  code under LGPL
7  *
8  */
9 
10 #include "Debug.h"
11 #include "StringUtils.h"
12 #include "CrashHandler.h"
13 #include "Command.h"
14 
15 
16 #include <time.h>
17 
18 #ifdef WIN32
19 
20 // TODO implement
AmIBeingDebugged()21 bool AmIBeingDebugged() { return false; }
22 
RaiseDebugger()23 void RaiseDebugger() {
24 #ifdef DEBUG
25 #if !defined(__MINGW32__)
26 	// HINT: ignored when not in debugger
27 	// If it just does nothing then, remove the surrounding #ifdef DEBUG
28 	// I read about a Win32's IsDebuggerPresent() function, perhaps you should use that one here.
29 	__asm  { int 3 };
30 #endif
31 #endif
32 }
33 
34 #else
35 
36 #if defined(__APPLE__)
37 
38 #include <cassert>
39 #include <stdbool.h>
40 #include <sys/types.h>
41 #include <unistd.h>
42 #include <sys/sysctl.h>
43 
44 // Based on Apple's recommended method as described in
45 // http://developer.apple.com/qa/qa2004/qa1361.html
AmIBeingDebugged()46 bool AmIBeingDebugged()
47     // Returns true if the current process is being debugged (either
48     // running under the debugger or has a debugger attached post facto).
49 {
50 	// Initialize mib, which tells sysctl what info we want.  In this case,
51 	// we're looking for information about a specific process ID.
52 	int mib[] =
53 	{
54 		CTL_KERN,
55 		KERN_PROC,
56 		KERN_PROC_PID,
57 		getpid()
58 	};
59 
60 	// Caution: struct kinfo_proc is marked __APPLE_API_UNSTABLE.  The source and
61 	// binary interfaces may change.
62 	struct kinfo_proc info;
63 	size_t info_size = sizeof ( info );
64 
65 	int sysctl_result = sysctl ( mib, sizeof(mib) / sizeof(*mib), &info, &info_size, NULL, 0 );
66 	if ( sysctl_result != 0 )
67 		return false;
68 
69 	// This process is being debugged if the P_TRACED flag is set.
70 	return ( info.kp_proc.p_flag & P_TRACED ) != 0;
71 }
72 
73 #else
74 
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <fcntl.h>
78 #include <cstring>
79 #include <unistd.h>
80 
81 
AmIBeingDebugged()82 bool AmIBeingDebugged() {
83 	// We can look in /proc/self/status for TracerPid.  We are likely used in crash
84 	// handling, so we are careful not to use the heap or have side effects.
85 	int status_fd = open("/proc/self/status", O_RDONLY);
86 	if (status_fd == -1)
87 		return false;
88 
89 	// We assume our line will be in the first 1024 characters and that we can
90 	// read this much all at once.  In practice this will generally be true.
91 	// This simplifies and speeds up things considerably.
92 	char buf[1024];
93 
94 	ssize_t num_read = read(status_fd, buf, sizeof(buf));
95 	fix_markend(buf);
96 	close(status_fd);
97 	if (num_read <= 0) return false;
98 
99 	const char* searchStr = "TracerPid:\t";
100 	const char* f = strstr(buf, searchStr);
101 	if(f == NULL) return false;
102 
103 	// Our pid is 0 without a debugger, assume this for any pid starting with 0.
104 	f += strlen(searchStr);
105 	return f < &buf[num_read] && *f != '0';
106 }
107 
108 #endif // OSX/LINUX
109 
110 #include <signal.h>
111 
RaiseDebugger()112 void RaiseDebugger() {
113 	if(AmIBeingDebugged()) {
114 		printf("I am being debugged, raising debugger ...\n");
115 		if(CrashHandler::get()) CrashHandler::get()->disable();
116 		// TODO: We need a way to set another ucontext here. (And that should be specified via a parameter
117 		// to RaiseDebugger().) E.g. when we use this function in the debugger thread, we want to set the
118 		// ucontext of the main loop thread.
119 		raise(SIGABRT);
120 		if(CrashHandler::get()) CrashHandler::get()->enable();
121 	} else
122 		printf("I am not being debugged, ignoring debugger raise.\n");
123 }
124 
125 #endif
126 
127 
128 #ifdef WIN32
129 
130 #include "AuxLib.h" // for Windows.h
131 
132 #include <dbghelp.h>
133 #include <shlobj.h>
134 
135 #include "LieroX.h"
136 #include "CClient.h"
137 #include "CServer.h"
138 #include "DedicatedControl.h"
139 #include "StringUtils.h"
140 #include "ConversationLogger.h"
141 #include "CGameMode.h"
142 
143 #define itoa _itoa
144 
ReadGameStateForReport(char * buffer,size_t bufsize)145 void *ReadGameStateForReport(char *buffer, size_t bufsize)
146 {
147 	memset(buffer, 0, bufsize);
148 #if !defined(__MINGW32__)
149 	__try {
150 #endif
151 		if (cClient)  {
152 			strncat(buffer, "Game state:\n", bufsize);
153 			if (cClient->getStatus() == NET_CONNECTED)  {
154 				if (cClient->getGameReady())
155 					strncat(buffer, "In game, selecting weapons.", bufsize);
156 				else
157 					strncat(buffer, "In lobby.", bufsize);
158 			} else if (cClient->getStatus() == NET_PLAYING)  {
159 				strncat(buffer, "In game, playing.", bufsize);
160 			} else if (cClient->getStatus() == NET_CONNECTING)  {
161 				strncat(buffer, "Connecting to a server.", bufsize);
162 			} else if (cClient->getStatus() == NET_DISCONNECTED)  {
163 				strncat(buffer, "Disconnected.\n", bufsize);
164 			} else {
165 				strncat(buffer, "Unknown state.\n", bufsize);
166 			}
167 		}
168 		buffer[bufsize - 1] = '\0';
169 #if !defined(__MINGW32__)
170 	} __except (EXCEPTION_EXECUTE_HANDLER)
171 	{ return buffer; }
172 #endif
173 
174 	return buffer;
175 }
176 
ReadGameInfoForReport(char * buffer,size_t bufsize)177 void *ReadGameInfoForReport(char *buffer, size_t bufsize)
178 {
179 	memset(buffer, 0, bufsize);
180 	if (!tLXOptions || !tLX)
181 		return buffer;
182 	char tmp[32];
183 #if !defined(__MINGW32__)
184 	__try  {
185 #endif
186 		// Game type
187 		strncat(buffer, "iGameType = ", bufsize);
188 		switch (tLX->iGameType)  {
189 		case GME_LOCAL:
190 			strncat(buffer, "GME_LOCAL", bufsize);
191 			break;
192 		case GME_HOST:
193 			strncat(buffer, "GME_HOST", bufsize);
194 			break;
195 		case GME_JOIN:
196 			strncat(buffer, "GME_JOIN", bufsize);
197 			break;
198 		default:
199 			itoa(tLX->iGameType, tmp, 10);
200 			fix_markend(tmp);
201 			strncat(buffer, "UNKNOWN ", bufsize); strncat(buffer, tmp, bufsize);
202 		}
203 		strncat(buffer, "\n", bufsize);
204 
205 		// Game mode
206 		strncat(buffer, "GameMode = ", bufsize);
207 		//char tmp[16];
208 		itoa(tLXOptions->tGameInfo.gameMode->GeneralGameType(), tmp, 10);
209 		fix_markend(tmp);
210 		strncat(buffer, tmp, bufsize);
211 		strncat(buffer, "\n", bufsize);
212 
213 		// Mod name
214 		strncat(buffer, "sModName = ", bufsize);
215 		if (tLXOptions->tGameInfo.sModName.size())
216 			strncat(buffer, tLXOptions->tGameInfo.sModName.c_str(), bufsize);
217 		strncat(buffer, "\n", bufsize);
218 
219 		// Map file
220 		strncat(buffer, "sMapFile = ", bufsize);
221 		if (tLXOptions->tGameInfo.sMapFile.size())
222 			strncat(buffer, tLXOptions->tGameInfo.sMapFile.c_str(), bufsize);
223 		strncat(buffer, "\n", bufsize);
224 
225 		// Map name
226 		strncat(buffer, "sMapName = ", bufsize);
227 		if (tLXOptions->tGameInfo.sMapName.size())
228 			strncat(buffer, tLXOptions->tGameInfo.sMapName.c_str(), bufsize);
229 		strncat(buffer, "\n", bufsize);
230 
231 		// Mod dir
232 		strncat(buffer, "sModDir = ", bufsize);
233 		if (tLXOptions->tGameInfo.sModDir.size())
234 			strncat(buffer, tLXOptions->tGameInfo.sModDir.c_str(), bufsize);
235 		strncat(buffer, "\n", bufsize);
236 
237 		// Loading time
238 		itoa(tLXOptions->tGameInfo.iLoadingTime, tmp, 10);
239 		fix_markend(tmp);
240 		strncat(buffer, "iLoadingTimes = ", bufsize);
241 		strncat(buffer, tmp, bufsize);
242 		strncat(buffer, "\n", bufsize);
243 
244 		// Server name
245 		strncat(buffer, "sServerName = ", bufsize);
246 		if (tLXOptions->sServerName.size())
247 			strncat(buffer, tLXOptions->sServerName.c_str(), bufsize);
248 		strncat(buffer, "\n", bufsize);
249 
250 		// Welcome message
251 		strncat(buffer, "sWelcomeMessage = ", bufsize);
252 		if (tLXOptions->sWelcomeMessage.size())
253 			strncat(buffer, tLXOptions->sWelcomeMessage.c_str(), bufsize);
254 		strncat(buffer, "\n", bufsize);
255 
256 		// Lives
257 		itoa(tLXOptions->tGameInfo.iLives, tmp, 10);
258 		fix_markend(tmp);
259 		strncat(buffer, "iLives = ", bufsize);
260 		strncat(buffer, tmp, bufsize);
261 		strncat(buffer, "\n", bufsize);
262 
263 		// Max kills
264 		itoa(tLXOptions->tGameInfo.iKillLimit, tmp, 10);
265 		fix_markend(tmp);
266 		strncat(buffer, "iKillLimit = ", bufsize);
267 		strncat(buffer, tmp, bufsize);
268 		strncat(buffer, "\n", bufsize);
269 
270 		// Time limit
271 		itoa((int)(tLXOptions->tGameInfo.fTimeLimit * 10), tmp, 10);
272 		fix_markend(tmp);
273 		strncat(buffer, "fTimeLimit = ", bufsize);
274 		strncat(buffer, tmp, bufsize);
275 		strncat(buffer, "\n", bufsize);
276 
277 		// Bonuses on
278 		strncat(buffer, "bBonusesOn = ", bufsize);
279 		strncat(buffer, tLXOptions->tGameInfo.bBonusesOn ? "true" : "false", bufsize);
280 		strncat(buffer, "\n", bufsize);
281 
282 		// Bonus names
283 		strncat(buffer, "bShowBonusName = ", bufsize);
284 		strncat(buffer, tLXOptions->tGameInfo.bShowBonusName ? "true" : "false", bufsize);
285 		strncat(buffer, "\n", bufsize);
286 
287 		// Number of players
288 		if (cServer)  {
289 			itoa(cServer->getNumPlayers(), tmp, 10);
290 			fix_markend(tmp);
291 			strncat(buffer, "iNumPlayers = ", bufsize);
292 			strncat(buffer, tmp, bufsize);
293 			strncat(buffer, "\n", bufsize);
294 		}
295 
296 		buffer[bufsize - 1] = '\0';
297 #if !defined(__MINGW32__)
298 	} __except (EXCEPTION_EXECUTE_HANDLER) {
299 		return buffer;
300 	}
301 #endif
302 	return buffer;
303 }
304 
305 // This function also used in CrashHandler.cpp
OlxWriteCoreDump_Win32(const char * fileName,PEXCEPTION_POINTERS pExInfo)306 void OlxWriteCoreDump_Win32(const char* fileName, PEXCEPTION_POINTERS pExInfo )
307 {
308 #if !defined(__MINGW32__) // Does not load on Windows machine, but loads fine on Wine
309 	// MSVC-compatible core dump, GDB cannot read it :(
310 	// Set the exception info for the minidump
311 	MINIDUMP_EXCEPTION_INFORMATION eInfo;
312 	eInfo.ThreadId = GetCurrentThreadId();
313 	eInfo.ExceptionPointers = pExInfo;
314 	eInfo.ClientPointers = FALSE;
315 
316 	// Set the minidump info
317 	MINIDUMP_CALLBACK_INFORMATION cbMiniDump;
318 	cbMiniDump.CallbackRoutine = NULL;
319 	cbMiniDump.CallbackParam = 0;
320 
321 	// Additional data
322 	MINIDUMP_USER_STREAM pExtraInfo[3];
323 
324 	// Version info
325 	char version[64];
326 	strcpy(version, GetFullGameName());
327 	pExtraInfo[0].Type = LastReservedStream + 1;
328 	pExtraInfo[0].BufferSize = sizeof(version);
329 	pExtraInfo[0].Buffer = (void *)&version[0];
330 
331 	// Current game info
332 	char game_info[1024];
333 	pExtraInfo[1].Type = LastReservedStream + 2;
334 	pExtraInfo[1].BufferSize = sizeof(game_info);
335 	pExtraInfo[1].Buffer = ReadGameInfoForReport(game_info, sizeof(game_info));
336 
337 	// Current game state
338 	char game_state[1024];
339 	pExtraInfo[2].Type = LastReservedStream + 3;
340 	pExtraInfo[2].BufferSize = sizeof(game_state);
341 	pExtraInfo[2].Buffer = ReadGameStateForReport(game_state, sizeof(game_state));
342 
343 	MINIDUMP_USER_STREAM_INFORMATION iStreams;
344 	iStreams.UserStreamCount = sizeof(pExtraInfo)/sizeof(MINIDUMP_USER_STREAM);
345 	iStreams.UserStreamArray = pExtraInfo;
346 
347 	// Open the file
348 	HANDLE hFile = CreateFile((LPCSTR)fileName,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
349 
350 	// Write the minidump
351 	if (hFile)  {
352 		MINIDUMP_TYPE type = (MINIDUMP_TYPE)(MiniDumpScanMemory | MiniDumpWithIndirectlyReferencedMemory);
353 		MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, type, &eInfo,&iStreams,&cbMiniDump);
354 	}
355 
356 	// Close the file
357 	CloseHandle(hFile);
358 #endif // !defined(__MINGW32__)
359 }
360 
OlxWriteCoreDump(const char * fileName)361 void OlxWriteCoreDump(const char* fileName)
362 {
363 	OlxWriteCoreDump_Win32(fileName, NULL);
364 }
365 
366 #else
367 
368 #ifdef GCOREDUMPER
369 #include <google/coredumper.h>
370 #endif
371 #include <unistd.h>
372 #include <sys/types.h>
373 #include <sys/wait.h>
374 #include <cstring>
375 #include <cstdio>
376 
377 #ifndef GCOREDUMPER
GdbWriteCoreDump(const char * fname)378 static void GdbWriteCoreDump(const char* fname) {
379 	// WARNING: this is terribly slow like this
380 	char gdbparam[PATH_MAX + 200];
381 	sprintf(gdbparam,
382 			"attach %i \n"
383 			"gcore %s \n"
384 			"detach \n"
385 			"quit \n",
386 			getpid(), fname);
387 	FILE* p = popen("gdb -q", "w");
388 	if(p) {
389 		fprintf(p, "%s", gdbparam);
390 		fflush(p);
391 		int status = 0; wait(&status);
392 		pclose(p);
393 	}
394 }
395 #endif
396 
OlxWriteCoreDump(const char * file_postfix)397 void OlxWriteCoreDump(const char* file_postfix) {
398 	char corefile[PATH_MAX + 100];
399 	if(getcwd(corefile, PATH_MAX) == NULL) strcpy(corefile, "");
400 	strcat(corefile, "/core.OpenLieroX");
401 	if(file_postfix) { strcat(corefile, "."); strcat(corefile, file_postfix); }
402 	printf("writing coredump to %s\n", corefile);
403 
404 	printf("dumping core ... "); fflush(0);
405 #ifdef GCOREDUMPER
406 	WriteCoreDump(corefile);
407 #else
408 	GdbWriteCoreDump(corefile);
409 #endif
410 	printf("ready\n");
411 }
412 
413 #endif
414 
415 // ----------------- core dump code end -------------
416 
417 
418 // --------------- backtrace stuff start --------------
419 
420 #ifndef HAVE_EXECINFO
421 #	if defined(__linux__)
422 #		define HAVE_EXECINFO 1
423 #	elif defined(__DARWIN_VERS_1050)
424 #		define HAVE_EXECINFO 1
425 #	else
426 #		define HAVE_EXECINFO 0
427 #	endif
428 #endif
429 
430 #if HAVE_EXECINFO
431 
432 #include <execinfo.h>
433 #include <stdio.h>
434 #include <stdlib.h>
435 
DumpCallstackPrintf(void * callpnt)436 void DumpCallstackPrintf(void* callpnt) {
437 	void *callstack[128];
438 	int framesC = backtrace(callstack, sizeof(callstack));
439 	printf("backtrace() returned %d addresses\n", framesC);
440 	if(callpnt != NULL && framesC > 3) callstack[3] = callpnt; // expected to be called from signal handler
441 	char** strs = backtrace_symbols(callstack, framesC);
442 	for(int i = 0; i < framesC; ++i) {
443 		if(strs[i])
444 			printf("%s\n", strs[i]);
445 		else
446 			break;
447 	}
448 	free(strs);
449 }
450 
DumpCallstack(const PrintOutFct & printer)451 void DumpCallstack(const PrintOutFct& printer) {
452 	void *callstack[128];
453 	int framesC = backtrace(callstack, sizeof(callstack));
454 	printer.print("DumpCallstack: " + itoa(framesC) + " addresses:");
455 	char** strs = backtrace_symbols(callstack, framesC);
456 	for(int i = 0; i < framesC; ++i) {
457 		if(strs[i])
458 			printer.print(std::string(" ") + strs[i] + "\n");
459 		else
460 			break;
461 	}
462 	free(strs);
463 }
464 
465 #elif defined(WIN32)
466 
467 #include "StackWalker.h"  // Call Luke Stackwalker for help
468 
469 typedef void (*DbgPrintOutFct) (const std::string&);
470 
471 // Override the default stackwalker with our own print functions
472 class PrintStackWalker : public StackWalker  {
473 private:
474 	DbgPrintOutFct m_print;
475 	PrintOutFct *m_print2;
476 
477 public:
PrintStackWalker(DbgPrintOutFct fct=NULL)478 	PrintStackWalker(DbgPrintOutFct fct = NULL) : StackWalker(RetrieveVerbose) { m_print = fct; m_print2 = NULL; }
PrintStackWalker(PrintOutFct * fct)479 	PrintStackWalker(PrintOutFct *fct) : StackWalker(RetrieveVerbose) { m_print2 = fct; m_print = NULL; }
OnLoadModule(LPCSTR img,LPCSTR mod,DWORD64 baseAddr,DWORD size,DWORD result,LPCSTR symType,LPCSTR pdbName,ULONGLONG fileVersion)480 	void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion)
481 	{
482 
483 	}
484 
OnOutput(LPCSTR szText)485 	void OnOutput(LPCSTR szText)
486 	{
487 		if (m_print)
488 			m_print(std::string(szText));
489 		else if (m_print2)
490 			m_print2->print(std::string(szText));
491 		else
492 			printf(szText);
493 
494 		StackWalker::OnOutput(szText);
495 	}
496 };
497 
DumpCallstackPrintf(void * callpnt)498 void DumpCallstackPrintf(void* callpnt)
499 {
500 	PrintStackWalker sw;
501 	sw.ShowCallstack();
502 
503 }
DumpCallstack(void (* LineOutFct)(const std::string &))504 void DumpCallstack(void (*LineOutFct) (const std::string&))
505 {
506 	PrintStackWalker sw(LineOutFct);
507 	sw.ShowCallstack();
508 }
509 
DumpCallstack(const PrintOutFct & printer)510 void DumpCallstack(const PrintOutFct& printer) {
511 	PrintStackWalker sw(const_cast<PrintOutFct *>(&printer));
512 	sw.ShowCallstack();
513 }
514 
515 #else
516 
517 #warning No DumpCallstack implementation for this arch/sys
518 
DumpCallstackPrintf(void * callpnt)519 void DumpCallstackPrintf(void* callpnt) {
520 	printf("DumpCallstackPrintf not implemented\n");
521 }
522 
DumpCallstack(const PrintOutFct & printer)523 void DumpCallstack(const PrintOutFct& printer) {
524 	printf("DumpCallstack not implemented\n");
525 }
526 
527 #endif
528 
529 #ifdef __ANDROID__
530 #include <android/log.h>
531 #endif
532 
GetLogTimeStamp()533 std::string GetLogTimeStamp()
534 {
535 	// TODO: please recode this, don't use C-strings!
536 	char buf[64];
537 	const time_t unif_time = time(NULL);
538 	struct tm *t = localtime(&unif_time);
539 	if (t == NULL)
540 		return "";
541 
542 	strftime(buf, sizeof(buf), "[%H:%M:%S] ", t);
543 	fix_markend(buf);
544 	return std::string(buf);
545 }
546 
547 Logger notes(0,2,1000, "n: ");
548 Logger hints(0,1,100, "H: ");
549 Logger warnings(0,0,10, "W: ");
550 Logger errors(-1,-1,1, "E: ");
551 
552 #include <iostream>
553 #include <sstream>
554 #include "ThreadPool.h"
555 #include "Options.h"
556 #include "console.h"
557 #include "StringUtils.h"
558 
559 static SDL_mutex* globalCoutMutex = NULL;
560 
Logger(int o,int ingame,int callst,const std::string & p)561 Logger::Logger(int o, int ingame, int callst, const std::string& p)
562 : minCoutVerb(o), minIngameConVerb(ingame), minCallstackVerb(callst), prefix(p), lastWasNewline(true), mutex(NULL) {
563 	mutex = SDL_CreateMutex();
564 	if(!globalCoutMutex)
565 		globalCoutMutex = SDL_CreateMutex();
566 }
567 
~Logger()568 Logger::~Logger() {
569 	SDL_DestroyMutex(mutex); mutex = NULL;
570 	if(globalCoutMutex) {
571 		SDL_DestroyMutex(globalCoutMutex);
572 		globalCoutMutex = NULL;
573 	}
574 }
575 
lock()576 void Logger::lock() {
577 	SDL_mutexP(mutex);
578 }
579 
unlock()580 void Logger::unlock() {
581 	SDL_mutexV(mutex);
582 }
583 
584 struct CoutPrint : PrintOutFct {
printCoutPrint585 	void print(const std::string& str) const {
586 		// TODO: We have used std::cout here before but it doesn't seem to work after a while for some reason.
587 		printf("%s", str.c_str());
588 #ifdef __ANDROID__
589 		__android_log_print(ANDROID_LOG_INFO, "OpenLieroX", "%s", str.c_str());
590 #endif
591 	}
592 };
593 
594 template<int col>
595 struct ConPrint : PrintOutFct {
printConPrint596 	void print(const std::string& str) const {
597 		// TODO: Con_AddText adds a line but we only want to add str
598 		std::string buf = str;
599 		if(buf.size() > 0 && buf[buf.size()-1] == '\n') buf.erase(buf.size()-1);
600 		Con_AddText(col, buf, false);
601 	}
602 };
603 
604 // true if last was newline
logger_output(Logger & log,const std::string & buf)605 static bool logger_output(Logger& log, const std::string& buf) {
606 	bool ret = true;
607 
608 	std::string prefix = log.prefix;
609 	if (tLXOptions && tLXOptions->bLogTimestamps)
610 		prefix = GetLogTimeStamp() + prefix;
611 
612 	if(!tLXOptions || tLXOptions->iVerbosity >= log.minCoutVerb) {
613 		SDL_mutexP(globalCoutMutex);
614 		ret = PrettyPrint(prefix, buf, CoutPrint(), log.lastWasNewline);
615 		//std::cout.flush();
616 		SDL_mutexV(globalCoutMutex);
617 	}
618 	if(tLXOptions && tLXOptions->iVerbosity >= log.minCallstackVerb) {
619 		DumpCallstackPrintf();
620 	}
621 	if(tLXOptions && Con_IsInited() && tLXOptions->iVerbosity >= log.minIngameConVerb) {
622 		// the check is a bit hacky (see Con_AddText) but I really dont want to overcomplicate this
623 		if(!strStartsWith(buf, "Ingame console: ")) {
624 			// we are not safing explicitly a color in the Logger, thus we try to assume a good color from the verbosity level
625 			if(log.minIngameConVerb < 0)
626 				ret = PrettyPrint(prefix, buf, ConPrint<CNC_ERROR>(), log.lastWasNewline);
627 			else if(log.minIngameConVerb == 0)
628 				ret = PrettyPrint(prefix, buf, ConPrint<CNC_WARNING>(), log.lastWasNewline);
629 			else if(log.minIngameConVerb == 1)
630 				ret = PrettyPrint(prefix, buf, ConPrint<CNC_NOTIFY>(), log.lastWasNewline);
631 			else if(log.minIngameConVerb < 5)
632 				ret = PrettyPrint(prefix, buf, ConPrint<CNC_NORMAL>(), log.lastWasNewline);
633 			else // >=5
634 				ret = PrettyPrint(prefix, buf, ConPrint<CNC_DEV>(), log.lastWasNewline);
635 		}
636 		if(tLXOptions->iVerbosity >= log.minCallstackVerb) {
637 			DumpCallstack(ConPrint<CNC_DEV>());
638 		}
639 	}
640 	return ret;
641 }
642 
flush()643 Logger& Logger::flush() {
644 	lock();
645 	lastWasNewline = logger_output(*this, buffer);
646 	buffer = "";
647 	unlock();
648 	return *this;
649 }
650