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