2 Copyright (c) 2001, Loki software, inc.
\r
5 Redistribution and use in source and binary forms, with or without modification,
\r
6 are permitted provided that the following conditions are met:
\r
8 Redistributions of source code must retain the above copyright notice, this list
\r
9 of conditions and the following disclaimer.
\r
11 Redistributions in binary form must reproduce the above copyright notice, this
\r
12 list of conditions and the following disclaimer in the documentation and/or
\r
13 other materials provided with the distribution.
\r
15 Neither the name of Loki software nor the names of its contributors may be used
\r
16 to endorse or promote products derived from this software without specific prior
\r
17 written permission.
\r
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
\r
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
\r
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
\r
22 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
\r
23 DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
\r
24 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
\r
25 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
\r
26 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
\r
27 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
\r
28 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\r
31 //-----------------------------------------------------------------------------
\r
34 // monitoring window for running BSP processes (and possibly various other stuff)
\r
37 #include "watchbsp.h"
\r
38 #include "feedback.h"
\r
41 #include <winsock2.h>
\r
44 #if defined (__linux__) || defined (__APPLE__)
\r
45 #include <sys/time.h>
\r
46 #define SOCKET_ERROR -1
\r
55 // Static functions for the SAX callbacks -------------------------------------------------------
\r
57 // utility for saxStartElement below
\r
58 static void abortStream(message_info_t *data)
\r
60 g_pParentWnd->GetWatchBSP()->Reset();
\r
61 // tell there has been an error
\r
62 if (g_pParentWnd->GetWatchBSP()->HasBSPPlugin ())
\r
63 g_BSPFrontendTable.m_pfnEndListen(2);
\r
64 // yeah this doesn't look good.. but it's needed so that everything will be ignored until the stream goes out
\r
65 data->ignore_depth = -1;
\r
69 #include "stream_version.h"
\r
71 static void saxStartElement(message_info_t *data, const xmlChar *name, const xmlChar **attrs)
\r
73 if (data->ignore_depth == 0)
\r
75 if (data->bGeometry)
\r
76 // we have a handler
\r
78 data->pGeometry->saxStartElement (data, name, attrs);
\r
82 if (strcmp ((char *)name, "q3map_feedback") == 0)
\r
84 // check the correct version
\r
85 // old q3map don't send a version attribute
\r
86 // the ones we support .. send Q3MAP_STREAM_VERSION
\r
87 if (!attrs[0] || !attrs[1] || (strcmp((char*)attrs[0],"version") != 0))
\r
89 Sys_FPrintf(SYS_ERR, "No stream version given in the feedback stream, this is an old q3map version.\n"
\r
90 "Please turn off monitored compiling if you still wish to use this q3map executable\n");
\r
94 else if (strcmp((char*)attrs[1],Q3MAP_STREAM_VERSION) != 0)
\r
96 Sys_FPrintf(SYS_ERR,
\r
97 "This version of Radiant reads version %s debug streams, I got an incoming connection with version %s\n"
\r
98 "Please make sure your versions of Radiant and q3map are matching.\n", Q3MAP_STREAM_VERSION, (char*)attrs[1]);
\r
103 // we don't treat locally
\r
104 else if (strcmp ((char *)name, "message") == 0)
\r
106 data->msg_level = atoi ((char *)attrs[1]);
\r
108 else if (strcmp ((char *)name, "polyline") == 0)
\r
109 // polyline has a particular status .. right now we only use it for leakfile ..
\r
111 data->bGeometry = true;
\r
112 data->pGeometry = &g_pointfile;
\r
113 data->pGeometry->saxStartElement (data, name, attrs);
\r
115 else if (strcmp ((char *)name, "select") == 0)
\r
117 CSelectMsg *pSelect = new CSelectMsg();
\r
118 data->bGeometry = true;
\r
119 data->pGeometry = pSelect;
\r
120 data->pGeometry->saxStartElement (data, name, attrs);
\r
122 else if (strcmp ((char *)name, "pointmsg") == 0)
\r
124 CPointMsg *pPoint = new CPointMsg();
\r
125 data->bGeometry = true;
\r
126 data->pGeometry = pPoint;
\r
127 data->pGeometry->saxStartElement (data, name, attrs);
\r
129 else if (strcmp ((char *)name, "windingmsg") == 0)
\r
131 CWindingMsg *pWinding = new CWindingMsg();
\r
132 data->bGeometry = true;
\r
133 data->pGeometry = pWinding;
\r
134 data->pGeometry->saxStartElement (data, name, attrs);
\r
138 Sys_FPrintf (SYS_WRN, "WARNING: ignoring unrecognized node in XML stream (%s)\n", name);
\r
139 // we don't recognize this node, jump over it
\r
140 // (NOTE: the ignore mechanism is a bit screwed, only works when starting an ignore at the highest level)
\r
141 data->ignore_depth = data->recurse;
\r
148 static void saxEndElement(message_info_t *data, const xmlChar *name)
\r
151 // we are out of an ignored chunk
\r
152 if (data->recurse == data->ignore_depth)
\r
154 data->ignore_depth = 0;
\r
157 if (data->bGeometry)
\r
159 data->pGeometry->saxEndElement (data, name);
\r
160 // we add the object to the debug window
\r
161 if (!data->bGeometry)
\r
163 g_DbgDlg.Push (data->pGeometry);
\r
166 if (data->recurse == data->stop_depth)
\r
169 Sys_Printf ("Received error msg .. shutting down..\n");
\r
171 g_pParentWnd->GetWatchBSP()->Reset();
\r
172 // tell there has been an error
\r
173 if (g_pParentWnd->GetWatchBSP()->HasBSPPlugin ())
\r
174 g_BSPFrontendTable.m_pfnEndListen(2);
\r
179 static void saxCharacters(message_info_t *data, const xmlChar *ch, int len)
\r
181 if (data->bGeometry)
\r
183 data->pGeometry->saxCharacters (data, ch, len);
\r
187 if (data->ignore_depth != 0)
\r
189 // output the message using the level
\r
191 memcpy( buf, ch, len );
\r
193 Sys_FPrintf (data->msg_level, "%s", buf);
\r
194 // if this message has error level flag, we mark the depth to stop the compilation when we get out
\r
195 // we don't set the msg level if we don't stop on leak
\r
196 if (data->msg_level == 3)
\r
198 data->stop_depth = data->recurse-1;
\r
203 static void saxComment(void *ctx, const xmlChar *msg)
\r
205 Sys_Printf("XML comment: %s\n", msg);
\r
208 static void saxWarning(void *ctx, const char *msg, ...)
\r
210 char saxMsgBuffer[4096];
\r
213 va_start(args, msg);
\r
214 vsprintf (saxMsgBuffer, msg, args);
\r
216 Sys_FPrintf(SYS_WRN, "XML warning: %s\n", saxMsgBuffer);
\r
219 static void saxError(void *ctx, const char *msg, ...)
\r
221 char saxMsgBuffer[4096];
\r
224 va_start(args, msg);
\r
225 vsprintf (saxMsgBuffer, msg, args);
\r
227 Sys_FPrintf(SYS_ERR, "XML error: %s\n", saxMsgBuffer);
\r
230 static void saxFatal(void *ctx, const char *msg, ...)
\r
236 va_start(args, msg);
\r
237 vsprintf (buffer, msg, args);
\r
239 Sys_FPrintf(SYS_ERR, "XML fatal error: %s\n", buffer);
\r
242 static xmlSAXHandler saxParser = {
\r
243 0, /* internalSubset */
\r
244 0, /* isStandalone */
\r
245 0, /* hasInternalSubset */
\r
246 0, /* hasExternalSubset */
\r
247 0, /* resolveEntity */
\r
249 0, /* entityDecl */
\r
250 0, /* notationDecl */
\r
251 0, /* attributeDecl */
\r
252 0, /* elementDecl */
\r
253 0, /* unparsedEntityDecl */
\r
254 0, /* setDocumentLocator */
\r
255 0, /* startDocument */
\r
256 0, /* endDocument */
\r
257 (startElementSAXFunc)saxStartElement, /* startElement */
\r
258 (endElementSAXFunc)saxEndElement, /* endElement */
\r
260 (charactersSAXFunc)saxCharacters, /* characters */
\r
261 0, /* ignorableWhitespace */
\r
262 0, /* processingInstruction */
\r
263 (commentSAXFunc)saxComment, /* comment */
\r
264 (warningSAXFunc)saxWarning, /* warning */
\r
265 (errorSAXFunc)saxError, /* error */
\r
266 (fatalErrorSAXFunc)saxFatal, /* fatalError */
\r
269 // ------------------------------------------------------------------------------------------------
\r
271 CWatchBSP::~CWatchBSP()
\r
276 delete[] m_sBSPName;
\r
282 void CWatchBSP::Reset()
\r
286 Net_Disconnect(m_pInSocket);
\r
287 m_pInSocket = NULL;
\r
289 if (m_pListenSocket)
\r
291 Net_Disconnect(m_pListenSocket);
\r
292 m_pListenSocket = NULL;
\r
294 if (m_xmlInputBuffer)
\r
296 xmlFreeParserInputBuffer (m_xmlInputBuffer);
\r
297 m_xmlInputBuffer = NULL;
\r
302 bool CWatchBSP::SetupListening()
\r
305 if (m_pListenSocket)
\r
307 Sys_Printf("ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n");
\r
311 Sys_Printf("Setting up\n");
\r
313 m_pListenSocket = Net_ListenSocket(39000);
\r
314 if (m_pListenSocket == NULL)
\r
316 Sys_Printf("Listening...\n");
\r
320 void CWatchBSP::DoEBeginStep()
\r
323 if (SetupListening() == false)
\r
326 msg = "Failed to get a listening socket on port 39000.\nTry running with BSP monitoring disabled if you can't fix this.\n";
\r
328 gtk_MessageBox (g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR);
\r
331 // set the timer for timeouts and step cancellation
\r
332 g_timer_reset( m_pTimer );
\r
333 g_timer_start( m_pTimer );
\r
337 Sys_Printf("=== running BSP command ===\n%s\n", g_ptr_array_index( m_pCmd, m_iCurrentStep ) );
\r
339 if (!Q_Exec(NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true ))
\r
342 msg = "Failed to execute the following command: ";
\r
343 msg += (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
\r
344 msg += "\nCheck that the file exists and that you don't run out of system resources.\n";
\r
346 gtk_MessageBox(g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR );
\r
349 // re-initialise the debug window
\r
350 if (m_iCurrentStep == 0)
\r
353 m_eState = EBeginStep;
\r
356 void CWatchBSP::RoutineProcessing()
\r
358 // used for select()
\r
360 TIMEVAL tout = { 0, 0 };
\r
362 #if defined (__linux__) || defined (__APPLE__)
\r
371 // timeout: if we don't get an incoming connection fast enough, go back to idle
\r
372 if ( g_timer_elapsed( m_pTimer, NULL ) > g_PrefsDlg.m_iTimeout )
\r
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 );
\r
378 // status == 1 : didn't get the connection
\r
379 g_BSPFrontendTable.m_pfnEndListen(1);
\r
384 // some debug checks
\r
385 if (!m_pListenSocket)
\r
387 Sys_Printf("ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n");
\r
391 // we are not connected yet, accept any incoming connection
\r
392 m_pInSocket = Net_Accept(m_pListenSocket);
\r
395 Sys_Printf("Connected.\n");
\r
396 // prepare the message info struct for diving in
\r
397 memset (&m_message_info, 0, sizeof(message_info_s));
\r
398 // a dumb flag to make sure we init the push parser context when first getting a msg
\r
399 m_bNeedCtxtInit = true;
\r
400 m_eState = EWatching;
\r
405 // some debug checks
\r
408 Sys_Printf("ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n");
\r
412 // select() will identify if the socket needs an update
\r
413 // if the socket is identified that means there's either a message or the connection has been closed/reset/terminated
\r
417 FD_SET(((unsigned int)m_pInSocket->socket), &readfds);
\r
418 // from select man page:
\r
419 // n is the highest-numbered descriptor in any of the three sets, plus 1
\r
420 // (no use on windows)
\r
421 ret = select( m_pInSocket->socket + 1, &readfds, NULL, NULL, &tout );
\r
422 if (ret == SOCKET_ERROR)
\r
424 Sys_Printf("WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n");
\r
425 Sys_Printf("Terminating the connection.\n");
\r
432 // we are non-blocking?? we should never get timeout errors
\r
433 Sys_Printf("WARNING: unexpected timeout expired in CWatchBSP::Processing\n");
\r
434 Sys_Printf("Terminating the connection.\n");
\r
441 // the socket has been identified, there's something (message or disconnection)
\r
442 // see if there's anything in input
\r
443 ret = Net_Receive( m_pInSocket, &msg );
\r
446 // unsigned int size = msg.size; //++timo just a check
\r
447 strcpy (m_xmlBuf, NMSG_ReadString (&msg));
\r
448 if (m_bNeedCtxtInit)
\r
450 m_xmlParserCtxt = NULL;
\r
451 m_xmlParserCtxt = xmlCreatePushParserCtxt (&saxParser, &m_message_info, m_xmlBuf, strlen(m_xmlBuf), NULL);
\r
452 if (m_xmlParserCtxt == NULL)
\r
454 Sys_FPrintf (SYS_ERR, "Failed to create the XML parser (incoming stream began with: %s)\n", m_xmlBuf);
\r
457 m_bNeedCtxtInit = false;
\r
461 xmlParseChunk (m_xmlParserCtxt, m_xmlBuf, strlen(m_xmlBuf), 0);
\r
466 // error or connection closed/reset
\r
467 // NOTE: if we get an error down the XML stream we don't reach here
\r
468 Net_Disconnect( m_pInSocket );
\r
469 m_pInSocket = NULL;
\r
470 Sys_Printf("Connection closed.\n");
\r
474 // let the BSP plugin know that the job is done
\r
475 g_BSPFrontendTable.m_pfnEndListen(0);
\r
478 // move to next step or finish
\r
480 if (m_iCurrentStep < m_pCmd->len )
\r
486 // release the GPtrArray and the strings
\r
487 if (m_pCmd != NULL)
\r
489 for (m_iCurrentStep=0; m_iCurrentStep < m_pCmd->len; m_iCurrentStep++ )
\r
491 delete[] (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
\r
493 g_ptr_array_free( m_pCmd, false );
\r
496 // launch the engine .. OMG
\r
497 if (g_PrefsDlg.m_bRunQuake)
\r
499 // do we enter sleep mode before?
\r
500 if (g_PrefsDlg.m_bDoSleep)
\r
502 Sys_Printf("Going into sleep mode..\n");
\r
503 g_pParentWnd->OnSleep();
\r
505 Sys_Printf("Running engine...\n");
\r
507 // build the command line
\r
508 cmd = g_pGameDescription->mEnginePath.GetBuffer();
\r
509 // this is game dependant
\r
510 //!\todo Read the engine binary name from a config file.
\r
511 if (g_pGameDescription->mGameFile == "wolf.game")
\r
513 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
\r
517 cmd += "WolfMP.exe";
\r
518 #elif defined(__linux__)
\r
520 #elif defined(__APPLE__)
\r
521 cmd += "wolfmp.app";
\r
523 #error "WTF are you compiling on"
\r
530 cmd += "WolfSP.exe";
\r
531 #elif defined(__linux__)
\r
533 #elif defined(__APPLE__)
\r
534 cmd += "wolfsp.app";
\r
536 #error "WTF are you compiling on"
\r
539 } else if (g_pGameDescription->mGameFile == "et.game")
\r
543 #elif defined(__linux__)
\r
545 #elif defined(__APPLE__)
\r
548 #error "WTF are you compiling on"
\r
553 else if (g_pGameDescription->mGameFile == "jk2.game")
\r
555 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
\r
559 cmd += "jk2MP.exe";
\r
560 #elif defined(__linux__)
\r
562 #elif defined(__APPLE__)
\r
563 cmd += "jk2mp.app";
\r
565 #error "WTF are you compiling on"
\r
572 cmd += "jk2SP.exe";
\r
573 #elif defined(__linux__)
\r
575 #elif defined(__APPLE__)
\r
576 cmd += "jk2sp.app";
\r
578 #error "WTF are you compiling on"
\r
584 else if (g_pGameDescription->mGameFile == "ja.game")
\r
586 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
\r
591 #elif !defined(__linux__) && !defined(__APPLE__)
\r
592 #error "WTF are you compiling on"
\r
600 #elif !defined(__linux__) && !defined(__APPLE__)
\r
601 #error "WTF are you compiling on"
\r
607 else if (g_pGameDescription->mGameFile == "stvef.game")
\r
609 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
\r
613 cmd += "stvoyHM.exe";
\r
614 #elif defined(__linux__)
\r
616 #elif defined(__APPLE__)
\r
617 cmd += "stvoyHM.app";
\r
619 #error "WTF are you compiling on"
\r
626 cmd += "stvoy.exe";
\r
627 #elif defined(__linux__)
\r
629 #elif defined(__APPLE__)
\r
630 cmd += "stvoy.app";
\r
632 #error "WTF are you compiling on"
\r
638 else if (g_pGameDescription->mGameFile == "sof2.game")
\r
640 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
\r
644 cmd += "sof2MP.exe";
\r
645 #elif defined(__linux__)
\r
647 #elif defined(__APPLE__)
\r
648 cmd += "sof2MP.app";
\r
650 #error "WTF are you compiling on"
\r
658 #elif defined(__linux__)
\r
660 #elif defined(__APPLE__)
\r
663 #error "WTF are you compiling on"
\r
669 cmd += g_pGameDescription->mEngine.GetBuffer();
\r
672 // NOTE: we are using unix pathnames and CreateProcess doesn't like / in the program path
\r
673 FindReplace( cmd, "/", "\\" );
\r
676 if ( (g_pGameDescription->mGameFile == "q2.game") || (g_pGameDescription->mGameFile == "heretic2.game") )
\r
678 cmdline = ". +exec radiant.cfg +map ";
\r
679 cmdline += m_sBSPName;
\r
683 cmdline = "+set sv_pure 0 ";
\r
684 // TTimo: a check for vm_* but that's all fine
\r
685 //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 ";
\r
686 if (*ValueForKey(g_qeglobals.d_project_entity, "gamename") != '\0')
\r
688 cmdline += "+set fs_game ";
\r
689 cmdline += ValueForKey(g_qeglobals.d_project_entity, "gamename");
\r
692 //!\todo Read the start-map args from a config file.
\r
693 if (g_pGameDescription->mGameFile == "wolf.game")
\r
695 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
\r
698 cmdline += "+devmap ";
\r
699 cmdline += m_sBSPName;
\r
704 cmdline += "+set nextmap \"spdevmap ";
\r
705 cmdline += m_sBSPName;
\r
711 cmdline += "+devmap ";
\r
712 cmdline += m_sBSPName;
\r
716 Sys_Printf("%s %s\n", cmd.GetBuffer(), cmdline.GetBuffer());
\r
719 if (!Q_Exec(cmd.GetBuffer(), (char *)cmdline.GetBuffer(), g_pGameDescription->mEnginePath.GetBuffer(), false))
\r
722 msg = "Failed to execute the following command: ";
\r
723 msg += cmd; msg += cmdline;
\r
725 gtk_MessageBox(g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR );
\r
738 void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, char *sBSPName )
\r
742 delete[] m_sBSPName;
\r
744 m_sBSPName = sBSPName;
\r
745 if (m_eState != EIdle)
\r
747 Sys_Printf("WatchBSP got a monitoring request while not idling...\n");
\r
748 // prompt the user, should we cancel the current process and go ahead?
\r
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?",
\r
750 "BSP process monitoring", MB_YESNO ) == IDYES)
\r
752 // disconnect and set EIdle state
\r
757 m_iCurrentStep = 0;
\r
761 void CWatchBSP::ExternalListen()
\r
763 m_bBSPPlugin = true;
\r
767 // the part of the watchbsp interface we export to plugins
\r
768 // NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level
\r
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)
\r
770 void WINAPI QERApp_Listen()
\r
772 // open the listening socket
\r
773 g_pParentWnd->GetWatchBSP()->ExternalListen();
\r