]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/watchbsp.cpp
more eol-style
[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 {
150   data->recurse--;
151   // we are out of an ignored chunk
152   if (data->recurse == data->ignore_depth)
153   {
154     data->ignore_depth = 0;
155     return;
156   }
157   if (data->bGeometry)
158   {
159     data->pGeometry->saxEndElement (data, name);
160     // we add the object to the debug window
161     if (!data->bGeometry)
162     {
163       g_DbgDlg.Push (data->pGeometry);
164     }
165   }
166   if (data->recurse == data->stop_depth)
167   {
168 #ifdef _DEBUG
169     Sys_Printf ("Received error msg .. shutting down..\n");
170 #endif
171     g_pParentWnd->GetWatchBSP()->Reset();
172     // tell there has been an error
173     if (g_pParentWnd->GetWatchBSP()->HasBSPPlugin ())
174       g_BSPFrontendTable.m_pfnEndListen(2);
175     return;
176   }
177 }
178
179 static void saxCharacters(message_info_t *data, const xmlChar *ch, int len)
180 {
181   if (data->bGeometry)
182   {
183     data->pGeometry->saxCharacters (data, ch, len);
184   }
185   else
186   {
187     if (data->ignore_depth != 0)
188       return;
189     // output the message using the level
190     char buf[1024];
191     memcpy( buf, ch, len );
192     buf[len] = '\0';
193     Sys_FPrintf (data->msg_level, "%s", buf);
194     // if this message has error level flag, we mark the depth to stop the compilation when we get out
195     // we don't set the msg level if we don't stop on leak
196     if (data->msg_level == 3)
197     {
198       data->stop_depth = data->recurse-1;
199     }
200   }
201 }
202
203 static void saxComment(void *ctx, const xmlChar *msg)
204 {
205   Sys_Printf("XML comment: %s\n", msg);
206 }
207
208 static void saxWarning(void *ctx, const char *msg, ...)
209 {
210   char saxMsgBuffer[4096];
211   va_list args;
212  
213   va_start(args, msg);
214   vsprintf (saxMsgBuffer, msg, args);
215   va_end(args);
216   Sys_FPrintf(SYS_WRN, "XML warning: %s\n", saxMsgBuffer);
217 }
218
219 static void saxError(void *ctx, const char *msg, ...)
220 {
221   char saxMsgBuffer[4096];
222   va_list args;
223  
224   va_start(args, msg);
225   vsprintf (saxMsgBuffer, msg, args);
226   va_end(args);
227   Sys_FPrintf(SYS_ERR, "XML error: %s\n", saxMsgBuffer);
228 }
229
230 static void saxFatal(void *ctx, const char *msg, ...)
231 {
232   char buffer[4096];
233   
234   va_list args;
235  
236   va_start(args, msg);
237   vsprintf (buffer, msg, args);
238   va_end(args);
239   Sys_FPrintf(SYS_ERR, "XML fatal error: %s\n", buffer);
240 }
241
242 static xmlSAXHandler saxParser = {
243     0, /* internalSubset */
244     0, /* isStandalone */
245     0, /* hasInternalSubset */
246     0, /* hasExternalSubset */
247     0, /* resolveEntity */
248     0, /* getEntity */
249     0, /* entityDecl */
250     0, /* notationDecl */
251     0, /* attributeDecl */
252     0, /* elementDecl */
253     0, /* unparsedEntityDecl */
254     0, /* setDocumentLocator */
255     0, /* startDocument */
256     0, /* endDocument */
257     (startElementSAXFunc)saxStartElement, /* startElement */
258     (endElementSAXFunc)saxEndElement, /* endElement */
259     0, /* reference */
260     (charactersSAXFunc)saxCharacters, /* characters */
261     0, /* ignorableWhitespace */
262     0, /* processingInstruction */
263     (commentSAXFunc)saxComment, /* comment */
264     (warningSAXFunc)saxWarning, /* warning */
265     (errorSAXFunc)saxError, /* error */
266     (fatalErrorSAXFunc)saxFatal, /* fatalError */
267 };
268
269 // ------------------------------------------------------------------------------------------------
270
271 CWatchBSP::~CWatchBSP()
272 {
273   Reset();
274   if (m_sBSPName)
275   {
276     delete[] m_sBSPName;
277     m_sBSPName = NULL;
278   }
279   Net_Shutdown();
280 }
281
282 void CWatchBSP::Reset()
283 {
284   if (m_pInSocket)
285   {
286     Net_Disconnect(m_pInSocket);
287     m_pInSocket = NULL;
288   }
289   if (m_pListenSocket)
290   {
291     Net_Disconnect(m_pListenSocket);
292     m_pListenSocket = NULL;
293   }
294   if (m_xmlInputBuffer)
295   {
296     xmlFreeParserInputBuffer (m_xmlInputBuffer);
297     m_xmlInputBuffer = NULL;
298   }
299   m_eState = EIdle;
300 }
301
302 bool CWatchBSP::SetupListening()
303 {
304 #ifdef _DEBUG
305   if (m_pListenSocket)
306   {
307     Sys_Printf("ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n");
308     return false;
309   }
310 #endif
311   Sys_Printf("Setting up\n");
312         Net_Setup();
313         m_pListenSocket = Net_ListenSocket(39000);
314   if (m_pListenSocket == NULL)
315     return false;
316   Sys_Printf("Listening...\n");
317   return true;
318 }
319
320 void CWatchBSP::DoEBeginStep()
321 {
322   Reset();
323   if (SetupListening() == false)
324   {
325     CString msg;
326     msg = "Failed to get a listening socket on port 39000.\nTry running with BSP monitoring disabled if you can't fix this.\n";
327     Sys_Printf (msg);
328     gtk_MessageBox (g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR);
329     return;
330   }
331   // set the timer for timeouts and step cancellation
332   g_timer_reset( m_pTimer );
333   g_timer_start( m_pTimer );
334
335   if (!m_bBSPPlugin)
336   {
337     Sys_Printf("=== running BSP command ===\n%s\n", g_ptr_array_index( m_pCmd, m_iCurrentStep ) );
338     
339     if (!Q_Exec(NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true ))
340     {
341       CString msg;
342       msg = "Failed to execute the following command: ";
343       msg += (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
344       msg += "\nCheck that the file exists and that you don't run out of system resources.\n";
345       Sys_Printf(msg);
346       gtk_MessageBox(g_pParentWnd->m_pWidget,  msg, "BSP monitoring", MB_OK | MB_ICONERROR );
347       return;
348     }
349     // re-initialise the debug window
350     if (m_iCurrentStep == 0)
351       g_DbgDlg.Init();
352   }
353   m_eState = EBeginStep;
354 }
355
356 void CWatchBSP::RoutineProcessing()
357 {
358   // used for select()
359 #ifdef _WIN32
360     TIMEVAL tout = { 0, 0 };
361 #endif
362 #if defined (__linux__) || defined (__APPLE__)
363                 timeval tout;
364                 tout.tv_sec = 0;
365                 tout.tv_usec = 0;
366 #endif
367
368   switch (m_eState)
369   {
370   case EBeginStep:
371     // timeout: if we don't get an incoming connection fast enough, go back to idle
372     if ( g_timer_elapsed( m_pTimer, NULL ) > g_PrefsDlg.m_iTimeout )
373     {
374       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 );
375       Reset();
376       if (m_bBSPPlugin)
377       {
378         // status == 1 : didn't get the connection
379         g_BSPFrontendTable.m_pfnEndListen(1);
380       }
381       return;
382     }
383 #ifdef _DEBUG
384     // some debug checks
385     if (!m_pListenSocket)
386     {
387       Sys_Printf("ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n");
388       return;
389     }
390 #endif
391     // we are not connected yet, accept any incoming connection
392     m_pInSocket = Net_Accept(m_pListenSocket);
393     if (m_pInSocket)
394     {
395       Sys_Printf("Connected.\n");
396       // prepare the message info struct for diving in
397       memset (&m_message_info, 0, sizeof(message_info_s)); 
398       // a dumb flag to make sure we init the push parser context when first getting a msg
399       m_bNeedCtxtInit = true;
400       m_eState = EWatching;
401     }
402     break;
403   case EWatching:
404 #ifdef _DEBUG
405     // some debug checks
406     if (!m_pInSocket)
407     {
408       Sys_Printf("ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n");
409       return;
410     }
411 #endif
412     // select() will identify if the socket needs an update
413     // if the socket is identified that means there's either a message or the connection has been closed/reset/terminated
414     fd_set readfds;
415     int ret;
416     FD_ZERO(&readfds);
417     FD_SET(((unsigned int)m_pInSocket->socket), &readfds);
418                 // from select man page:
419                 // n is the highest-numbered descriptor in any of the three sets, plus 1
420                 // (no use on windows)
421     ret = select( m_pInSocket->socket + 1, &readfds, NULL, NULL, &tout );
422     if (ret == SOCKET_ERROR)
423     {
424       Sys_Printf("WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n");
425       Sys_Printf("Terminating the connection.\n");
426       Reset();
427       return;
428     }
429 #ifdef _DEBUG
430     if (ret == -1)
431     {
432       // we are non-blocking?? we should never get timeout errors
433       Sys_Printf("WARNING: unexpected timeout expired in CWatchBSP::Processing\n");
434       Sys_Printf("Terminating the connection.\n");
435       Reset();
436       return;
437     }
438 #endif
439     if (ret == 1)
440     {
441       // the socket has been identified, there's something (message or disconnection)
442       // see if there's anything in input
443       ret = Net_Receive( m_pInSocket, &msg );
444       if (ret > 0)
445       {
446         //        unsigned int size = msg.size; //++timo just a check
447         strcpy (m_xmlBuf, NMSG_ReadString (&msg));
448         if (m_bNeedCtxtInit)
449         {
450           m_xmlParserCtxt = NULL;
451           m_xmlParserCtxt = xmlCreatePushParserCtxt (&saxParser, &m_message_info, m_xmlBuf, strlen(m_xmlBuf), NULL);
452           if (m_xmlParserCtxt == NULL)
453           {
454             Sys_FPrintf (SYS_ERR, "Failed to create the XML parser (incoming stream began with: %s)\n", m_xmlBuf);
455             Reset();
456           }
457           m_bNeedCtxtInit = false;
458         }
459         else
460         {
461           xmlParseChunk (m_xmlParserCtxt, m_xmlBuf, strlen(m_xmlBuf), 0);
462         }
463       }
464       else
465       {
466         // error or connection closed/reset
467         // NOTE: if we get an error down the XML stream we don't reach here
468         Net_Disconnect( m_pInSocket );
469         m_pInSocket = NULL;
470         Sys_Printf("Connection closed.\n");
471         if (m_bBSPPlugin)
472         {
473           Reset();
474           // let the BSP plugin know that the job is done
475           g_BSPFrontendTable.m_pfnEndListen(0);
476           return;
477         }
478         // move to next step or finish
479         m_iCurrentStep++;
480         if (m_iCurrentStep < m_pCmd->len )
481         {
482           DoEBeginStep();
483         }
484         else
485         {
486           // release the GPtrArray and the strings
487           if (m_pCmd != NULL)
488           {
489             for (m_iCurrentStep=0; m_iCurrentStep < m_pCmd->len; m_iCurrentStep++ )
490             {
491               delete[] (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
492             }
493             g_ptr_array_free( m_pCmd, false );
494           }
495           m_pCmd = NULL;
496           // launch the engine .. OMG
497           if (g_PrefsDlg.m_bRunQuake)
498           {
499             // do we enter sleep mode before?
500             if (g_PrefsDlg.m_bDoSleep)
501             {
502               Sys_Printf("Going into sleep mode..\n");
503               g_pParentWnd->OnSleep();
504             }
505             Sys_Printf("Running engine...\n");
506             Str cmd;
507             // build the command line
508             cmd = g_pGameDescription->mEnginePath.GetBuffer();
509             // this is game dependant
510             //!\todo Read the engine binary name from a config file.
511             if (g_pGameDescription->mGameFile == "wolf.game")
512             {
513               if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
514               {
515                 // MP
516 #if defined(WIN32)
517                 cmd += "WolfMP.exe";
518 #elif defined(__linux__)
519                 cmd += "wolfmp";
520 #elif defined(__APPLE__)
521                 cmd += "wolfmp.app";
522 #else
523 #error "WTF are you compiling on"
524 #endif
525               }
526               else
527               {
528                 // SP
529 #if defined(WIN32)
530                 cmd += "WolfSP.exe";
531 #elif defined(__linux__)
532                 cmd += "wolfsp";
533 #elif defined(__APPLE__)
534                 cmd += "wolfsp.app";
535 #else
536 #error "WTF are you compiling on"
537 #endif
538               }
539             } else if (g_pGameDescription->mGameFile == "et.game")
540             {
541 #if defined(WIN32)
542               cmd += "et.exe";
543 #elif defined(__linux__)
544               cmd += "et";
545 #elif defined(__APPLE__)
546               cmd += "et.app";
547 #else
548 #error "WTF are you compiling on"
549 #endif
550             }
551             // RIANT
552             // JK2 HACK
553             else if (g_pGameDescription->mGameFile == "jk2.game")
554             {
555               if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
556               {
557                 // MP
558 #if defined(WIN32)
559                 cmd += "jk2MP.exe";
560 #elif defined(__linux__)
561                 cmd += "jk2mp";
562 #elif defined(__APPLE__)
563                 cmd += "jk2mp.app";
564 #else
565 #error "WTF are you compiling on"
566 #endif
567               }
568               else
569               {
570                 // SP
571 #if defined(WIN32)
572                 cmd += "jk2SP.exe";
573 #elif defined(__linux__)
574                 cmd += "jk2sp";
575 #elif defined(__APPLE__)
576                 cmd += "jk2sp.app";
577 #else
578 #error "WTF are you compiling on"
579 #endif
580               }
581             }
582             // TTimo
583             // JA HACK
584             else if (g_pGameDescription->mGameFile == "ja.game")
585             {
586               if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
587               {
588                 // MP
589 #if defined(WIN32)
590                 cmd += "jamp.exe";
591 #elif !defined(__linux__) && !defined(__APPLE__)
592 #error "WTF are you compiling on"
593 #endif
594               }
595               else
596               {
597                 // SP
598 #if defined(WIN32)
599                 cmd += "jasp.exe";
600 #elif !defined(__linux__) && !defined(__APPLE__)
601 #error "WTF are you compiling on"
602 #endif
603               }
604             }
605             // RIANT
606             // STVEF HACK
607             else if (g_pGameDescription->mGameFile == "stvef.game")
608             {
609               if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
610               {
611                 // MP
612 #if defined(WIN32)
613                 cmd += "stvoyHM.exe";
614 #elif defined(__linux__)
615                 cmd += "stvoyHM";
616 #elif defined(__APPLE__)
617                 cmd += "stvoyHM.app";
618 #else
619 #error "WTF are you compiling on"
620 #endif
621               }
622               else
623               {
624                 // SP
625 #if defined(WIN32)
626                 cmd += "stvoy.exe";
627 #elif defined(__linux__)
628                 cmd += "stvoy";
629 #elif defined(__APPLE__)
630                 cmd += "stvoy.app";
631 #else
632 #error "WTF are you compiling on"
633 #endif
634               }
635             }
636             // RIANT
637             // SOF2 HACK
638             else if (g_pGameDescription->mGameFile == "sof2.game")
639             {
640               if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
641               {
642                 // MP
643 #if defined(WIN32)
644                 cmd += "sof2MP.exe";
645 #elif defined(__linux__)
646                 cmd += "b00gus";
647 #elif defined(__APPLE__)
648                 cmd += "sof2MP.app";
649 #else
650 #error "WTF are you compiling on"
651 #endif
652               }
653               else
654               {
655                 // SP
656 #if defined(WIN32)
657                 cmd += "sof2.exe";
658 #elif defined(__linux__)
659                 cmd += "b00gus";
660 #elif defined(__APPLE__)
661                 cmd += "sof2.app";
662 #else
663 #error "WTF are you compiling on"
664 #endif
665               }
666             }
667             else
668             {
669               cmd += g_pGameDescription->mEngine.GetBuffer();
670             }
671 #ifdef _WIN32
672             // NOTE: we are using unix pathnames and CreateProcess doesn't like / in the program path
673             FindReplace( cmd, "/", "\\" );
674 #endif
675             Str cmdline;
676             if ( (g_pGameDescription->mGameFile == "q2.game") || (g_pGameDescription->mGameFile == "heretic2.game") )
677             {
678                 cmdline = ". +exec radiant.cfg +map ";
679                 cmdline += m_sBSPName;
680             }
681             else
682             {
683               cmdline = "+set sv_pure 0 ";
684               // TTimo: a check for vm_* but that's all fine
685               //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 ";
686               if (*ValueForKey(g_qeglobals.d_project_entity, "gamename") != '\0')
687               {
688                 cmdline += "+set fs_game ";
689                 cmdline += ValueForKey(g_qeglobals.d_project_entity, "gamename");
690                 cmdline += " ";
691               }
692               //!\todo Read the start-map args from a config file.
693               if (g_pGameDescription->mGameFile == "wolf.game")
694               {
695                 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
696                 {
697                   // MP
698                   cmdline += "+devmap ";
699                   cmdline += m_sBSPName;
700                 }
701                 else
702                 {
703                   // SP                
704                   cmdline += "+set nextmap \"spdevmap ";
705                   cmdline += m_sBSPName;
706                   cmdline += "\"";
707                 }
708               }
709               else
710               {
711                 cmdline += "+devmap ";
712                 cmdline += m_sBSPName;
713               }
714             }
715
716             Sys_Printf("%s %s\n", cmd.GetBuffer(), cmdline.GetBuffer());
717
718             // execute now
719             if (!Q_Exec(cmd.GetBuffer(), (char *)cmdline.GetBuffer(), g_pGameDescription->mEnginePath.GetBuffer(), false))
720             {
721               CString msg;
722               msg = "Failed to execute the following command: ";
723               msg += cmd; msg += cmdline;
724               Sys_Printf(msg);
725               gtk_MessageBox(g_pParentWnd->m_pWidget,  msg, "BSP monitoring", MB_OK | MB_ICONERROR );
726             }
727           }
728           Reset();
729         }
730       }
731     }
732     break;
733   default:
734     break;
735   }
736 }
737
738 void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, char *sBSPName )
739 {
740   if (m_sBSPName)
741   {
742     delete[] m_sBSPName;
743   }
744   m_sBSPName = sBSPName;
745   if (m_eState != EIdle)
746   {
747     Sys_Printf("WatchBSP got a monitoring request while not idling...\n");
748     // prompt the user, should we cancel the current process and go ahead?
749     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?", 
750       "BSP process monitoring", MB_YESNO ) == IDYES)
751     {
752       // disconnect and set EIdle state
753       Reset();
754     }
755   }
756   m_pCmd = pCmd;
757   m_iCurrentStep = 0;
758   DoEBeginStep();
759 }
760
761 void CWatchBSP::ExternalListen()
762 {
763   m_bBSPPlugin = true;
764   DoEBeginStep ();
765 }
766
767 // the part of the watchbsp interface we export to plugins
768 // NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level
769 // for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final)
770 void WINAPI QERApp_Listen()
771 {
772   // open the listening socket
773   g_pParentWnd->GetWatchBSP()->ExternalListen();
774 }