]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/main.cpp
a6e19296fa293d748107d692dee82ef830f75713
[xonotic/netradiant.git] / radiant / main.cpp
1 /*
2 Copyright (C) 1999-2007 id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
4
5 This file is part of GtkRadiant.
6
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 */
21
22 #if defined (__linux__) || defined (__APPLE__)
23   #include <gdk/gdkx.h>
24   #include <pwd.h>
25   #include <unistd.h>
26   #ifdef __linux__
27     #include <mntent.h>
28   #endif
29   #include <dirent.h>
30   #include <pthread.h>
31   #include <sys/wait.h>
32   #include <signal.h>
33   #include <sys/stat.h>
34 #endif
35
36 #include <gtk/gtk.h>
37 #include <gtk/gtkgl.h>
38 #include <glib/gi18n.h>
39 #include "stdafx.h"
40 #include <assert.h>
41 #include <sys/types.h>
42 #include <sys/stat.h>
43
44 #include <stdlib.h>
45
46 #include "watchbsp.h"
47 #include "filters.h"
48 #include "glwidget.h"
49
50 bool g_bBuildList = false;
51 int g_argc;
52 char** g_argv;
53
54 // =============================================================================
55 // Splash screen
56
57 // get rid of it when debugging
58 #if defined (_DEBUG)
59   #define SKIP_SPLASH
60 #endif
61
62 static GtkWidget *splash_screen;
63
64 // called based on a timer, or in particular cases when we don't want to keep it around
65 gint try_destroy_splash (gpointer data)
66 {
67   if (splash_screen)
68   {
69     gtk_widget_destroy (splash_screen);
70     splash_screen = NULL;
71   }
72   return FALSE;
73 }
74
75 static void create_splash ()
76 {
77   GtkWidget *alert_frame, *alert_frame1, *pixmap;
78
79   splash_screen = gtk_window_new (GTK_WINDOW_POPUP);
80   gtk_window_position (GTK_WINDOW (splash_screen), GTK_WIN_POS_CENTER);
81   gtk_widget_realize (splash_screen);
82
83   alert_frame1 = gtk_frame_new (NULL);
84   gtk_widget_show (alert_frame1);
85   gtk_container_add (GTK_CONTAINER (splash_screen), alert_frame1);
86   gtk_frame_set_shadow_type (GTK_FRAME (alert_frame1), GTK_SHADOW_OUT);
87
88   alert_frame = gtk_frame_new (NULL);
89   gtk_widget_show (alert_frame);
90
91   gtk_container_add (GTK_CONTAINER (alert_frame1), alert_frame);
92   gtk_frame_set_shadow_type (GTK_FRAME (alert_frame), GTK_SHADOW_IN);
93   gtk_container_border_width (GTK_CONTAINER (alert_frame), 3);
94
95   pixmap = gtk_preview_new (GTK_PREVIEW_COLOR);
96   gtk_widget_show (pixmap);
97   gtk_container_add (GTK_CONTAINER (alert_frame), pixmap);
98
99   CString str;
100   guint16 width, height;
101   unsigned char *buf;
102
103   str = g_strGameToolsPath;
104   str += "bitmaps/splash.bmp";
105
106   unsigned char* load_bitmap_file (const char* filename, guint16* width, guint16* height);
107   buf = load_bitmap_file (str.GetBuffer (), &width, &height);
108
109   if (!buf)
110   {
111     str = g_strBitmapsPath;
112     str += "splash.bmp";
113
114     buf = load_bitmap_file (str.GetBuffer (), &width, &height);
115   }
116
117   if (buf)
118   {
119     GtkPreview *preview = GTK_PREVIEW (pixmap);
120     gtk_preview_size (preview, width, height);
121     for (int y = 0; y < height; y++)
122       gtk_preview_draw_row (preview, buf+y*width*3, 0, y, width);
123   }
124
125   gtk_widget_show_all (splash_screen);
126
127   while (gtk_events_pending ())
128     gtk_main_iteration ();
129 }
130
131 // =============================================================================
132 // Loki stuff
133
134 #if defined (__linux__) || defined (__APPLE__)
135
136 /* A short game name, could be used as argv[0] */
137 static char game_name[100] = "";
138
139 /* The directory where the data files can be found (run directory) */
140 static char datapath[PATH_MAX];
141
142 char *loki_gethomedir(void)
143 {
144   char *home = NULL;
145
146   home = getenv("HOME");
147   if ( home == NULL )
148   {
149     uid_t id = getuid();
150     struct passwd *pwd;
151
152     setpwent();
153     while ( (pwd = getpwent()) != NULL )
154     {
155       if ( pwd->pw_uid == id )
156       {
157         home = pwd->pw_dir;
158         break;
159       }
160     }
161     endpwent();
162   }
163   return home;
164 }
165
166 /* Must be called BEFORE loki_initialize */
167 void loki_setgamename(const char *n)
168 {
169   strncpy(game_name, n, sizeof(game_name));
170 }
171
172   #ifdef __linux__
173 /* Code to determine the mount point of a CD-ROM */
174 int loki_getmountpoint(const char *device, char *mntpt, int max_size)
175 {
176   char devpath[PATH_MAX], mntdevpath[PATH_MAX];
177   FILE * mountfp;
178   struct mntent *mntent;
179   int mounted;
180
181   /* Nothing to do with no device file */
182   if ( device == NULL )
183   {
184     *mntpt = '\0';
185     return -1;
186   }
187
188   /* Get the fully qualified path of the CD-ROM device */
189   if ( realpath(device, devpath) == NULL )
190   {
191     perror("realpath() on your CD-ROM failed");
192     return(-1);
193   }
194
195   /* Get the mount point */
196   mounted = -1;
197   memset(mntpt, 0, max_size);
198   mountfp = setmntent( _PATH_MNTTAB, "r" );
199   if ( mountfp != NULL )
200   {
201     mounted = 0;
202     while ( (mntent = getmntent( mountfp )) != NULL )
203     {
204       char *tmp, mntdev[1024];
205
206       strcpy(mntdev, mntent->mnt_fsname);
207       if ( strcmp(mntent->mnt_type, "supermount") == 0 )
208       {
209         tmp = strstr(mntent->mnt_opts, "dev=");
210         if ( tmp )
211         {
212           strcpy(mntdev, tmp+strlen("dev="));
213           tmp = strchr(mntdev, ',');
214           if ( tmp )
215           {
216             *tmp = '\0';
217           }
218         }
219       }
220       if ( strncmp(mntdev, "/dev", 4) ||
221            realpath(mntdev, mntdevpath) == NULL )
222       {
223         continue;
224       }
225       if ( strcmp( mntdevpath, devpath ) == 0 )
226       {
227         mounted = 1;
228         assert((int)strlen( mntent->mnt_dir ) < max_size);
229         strncpy( mntpt, mntent->mnt_dir, max_size-1);
230         mntpt[max_size-1] = '\0';
231         break;
232       }
233     }
234     endmntent( mountfp );
235   }
236   return(mounted);
237 }
238   #endif
239
240 /*
241     This function gets the directory containing the running program.
242     argv0 - the 0'th argument to the program
243 */
244 // FIXME TTimo
245 // I don't understand this function. It looks like something cut from another piece of software
246 // we somehow get the g_strAppPath from it, but it's done through a weird scan across $PATH env. var.
247 // even worse, it doesn't behave the same in all cases .. works well when ran through gdb and borks when ran from a shell
248 void loki_initpaths(char *argv0)
249 {
250   char temppath[PATH_MAX]; //, env[100];
251   char *home; //, *ptr, *data_env;
252
253   home = loki_gethomedir();
254   if ( home == NULL )
255   {
256     home = ".";
257   }
258
259   if (*game_name == 0) /* Game name defaults to argv[0] */
260     loki_setgamename(argv0);
261
262   strcpy(temppath, argv0);  /* If this overflows, it's your own fault :) */
263   if ( ! strrchr(temppath, '/') )
264   {
265     char *path;
266     char *last;
267     int found;
268
269     found = 0;
270     path = getenv("PATH");
271     do
272     {
273       /* Initialize our filename variable */
274       temppath[0] = '\0';
275
276       /* Get next entry from path variable */
277       last = strchr(path, ':');
278       if ( ! last )
279         last = path+strlen(path);
280
281       /* Perform tilde expansion */
282       if ( *path == '~' )
283       {
284         strcpy(temppath, home);
285         ++path;
286       }
287
288       /* Fill in the rest of the filename */
289       if ( last > (path+1) )
290       {
291         strncat(temppath, path, (last-path));
292         strcat(temppath, "/");
293       }
294       strcat(temppath, "./");
295       strcat(temppath, argv0);
296
297       /* See if it exists, and update path */
298       if ( access(temppath, X_OK) == 0 )
299       {
300         ++found;
301       }
302       path = last+1;
303
304     } while ( *last && !found );
305
306   } else
307   {
308     /* Increment argv0 to the basename */
309     argv0 = strrchr(argv0, '/')+1;
310   }
311
312   /* Now canonicalize it to a full pathname for the data path */
313   if ( realpath(temppath, datapath) )
314   {
315     /* There should always be '/' in the path */
316     *(strrchr(datapath, '/')) = '\0';
317   }
318 }
319
320 char *loki_getdatapath(void)
321 {
322   return(datapath);
323 }
324
325 #endif
326
327 // end of Loki stuff
328 // =============================================================================
329
330 void error_redirect (const gchar *domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data)
331 {
332   gboolean in_recursion;
333   gboolean is_fatal;
334   char buf[256];
335
336   in_recursion = (log_level & G_LOG_FLAG_RECURSION) != 0;
337   is_fatal = (log_level & G_LOG_FLAG_FATAL) != 0;
338   log_level = (GLogLevelFlags) (log_level & G_LOG_LEVEL_MASK);
339
340   if (!message)
341     message = "(NULL) message";
342
343   if (domain)
344     strcpy (buf, domain);
345   else
346     strcpy (buf, "**");
347   strcat (buf, "-");
348
349   switch (log_level)
350   {
351   case G_LOG_LEVEL_ERROR:
352     if (in_recursion)
353       strcat (buf, "ERROR (recursed) **: ");
354     else
355       strcat (buf, "ERROR **: ");
356     break;
357   case G_LOG_LEVEL_CRITICAL:
358     if (in_recursion)
359       strcat (buf, "CRITICAL (recursed) **: ");
360     else
361       strcat (buf, "CRITICAL **: ");
362     break;
363   case G_LOG_LEVEL_WARNING:
364     if (in_recursion)
365       strcat (buf, "WARNING (recursed) **: ");
366     else
367       strcat (buf, "WARNING **: ");
368     break;
369   case G_LOG_LEVEL_MESSAGE:
370     if (in_recursion)
371       strcat (buf, "Message (recursed): ");
372     else
373       strcat (buf, "Message: ");
374     break;
375   case G_LOG_LEVEL_INFO:
376     if (in_recursion)
377       strcat (buf, "INFO (recursed): ");
378     else
379       strcat (buf, "INFO: ");
380     break;
381   case G_LOG_LEVEL_DEBUG:
382     if (in_recursion)
383       strcat (buf, "DEBUG (recursed): ");
384     else
385       strcat (buf, "DEBUG: ");
386     break;
387   default:
388     /* we are used for a log level that is not defined by GLib itself,
389      * try to make the best out of it.
390      */
391     if (in_recursion)
392       strcat (buf, "LOG (recursed:");
393     else
394       strcat (buf, "LOG (");
395     if (log_level)
396     {
397       gchar string[] = "0x00): ";
398       gchar *p = string + 2;
399       guint i;
400
401       i = g_bit_nth_msf (log_level, -1);
402       *p = i >> 4;
403       p++;
404       *p = '0' + (i & 0xf);
405       if (*p > '9')
406         *p += 'A' - '9' - 1;
407
408       strcat (buf, string);
409     } else
410       strcat (buf, "): ");
411   }
412
413   strcat (buf, message);
414   if (is_fatal)
415     strcat (buf, "\naborting...\n");
416   else
417     strcat (buf, "\n");
418
419   printf ("%s\n", buf);
420   Sys_FPrintf (SYS_WRN, buf);
421   // TTimo NOTE: in some cases it may be handy to log only to the file
422 //  Sys_FPrintf (SYS_NOCON, buf);
423 }
424
425 #define GETTEXT_PACKAGE "radiant"
426 #define LOCALEDIR "lang"
427
428 int main( int argc, char* argv[] ) {
429         char *libgl, *ptr;
430         int i, j, k;
431
432   /*
433     Rambetter on Fri Jan 21, 2011:
434
435     People with recent NVIDIA cards and Windows 7 (Aero) are complaining about "hall of mirrors"
436     effect in the OpenGL viewports.  The code below is borrowed from NetRadiant to address
437     this issue.  This change turns off desktop compositing (globally) while Radiant is running.
438     It's a real hack but the "correct" fix is very involving, so until the underlying problem
439     is addressed we need this code here.  I don't know for sure what the underlying problem is,
440     but I have a hunch it's the version of gtkglext.dll we're using.
441   */
442
443 #ifdef _WIN32
444   HMODULE lib;
445   lib = LoadLibrary("dwmapi.dll");
446   if (lib != 0)
447   {
448     void (WINAPI *qDwmEnableComposition) (bool bEnable) =
449       (void (WINAPI *) (bool bEnable)) GetProcAddress(lib, "DwmEnableComposition");
450     if (qDwmEnableComposition)
451     {
452       qDwmEnableComposition(FALSE);
453     }
454     FreeLibrary(lib);
455   }
456 #endif
457
458
459   /*
460     Rambetter on Sat Nov 13, 2010:
461
462     The following line fixes parsing and writing of floating point numbers in locales such as
463     Italy, Germany, and others outside of en_US.  In particular, in such problem locales, users
464     are not able to use certain map entities such as "light" because the definitions of these entities
465     in the entity definition files contain floating point values written in the standard "C" format
466     (containing a dot instead of, for example, a comma).  The call sscanf() is all over the code,
467     including parsing entity definition files and reading Radiant preferences.  sscanf() is sensitive
468     to locale (in particular when reading floating point numbers).
469
470     The line below is the minimalistic way to address only this particular problem - the parsing
471     and writing of floating point values.  There may be other yet-undiscovered bugs related to
472     locale still lingering in the code.  When such bugs are discovered, they should be addressed by
473     setting more than just "LC_NUMERIC=C" (for example LC_CTYPE for regular expression matching)
474     or by fixing the problem in the actual code instead of fiddling with LC_* variables.
475
476     Another way to fix the floating point format problem is to locate all calls such as *scanf()
477     and *printf() in the code and replace them with other functions.  However, we're also using
478     external libraries such as libxml and [maybe?] they use locale to parse their numeric values.
479     I'm just saying, it may get ugly if we try to fix the problem without setting LC_NUMERIC.
480
481     Usage of sscanf() throughout the code looks like so:
482       sscanf(str, "%f %f %f", &val1, &val2, &val3);
483     Code like this exists in many files, here are 4 examples:
484       tools/quake3/q3map2/light.c
485       tools/quake3/q3map2/model.c
486       radiant/preferences.cpp
487       plugins/entity/miscmodel.cpp
488
489     Also affected are printf() calls when using formats that contain "%f".
490
491     I did some research and putenv() seems to be the best choice for being cross-platform.  It
492     used to be a function in Windows (now deprecated):
493       http://msdn.microsoft.com/en-us/library/ms235321(VS.80).aspx
494     And of course it's defined in UNIX.
495
496     One more thing.  the gtk_init() call below modifies all of the locale settings.  In fact if it
497     weren't for gtk_init(), we wouldn't have to set LC_NUMERIC (parsing of floating points with
498     a dot works just fine before the gtk_init() call on a sample Linux system).  If we were to
499     just setlocale() here, it would get clobbered by gtk_init().  So instead of using setlocale()
500     _after_ gtk_init(), I chose to fix this problem via environment variable.  I think it's cleaner
501     that way.
502   */
503   putenv("LC_NUMERIC=C");
504
505 #ifdef _WIN32
506   libgl = "opengl32.dll";
507 #endif
508
509 #if defined (__linux__)
510   libgl = "libGL.so.1";
511 #endif
512
513 #ifdef __APPLE__
514   libgl = "/usr/X11R6/lib/libGL.1.dylib";
515 #endif
516
517 #if defined (__linux__) || defined (__APPLE__)
518   // Give away unnecessary root privileges.
519   // Important: must be done before calling gtk_init().
520   char *loginname;
521   struct passwd *pw;
522   seteuid(getuid());
523   if ( geteuid() == 0 && ( loginname = getlogin() ) != NULL && ( pw = getpwnam(loginname) ) != NULL ) {
524           setuid(pw->pw_uid);
525   }
526 #endif
527
528
529         bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
530         bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
531         textdomain(GETTEXT_PACKAGE);
532 //  gtk_disable_setlocale();
533
534   gtk_init(&argc, &argv);
535   gtk_gl_init(&argc, &argv);
536   gdk_gl_init(&argc, &argv);
537
538   // TODO: Find a better place to call this.
539   gtk_glwidget_create_font();
540
541   if ((ptr = getenv ("Q3R_LIBGL")) != NULL)
542     libgl = ptr;
543
544   for (i = 1; i < argc; i++)
545   {
546     char* param = argv[i];
547
548     if (param[0] == '-' && param[1] == '-')
549     {
550       param += 2;
551
552       if ((strcmp (param, "libgl") == 0) && (i != argc))
553       {
554         libgl = argv[i+1];
555         argv[i] = argv[i+1] = NULL;
556         i++;
557       } else if (strcmp (param, "builddefs") == 0)
558       {
559         g_bBuildList = true;
560         argv[i] = NULL;
561       }
562     }
563   }
564
565   for (i = 1; i < argc; i++)
566   {
567     for (k = i; k < argc; k++)
568       if (argv[k] != NULL)
569         break;
570
571     if (k > i)
572     {
573       k -= i;
574       for (j = i + k; j < argc; j++)
575         argv[j-k] = argv[j];
576       argc -= k;
577     }
578   }
579
580   g_argc = argc;
581   g_argv = argv;
582
583   g_strPluginsDir = "plugins/";
584   g_strModulesDir = "modules/";
585
586 #ifdef _WIN32
587   // get path to the editor
588   char* pBuffer = g_strAppPath.GetBufferSetLength(_MAX_PATH + 1);
589   GetModuleFileName(NULL, pBuffer, _MAX_PATH);
590   pBuffer[g_strAppPath.ReverseFind('\\') + 1] = '\0';
591   QE_ConvertDOSToUnixName(pBuffer, pBuffer);
592   g_strAppPath.ReleaseBuffer();
593
594   g_strBitmapsPath = g_strAppPath;
595   g_strBitmapsPath += "bitmaps/";
596
597   CGameDialog::UpdateNetrun(false); // read the netrun configuration
598
599   if ( CGameDialog::GetNetrun() ) {
600     // we have to find a per-user g_strTempPath
601     // this behaves the same as on Linux
602     g_strTempPath = getenv("USERPROFILE");
603     if (!g_strTempPath.GetLength())
604     {
605       CString msg;
606       msg = "Radiant is configured to run from a network installation.\n";
607       msg += "I couldn't find the environement variable USERPROFILE\n";
608       msg += "I'm going to use C:\\RadiantSettings. Please set USERPROFILE\n";
609       gtk_MessageBox (NULL, msg, "Radiant - Network mode", MB_OK);
610       g_strTempPath = "C:\\";
611     }
612     g_strTempPath += "\\RadiantSettings\\";
613     Q_mkdir(g_strTempPath.GetBuffer(), 0755);
614     g_strTempPath += RADIANT_VERSION;
615     g_strTempPath += "\\";
616     Q_mkdir(g_strTempPath.GetBuffer(), 0755);
617   }
618   else
619   {
620     // use the core path as temp (to save commandlist.txt, and do the .pid files)
621     g_strTempPath = g_strAppPath;
622   }
623
624 #endif
625
626 #if defined (__linux__) || defined (__APPLE__)
627   Str home;
628   home = g_get_home_dir ();
629   AddSlash (home);
630   home += ".radiant/";
631   Q_mkdir (home.GetBuffer (), 0775);
632   home += RADIANT_VERSION;
633   Q_mkdir (home.GetBuffer (), 0775);
634   g_strTempPath = home.GetBuffer ();
635   AddSlash (g_strTempPath);
636
637   loki_initpaths(argv[0]);
638
639   // NOTE: we build g_strAppPath with a '/' (or '\' on WIN32)
640   // it's a general convention in Radiant to have the slash at the end of directories
641   char real[PATH_MAX];
642   realpath (loki_getdatapath(), real);
643   if (real[strlen(real)-1] != '/')
644     strcat(real, "/");
645
646   g_strAppPath = real;
647
648   // radiant is installed in the parent dir of "tools/"
649   // NOTE: this is not very easy for debugging
650   // maybe add options to lookup in several places?
651   // (for now I had to create symlinks)
652   g_strBitmapsPath = g_strAppPath;
653   g_strBitmapsPath += "bitmaps/";
654
655   // we will set this right after the game selection is done
656   g_strGameToolsPath = g_strAppPath;
657
658 #endif
659
660   // init the DTD path
661   g_strDTDPath = g_strAppPath;
662   g_strDTDPath += "dtds/";
663
664   /*!
665   the global prefs loading / game selection dialog might fail for any reason we don't know about
666   we need to catch when it happens, to cleanup the stateful prefs which might be killing it
667   and to turn on console logging for lookup of the problem
668   this is the first part of the two step .pid system
669   */
670   g_pidFile = g_strTempPath.GetBuffer ();
671   g_pidFile += "radiant.pid";
672
673   FILE *pid;
674   pid = fopen( g_pidFile.GetBuffer(), "r" );
675   if ( pid != NULL ) {
676     fclose (pid);
677     CString msg;
678
679     if (remove (g_pidFile.GetBuffer ()) == -1)
680     {
681       msg = "WARNING: Could not delete "; msg += g_pidFile;
682       gtk_MessageBox (NULL, msg, "Radiant", MB_OK | MB_ICONERROR );
683     }
684
685     // in debug, never prompt to clean registry, turn console logging auto after a failed start
686 #if !defined(_DEBUG)
687     msg = "Found the file ";
688     msg += g_pidFile;
689     msg += ".\nThis indicates that Radiant failed during the game selection startup last time it was run.\n"
690            "Choose YES to clean Radiant's registry settings and shut down Radiant.\n"
691            "WARNING: the global prefs will be lost if you choose YES.";
692
693     if (gtk_MessageBox (NULL, msg, "Radiant - Reset global startup?", MB_YESNO | MB_ICONQUESTION) == IDYES)
694     {
695       // remove global prefs and shutdown
696       g_PrefsDlg.mGamesDialog.Reset();
697       // remove the prefs file (like a full reset of the registry)
698       //remove (g_PrefsDlg.m_inipath->str);
699       gtk_MessageBox(NULL, "Removed global settings, choose OK to close Radiant.", "Radiant", MB_OK );
700       _exit(-1);
701     }
702     msg = "Logging console output to ";
703     msg += g_strTempPath;
704     msg += "radiant.log\nRefer to the log if Radiant fails to start again.";
705
706     gtk_MessageBox (NULL, msg, "Radiant - Console Log", MB_OK);
707 #endif
708
709     // set without saving, the class is not in a coherent state yet
710     // just do the value change and call to start logging, CGamesDialog will pickup when relevant
711     g_PrefsDlg.mGamesDialog.m_bLogConsole = true;
712     g_PrefsDlg.mGamesDialog.m_bForceLogConsole = true;
713     Sys_LogFile();
714   }
715
716   // create a primary .pid for global init run
717   pid = fopen( g_pidFile.GetBuffer(), "w" );
718   if ( pid ) {
719           fclose( pid );
720   }
721
722   // a safe check to avoid people running broken installations
723   // (otherwise, they run it, crash it, and blame us for not forcing them hard enough to pay attention while installing)
724   // make something idiot proof and someone will make better idiots, this may be overkill
725   // let's leave it disabled in debug mode in any case
726 #ifndef _DEBUG
727   //#define CHECK_VERSION
728 #endif
729 #ifdef CHECK_VERSION
730   // locate and open RADIANT_MAJOR and RADIANT_MINOR
731   qboolean bVerIsGood = true;
732   Str ver_file_name;
733   ver_file_name = g_strAppPath;
734   ver_file_name += "RADIANT_MAJOR";
735   FILE *ver_file = fopen (ver_file_name.GetBuffer(), "r");
736   if (ver_file)
737   {
738     char buf[10];
739     int chomp;
740     fread(buf, 1, 10, ver_file);
741     // chomp it (the hard way)
742     chomp = 0;
743     while(buf[chomp] >= '0' && buf[chomp] <= '9')
744       chomp++;
745     buf[chomp] = '\0';
746     if (strcmp(buf, RADIANT_MAJOR_VERSION))
747     {
748       Sys_Printf("ERROR: file RADIANT_MAJOR doesn't match ('%s')\n", buf);
749       bVerIsGood = false;
750     }
751   }
752   else
753   {
754     Sys_Printf("ERROR: can't find RADIANT_MAJOR in '%s'\n", ver_file_name.GetBuffer());
755     bVerIsGood = false;
756   }
757   ver_file_name = g_strAppPath;
758   ver_file_name += "RADIANT_MINOR";
759   ver_file = fopen (ver_file_name.GetBuffer(), "r");
760   if (ver_file)
761   {
762     char buf[10];
763     int chomp;
764     fread(buf, 1, 10, ver_file);
765     // chomp it (the hard way)
766     chomp = 0;
767     while(buf[chomp] >= '0' && buf[chomp] <= '9')
768       chomp++;
769     buf[chomp] = '\0';
770     if (strcmp(buf, RADIANT_MINOR_VERSION))
771     {
772       Sys_Printf("ERROR: file RADIANT_MINOR doesn't match ('%s')\n", buf);
773       bVerIsGood = false;
774     }
775   }
776   else
777   {
778     Sys_Printf("ERROR: can't find RADIANT_MINOR in '%s'\n", ver_file_name.GetBuffer());
779     bVerIsGood = false;
780   }
781   if (!bVerIsGood)
782   {
783     CString msg;
784     msg = "This editor binary (" RADIANT_VERSION ") doesn't match what the latest setup has configured in this directory\n";
785     msg += "Make sure you run the right/latest editor binary you installed\n";
786     msg += g_strAppPath; msg += "\n";
787     msg += "Check http://www.qeradiant.com/faq/index.cgi?file=219 for more information";
788     gtk_MessageBox(NULL, msg.GetBuffer(), "Radiant", MB_OK, "http://www.qeradiant.com/faq/index.cgi?file=219");
789     _exit(-1);
790   }
791 #endif
792
793   g_qeglobals.disable_ini = false;
794   g_PrefsDlg.Init();
795
796   // close the primary
797   if ( remove( g_pidFile.GetBuffer () ) == -1 ) {
798     CString msg;
799     msg = "WARNING: Could not delete "; msg += g_pidGameFile;
800     gtk_MessageBox (NULL, msg, "Radiant", MB_OK | MB_ICONERROR );
801   }
802
803   /*!
804   now the secondary game dependant .pid file
805   */
806   g_pidGameFile = g_PrefsDlg.m_rc_path->str;
807   g_pidGameFile += "radiant-game.pid";
808
809   pid = fopen (g_pidGameFile.GetBuffer(), "r");
810   if (pid != NULL)
811   {
812     fclose (pid);
813     CString msg;
814     if (remove (g_pidGameFile.GetBuffer ()) == -1)
815     {
816       msg = "WARNING: Could not delete "; msg += g_pidGameFile;
817       gtk_MessageBox (NULL, msg, "Radiant", MB_OK | MB_ICONERROR );
818     }
819
820     msg = "Found the file ";
821     msg += g_pidGameFile;
822     msg += ".\nThis indicates that Radiant failed to load the last time it was run.\n"
823            "Choose YES to clean Radiant's registry settings and shut down Radiant.\n"
824            "WARNING: preferences will be lost if you choose YES.";
825
826     // in debug, never prompt to clean registry, turn console logging auto after a failed start
827 #if !defined(_DEBUG)
828     //bleh
829     if (gtk_MessageBox (NULL, msg, "Radiant - Clean Registry?", MB_YESNO | MB_ICONQUESTION) == IDYES)
830     {
831       // remove the game prefs files
832       remove (g_PrefsDlg.m_inipath->str);
833       char buf[PATH_MAX];
834       sprintf(buf, "%sSavedInfo.bin", g_PrefsDlg.m_rc_path->str);
835       remove(buf);
836       // remove the global pref too
837       g_PrefsDlg.mGamesDialog.Reset();
838       gtk_MessageBox(NULL, "Cleaned registry settings, choose OK to close Radiant.\nThe next time Radiant runs it will use default settings.", "Radiant", MB_OK );
839       _exit(-1);
840     }
841     msg = "Logging console output to ";
842     msg += g_strTempPath;
843     msg += "radiant.log\nRefer to the log if Radiant fails to start again.";
844
845     gtk_MessageBox (NULL, msg, "Radiant - Console Log", MB_OK);
846 #endif
847
848     // force console logging on! (will go in prefs too)
849     g_PrefsDlg.mGamesDialog.m_bLogConsole = true;
850     g_PrefsDlg.mGamesDialog.SavePrefs();
851     Sys_LogFile();
852
853     g_PrefsDlg.LoadPrefs();
854
855   } else
856   {
857     // create one, will remove right after entering message loop
858     pid = fopen (g_pidGameFile.GetBuffer(), "w");
859     if (pid)
860       fclose (pid);
861
862     g_PrefsDlg.LoadPrefs();
863
864 #ifndef _DEBUG // I can't be arsed about that prompt in debug mode
865     // if console logging is on in the prefs, warn about performance hit
866     if (g_PrefsDlg.mGamesDialog.m_bLogConsole)
867     {
868       if (gtk_MessageBox (NULL, "Preferences indicate that console logging is on. This affects performance.\n"
869                           "Turn it off?", "Radiant", MB_YESNO | MB_ICONQUESTION) == IDYES)
870       {
871         g_PrefsDlg.mGamesDialog.m_bLogConsole = false;
872         g_PrefsDlg.mGamesDialog.SavePrefs();
873       }
874     }
875 #endif
876     // toggle console logging if necessary
877     Sys_LogFile();
878   }
879
880   // we should search in g_strTempPath, then move over to look at g_strAppPath?
881 #ifdef _WIN32
882   // fine tune the look of the app using a gtk rc file
883   // we try to load an RC file placed in the application directory
884   // build the full path
885   Str sRCFile;
886   sRCFile = g_strAppPath;
887   sRCFile += "radiantgtkrc";
888   // we load that file with g_new in gtk_rc_parse_file (gtkrc.c), change the '/' into '\\'
889   pBuffer = (char *)sRCFile.GetBuffer();
890   for (i=0; i<sRCFile.GetLength(); i++)
891   {
892     if (pBuffer[i]=='/')
893     {
894       pBuffer[i] = '\\';
895     }
896   }
897   // check the file exists
898   if (access(sRCFile.GetBuffer(), R_OK) != 0)
899     Sys_Printf("RC file %s not found\n", sRCFile.GetBuffer());
900   else
901   {
902     Sys_Printf ("Attemping to load RC file %s\n", sRCFile.GetBuffer());
903     gtk_rc_parse (sRCFile.GetBuffer());
904   }
905 #endif
906
907 #ifndef SKIP_SPLASH
908   create_splash();
909 #endif
910
911   if (!QGL_Init(libgl, ""))
912   {
913     Sys_FPrintf (SYS_ERR, "Failed to load OpenGL libraries\n");
914     _exit (1);
915     return 1;
916   }
917
918 #if defined (__linux__) || defined (__APPLE__)
919   if ((qglXQueryExtension == NULL) || (qglXQueryExtension(GDK_DISPLAY(),NULL,NULL) != True))
920   {
921     Sys_FPrintf (SYS_ERR, "glXQueryExtension failed\n");
922     _exit (1);
923     return 1;
924   }
925 #endif
926
927   // redirect Gtk warnings to the console
928   g_log_set_handler( "Gdk", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
929                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG), error_redirect, NULL);
930   g_log_set_handler( "Gtk", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
931                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG), error_redirect, NULL);
932
933   // spog - creates new filters list for the first time
934   g_qeglobals.d_savedinfo.filters = NULL;
935   g_qeglobals.d_savedinfo.filters = FilterAddBase(g_qeglobals.d_savedinfo.filters);
936
937   g_pParentWnd = new MainFrame();
938
939   if ( g_PrefsDlg.m_bLoadLastMap && g_PrefsDlg.m_strLastMap.GetLength() > 0 ) {
940           Map_LoadFile(g_PrefsDlg.m_strLastMap.GetBuffer());
941   } else {
942           Map_New();
943   }
944
945   // load up shaders now that we have the map loaded
946   // eviltypeguy
947   Texture_ShowStartupShaders();
948
949 #ifndef SKIP_SPLASH
950   gdk_window_raise(splash_screen->window);
951   gtk_window_set_transient_for( GTK_WINDOW( splash_screen ), GTK_WINDOW( g_pParentWnd->m_pWidget ) );
952   gtk_timeout_add( 1000, try_destroy_splash, NULL );
953 #endif
954
955   g_pParentWnd->GetSynapseServer().DumpActiveClients();
956
957   //++timo: temporary debug
958   g_pParentWnd->DoWatchBSP();
959
960   gtk_main();
961
962   // close the log file if any
963   // NOTE: don't save prefs past this point!
964   g_PrefsDlg.mGamesDialog.m_bLogConsole = false;
965   // set the console window to NULL to avoid Sys_Printf crashing
966   g_qeglobals_gui.d_edit = NULL;
967   Sys_LogFile();
968
969   // NOTE TTimo not sure what this _exit(0) call is worth
970   //   restricting it to linux build
971 #ifdef __linux__
972   _exit( 0 );
973 #endif
974   return 0;
975 }
976
977 // ydnar: quick and dirty fix, just make the buffer bigger
978 #define BIG_PATH_MAX    4096
979
980 // TTimo: decompose the BSP command into several steps so we can monitor them eventually
981 void QE_ExpandBspString (char *bspaction, GPtrArray *out_array, char *mapname)
982 {
983   const char  *in;
984   char  *out;
985   char  src[BIG_PATH_MAX];
986   char  rsh[BIG_PATH_MAX];
987   char  base[BIG_PATH_MAX];
988
989   strcpy(src, mapname);
990   strlwr(src);
991   in = strstr(src, "maps/");
992   if (!in)
993   {
994     in = strstr(src, "maps/");
995   }
996   if (in)
997   {
998     in += 5;
999     strcpy(base, in);
1000     out = base;
1001     while (*out)
1002     {
1003       if (*out == '\\')
1004       {
1005         *out = '/';
1006       }
1007       out++;
1008     }
1009   } else
1010   {
1011     ExtractFileName (mapname, base);
1012   }
1013
1014   // this important step alters the map name to add fs_game
1015   // NOTE: it used to add fs_basepath too
1016   // the fs_basepath addition moved to being in the project file during the bug fixing rush
1017   // but it may not have been the right thing to do
1018
1019   // HACK: halflife compiler tools don't support -fs_game
1020   // HACK: neither does JKII/SoF2/ etc..
1021   // do we use & have fs_game?
1022
1023   if (g_pGameDescription->mGameFile != "hl.game" &&
1024       *ValueForKey(g_qeglobals.d_project_entity,"gamename") != '\0')
1025     {
1026       // set with fs_game
1027       sprintf(src, "-fs_game %s \"%s\"", ValueForKey(g_qeglobals.d_project_entity,"gamename"), mapname);
1028     }
1029     else
1030     {
1031       sprintf(src, "\"%s\"", mapname);
1032   }
1033
1034   rsh[0] = 0;
1035
1036   QE_ConvertDOSToUnixName(src, src);
1037
1038   // initialise the first step
1039   out = new char[BIG_PATH_MAX]; //% PATH_MAX
1040   g_ptr_array_add( out_array, out );
1041
1042   in = ValueForKey( g_qeglobals.d_project_entity, bspaction );
1043   while (*in)
1044   {
1045     if (in[0] == '!')
1046     {
1047       strcpy (out, rsh);
1048       out += strlen(rsh);
1049       in++;
1050       continue;
1051     }
1052     if (in[0] == '#')
1053     {
1054       char tmp[2048];
1055       // we process these only if monitoring
1056       if (g_PrefsDlg.m_bWatchBSP)
1057       {
1058         // -connect global option (the only global option so far anyway)
1059         strcpy (tmp, " -connect 127.0.0.1:39000 ");
1060         strcpy (out, tmp);
1061         out += strlen(tmp);
1062       }
1063       in++;
1064       continue;
1065     }
1066     if (in[0] == '$')
1067     {
1068       // $ expansion
1069       strcpy (out, src);
1070       out += strlen(src);
1071       in++;
1072       continue;
1073     }
1074     if (in[0] == '@')
1075     {
1076       *out++ = '"';
1077       in++;
1078       continue;
1079     }
1080     if (in[0] == '&')
1081       if (in[1] == '&')
1082       {
1083         // start a new step
1084         *out = 0;
1085         in = in + 2;
1086         out = new char[BIG_PATH_MAX];   //% PATH_MAX
1087         g_ptr_array_add( out_array, out );
1088       }
1089     *out++ = *in++;
1090   }
1091   *out = 0;
1092 }
1093
1094 void FindReplace(CString& strContents, const char* pTag, const char* pValue)
1095 {
1096   if (strcmp(pTag, pValue) == 0)
1097     return;
1098   for (int nPos = strContents.Find(pTag); nPos >= 0; nPos = strContents.Find(pTag))
1099   {
1100     int nRightLen = strContents.GetLength() - strlen(pTag) - nPos;
1101     CString strLeft = strContents.Left(nPos);
1102     CString strRight = strContents.Right(nRightLen);
1103     strLeft += pValue;
1104     strLeft += strRight;
1105     strContents = strLeft;
1106   }
1107 }
1108
1109 // save the map, deals with regioning
1110 void SaveWithRegion(char *name)
1111 {
1112   strcpy (name, currentmap);
1113   if (region_active)
1114   {
1115     // temporary cut the region to save regular map
1116     region_active = false;
1117     Map_SaveFile (name, false);
1118     region_active = true;
1119     StripExtension (name);
1120     strcat (name, ".reg");
1121   }
1122
1123   Map_SaveFile (name, region_active);
1124 }
1125
1126 void RunBsp (char *command)
1127 {
1128   GPtrArray *sys;
1129   char  batpath[BIG_PATH_MAX];  //% PATH_MAX
1130   char  temppath[BIG_PATH_MAX]; //% PATH_MAX
1131   char  name[BIG_PATH_MAX];             //% PATH_MAX
1132   char  cWork[BIG_PATH_MAX];    //% PATH_MAX
1133   FILE  *hFile;
1134   unsigned int   i;
1135
1136   SetInspectorMode(W_CONSOLE);
1137
1138   strcpy (temppath, g_strTempPath.GetBuffer ());
1139
1140   SaveWithRegion(name);
1141
1142   const char *rsh = ValueForKey(g_qeglobals.d_project_entity, "rshcmd");
1143   if (rsh == NULL)
1144   {
1145     CString strPath, strFile;
1146
1147     ExtractPath_and_Filename(name, strPath, strFile);
1148     AddSlash(strPath);
1149     strncpy(cWork, strPath, 1024);
1150     strcat(cWork, strFile);
1151   } else
1152   {
1153     strcpy(cWork, name);
1154   }
1155
1156   // get the array ready
1157   //++timo TODO: free the array, free the strings ourselves with delete[]
1158   sys = g_ptr_array_new();
1159
1160   QE_ExpandBspString (command, sys, cWork);
1161
1162   if (g_PrefsDlg.m_bWatchBSP)
1163   {
1164     // grab the file name for engine running
1165     char *bspname = new char[1024];
1166     ExtractFileName( currentmap, bspname );
1167     StripExtension( bspname );
1168     g_pParentWnd->GetWatchBSP()->DoMonitoringLoop( sys, bspname );
1169   } else
1170   {
1171     // write all the steps in a single BAT / .sh file and run it, don't bother monitoring it
1172     CString strSys;
1173     for (i=0; i < sys->len; i++ )
1174     {
1175       strSys += (char *)g_ptr_array_index( sys, i);
1176 #ifdef _WIN32  // write temp\junk.txt in win32... NOTE: stops output to shell prompt window
1177       if (i==0)
1178         strSys += " >";
1179       else
1180         strSys += " >>";
1181       strSys += "\"";
1182       strSys += temppath;
1183       strSys += "junk.txt\"";
1184 #endif
1185       strSys += "\n";
1186     };
1187
1188 #if defined (__linux__) || defined (__APPLE__)
1189
1190     // write qe3bsp.sh
1191     sprintf (batpath, "%sqe3bsp.sh", temppath);
1192     Sys_Printf("Writing the compile script to '%s'\n", batpath);
1193     Sys_Printf("The build output will be saved in '%sjunk.txt'\n", temppath);
1194     hFile = fopen(batpath, "w");
1195     if (!hFile)
1196       Error ("Can't write to %s", batpath);
1197     fprintf (hFile, "#!/bin/sh \n\n");
1198     fprintf (hFile, strSys.GetBuffer());
1199     fclose (hFile);
1200     chmod (batpath, 0744);
1201 #endif
1202
1203 #ifdef _WIN32
1204     sprintf (batpath, "%sqe3bsp.bat", temppath);
1205     Sys_Printf("Writing the compile script to '%s'\n", batpath);
1206     Sys_Printf("The build output will be saved in '%sjunk.txt'\n", temppath);
1207     hFile = fopen(batpath, "w");
1208     if (!hFile)
1209       Error ("Can't write to %s", batpath);
1210     fprintf (hFile, strSys.GetBuffer());
1211     fclose (hFile);
1212 #endif
1213
1214     Pointfile_Delete ();
1215
1216 #if defined (__linux__) || defined (__APPLE__)
1217
1218     pid_t pid;
1219
1220     pid = fork ();
1221     switch (pid)
1222     {
1223     case -1:
1224       Error ("CreateProcess failed");
1225       break;
1226     case 0:
1227       execlp (batpath, batpath, NULL);
1228       printf ("execlp error !");
1229       _exit (0);
1230       break;
1231     default:
1232       break;
1233     }
1234 #endif
1235
1236 #ifdef _WIN32
1237     Sys_Printf ("Running bsp command...\n");
1238     Sys_Printf ("\n%s\n", strSys.GetBuffer());
1239
1240     WinExec( batpath, SW_SHOWNORMAL );
1241 #endif
1242   }
1243 #ifdef _DEBUG
1244   // yeah, do it .. but not now right before 1.1-TA-beta release
1245   Sys_Printf("TODO: erase GPtrArray\n");
1246 #endif
1247 }
1248
1249 #if 0
1250
1251 #ifdef _WIN32
1252
1253 int WINAPI QEW_SetupPixelFormat(HDC hDC, qboolean zbuffer )
1254 {
1255   static PIXELFORMATDESCRIPTOR pfd = {
1256     sizeof(PIXELFORMATDESCRIPTOR),  // size of this pfd
1257     1,                              // version number
1258     PFD_DRAW_TO_WINDOW |            // support window
1259     PFD_SUPPORT_OPENGL |            // support OpenGL
1260     PFD_DOUBLEBUFFER,               // double buffered
1261     PFD_TYPE_RGBA,                  // RGBA type
1262     24,                             // 24-bit color depth
1263     0, 0, 0, 0, 0, 0,               // color bits ignored
1264     0,                              // no alpha buffer
1265     0,                              // shift bit ignored
1266     0,                              // no accumulation buffer
1267     0, 0, 0, 0,                     // accum bits ignored
1268     32,                             // depth bits
1269     0,                              // no stencil buffer
1270     0,                              // no auxiliary buffer
1271     PFD_MAIN_PLANE,                 // main layer
1272     0,                              // reserved
1273     0, 0, 0                         // layer masks ignored
1274   };                              //
1275   int pixelformat = 0;
1276
1277   zbuffer = true;
1278   if ( !zbuffer )
1279     pfd.cDepthBits = 0;
1280
1281   if ( (pixelformat = ChoosePixelFormat(hDC, &pfd)) == 0 )
1282   {
1283     LPVOID lpMsgBuf;
1284     FormatMessage(
1285                  FORMAT_MESSAGE_ALLOCATE_BUFFER |
1286                  FORMAT_MESSAGE_FROM_SYSTEM |
1287                  FORMAT_MESSAGE_IGNORE_INSERTS,
1288                  NULL,
1289                  GetLastError(),
1290                  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
1291                  (LPTSTR) &lpMsgBuf,
1292                  0,
1293                  NULL
1294                  );
1295     Sys_FPrintf (SYS_WRN, "GetLastError: %s", lpMsgBuf);
1296     Error ("ChoosePixelFormat failed");
1297   }
1298
1299   if (!SetPixelFormat(hDC, pixelformat, &pfd))
1300     Error ("SetPixelFormat failed");
1301
1302   return pixelformat;
1303 }
1304
1305 #endif
1306
1307 #endif