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