apply back 335 and 336, will need to update deps and put a zip out
[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 Sat Nov 13, 2010:
434
435     The following line fixes parsing and writing of floating point numbers in locales such as
436     Italy, Germany, and others outside of en_US.  In particular, in such problem locales, users
437     are not able to use certain map entities such as "light" because the definitions of these entities
438     in the entity definition files contain floating point values written in the standard "C" format
439     (containing a dot instead of, for example, a comma).  The call sscanf() is all over the code,
440     including parsing entity definition files and reading Radiant preferences.  sscanf() is sensitive
441     to locale (in particular when reading floating point numbers).
442
443     The line below is the minimalistic way to address only this particular problem - the parsing
444     and writing of floating point values.  There may be other yet-undiscovered bugs related to
445     locale still lingering in the code.  When such bugs are discovered, they should be addressed by
446     setting more than just "LC_NUMERIC=C" (for example LC_CTYPE for regular expression matching)
447     or by fixing the problem in the actual code instead of fiddling with LC_* variables.
448
449     Another way to fix the floating point format problem is to locate all calls such as *scanf()
450     and *printf() in the code and replace them with other functions.  However, we're also using
451     external libraries such as libxml and [maybe?] they use locale to parse their numeric values.
452     I'm just saying, it may get ugly if we try to fix the problem without setting LC_NUMERIC.
453
454     Usage of sscanf() throughout the code looks like so:
455       sscanf(str, "%f %f %f", &val1, &val2, &val3);
456     Code like this exists in many files, here are 4 examples:
457       tools/quake3/q3map2/light.c
458       tools/quake3/q3map2/model.c
459       radiant/preferences.cpp
460       plugins/entity/miscmodel.cpp
461
462     Also affected are printf() calls when using formats that contain "%f".
463
464     I did some research and putenv() seems to be the best choice for being cross-platform.  It
465     used to be a function in Windows (now deprecated):
466       http://msdn.microsoft.com/en-us/library/ms235321(VS.80).aspx
467     And of course it's defined in UNIX.
468
469     One more thing.  the gtk_init() call below modifies all of the locale settings.  In fact if it
470     weren't for gtk_init(), we wouldn't have to set LC_NUMERIC (parsing of floating points with
471     a dot works just fine before the gtk_init() call on a sample Linux system).  If we were to
472     just setlocale() here, it would get clobbered by gtk_init().  So instead of using setlocale()
473     _after_ gtk_init(), I chose to fix this problem via environment variable.  I think it's cleaner
474     that way.
475   */
476   putenv("LC_NUMERIC=C");
477
478 #ifdef _WIN32
479   libgl = "opengl32.dll";
480 #endif
481
482 #if defined (__linux__)
483   libgl = "libGL.so.1";
484 #endif
485
486 #ifdef __APPLE__
487   libgl = "/usr/X11R6/lib/libGL.1.dylib";
488 #endif
489
490 #if defined (__linux__) || defined (__APPLE__)
491   // Give away unnecessary root privileges.
492   // Important: must be done before calling gtk_init().
493   char *loginname;
494   struct passwd *pw;
495   seteuid(getuid());
496   if ( geteuid() == 0 && ( loginname = getlogin() ) != NULL && ( pw = getpwnam(loginname) ) != NULL ) {
497           setuid(pw->pw_uid);
498   }
499 #endif
500
501
502         bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
503         bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
504         textdomain(GETTEXT_PACKAGE);
505 //  gtk_disable_setlocale();
506
507   gtk_init(&argc, &argv);
508   gtk_gl_init(&argc, &argv);
509   gdk_gl_init(&argc, &argv);
510
511   // TODO: Find a better place to call this.
512   gtk_glwidget_create_font();
513
514   if ((ptr = getenv ("Q3R_LIBGL")) != NULL)
515     libgl = ptr;
516
517   for (i = 1; i < argc; i++)
518   {
519     char* param = argv[i];
520
521     if (param[0] == '-' && param[1] == '-')
522     {
523       param += 2;
524
525       if ((strcmp (param, "libgl") == 0) && (i != argc))
526       {
527         libgl = argv[i+1];
528         argv[i] = argv[i+1] = NULL;
529         i++;
530       } else if (strcmp (param, "builddefs") == 0)
531       {
532         g_bBuildList = true;
533         argv[i] = NULL;
534       }
535     }
536   }
537
538   for (i = 1; i < argc; i++)
539   {
540     for (k = i; k < argc; k++)
541       if (argv[k] != NULL)
542         break;
543
544     if (k > i)
545     {
546       k -= i;
547       for (j = i + k; j < argc; j++)
548         argv[j-k] = argv[j];
549       argc -= k;
550     }
551   }
552
553   g_argc = argc;
554   g_argv = argv;
555
556   g_strPluginsDir = "plugins/";
557   g_strModulesDir = "modules/";
558
559 #ifdef _WIN32
560   // get path to the editor
561   char* pBuffer = g_strAppPath.GetBufferSetLength(_MAX_PATH + 1);
562   GetModuleFileName(NULL, pBuffer, _MAX_PATH);
563   pBuffer[g_strAppPath.ReverseFind('\\') + 1] = '\0';
564   QE_ConvertDOSToUnixName(pBuffer, pBuffer);
565   g_strAppPath.ReleaseBuffer();
566
567   g_strBitmapsPath = g_strAppPath;
568   g_strBitmapsPath += "bitmaps/";
569
570   CGameDialog::UpdateNetrun(false); // read the netrun configuration
571
572   if ( CGameDialog::GetNetrun() ) {
573     // we have to find a per-user g_strTempPath
574     // this behaves the same as on Linux
575     g_strTempPath = getenv("USERPROFILE");
576     if (!g_strTempPath.GetLength())
577     {
578       CString msg;
579       msg = "Radiant is configured to run from a network installation.\n";
580       msg += "I couldn't find the environement variable USERPROFILE\n";
581       msg += "I'm going to use C:\\RadiantSettings. Please set USERPROFILE\n";
582       gtk_MessageBox (NULL, msg, "Radiant - Network mode", MB_OK);
583       g_strTempPath = "C:\\";
584     }
585     g_strTempPath += "\\RadiantSettings\\";
586     Q_mkdir(g_strTempPath.GetBuffer(), 0755);
587     g_strTempPath += RADIANT_VERSION;
588     g_strTempPath += "\\";
589     Q_mkdir(g_strTempPath.GetBuffer(), 0755);
590   }
591   else
592   {
593     // use the core path as temp (to save commandlist.txt, and do the .pid files)
594     g_strTempPath = g_strAppPath;
595   }
596
597 #endif
598
599 #if defined (__linux__) || defined (__APPLE__)
600   Str home;
601   home = g_get_home_dir ();
602   AddSlash (home);
603   home += ".radiant/";
604   Q_mkdir (home.GetBuffer (), 0775);
605   home += RADIANT_VERSION;
606   Q_mkdir (home.GetBuffer (), 0775);
607   g_strTempPath = home.GetBuffer ();
608   AddSlash (g_strTempPath);
609
610   loki_initpaths(argv[0]);
611
612   // NOTE: we build g_strAppPath with a '/' (or '\' on WIN32)
613   // it's a general convention in Radiant to have the slash at the end of directories
614   char real[PATH_MAX];
615   realpath (loki_getdatapath(), real);
616   if (real[strlen(real)-1] != '/')
617     strcat(real, "/");
618
619   g_strAppPath = real;
620
621   // radiant is installed in the parent dir of "tools/"
622   // NOTE: this is not very easy for debugging
623   // maybe add options to lookup in several places?
624   // (for now I had to create symlinks)
625   g_strBitmapsPath = g_strAppPath;
626   g_strBitmapsPath += "bitmaps/";
627
628   // we will set this right after the game selection is done
629   g_strGameToolsPath = g_strAppPath;
630
631 #endif
632
633   // init the DTD path
634   g_strDTDPath = g_strAppPath;
635   g_strDTDPath += "dtds/";
636
637   /*!
638   the global prefs loading / game selection dialog might fail for any reason we don't know about
639   we need to catch when it happens, to cleanup the stateful prefs which might be killing it
640   and to turn on console logging for lookup of the problem
641   this is the first part of the two step .pid system
642   */
643   g_pidFile = g_strTempPath.GetBuffer ();
644   g_pidFile += "radiant.pid";
645
646   FILE *pid;
647   pid = fopen( g_pidFile.GetBuffer(), "r" );
648   if ( pid != NULL ) {
649     fclose (pid);
650     CString msg;
651
652     if (remove (g_pidFile.GetBuffer ()) == -1)
653     {
654       msg = "WARNING: Could not delete "; msg += g_pidFile;
655       gtk_MessageBox (NULL, msg, "Radiant", MB_OK | MB_ICONERROR );
656     }
657
658     // in debug, never prompt to clean registry, turn console logging auto after a failed start
659 #if !defined(_DEBUG)
660     msg = "Found the file ";
661     msg += g_pidFile;
662     msg += ".\nThis indicates that Radiant failed during the game selection startup last time it was run.\n"
663            "Choose YES to clean Radiant's registry settings and shut down Radiant.\n"
664            "WARNING: the global prefs will be lost if you choose YES.";
665
666     if (gtk_MessageBox (NULL, msg, "Radiant - Reset global startup?", MB_YESNO | MB_ICONQUESTION) == IDYES)
667     {
668       // remove global prefs and shutdown
669       g_PrefsDlg.mGamesDialog.Reset();
670       // remove the prefs file (like a full reset of the registry)
671       //remove (g_PrefsDlg.m_inipath->str);
672       gtk_MessageBox(NULL, "Removed global settings, choose OK to close Radiant.", "Radiant", MB_OK );
673       _exit(-1);
674     }
675     msg = "Logging console output to ";
676     msg += g_strTempPath;
677     msg += "radiant.log\nRefer to the log if Radiant fails to start again.";
678
679     gtk_MessageBox (NULL, msg, "Radiant - Console Log", MB_OK);
680 #endif
681
682     // set without saving, the class is not in a coherent state yet
683     // just do the value change and call to start logging, CGamesDialog will pickup when relevant
684     g_PrefsDlg.mGamesDialog.m_bLogConsole = true;
685     g_PrefsDlg.mGamesDialog.m_bForceLogConsole = true;
686     Sys_LogFile();
687   }
688
689   // create a primary .pid for global init run
690   pid = fopen( g_pidFile.GetBuffer(), "w" );
691   if ( pid ) {
692           fclose( pid );
693   }
694
695   // a safe check to avoid people running broken installations
696   // (otherwise, they run it, crash it, and blame us for not forcing them hard enough to pay attention while installing)
697   // make something idiot proof and someone will make better idiots, this may be overkill
698   // let's leave it disabled in debug mode in any case
699 #ifndef _DEBUG
700   //#define CHECK_VERSION
701 #endif
702 #ifdef CHECK_VERSION
703   // locate and open RADIANT_MAJOR and RADIANT_MINOR
704   qboolean bVerIsGood = true;
705   Str ver_file_name;
706   ver_file_name = g_strAppPath;
707   ver_file_name += "RADIANT_MAJOR";
708   FILE *ver_file = fopen (ver_file_name.GetBuffer(), "r");
709   if (ver_file)
710   {
711     char buf[10];
712     int chomp;
713     fread(buf, 1, 10, ver_file);
714     // chomp it (the hard way)
715     chomp = 0;
716     while(buf[chomp] >= '0' && buf[chomp] <= '9')
717       chomp++;
718     buf[chomp] = '\0';
719     if (strcmp(buf, RADIANT_MAJOR_VERSION))
720     {
721       Sys_Printf("ERROR: file RADIANT_MAJOR doesn't match ('%s')\n", buf);
722       bVerIsGood = false;
723     }
724   }
725   else
726   {
727     Sys_Printf("ERROR: can't find RADIANT_MAJOR in '%s'\n", ver_file_name.GetBuffer());
728     bVerIsGood = false;
729   }
730   ver_file_name = g_strAppPath;
731   ver_file_name += "RADIANT_MINOR";
732   ver_file = fopen (ver_file_name.GetBuffer(), "r");
733   if (ver_file)
734   {
735     char buf[10];
736     int chomp;
737     fread(buf, 1, 10, ver_file);
738     // chomp it (the hard way)
739     chomp = 0;
740     while(buf[chomp] >= '0' && buf[chomp] <= '9')
741       chomp++;
742     buf[chomp] = '\0';
743     if (strcmp(buf, RADIANT_MINOR_VERSION))
744     {
745       Sys_Printf("ERROR: file RADIANT_MINOR doesn't match ('%s')\n", buf);
746       bVerIsGood = false;
747     }
748   }
749   else
750   {
751     Sys_Printf("ERROR: can't find RADIANT_MINOR in '%s'\n", ver_file_name.GetBuffer());
752     bVerIsGood = false;
753   }
754   if (!bVerIsGood)
755   {
756     CString msg;
757     msg = "This editor binary (" RADIANT_VERSION ") doesn't match what the latest setup has configured in this directory\n";
758     msg += "Make sure you run the right/latest editor binary you installed\n";
759     msg += g_strAppPath; msg += "\n";
760     msg += "Check http://www.qeradiant.com/faq/index.cgi?file=219 for more information";
761     gtk_MessageBox(NULL, msg.GetBuffer(), "Radiant", MB_OK, "http://www.qeradiant.com/faq/index.cgi?file=219");
762     _exit(-1);
763   }
764 #endif
765
766   g_qeglobals.disable_ini = false;
767   g_PrefsDlg.Init();
768
769   // close the primary
770   if ( remove( g_pidFile.GetBuffer () ) == -1 ) {
771     CString msg;
772     msg = "WARNING: Could not delete "; msg += g_pidGameFile;
773     gtk_MessageBox (NULL, msg, "Radiant", MB_OK | MB_ICONERROR );
774   }
775
776   /*!
777   now the secondary game dependant .pid file
778   */
779   g_pidGameFile = g_PrefsDlg.m_rc_path->str;
780   g_pidGameFile += "radiant-game.pid";
781
782   pid = fopen (g_pidGameFile.GetBuffer(), "r");
783   if (pid != NULL)
784   {
785     fclose (pid);
786     CString msg;
787     if (remove (g_pidGameFile.GetBuffer ()) == -1)
788     {
789       msg = "WARNING: Could not delete "; msg += g_pidGameFile;
790       gtk_MessageBox (NULL, msg, "Radiant", MB_OK | MB_ICONERROR );
791     }
792
793     msg = "Found the file ";
794     msg += g_pidGameFile;
795     msg += ".\nThis indicates that Radiant failed to load the last time it was run.\n"
796            "Choose YES to clean Radiant's registry settings and shut down Radiant.\n"
797            "WARNING: preferences will be lost if you choose YES.";
798
799     // in debug, never prompt to clean registry, turn console logging auto after a failed start
800 #if !defined(_DEBUG)
801     //bleh
802     if (gtk_MessageBox (NULL, msg, "Radiant - Clean Registry?", MB_YESNO | MB_ICONQUESTION) == IDYES)
803     {
804       // remove the game prefs files
805       remove (g_PrefsDlg.m_inipath->str);
806       char buf[PATH_MAX];
807       sprintf(buf, "%sSavedInfo.bin", g_PrefsDlg.m_rc_path->str);
808       remove(buf);
809       // remove the global pref too
810       g_PrefsDlg.mGamesDialog.Reset();
811       gtk_MessageBox(NULL, "Cleaned registry settings, choose OK to close Radiant.\nThe next time Radiant runs it will use default settings.", "Radiant", MB_OK );
812       _exit(-1);
813     }
814     msg = "Logging console output to ";
815     msg += g_strTempPath;
816     msg += "radiant.log\nRefer to the log if Radiant fails to start again.";
817
818     gtk_MessageBox (NULL, msg, "Radiant - Console Log", MB_OK);
819 #endif
820
821     // force console logging on! (will go in prefs too)
822     g_PrefsDlg.mGamesDialog.m_bLogConsole = true;
823     g_PrefsDlg.mGamesDialog.SavePrefs();
824     Sys_LogFile();
825
826     g_PrefsDlg.LoadPrefs();
827
828   } else
829   {
830     // create one, will remove right after entering message loop
831     pid = fopen (g_pidGameFile.GetBuffer(), "w");
832     if (pid)
833       fclose (pid);
834
835     g_PrefsDlg.LoadPrefs();
836
837 #ifndef _DEBUG // I can't be arsed about that prompt in debug mode
838     // if console logging is on in the prefs, warn about performance hit
839     if (g_PrefsDlg.mGamesDialog.m_bLogConsole)
840     {
841       if (gtk_MessageBox (NULL, "Preferences indicate that console logging is on. This affects performance.\n"
842                           "Turn it off?", "Radiant", MB_YESNO | MB_ICONQUESTION) == IDYES)
843       {
844         g_PrefsDlg.mGamesDialog.m_bLogConsole = false;
845         g_PrefsDlg.mGamesDialog.SavePrefs();
846       }
847     }
848 #endif
849     // toggle console logging if necessary
850     Sys_LogFile();
851   }
852
853   // we should search in g_strTempPath, then move over to look at g_strAppPath?
854 #ifdef _WIN32
855   // fine tune the look of the app using a gtk rc file
856   // we try to load an RC file placed in the application directory
857   // build the full path
858   Str sRCFile;
859   sRCFile = g_strAppPath;
860   sRCFile += "radiantgtkrc";
861   // we load that file with g_new in gtk_rc_parse_file (gtkrc.c), change the '/' into '\\'
862   pBuffer = (char *)sRCFile.GetBuffer();
863   for (i=0; i<sRCFile.GetLength(); i++)
864   {
865     if (pBuffer[i]=='/')
866     {
867       pBuffer[i] = '\\';
868     }
869   }
870   // check the file exists
871   if (access(sRCFile.GetBuffer(), R_OK) != 0)
872     Sys_Printf("RC file %s not found\n", sRCFile.GetBuffer());
873   else
874   {
875     Sys_Printf ("Attemping to load RC file %s\n", sRCFile.GetBuffer());
876     gtk_rc_parse (sRCFile.GetBuffer());
877   }
878 #endif
879
880 #ifndef SKIP_SPLASH
881   create_splash();
882 #endif
883
884   if (!QGL_Init(libgl, ""))
885   {
886     Sys_FPrintf (SYS_ERR, "Failed to load OpenGL libraries\n");
887     _exit (1);
888     return 1;
889   }
890
891 #if defined (__linux__) || defined (__APPLE__)
892   if ((qglXQueryExtension == NULL) || (qglXQueryExtension(GDK_DISPLAY(),NULL,NULL) != True))
893   {
894     Sys_FPrintf (SYS_ERR, "glXQueryExtension failed\n");
895     _exit (1);
896     return 1;
897   }
898 #endif
899
900   // redirect Gtk warnings to the console
901   g_log_set_handler( "Gdk", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
902                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG), error_redirect, NULL);
903   g_log_set_handler( "Gtk", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
904                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG), error_redirect, NULL);
905
906   // spog - creates new filters list for the first time
907   g_qeglobals.d_savedinfo.filters = NULL;
908   g_qeglobals.d_savedinfo.filters = FilterUpdate(g_qeglobals.d_savedinfo.filters);
909
910   g_pParentWnd = new MainFrame();
911
912   if ( g_PrefsDlg.m_bLoadLastMap && g_PrefsDlg.m_strLastMap.GetLength() > 0 ) {
913           Map_LoadFile(g_PrefsDlg.m_strLastMap.GetBuffer());
914   } else {
915           Map_New();
916   }
917
918   // load up shaders now that we have the map loaded
919   // eviltypeguy
920   Texture_ShowStartupShaders();
921
922 #ifndef SKIP_SPLASH
923   gdk_window_raise(splash_screen->window);
924   gtk_window_set_transient_for( GTK_WINDOW( splash_screen ), GTK_WINDOW( g_pParentWnd->m_pWidget ) );
925   gtk_timeout_add( 1000, try_destroy_splash, NULL );
926 #endif
927
928   g_pParentWnd->GetSynapseServer().DumpActiveClients();
929
930   //++timo: temporary debug
931   g_pParentWnd->DoWatchBSP();
932
933   gtk_main();
934
935   // close the log file if any
936   // NOTE: don't save prefs past this point!
937   g_PrefsDlg.mGamesDialog.m_bLogConsole = false;
938   // set the console window to NULL to avoid Sys_Printf crashing
939   g_qeglobals_gui.d_edit = NULL;
940   Sys_LogFile();
941
942   // NOTE TTimo not sure what this _exit(0) call is worth
943   //   restricting it to linux build
944 #ifdef __linux__
945   _exit( 0 );
946 #endif
947   return 0;
948 }
949
950 // ydnar: quick and dirty fix, just make the buffer bigger
951 #define BIG_PATH_MAX    4096
952
953 // TTimo: decompose the BSP command into several steps so we can monitor them eventually
954 void QE_ExpandBspString (char *bspaction, GPtrArray *out_array, char *mapname)
955 {
956   const char  *in;
957   char  *out;
958   char  src[BIG_PATH_MAX];
959   char  rsh[BIG_PATH_MAX];
960   char  base[BIG_PATH_MAX];
961
962   strcpy(src, mapname);
963   strlwr(src);
964   in = strstr(src, "maps/");
965   if (!in)
966   {
967     in = strstr(src, "maps/");
968   }
969   if (in)
970   {
971     in += 5;
972     strcpy(base, in);
973     out = base;
974     while (*out)
975     {
976       if (*out == '\\')
977       {
978         *out = '/';
979       }
980       out++;
981     }
982   } else
983   {
984     ExtractFileName (mapname, base);
985   }
986
987   // this important step alters the map name to add fs_game
988   // NOTE: it used to add fs_basepath too
989   // the fs_basepath addition moved to being in the project file during the bug fixing rush
990   // but it may not have been the right thing to do
991
992   // HACK: halflife compiler tools don't support -fs_game
993   // HACK: neither does JKII/SoF2/ etc..
994   // do we use & have fs_game?
995
996   if (g_pGameDescription->mGameFile != "hl.game" &&
997       *ValueForKey(g_qeglobals.d_project_entity,"gamename") != '\0')
998     {
999       // set with fs_game
1000       sprintf(src, "-fs_game %s \"%s\"", ValueForKey(g_qeglobals.d_project_entity,"gamename"), mapname);
1001     }
1002     else
1003     {
1004       sprintf(src, "\"%s\"", mapname);
1005   }
1006
1007   rsh[0] = 0;
1008
1009   QE_ConvertDOSToUnixName(src, src);
1010
1011   // initialise the first step
1012   out = new char[BIG_PATH_MAX]; //% PATH_MAX
1013   g_ptr_array_add( out_array, out );
1014
1015   in = ValueForKey( g_qeglobals.d_project_entity, bspaction );
1016   while (*in)
1017   {
1018     if (in[0] == '!')
1019     {
1020       strcpy (out, rsh);
1021       out += strlen(rsh);
1022       in++;
1023       continue;
1024     }
1025     if (in[0] == '#')
1026     {
1027       char tmp[2048];
1028       // we process these only if monitoring
1029       if (g_PrefsDlg.m_bWatchBSP)
1030       {
1031         // -connect global option (the only global option so far anyway)
1032         strcpy (tmp, " -connect 127.0.0.1:39000 ");
1033         strcpy (out, tmp);
1034         out += strlen(tmp);
1035       }
1036       in++;
1037       continue;
1038     }
1039     if (in[0] == '$')
1040     {
1041       // $ expansion
1042       strcpy (out, src);
1043       out += strlen(src);
1044       in++;
1045       continue;
1046     }
1047     if (in[0] == '@')
1048     {
1049       *out++ = '"';
1050       in++;
1051       continue;
1052     }
1053     if (in[0] == '&')
1054       if (in[1] == '&')
1055       {
1056         // start a new step
1057         *out = 0;
1058         in = in + 2;
1059         out = new char[BIG_PATH_MAX];   //% PATH_MAX
1060         g_ptr_array_add( out_array, out );
1061       }
1062     *out++ = *in++;
1063   }
1064   *out = 0;
1065 }
1066
1067 void FindReplace(CString& strContents, const char* pTag, const char* pValue)
1068 {
1069   if (strcmp(pTag, pValue) == 0)
1070     return;
1071   for (int nPos = strContents.Find(pTag); nPos >= 0; nPos = strContents.Find(pTag))
1072   {
1073     int nRightLen = strContents.GetLength() - strlen(pTag) - nPos;
1074     CString strLeft = strContents.Left(nPos);
1075     CString strRight = strContents.Right(nRightLen);
1076     strLeft += pValue;
1077     strLeft += strRight;
1078     strContents = strLeft;
1079   }
1080 }
1081
1082 // save the map, deals with regioning
1083 void SaveWithRegion(char *name)
1084 {
1085   strcpy (name, currentmap);
1086   if (region_active)
1087   {
1088     // temporary cut the region to save regular map
1089     region_active = false;
1090     Map_SaveFile (name, false);
1091     region_active = true;
1092     StripExtension (name);
1093     strcat (name, ".reg");
1094   }
1095
1096   Map_SaveFile (name, region_active);
1097 }
1098
1099 void RunBsp (char *command)
1100 {
1101   GPtrArray *sys;
1102   char  batpath[BIG_PATH_MAX];  //% PATH_MAX
1103   char  temppath[BIG_PATH_MAX]; //% PATH_MAX
1104   char  name[BIG_PATH_MAX];             //% PATH_MAX
1105   char  cWork[BIG_PATH_MAX];    //% PATH_MAX
1106   FILE  *hFile;
1107   unsigned int   i;
1108
1109   SetInspectorMode(W_CONSOLE);
1110
1111   strcpy (temppath, g_strTempPath.GetBuffer ());
1112
1113   SaveWithRegion(name);
1114
1115   const char *rsh = ValueForKey(g_qeglobals.d_project_entity, "rshcmd");
1116   if (rsh == NULL)
1117   {
1118     CString strPath, strFile;
1119
1120     ExtractPath_and_Filename(name, strPath, strFile);
1121     AddSlash(strPath);
1122     strncpy(cWork, strPath, 1024);
1123     strcat(cWork, strFile);
1124   } else
1125   {
1126     strcpy(cWork, name);
1127   }
1128
1129   // get the array ready
1130   //++timo TODO: free the array, free the strings ourselves with delete[]
1131   sys = g_ptr_array_new();
1132
1133   QE_ExpandBspString (command, sys, cWork);
1134
1135   if (g_PrefsDlg.m_bWatchBSP)
1136   {
1137     // grab the file name for engine running
1138     char *bspname = new char[1024];
1139     ExtractFileName( currentmap, bspname );
1140     StripExtension( bspname );
1141     g_pParentWnd->GetWatchBSP()->DoMonitoringLoop( sys, bspname );
1142   } else
1143   {
1144     // write all the steps in a single BAT / .sh file and run it, don't bother monitoring it
1145     CString strSys;
1146     for (i=0; i < sys->len; i++ )
1147     {
1148       strSys += (char *)g_ptr_array_index( sys, i);
1149 #ifdef _WIN32  // write temp\junk.txt in win32... NOTE: stops output to shell prompt window
1150       if (i==0)
1151         strSys += " >";
1152       else
1153         strSys += " >>";
1154       strSys += "\"";
1155       strSys += temppath;
1156       strSys += "junk.txt\"";
1157 #endif
1158       strSys += "\n";
1159     };
1160
1161 #if defined (__linux__) || defined (__APPLE__)
1162
1163     // write qe3bsp.sh
1164     sprintf (batpath, "%sqe3bsp.sh", temppath);
1165     Sys_Printf("Writing the compile script to '%s'\n", batpath);
1166     Sys_Printf("The build output will be saved in '%sjunk.txt'\n", temppath);
1167     hFile = fopen(batpath, "w");
1168     if (!hFile)
1169       Error ("Can't write to %s", batpath);
1170     fprintf (hFile, "#!/bin/sh \n\n");
1171     fprintf (hFile, strSys.GetBuffer());
1172     fclose (hFile);
1173     chmod (batpath, 0744);
1174 #endif
1175
1176 #ifdef _WIN32
1177     sprintf (batpath, "%sqe3bsp.bat", temppath);
1178     Sys_Printf("Writing the compile script to '%s'\n", batpath);
1179     Sys_Printf("The build output will be saved in '%sjunk.txt'\n", temppath);
1180     hFile = fopen(batpath, "w");
1181     if (!hFile)
1182       Error ("Can't write to %s", batpath);
1183     fprintf (hFile, strSys.GetBuffer());
1184     fclose (hFile);
1185 #endif
1186
1187     Pointfile_Delete ();
1188
1189 #if defined (__linux__) || defined (__APPLE__)
1190
1191     pid_t pid;
1192
1193     pid = fork ();
1194     switch (pid)
1195     {
1196     case -1:
1197       Error ("CreateProcess failed");
1198       break;
1199     case 0:
1200       execlp (batpath, batpath, NULL);
1201       printf ("execlp error !");
1202       _exit (0);
1203       break;
1204     default:
1205       break;
1206     }
1207 #endif
1208
1209 #ifdef _WIN32
1210     Sys_Printf ("Running bsp command...\n");
1211     Sys_Printf ("\n%s\n", strSys.GetBuffer());
1212
1213     WinExec( batpath, SW_SHOWNORMAL );
1214 #endif
1215   }
1216 #ifdef _DEBUG
1217   // yeah, do it .. but not now right before 1.1-TA-beta release
1218   Sys_Printf("TODO: erase GPtrArray\n");
1219 #endif
1220 }
1221
1222 #if 0
1223
1224 #ifdef _WIN32
1225
1226 int WINAPI QEW_SetupPixelFormat(HDC hDC, qboolean zbuffer )
1227 {
1228   static PIXELFORMATDESCRIPTOR pfd = {
1229     sizeof(PIXELFORMATDESCRIPTOR),  // size of this pfd
1230     1,                              // version number
1231     PFD_DRAW_TO_WINDOW |            // support window
1232     PFD_SUPPORT_OPENGL |            // support OpenGL
1233     PFD_DOUBLEBUFFER,               // double buffered
1234     PFD_TYPE_RGBA,                  // RGBA type
1235     24,                             // 24-bit color depth
1236     0, 0, 0, 0, 0, 0,               // color bits ignored
1237     0,                              // no alpha buffer
1238     0,                              // shift bit ignored
1239     0,                              // no accumulation buffer
1240     0, 0, 0, 0,                     // accum bits ignored
1241     32,                             // depth bits
1242     0,                              // no stencil buffer
1243     0,                              // no auxiliary buffer
1244     PFD_MAIN_PLANE,                 // main layer
1245     0,                              // reserved
1246     0, 0, 0                         // layer masks ignored
1247   };                              //
1248   int pixelformat = 0;
1249
1250   zbuffer = true;
1251   if ( !zbuffer )
1252     pfd.cDepthBits = 0;
1253
1254   if ( (pixelformat = ChoosePixelFormat(hDC, &pfd)) == 0 )
1255   {
1256     LPVOID lpMsgBuf;
1257     FormatMessage(
1258                  FORMAT_MESSAGE_ALLOCATE_BUFFER |
1259                  FORMAT_MESSAGE_FROM_SYSTEM |
1260                  FORMAT_MESSAGE_IGNORE_INSERTS,
1261                  NULL,
1262                  GetLastError(),
1263                  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
1264                  (LPTSTR) &lpMsgBuf,
1265                  0,
1266                  NULL
1267                  );
1268     Sys_FPrintf (SYS_WRN, "GetLastError: %s", lpMsgBuf);
1269     Error ("ChoosePixelFormat failed");
1270   }
1271
1272   if (!SetPixelFormat(hDC, pixelformat, &pfd))
1273     Error ("SetPixelFormat failed");
1274
1275   return pixelformat;
1276 }
1277
1278 #endif
1279
1280 #endif