1 /*
2 Copyright (C) 2015 - 2018 by Iris Morelle <shadowm2006@gmail.com>
3 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY.
11
12 See the COPYING file for more details.
13 */
14
15 #define GETTEXT_DOMAIN "wesnoth-lib"
16
17 #include "build_info.hpp"
18
19 #include "desktop/version.hpp"
20 #include "game_config.hpp"
21 #include "filesystem.hpp"
22 #include "formatter.hpp"
23 #include "gettext.hpp"
24 #include "serialization/unicode.hpp"
25 #include "video.hpp"
26 #include "addon/manager.hpp"
27
28 #include <algorithm>
29 #include <fstream>
30
31 #include <SDL2/SDL.h>
32 #include <SDL2/SDL_image.h>
33 #include <SDL2/SDL_mixer.h>
34 #include <SDL2/SDL_ttf.h>
35
36 #include <boost/algorithm/string.hpp>
37 #include <boost/version.hpp>
38
39 #ifndef __APPLE__
40 #include <openssl/crypto.h>
41 #include <openssl/opensslv.h>
42 #endif
43
44 #include <pango/pangocairo.h>
45
46 #ifdef __APPLE__
47 // apple_notification.mm uses Foundation.h, which is an Objective-C header;
48 // but CoreFoundation.h is a C header which also defines these.
49 #include <CoreFoundation/CoreFoundation.h>
50 #endif
51
52 namespace game_config
53 {
54
55 namespace {
56
57 struct version_table_manager
58 {
59 std::vector<std::string> compiled, linked, names;
60 std::vector<optional_feature> features;
61
62 version_table_manager();
63 };
64
65 const version_table_manager versions;
66
67 #if 0
68 std::string format_version(unsigned a, unsigned b, unsigned c)
69 {
70 return formatter() << a << '.' << b << '.' << c;
71 }
72 #endif
73
format_version(const SDL_version & v)74 std::string format_version(const SDL_version& v)
75 {
76 return formatter() << static_cast<unsigned>(v.major) << '.'
77 << static_cast<unsigned>(v.minor) << '.'
78 << static_cast<unsigned>(v.patch);
79 }
80
81 #ifndef __APPLE__
82
format_openssl_patch_level(uint8_t p)83 std::string format_openssl_patch_level(uint8_t p)
84 {
85 return p <= 26
86 ? std::string(1, 'a' + static_cast<char>(p) - 1)
87 : "patch" + std::to_string(p);
88 }
89
format_openssl_version(long v)90 std::string format_openssl_version(long v)
91 {
92 int major, minor, fix, patch, status;
93 std::ostringstream fmt;
94
95 //
96 // The people who maintain OpenSSL are not from this world. I suppose it's
97 // only fair that I'm the one who gets to try to make sense of their version
98 // encoding scheme. -- shadowm
99 //
100
101 if(v < 0x0930L) {
102 // Pre-0.9.3 seems simpler times overall.
103 minor = v & 0x0F00L >> 8;
104 fix = v & 0x00F0L >> 4;
105 patch = v & 0x000FL;
106
107 fmt << "0." << minor << '.' << fix;
108 if(patch) {
109 fmt << format_openssl_patch_level(patch);
110 }
111 } else {
112 //
113 // Note that they either assume the major version will never be greater than
114 // 9, they plan to use hexadecimal digits for versions 10.x.x through
115 // 15.x.x, or they expect long to be always > 32-bits by then. Who the hell
116 // knows, really.
117 //
118 major = (v & 0xF0000000L) >> 28;
119 minor = (v & 0x0FF00000L) >> 20;
120 fix = (v & 0x000FF000L) >> 12;
121 patch = (v & 0x00000FF0L) >> 4;
122 status = (v & 0x0000000FL);
123
124 if(v < 0x00905100L) {
125 //
126 // From wiki.openssl.org (also mentioned in opensslv.h, in the most oblique
127 // fashion possible):
128 //
129 // "Versions between 0.9.3 and 0.9.5 had a version identifier with this interpretation:
130 // MMNNFFRBB major minor fix final beta/patch"
131 //
132 // Both the wiki and opensslv.h fail to accurately list actual version
133 // numbers that ended up used in the wild -- e.g. 0.9.3a is supposedly
134 // 0x0090301f when it really was 0x00903101.
135 //
136 const uint8_t is_final = (v & 0xF00L) >> 8;
137 status = is_final ? 0xF : 0;
138 patch = v & 0xFFL;
139 } else if(v < 0x00906000L) {
140 //
141 // Quoth opensslv.h:
142 //
143 // "For continuity reasons (because 0.9.5 is already out, and is coded
144 // 0x00905100), between 0.9.5 and 0.9.6 the coding of the patch level
145 // part is slightly different, by setting the highest bit. This means
146 // that 0.9.5a looks like this: 0x0090581f. At 0.9.6, we can start
147 // with 0x0090600S..."
148 //
149 patch ^= 1 << 7;
150 }
151
152 fmt << major << '.' << minor << '.' << fix;
153
154 if(patch) {
155 fmt << format_openssl_patch_level(patch);
156 }
157
158 if(status == 0x0) {
159 fmt << "-dev";
160 } else if(status < 0xF) {
161 fmt << "-beta" << status;
162 }
163 }
164
165 return fmt.str();
166
167 }
168
169 #endif
170
version_table_manager()171 version_table_manager::version_table_manager()
172 : compiled(LIB_COUNT, "")
173 , linked(LIB_COUNT, "")
174 , names(LIB_COUNT, "")
175 , features()
176 {
177 SDL_version sdl_version;
178
179
180 //
181 // SDL
182 //
183
184 SDL_VERSION(&sdl_version);
185 compiled[LIB_SDL] = format_version(sdl_version);
186
187 SDL_GetVersion(&sdl_version);
188 linked[LIB_SDL] = format_version(sdl_version);
189
190 names[LIB_SDL] = "SDL";
191
192 //
193 // SDL_image
194 //
195
196 SDL_IMAGE_VERSION(&sdl_version);
197 compiled[LIB_SDL_IMAGE] = format_version(sdl_version);
198
199 const SDL_version* sdl_rt_version = IMG_Linked_Version();
200 if(sdl_rt_version) {
201 linked[LIB_SDL_IMAGE] = format_version(*sdl_rt_version);
202 }
203
204 names[LIB_SDL_IMAGE] = "SDL_image";
205
206 //
207 // SDL_mixer
208 //
209
210 SDL_MIXER_VERSION(&sdl_version);
211 compiled[LIB_SDL_MIXER] = format_version(sdl_version);
212
213 sdl_rt_version = Mix_Linked_Version();
214 if(sdl_rt_version) {
215 linked[LIB_SDL_MIXER] = format_version(*sdl_rt_version);
216 }
217
218 names[LIB_SDL_MIXER] = "SDL_mixer";
219
220 //
221 // SDL_ttf
222 //
223
224 SDL_TTF_VERSION(&sdl_version);
225 compiled[LIB_SDL_TTF] = format_version(sdl_version);
226
227 sdl_rt_version = TTF_Linked_Version();
228 if(sdl_rt_version) {
229 linked[LIB_SDL_TTF] = format_version(*sdl_rt_version);
230 }
231
232 names[LIB_SDL_TTF] = "SDL_ttf";
233
234 //
235 // Boost
236 //
237
238 compiled[LIB_BOOST] = BOOST_LIB_VERSION;
239 std::replace(compiled[LIB_BOOST].begin(), compiled[LIB_BOOST].end(), '_', '.');
240 names[LIB_BOOST] = "Boost";
241
242 //
243 // OpenSSL/libcrypto
244 //
245
246 #ifndef __APPLE__
247 compiled[LIB_CRYPTO] = format_openssl_version(OPENSSL_VERSION_NUMBER);
248 linked[LIB_CRYPTO] = format_openssl_version(SSLeay());
249 names[LIB_CRYPTO] = "OpenSSL/libcrypto";
250 #endif
251
252 //
253 // Cairo
254 //
255
256 compiled[LIB_CAIRO] = CAIRO_VERSION_STRING;
257 linked[LIB_CAIRO] = cairo_version_string();
258 names[LIB_CAIRO] = "Cairo";
259
260 //
261 // Pango
262 //
263
264 compiled[LIB_PANGO] = PANGO_VERSION_STRING;
265 linked[LIB_PANGO] = pango_version_string();
266 names[LIB_PANGO] = "Pango";
267
268 //
269 // Features table.
270 //
271
272 features.emplace_back(N_("feature^JPEG screenshots"));
273 #ifdef SDL_IMAGE_VERSION_ATLEAST
274 #if SDL_IMAGE_VERSION_ATLEAST(2, 0, 2)
275 features.back().enabled = true;
276 #endif
277 #endif
278
279 features.emplace_back(N_("feature^Lua console completion"));
280 #ifdef HAVE_HISTORY
281 features.back().enabled = true;
282 #endif
283
284 features.emplace_back(N_("feature^Legacy bidirectional rendering"));
285 #ifdef HAVE_FRIBIDI
286 features.back().enabled = true;
287 #endif
288
289 #ifdef _X11
290
291 features.emplace_back(N_("feature^D-Bus notifications back end"));
292 #ifdef HAVE_LIBDBUS
293 features.back().enabled = true;
294 #endif
295
296 #endif /* _X11 */
297
298 #ifdef _WIN32
299 // Always compiled in.
300 features.emplace_back(N_("feature^Win32 notifications back end"));
301 features.back().enabled = true;
302 #endif
303
304 #ifdef __APPLE__
305 // Always compiled in.
306 features.emplace_back(N_("feature^Cocoa notifications back end"));
307 features.back().enabled = true;
308 #endif /* __APPLE__ */
309 }
310
311 const std::string empty_version = "";
312
313 } // end anonymous namespace 1
314
optional_features_table()315 std::vector<optional_feature> optional_features_table()
316 {
317 std::vector<optional_feature> res = versions.features;
318
319 for(size_t k = 0; k < res.size(); ++k) {
320 res[k].name = _(res[k].name.c_str());
321 }
322 return res;
323 }
324
library_build_version(LIBRARY_ID lib)325 const std::string& library_build_version(LIBRARY_ID lib)
326 {
327 if(lib >= LIB_COUNT) {
328 return empty_version;
329 }
330
331 return versions.compiled[lib];
332 }
333
library_runtime_version(LIBRARY_ID lib)334 const std::string& library_runtime_version(LIBRARY_ID lib)
335 {
336 if(lib >= LIB_COUNT) {
337 return empty_version;
338 }
339
340 return versions.linked[lib];
341 }
342
library_name(LIBRARY_ID lib)343 const std::string& library_name(LIBRARY_ID lib)
344 {
345 if(lib >= LIB_COUNT) {
346 return empty_version;
347 }
348
349 return versions.names[lib];
350 }
351
dist_channel_id()352 std::string dist_channel_id()
353 {
354 std::string info;
355 std::ifstream infofile(game_config::path + "/data/dist");
356 if(infofile.is_open()) {
357 std::getline(infofile, info);
358 infofile.close();
359 boost::trim(info);
360 }
361
362 if(info.empty()) {
363 return "Default";
364 }
365
366 return info;
367 }
368
369 namespace {
370
strlen_comparator(const std::string & a,const std::string & b)371 bool strlen_comparator(const std::string& a, const std::string& b)
372 {
373 return a.length() < b.length();
374 }
375
max_strlen(const std::vector<std::string> & strs)376 size_t max_strlen(const std::vector<std::string>& strs)
377 {
378 const std::vector<std::string>::const_iterator it =
379 std::max_element(strs.begin(), strs.end(), strlen_comparator);
380
381 return it != strs.end() ? it->length() : 0;
382 }
383
report_heading(const std::string & heading_text)384 std::string report_heading(const std::string& heading_text)
385 {
386 return heading_text + '\n' + std::string(utf8::size(heading_text), '=') + '\n';
387 }
388
389 } // end anonymous namespace 2
390
library_versions_report()391 std::string library_versions_report()
392 {
393 std::ostringstream o;
394
395 const size_t col2_start = max_strlen(versions.names) + 2;
396 const size_t col3_start = max_strlen(versions.compiled) + 1;
397
398 for(unsigned n = 0; n < LIB_COUNT; ++n)
399 {
400 const std::string& name = versions.names[n];
401 const std::string& compiled = versions.compiled[n];
402 const std::string& linked = versions.linked[n];
403
404 if(name.empty()) {
405 continue;
406 }
407
408 o << name << ": ";
409
410 const size_t pos2 = name.length() + 2;
411 if(pos2 < col2_start) {
412 o << std::string(col2_start - pos2, ' ');
413 }
414
415 o << compiled;
416
417 if(!linked.empty()) {
418 const size_t pos3 = compiled.length() + 1;
419 if(pos3 < col3_start) {
420 o << std::string(col3_start - pos3, ' ');
421 }
422 o << " (runtime " << linked << ")";
423 }
424
425 o << '\n';
426 }
427
428 return o.str();
429 }
430
optional_features_report()431 std::string optional_features_report()
432 {
433 // Yes, it's for stdout/stderr but we still want the localized version so
434 // that the context prefixes are stripped.
435 const std::vector<optional_feature>& features = optional_features_table();
436
437 size_t col2_start = 0;
438
439 for(size_t k = 0; k < features.size(); ++k)
440 {
441 col2_start = std::max(col2_start, features[k].name.length() + 2);
442 }
443
444 std::ostringstream o;
445
446 for(size_t k = 0; k < features.size(); ++k)
447 {
448 const optional_feature& f = features[k];
449
450 o << f.name << ": ";
451
452 const size_t pos2 = f.name.length() + 2;
453 if(pos2 < col2_start) {
454 o << std::string(col2_start - pos2, ' ');
455 }
456
457 o << (f.enabled ? "yes" : "no") << '\n';
458 }
459
460 return o.str();
461 }
462
full_build_report()463 std::string full_build_report()
464 {
465 std::ostringstream o;
466
467 o << "The Battle for Wesnoth version " << game_config::revision << '\n'
468 << "Running on " << desktop::os_version() << '\n'
469 << "Distribution channel: " << dist_channel_id() << '\n'
470 << '\n'
471 << report_heading("Game paths")
472 << '\n'
473 << "Data dir: " << filesystem::sanitize_path(game_config::path) << '\n'
474 << "User config dir: " << filesystem::sanitize_path(filesystem::get_user_config_dir()) << '\n'
475 << "User data dir: " << filesystem::sanitize_path(filesystem::get_user_data_dir()) << '\n'
476 << "Saves dir: " << filesystem::sanitize_path(filesystem::get_saves_dir()) << '\n'
477 << "Add-ons dir: " << filesystem::sanitize_path(filesystem::get_addons_dir()) << '\n'
478 << "Cache dir: " << filesystem::sanitize_path(filesystem::get_cache_dir()) << '\n'
479 << '\n'
480 << report_heading("Libraries")
481 << '\n'
482 << game_config::library_versions_report()
483 << '\n'
484 << report_heading("Features")
485 << '\n'
486 << game_config::optional_features_report()
487 << '\n'
488 << report_heading("Current video settings")
489 << '\n'
490 << CVideo::video_settings_report()
491 << '\n'
492 << report_heading("Installed add-ons")
493 << '\n';
494 const auto installed_addons = installed_addons_and_versions();
495 if(installed_addons.size() == 0)
496 {
497 o << "No add-ons installed.\n";
498 }
499 else
500 {
501 for(const auto& addon_info : installed_addons)
502 {
503 o << addon_info.first << " : " << addon_info.second << '\n';
504 }
505 }
506 o << '\n';
507 return o.str();
508 }
509
510 } // end namespace game_config
511