]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/main.cpp
93de25c9165056fa13c979ea71fe950f2552ca3b
[xonotic/netradiant.git] / radiant / main.cpp
1 /*
2    Copyright (C) 1999-2006 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 /*! \mainpage GtkRadiant Documentation Index
23
24    \section intro_sec Introduction
25
26    This documentation is generated from comments in the source code.
27
28    \section links_sec Useful Links
29
30    \link include/itextstream.h include/itextstream.h \endlink - Global output and error message streams, similar to std::cout and std::cerr. \n
31
32    FileInputStream - similar to std::ifstream (binary mode) \n
33    FileOutputStream - similar to std::ofstream (binary mode) \n
34    TextFileInputStream - similar to std::ifstream (text mode) \n
35    TextFileOutputStream - similar to std::ofstream (text mode) \n
36    StringOutputStream - similar to std::stringstream \n
37
38    \link string/string.h string/string.h \endlink - C-style string comparison and memory management. \n
39    \link os/path.h os/path.h \endlink - Path manipulation for radiant's standard path format \n
40    \link os/file.h os/file.h \endlink - OS file-system access. \n
41
42    ::CopiedString - automatic string memory management \n
43    Array - automatic array memory management \n
44    HashTable - generic hashtable, similar to std::hash_map \n
45
46    \link math/vector.h math/vector.h \endlink - Vectors \n
47    \link math/matrix.h math/matrix.h \endlink - Matrices \n
48    \link math/quaternion.h math/quaternion.h \endlink - Quaternions \n
49    \link math/plane.h math/plane.h \endlink - Planes \n
50    \link math/aabb.h math/aabb.h \endlink - AABBs \n
51
52    Callback MemberCaller FunctionCaller - callbacks similar to using boost::function with boost::bind \n
53    SmartPointer SmartReference - smart-pointer and smart-reference similar to Loki's SmartPtr \n
54
55    \link generic/bitfield.h generic/bitfield.h \endlink - Type-safe bitfield \n
56    \link generic/enumeration.h generic/enumeration.h \endlink - Type-safe enumeration \n
57
58    DefaultAllocator - Memory allocation using new/delete, compliant with std::allocator interface \n
59
60    \link debugging/debugging.h debugging/debugging.h \endlink - Debugging macros \n
61
62  */
63
64 #include "main.h"
65
66 #include "version.h"
67
68 #include "debugging/debugging.h"
69
70 #include "iundo.h"
71
72 #include "uilib/uilib.h"
73
74 #include "cmdlib.h"
75 #include "os/file.h"
76 #include "os/path.h"
77 #include "stream/stringstream.h"
78 #include "stream/textfilestream.h"
79
80 #include "gtkutil/messagebox.h"
81 #include "gtkutil/image.h"
82 #include "console.h"
83 #include "texwindow.h"
84 #include "map.h"
85 #include "mainframe.h"
86 #include "commands.h"
87 #include "preferences.h"
88 #include "environment.h"
89 #include "referencecache.h"
90 #include "stacktrace.h"
91
92 #ifdef WIN32
93 #include <windows.h>
94 #endif
95
96 void show_splash();
97 void hide_splash();
98
99 void error_redirect( const gchar *domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data ){
100         gboolean in_recursion;
101         gboolean is_fatal;
102         char buf[256];
103
104         in_recursion = ( log_level & G_LOG_FLAG_RECURSION ) != 0;
105         is_fatal = ( log_level & G_LOG_FLAG_FATAL ) != 0;
106         log_level = (GLogLevelFlags) ( log_level & G_LOG_LEVEL_MASK );
107
108         if ( !message ) {
109                 message = "(0) message";
110         }
111
112         if ( domain ) {
113                 strcpy( buf, domain );
114         }
115         else{
116                 strcpy( buf, "**" );
117         }
118         strcat( buf, "-" );
119
120         switch ( log_level )
121         {
122         case G_LOG_LEVEL_ERROR:
123                 if ( in_recursion ) {
124                         strcat( buf, "ERROR (recursed) **: " );
125                 }
126                 else{
127                         strcat( buf, "ERROR **: " );
128                 }
129                 break;
130         case G_LOG_LEVEL_CRITICAL:
131                 if ( in_recursion ) {
132                         strcat( buf, "CRITICAL (recursed) **: " );
133                 }
134                 else{
135                         strcat( buf, "CRITICAL **: " );
136                 }
137                 break;
138         case G_LOG_LEVEL_WARNING:
139                 if ( in_recursion ) {
140                         strcat( buf, "WARNING (recursed) **: " );
141                 }
142                 else{
143                         strcat( buf, "WARNING **: " );
144                 }
145                 break;
146         case G_LOG_LEVEL_MESSAGE:
147                 if ( in_recursion ) {
148                         strcat( buf, "Message (recursed): " );
149                 }
150                 else{
151                         strcat( buf, "Message: " );
152                 }
153                 break;
154         case G_LOG_LEVEL_INFO:
155                 if ( in_recursion ) {
156                         strcat( buf, "INFO (recursed): " );
157                 }
158                 else{
159                         strcat( buf, "INFO: " );
160                 }
161                 break;
162         case G_LOG_LEVEL_DEBUG:
163                 if ( in_recursion ) {
164                         strcat( buf, "DEBUG (recursed): " );
165                 }
166                 else{
167                         strcat( buf, "DEBUG: " );
168                 }
169                 break;
170         default:
171                 /* we are used for a log level that is not defined by GLib itself,
172                  * try to make the best out of it.
173                  */
174                 if ( in_recursion ) {
175                         strcat( buf, "LOG (recursed:" );
176                 }
177                 else{
178                         strcat( buf, "LOG (" );
179                 }
180                 if ( log_level ) {
181                         gchar string[] = "0x00): ";
182                         gchar *p = string + 2;
183                         guint i;
184
185                         i = g_bit_nth_msf( log_level, -1 );
186                         *p = i >> 4;
187                         p++;
188                         *p = '0' + ( i & 0xf );
189                         if ( *p > '9' ) {
190                                 *p += 'A' - '9' - 1;
191                         }
192
193                         strcat( buf, string );
194                 }
195                 else{
196                         strcat( buf, "): " );
197                 }
198         }
199
200         strcat( buf, message );
201         if ( is_fatal ) {
202                 strcat( buf, "\naborting...\n" );
203         }
204         else{
205                 strcat( buf, "\n" );
206         }
207
208         // spam it...
209         globalErrorStream() << buf << "\n";
210
211         if (is_fatal) {
212             ERROR_MESSAGE( "GTK+ error: " << buf );
213     }
214 }
215
216 #if defined ( _DEBUG ) && defined ( WIN32 ) && defined ( _MSC_VER )
217 #include "crtdbg.h"
218 #endif
219
220 void crt_init(){
221 #if defined ( _DEBUG ) && defined ( WIN32 ) && defined ( _MSC_VER )
222         _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
223 #endif
224 }
225
226 class Lock
227 {
228 bool m_locked;
229 public:
230 Lock() : m_locked( false ){
231 }
232 void lock(){
233         m_locked = true;
234 }
235 void unlock(){
236         m_locked = false;
237 }
238 bool locked() const {
239         return m_locked;
240 }
241 };
242
243 class ScopedLock
244 {
245 Lock& m_lock;
246 public:
247 ScopedLock( Lock& lock ) : m_lock( lock ){
248         m_lock.lock();
249 }
250 ~ScopedLock(){
251         m_lock.unlock();
252 }
253 };
254
255 class LineLimitedTextOutputStream : public TextOutputStream
256 {
257 TextOutputStream& outputStream;
258 std::size_t count;
259 public:
260 LineLimitedTextOutputStream( TextOutputStream& outputStream, std::size_t count )
261         : outputStream( outputStream ), count( count ){
262 }
263 std::size_t write( const char* buffer, std::size_t length ){
264         if ( count != 0 ) {
265                 const char* p = buffer;
266                 const char* end = buffer + length;
267                 for (;; )
268                 {
269                         p = std::find( p, end, '\n' );
270                         if ( p == end ) {
271                                 break;
272                         }
273                         ++p;
274                         if ( --count == 0 ) {
275                                 length = p - buffer;
276                                 break;
277                         }
278                 }
279                 outputStream.write( buffer, length );
280         }
281         return length;
282 }
283 };
284
285 class PopupDebugMessageHandler : public DebugMessageHandler
286 {
287 StringOutputStream m_buffer;
288 Lock m_lock;
289 public:
290 TextOutputStream& getOutputStream(){
291         if ( !m_lock.locked() ) {
292                 return m_buffer;
293         }
294         return globalErrorStream();
295 }
296 bool handleMessage(){
297         getOutputStream() << "----------------\n";
298         LineLimitedTextOutputStream outputStream( getOutputStream(), 24 );
299         write_stack_trace( outputStream );
300         getOutputStream() << "----------------\n";
301         globalErrorStream() << m_buffer.c_str();
302         if ( !m_lock.locked() ) {
303                 ScopedLock lock( m_lock );
304 #if defined _DEBUG
305                 m_buffer << "Break into the debugger?\n";
306                 bool handled = ui::root.alert( m_buffer.c_str(), "Radiant - Runtime Error", ui::alert_type::YESNO, ui::alert_icon::Error ) == ui::alert_response::NO;
307                 m_buffer.clear();
308                 return handled;
309 #else
310                 m_buffer << "Please report this error to the developers\n";
311                 ui::root.alert( m_buffer.c_str(), "Radiant - Runtime Error", ui::alert_type::OK, ui::alert_icon::Error );
312                 m_buffer.clear();
313 #endif
314         }
315         return true;
316 }
317 };
318
319 typedef Static<PopupDebugMessageHandler> GlobalPopupDebugMessageHandler;
320
321 void streams_init(){
322         GlobalErrorStream::instance().setOutputStream( getSysPrintErrorStream() );
323         GlobalOutputStream::instance().setOutputStream( getSysPrintOutputStream() );
324 }
325
326 void paths_init(){
327         g_strSettingsPath = environment_get_home_path();
328
329         Q_mkdir( g_strSettingsPath.c_str() );
330
331         g_strAppPath = environment_get_app_path();
332
333         // radiant is installed in the parent dir of "tools/"
334         // NOTE: this is not very easy for debugging
335         // maybe add options to lookup in several places?
336         // (for now I had to create symlinks)
337         {
338                 StringOutputStream path( 256 );
339                 path << g_strAppPath.c_str() << "bitmaps/";
340                 BitmapsPath_set( path.c_str() );
341         }
342
343         // we will set this right after the game selection is done
344         g_strGameToolsPath = g_strAppPath;
345 }
346
347 bool check_version_file( const char* filename, const char* version ){
348         TextFileInputStream file( filename );
349         if ( !file.failed() ) {
350                 char buf[10];
351                 buf[file.read( buf, 9 )] = '\0';
352
353                 // chomp it (the hard way)
354                 int chomp = 0;
355                 while ( buf[chomp] >= '0' && buf[chomp] <= '9' )
356                         chomp++;
357                 buf[chomp] = '\0';
358
359                 return string_equal( buf, version );
360         }
361         return false;
362 }
363
364 bool check_version(){
365         // a safe check to avoid people running broken installations
366         // (otherwise, they run it, crash it, and blame us for not forcing them hard enough to pay attention while installing)
367         // make something idiot proof and someone will make better idiots, this may be overkill
368         // let's leave it disabled in debug mode in any case
369         // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=431
370 #ifndef _DEBUG
371 #define CHECK_VERSION
372 #endif
373 #ifdef CHECK_VERSION
374         // locate and open RADIANT_MAJOR and RADIANT_MINOR
375         bool bVerIsGood = true;
376         {
377                 StringOutputStream ver_file_name( 256 );
378                 ver_file_name << AppPath_get() << "RADIANT_MAJOR";
379                 bVerIsGood = check_version_file( ver_file_name.c_str(), RADIANT_MAJOR_VERSION );
380         }
381         {
382                 StringOutputStream ver_file_name( 256 );
383                 ver_file_name << AppPath_get() << "RADIANT_MINOR";
384                 bVerIsGood = check_version_file( ver_file_name.c_str(), RADIANT_MINOR_VERSION );
385         }
386
387         if ( !bVerIsGood ) {
388                 StringOutputStream msg( 256 );
389                 msg << "This editor binary (" RADIANT_VERSION ") doesn't match what the latest setup has configured in this directory\n"
390                                 "Make sure you run the right/latest editor binary you installed\n"
391                         << AppPath_get();
392                 ui::root.alert( msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Default);
393         }
394         return bVerIsGood;
395 #else
396         return true;
397 #endif
398 }
399
400 void create_global_pid(){
401         /*!
402            the global prefs loading / game selection dialog might fail for any reason we don't know about
403            we need to catch when it happens, to cleanup the stateful prefs which might be killing it
404            and to turn on console logging for lookup of the problem
405            this is the first part of the two step .pid system
406            http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
407          */
408         StringOutputStream g_pidFile( 256 ); ///< the global .pid file (only for global part of the startup)
409
410         g_pidFile << SettingsPath_get() << "radiant.pid";
411
412         FILE *pid;
413         pid = fopen( g_pidFile.c_str(), "r" );
414         if ( pid != 0 ) {
415                 fclose( pid );
416
417                 if ( remove( g_pidFile.c_str() ) == -1 ) {
418                         StringOutputStream msg( 256 );
419                         msg << "WARNING: Could not delete " << g_pidFile.c_str();
420                         ui::root.alert( msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error );
421                 }
422
423                 // in debug, never prompt to clean registry, turn console logging auto after a failed start
424 #if !defined( _DEBUG )
425                 StringOutputStream msg( 256 );
426                 msg << "Radiant failed to start properly the last time it was run.\n"
427                            "The failure may be related to current global preferences.\n"
428                            "Do you want to reset global preferences to defaults?";
429
430                 if ( ui::root.alert( msg.c_str(), "Radiant - Startup Failure", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::YES ) {
431                         g_GamesDialog.Reset();
432                 }
433
434                 msg.clear();
435                 msg << "Logging console output to " << SettingsPath_get() << "radiant.log\nRefer to the log if Radiant fails to start again.";
436
437                 ui::root.alert( msg.c_str(), "Radiant - Console Log", ui::alert_type::OK );
438 #endif
439
440                 // set without saving, the class is not in a coherent state yet
441                 // just do the value change and call to start logging, CGamesDialog will pickup when relevant
442                 g_GamesDialog.m_bForceLogConsole = true;
443                 Sys_LogFile( true );
444         }
445
446         // create a primary .pid for global init run
447         pid = fopen( g_pidFile.c_str(), "w" );
448         if ( pid ) {
449                 fclose( pid );
450         }
451 }
452
453 void remove_global_pid(){
454         StringOutputStream g_pidFile( 256 );
455         g_pidFile << SettingsPath_get() << "radiant.pid";
456
457         // close the primary
458         if ( remove( g_pidFile.c_str() ) == -1 ) {
459                 StringOutputStream msg( 256 );
460                 msg << "WARNING: Could not delete " << g_pidFile.c_str();
461                 ui::root.alert( msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error );
462         }
463 }
464
465 /*!
466    now the secondary game dependant .pid file
467    http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
468  */
469 void create_local_pid(){
470         StringOutputStream g_pidGameFile( 256 ); ///< the game-specific .pid file
471         g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
472
473         FILE *pid = fopen( g_pidGameFile.c_str(), "r" );
474         if ( pid != 0 ) {
475                 fclose( pid );
476                 if ( remove( g_pidGameFile.c_str() ) == -1 ) {
477                         StringOutputStream msg;
478                         msg << "WARNING: Could not delete " << g_pidGameFile.c_str();
479                         ui::root.alert( msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error );
480                 }
481
482                 // in debug, never prompt to clean registry, turn console logging auto after a failed start
483 #if !defined( _DEBUG )
484                 StringOutputStream msg;
485                 msg << "Radiant failed to start properly the last time it was run.\n"
486                            "The failure may be caused by current preferences.\n"
487                            "Do you want to reset all preferences to defaults?";
488
489                 if ( ui::root.alert( msg.c_str(), "Radiant - Startup Failure", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::YES ) {
490                         Preferences_Reset();
491                 }
492
493                 msg.clear();
494                 msg << "Logging console output to " << SettingsPath_get() << "radiant.log\nRefer to the log if Radiant fails to start again.";
495
496                 ui::root.alert( msg.c_str(), "Radiant - Console Log", ui::alert_type::OK );
497 #endif
498
499                 // force console logging on! (will go in prefs too)
500                 g_GamesDialog.m_bForceLogConsole = true;
501                 Sys_LogFile( true );
502         }
503         else
504         {
505                 // create one, will remove right after entering message loop
506                 pid = fopen( g_pidGameFile.c_str(), "w" );
507                 if ( pid ) {
508                         fclose( pid );
509                 }
510         }
511 }
512
513
514 /*!
515    now the secondary game dependant .pid file
516    http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
517  */
518 void remove_local_pid(){
519         StringOutputStream g_pidGameFile( 256 );
520         g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
521         remove( g_pidGameFile.c_str() );
522 }
523
524 void user_shortcuts_init(){
525         StringOutputStream path( 256 );
526         path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
527         LoadCommandMap( path.c_str() );
528         SaveCommandMap( path.c_str() );
529 }
530
531 void user_shortcuts_save(){
532         StringOutputStream path( 256 );
533         path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
534         SaveCommandMap( path.c_str() );
535 }
536
537 int main( int argc, char* argv[] ){
538         crt_init();
539
540         streams_init();
541
542 #ifdef WIN32
543         HMODULE lib;
544         lib = LoadLibrary( "dwmapi.dll" );
545         if ( lib != 0 ) {
546                 void ( WINAPI *qDwmEnableComposition )( bool bEnable ) = ( void (WINAPI *) ( bool bEnable ) )GetProcAddress( lib, "DwmEnableComposition" );
547                 if ( qDwmEnableComposition ) {
548                         qDwmEnableComposition( FALSE );
549                 }
550                 FreeLibrary( lib );
551         }
552 #endif
553
554         const char* mapname = NULL;
555     char const *error = NULL;
556         if ( !ui::init( &argc, &argv, "<filename.map>", &error) ) {
557                 g_print( "%s\n", error );
558                 return -1;
559         }
560
561         // Gtk already removed parsed `--options`
562         if (argc == 2) {
563                 if ( strlen( argv[1] ) > 1 ) {
564                         if ( g_str_has_suffix( argv[1], ".map" ) ) {
565                                 if ( g_path_is_absolute( argv[1] ) ) {
566                                         mapname = argv[1];
567                                 }
568                                 else {
569                                         mapname = g_build_filename( g_get_current_dir(), argv[1], NULL );
570                                 }
571                         }
572                         else {
573                                 g_print( "bad file name, will not load: %s\n", argv[1] );
574                         }
575                 }
576         }
577         else if (argc > 2) {
578                 g_print ( "%s\n", "too many arguments" );
579                 return -1;
580         }
581
582         // redirect Gtk warnings to the console
583         g_log_set_handler( "Gdk", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
584                                                                                                 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
585         g_log_set_handler( "Gtk", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
586                                                                                                 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
587         g_log_set_handler( "GtkGLExt", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
588                                                                                                          G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
589         g_log_set_handler( "GLib", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
590                                                                                                  G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
591         g_log_set_handler( 0, (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
592                                                                                         G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
593
594         GlobalDebugMessageHandler::instance().setHandler( GlobalPopupDebugMessageHandler::instance() );
595
596         environment_init( argc, argv );
597
598         paths_init();
599
600         if ( !check_version() ) {
601                 return EXIT_FAILURE;
602         }
603
604         show_splash();
605
606         create_global_pid();
607
608         GlobalPreferences_Init();
609
610         g_GamesDialog.Init();
611
612         g_strGameToolsPath = g_pGameDescription->mGameToolsPath;
613
614         remove_global_pid();
615
616         g_Preferences.Init(); // must occur before create_local_pid() to allow preferences to be reset
617
618         create_local_pid();
619
620         // in a very particular post-.pid startup
621         // we may have the console turned on and want to keep it that way
622         // so we use a latching system
623         if ( g_GamesDialog.m_bForceLogConsole ) {
624                 Sys_LogFile( true );
625                 g_Console_enableLogging = true;
626                 g_GamesDialog.m_bForceLogConsole = false;
627         }
628
629
630         Radiant_Initialise();
631
632         user_shortcuts_init();
633
634         g_pParentWnd = 0;
635         g_pParentWnd = new MainFrame();
636
637         hide_splash();
638
639         if ( mapname != NULL ) {
640                 Map_LoadFile( mapname );
641         }
642         else if ( g_bLoadLastMap && !g_strLastMap.empty() ) {
643                 Map_LoadFile( g_strLastMap.c_str() );
644         }
645         else
646         {
647                 Map_New();
648         }
649
650         // load up shaders now that we have the map loaded
651         // eviltypeguy
652         TextureBrowser_ShowStartupShaders( GlobalTextureBrowser() );
653
654
655         remove_local_pid();
656
657         ui::main();
658
659         // avoid saving prefs when the app is minimized
660         if ( g_pParentWnd->IsSleeping() ) {
661                 globalOutputStream() << "Shutdown while sleeping, not saving prefs\n";
662                 g_preferences_globals.disable_ini = true;
663         }
664
665         Map_Free();
666
667         if ( !Map_Unnamed( g_map ) ) {
668                 g_strLastMap = Map_Name( g_map );
669         }
670
671         delete g_pParentWnd;
672
673         user_shortcuts_save();
674
675         Radiant_Shutdown();
676
677         // close the log file if any
678         Sys_LogFile( false );
679
680         return EXIT_SUCCESS;
681 }