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