]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/watchbsp.cpp
from nxn
[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         Net_Setup();
309         m_pListenSocket = Net_ListenSocket(39000);
310   if (m_pListenSocket == NULL)
311     return false;
312   Sys_Printf("Listening...\n");
313   return true;
314 }
315
316 void CWatchBSP::DoEBeginStep() {
317         Reset();
318         if ( !SetupListening() ) {
319                 CString msg;
320                 msg = "Failed to get a listening socket on port 39000.\nTry running with BSP monitoring disabled if you can't fix this.\n";
321                 Sys_Printf( msg );
322                 gtk_MessageBox( g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR );
323                 return;
324         }
325         // set the timer for timeouts and step cancellation
326         g_timer_reset( m_pTimer );
327         g_timer_start( m_pTimer );
328
329         if ( !m_bBSPPlugin ) {
330                 Sys_Printf( "=== running BSP command ===\n%s\n", g_ptr_array_index( m_pCmd, m_iCurrentStep ) );
331
332                 if ( !Q_Exec( NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true ) ) {
333                         CString msg;
334                         msg = "Failed to execute the following command: ";
335                         msg += (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
336                         msg += "\nCheck that the file exists and that you don't run out of system resources.\n";
337                         Sys_Printf( msg );
338                         gtk_MessageBox( g_pParentWnd->m_pWidget,  msg, "BSP monitoring", MB_OK | MB_ICONERROR );
339                         return;
340                 }
341                 // re-initialise the debug window
342                 if ( m_iCurrentStep == 0 ) {
343                         g_DbgDlg.Init();
344                 }
345         }
346         m_eState = EBeginStep;
347 }
348
349 void CWatchBSP::RoutineProcessing()
350 {
351   // used for select()
352 #ifdef _WIN32
353     TIMEVAL tout = { 0, 0 };
354 #endif
355 #if defined (__linux__) || defined (__APPLE__)
356                 timeval tout;
357                 tout.tv_sec = 0;
358                 tout.tv_usec = 0;
359 #endif
360
361   switch (m_eState)
362   {
363   case EBeginStep:
364     // timeout: if we don't get an incoming connection fast enough, go back to idle
365     if ( g_timer_elapsed( m_pTimer, NULL ) > g_PrefsDlg.m_iTimeout )
366     {
367       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 );
368       Reset();
369       if (m_bBSPPlugin)
370       {
371         // status == 1 : didn't get the connection
372         g_BSPFrontendTable.m_pfnEndListen(1);
373       }
374       return;
375     }
376 #ifdef _DEBUG
377     // some debug checks
378     if (!m_pListenSocket)
379     {
380       Sys_Printf("ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n");
381       return;
382     }
383 #endif
384     // we are not connected yet, accept any incoming connection
385     m_pInSocket = Net_Accept(m_pListenSocket);
386     if (m_pInSocket)
387     {
388       Sys_Printf("Connected.\n");
389       // prepare the message info struct for diving in
390       memset (&m_message_info, 0, sizeof(message_info_s)); 
391       // a dumb flag to make sure we init the push parser context when first getting a msg
392       m_bNeedCtxtInit = true;
393       m_eState = EWatching;
394     }
395     break;
396   case EWatching:
397 #ifdef _DEBUG
398     // some debug checks
399     if (!m_pInSocket)
400     {
401       Sys_Printf("ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n");
402       return;
403     }
404 #endif
405     // select() will identify if the socket needs an update
406     // if the socket is identified that means there's either a message or the connection has been closed/reset/terminated
407     fd_set readfds;
408     int ret;
409     FD_ZERO(&readfds);
410     FD_SET(((unsigned int)m_pInSocket->socket), &readfds);
411                 // from select man page:
412                 // n is the highest-numbered descriptor in any of the three sets, plus 1
413                 // (no use on windows)
414     ret = select( m_pInSocket->socket + 1, &readfds, NULL, NULL, &tout );
415     if (ret == SOCKET_ERROR)
416     {
417       Sys_Printf("WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n");
418       Sys_Printf("Terminating the connection.\n");
419       Reset();
420       return;
421     }
422 #ifdef _DEBUG
423     if (ret == -1)
424     {
425       // we are non-blocking?? we should never get timeout errors
426       Sys_Printf("WARNING: unexpected timeout expired in CWatchBSP::Processing\n");
427       Sys_Printf("Terminating the connection.\n");
428       Reset();
429       return;
430     }
431 #endif
432     if (ret == 1)
433     {
434       // the socket has been identified, there's something (message or disconnection)
435       // see if there's anything in input
436       ret = Net_Receive( m_pInSocket, &msg );
437       if (ret > 0)
438       {
439         //        unsigned int size = msg.size; //++timo just a check
440         strcpy (m_xmlBuf, NMSG_ReadString (&msg));
441         if (m_bNeedCtxtInit)
442         {
443           m_xmlParserCtxt = NULL;
444           m_xmlParserCtxt = xmlCreatePushParserCtxt (&saxParser, &m_message_info, m_xmlBuf, strlen(m_xmlBuf), NULL);
445           if (m_xmlParserCtxt == NULL)
446           {
447             Sys_FPrintf (SYS_ERR, "Failed to create the XML parser (incoming stream began with: %s)\n", m_xmlBuf);
448             Reset();
449           }
450           m_bNeedCtxtInit = false;
451         }
452         else
453         {
454           xmlParseChunk (m_xmlParserCtxt, m_xmlBuf, strlen(m_xmlBuf), 0);
455         }
456       }
457       else
458       {
459         // error or connection closed/reset
460         // NOTE: if we get an error down the XML stream we don't reach here
461         Net_Disconnect( m_pInSocket );
462         m_pInSocket = NULL;
463         Sys_Printf("Connection closed.\n");
464         if (m_bBSPPlugin)
465         {
466           Reset();
467           // let the BSP plugin know that the job is done
468           g_BSPFrontendTable.m_pfnEndListen(0);
469           return;
470         }
471         // move to next step or finish
472         m_iCurrentStep++;
473         if (m_iCurrentStep < m_pCmd->len )
474         {
475           DoEBeginStep();
476         }
477         else
478         {
479           // release the GPtrArray and the strings
480           if (m_pCmd != NULL)
481           {
482             for (m_iCurrentStep=0; m_iCurrentStep < m_pCmd->len; m_iCurrentStep++ )
483             {
484               delete[] (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
485             }
486             g_ptr_array_free( m_pCmd, false );
487           }
488           m_pCmd = NULL;
489           // launch the engine .. OMG
490           if (g_PrefsDlg.m_bRunQuake)
491           {
492             // do we enter sleep mode before?
493             if (g_PrefsDlg.m_bDoSleep)
494             {
495               Sys_Printf("Going into sleep mode..\n");
496               g_pParentWnd->OnSleep();
497             }
498             Sys_Printf("Running engine...\n");
499             Str cmd;
500             // build the command line
501             cmd = g_pGameDescription->mEnginePath.GetBuffer();
502             // this is game dependant
503             //!\todo Read the engine binary name from a config file.
504             if (g_pGameDescription->mGameFile == "wolf.game")
505             {
506               if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
507               {
508                 // MP
509 #if defined(WIN32)
510                 cmd += "WolfMP.exe";
511 #elif defined(__linux__)
512                 cmd += "wolfmp";
513 #elif defined(__APPLE__)
514                 cmd += "wolfmp.app";
515 #else
516 #error "WTF are you compiling on"
517 #endif
518               }
519               else
520               {
521                 // SP
522 #if defined(WIN32)
523                 cmd += "WolfSP.exe";
524 #elif defined(__linux__)
525                 cmd += "wolfsp";
526 #elif defined(__APPLE__)
527                 cmd += "wolfsp.app";
528 #else
529 #error "WTF are you compiling on"
530 #endif
531               }
532             } else if (g_pGameDescription->mGameFile == "et.game")
533             {
534 #if defined(WIN32)
535               cmd += "et.exe";
536 #elif defined(__linux__)
537               cmd += "et";
538 #elif defined(__APPLE__)
539               cmd += "et.app";
540 #else
541 #error "WTF are you compiling on"
542 #endif
543             }
544             // RIANT
545             // JK2 HACK
546             else if (g_pGameDescription->mGameFile == "jk2.game")
547             {
548               if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
549               {
550                 // MP
551 #if defined(WIN32)
552                 cmd += "jk2MP.exe";
553 #elif defined(__linux__)
554                 cmd += "jk2mp";
555 #elif defined(__APPLE__)
556                 cmd += "jk2mp.app";
557 #else
558 #error "WTF are you compiling on"
559 #endif
560               }
561               else
562               {
563                 // SP
564 #if defined(WIN32)
565                 cmd += "jk2SP.exe";
566 #elif defined(__linux__)
567                 cmd += "jk2sp";
568 #elif defined(__APPLE__)
569                 cmd += "jk2sp.app";
570 #else
571 #error "WTF are you compiling on"
572 #endif
573               }
574             }
575             // TTimo
576             // JA HACK
577             else if (g_pGameDescription->mGameFile == "ja.game")
578             {
579               if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
580               {
581                 // MP
582 #if defined(WIN32)
583                 cmd += "jamp.exe";
584 #elif !defined(__linux__) && !defined(__APPLE__)
585 #error "WTF are you compiling on"
586 #endif
587               }
588               else
589               {
590                 // SP
591 #if defined(WIN32)
592                 cmd += "jasp.exe";
593 #elif !defined(__linux__) && !defined(__APPLE__)
594 #error "WTF are you compiling on"
595 #endif
596               }
597             }
598             // RIANT
599             // STVEF HACK
600             else if (g_pGameDescription->mGameFile == "stvef.game")
601             {
602               if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
603               {
604                 // MP
605 #if defined(WIN32)
606                 cmd += "stvoyHM.exe";
607 #elif defined(__linux__)
608                 cmd += "stvoyHM";
609 #elif defined(__APPLE__)
610                 cmd += "stvoyHM.app";
611 #else
612 #error "WTF are you compiling on"
613 #endif
614               }
615               else
616               {
617                 // SP
618 #if defined(WIN32)
619                 cmd += "stvoy.exe";
620 #elif defined(__linux__)
621                 cmd += "stvoy";
622 #elif defined(__APPLE__)
623                 cmd += "stvoy.app";
624 #else
625 #error "WTF are you compiling on"
626 #endif
627               }
628             }
629             // RIANT
630             // SOF2 HACK
631             else if (g_pGameDescription->mGameFile == "sof2.game")
632             {
633               if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
634               {
635                 // MP
636 #if defined(WIN32)
637                 cmd += "sof2MP.exe";
638 #elif defined(__linux__)
639                 cmd += "b00gus";
640 #elif defined(__APPLE__)
641                 cmd += "sof2MP.app";
642 #else
643 #error "WTF are you compiling on"
644 #endif
645               }
646               else
647               {
648                 // SP
649 #if defined(WIN32)
650                 cmd += "sof2.exe";
651 #elif defined(__linux__)
652                 cmd += "b00gus";
653 #elif defined(__APPLE__)
654                 cmd += "sof2.app";
655 #else
656 #error "WTF are you compiling on"
657 #endif
658               }
659             }
660             else
661             {
662               cmd += g_pGameDescription->mEngine.GetBuffer();
663             }
664 #ifdef _WIN32
665             // NOTE: we are using unix pathnames and CreateProcess doesn't like / in the program path
666             FindReplace( cmd, "/", "\\" );
667 #endif
668             Str cmdline;
669             if ( (g_pGameDescription->mGameFile == "q2.game") || (g_pGameDescription->mGameFile == "heretic2.game") )
670             {
671                 cmdline = ". +exec radiant.cfg +map ";
672                 cmdline += m_sBSPName;
673             }
674             else
675             {
676               cmdline = "+set sv_pure 0 ";
677               // TTimo: a check for vm_* but that's all fine
678               //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 ";
679               if (*ValueForKey(g_qeglobals.d_project_entity, "gamename") != '\0')
680               {
681                 cmdline += "+set fs_game ";
682                 cmdline += ValueForKey(g_qeglobals.d_project_entity, "gamename");
683                 cmdline += " ";
684               }
685               //!\todo Read the start-map args from a config file.
686               if (g_pGameDescription->mGameFile == "wolf.game")
687               {
688                 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
689                 {
690                   // MP
691                   cmdline += "+devmap ";
692                   cmdline += m_sBSPName;
693                 }
694                 else
695                 {
696                   // SP                
697                   cmdline += "+set nextmap \"spdevmap ";
698                   cmdline += m_sBSPName;
699                   cmdline += "\"";
700                 }
701               }
702               else
703               {
704                 cmdline += "+devmap ";
705                 cmdline += m_sBSPName;
706               }
707             }
708
709             Sys_Printf("%s %s\n", cmd.GetBuffer(), cmdline.GetBuffer());
710
711             // execute now
712             if (!Q_Exec(cmd.GetBuffer(), (char *)cmdline.GetBuffer(), g_pGameDescription->mEnginePath.GetBuffer(), false))
713             {
714               CString msg;
715               msg = "Failed to execute the following command: ";
716               msg += cmd; msg += cmdline;
717               Sys_Printf(msg);
718               gtk_MessageBox(g_pParentWnd->m_pWidget,  msg, "BSP monitoring", MB_OK | MB_ICONERROR );
719             }
720           }
721           Reset();
722         }
723       }
724     }
725     break;
726   default:
727     break;
728   }
729 }
730
731 void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, char *sBSPName )
732 {
733   if (m_sBSPName)
734   {
735     delete[] m_sBSPName;
736   }
737   m_sBSPName = sBSPName;
738   if (m_eState != EIdle)
739   {
740     Sys_Printf("WatchBSP got a monitoring request while not idling...\n");
741     // prompt the user, should we cancel the current process and go ahead?
742     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?", 
743       "BSP process monitoring", MB_YESNO ) == IDYES)
744     {
745       // disconnect and set EIdle state
746       Reset();
747     }
748   }
749   m_pCmd = pCmd;
750   m_iCurrentStep = 0;
751   DoEBeginStep();
752 }
753
754 void CWatchBSP::ExternalListen()
755 {
756   m_bBSPPlugin = true;
757   DoEBeginStep ();
758 }
759
760 // the part of the watchbsp interface we export to plugins
761 // NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level
762 // for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final)
763 void WINAPI QERApp_Listen()
764 {
765   // open the listening socket
766   g_pParentWnd->GetWatchBSP()->ExternalListen();
767 }