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