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