Rebase onto master
[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         const char* home = environment_get_home_path();
328         Q_mkdir( home );
329
330         {
331                 StringOutputStream path( 256 );
332                 path << home << "/";
333                 g_strSettingsPath = path.c_str();
334         }
335
336         Q_mkdir( g_strSettingsPath.c_str() );
337
338         g_strAppPath = environment_get_app_path();
339
340         // radiant is installed in the parent dir of "tools/"
341         // NOTE: this is not very easy for debugging
342         // maybe add options to lookup in several places?
343         // (for now I had to create symlinks)
344         {
345                 StringOutputStream path( 256 );
346                 path << g_strAppPath.c_str() << "bitmaps/";
347                 BitmapsPath_set( path.c_str() );
348         }
349
350         // we will set this right after the game selection is done
351         g_strGameToolsPath = g_strAppPath;
352 }
353
354 bool check_version_file( const char* filename, const char* version ){
355         TextFileInputStream file( filename );
356         if ( !file.failed() ) {
357                 char buf[10];
358                 buf[file.read( buf, 9 )] = '\0';
359
360                 // chomp it (the hard way)
361                 int chomp = 0;
362                 while ( buf[chomp] >= '0' && buf[chomp] <= '9' )
363                         chomp++;
364                 buf[chomp] = '\0';
365
366                 return string_equal( buf, version );
367         }
368         return false;
369 }
370
371 bool check_version(){
372         // a safe check to avoid people running broken installations
373         // (otherwise, they run it, crash it, and blame us for not forcing them hard enough to pay attention while installing)
374         // make something idiot proof and someone will make better idiots, this may be overkill
375         // let's leave it disabled in debug mode in any case
376         // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=431
377 #ifndef _DEBUG
378 #define CHECK_VERSION
379 #endif
380 #ifdef CHECK_VERSION
381         // locate and open RADIANT_MAJOR and RADIANT_MINOR
382         bool bVerIsGood = true;
383         {
384                 StringOutputStream ver_file_name( 256 );
385                 ver_file_name << AppPath_get() << "RADIANT_MAJOR";
386                 bVerIsGood = check_version_file( ver_file_name.c_str(), RADIANT_MAJOR_VERSION );
387         }
388         {
389                 StringOutputStream ver_file_name( 256 );
390                 ver_file_name << AppPath_get() << "RADIANT_MINOR";
391                 bVerIsGood = check_version_file( ver_file_name.c_str(), RADIANT_MINOR_VERSION );
392         }
393
394         if ( !bVerIsGood ) {
395                 StringOutputStream msg( 256 );
396                 msg << "This editor binary (" RADIANT_VERSION ") doesn't match what the latest setup has configured in this directory\n"
397                                 "Make sure you run the right/latest editor binary you installed\n"
398                         << AppPath_get();
399                 ui::root.alert( msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Default);
400         }
401         return bVerIsGood;
402 #else
403         return true;
404 #endif
405 }
406
407 void create_global_pid(){
408         /*!
409            the global prefs loading / game selection dialog might fail for any reason we don't know about
410            we need to catch when it happens, to cleanup the stateful prefs which might be killing it
411            and to turn on console logging for lookup of the problem
412            this is the first part of the two step .pid system
413            http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
414          */
415         StringOutputStream g_pidFile( 256 ); ///< the global .pid file (only for global part of the startup)
416
417         g_pidFile << SettingsPath_get() << "radiant.pid";
418
419         FILE *pid;
420         pid = fopen( g_pidFile.c_str(), "r" );
421         if ( pid != 0 ) {
422                 fclose( pid );
423
424                 if ( remove( g_pidFile.c_str() ) == -1 ) {
425                         StringOutputStream msg( 256 );
426                         msg << "WARNING: Could not delete " << g_pidFile.c_str();
427                         ui::root.alert( msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error );
428                 }
429
430                 // in debug, never prompt to clean registry, turn console logging auto after a failed start
431 #if !defined( _DEBUG )
432                 StringOutputStream msg( 256 );
433                 msg << "Radiant failed to start properly the last time it was run.\n"
434                            "The failure may be related to current global preferences.\n"
435                            "Do you want to reset global preferences to defaults?";
436
437                 if ( ui::root.alert( msg.c_str(), "Radiant - Startup Failure", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::YES ) {
438                         g_GamesDialog.Reset();
439                 }
440
441                 msg.clear();
442                 msg << "Logging console output to " << SettingsPath_get() << "radiant.log\nRefer to the log if Radiant fails to start again.";
443
444                 ui::root.alert( msg.c_str(), "Radiant - Console Log", ui::alert_type::OK );
445 #endif
446
447                 // set without saving, the class is not in a coherent state yet
448                 // just do the value change and call to start logging, CGamesDialog will pickup when relevant
449                 g_GamesDialog.m_bForceLogConsole = true;
450                 Sys_LogFile( true );
451         }
452
453         // create a primary .pid for global init run
454         pid = fopen( g_pidFile.c_str(), "w" );
455         if ( pid ) {
456                 fclose( pid );
457         }
458 }
459
460 void remove_global_pid(){
461         StringOutputStream g_pidFile( 256 );
462         g_pidFile << SettingsPath_get() << "radiant.pid";
463
464         // close the primary
465         if ( remove( g_pidFile.c_str() ) == -1 ) {
466                 StringOutputStream msg( 256 );
467                 msg << "WARNING: Could not delete " << g_pidFile.c_str();
468                 ui::root.alert( msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error );
469         }
470 }
471
472 /*!
473    now the secondary game dependant .pid file
474    http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
475  */
476 void create_local_pid(){
477         StringOutputStream g_pidGameFile( 256 ); ///< the game-specific .pid file
478         g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
479
480         FILE *pid = fopen( g_pidGameFile.c_str(), "r" );
481         if ( pid != 0 ) {
482                 fclose( pid );
483                 if ( remove( g_pidGameFile.c_str() ) == -1 ) {
484                         StringOutputStream msg;
485                         msg << "WARNING: Could not delete " << g_pidGameFile.c_str();
486                         ui::root.alert( msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error );
487                 }
488
489                 // in debug, never prompt to clean registry, turn console logging auto after a failed start
490 #if !defined( _DEBUG )
491                 StringOutputStream msg;
492                 msg << "Radiant failed to start properly the last time it was run.\n"
493                            "The failure may be caused by current preferences.\n"
494                            "Do you want to reset all preferences to defaults?";
495
496                 if ( ui::root.alert( msg.c_str(), "Radiant - Startup Failure", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::YES ) {
497                         Preferences_Reset();
498                 }
499
500                 msg.clear();
501                 msg << "Logging console output to " << SettingsPath_get() << "radiant.log\nRefer to the log if Radiant fails to start again.";
502
503                 ui::root.alert( msg.c_str(), "Radiant - Console Log", ui::alert_type::OK );
504 #endif
505
506                 // force console logging on! (will go in prefs too)
507                 g_GamesDialog.m_bForceLogConsole = true;
508                 Sys_LogFile( true );
509         }
510         else
511         {
512                 // create one, will remove right after entering message loop
513                 pid = fopen( g_pidGameFile.c_str(), "w" );
514                 if ( pid ) {
515                         fclose( pid );
516                 }
517         }
518 }
519
520
521 /*!
522    now the secondary game dependant .pid file
523    http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
524  */
525 void remove_local_pid(){
526         StringOutputStream g_pidGameFile( 256 );
527         g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
528         remove( g_pidGameFile.c_str() );
529 }
530
531 void user_shortcuts_init(){
532         StringOutputStream path( 256 );
533         path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
534         LoadCommandMap( path.c_str() );
535         SaveCommandMap( path.c_str() );
536 }
537
538 void user_shortcuts_save(){
539         StringOutputStream path( 256 );
540         path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
541         SaveCommandMap( path.c_str() );
542 }
543
544 int main( int argc, char* argv[] ){
545         crt_init();
546
547         streams_init();
548
549 #ifdef WIN32
550         HMODULE lib;
551         lib = LoadLibrary( "dwmapi.dll" );
552         if ( lib != 0 ) {
553                 void ( WINAPI *qDwmEnableComposition )( bool bEnable ) = ( void (WINAPI *) ( bool bEnable ) )GetProcAddress( lib, "DwmEnableComposition" );
554                 if ( qDwmEnableComposition ) {
555                         qDwmEnableComposition( FALSE );
556                 }
557                 FreeLibrary( lib );
558         }
559 #endif
560
561         const char* mapname = NULL;
562     char const *error = NULL;
563         if ( !ui::init( &argc, &argv, "<filename.map>", &error) ) {
564                 g_print( "%s\n", error );
565                 return -1;
566         }
567
568         // Gtk already removed parsed `--options`
569         if (argc == 2) {
570                 if ( strlen( argv[1] ) > 1 ) {
571                         if ( g_str_has_suffix( argv[1], ".map" ) ) {
572                                 if ( g_path_is_absolute( argv[1] ) ) {
573                                         mapname = argv[1];
574                                 }
575                                 else {
576                                         mapname = g_build_filename( g_get_current_dir(), argv[1], NULL );
577                                 }
578                         }
579                         else {
580                                 g_print( "bad file name, will not load: %s\n", argv[1] );
581                         }
582                 }
583         }
584         else if (argc > 2) {
585                 g_print ( "%s\n", "too many arguments" );
586                 return -1;
587         }
588
589         // redirect Gtk warnings to the console
590         g_log_set_handler( "Gdk", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
591                                                                                                 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
592         g_log_set_handler( "Gtk", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
593                                                                                                 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
594         g_log_set_handler( "GtkGLExt", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
595                                                                                                          G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
596         g_log_set_handler( "GLib", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
597                                                                                                  G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
598         g_log_set_handler( 0, (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
599                                                                                         G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
600
601         GlobalDebugMessageHandler::instance().setHandler( GlobalPopupDebugMessageHandler::instance() );
602
603         environment_init( argc, argv );
604
605         paths_init();
606
607         if ( !check_version() ) {
608                 return EXIT_FAILURE;
609         }
610
611         show_splash();
612
613         create_global_pid();
614
615         GlobalPreferences_Init();
616
617         g_GamesDialog.Init();
618
619         g_strGameToolsPath = g_pGameDescription->mGameToolsPath;
620
621         remove_global_pid();
622
623         g_Preferences.Init(); // must occur before create_local_pid() to allow preferences to be reset
624
625         create_local_pid();
626
627         // in a very particular post-.pid startup
628         // we may have the console turned on and want to keep it that way
629         // so we use a latching system
630         if ( g_GamesDialog.m_bForceLogConsole ) {
631                 Sys_LogFile( true );
632                 g_Console_enableLogging = true;
633                 g_GamesDialog.m_bForceLogConsole = false;
634         }
635
636
637         Radiant_Initialise();
638
639         user_shortcuts_init();
640
641         g_pParentWnd = 0;
642         g_pParentWnd = new MainFrame();
643
644         hide_splash();
645
646         if ( mapname != NULL ) {
647                 Map_LoadFile( mapname );
648         }
649         else if ( g_bLoadLastMap && !g_strLastMap.empty() ) {
650                 Map_LoadFile( g_strLastMap.c_str() );
651         }
652         else
653         {
654                 Map_New();
655         }
656
657         // load up shaders now that we have the map loaded
658         // eviltypeguy
659         TextureBrowser_ShowStartupShaders( GlobalTextureBrowser() );
660
661
662         remove_local_pid();
663
664         ui::main();
665
666         // avoid saving prefs when the app is minimized
667         if ( g_pParentWnd->IsSleeping() ) {
668                 globalOutputStream() << "Shutdown while sleeping, not saving prefs\n";
669                 g_preferences_globals.disable_ini = true;
670         }
671
672         Map_Free();
673
674         if ( !Map_Unnamed( g_map ) ) {
675                 g_strLastMap = Map_Name( g_map );
676         }
677
678         delete g_pParentWnd;
679
680         user_shortcuts_save();
681
682         Radiant_Shutdown();
683
684         // close the log file if any
685         Sys_LogFile( false );
686
687         return EXIT_SUCCESS;
688 }