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