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