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