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