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