1 #include "debug.h"
2 
3 #include <cctype>
4 // IWYU pragma: no_include <sys/errno.h>
5 #include <sys/stat.h>
6 // IWYU pragma: no_include <sys/unistd.h>
7 #include <clocale>
8 #include <algorithm>
9 #include <cmath>
10 #include <cstdint>
11 #include <cstdio>
12 #include <cstdlib>
13 #include <cstring>
14 #include <ctime>
15 #include <fstream>
16 #include <iomanip>
17 #include <iterator>
18 #include <map>
19 #include <memory>
20 #include <new>
21 #include <regex>
22 #include <set>
23 #include <sstream>
24 #include <string>
25 #include <type_traits>
26 #include <utility>
27 #include <vector>
28 
29 #include "cached_options.h"
30 #include "cata_assert.h"
31 #include "cata_utility.h"
32 #include "color.h"
33 #include "cursesdef.h"
34 #include "filesystem.h"
35 #include "get_version.h"
36 #include "input.h"
37 #include "mod_manager.h"
38 #include "optional.h"
39 #include "options.h"
40 #include "output.h"
41 #include "path_info.h"
42 #include "point.h"
43 #include "translations.h"
44 #include "type_id.h"
45 #include "ui_manager.h"
46 #include "worldfactory.h"
47 
48 #if !defined(_MSC_VER)
49 #include <sys/time.h>
50 #endif
51 
52 #if defined(_WIN32)
53 #   if 1 // HACK: Hack to prevent reordering of #include "platform_win.h" by IWYU
54 #       include "platform_win.h"
55 #   endif
56 #endif
57 
58 #if defined(BACKTRACE)
59 #   if defined(_WIN32)
60 #       include <dbghelp.h>
61 #       if defined(LIBBACKTRACE)
62 #           include <backtrace.h>
63 #       endif
64 #   elif defined(__ANDROID__)
65 #       include <unwind.h>
66 #       include <dlfcn.h>
67 #   else
68 #       include <execinfo.h>
69 #       include <unistd.h>
70 #   endif
71 #   if defined(__GNUC__) || defined(__clang__)
72 #       include <cxxabi.h>
73 #   endif
74 #endif
75 
76 #if defined(TILES)
77 #   if defined(_MSC_VER) && defined(USE_VCPKG)
78 #       include <SDL2/SDL.h>
79 #   else
80 #       include <SDL.h>
81 #   endif
82 #endif // TILES
83 
84 #if defined(__ANDROID__)
85 // used by android_version() function for __system_property_get().
86 #include <sys/system_properties.h>
87 #endif
88 
89 #if (defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)) && !defined(BSD)
90 #define BSD
91 #endif
92 
93 // Static defines                                                   {{{1
94 // ---------------------------------------------------------------------
95 
96 #if (defined(DEBUG) || defined(_DEBUG)) && !defined(NDEBUG)
97 static int debugLevel = DL_ALL;
98 static int debugClass = DC_ALL;
99 #else
100 static int debugLevel = D_ERROR;
101 static int debugClass = D_MAIN;
102 #endif
103 
104 /** Set to true when any error is logged. */
105 static bool error_observed = false;
106 
107 /** If true, debug messages will be captured,
108  * used to test debugmsg calls in the unit tests
109  */
110 static bool capturing = false;
111 /** сaptured debug messages */
112 static std::string captured;
113 
114 /**
115  * Class for capturing debugmsg,
116  * used by capture_debugmsg_during.
117  */
118 class capture_debugmsg
119 {
120     public:
121         capture_debugmsg();
122         std::string dmsg();
123         ~capture_debugmsg();
124 };
125 
capture_debugmsg_during(const std::function<void ()> & func)126 std::string capture_debugmsg_during( const std::function<void()> &func )
127 {
128     capture_debugmsg capture;
129     func();
130     return capture.dmsg();
131 }
132 
capture_debugmsg()133 capture_debugmsg::capture_debugmsg()
134 {
135     capturing = true;
136     captured.clear();
137 }
138 
dmsg()139 std::string capture_debugmsg::dmsg()
140 {
141     capturing = false;
142     return captured;
143 }
144 
~capture_debugmsg()145 capture_debugmsg::~capture_debugmsg()
146 {
147     capturing = false;
148 }
149 
debug_has_error_been_observed()150 bool debug_has_error_been_observed()
151 {
152     return error_observed;
153 }
154 
155 bool debug_mode = false;
156 
157 struct buffered_prompt_info {
158     std::string filename;
159     std::string line;
160     std::string funcname;
161     std::string text;
162 };
163 
164 namespace
165 {
166 
167 std::set<std::string> ignored_messages;
168 
169 } // namespace
170 
171 // debugmsg prompts that could not be shown immediately are buffered and replayed when catacurses::stdscr is available
172 // need to use method here to ensure `buffered_prompts` vector is initialized single time
buffered_prompts()173 static std::vector<buffered_prompt_info> &buffered_prompts()
174 {
175     static std::vector<buffered_prompt_info> buffered_prompts;
176     return buffered_prompts;
177 }
178 
debug_error_prompt(const char * filename,const char * line,const char * funcname,const char * text)179 static void debug_error_prompt(
180     const char *filename,
181     const char *line,
182     const char *funcname,
183     const char *text )
184 {
185     cata_assert( catacurses::stdscr );
186     cata_assert( filename != nullptr );
187     cata_assert( line != nullptr );
188     cata_assert( funcname != nullptr );
189     cata_assert( text != nullptr );
190 
191     std::string msg_key( filename );
192     msg_key += line;
193 
194     if( ignored_messages.count( msg_key ) > 0 ) {
195         return;
196     }
197 
198     std::string formatted_report =
199         string_format( // developer-facing error report. INTENTIONALLY UNTRANSLATED!
200             " DEBUG    : %s\n\n"
201             " FUNCTION : %s\n"
202             " FILE     : %s\n"
203             " LINE     : %s\n",
204             text, funcname, filename, line
205         );
206 
207 #if defined(BACKTRACE)
208     std::string backtrace_instructions =
209         string_format(
210             _( "See %s for a full stack backtrace" ),
211             PATH_INFO::debug()
212         );
213 #endif
214 
215     // temporarily disable redrawing and resizing of previous uis since they
216     // could be in an unknown state.
217     ui_adaptor ui( ui_adaptor::disable_uis_below {} );
218     const auto init_window = []( ui_adaptor & ui ) {
219         ui.position_from_window( catacurses::stdscr );
220     };
221     init_window( ui );
222     ui.on_screen_resize( init_window );
223     const std::string message = string_format(
224                                     "\n\n" // Looks nicer with some space
225                                     " %s\n" // translated user string: error notification
226                                     " -----------------------------------------------------------\n"
227                                     "%s"
228                                     " -----------------------------------------------------------\n"
229 #if defined(BACKTRACE)
230                                     " %s\n" // translated user string: where to find backtrace
231 #endif
232                                     " %s\n" // translated user string: space to continue
233                                     " %s\n" // translated user string: ignore key
234 #if defined(TILES)
235                                     " %s\n" // translated user string: copy
236 #endif // TILES
237                                     , _( "An error has occurred!  Written below is the error report:" ),
238                                     formatted_report,
239 #if defined(BACKTRACE)
240                                     backtrace_instructions,
241 #endif
242                                     _( "Press <color_white>space bar</color> to continue the game." ),
243                                     _( "Press <color_white>I</color> (or <color_white>i</color>) to also ignore this particular message in the future." )
244 #if defined(TILES)
245                                     , _( "Press <color_white>C</color> (or <color_white>c</color>) to copy this message to the clipboard." )
246 #endif // TILES
247                                 );
248     ui.on_redraw( [&]( const ui_adaptor & ) {
249         catacurses::erase();
250         fold_and_print( catacurses::stdscr, point_zero, getmaxx( catacurses::stdscr ), c_light_red,
251                         "%s", message );
252         catacurses::refresh();
253     } );
254 
255 #if defined(__ANDROID__)
256     input_context ctxt( "DEBUG_MSG", keyboard_mode::keycode );
257     ctxt.register_manual_key( 'C' );
258     ctxt.register_manual_key( 'I' );
259     ctxt.register_manual_key( ' ' );
260 #endif
261     for( bool stop = false; !stop; ) {
262         ui_manager::redraw();
263         switch( inp_mngr.get_input_event().get_first_input() ) {
264 #if defined(TILES)
265             case 'c':
266             case 'C':
267                 SDL_SetClipboardText( formatted_report.c_str() );
268                 break;
269 #endif // TILES
270             case 'i':
271             case 'I':
272                 ignored_messages.insert( msg_key );
273             /* fallthrough */
274             case ' ':
275                 stop = true;
276                 break;
277         }
278     }
279 }
280 
replay_buffered_debugmsg_prompts()281 void replay_buffered_debugmsg_prompts()
282 {
283     if( buffered_prompts().empty() || !catacurses::stdscr ) {
284         return;
285     }
286     for( const auto &prompt : buffered_prompts() ) {
287         debug_error_prompt(
288             prompt.filename.c_str(),
289             prompt.line.c_str(),
290             prompt.funcname.c_str(),
291             prompt.text.c_str()
292         );
293     }
294     buffered_prompts().clear();
295 }
296 
realDebugmsg(const char * filename,const char * line,const char * funcname,const std::string & text)297 void realDebugmsg( const char *filename, const char *line, const char *funcname,
298                    const std::string &text )
299 {
300     cata_assert( filename != nullptr );
301     cata_assert( line != nullptr );
302     cata_assert( funcname != nullptr );
303 
304     if( capturing ) {
305         captured += text;
306     } else {
307         DebugLog( D_ERROR, D_MAIN ) << filename << ":" << line << " [" << funcname << "] " << text <<
308                                     std::flush;
309     }
310 
311     if( test_mode ) {
312         return;
313     }
314 
315     if( !catacurses::stdscr ) {
316         buffered_prompts().push_back( {filename, line, funcname, text } );
317         return;
318     }
319 
320     debug_error_prompt( filename, line, funcname, text.c_str() );
321 }
322 
323 // Normal functions                                                 {{{1
324 // ---------------------------------------------------------------------
325 
limitDebugLevel(int level_bitmask)326 void limitDebugLevel( int level_bitmask )
327 {
328     DebugLog( DL_ALL, DC_ALL ) << "Set debug level to: " << level_bitmask;
329     debugLevel = level_bitmask;
330 }
331 
limitDebugClass(int class_bitmask)332 void limitDebugClass( int class_bitmask )
333 {
334     DebugLog( DL_ALL, DC_ALL ) << "Set debug class to: " << class_bitmask;
335     debugClass = class_bitmask;
336 }
337 
338 // Debug only                                                       {{{1
339 // ---------------------------------------------------------------------
340 
341 // Debug Includes                                                   {{{2
342 // ---------------------------------------------------------------------
343 
344 // Null OStream                                                     {{{2
345 // ---------------------------------------------------------------------
346 
347 struct NullBuf : public std::streambuf {
348     NullBuf() = default;
overflowNullBuf349     int overflow( int c ) override {
350         return c;
351     }
352 };
353 
354 // DebugFile OStream Wrapper                                        {{{2
355 // ---------------------------------------------------------------------
356 
357 struct time_info {
358     int hours;
359     int minutes;
360     int seconds;
361     int mseconds;
362 
363     template <typename Stream>
operator <<(Stream & out,const time_info & t)364     friend Stream &operator<<( Stream &out, const time_info &t ) {
365         using char_t = typename Stream::char_type;
366         using base   = std::basic_ostream<char_t>;
367 
368         static_assert( std::is_base_of<base, Stream>::value, "" );
369 
370         out << std::setfill( '0' );
371         out << std::setw( 2 ) << t.hours << ':' << std::setw( 2 ) << t.minutes << ':' <<
372             std::setw( 2 ) << t.seconds << '.' << std::setw( 3 ) << t.mseconds;
373 
374         return out;
375     }
376 };
377 
378 #if defined(_WIN32)
get_time()379 static time_info get_time() noexcept
380 {
381     SYSTEMTIME time {};
382 
383     GetLocalTime( &time );
384 
385     return time_info { static_cast<int>( time.wHour ), static_cast<int>( time.wMinute ),
386                        static_cast<int>( time.wSecond ), static_cast<int>( time.wMilliseconds )
387                      };
388 }
389 #else
get_time()390 static time_info get_time() noexcept
391 {
392     timeval tv;
393     gettimeofday( &tv, nullptr );
394 
395     const time_t tt      = time_t {tv.tv_sec};
396     tm current;
397     localtime_r( &tt, &current );
398 
399     return time_info { current.tm_hour, current.tm_min, current.tm_sec,
400                        static_cast<int>( std::lround( tv.tv_usec / 1000.0 ) )
401                      };
402 }
403 #endif
404 
405 struct DebugFile {
406     DebugFile();
407     ~DebugFile();
408     void init( DebugOutput, const std::string &filename );
409     void deinit();
410     std::ostream &get_file();
411 
412     // Using shared_ptr for the type-erased deleter support, not because
413     // it needs to be shared.
414     std::shared_ptr<std::ostream> file;
415     std::string filename;
416 };
417 
418 // DebugFile OStream Wrapper                                        {{{2
419 // ---------------------------------------------------------------------
420 
421 // needs to be inside the method to ensure it's initialized (and only once)
422 // NOTE: using non-local static variables (defined at top level in cpp file) here is wrong,
423 // because DebugLog (that uses them) might be called from the constructor of some non-local static entity
424 // during dynamic initialization phase, when non-local static variables here are
425 // only zero-initialized
debugFile()426 static DebugFile &debugFile()
427 {
428     static DebugFile debugFile;
429     return debugFile;
430 }
431 
432 DebugFile::DebugFile() = default;
433 
~DebugFile()434 DebugFile::~DebugFile()
435 {
436     deinit();
437 }
438 
deinit()439 void DebugFile::deinit()
440 {
441     if( file && file.get() != &std::cerr ) {
442         *file << "\n";
443         *file << get_time() << " : Log shutdown.\n";
444         *file << "-----------------------------------------\n\n";
445     }
446     file.reset();
447 }
448 
get_file()449 std::ostream &DebugFile::get_file()
450 {
451     if( !file ) {
452         file = std::make_shared<std::ostringstream>();
453     }
454     return *file;
455 }
456 
init(DebugOutput output_mode,const std::string & filename)457 void DebugFile::init( DebugOutput output_mode, const std::string &filename )
458 {
459     std::shared_ptr<std::ostringstream> str_buffer = std::dynamic_pointer_cast<std::ostringstream>
460             ( file );
461 
462     switch( output_mode ) {
463         case DebugOutput::std_err:
464             file = std::shared_ptr<std::ostream>( &std::cerr, null_deleter() );
465             break;
466         case DebugOutput::file: {
467             this->filename = filename;
468             const std::string oldfile = filename + ".prev";
469             bool rename_failed = false;
470             struct stat buffer;
471             if( stat( filename.c_str(), &buffer ) == 0 ) {
472                 // Continue with the old log file if it's smaller than 1 MiB
473                 if( buffer.st_size >= 1024 * 1024 ) {
474                     rename_failed = !rename_file( filename, oldfile );
475                 }
476             }
477             file = std::make_shared<std::ofstream>(
478                        filename.c_str(), std::ios::out | std::ios::app );
479             *file << "\n\n-----------------------------------------\n";
480             *file << get_time() << " : Starting log.";
481             DebugLog( D_INFO, D_MAIN ) << "Cataclysm DDA version " << getVersionString();
482             if( rename_failed ) {
483                 DebugLog( D_ERROR, DC_ALL ) << "Moving the previous log file to "
484                                             << oldfile << " failed.\n"
485                                             << "Check the file permissions.  This "
486                                             "program will continue to use the "
487                                             "previous log file.";
488             }
489         }
490         break;
491         default:
492             std::cerr << "Unexpected debug output mode " << static_cast<int>( output_mode )
493                       << std::endl;
494             return;
495     }
496 
497     if( str_buffer && file ) {
498         *file << str_buffer->str();
499     }
500 }
501 
setupDebug(DebugOutput output_mode)502 void setupDebug( DebugOutput output_mode )
503 {
504     int level = 0;
505 
506 #if defined(DEBUG_INFO)
507     level |= D_INFO;
508 #endif
509 
510 #if defined(DEBUG_WARNING)
511     level |= D_WARNING;
512 #endif
513 
514 #if defined(DEBUG_ERROR)
515     level |= D_ERROR;
516 #endif
517 
518 #if defined(DEBUG_PEDANTIC_INFO)
519     level |= D_PEDANTIC_INFO;
520 #endif
521 
522     if( level != 0 ) {
523         limitDebugLevel( level );
524     }
525 
526     int cl = 0;
527 
528 #if defined(DEBUG_ENABLE_MAIN)
529     cl |= D_MAIN;
530 #endif
531 
532 #if defined(DEBUG_ENABLE_MAP)
533     cl |= D_MAP;
534 #endif
535 
536 #if defined(DEBUG_ENABLE_MAP_GEN)
537     cl |= D_MAP_GEN;
538 #endif
539 
540 #if defined(DEBUG_ENABLE_GAME)
541     cl |= D_GAME;
542 #endif
543 
544     if( cl != 0 ) {
545         limitDebugClass( cl );
546     }
547 
548     debugFile().init( output_mode, PATH_INFO::debug() );
549 }
550 
deinitDebug()551 void deinitDebug()
552 {
553     debugFile().deinit();
554 }
555 
556 // OStream Operators                                                {{{2
557 // ---------------------------------------------------------------------
558 
operator <<(std::ostream & out,DebugLevel lev)559 static std::ostream &operator<<( std::ostream &out, DebugLevel lev )
560 {
561     if( lev != DL_ALL ) {
562         if( lev & D_INFO ) {
563             out << "INFO ";
564         }
565         if( lev & D_WARNING ) {
566             out << "WARNING ";
567         }
568         if( lev & D_ERROR ) {
569             out << "ERROR ";
570         }
571         if( lev & D_PEDANTIC_INFO ) {
572             out << "PEDANTIC ";
573         }
574     }
575     return out;
576 }
577 
operator <<(std::ostream & out,DebugClass cl)578 static std::ostream &operator<<( std::ostream &out, DebugClass cl )
579 {
580     if( cl != DC_ALL ) {
581         if( cl & D_MAIN ) {
582             out << "MAIN ";
583         }
584         if( cl & D_MAP ) {
585             out << "MAP ";
586         }
587         if( cl & D_MAP_GEN ) {
588             out << "MAP_GEN ";
589         }
590         if( cl & D_NPC ) {
591             out << "NPC ";
592         }
593         if( cl & D_GAME ) {
594             out << "GAME ";
595         }
596         if( cl & D_SDL ) {
597             out << "SDL ";
598         }
599     }
600     return out;
601 }
602 
603 #if defined(BACKTRACE)
604 #if !defined(_WIN32) && !defined(__CYGWIN__) && !defined(__ANDROID__)
605 // Verify that a string is safe for passing as an argument to addr2line.
606 // In particular, we want to avoid any characters of significance to the shell.
debug_is_safe_string(const char * start,const char * finish)607 static bool debug_is_safe_string( const char *start, const char *finish )
608 {
609     static constexpr char safe_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
610                                          "abcdefghijklmnopqrstuvwxyz"
611                                          "01234567890_./-+";
612     using std::begin;
613     using std::end;
614     const auto is_safe_char =
615     [&]( char c ) {
616         const char *in_safe = std::find( begin( safe_chars ), end( safe_chars ), c );
617         return c && in_safe != end( safe_chars );
618     };
619     return std::all_of( start, finish, is_safe_char );
620 }
621 
debug_resolve_binary(const std::string & binary,std::ostream & out)622 static std::string debug_resolve_binary( const std::string &binary, std::ostream &out )
623 {
624     if( binary.find( '/' ) != std::string::npos ) {
625         // The easy case, where we have a path to the binary
626         return binary;
627     }
628     // If the binary name has no slashes then it was found via PATH
629     // lookup, and we need to do the same to pass the correct name
630     // to addr2line.  An alternative would be to use /proc/self/exe,
631     // but that's Linux-specific.
632     // Obviously this will not work in all situations, but it will
633     // usually do the right thing.
634     const char *path = std::getenv( "PATH" );
635     if( !path ) {
636         // Should be impossible, but I want to avoid segfaults
637         // in the crash handler.
638         out << "    backtrace: PATH not set\n";
639         return binary;
640     }
641 
642     for( const std::string &path_elem : string_split( path, ':' ) ) {
643         if( path_elem.empty() ) {
644             continue;
645         }
646         std::string candidate = path_elem + "/" + binary;
647         if( 0 == access( candidate.c_str(), X_OK ) ) {
648             return candidate;
649         }
650     }
651 
652     return binary;
653 }
654 
debug_compute_load_offset(const std::string & binary,const std::string & symbol,const std::string & offset_within_symbol_s,void * address,std::ostream & out)655 static cata::optional<uintptr_t> debug_compute_load_offset(
656     const std::string &binary, const std::string &symbol,
657     const std::string &offset_within_symbol_s, void *address, std::ostream &out )
658 {
659     // I don't know a good way to compute this offset.  This
660     // seems to work, but I'm not sure how portable it is.
661     //
662     // backtrace_symbols has provided the address of a symbol as loaded
663     // in memory.  We use nm to compute the address of the same symbol
664     // in the binary file, and take the difference of the two.
665     //
666     // There are platform-specific functions which can do similar
667     // things (e.g. dladdr1 in GNU libdl) but this approach might
668     // perhaps be more portable and adds no link-time dependencies.
669 
670     uintptr_t offset_within_symbol = std::stoull( offset_within_symbol_s, nullptr, 0 );
671     std::string string_sought = " " + symbol;
672 
673     // We need to try calling nm in two different ways, because one
674     // works for executables and the other for libraries.
675     const char *nm_variants[] = { "nm", "nm -D" };
676     for( const char *nm_variant : nm_variants ) {
677         std::ostringstream cmd;
678         cmd << nm_variant << ' ' << binary << " 2>&1";
679         FILE *nm = popen( cmd.str().c_str(), "re" );
680         if( !nm ) {
681             out << "    backtrace: popen(nm) failed: " << strerror( errno ) << "\n";
682             return cata::nullopt;
683         }
684 
685         char buf[1024];
686         while( fgets( buf, sizeof( buf ), nm ) ) {
687             std::string line( buf );
688             while( !line.empty() && isspace( line.end()[-1] ) ) {
689                 line.erase( line.end() - 1 );
690             }
691             if( string_ends_with( line, string_sought ) ) {
692                 std::istringstream line_is( line );
693                 uintptr_t symbol_address;
694                 line_is >> std::hex >> symbol_address;
695                 if( line_is ) {
696                     pclose( nm );
697                     return reinterpret_cast<uintptr_t>( address ) -
698                            ( symbol_address + offset_within_symbol );
699                 }
700             }
701         }
702 
703         pclose( nm );
704     }
705 
706     return cata::nullopt;
707 }
708 #endif
709 
710 #if defined(_WIN32) && defined(LIBBACKTRACE)
711 // wrap libbacktrace to use std::function instead of function pointers
712 using bt_error_callback = std::function<void( const char *, int )>;
713 using bt_full_callback = std::function<int( uintptr_t, const char *, int, const char * )>;
714 using bt_syminfo_callback = std::function<void( uintptr_t, const char *, uintptr_t, uintptr_t )>;
715 
bt_create_state(const char * const filename,const int threaded,const bt_error_callback & cb)716 static backtrace_state *bt_create_state( const char *const filename, const int threaded,
717         const bt_error_callback &cb )
718 {
719     return backtrace_create_state( filename, threaded,
720     []( void *const data, const char *const msg, const int errnum ) {
721         const bt_error_callback &cb = *reinterpret_cast<const bt_error_callback *>( data );
722         cb( msg, errnum );
723     },
724     const_cast<bt_error_callback *>( &cb ) );
725 }
726 
bt_pcinfo(backtrace_state * const state,const uintptr_t pc,const bt_full_callback & cb_full,const bt_error_callback & cb_error)727 static int bt_pcinfo( backtrace_state *const state, const uintptr_t pc,
728                       const bt_full_callback &cb_full, const bt_error_callback &cb_error )
729 {
730     using cb_pair = std::pair<const bt_full_callback &, const bt_error_callback &>;
731     cb_pair cb { cb_full, cb_error };
732     return backtrace_pcinfo( state, pc,
733                              // backtrace callback
734                              []( void *const data, const uintptr_t pc, const char *const filename,
735     const int lineno, const char *const function ) -> int {
736         cb_pair &cb = *reinterpret_cast<cb_pair *>( data );
737         return cb.first( pc, filename, lineno, function );
738     },
739     // error callback
740     []( void *const data, const char *const msg, const int errnum ) {
741         cb_pair &cb = *reinterpret_cast<cb_pair *>( data );
742         cb.second( msg, errnum );
743     },
744     &cb );
745 }
746 
bt_syminfo(backtrace_state * const state,const uintptr_t addr,const bt_syminfo_callback & cb_syminfo,const bt_error_callback cb_error)747 static int bt_syminfo( backtrace_state *const state, const uintptr_t addr,
748                        const bt_syminfo_callback &cb_syminfo, const bt_error_callback cb_error )
749 {
750     using cb_pair = std::pair<const bt_syminfo_callback &, const bt_error_callback &>;
751     cb_pair cb { cb_syminfo, cb_error };
752     return backtrace_syminfo( state, addr,
753                               // syminfo callback
754                               []( void *const data, const uintptr_t pc, const char *const symname,
755     const uintptr_t symval, const uintptr_t symsize ) {
756         cb_pair &cb = *reinterpret_cast<cb_pair *>( data );
757         cb.first( pc, symname, symval, symsize );
758     },
759     // error callback
760     []( void *const data, const char *const msg, const int errnum ) {
761         cb_pair &cb = *reinterpret_cast<cb_pair *>( data );
762         cb.second( msg, errnum );
763     },
764     &cb );
765 }
766 #endif
767 
768 #if defined(_WIN32)
769 class sym_init
770 {
771     public:
sym_init()772         sym_init() {
773             SymInitialize( GetCurrentProcess(), nullptr, TRUE );
774         }
775 
~sym_init()776         ~sym_init() {
777             SymCleanup( GetCurrentProcess() );
778         }
779 };
780 static std::unique_ptr<sym_init> sym_init_;
781 
782 constexpr int module_path_len = 512;
783 // on some systems the number of frames to capture have to be less than 63 according to the documentation
784 constexpr int bt_cnt = 62;
785 constexpr int max_name_len = 512;
786 // ( max_name_len - 1 ) because SYMBOL_INFO already contains a TCHAR
787 constexpr int sym_size = sizeof( SYMBOL_INFO ) + ( max_name_len - 1 ) * sizeof( TCHAR );
788 static char mod_path[module_path_len];
789 static PVOID bt[bt_cnt];
790 static struct {
791     alignas( SYMBOL_INFO ) char storage[sym_size];
792 } sym_storage;
793 static SYMBOL_INFO &sym = reinterpret_cast<SYMBOL_INFO &>( sym_storage );
794 #if defined(LIBBACKTRACE)
795 static std::map<DWORD64, backtrace_state *> bt_states;
796 #endif
797 #elif !defined(__ANDROID__)
798 static constexpr int bt_cnt = 20;
799 static void *bt[bt_cnt];
800 #endif
801 
demangle(const char * symbol)802 static std::string demangle( const char *symbol )
803 {
804 #if defined(_MSC_VER)
805     // TODO: implement demangling on MSVC
806 #elif defined(__GNUC__) || defined(__clang__)
807     int status = -1;
808     char *demangled = abi::__cxa_demangle( symbol, nullptr, nullptr, &status );
809     if( status == 0 ) {
810         std::string demangled_str( demangled );
811         free( demangled );
812         return demangled_str;
813     }
814 #if defined(_WIN32)
815     // https://stackoverflow.com/questions/54333608/boost-stacktrace-not-demangling-names-when-cross-compiled
816     // libbacktrace may strip leading underscore character in the symbol name returned
817     // so if demangling failed, try again with an underscore prepended
818     std::string prepend_underscore( "_" );
819     prepend_underscore = prepend_underscore + symbol;
820     demangled = abi::__cxa_demangle( prepend_underscore.c_str(), nullptr, nullptr, &status );
821     if( status == 0 ) {
822         std::string demangled_str( demangled );
823         free( demangled );
824         return demangled_str;
825     }
826 #endif // defined(_WIN32)
827 #endif // compiler macros
828     return std::string( symbol );
829 }
830 
831 #if !defined(_WIN32) && !defined(__ANDROID__)
write_demangled_frame(std::ostream & out,const char * frame)832 static void write_demangled_frame( std::ostream &out, const char *frame )
833 {
834 #if defined(__linux__)
835     // ./cataclysm(_ZN4game13handle_actionEv+0x47e8) [0xaaaae91e80fc]
836     static const std::regex symbol_regex( R"(^(.*)\((.*)\+(0x?[a-f0-9]*)\)\s\[(0x[a-f0-9]+)\]$)" );
837     std::cmatch match_result;
838     if( std::regex_search( frame, match_result, symbol_regex ) && match_result.size() == 5 ) {
839         std::csub_match file_name = match_result[1];
840         std::csub_match raw_symbol_name = match_result[2];
841         std::csub_match offset = match_result[3];
842         std::csub_match address = match_result[4];
843         out << "\n    " << file_name.str() << "(" << demangle( raw_symbol_name.str().c_str() ) << "+" <<
844             offset.str() << ") [" << address.str() << "]";
845     } else {
846         out << "\n    " << frame;
847     }
848 #elif defined(MACOSX)
849     //1   cataclysm-tiles                     0x0000000102ba2244 _ZL9log_crashPKcS0_ + 608
850     static const std::regex symbol_regex( R"(^(.*)(0x[a-f0-9]{16})\s(.*)\s\+\s([0-9]+)$)" );
851     std::cmatch match_result;
852     if( std::regex_search( frame, match_result, symbol_regex ) && match_result.size() == 5 ) {
853         std::csub_match prefix = match_result[1];
854         std::csub_match address = match_result[2];
855         std::csub_match raw_symbol_name = match_result[3];
856         std::csub_match offset = match_result[4];
857         out << "\n    " << prefix.str() << address.str() << ' ' << demangle( raw_symbol_name.str().c_str() )
858             << " + " << offset.str();
859     } else {
860         out << "\n    " << frame;
861     }
862 #elif defined(BSD)
863     static const std::regex symbol_regex( R"(^(0x[a-f0-9]+)\s<(.*)\+(0?x?[a-f0-9]*)>\sat\s(.*)$)" );
864     std::cmatch match_result;
865     if( std::regex_search( frame, match_result, symbol_regex ) && match_result.size() == 5 ) {
866         std::csub_match address = match_result[1];
867         std::csub_match raw_symbol_name = match_result[2];
868         std::csub_match offset = match_result[3];
869         std::csub_match file_name = match_result[4];
870         out << "\n    " << address.str() << " <" << demangle( raw_symbol_name.str().c_str() ) << "+" <<
871             offset.str() << "> at " << file_name.str();
872     } else {
873         out << "\n    " << frame;
874     }
875 #else
876     out << "\n    " << frame;
877 #endif
878 }
879 #endif // !defined(_WIN32) && !defined(__ANDROID__)
880 
881 #if !defined(__ANDROID__)
debug_write_backtrace(std::ostream & out)882 void debug_write_backtrace( std::ostream &out )
883 {
884 #if defined(_WIN32)
885     if( !sym_init_ ) {
886         sym_init_ = std::make_unique<sym_init>();
887     }
888     sym.SizeOfStruct = sizeof( SYMBOL_INFO );
889     sym.MaxNameLen = max_name_len;
890     // libbacktrace's own backtrace capturing doesn't seem to work on Windows
891     const USHORT num_bt = CaptureStackBackTrace( 0, bt_cnt, bt, nullptr );
892     const HANDLE proc = GetCurrentProcess();
893     for( USHORT i = 0; i < num_bt; ++i ) {
894         DWORD64 off;
895         out << "\n  #" << i;
896         out << "\n    (dbghelp: ";
897         if( SymFromAddr( proc, reinterpret_cast<DWORD64>( bt[i] ), &off, &sym ) ) {
898             out << demangle( sym.Name ) << "+0x" << std::hex << off << std::dec;
899         }
900         out << "@" << bt[i];
901         const DWORD64 mod_base = SymGetModuleBase64( proc, reinterpret_cast<DWORD64>( bt[i] ) );
902         if( mod_base ) {
903             out << "[";
904             const DWORD mod_len = GetModuleFileName( reinterpret_cast<HMODULE>( mod_base ), mod_path,
905                                   module_path_len );
906             // mod_len == module_path_len means insufficient buffer
907             if( mod_len > 0 && mod_len < module_path_len ) {
908                 const char *mod_name = mod_path + mod_len;
909                 for( ; mod_name > mod_path && *( mod_name - 1 ) != '\\'; --mod_name ) {
910                 }
911                 out << mod_name;
912             } else {
913                 out << "0x" << std::hex << mod_base << std::dec;
914             }
915             out << "+0x" << std::hex << reinterpret_cast<uintptr_t>( bt[i] ) - mod_base <<
916                 std::dec << "]";
917         }
918         out << "), ";
919 #if defined(LIBBACKTRACE)
920         backtrace_state *bt_state = nullptr;
921         if( mod_base ) {
922             const auto it = bt_states.find( mod_base );
923             if( it != bt_states.end() ) {
924                 bt_state = it->second;
925             } else {
926                 const DWORD mod_len = GetModuleFileName( reinterpret_cast<HMODULE>( mod_base ), mod_path,
927                                       module_path_len );
928                 if( mod_len > 0 && mod_len < module_path_len ) {
929                     bt_state = bt_create_state( mod_path, 0,
930                                                 // error callback
931                     [&out]( const char *const msg, const int errnum ) {
932                         out << "\n    (backtrace_create_state failed: errno = " << errnum
933                             << ", msg = " << ( msg ? msg : "[no msg]" ) << "),";
934                     } );
935                 } else {
936                     out << "\n    (executable path exceeds " << module_path_len << " chars),";
937                 }
938                 if( bt_state ) {
939                     bt_states.emplace( mod_base, bt_state );
940                 }
941             }
942         } else {
943             out << "\n    (unable to get module base address),";
944         }
945         if( bt_state ) {
946             bt_syminfo( bt_state, reinterpret_cast<uintptr_t>( bt[i] ),
947                         // syminfo callback
948                         [&out]( const uintptr_t pc, const char *const symname,
949             const uintptr_t symval, const uintptr_t ) {
950                 out << "\n    (libbacktrace: " << ( symname ? demangle( symname ) : "[unknown symbol]" )
951                     << "+0x" << std::hex << pc - symval << std::dec
952                     << "@0x" << std::hex << pc << std::dec
953                     << "),";
954             },
955             // error callback
956             [&out]( const char *const msg, const int errnum ) {
957                 out << "\n    (backtrace_syminfo failed: errno = " << errnum
958                     << ", msg = " << ( msg ? msg : "[no msg]" )
959                     << "),";
960             } );
961             bt_pcinfo( bt_state, reinterpret_cast<uintptr_t>( bt[i] ),
962                        // backtrace callback
963                        [&out]( const uintptr_t pc, const char *const filename,
964             const int lineno, const char *const function ) -> int {
965                 out << "\n    (libbacktrace: 0x" << std::hex << pc << std::dec
966                     << "    " << ( filename ? filename : "[unknown src]" )
967                     << ":" << lineno
968                     << "    " << ( function ? function : "[unknown func]" )
969                     << "),";
970                 return 0;
971             },
972             // error callback
973             [&out]( const char *const msg, const int errnum ) {
974                 out << "\n    (backtrace_pcinfo failed: errno = " << errnum
975                     << ", msg = " << ( msg ? msg : "[no msg]" )
976                     << "),";
977             } );
978         }
979 #endif
980     }
981     out << "\n";
982 #else
983 #   if defined(__CYGWIN__)
984     // BACKTRACE is not supported under CYGWIN!
985     ( void ) out;
986 #   else
987     int count = backtrace( bt, bt_cnt );
988     char **funcNames = backtrace_symbols( bt, count );
989     for( int i = 0; i < count; ++i ) {
990         write_demangled_frame( out, funcNames[i] );
991     }
992     out << "\n\n    Attempting to repeat stack trace using debug symbols…\n";
993     // Try to print the backtrace again, but this time using addr2line
994     // to extract debug info and thus get a more detailed / useful
995     // version.  If addr2line is not available this will just fail,
996     // which is fine.
997     //
998     // To make this fast, we need to call addr2line with as many
999     // addresses as possible in each commandline.  To that end, we track
1000     // the binary of the frame and issue a command whenever that
1001     // changes.
1002     std::vector<uintptr_t> addresses;
1003     std::map<std::string, uintptr_t> load_offsets;
1004     std::string last_binary_name;
1005 
1006     auto call_addr2line = [&out, &load_offsets]( const std::string & binary,
1007     const std::vector<uintptr_t> &addresses ) {
1008         const auto load_offset_it = load_offsets.find( binary );
1009         const uintptr_t load_offset = ( load_offset_it == load_offsets.end() ) ? 0 :
1010                                       load_offset_it->second;
1011 
1012         std::ostringstream cmd;
1013         cmd.imbue( std::locale::classic() );
1014         cmd << "addr2line -i -e " << binary << " -f -C" << std::hex;
1015         for( uintptr_t address : addresses ) {
1016             cmd << " 0x" << ( address - load_offset );
1017         }
1018         cmd << " 2>&1";
1019         FILE *addr2line = popen( cmd.str().c_str(), "re" );
1020         if( addr2line == nullptr ) {
1021             out << "    backtrace: popen(addr2line) failed\n";
1022             return false;
1023         }
1024         char buf[1024];
1025         while( fgets( buf, sizeof( buf ), addr2line ) ) {
1026             out.write( "    ", 4 );
1027             // Strip leading directories for source file path
1028             char search_for[] = "/src/";
1029             char *buf_end = buf + strlen( buf );
1030             char *src = std::find_end( buf, buf_end,
1031                                        search_for, search_for + strlen( search_for ) );
1032             if( src == buf_end ) {
1033                 src = buf;
1034             } else {
1035                 out << "…";
1036             }
1037             out.write( src, strlen( src ) );
1038         }
1039         if( 0 != pclose( addr2line ) ) {
1040             // Most likely reason is that addr2line is not installed, so
1041             // in this case we give up and don't try any more frames.
1042             out << "    backtrace: addr2line failed\n";
1043             return false;
1044         }
1045         return true;
1046     };
1047 
1048     for( int i = 0; i < count; ++i ) {
1049         // An example string from backtrace_symbols is
1050         //   ./cataclysm-tiles(_Z21debug_write_backtraceRSo+0x3d) [0x55ddebfa313d]
1051         // From that we need to extract the binary name, the symbol
1052         // name, and the offset within the symbol.  We don't need to
1053         // extract the address (the last thing) because that's already
1054         // available in bt.
1055 
1056         char *funcName = funcNames[i];
1057         cata_assert( funcName ); // To appease static analysis
1058         char *const funcNameEnd = funcName + std::strlen( funcName );
1059         char *const binaryEnd = std::find( funcName, funcNameEnd, '(' );
1060         if( binaryEnd == funcNameEnd ) {
1061             out << "    backtrace: Could not extract binary name from line\n";
1062             continue;
1063         }
1064 
1065         if( !debug_is_safe_string( funcName, binaryEnd ) ) {
1066             out << "    backtrace: Binary name not safe\n";
1067             continue;
1068         }
1069 
1070         std::string binary_name( funcName, binaryEnd );
1071         binary_name = debug_resolve_binary( binary_name, out );
1072 
1073         // For each binary we need to determine its offset relative to
1074         // its natural load address in order to undo ASLR and pass the
1075         // correct addresses to addr2line
1076         auto load_offset = load_offsets.find( binary_name );
1077         if( load_offset == load_offsets.end() ) {
1078             char *const symbolNameStart = binaryEnd + 1;
1079             char *const symbolNameEnd = std::find( symbolNameStart, funcNameEnd, '+' );
1080             char *const offsetEnd = std::find( symbolNameStart, funcNameEnd, ')' );
1081 
1082             if( symbolNameEnd < offsetEnd && offsetEnd < funcNameEnd ) {
1083                 char *const offsetStart = symbolNameEnd + 1;
1084                 std::string symbol_name( symbolNameStart, symbolNameEnd );
1085                 std::string offset_within_symbol( offsetStart, offsetEnd );
1086 
1087                 cata::optional<uintptr_t> offset =
1088                     debug_compute_load_offset( binary_name, symbol_name, offset_within_symbol,
1089                                                bt[i], out );
1090                 if( offset ) {
1091                     load_offsets.emplace( binary_name, *offset );
1092                 }
1093             }
1094         }
1095 
1096         if( !last_binary_name.empty() && binary_name != last_binary_name ) {
1097             // We have reached the end of the sequence of addresses
1098             // within this binary, so call addr2line before proceeding
1099             // to the next binary.
1100             if( !call_addr2line( last_binary_name, addresses ) ) {
1101                 addresses.clear();
1102                 break;
1103             }
1104 
1105             addresses.clear();
1106         }
1107 
1108         last_binary_name = binary_name;
1109         addresses.push_back( reinterpret_cast<uintptr_t>( bt[i] ) );
1110     }
1111 
1112     if( !addresses.empty() ) {
1113         call_addr2line( last_binary_name, addresses );
1114     }
1115     free( funcNames );
1116 #   endif
1117 #endif
1118 }
1119 #endif
1120 #endif
1121 
1122 // Probably because there are too many nested #if..#else..#endif in this file
1123 // NDK compiler doesn't understand #if defined(__ANDROID__)..#else..#endif
1124 // So write as two separate #if blocks
1125 #if defined(__ANDROID__)
1126 
1127 // The following Android backtrace code was originally written by Eugene Shapovalov
1128 // on https://stackoverflow.com/questions/8115192/android-ndk-getting-the-backtrace
1129 struct android_backtrace_state {
1130     void **current;
1131     void **end;
1132 };
1133 
unwindCallback(struct _Unwind_Context * context,void * arg)1134 static _Unwind_Reason_Code unwindCallback( struct _Unwind_Context *context, void *arg )
1135 {
1136     android_backtrace_state *state = static_cast<android_backtrace_state *>( arg );
1137     uintptr_t pc = _Unwind_GetIP( context );
1138     if( pc ) {
1139         if( state->current == state->end ) {
1140             return _URC_END_OF_STACK;
1141         } else {
1142             *state->current++ = reinterpret_cast<void *>( pc );
1143         }
1144     }
1145     return _URC_NO_REASON;
1146 }
1147 
debug_write_backtrace(std::ostream & out)1148 void debug_write_backtrace( std::ostream &out )
1149 {
1150     const size_t max = 50;
1151     void *buffer[max];
1152     android_backtrace_state state = {buffer, buffer + max};
1153     _Unwind_Backtrace( unwindCallback, &state );
1154     const std::size_t count = state.current - buffer;
1155     // Start from 1: skip debug_write_backtrace ourselves
1156     for( size_t idx = 1; idx < count && idx < max; ++idx ) {
1157         const void *addr = buffer[idx];
1158         Dl_info info;
1159         if( dladdr( addr, &info ) && info.dli_sname ) {
1160             out << "#" << std::setw( 2 ) << idx << ": " << addr << " " << demangle( info.dli_sname ) << "\n";
1161         }
1162     }
1163 }
1164 #endif
1165 
DebugLog(DebugLevel lev,DebugClass cl)1166 std::ostream &DebugLog( DebugLevel lev, DebugClass cl )
1167 {
1168     if( lev & D_ERROR ) {
1169         error_observed = true;
1170     }
1171 
1172     // Error are always logged, they are important,
1173     // Messages from D_MAIN come from debugmsg and are equally important.
1174     if( ( lev & debugLevel && cl & debugClass ) || lev & D_ERROR || cl & D_MAIN ) {
1175         std::ostream &out = debugFile().get_file();
1176         out << std::endl;
1177         out << get_time() << " ";
1178         out << lev;
1179         if( cl != debugClass ) {
1180             out << cl;
1181         }
1182         out << ": ";
1183 
1184         // Backtrace on error.
1185 #if defined(BACKTRACE)
1186         // Push the first retrieved value back by a second so it won't match.
1187         static time_t next_backtrace = time( nullptr ) - 1;
1188         time_t now = time( nullptr );
1189         if( lev == D_ERROR && now >= next_backtrace ) {
1190             out << "(error message will follow backtrace)";
1191             debug_write_backtrace( out );
1192             time_t after = time( nullptr );
1193             // Cool down for 60s between backtrace emissions.
1194             next_backtrace = after + 60;
1195             out << "Backtrace emission took " << after - now << " seconds." << std::endl;
1196             out << "(continued from above) " << lev << ": ";
1197         }
1198 #endif
1199 
1200         return out;
1201     }
1202 
1203     static NullBuf nullBuf;
1204     static std::ostream nullStream( &nullBuf );
1205     return nullStream;
1206 }
1207 
operating_system()1208 std::string game_info::operating_system()
1209 {
1210 #if defined(__ANDROID__)
1211     return "Android";
1212 #elif defined(_WIN32)
1213     return "Windows";
1214 #elif defined(__linux__)
1215     return "Linux";
1216 #elif defined(unix) || defined(__unix__) || defined(__unix) || ( defined(__APPLE__) && defined(__MACH__) ) // unix; BSD; MacOs
1217 #if defined(__APPLE__) && defined(__MACH__)
1218     // The following include is **only** needed for the TARGET_xxx defines below and is only included if both of the above defines are true.
1219     // The whole function only relying on compiler defines, it is probably more meaningful to include it here and not mingle with the
1220     // headers at the top of the .cpp file.
1221 #include <TargetConditionals.h>
1222 #if TARGET_IPHONE_SIMULATOR == 1
1223     /* iOS in Xcode simulator */
1224     return "iOS Simulator";
1225 #elif TARGET_OS_IPHONE == 1
1226     /* iOS on iPhone, iPad, etc. */
1227     return "iOS";
1228 #elif TARGET_OS_MAC == 1
1229     /* OSX */
1230     return "MacOs";
1231 #endif // TARGET_IPHONE_SIMULATOR
1232 #elif defined(BSD)
1233     return "BSD";
1234 #else
1235     return "Unix";
1236 #endif // __APPLE__
1237 #else
1238     return "Unknown";
1239 #endif
1240 }
1241 
1242 #if !defined(__CYGWIN__) && !defined (__ANDROID__) && ( defined (__linux__) || defined(unix) || defined(__unix__) || defined(__unix) || ( defined(__APPLE__) && defined(__MACH__) ) || defined(BSD) ) // linux; unix; MacOs; BSD
1243 /** Execute a command with the shell by using `popen()`.
1244  * @param command The full command to execute.
1245  * @note The output buffer is limited to 512 characters.
1246  * @returns The result of the command (only stdout) or an empty string if there was a problem.
1247  */
shell_exec(const std::string & command)1248 static std::string shell_exec( const std::string &command )
1249 {
1250     std::vector<char> buffer( 512 );
1251     std::string output;
1252     try {
1253         std::unique_ptr<FILE, decltype( &pclose )> pipe( popen( command.c_str(), "r" ), pclose );
1254         if( pipe ) {
1255             while( fgets( buffer.data(), buffer.size(), pipe.get() ) != nullptr ) {
1256                 output += buffer.data();
1257             }
1258         }
1259     } catch( ... ) {
1260         output = "";
1261     }
1262     return output;
1263 }
1264 #endif
1265 
1266 #if defined (__ANDROID__)
1267 /** Get a precise version number for Android systems.
1268  * @note see:
1269  *    - https://stackoverflow.com/a/19777977/507028
1270  *    - https://github.com/pytorch/cpuinfo/blob/master/test/build.prop/galaxy-s7-us.log
1271  * @returns If successful, a string containing the Android system version, otherwise an empty string.
1272  */
android_version()1273 static std::string android_version()
1274 {
1275     std::string output;
1276 
1277     // buffer used for the __system_property_get() function.
1278     // note: according to android sources, it can't be greater than 92 chars (see 'PROP_VALUE_MAX' define in system_properties.h)
1279     std::vector<char> buffer( 255 );
1280 
1281     static const std::vector<std::pair<std::string, std::string>> system_properties = {
1282         // The manufacturer of the product/hardware; e.g. "Samsung", this is different than the carrier.
1283         { "ro.product.manufacturer", "Manufacturer" },
1284         // The end-user-visible name for the end product; .e.g. "SAMSUNG-SM-G930A" for a Samsung S7.
1285         { "ro.product.model", "Model" },
1286         // The Android system version; e.g. "6.0.1"
1287         { "ro.build.version.release", "Release" },
1288         // The internal value used by the underlying source control to represent this build; e.g "G930AUCS4APK1" for a Samsung S7 on 6.0.1.
1289         { "ro.build.version.incremental", "Incremental" },
1290     };
1291 
1292     for( const auto &entry : system_properties ) {
1293         int len = __system_property_get( entry.first.c_str(), &buffer[0] );
1294         std::string value;
1295         if( len <= 0 ) {
1296             // failed to get the property
1297             value = "<unknown>";
1298         } else {
1299             value = std::string( buffer.data() );
1300         }
1301         output.append( string_format( "%s: %s; ", entry.second, value ) );
1302     }
1303     return output;
1304 }
1305 
1306 #elif defined(BSD)
1307 
1308 /** Get a precise version number for BSD systems.
1309  * @note The code shells-out to call `uname -a`.
1310  * @returns If successful, a string containing the Linux system version, otherwise an empty string.
1311  */
bsd_version()1312 static std::string bsd_version()
1313 {
1314     std::string output;
1315     output = shell_exec( "uname -a" );
1316     if( !output.empty() ) {
1317         // remove trailing '\n', if any.
1318         output.erase( std::remove( output.begin(), output.end(), '\n' ),
1319                       output.end() );
1320     }
1321     return output;
1322 }
1323 
1324 #elif defined(__linux__)
1325 
1326 /** Get a precise version number for Linux systems.
1327  * @note The code shells-out to call `lsb_release -a`.
1328  * @returns If successful, a string containing the Linux system version, otherwise an empty string.
1329  */
linux_version()1330 static std::string linux_version()
1331 {
1332     std::string output;
1333     output = shell_exec( "lsb_release -a" );
1334     if( !output.empty() ) {
1335         // replace '\n' and '\t' in output.
1336         static const std::vector<std::pair<std::string, std::string>> to_replace = {
1337             {"\n", "; "},
1338             {"\t", " "}, // NOLINT(cata-text-style)
1339         };
1340         for( const auto &e : to_replace ) {
1341             std::string::size_type pos;
1342             while( ( pos = output.find( e.first ) ) != std::string::npos ) {
1343                 output.replace( pos, e.first.length(), e.second );
1344             }
1345         }
1346     }
1347     return output;
1348 }
1349 
1350 #elif defined(__APPLE__) && defined(__MACH__) && !defined(BSD)
1351 
1352 /** Get a precise version number for MacOs systems.
1353  * @note The code shells-out to call `sw_vers` with various options.
1354  * @returns If successful, a string containing the MacOS system version, otherwise an empty string.
1355  */
mac_os_version()1356 static std::string mac_os_version()
1357 {
1358     std::string output;
1359     static const std::vector<std::pair<std::string,  std::string>> commands = {
1360         { "sw_vers -productName", "Name" },
1361         { "sw_vers -productVersion", "Version" },
1362         { "sw_vers -buildVersion", "Build" },
1363     };
1364 
1365     for( const auto &entry : commands ) {
1366         std::string command_result = shell_exec( entry.first );
1367         if( command_result.empty() ) {
1368             command_result = "<unknown>";
1369         } else {
1370             // remove trailing '\n', if any.
1371             command_result.erase( std::remove( command_result.begin(), command_result.end(), '\n' ),
1372                                   command_result.end() );
1373         }
1374         output.append( string_format( "%s: %s; ", entry.second, command_result ) );
1375     }
1376     return output;
1377 }
1378 
1379 #elif defined (_WIN32)
1380 
1381 /** Get a precise version number for Windows systems.
1382  * @note Since Windows 10 all version-related APIs lie about the underlying system if the application is not Manifested (see VerifyVersionInfoA
1383  *     or GetVersionEx documentation for further explanation). In this function we use the registry or the native RtlGetVersion which both
1384  *     report correct versions and are compatible down to XP.
1385  * @returns If successful, a string containing the Windows system version number, otherwise an empty string.
1386  */
windows_version()1387 static std::string windows_version()
1388 {
1389     std::string output;
1390     HKEY handle_key;
1391     bool success = RegOpenKeyExA( HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)",
1392                                   0,
1393                                   KEY_QUERY_VALUE, &handle_key ) == ERROR_SUCCESS;
1394     if( success ) {
1395         DWORD value_type;
1396         constexpr DWORD c_buffer_size = 512;
1397         std::vector<BYTE> byte_buffer( c_buffer_size );
1398         DWORD buffer_size = c_buffer_size;
1399         DWORD major_version = 0;
1400         success = RegQueryValueExA( handle_key, "CurrentMajorVersionNumber", nullptr, &value_type,
1401                                     &byte_buffer[0], &buffer_size ) == ERROR_SUCCESS && value_type == REG_DWORD;
1402         if( success ) {
1403             major_version = *reinterpret_cast<const DWORD *>( &byte_buffer[0] );
1404             output.append( std::to_string( major_version ) );
1405         }
1406         if( success ) {
1407             buffer_size = c_buffer_size;
1408             success = RegQueryValueExA( handle_key, "CurrentMinorVersionNumber", nullptr, &value_type,
1409                                         &byte_buffer[0], &buffer_size ) == ERROR_SUCCESS && value_type == REG_DWORD;
1410             if( success ) {
1411                 const DWORD minor_version = *reinterpret_cast<const DWORD *>( &byte_buffer[0] );
1412                 output.append( "." );
1413                 output.append( std::to_string( minor_version ) );
1414             }
1415         }
1416         if( success && major_version == 10 ) {
1417             buffer_size = c_buffer_size;
1418             success = RegQueryValueExA( handle_key, "ReleaseId", nullptr, &value_type, &byte_buffer[0],
1419                                         &buffer_size ) == ERROR_SUCCESS && value_type == REG_SZ;
1420             if( success ) {
1421                 output.append( " " );
1422                 output.append( std::string( reinterpret_cast<char *>( byte_buffer.data() ) ) );
1423             }
1424         }
1425 
1426         RegCloseKey( handle_key );
1427     }
1428 
1429     if( !success ) {
1430 #if defined (__MINGW32__) || defined (__MINGW64__) || defined (__CYGWIN__) || defined (MSYS2)
1431         output = "MINGW/CYGWIN/MSYS2 on unknown Windows version";
1432 #else
1433         output.clear();
1434         using RtlGetVersion = LONG( WINAPI * )( PRTL_OSVERSIONINFOW );
1435         const HMODULE handle_ntdll = GetModuleHandleA( "ntdll" );
1436         if( handle_ntdll != nullptr ) {
1437             // Use union-based type-punning to convert function pointer
1438             // type without gcc warnings.
1439             union {
1440                 RtlGetVersion p;
1441                 FARPROC q;
1442             } rtl_get_version_func;
1443             rtl_get_version_func.q = GetProcAddress( handle_ntdll, "RtlGetVersion" );
1444             if( rtl_get_version_func.p != nullptr ) {
1445                 RTL_OSVERSIONINFOW os_version_info = RTL_OSVERSIONINFOW();
1446                 os_version_info.dwOSVersionInfoSize = sizeof( RTL_OSVERSIONINFOW );
1447                 if( rtl_get_version_func.p( &os_version_info ) == 0 ) { // NT_STATUS_SUCCESS = 0
1448                     output.append( string_format( "%i.%i %i", os_version_info.dwMajorVersion,
1449                                                   os_version_info.dwMinorVersion, os_version_info.dwBuildNumber ) );
1450                 }
1451             }
1452         }
1453 #endif
1454     }
1455     return output;
1456 }
1457 #endif // Various OS define tests
1458 
operating_system_version()1459 std::string game_info::operating_system_version()
1460 {
1461 #if defined(__ANDROID__)
1462     return android_version();
1463 #elif defined(BSD)
1464     return bsd_version();
1465 #elif defined(__linux__)
1466     return linux_version();
1467 #elif defined(__APPLE__) && defined(__MACH__) && !defined(BSD)
1468     return mac_os_version();
1469 #elif defined(_WIN32)
1470     return windows_version();
1471 #else
1472     return "<unknown>";
1473 #endif
1474 }
1475 
bitness()1476 std::string game_info::bitness()
1477 {
1478     if( sizeof( void * ) == 8 ) {
1479         return "64-bit";
1480     }
1481 
1482     if( sizeof( void * ) == 4 ) {
1483         return "32-bit";
1484     }
1485 
1486     return "Unknown";
1487 }
1488 
game_version()1489 std::string game_info::game_version()
1490 {
1491     return getVersionString();
1492 }
1493 
graphics_version()1494 std::string game_info::graphics_version()
1495 {
1496 #if defined(TILES)
1497     return "Tiles";
1498 #else
1499     return "Curses";
1500 #endif
1501 }
1502 
mods_loaded()1503 std::string game_info::mods_loaded()
1504 {
1505     if( world_generator->active_world == nullptr ) {
1506         return "No active world";
1507     }
1508 
1509     const std::vector<mod_id> &mod_ids = world_generator->active_world->active_mod_order;
1510     if( mod_ids.empty() ) {
1511         return "No loaded mods";
1512     }
1513 
1514     std::vector<std::string> mod_names;
1515     mod_names.reserve( mod_ids.size() );
1516     std::transform( mod_ids.begin(), mod_ids.end(),
1517     std::back_inserter( mod_names ), []( const mod_id & mod ) -> std::string {
1518         // e.g. "Dark Days Ahead [dda]".
1519         return string_format( "%s [%s]", mod->name(), mod->ident.str() );
1520     } );
1521 
1522     return join( mod_names, ",\n    " ); // note: 4 spaces for a slight offset.
1523 }
1524 
game_report()1525 std::string game_info::game_report()
1526 {
1527     std::string os_version = operating_system_version();
1528     if( os_version.empty() ) {
1529         os_version = "<unknown>";
1530     }
1531     std::stringstream report;
1532 
1533     std::string lang = get_option<std::string>( "USE_LANG" );
1534     std::string lang_translated;
1535     for( const options_manager::id_and_option &vItem : options_manager::get_lang_options() ) {
1536         if( vItem.first == lang ) {
1537             lang_translated = vItem.second.translated();
1538             break;
1539         }
1540     }
1541 
1542     report <<
1543            "- OS: " << operating_system() << "\n" <<
1544            "    - OS Version: " << os_version << "\n" <<
1545            "- Game Version: " << game_version() << " [" << bitness() << "]\n" <<
1546            "- Graphics Version: " << graphics_version() << "\n" <<
1547            "- Game Language: " << lang_translated << " [" << lang << "]\n" <<
1548            "- Mods loaded: [\n    " << mods_loaded() << "\n]\n";
1549 
1550     return report.str();
1551 }
1552 
1553 // vim:tw=72:sw=4:fdm=marker:fdl=0:
1554