]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/watchbsp.cpp
error handling in watchbsp (contributed by ilm)
[xonotic/netradiant.git] / radiant / watchbsp.cpp
1 /*
2 Copyright (c) 2001, Loki software, inc.
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without modification, 
6 are permitted provided that the following conditions are met:
7
8 Redistributions of source code must retain the above copyright notice, this list 
9 of conditions and the following disclaimer.
10
11 Redistributions in binary form must reproduce the above copyright notice, this
12 list of conditions and the following disclaimer in the documentation and/or
13 other materials provided with the distribution.
14
15 Neither the name of Loki software nor the names of its contributors may be used 
16 to endorse or promote products derived from this software without specific prior 
17 written permission. 
18
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' 
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
22 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY 
23 DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
24 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
25 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
26 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
27 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
28 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
29 */
30
31 //-----------------------------------------------------------------------------
32 //
33 // DESCRIPTION:
34 // monitoring window for running BSP processes (and possibly various other stuff)
35
36 #include "stdafx.h"
37 #include "watchbsp.h"
38 #include "feedback.h"
39
40 #ifdef _WIN32
41 #include <winsock2.h>
42 #endif
43
44 #if defined (__linux__) || defined (__APPLE__)
45 #include <sys/time.h>
46 #define SOCKET_ERROR -1
47 #endif
48
49 #ifdef __APPLE__
50 #include <unistd.h>
51 #endif
52
53 #include <assert.h>
54
55 // Static functions for the SAX callbacks -------------------------------------------------------
56
57 // utility for saxStartElement below
58 static void abortStream(message_info_t *data)
59 {
60   g_pParentWnd->GetWatchBSP()->Reset();
61   // tell there has been an error
62   if (g_pParentWnd->GetWatchBSP()->HasBSPPlugin ())
63     g_BSPFrontendTable.m_pfnEndListen(2);
64   // yeah this doesn't look good.. but it's needed so that everything will be ignored until the stream goes out
65   data->ignore_depth = -1;
66   data->recurse++;
67 }
68
69 #include "stream_version.h"
70
71 static void saxStartElement(message_info_t *data, const xmlChar *name, const xmlChar **attrs) 
72 {
73   if (data->ignore_depth == 0)
74   {
75     if (data->bGeometry)
76       // we have a handler
77     {
78       data->pGeometry->saxStartElement (data, name, attrs);
79     }
80     else
81     {
82       if (strcmp ((char *)name, "q3map_feedback") == 0)
83       {
84         // check the correct version
85         // old q3map don't send a version attribute
86         // the ones we support .. send Q3MAP_STREAM_VERSION
87         if (!attrs[0] || !attrs[1] || (strcmp((char*)attrs[0],"version") != 0))
88         {
89           Sys_FPrintf(SYS_ERR, "No stream version given in the feedback stream, this is an old q3map version.\n"
90                       "Please turn off monitored compiling if you still wish to use this q3map executable\n");
91           abortStream(data);
92           return;
93         }
94         else if (strcmp((char*)attrs[1],Q3MAP_STREAM_VERSION) != 0)
95         {
96           Sys_FPrintf(SYS_ERR, 
97             "This version of Radiant reads version %s debug streams, I got an incoming connection with version %s\n"
98             "Please make sure your versions of Radiant and q3map are matching.\n", Q3MAP_STREAM_VERSION, (char*)attrs[1]);
99           abortStream(data);
100           return;
101         }
102       }
103       // we don't treat locally
104       else if (strcmp ((char *)name, "message") == 0)
105       {
106         data->msg_level = atoi ((char *)attrs[1]);
107       }
108       else if (strcmp ((char *)name, "polyline") == 0) 
109       // polyline has a particular status .. right now we only use it for leakfile ..
110       {
111         data->bGeometry = true;
112         data->pGeometry = &g_pointfile;
113         data->pGeometry->saxStartElement( data, name, attrs );
114       }
115       else if (strcmp ((char *)name, "select") == 0)
116       {
117         CSelectMsg *pSelect = new CSelectMsg();
118         data->bGeometry = true;
119         data->pGeometry = pSelect;
120         data->pGeometry->saxStartElement( data, name, attrs );
121       }
122       else if (strcmp ((char *)name, "pointmsg") == 0)
123       {
124         CPointMsg *pPoint = new CPointMsg();
125         data->bGeometry = true;
126         data->pGeometry = pPoint;
127         data->pGeometry->saxStartElement( data, name, attrs );
128       }
129       else if (strcmp ((char *)name, "windingmsg") == 0)
130       {
131         CWindingMsg *pWinding = new CWindingMsg();
132         data->bGeometry = true;
133         data->pGeometry = pWinding;
134         data->pGeometry->saxStartElement( data, name, attrs );
135       }
136       else
137       {
138         Sys_FPrintf (SYS_WRN, "WARNING: ignoring unrecognized node in XML stream (%s)\n", name);
139         // we don't recognize this node, jump over it
140         // (NOTE: the ignore mechanism is a bit screwed, only works when starting an ignore at the highest level)
141         data->ignore_depth = data->recurse;
142       }
143     }
144   }
145   data->recurse++;
146 }
147
148 static void saxEndElement(message_info_t *data, const xmlChar *name)  {
149         data->recurse--;
150         // we are out of an ignored chunk
151         if ( data->recurse == data->ignore_depth ) {
152                 data->ignore_depth = 0;
153                 return;
154         }
155         if ( data->bGeometry ) {
156                 data->pGeometry->saxEndElement( data, name );
157                 // we add the object to the debug window
158                 if ( !data->bGeometry ) {
159                         g_DbgDlg.Push( data->pGeometry );
160                 }
161         }
162         if ( data->recurse == data->stop_depth ) {
163 #ifdef _DEBUG
164                 Sys_Printf ("Received error msg .. shutting down..\n");
165 #endif
166                 g_pParentWnd->GetWatchBSP()->Reset();
167                 // tell there has been an error
168                 if ( g_pParentWnd->GetWatchBSP()->HasBSPPlugin() ) {
169                         g_BSPFrontendTable.m_pfnEndListen( 2 );
170                 }
171                 return;
172         }
173 }
174
175 static void saxCharacters(message_info_t *data, const xmlChar *ch, int len)
176 {
177   if (data->bGeometry)
178   {
179     data->pGeometry->saxCharacters (data, ch, len);
180   }
181   else
182   {
183     if (data->ignore_depth != 0)
184       return;
185     // output the message using the level
186     char buf[1024];
187     memcpy( buf, ch, len );
188     buf[len] = '\0';
189     Sys_FPrintf (data->msg_level, "%s", buf);
190     // if this message has error level flag, we mark the depth to stop the compilation when we get out
191     // we don't set the msg level if we don't stop on leak
192     if (data->msg_level == 3)
193     {
194       data->stop_depth = data->recurse-1;
195     }
196   }
197 }
198
199 static void saxComment(void *ctx, const xmlChar *msg)
200 {
201   Sys_Printf("XML comment: %s\n", msg);
202 }
203
204 static void saxWarning(void *ctx, const char *msg, ...)
205 {
206   char saxMsgBuffer[4096];
207   va_list args;
208  
209   va_start(args, msg);
210   vsprintf (saxMsgBuffer, msg, args);
211   va_end(args);
212   Sys_FPrintf(SYS_WRN, "XML warning: %s\n", saxMsgBuffer);
213 }
214
215 static void saxError(void *ctx, const char *msg, ...)
216 {
217   char saxMsgBuffer[4096];
218   va_list args;
219  
220   va_start(args, msg);
221   vsprintf (saxMsgBuffer, msg, args);
222   va_end(args);
223   Sys_FPrintf(SYS_ERR, "XML error: %s\n", saxMsgBuffer);
224 }
225
226 static void saxFatal(void *ctx, const char *msg, ...)
227 {
228   char buffer[4096];
229   
230   va_list args;
231  
232   va_start(args, msg);
233   vsprintf (buffer, msg, args);
234   va_end(args);
235   Sys_FPrintf(SYS_ERR, "XML fatal error: %s\n", buffer);
236 }
237
238 static xmlSAXHandler saxParser = {
239     0, /* internalSubset */
240     0, /* isStandalone */
241     0, /* hasInternalSubset */
242     0, /* hasExternalSubset */
243     0, /* resolveEntity */
244     0, /* getEntity */
245     0, /* entityDecl */
246     0, /* notationDecl */
247     0, /* attributeDecl */
248     0, /* elementDecl */
249     0, /* unparsedEntityDecl */
250     0, /* setDocumentLocator */
251     0, /* startDocument */
252     0, /* endDocument */
253     (startElementSAXFunc)saxStartElement, /* startElement */
254     (endElementSAXFunc)saxEndElement, /* endElement */
255     0, /* reference */
256     (charactersSAXFunc)saxCharacters, /* characters */
257     0, /* ignorableWhitespace */
258     0, /* processingInstruction */
259     (commentSAXFunc)saxComment, /* comment */
260     (warningSAXFunc)saxWarning, /* warning */
261     (errorSAXFunc)saxError, /* error */
262     (fatalErrorSAXFunc)saxFatal, /* fatalError */
263 };
264
265 // ------------------------------------------------------------------------------------------------
266
267 CWatchBSP::~CWatchBSP()
268 {
269   Reset();
270   if (m_sBSPName)
271   {
272     delete[] m_sBSPName;
273     m_sBSPName = NULL;
274   }
275   Net_Shutdown();
276 }
277
278 void CWatchBSP::Reset()
279 {
280   if (m_pInSocket)
281   {
282     Net_Disconnect(m_pInSocket);
283     m_pInSocket = NULL;
284   }
285   if (m_pListenSocket)
286   {
287     Net_Disconnect(m_pListenSocket);
288     m_pListenSocket = NULL;
289   }
290   if (m_xmlInputBuffer)
291   {
292     xmlFreeParserInputBuffer (m_xmlInputBuffer);
293     m_xmlInputBuffer = NULL;
294   }
295   m_eState = EIdle;
296 }
297
298 bool CWatchBSP::SetupListening()
299 {
300 #ifdef _DEBUG
301   if (m_pListenSocket)
302   {
303     Sys_Printf("ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n");
304     return false;
305   }
306 #endif
307   Sys_Printf("Setting up\n");
308   if( !Net_Setup() )
309     return false;
310
311   m_pListenSocket = Net_ListenSocket(39000);
312   if (m_pListenSocket == NULL)
313     return false;
314
315   Sys_Printf("Listening...\n");
316   return true;
317 }
318
319 void CWatchBSP::DoEBeginStep() {
320         Reset();
321         if ( !SetupListening() ) {
322                 CString msg;
323                 msg = "Failed to get a listening socket on port 39000.\nTry running with BSP monitoring disabled if you can't fix this.\n";
324                 Sys_Printf( msg );
325                 gtk_MessageBox( g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR );
326                 return;
327         }
328         // set the timer for timeouts and step cancellation
329         g_timer_reset( m_pTimer );
330         g_timer_start( m_pTimer );
331
332         if ( !m_bBSPPlugin ) {
333                 Sys_Printf( "=== running BSP command ===\n%s\n", g_ptr_array_index( m_pCmd, m_iCurrentStep ) );
334
335                 if ( !Q_Exec( NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true ) ) {
336                         CString msg;
337                         msg = "Failed to execute the following command: ";
338                         msg += (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
339                         msg += "\nCheck that the file exists and that you don't run out of system resources.\n";
340                         Sys_Printf( msg );
341                         gtk_MessageBox( g_pParentWnd->m_pWidget,  msg, "BSP monitoring", MB_OK | MB_ICONERROR );
342                         return;
343                 }
344                 // re-initialise the debug window
345                 if ( m_iCurrentStep == 0 ) {
346                         g_DbgDlg.Init();
347                 }
348         }
349         m_eState = EBeginStep;
350 }
351
352 void CWatchBSP::RoutineProcessing()
353 {
354   // used for select()
355 #ifdef _WIN32
356     TIMEVAL tout = { 0, 0 };
357 #endif
358 #if defined (__linux__) || defined (__APPLE__)
359                 timeval tout;
360                 tout.tv_sec = 0;
361                 tout.tv_usec = 0;
362 #endif
363
364   switch (m_eState)
365   {
366   case EBeginStep:
367     // timeout: if we don't get an incoming connection fast enough, go back to idle
368     if ( g_timer_elapsed( m_pTimer, NULL ) > g_PrefsDlg.m_iTimeout )
369     {
370       gtk_MessageBox(g_pParentWnd->m_pWidget,  "The connection timed out, assuming the BSP process failed\nMake sure you are using a networked version of Q3Map?\nOtherwise you need to disable BSP Monitoring in prefs.", "BSP process monitoring", MB_OK );
371       Reset();
372       if (m_bBSPPlugin)
373       {
374         // status == 1 : didn't get the connection
375         g_BSPFrontendTable.m_pfnEndListen(1);
376       }
377       return;
378     }
379 #ifdef _DEBUG
380     // some debug checks
381     if (!m_pListenSocket)
382     {
383       Sys_Printf("ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n");
384       return;
385     }
386 #endif
387     // we are not connected yet, accept any incoming connection
388     m_pInSocket = Net_Accept(m_pListenSocket);
389     if (m_pInSocket)
390     {
391       Sys_Printf("Connected.\n");
392       // prepare the message info struct for diving in
393       memset (&m_message_info, 0, sizeof(message_info_s)); 
394       // a dumb flag to make sure we init the push parser context when first getting a msg
395       m_bNeedCtxtInit = true;
396       m_eState = EWatching;
397     }
398     break;
399   case EWatching:
400 #ifdef _DEBUG
401     // some debug checks
402     if (!m_pInSocket)
403     {
404       Sys_Printf("ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n");
405       return;
406     }
407 #endif
408     // select() will identify if the socket needs an update
409     // if the socket is identified that means there's either a message or the connection has been closed/reset/terminated
410     fd_set readfds;
411     int ret;
412     FD_ZERO(&readfds);
413     FD_SET(((unsigned int)m_pInSocket->socket), &readfds);
414                 // from select man page:
415                 // n is the highest-numbered descriptor in any of the three sets, plus 1
416                 // (no use on windows)
417     ret = select( m_pInSocket->socket + 1, &readfds, NULL, NULL, &tout );
418     if (ret == SOCKET_ERROR)
419     {
420       Sys_Printf("WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n");
421       Sys_Printf("Terminating the connection.\n");
422       Reset();
423       return;
424     }
425 #ifdef _DEBUG
426     if (ret == -1)
427     {
428       // we are non-blocking?? we should never get timeout errors
429       Sys_Printf("WARNING: unexpected timeout expired in CWatchBSP::Processing\n");
430       Sys_Printf("Terminating the connection.\n");
431       Reset();
432       return;
433     }
434 #endif
435     if (ret == 1)
436     {
437       // the socket has been identified, there's something (message or disconnection)
438       // see if there's anything in input
439       ret = Net_Receive( m_pInSocket, &msg );
440       if (ret > 0)
441       {
442         //        unsigned int size = msg.size; //++timo just a check
443         strcpy (m_xmlBuf, NMSG_ReadString (&msg));
444         if (m_bNeedCtxtInit)
445         {
446           m_xmlParserCtxt = NULL;
447           m_xmlParserCtxt = xmlCreatePushParserCtxt (&saxParser, &m_message_info, m_xmlBuf, strlen(m_xmlBuf), NULL);
448           if (m_xmlParserCtxt == NULL)
449           {
450             Sys_FPrintf (SYS_ERR, "Failed to create the XML parser (incoming stream began with: %s)\n", m_xmlBuf);
451             Reset();
452           }
453           m_bNeedCtxtInit = false;
454         }
455         else
456         {
457           xmlParseChunk (m_xmlParserCtxt, m_xmlBuf, strlen(m_xmlBuf), 0);
458         }
459       }
460       else
461       {
462         // error or connection closed/reset
463         // NOTE: if we get an error down the XML stream we don't reach here
464         Net_Disconnect( m_pInSocket );
465         m_pInSocket = NULL;
466         Sys_Printf("Connection closed.\n");
467         if (m_bBSPPlugin)
468         {
469           Reset();
470           // let the BSP plugin know that the job is done
471           g_BSPFrontendTable.m_pfnEndListen(0);
472           return;
473         }
474         // move to next step or finish
475         m_iCurrentStep++;
476         if (m_iCurrentStep < m_pCmd->len )
477         {
478           DoEBeginStep();
479         }
480         else
481         {
482           // release the GPtrArray and the strings
483           if (m_pCmd != NULL)
484           {
485             for (m_iCurrentStep=0; m_iCurrentStep < m_pCmd->len; m_iCurrentStep++ )
486             {
487               delete[] (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
488             }
489             g_ptr_array_free( m_pCmd, false );
490           }
491           m_pCmd = NULL;
492           // launch the engine .. OMG
493           if (g_PrefsDlg.m_bRunQuake)
494           {
495             // do we enter sleep mode before?
496             if (g_PrefsDlg.m_bDoSleep)
497             {
498               Sys_Printf("Going into sleep mode..\n");
499               g_pParentWnd->OnSleep();
500             }
501             Sys_Printf("Running engine...\n");
502             Str cmd;
503             // build the command line
504             cmd = g_pGameDescription->mEnginePath.GetBuffer();
505             // this is game dependant
506             //!\todo Read the engine binary name from a config file.
507             if (g_pGameDescription->mGameFile == "wolf.game")
508             {
509               if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
510               {
511                 // MP
512 #if defined(WIN32)
513                 cmd += "WolfMP.exe";
514 #elif defined(__linux__)
515                 cmd += "wolfmp";
516 #elif defined(__APPLE__)
517                 cmd += "wolfmp.app";
518 #else
519 #error "WTF are you compiling on"
520 #endif
521               }
522               else
523               {
524                 // SP
525 #if defined(WIN32)
526                 cmd += "WolfSP.exe";
527 #elif defined(__linux__)
528                 cmd += "wolfsp";
529 #elif defined(__APPLE__)
530                 cmd += "wolfsp.app";
531 #else
532 #error "WTF are you compiling on"
533 #endif
534               }
535             } else if (g_pGameDescription->mGameFile == "et.game")
536             {
537 #if defined(WIN32)
538               cmd += "et.exe";
539 #elif defined(__linux__)
540               cmd += "et";
541 #elif defined(__APPLE__)
542               cmd += "et.app";
543 #else
544 #error "WTF are you compiling on"
545 #endif
546             }
547             // RIANT
548             // JK2 HACK
549             else if (g_pGameDescription->mGameFile == "jk2.game")
550             {
551               if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
552               {
553                 // MP
554 #if defined(WIN32)
555                 cmd += "jk2MP.exe";
556 #elif defined(__linux__)
557                 cmd += "jk2mp";
558 #elif defined(__APPLE__)
559                 cmd += "jk2mp.app";
560 #else
561 #error "WTF are you compiling on"
562 #endif
563               }
564               else
565               {
566                 // SP
567 #if defined(WIN32)
568                 cmd += "jk2SP.exe";
569 #elif defined(__linux__)
570                 cmd += "jk2sp";
571 #elif defined(__APPLE__)
572                 cmd += "jk2sp.app";
573 #else
574 #error "WTF are you compiling on"
575 #endif
576               }
577             }
578             // TTimo
579             // JA HACK
580             else if (g_pGameDescription->mGameFile == "ja.game")
581             {
582               if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
583               {
584                 // MP
585 #if defined(WIN32)
586                 cmd += "jamp.exe";
587 #elif !defined(__linux__) && !defined(__APPLE__)
588 #error "WTF are you compiling on"
589 #endif
590               }
591               else
592               {
593                 // SP
594 #if defined(WIN32)
595                 cmd += "jasp.exe";
596 #elif !defined(__linux__) && !defined(__APPLE__)
597 #error "WTF are you compiling on"
598 #endif
599               }
600             }
601             // RIANT
602             // STVEF HACK
603             else if (g_pGameDescription->mGameFile == "stvef.game")
604             {
605               if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
606               {
607                 // MP
608 #if defined(WIN32)
609                 cmd += "stvoyHM.exe";
610 #elif defined(__linux__)
611                 cmd += "stvoyHM";
612 #elif defined(__APPLE__)
613                 cmd += "stvoyHM.app";
614 #else
615 #error "WTF are you compiling on"
616 #endif
617               }
618               else
619               {
620                 // SP
621 #if defined(WIN32)
622                 cmd += "stvoy.exe";
623 #elif defined(__linux__)
624                 cmd += "stvoy";
625 #elif defined(__APPLE__)
626                 cmd += "stvoy.app";
627 #else
628 #error "WTF are you compiling on"
629 #endif
630               }
631             }
632             // RIANT
633             // SOF2 HACK
634             else if (g_pGameDescription->mGameFile == "sof2.game")
635             {
636               if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
637               {
638                 // MP
639 #if defined(WIN32)
640                 cmd += "sof2MP.exe";
641 #elif defined(__linux__)
642                 cmd += "b00gus";
643 #elif defined(__APPLE__)
644                 cmd += "sof2MP.app";
645 #else
646 #error "WTF are you compiling on"
647 #endif
648               }
649               else
650               {
651                 // SP
652 #if defined(WIN32)
653                 cmd += "sof2.exe";
654 #elif defined(__linux__)
655                 cmd += "b00gus";
656 #elif defined(__APPLE__)
657                 cmd += "sof2.app";
658 #else
659 #error "WTF are you compiling on"
660 #endif
661               }
662             }
663             else
664             {
665               cmd += g_pGameDescription->mEngine.GetBuffer();
666             }
667 #ifdef _WIN32
668             // NOTE: we are using unix pathnames and CreateProcess doesn't like / in the program path
669             FindReplace( cmd, "/", "\\" );
670 #endif
671             Str cmdline;
672             if ( (g_pGameDescription->mGameFile == "q2.game") || (g_pGameDescription->mGameFile == "heretic2.game") )
673             {
674                 cmdline = ". +exec radiant.cfg +map ";
675                 cmdline += m_sBSPName;
676             }
677             else
678             {
679               cmdline = "+set sv_pure 0 ";
680               // TTimo: a check for vm_* but that's all fine
681               //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 ";
682               if (*ValueForKey(g_qeglobals.d_project_entity, "gamename") != '\0')
683               {
684                 cmdline += "+set fs_game ";
685                 cmdline += ValueForKey(g_qeglobals.d_project_entity, "gamename");
686                 cmdline += " ";
687               }
688               //!\todo Read the start-map args from a config file.
689               if (g_pGameDescription->mGameFile == "wolf.game")
690               {
691                 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
692                 {
693                   // MP
694                   cmdline += "+devmap ";
695                   cmdline += m_sBSPName;
696                 }
697                 else
698                 {
699                   // SP                
700                   cmdline += "+set nextmap \"spdevmap ";
701                   cmdline += m_sBSPName;
702                   cmdline += "\"";
703                 }
704               }
705               else
706               {
707                 cmdline += "+devmap ";
708                 cmdline += m_sBSPName;
709               }
710             }
711
712             Sys_Printf("%s %s\n", cmd.GetBuffer(), cmdline.GetBuffer());
713
714             // execute now
715             if (!Q_Exec(cmd.GetBuffer(), (char *)cmdline.GetBuffer(), g_pGameDescription->mEnginePath.GetBuffer(), false))
716             {
717               CString msg;
718               msg = "Failed to execute the following command: ";
719               msg += cmd; msg += cmdline;
720               Sys_Printf(msg);
721               gtk_MessageBox(g_pParentWnd->m_pWidget,  msg, "BSP monitoring", MB_OK | MB_ICONERROR );
722             }
723           }
724           Reset();
725         }
726       }
727     }
728     break;
729   default:
730     break;
731   }
732 }
733
734 void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, char *sBSPName )
735 {
736   if (m_sBSPName)
737   {
738     delete[] m_sBSPName;
739   }
740   m_sBSPName = sBSPName;
741   if (m_eState != EIdle)
742   {
743     Sys_Printf("WatchBSP got a monitoring request while not idling...\n");
744     // prompt the user, should we cancel the current process and go ahead?
745     if (gtk_MessageBox(g_pParentWnd->m_pWidget,  "I am already monitoring a BSP process.\nDo you want me to override and start a new compilation?", 
746       "BSP process monitoring", MB_YESNO ) == IDYES)
747     {
748       // disconnect and set EIdle state
749       Reset();
750     }
751   }
752   m_pCmd = pCmd;
753   m_iCurrentStep = 0;
754   DoEBeginStep();
755 }
756
757 void CWatchBSP::ExternalListen()
758 {
759   m_bBSPPlugin = true;
760   DoEBeginStep ();
761 }
762
763 // the part of the watchbsp interface we export to plugins
764 // NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level
765 // for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final)
766 void WINAPI QERApp_Listen()
767 {
768   // open the listening socket
769   g_pParentWnd->GetWatchBSP()->ExternalListen();
770 }