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