The Battle for Wesnoth  1.17.10+dev
build_info.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2015 - 2022
3  by Iris Morelle <shadowm2006@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #define GETTEXT_DOMAIN "wesnoth-lib"
17 
18 #include "build_info.hpp"
19 
20 #include "desktop/version.hpp"
21 #include "game_config.hpp"
22 #include "filesystem.hpp"
23 #include "formatter.hpp"
24 #include "gettext.hpp"
27 #include "game_version.hpp"
28 #include "sound.hpp"
29 #include "video.hpp"
30 #include "addon/manager.hpp"
31 #include "sdl/point.hpp"
32 
33 #include <algorithm>
34 #include <fstream>
35 #include <iomanip>
36 
37 #include "lua/lua.h"
38 
39 #include <SDL2/SDL.h>
40 #include <SDL2/SDL_image.h>
41 #include <SDL2/SDL_mixer.h>
42 
43 #include <boost/algorithm/string.hpp>
44 #include <boost/predef.h>
45 #include <boost/version.hpp>
46 
47 #ifndef __APPLE__
48 #include <openssl/crypto.h>
49 #include <openssl/opensslv.h>
50 #endif
51 
52 #include <pango/pangocairo.h>
53 
54 #ifdef __APPLE__
55 // apple_notification.mm uses Foundation.h, which is an Objective-C header;
56 // but CoreFoundation.h is a C header which also defines these.
57 #include <CoreFoundation/CoreFoundation.h>
58 #endif
59 
60 namespace game_config
61 {
62 
63 namespace {
64 
65 struct version_table_manager
66 {
67  std::vector<std::string> compiled, linked, names;
68  std::vector<optional_feature> features;
69 
70  version_table_manager();
71 };
72 
73 const version_table_manager versions;
74 
75 #if 0
76 std::string format_version(unsigned a, unsigned b, unsigned c)
77 {
78  return formatter() << a << '.' << b << '.' << c;
79 }
80 #endif
81 
82 std::string format_version(const SDL_version& v)
83 {
84  return formatter() << static_cast<unsigned>(v.major) << '.'
85  << static_cast<unsigned>(v.minor) << '.'
86  << static_cast<unsigned>(v.patch);
87 }
88 
89 #ifndef __APPLE__
90 
91 std::string format_openssl_patch_level(uint8_t p)
92 {
93  return p <= 26
94  ? std::string(1, 'a' + static_cast<char>(p) - 1)
95  : "patch" + std::to_string(p);
96 }
97 
98 std::string format_openssl_version(long v)
99 {
100  int major, minor, fix, patch, status;
101  std::ostringstream fmt;
102 
103  //
104  // The people who maintain OpenSSL are not from this world. I suppose it's
105  // only fair that I'm the one who gets to try to make sense of their version
106  // encoding scheme. -- shadowm
107  //
108 
109  if(v < 0x0930L) {
110  // Pre-0.9.3 seems simpler times overall.
111  minor = v & 0x0F00L >> 8;
112  fix = v & 0x00F0L >> 4;
113  patch = v & 0x000FL;
114 
115  fmt << "0." << minor << '.' << fix;
116  if(patch) {
117  fmt << format_openssl_patch_level(patch);
118  }
119  } else {
120  //
121  // Note that they either assume the major version will never be greater than
122  // 9, they plan to use hexadecimal digits for versions 10.x.x through
123  // 15.x.x, or they expect long to be always > 32-bits by then. Who the hell
124  // knows, really.
125  //
126  major = (v & 0xF0000000L) >> 28;
127  minor = (v & 0x0FF00000L) >> 20;
128  fix = (v & 0x000FF000L) >> 12;
129  patch = (v & 0x00000FF0L) >> 4;
130  status = (v & 0x0000000FL);
131 
132  if(v < 0x00905100L) {
133  //
134  // From wiki.openssl.org (also mentioned in opensslv.h, in the most oblique
135  // fashion possible):
136  //
137  // "Versions between 0.9.3 and 0.9.5 had a version identifier with this interpretation:
138  // MMNNFFRBB major minor fix final beta/patch"
139  //
140  // Both the wiki and opensslv.h fail to accurately list actual version
141  // numbers that ended up used in the wild -- e.g. 0.9.3a is supposedly
142  // 0x0090301f when it really was 0x00903101.
143  //
144  const uint8_t is_final = (v & 0xF00L) >> 8;
145  status = is_final ? 0xF : 0;
146  patch = v & 0xFFL;
147  } else if(v < 0x00906000L) {
148  //
149  // Quoth opensslv.h:
150  //
151  // "For continuity reasons (because 0.9.5 is already out, and is coded
152  // 0x00905100), between 0.9.5 and 0.9.6 the coding of the patch level
153  // part is slightly different, by setting the highest bit. This means
154  // that 0.9.5a looks like this: 0x0090581f. At 0.9.6, we can start
155  // with 0x0090600S..."
156  //
157  patch ^= 1 << 7;
158  }
159 
160  fmt << major << '.' << minor << '.' << fix;
161 
162  if(patch) {
163  fmt << format_openssl_patch_level(patch);
164  }
165 
166  if(status == 0x0) {
167  fmt << "-dev";
168  } else if(status < 0xF) {
169  fmt << "-beta" << status;
170  }
171  }
172 
173  return fmt.str();
174 
175 }
176 
177 #endif
178 
179 version_table_manager::version_table_manager()
180  : compiled(LIB_COUNT, "")
181  , linked(LIB_COUNT, "")
182  , names(LIB_COUNT, "")
183  , features()
184 {
185  SDL_version sdl_version;
186 
187 
188  //
189  // SDL
190  //
191 
192  SDL_VERSION(&sdl_version);
193  compiled[LIB_SDL] = format_version(sdl_version);
194 
195  SDL_GetVersion(&sdl_version);
196  linked[LIB_SDL] = format_version(sdl_version);
197 
198  names[LIB_SDL] = "SDL";
199 
200  //
201  // SDL_image
202  //
203 
204  SDL_IMAGE_VERSION(&sdl_version);
205  compiled[LIB_SDL_IMAGE] = format_version(sdl_version);
206 
207  const SDL_version* sdl_rt_version = IMG_Linked_Version();
208  if(sdl_rt_version) {
209  linked[LIB_SDL_IMAGE] = format_version(*sdl_rt_version);
210  }
211 
212  names[LIB_SDL_IMAGE] = "SDL_image";
213 
214  //
215  // SDL_mixer
216  //
217 
218  SDL_MIXER_VERSION(&sdl_version);
219  compiled[LIB_SDL_MIXER] = format_version(sdl_version);
220 
221  sdl_rt_version = Mix_Linked_Version();
222  if(sdl_rt_version) {
223  linked[LIB_SDL_MIXER] = format_version(*sdl_rt_version);
224  }
225 
226  names[LIB_SDL_MIXER] = "SDL_mixer";
227 
228  //
229  // Boost
230  //
231 
232  compiled[LIB_BOOST] = BOOST_LIB_VERSION;
233  std::replace(compiled[LIB_BOOST].begin(), compiled[LIB_BOOST].end(), '_', '.');
234  names[LIB_BOOST] = "Boost";
235 
236  //
237  // Lua
238  //
239 
240  compiled[LIB_LUA] = LUA_VERSION_MAJOR "." LUA_VERSION_MINOR "." LUA_VERSION_RELEASE;
241  names[LIB_LUA] = "Lua";
242 
243  //
244  // OpenSSL/libcrypto
245  //
246 
247 #ifndef __APPLE__
248  compiled[LIB_CRYPTO] = format_openssl_version(OPENSSL_VERSION_NUMBER);
249  linked[LIB_CRYPTO] = format_openssl_version(SSLeay());
250  names[LIB_CRYPTO] = "OpenSSL/libcrypto";
251 #endif
252 
253  //
254  // Cairo
255  //
256 
257  compiled[LIB_CAIRO] = CAIRO_VERSION_STRING;
258  linked[LIB_CAIRO] = cairo_version_string();
259  names[LIB_CAIRO] = "Cairo";
260 
261  //
262  // Pango
263  //
264 
265  compiled[LIB_PANGO] = PANGO_VERSION_STRING;
266  linked[LIB_PANGO] = pango_version_string();
267  names[LIB_PANGO] = "Pango";
268 
269  //
270  // Features table.
271  //
272 
273  features.emplace_back(N_("feature^Lua console completion"));
274 #ifdef HAVE_HISTORY
275  features.back().enabled = true;
276 #endif
277 
278 #ifdef _X11
279 
280  features.emplace_back(N_("feature^D-Bus notifications back end"));
281 #ifdef HAVE_LIBDBUS
282  features.back().enabled = true;
283 #endif
284 
285 #endif /* _X11 */
286 
287 #ifdef _WIN32
288  // Always compiled in.
289  features.emplace_back(N_("feature^Win32 notifications back end"));
290  features.back().enabled = true;
291 #endif
292 
293 #ifdef __APPLE__
294  // Always compiled in.
295  features.emplace_back(N_("feature^Cocoa notifications back end"));
296  features.back().enabled = true;
297 #endif /* __APPLE__ */
298 }
299 
300 const std::string empty_version = "";
301 
302 } // end anonymous namespace 1
303 
304 std::string build_arch()
305 {
306 #if BOOST_ARCH_X86_64
307  return "x86_64";
308 #elif BOOST_ARCH_X86_32
309  return "x86";
310 #elif BOOST_ARCH_ARM && (defined(__arm64) || defined(_M_ARM64))
311  return "arm64";
312 #elif BOOST_ARCH_ARM
313  return "arm";
314 #elif BOOST_ARCH_IA64
315  return "ia64";
316 #elif BOOST_ARCH_PPC
317  return "ppc";
318 #elif BOOST_ARCH_ALPHA
319  return "alpha";
320 #elif BOOST_ARCH_MIPS
321  return "mips";
322 #elif BOOST_ARCH_SPARC
323  return "sparc";
324 #else
325  // Congratulations, you're running Wesnoth on an exotic platform -- either that or you live in
326  // the foretold future where x86 and ARM stopped being the dominant CPU architectures for the
327  // general-purpose consumer market. If you want to add label support for your platform, check
328  // out the Boost.Predef library's documentation and alter the code above accordingly.
329  //
330  // On the other hand, if you got here looking for Wesnoth's biggest secret let me just say
331  // right here and now that Irdya is round. There, I said the thing that nobody has dared say
332  // in mainline content before.
333  return _("cpu_architecture^<unknown>");
334 #endif
335 }
336 
337 std::vector<optional_feature> optional_features_table(bool localize)
338 {
339  std::vector<optional_feature> res = versions.features;
340 
341  for(std::size_t k = 0; k < res.size(); ++k) {
342  if(localize) {
343  res[k].name = _(res[k].name.c_str());
344  } else {
345  // Strip annotation carets ("blah blah^actual text here") from translatable
346  // strings.
347  const auto caret_pos = res[k].name.find('^');
348  if(caret_pos != std::string::npos) {
349  res[k].name.erase(0, caret_pos + 1);
350  }
351  }
352  }
353  return res;
354 }
355 
356 const std::string& library_build_version(LIBRARY_ID lib)
357 {
358  if(lib >= LIB_COUNT) {
359  return empty_version;
360  }
361 
362  return versions.compiled[lib];
363 }
364 
365 const std::string& library_runtime_version(LIBRARY_ID lib)
366 {
367  if(lib >= LIB_COUNT) {
368  return empty_version;
369  }
370 
371  return versions.linked[lib];
372 }
373 
374 const std::string& library_name(LIBRARY_ID lib)
375 {
376  if(lib >= LIB_COUNT) {
377  return empty_version;
378  }
379 
380  return versions.names[lib];
381 }
382 
383 std::string dist_channel_id()
384 {
385  std::string info;
386  std::ifstream infofile(game_config::path + "/data/dist");
387  if(infofile.is_open()) {
388  std::getline(infofile, info);
389  infofile.close();
390  boost::trim(info);
391  }
392 
393  if(info.empty()) {
394  return "Default";
395  }
396 
397  return info;
398 }
399 
400 namespace {
401 
402 /**
403  * Formats items into a tidy 2-column list with a fixed-length first column.
404  */
405 class list_formatter
406 {
407 public:
408  using list_entry = std::pair<std::string, std::string>;
409  using contents_list = std::vector<list_entry>;
410 
411  list_formatter(const std::string& heading, const contents_list& contents = {}, const std::string& empty_placeholder = "")
412  : heading_(heading)
413  , placeholder_(empty_placeholder)
414  , contents_(contents)
415  {
416  }
417 
418  void insert(const std::string& label, const std::string& value)
419  {
420  contents_.emplace_back(label, value);
421  }
422 
423  void set_placeholder(const std::string& placeholder)
424  {
425  placeholder_ = placeholder;
426  }
427 
428  void stream_put(std::ostream& os) const;
429 
430 private:
431  static const char heading_delimiter;
432  static const std::string label_delimiter;
433 
434  std::string heading_;
435  std::string placeholder_;
436 
437  contents_list contents_;
438 };
439 
440 const char list_formatter::heading_delimiter = '=';
441 const std::string list_formatter::label_delimiter = ": ";
442 
443 void list_formatter::stream_put(std::ostream& os) const
444 {
445  if(!heading_.empty()) {
446  os << heading_ << '\n' << std::string(utf8::size(heading_), heading_delimiter) << "\n\n";
447  }
448 
449  if(contents_.empty() && !placeholder_.empty()) {
450  os << placeholder_ << '\n';
451  } else if(!contents_.empty()) {
452  auto label_length_comparator = [](const list_entry& a, const list_entry& b)
453  {
454  return utf8::size(a.first) < utf8::size(b.first);
455  };
456 
457  const auto longest_entry_label = std::max_element(contents_.begin(), contents_.end(), label_length_comparator);
458  const std::size_t min_length = longest_entry_label != contents_.end()
459  ? utf8::size(label_delimiter) + utf8::size(longest_entry_label->first)
460  : 0;
461 
462  // Save stream attributes for resetting them later after completing the loop
463  const std::size_t prev_width = os.width();
464  const std::ostream::fmtflags prev_flags = os.flags();
465 
466  os << std::left;
467 
468  for(const auto& entry : contents_) {
469  os << std::setw(min_length) << entry.first + label_delimiter << entry.second << '\n';
470  }
471 
472  os.width(prev_width);
473  os.flags(prev_flags);
474  }
475 
476  os << '\n';
477 }
478 
479 std::ostream& operator<<(std::ostream& os, const list_formatter& fmt)
480 {
481  fmt.stream_put(os);
482  return os;
483 }
484 
485 list_formatter library_versions_report_internal(const std::string& heading = "")
486 {
487  list_formatter fmt{heading};
488 
489  for(unsigned n = 0; n < LIB_COUNT; ++n)
490  {
491  if(versions.names[n].empty()) {
492  continue;
493  }
494 
495  std::string text = versions.compiled[n];
496  if(!versions.linked[n].empty()) {
497  text += " (runtime " + versions.linked[n] + ")";
498  }
499 
500  fmt.insert(versions.names[n], text);
501  }
502 
503  return fmt;
504 }
505 
506 list_formatter optional_features_report_internal(const std::string& heading = "")
507 {
508  list_formatter fmt{heading};
509 
510  const std::vector<optional_feature>& features = optional_features_table(false);
511 
512  for(const auto& feature : features) {
513  fmt.insert(feature.name, feature.enabled ? "yes" : "no");
514  }
515 
516  return fmt;
517 }
518 
519 inline std::string geometry_to_string(point p)
520 {
521  return std::to_string(p.x) + 'x' + std::to_string(p.y);
522 }
523 
524 std::string format_sdl_driver_list(std::vector<std::string> drivers, const std::string& current_driver)
525 {
526  bool found_current_driver = false;
527 
528  for(auto& drvname : drivers) {
529  if(current_driver == drvname) {
530  found_current_driver = true;
531  drvname = "[" + current_driver + "]";
532  }
533  }
534 
535  if(drivers.empty() || !found_current_driver) {
536  // This shouldn't happen but SDL is weird at times so whatevs
537  drivers.emplace_back("[" + current_driver + "]");
538  }
539 
540  return utils::join(drivers, " ");
541 }
542 
543 list_formatter video_settings_report_internal(const std::string& heading = "")
544 {
545  list_formatter fmt{heading};
546 
547  std::string placeholder;
548 
549  if(video::headless()) {
550  placeholder = "Running in non-interactive mode.";
551  }
552 
553  if(!placeholder.empty()) {
554  fmt.set_placeholder(placeholder);
555  return fmt;
556  }
557 
558  const auto& current_driver = video::current_driver();
559  auto drivers = video::enumerate_drivers();
560 
561  fmt.insert("SDL video drivers", format_sdl_driver_list(drivers, current_driver));
562  fmt.insert("Window size", geometry_to_string(video::current_resolution()));
563  fmt.insert("Game canvas size", geometry_to_string(video::game_canvas_size()));
564  fmt.insert("Final render target size", geometry_to_string(video::output_size()));
565  fmt.insert("Screen refresh rate", std::to_string(video::current_refresh_rate()));
566 
567  return fmt;
568 }
569 
570 list_formatter sound_settings_report_internal(const std::string& heading = "")
571 {
572  list_formatter fmt{heading};
573 
574  const auto& driver_status = sound::driver_status::query();
575 
576  if(!driver_status.initialized) {
577  fmt.set_placeholder("Audio not initialized.");
578  return fmt;
579  }
580 
581  const auto& current_driver = sound::current_driver();
582  auto drivers = sound::enumerate_drivers();
583 
584  static std::map<uint16_t, std::string> audio_format_names = {
585  // 8 bits
586  { AUDIO_U8, "unsigned 8 bit" },
587  { AUDIO_S8, "signed 8 bit" },
588  // 16 bits
589  { AUDIO_U16LSB, "unsigned 16 bit little-endian" },
590  { AUDIO_U16MSB, "unsigned 16 bit big-endian" },
591  { AUDIO_S16LSB, "signed 16 bit little-endian" },
592  { AUDIO_S16MSB, "signed 16 bit big-endian" },
593  // 32 bits
594  { AUDIO_S32LSB, "signed 32 bit little-endian" },
595  { AUDIO_S32MSB, "signed 32 bit big-endian" },
596  { AUDIO_F32LSB, "signed 32 bit floating point little-endian" },
597  { AUDIO_F32MSB, "signed 32 bit floating point big-endian" },
598  };
599 
600  auto fmt_names_it = audio_format_names.find(driver_status.format);
601  // If we don't recognize the format id just print the raw number
602  const std::string fmt_name = fmt_names_it != audio_format_names.end()
603  ? fmt_names_it->second
604  : formatter() << "0x" << std::setfill('0') << std::setw(2*sizeof(driver_status.format)) << std::hex << std::uppercase << driver_status.format;
605 
606  fmt.insert("SDL audio drivers", format_sdl_driver_list(drivers, current_driver));
607  fmt.insert("Number of channels", std::to_string(driver_status.channels));
608  fmt.insert("Output rate", std::to_string(driver_status.frequency) + " Hz");
609  fmt.insert("Sample format", fmt_name);
610  fmt.insert("Sample size", std::to_string(driver_status.chunk_size) + " bytes");
611 
612  return fmt;
613 }
614 
615 } // end anonymous namespace 2
616 
618 {
619  return formatter{} << library_versions_report_internal();
620 }
621 
623 {
624  return formatter{} << optional_features_report_internal();
625 }
626 
627 std::string full_build_report()
628 {
629  list_formatter::contents_list paths{
630  {"Data dir", game_config::path},
631  {"User config dir", filesystem::get_user_config_dir()},
632  {"User data dir", filesystem::get_user_data_dir()},
633  {"Saves dir", filesystem::get_saves_dir()},
634  {"Add-ons dir", filesystem::get_addons_dir()},
635  {"Cache dir", filesystem::get_cache_dir()},
636  {"Logs dir", filesystem::get_logs_dir()},
637  };
638 
639  // Obfuscate usernames in paths
640  for(auto& entry : paths) {
641  entry.second = filesystem::sanitize_path(entry.second);
642  }
643 
644  list_formatter::contents_list addons;
645 
646  for(const auto& addon_info : installed_addons_and_versions()) {
647  addons.emplace_back(addon_info.first, addon_info.second);
648  }
649 
650  std::ostringstream o;
651 
652  o << "The Battle for Wesnoth version " << game_config::revision << " " << build_arch() << '\n'
653  << "Running on " << desktop::os_version() << '\n'
654  << "Distribution channel: " << dist_channel_id() << '\n'
655  << '\n'
656  << list_formatter{"Game paths", paths}
657  << library_versions_report_internal("Libraries")
658  << optional_features_report_internal("Features")
659  << video_settings_report_internal("Current video settings")
660  << sound_settings_report_internal("Current audio settings")
661  << list_formatter("Installed add-ons", addons, "No add-ons installed.");
662 
663  return o.str();
664 }
665 
666 } // end namespace game_config
std::string heading_
Definition: build_info.cpp:434
static const char heading_delimiter
Definition: build_info.cpp:431
std::string library_versions_report()
Produce a plain-text report of library versions suitable for stdout/stderr.
Definition: build_info.cpp:617
Interfaces for manipulating version numbers of engine, add-ons, etc.
std::string current_driver()
Definition: sound.cpp:412
std::string current_driver()
The current video driver in use, or else "<not initialized>".
Definition: video.cpp:643
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
static const std::string label_delimiter
Definition: build_info.cpp:432
std::string optional_features_report()
Produce a plain-text report of features suitable for stdout/stderr.
Definition: build_info.cpp:622
logger & info()
Definition: log.cpp:182
#define a
std::string placeholder_
Definition: build_info.cpp:435
int current_refresh_rate()
The refresh rate of the screen.
Definition: video.cpp:476
std::string get_logs_dir()
Definition: filesystem.cpp:812
contents_list contents_
Definition: build_info.cpp:437
STL namespace.
const std::string & library_build_version(LIBRARY_ID lib)
Retrieve the build-time version number of the given library.
Definition: build_info.cpp:356
std::vector< optional_feature > optional_features_table(bool localize)
Retrieve the features table.
Definition: build_info.cpp:337
static std::string _(const char *str)
Definition: gettext.hpp:93
std::string get_saves_dir()
std::vector< std::string > enumerate_drivers()
A list of available video drivers.
Definition: video.cpp:649
point current_resolution()
The current window size in desktop coordinates.
Definition: video.cpp:739
#define b
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
const std::string & library_name(LIBRARY_ID lib)
Retrieve the user-visible name for the given library.
Definition: build_info.cpp:374
point output_size()
Returns the size of the final render target.
Definition: video.cpp:400
std::string label
What to show in the filter&#39;s drop-down list.
Definition: manager.cpp:217
std::vector< optional_feature > features
Definition: build_info.cpp:68
std::ostringstream wrapper.
Definition: formatter.hpp:39
std::string get_user_data_dir()
Definition: filesystem.cpp:807
std::string dist_channel_id()
Return the distribution channel identifier, or "Default" if missing.
Definition: build_info.cpp:383
bool headless()
The game is running headless.
Definition: video.cpp:143
std::string path
Definition: game_config.cpp:39
std::string build_arch()
Obtain the processor architecture for this build.
Definition: build_info.cpp:304
std::string sanitize_path(const std::string &path)
Sanitizes a path to remove references to the user&#39;s name.
std::ostream & operator<<(std::ostream &s, const ai::attack_result &r)
Definition: actions.cpp:1134
Platform identification and version information functions.
std::string get_cache_dir()
Definition: filesystem.cpp:817
std::vector< std::string > linked
Definition: build_info.cpp:67
const std::string revision
mock_party p
Game configuration data as global variables.
Definition: build_info.cpp:60
std::string os_version()
Returns a string with the running OS name and version information.
Definition: version.cpp:217
std::vector< std::string > names
Definition: build_info.cpp:67
point game_canvas_size()
The size of the game canvas, in drawing coordinates / game pixels.
Definition: video.cpp:422
const std::string & library_runtime_version(LIBRARY_ID lib)
Retrieve the runtime version number of the given library.
Definition: build_info.cpp:365
Holds a 2D point.
Definition: point.hpp:24
std::string & insert(std::string &str, const std::size_t pos, const std::string &insert)
Insert a UTF-8 string at the specified position.
Definition: unicode.cpp:100
Declarations for File-IO.
#define N_(String)
Definition: gettext.hpp:101
std::string get_user_config_dir()
Definition: filesystem.cpp:778
std::string get_addons_dir()
void trim(std::string_view &s)
static driver_status query()
Definition: sound.cpp:431
mock_char c
std::vector< std::string > enumerate_drivers()
Definition: sound.cpp:418
static map_location::DIRECTION n
std::vector< std::string > compiled
Definition: build_info.cpp:67
std::string full_build_report()
Produce a bug report-style info dump.
Definition: build_info.cpp:627
std::map< std::string, std::string > installed_addons_and_versions()
Retrieves the ids and versions of all installed add-ons.
Definition: manager.cpp:183