Changeset - r27914:bfd64f30ccdf
[Not reviewed]
master
0 12 2
Patric Stout - 15 months ago 2023-09-14 18:13:27
truebrain@openttd.org
Change: store crash logs in JSON format (#11232)
14 files changed with 730 insertions and 752 deletions:
0 comments (0 inline, 0 general)
src/CMakeLists.txt
Show inline comments
 
@@ -432,6 +432,8 @@ add_files(
 
    subsidy_func.h
 
    subsidy_gui.cpp
 
    subsidy_type.h
 
    survey.cpp
 
    survey.h
 
    tar_type.h
 
    terraform_cmd.cpp
 
    terraform_cmd.h
src/crashlog.cpp
Show inline comments
 
@@ -9,297 +9,60 @@
 

	
 
#include "stdafx.h"
 
#include "crashlog.h"
 
#include "survey.h"
 
#include "gamelog.h"
 
#include "timer/timer_game_calendar.h"
 
#include "map_func.h"
 
#include "rev.h"
 
#include "strings_func.h"
 
#include "blitter/factory.hpp"
 
#include "base_media_base.h"
 
#include "music/music_driver.hpp"
 
#include "sound/sound_driver.hpp"
 
#include "video/video_driver.hpp"
 
#include "saveload/saveload.h"
 
#include "screenshot.h"
 
#include "gfx_func.h"
 
#include "network/network.h"
 
#include "network/network_survey.h"
 
#include "language.h"
 
#include "fontcache.h"
 
#include "news_gui.h"
 
#include "fileio_func.h"
 
#include "fileio_type.h"
 

	
 
#include "ai/ai_info.hpp"
 
#include "game/game.hpp"
 
#include "game/game_info.hpp"
 
#include "company_base.h"
 
#include "company_func.h"
 
#include "3rdparty/fmt/chrono.h"
 
#include "3rdparty/fmt/std.h"
 

	
 
#ifdef WITH_ALLEGRO
 
#	include <allegro.h>
 
#endif /* WITH_ALLEGRO */
 
#ifdef WITH_FONTCONFIG
 
#	include <fontconfig/fontconfig.h>
 
#endif /* WITH_FONTCONFIG */
 
#ifdef WITH_PNG
 
	/* pngconf.h, included by png.h doesn't like something in the
 
	 * freetype headers. As such it's not alphabetically sorted. */
 
#	include <png.h>
 
#endif /* WITH_PNG */
 
#ifdef WITH_FREETYPE
 
#	include <ft2build.h>
 
#	include FT_FREETYPE_H
 
#endif /* WITH_FREETYPE */
 
#ifdef WITH_HARFBUZZ
 
#	include <hb.h>
 
#endif /* WITH_HARFBUZZ */
 
#ifdef WITH_ICU_I18N
 
#	include <unicode/uversion.h>
 
#endif /* WITH_ICU_I18N */
 
#ifdef WITH_LIBLZMA
 
#	include <lzma.h>
 
#endif
 
#ifdef WITH_LZO
 
#include <lzo/lzo1x.h>
 
#endif
 
#if defined(WITH_SDL) || defined(WITH_SDL2)
 
#	include <SDL.h>
 
#endif /* WITH_SDL || WITH_SDL2 */
 
#ifdef WITH_ZLIB
 
# include <zlib.h>
 
#endif
 
#ifdef WITH_CURL
 
# include <curl/curl.h>
 
#endif
 
#include "core/format.hpp"
 

	
 
#include "safeguards.h"
 

	
 
/* static */ std::string CrashLog::message{ "<none>" };
 

	
 
void CrashLog::LogCompiler(std::back_insert_iterator<std::string> &output_iterator) const
 
{
 
	fmt::format_to(output_iterator, " Compiler: "
 
#if defined(_MSC_VER)
 
			"MSVC {}", _MSC_VER
 
#elif defined(__ICC) && defined(__GNUC__)
 
			"ICC {} (GCC {}.{}.{} mode)", __ICC,  __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__
 
#elif defined(__ICC)
 
			"ICC {}", __ICC
 
#elif defined(__GNUC__)
 
			"GCC {}.{}.{}", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__
 
#else
 
			"<unknown>"
 
#endif
 
			);
 
#if defined(__VERSION__)
 
	fmt::format_to(output_iterator, " \"" __VERSION__ "\"\n\n");
 
#else
 
	fmt::format_to(output_iterator, "\n\n");
 
#endif
 
}
 

	
 
/**
 
 * Writes OpenTTD's version to the buffer.
 
 * @param output_iterator Iterator to write the output to.
 
 */
 
void CrashLog::LogOpenTTDVersion(std::back_insert_iterator<std::string> &output_iterator) const
 
{
 
	fmt::format_to(output_iterator,
 
			"OpenTTD version:\n"
 
			" Version:    {} ({})\n"
 
			" NewGRF ver: {:08x}\n"
 
			" Bits:       {}\n"
 
			" Endian:     {}\n"
 
			" Dedicated:  {}\n"
 
			" Build date: {}\n\n",
 
			_openttd_revision,
 
			_openttd_revision_modified,
 
			_openttd_newgrf_version,
 
#ifdef POINTER_IS_64BIT
 
			64,
 
#else
 
			32,
 
#endif
 
#if (TTD_ENDIAN == TTD_LITTLE_ENDIAN)
 
			"little",
 
#else
 
			"big",
 
#endif
 
#ifdef DEDICATED
 
			"yes",
 
#else
 
			"no",
 
#endif
 
			_openttd_build_date
 
	);
 
}
 

	
 
/**
 
 * Writes the (important) configuration settings to the buffer.
 
 * E.g. graphics set, sound set, blitter and AIs.
 
 * @param output_iterator Iterator to write the output to.
 
 */
 
void CrashLog::LogConfiguration(std::back_insert_iterator<std::string> &output_iterator) const
 
{
 
	fmt::format_to(output_iterator,
 
			"Configuration:\n"
 
			" Blitter:      {}\n"
 
			" Graphics set: {} ({})\n"
 
			" Language:     {}\n"
 
			" Music driver: {}\n"
 
			" Music set:    {} ({})\n"
 
			" Network:      {}\n"
 
			" Sound driver: {}\n"
 
			" Sound set:    {} ({})\n"
 
			" Video driver: {}\n\n",
 
			BlitterFactory::GetCurrentBlitter() == nullptr ? "none" : BlitterFactory::GetCurrentBlitter()->GetName(),
 
			BaseGraphics::GetUsedSet() == nullptr ? "none" : BaseGraphics::GetUsedSet()->name,
 
			BaseGraphics::GetUsedSet() == nullptr ? UINT32_MAX : BaseGraphics::GetUsedSet()->version,
 
			_current_language == nullptr ? "none" : _current_language->file.filename(),
 
			MusicDriver::GetInstance() == nullptr ? "none" : MusicDriver::GetInstance()->GetName(),
 
			BaseMusic::GetUsedSet() == nullptr ? "none" : BaseMusic::GetUsedSet()->name,
 
			BaseMusic::GetUsedSet() == nullptr ? UINT32_MAX : BaseMusic::GetUsedSet()->version,
 
			_networking ? (_network_server ? "server" : "client") : "no",
 
			SoundDriver::GetInstance() == nullptr ? "none" : SoundDriver::GetInstance()->GetName(),
 
			BaseSounds::GetUsedSet() == nullptr ? "none" : BaseSounds::GetUsedSet()->name,
 
			BaseSounds::GetUsedSet() == nullptr ? UINT32_MAX : BaseSounds::GetUsedSet()->version,
 
			VideoDriver::GetInstance() == nullptr ? "none" : VideoDriver::GetInstance()->GetInfoString()
 
	);
 
/* static */ std::string CrashLog::message{};
 

	
 
	fmt::format_to(output_iterator,
 
			"Fonts:\n"
 
			" Small:  {}\n"
 
			" Medium: {}\n"
 
			" Large:  {}\n"
 
			" Mono:   {}\n\n",
 
			FontCache::GetName(FS_SMALL),
 
			FontCache::GetName(FS_NORMAL),
 
			FontCache::GetName(FS_LARGE),
 
			FontCache::GetName(FS_MONO)
 
	);
 

	
 
	fmt::format_to(output_iterator, "AI Configuration (local: {}) (current: {}):\n", _local_company, _current_company);
 
	for (const Company *c : Company::Iterate()) {
 
		if (c->ai_info == nullptr) {
 
			fmt::format_to(output_iterator, " {:2}: Human\n", c->index);
 
		} else {
 
			fmt::format_to(output_iterator, " {:2}: {} (v{})\n", (int)c->index, c->ai_info->GetName(), c->ai_info->GetVersion());
 
		}
 
	}
 

	
 
	if (Game::GetInfo() != nullptr) {
 
		fmt::format_to(output_iterator, " GS: {} (v{})\n", Game::GetInfo()->GetName(), Game::GetInfo()->GetVersion());
 
	}
 
	fmt::format_to(output_iterator, "\n");
 
}
 

	
 
/**
 
 * Writes information (versions) of the used libraries.
 
 * @param output_iterator Iterator to write the output to.
 
 */
 
void CrashLog::LogLibraries(std::back_insert_iterator<std::string> &output_iterator) const
 
{
 
	fmt::format_to(output_iterator, "Libraries:\n");
 

	
 
#ifdef WITH_ALLEGRO
 
	fmt::format_to(output_iterator, " Allegro:    {}\n", allegro_id);
 
#endif /* WITH_ALLEGRO */
 

	
 
#ifdef WITH_FONTCONFIG
 
	int version = FcGetVersion();
 
	fmt::format_to(output_iterator, " FontConfig: {}.{}.{}\n", version / 10000, (version / 100) % 100, version % 100);
 
#endif /* WITH_FONTCONFIG */
 

	
 
#ifdef WITH_FREETYPE
 
	FT_Library library;
 
	int major, minor, patch;
 
	FT_Init_FreeType(&library);
 
	FT_Library_Version(library, &major, &minor, &patch);
 
	FT_Done_FreeType(library);
 
	fmt::format_to(output_iterator, " FreeType:   {}.{}.{}\n", major, minor, patch);
 
#endif /* WITH_FREETYPE */
 

	
 
#if defined(WITH_HARFBUZZ)
 
	fmt::format_to(output_iterator, " HarfBuzz:   {}\n", hb_version_string());
 
#endif /* WITH_HARFBUZZ */
 

	
 
#if defined(WITH_ICU_I18N)
 
	/* 4 times 0-255, separated by dots (.) and a trailing '\0' */
 
	char buf[4 * 3 + 3 + 1];
 
	UVersionInfo ver;
 
	u_getVersion(ver);
 
	u_versionToString(ver, buf);
 
	fmt::format_to(output_iterator, " ICU i18n:   {}\n", buf);
 
#endif /* WITH_ICU_I18N */
 

	
 
#ifdef WITH_LIBLZMA
 
	fmt::format_to(output_iterator, " LZMA:       {}\n", lzma_version_string());
 
#endif
 

	
 
#ifdef WITH_LZO
 
	fmt::format_to(output_iterator, " LZO:        {}\n", lzo_version_string());
 
#endif
 

	
 
#ifdef WITH_PNG
 
	fmt::format_to(output_iterator, " PNG:        {}\n", png_get_libpng_ver(nullptr));
 
#endif /* WITH_PNG */
 

	
 
#ifdef WITH_SDL
 
	const SDL_version *sdl_v = SDL_Linked_Version();
 
	fmt::format_to(output_iterator, " SDL1:       {}.{}.{}\n", sdl_v->major, sdl_v->minor, sdl_v->patch);
 
#elif defined(WITH_SDL2)
 
	SDL_version sdl2_v;
 
	SDL_GetVersion(&sdl2_v);
 
	fmt::format_to(output_iterator, " SDL2:       {}.{}.{}\n", sdl2_v.major, sdl2_v.minor, sdl2_v.patch);
 
#endif
 

	
 
#ifdef WITH_ZLIB
 
	fmt::format_to(output_iterator, " Zlib:       {}\n", zlibVersion());
 
#endif
 

	
 
#ifdef WITH_CURL
 
	auto *curl_v = curl_version_info(CURLVERSION_NOW);
 
	fmt::format_to(output_iterator, " Curl:       {}\n", curl_v->version);
 
	if (curl_v->ssl_version != nullptr) {
 
		fmt::format_to(output_iterator, " Curl SSL:   {}\n", curl_v->ssl_version);
 
	} else {
 
		fmt::format_to(output_iterator, " Curl SSL:   none\n");
 
	}
 
#endif
 

	
 
	fmt::format_to(output_iterator, "\n");
 
}
 
/** The version of the schema of the JSON information. */
 
constexpr uint8_t CRASHLOG_SURVEY_VERSION = 1;
 

	
 
/**
 
 * Writes the gamelog data to the buffer.
 
 * @param output_iterator Iterator to write the output to.
 
 */
 
void CrashLog::LogGamelog(std::back_insert_iterator<std::string> &output_iterator) const
 
static void SurveyGamelog(nlohmann::json &json)
 
{
 
	_gamelog.Print([&output_iterator](const std::string &s) {
 
		fmt::format_to(output_iterator, "{}\n", s);
 
	json = nlohmann::json::array();
 

	
 
	_gamelog.Print([&json](const std::string &s) {
 
		json.push_back(s);
 
	});
 
	fmt::format_to(output_iterator, "\n");
 
}
 

	
 
/**
 
 * Writes up to 32 recent news messages to the buffer, with the most recent first.
 
 * @param output_iterator Iterator to write the output to.
 
 */
 
void CrashLog::LogRecentNews(std::back_insert_iterator<std::string> &output_iterator) const
 
static void SurveyRecentNews(nlohmann::json &json)
 
{
 
	fmt::format_to(output_iterator, "Recent news messages:\n");
 
	json = nlohmann::json::array();
 

	
 
	int i = 0;
 
	for (NewsItem *news = _latest_news; i < 32 && news != nullptr; news = news->prev, i++) {
 
		TimerGameCalendar::YearMonthDay ymd;
 
		TimerGameCalendar::ConvertDateToYMD(news->date, &ymd);
 
		fmt::format_to(output_iterator, "({}-{:02}-{:02}) StringID: {}, Type: {}, Ref1: {}, {}, Ref2: {}, {}\n",
 
		                   ymd.year, ymd.month + 1, ymd.day, news->string_id, news->type,
 
		                   news->reftype1, news->ref1, news->reftype2, news->ref2);
 
		json.push_back(fmt::format("({}-{:02}-{:02}) StringID: {}, Type: {}, Ref1: {}, {}, Ref2: {}, {}",
 
		               ymd.year, ymd.month + 1, ymd.day, news->string_id, news->type,
 
		               news->reftype1, news->ref1, news->reftype2, news->ref2));
 
	}
 
	fmt::format_to(output_iterator, "\n");
 
}
 

	
 
/**
 
@@ -320,28 +83,95 @@ std::string CrashLog::CreateFileName(con
 

	
 
/**
 
 * Fill the crash log buffer with all data of a crash log.
 
 * @param output_iterator Iterator to write the output to.
 
 */
 
void CrashLog::FillCrashLog(std::back_insert_iterator<std::string> &output_iterator) const
 
void CrashLog::FillCrashLog()
 
{
 
	fmt::format_to(output_iterator, "*** OpenTTD Crash Report ***\n\n");
 
	fmt::format_to(output_iterator, "Crash at: {:%Y-%m-%d %H:%M:%S} (UTC)\n", fmt::gmtime(time(nullptr)));
 
	/* Reminder: this JSON is read in an automated fashion.
 
	 * If any structural changes are applied, please bump the version. */
 
	this->survey["schema"] = CRASHLOG_SURVEY_VERSION;
 
	this->survey["date"] = fmt::format("{:%Y-%m-%d %H:%M:%S} (UTC)", fmt::gmtime(time(nullptr)));
 

	
 
	/* If no internal reason was logged, it must be a crash. */
 
	if (CrashLog::message.empty()) {
 
		this->SurveyCrash(this->survey["crash"]);
 
	} else {
 
		this->survey["crash"]["reason"] = CrashLog::message;
 
		CrashLog::message.clear();
 
	}
 

	
 
	if (!this->TryExecute("stacktrace", [this]() { this->SurveyStacktrace(this->survey["stacktrace"]); return true; })) {
 
		this->survey["stacktrace"] = "crashed while gathering information";
 
	}
 

	
 
	TimerGameCalendar::YearMonthDay ymd;
 
	TimerGameCalendar::ConvertDateToYMD(TimerGameCalendar::date, &ymd);
 
	fmt::format_to(output_iterator, "In game date: {}-{:02}-{:02} ({})\n\n", ymd.year, ymd.month + 1, ymd.day, TimerGameCalendar::date_fract);
 
	{
 
		auto &info = this->survey["info"];
 
		if (!this->TryExecute("os", [&info]() { SurveyOS(info["os"]); return true; })) {
 
			info["os"] = "crashed while gathering information";
 
		}
 
		if (!this->TryExecute("openttd", [&info]() { SurveyOpenTTD(info["openttd"]); return true; })) {
 
			info["openttd"] = "crashed while gathering information";
 
		}
 
		if (!this->TryExecute("configuration", [&info]() { SurveyConfiguration(info["configuration"]); return true; })) {
 
			info["configuration"] = "crashed while gathering information";
 
		}
 
		if (!this->TryExecute("font", [&info]() { SurveyFont(info["font"]); return true; })) {
 
			info["font"] = "crashed while gathering information";
 
		}
 
		if (!this->TryExecute("compiler", [&info]() { SurveyCompiler(info["compiler"]); return true; })) {
 
			info["compiler"] = "crashed while gathering information";
 
		}
 
		if (!this->TryExecute("libraries", [&info]() { SurveyLibraries(info["libraries"]); return true; })) {
 
			info["libraries"] = "crashed while gathering information";
 
		}
 
	}
 

	
 
	this->LogError(output_iterator, CrashLog::message);
 
	this->LogOpenTTDVersion(output_iterator);
 
	this->LogStacktrace(output_iterator);
 
	this->LogOSVersion(output_iterator);
 
	this->LogCompiler(output_iterator);
 
	this->LogConfiguration(output_iterator);
 
	this->LogLibraries(output_iterator);
 
	this->LogGamelog(output_iterator);
 
	this->LogRecentNews(output_iterator);
 
	{
 
		auto &game = this->survey["game"];
 
		game["local_company"] = _local_company;
 
		game["current_company"] = _current_company;
 

	
 
	fmt::format_to(output_iterator, "*** End of OpenTTD Crash Report ***\n");
 
		if (!this->TryExecute("timers", [&game]() { SurveyTimers(game["timers"]); return true; })) {
 
			game["libraries"] = "crashed while gathering information";
 
		}
 
		if (!this->TryExecute("companies", [&game]() { SurveyCompanies(game["companies"]); return true; })) {
 
			game["companies"] = "crashed while gathering information";
 
		}
 
		if (!this->TryExecute("settings", [&game]() { SurveySettings(game["settings_changed"], true); return true; })) {
 
			game["settings"] = "crashed while gathering information";
 
		}
 
		if (!this->TryExecute("grfs", [&game]() { SurveyGrfs(game["grfs"]); return true; })) {
 
			game["grfs"] = "crashed while gathering information";
 
		}
 
		if (!this->TryExecute("game_script", [&game]() { SurveyGameScript(game["game_script"]); return true; })) {
 
			game["game_script"] = "crashed while gathering information";
 
		}
 
		if (!this->TryExecute("gamelog", [&game]() { SurveyGamelog(game["gamelog"]); return true; })) {
 
			game["gamelog"] = "crashed while gathering information";
 
		}
 
		if (!this->TryExecute("news", [&game]() { SurveyRecentNews(game["news"]); return true; })) {
 
			game["news"] = "crashed while gathering information";
 
		}
 
	}
 
}
 

	
 
void CrashLog::PrintCrashLog() const
 
{
 
	fmt::print("  OpenTTD version:\n");
 
	fmt::print("    Version: {}\n", this->survey["info"]["openttd"]["version"]["revision"].get<std::string>());
 
	fmt::print("    Hash: {}\n", this->survey["info"]["openttd"]["version"]["hash"].get<std::string>());
 
	fmt::print("    NewGRF ver: {}\n", this->survey["info"]["openttd"]["version"]["newgrf"].get<std::string>());
 
	fmt::print("    Content ver: {}\n", this->survey["info"]["openttd"]["version"]["content"].get<std::string>());
 
	fmt::print("\n");
 

	
 
	fmt::print("  Crash:\n");
 
	fmt::print("    Reason: {}\n", this->survey["crash"]["reason"].get<std::string>());
 
	fmt::print("\n");
 

	
 
	fmt::print("  Stacktrace:\n");
 
	for (const auto &line : this->survey["stacktrace"]) {
 
		fmt::print("    {}\n", line.get<std::string>());
 
	}
 
	fmt::print("\n");
 
}
 

	
 
/**
 
@@ -351,13 +181,15 @@ void CrashLog::FillCrashLog(std::back_in
 
 */
 
bool CrashLog::WriteCrashLog()
 
{
 
	this->crashlog_filename = this->CreateFileName(".log");
 
	this->crashlog_filename = this->CreateFileName(".json.log");
 

	
 
	FILE *file = FioFOpenFile(this->crashlog_filename, "w", NO_DIRECTORY);
 
	if (file == nullptr) return false;
 

	
 
	size_t len = this->crashlog.size();
 
	size_t written = fwrite(this->crashlog.data(), 1, len, file);
 
	std::string survey_json = this->survey.dump(4);
 

	
 
	size_t len = survey_json.size();
 
	size_t written = fwrite(survey_json.data(), 1, len, file);
 

	
 
	FioFCloseFile(file);
 
	return len == written;
 
@@ -414,6 +246,9 @@ bool CrashLog::WriteScreenshot()
 
	return res;
 
}
 

	
 
/**
 
 * Send the survey result, noting it was a crash.
 
 */
 
void CrashLog::SendSurvey() const
 
{
 
	if (_game_mode == GM_NORMAL) {
 
@@ -433,13 +268,12 @@ void CrashLog::MakeCrashLog()
 
	if (crashlogged) return;
 
	crashlogged = true;
 

	
 
	crashlog.reserve(65536);
 
	auto output_iterator = std::back_inserter(crashlog);
 
	fmt::print("Crash encountered, generating crash log...\n");
 
	this->FillCrashLog();
 
	fmt::print("Crash log generated.\n\n");
 

	
 
	fmt::print("Crash encountered, generating crash log...\n");
 
	this->FillCrashLog(output_iterator);
 
	fmt::print("{}\n", crashlog);
 
	fmt::print("Crash log generated.\n\n");
 
	fmt::print("Crash in summary:\n");
 
	this->TryExecute("crashlog", [this]() { this->PrintCrashLog(); return true; });
 

	
 
	fmt::print("Writing crash log to disk...\n");
 
	bool ret = this->TryExecute("crashlog", [this]() { return this->WriteCrashLog(); });
src/crashlog.h
Show inline comments
 
@@ -10,6 +10,8 @@
 
#ifndef CRASHLOG_H
 
#define CRASHLOG_H
 

	
 
#include "3rdparty/nlohmann/json.hpp"
 

	
 
/**
 
 * Helper class for creating crash logs.
 
 */
 
@@ -17,40 +19,20 @@ class CrashLog {
 
private:
 
	/** Error message coming from #FatalError(format, ...). */
 
	static std::string message;
 
protected:
 
	/**
 
	 * Writes OS' version to the buffer.
 
	 * @param output_iterator Iterator to write the output to.
 
	 */
 
	virtual void LogOSVersion(std::back_insert_iterator<std::string> &output_iterator) const = 0;
 

	
 
	/**
 
	 * Writes compiler (and its version, if available) to the buffer.
 
	 * @param output_iterator Iterator to write the output to.
 
	 */
 
	virtual void LogCompiler(std::back_insert_iterator<std::string> &output_iterator) const;
 

	
 
	/**
 
	 * Writes actually encountered error to the buffer.
 
	 * @param output_iterator Iterator to write the output to.
 
	 * @param message Message passed to use for errors.
 
	 * Convert system crash reason to JSON.
 
	 *
 
	 * @param survey The JSON object.
 
	 */
 
	virtual void LogError(std::back_insert_iterator<std::string> &output_iterator, const std::string_view message) const = 0;
 
	virtual void SurveyCrash(nlohmann::json &survey) const = 0;
 

	
 
	/**
 
	 * Writes the stack trace to the buffer, if there is information about it
 
	 * available.
 
	 * @param output_iterator Iterator to write the output to.
 
	 * Convert stacktrace to JSON.
 
	 *
 
	 * @param survey The JSON object.
 
	 */
 
	virtual void LogStacktrace(std::back_insert_iterator<std::string> &output_iterator) const = 0;
 

	
 
	void LogOpenTTDVersion(std::back_insert_iterator<std::string> &output_iterator) const;
 
	void LogConfiguration(std::back_insert_iterator<std::string> &output_iterator) const;
 
	void LogLibraries(std::back_insert_iterator<std::string> &output_iterator) const;
 
	void LogGamelog(std::back_insert_iterator<std::string> &output_iterator) const;
 
	void LogRecentNews(std::back_insert_iterator<std::string> &output_iterator) const;
 

	
 
	std::string CreateFileName(const char *ext, bool with_dir = true) const;
 
	virtual void SurveyStacktrace(nlohmann::json &survey) const = 0;
 

	
 
	/**
 
	 * Execute the func() and return its value. If any exception / signal / crash happens,
 
@@ -63,19 +45,23 @@ protected:
 
	 */
 
	virtual bool TryExecute(std::string_view section_name, std::function<bool()> &&func) = 0;
 

	
 
protected:
 
	std::string CreateFileName(const char *ext, bool with_dir = true) const;
 

	
 
public:
 
	/** Stub destructor to silence some compilers. */
 
	virtual ~CrashLog() = default;
 

	
 
	std::string crashlog;
 
	nlohmann::json survey;
 
	std::string crashlog_filename;
 
	std::string crashdump_filename;
 
	std::string savegame_filename;
 
	std::string screenshot_filename;
 

	
 
	void FillCrashLog(std::back_insert_iterator<std::string> &output_iterator) const;
 
	void FillCrashLog();
 
	void PrintCrashLog() const;
 

	
 
	bool WriteCrashLog();
 

	
 
	virtual bool WriteCrashDump();
 
	bool WriteSavegame();
 
	bool WriteScreenshot();
src/network/network_survey.cpp
Show inline comments
 
@@ -12,26 +12,9 @@
 
#include "settings_table.h"
 
#include "network.h"
 
#include "../debug.h"
 
#include "../rev.h"
 
#include "../settings_type.h"
 
#include "../timer/timer_game_tick.h"
 

	
 
#include "../currency.h"
 
#include "../fontcache.h"
 
#include "../language.h"
 

	
 
#include "../ai/ai_info.hpp"
 
#include "../game/game.hpp"
 
#include "../game/game_info.hpp"
 

	
 
#include "../music/music_driver.hpp"
 
#include "../sound/sound_driver.hpp"
 
#include "../video/video_driver.hpp"
 

	
 
#include "../base_media_base.h"
 
#include "../blitter/factory.hpp"
 

	
 
#include "../3rdparty/nlohmann/json.hpp"
 
#include "../survey.h"
 
#include "../3rdparty/fmt/chrono.h"
 
#include "../3rdparty/fmt/std.h"
 

	
 
#include "../safeguards.h"
 

	
 
@@ -46,294 +29,6 @@ NLOHMANN_JSON_SERIALIZE_ENUM(NetworkSurv
 
	{NetworkSurveyHandler::Reason::CRASH, "crash"},
 
})
 

	
 
NLOHMANN_JSON_SERIALIZE_ENUM(GRFStatus, {
 
	{GRFStatus::GCS_UNKNOWN, "unknown"},
 
	{GRFStatus::GCS_DISABLED, "disabled"},
 
	{GRFStatus::GCS_NOT_FOUND, "not found"},
 
	{GRFStatus::GCS_INITIALISED, "initialised"},
 
	{GRFStatus::GCS_ACTIVATED, "activated"},
 
})
 

	
 
static const std::string _vehicle_type_to_string[] = {
 
	"train",
 
	"roadveh",
 
	"ship",
 
	"aircraft",
 
};
 

	
 
/* Defined in one of the os/ survey files. */
 
extern void SurveyOS(nlohmann::json &json);
 

	
 
/**
 
 * List of all the generic setting tables.
 
 *
 
 * There are a few tables that are special and not processed like the rest:
 
 * - _currency_settings
 
 * - _misc_settings
 
 * - _company_settings
 
 * - _win32_settings
 
 * As such, they are not part of this list.
 
 */
 
static auto &GenericSettingTables()
 
{
 
	static const SettingTable _generic_setting_tables[] = {
 
		_difficulty_settings,
 
		_economy_settings,
 
		_game_settings,
 
		_gui_settings,
 
		_linkgraph_settings,
 
		_locale_settings,
 
		_multimedia_settings,
 
		_network_settings,
 
		_news_display_settings,
 
		_pathfinding_settings,
 
		_script_settings,
 
		_world_settings,
 
	};
 
	return _generic_setting_tables;
 
}
 

	
 
/**
 
 * Convert a settings table to JSON.
 
 *
 
 * @param survey The JSON object.
 
 * @param table The settings table to convert.
 
 * @param object The object to get the settings from.
 
 */
 
static void SurveySettingsTable(nlohmann::json &survey, const SettingTable &table, void *object)
 
{
 
	for (auto &desc : table) {
 
		const SettingDesc *sd = GetSettingDesc(desc);
 
		/* Skip any old settings we no longer save/load. */
 
		if (!SlIsObjectCurrentlyValid(sd->save.version_from, sd->save.version_to)) continue;
 

	
 
		auto name = sd->GetName();
 
		survey[name] = sd->FormatValue(object);
 
	}
 
}
 

	
 
/**
 
 * Convert settings to JSON.
 
 *
 
 * @param survey The JSON object.
 
 */
 
static void SurveySettings(nlohmann::json &survey)
 
{
 
	SurveySettingsTable(survey, _misc_settings, nullptr);
 
#if defined(_WIN32) && !defined(DEDICATED)
 
	SurveySettingsTable(survey, _win32_settings, nullptr);
 
#endif
 
	for (auto &table : GenericSettingTables()) {
 
		SurveySettingsTable(survey, table, &_settings_game);
 
	}
 
	SurveySettingsTable(survey, _currency_settings, &_custom_currency);
 
	SurveySettingsTable(survey, _company_settings, &_settings_client.company);
 
}
 

	
 
/**
 
 * Convert generic OpenTTD information to JSON.
 
 *
 
 * @param survey The JSON object.
 
 */
 
static void SurveyOpenTTD(nlohmann::json &survey)
 
{
 
	survey["version"]["revision"] = std::string(_openttd_revision);
 
	survey["version"]["modified"] = _openttd_revision_modified;
 
	survey["version"]["tagged"] = _openttd_revision_tagged;
 
	survey["version"]["hash"] = std::string(_openttd_revision_hash);
 
	survey["version"]["newgrf"] = fmt::format("{:X}", _openttd_newgrf_version);
 
	survey["version"]["content"] = std::string(_openttd_content_version);
 
	survey["build_date"] = std::string(_openttd_build_date);
 
	survey["bits"] =
 
#ifdef POINTER_IS_64BIT
 
			64
 
#else
 
			32
 
#endif
 
		;
 
	survey["endian"] =
 
#if (TTD_ENDIAN == TTD_LITTLE_ENDIAN)
 
			"little"
 
#else
 
			"big"
 
#endif
 
		;
 
	survey["dedicated_build"] =
 
#ifdef DEDICATED
 
			"yes"
 
#else
 
			"no"
 
#endif
 
		;
 
}
 

	
 
/**
 
 * Convert generic game information to JSON.
 
 *
 
 * @param survey The JSON object.
 
 */
 
static void SurveyConfiguration(nlohmann::json &survey)
 
{
 
	survey["network"] = _networking ? (_network_server ? "server" : "client") : "no";
 
	if (_current_language != nullptr) {
 
		survey["language"]["filename"] = _current_language->file.filename().string();
 
		survey["language"]["name"] = _current_language->name;
 
		survey["language"]["isocode"] = _current_language->isocode;
 
	}
 
	if (BlitterFactory::GetCurrentBlitter() != nullptr) {
 
		survey["blitter"] = BlitterFactory::GetCurrentBlitter()->GetName();
 
	}
 
	if (MusicDriver::GetInstance() != nullptr) {
 
		survey["music_driver"] = MusicDriver::GetInstance()->GetName();
 
	}
 
	if (SoundDriver::GetInstance() != nullptr) {
 
		survey["sound_driver"] = SoundDriver::GetInstance()->GetName();
 
	}
 
	if (VideoDriver::GetInstance() != nullptr) {
 
		survey["video_driver"] = VideoDriver::GetInstance()->GetName();
 
		survey["video_info"] = VideoDriver::GetInstance()->GetInfoString();
 
	}
 
	if (BaseGraphics::GetUsedSet() != nullptr) {
 
		survey["graphics_set"] = fmt::format("{}.{}", BaseGraphics::GetUsedSet()->name, BaseGraphics::GetUsedSet()->version);
 
	}
 
	if (BaseMusic::GetUsedSet() != nullptr) {
 
		survey["music_set"] = fmt::format("{}.{}", BaseMusic::GetUsedSet()->name, BaseMusic::GetUsedSet()->version);
 
	}
 
	if (BaseSounds::GetUsedSet() != nullptr) {
 
		survey["sound_set"] = fmt::format("{}.{}", BaseSounds::GetUsedSet()->name, BaseSounds::GetUsedSet()->version);
 
	}
 
}
 

	
 
/**
 
 * Convert font information to JSON.
 
 *
 
 * @param survey The JSON object.
 
 */
 
static void SurveyFont(nlohmann::json &survey)
 
{
 
	survey["small"] = FontCache::Get(FS_SMALL)->GetFontName();
 
	survey["medium"] = FontCache::Get(FS_NORMAL)->GetFontName();
 
	survey["large"] = FontCache::Get(FS_LARGE)->GetFontName();
 
	survey["mono"] = FontCache::Get(FS_MONO)->GetFontName();
 
}
 

	
 
/**
 
 * Convert company information to JSON.
 
 *
 
 * @param survey The JSON object.
 
 */
 
static void SurveyCompanies(nlohmann::json &survey)
 
{
 
	for (const Company *c : Company::Iterate()) {
 
		auto &company = survey[std::to_string(c->index)];
 
		if (c->ai_info == nullptr) {
 
			company["type"] = "human";
 
		} else {
 
			company["type"] = "ai";
 
			company["script"] = fmt::format("{}.{}", c->ai_info->GetName(), c->ai_info->GetVersion());
 
		}
 

	
 
		for (VehicleType type = VEH_BEGIN; type < VEH_COMPANY_END; type++) {
 
			uint amount = c->group_all[type].num_vehicle;
 
			company["vehicles"][_vehicle_type_to_string[type]] = amount;
 
		}
 

	
 
		company["infrastructure"]["road"] = c->infrastructure.GetRoadTotal();
 
		company["infrastructure"]["tram"] = c->infrastructure.GetTramTotal();
 
		company["infrastructure"]["rail"] = c->infrastructure.GetRailTotal();
 
		company["infrastructure"]["signal"] = c->infrastructure.signal;
 
		company["infrastructure"]["water"] = c->infrastructure.water;
 
		company["infrastructure"]["station"] = c->infrastructure.station;
 
		company["infrastructure"]["airport"] = c->infrastructure.airport;
 
	}
 
}
 

	
 
/**
 
 * Convert timer information to JSON.
 
 *
 
 * @param survey The JSON object.
 
 */
 
static void SurveyTimers(nlohmann::json &survey)
 
{
 
	survey["ticks"] = TimerGameTick::counter;
 
	survey["seconds"] = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - _switch_mode_time).count();
 

	
 
	TimerGameCalendar::YearMonthDay ymd;
 
	TimerGameCalendar::ConvertDateToYMD(TimerGameCalendar::date, &ymd);
 
	survey["calendar"] = fmt::format("{:04}-{:02}-{:02} ({})", ymd.year, ymd.month + 1, ymd.day, TimerGameCalendar::date_fract);
 
}
 

	
 
/**
 
 * Convert GRF information to JSON.
 
 *
 
 * @param survey The JSON object.
 
 */
 
static void SurveyGrfs(nlohmann::json &survey)
 
{
 
	for (GRFConfig *c = _grfconfig; c != nullptr; c = c->next) {
 
		auto grfid = fmt::format("{:08x}", BSWAP32(c->ident.grfid));
 
		auto &grf = survey[grfid];
 

	
 
		grf["md5sum"] = FormatArrayAsHex(c->ident.md5sum);
 
		grf["status"] = c->status;
 

	
 
		if ((c->palette & GRFP_GRF_MASK) == GRFP_GRF_UNSET) grf["palette"] = "unset";
 
		if ((c->palette & GRFP_GRF_MASK) == GRFP_GRF_DOS) grf["palette"] = "dos";
 
		if ((c->palette & GRFP_GRF_MASK) == GRFP_GRF_WINDOWS) grf["palette"] = "windows";
 
		if ((c->palette & GRFP_GRF_MASK) == GRFP_GRF_ANY) grf["palette"] = "any";
 

	
 
		if ((c->palette & GRFP_BLT_MASK) == GRFP_BLT_UNSET) grf["blitter"] = "unset";
 
		if ((c->palette & GRFP_BLT_MASK) == GRFP_BLT_32BPP) grf["blitter"] = "32bpp";
 

	
 
		grf["is_static"] = HasBit(c->flags, GCF_STATIC);
 

	
 
		std::vector<uint32_t> parameters;
 
		for (int i = 0; i < c->num_params; i++) {
 
			parameters.push_back(c->param[i]);
 
		}
 
		grf["parameters"] = parameters;
 
	}
 
}
 

	
 
/**
 
 * Convert game-script information to JSON.
 
 *
 
 * @param survey The JSON object.
 
 */
 
static void SurveyGameScript(nlohmann::json &survey)
 
{
 
	if (Game::GetInfo() == nullptr) return;
 

	
 
	survey = fmt::format("{}.{}", Game::GetInfo()->GetName(), Game::GetInfo()->GetVersion());
 
}
 

	
 
/**
 
 * Change the bytes of memory into a textual version rounded up to the biggest unit.
 
 *
 
 * For example, 16751108096 would become 16 GiB.
 
 *
 
 * @param memory The bytes of memory.
 
 * @return std::string A textual representation.
 
 */
 
std::string SurveyMemoryToText(uint64_t memory)
 
{
 
	memory = memory / 1024; // KiB
 
	memory = CeilDiv(memory, 1024); // MiB
 

	
 
	/* Anything above 512 MiB we represent in GiB. */
 
	if (memory > 512) {
 
		return fmt::format("{} GiB", CeilDiv(memory, 1024));
 
	}
 

	
 
	/* Anything above 64 MiB we represent in a multiplier of 128 MiB. */
 
	if (memory > 64) {
 
		return fmt::format("{} MiB", Ceil(memory, 128));
 
	}
 

	
 
	/* Anything else in a multiplier of 4 MiB. */
 
	return fmt::format("{} MiB", Ceil(memory, 4));
 
}
 

	
 
/**
 
 * Create the payload for the survey.
 
 *
 
@@ -348,6 +43,7 @@ std::string NetworkSurveyHandler::Create
 
	survey["schema"] = NETWORK_SURVEY_VERSION;
 
	survey["reason"] = reason;
 
	survey["id"] = _savegame_id;
 
	survey["date"] = fmt::format("{:%Y-%m-%d %H:%M:%S} (UTC)", fmt::gmtime(time(nullptr)));
 

	
 
#ifdef SURVEY_KEY
 
	/* We censor the key to avoid people trying to be "clever" and use it to send their own surveys. */
 
@@ -362,13 +58,15 @@ std::string NetworkSurveyHandler::Create
 
		SurveyOpenTTD(info["openttd"]);
 
		SurveyConfiguration(info["configuration"]);
 
		SurveyFont(info["font"]);
 
		SurveyCompiler(info["compiler"]);
 
		SurveyLibraries(info["libraries"]);
 
	}
 

	
 
	{
 
		auto &game = survey["game"];
 
		SurveyTimers(game["timers"]);
 
		SurveyCompanies(game["companies"]);
 
		SurveySettings(game["settings"]);
 
		SurveySettings(game["settings"], false);
 
		SurveyGrfs(game["grfs"]);
 
		SurveyGameScript(game["game_script"]);
 
	}
src/os/macosx/crashlog_osx.cpp
Show inline comments
 
@@ -49,53 +49,24 @@ class CrashLogOSX : public CrashLog {
 
	/** Signal that has been thrown. */
 
	int signum;
 

	
 
	void LogOSVersion(std::back_insert_iterator<std::string> &output_iterator) const override
 
	void SurveyCrash(nlohmann::json &survey) const override
 
	{
 
		int ver_maj, ver_min, ver_bug;
 
		GetMacOSVersion(&ver_maj, &ver_min, &ver_bug);
 

	
 
		const NXArchInfo *arch = NXGetLocalArchInfo();
 

	
 
		fmt::format_to(output_iterator,
 
				"Operating system:\n"
 
				" Name:     Mac OS X\n"
 
				" Release:  {}.{}.{}\n"
 
				" Machine:  {}\n"
 
				" Min Ver:  {}\n"
 
				" Max Ver:  {}\n",
 
				ver_maj, ver_min, ver_bug,
 
				arch != nullptr ? arch->description : "unknown",
 
				MAC_OS_X_VERSION_MIN_REQUIRED,
 
				MAC_OS_X_VERSION_MAX_ALLOWED
 
		);
 
		survey["id"] = signum;
 
		survey["reason"] = strsignal(signum);
 
	}
 

	
 
	void LogError(std::back_insert_iterator<std::string> &output_iterator, const std::string_view message) const override
 
	void SurveyStacktrace(nlohmann::json &survey) const override
 
	{
 
		fmt::format_to(output_iterator,
 
				"Crash reason:\n"
 
				" Signal:  {} ({})\n"
 
				" Message: {}\n\n",
 
				strsignal(this->signum),
 
				this->signum,
 
				message
 
		);
 
	}
 

	
 
	void LogStacktrace(std::back_insert_iterator<std::string> &output_iterator) const override
 
	{
 
		fmt::format_to(output_iterator, "\nStacktrace:\n");
 

	
 
		void *trace[64];
 
		int trace_size = backtrace(trace, lengthof(trace));
 

	
 
		survey = nlohmann::json::array();
 

	
 
		char **messages = backtrace_symbols(trace, trace_size);
 
		for (int i = 0; i < trace_size; i++) {
 
			fmt::format_to(output_iterator, "{}\n", messages[i]);
 
			survey.push_back(messages[i]);
 
		}
 
		free(messages);
 

	
 
		fmt::format_to(output_iterator, "\n");
 
	}
 

	
 
#ifdef WITH_UNOFFICIAL_BREAKPAD
 
@@ -153,7 +124,7 @@ public:
 
			"A serious fault condition occurred in the game. The game will shut down.";
 

	
 
		std::string message = fmt::format(
 
				 "Please send crash.log, crash.dmp, and crash.sav to the developers. "
 
				 "Please send crash.json.log, crash.dmp, and crash.sav to the developers. "
 
				 "This will greatly help debugging.\n\n"
 
				 "https://github.com/OpenTTD/OpenTTD/issues.\n\n"
 
				 "{}\n{}\n{}\n{}",
src/os/macosx/survey_osx.cpp
Show inline comments
 
@@ -10,7 +10,7 @@
 
#include "../../stdafx.h"
 

	
 
#include "../../3rdparty/fmt/format.h"
 
#include "../../3rdparty/nlohmann/json.hpp"
 
#include "../../survey.h"
 
#include "macos.h"
 

	
 
#include <mach-o/arch.h>
 
@@ -18,8 +18,6 @@
 

	
 
#include "../../safeguards.h"
 

	
 
extern std::string SurveyMemoryToText(uint64_t memory);
 

	
 
void SurveyOS(nlohmann::json &json)
 
{
 
	int ver_maj, ver_min, ver_bug;
src/os/unix/crashlog_unix.cpp
Show inline comments
 
@@ -49,55 +49,26 @@ class CrashLogUnix : public CrashLog {
 
	/** Signal that has been thrown. */
 
	int signum;
 

	
 
	void LogOSVersion(std::back_insert_iterator<std::string> &output_iterator) const override
 
	void SurveyCrash(nlohmann::json &survey) const override
 
	{
 
		struct utsname name;
 
		if (uname(&name) < 0) {
 
			 fmt::format_to(output_iterator, "Could not get OS version: {}\n", strerror(errno));
 
			 return;
 
		}
 

	
 
		fmt::format_to(output_iterator,
 
				"Operating system:\n"
 
				" Name:     {}\n"
 
				" Release:  {}\n"
 
				" Version:  {}\n"
 
				" Machine:  {}\n",
 
				name.sysname,
 
				name.release,
 
				name.version,
 
				name.machine
 
		);
 
		survey["id"] = signum;
 
		survey["reason"] = strsignal(signum);
 
	}
 

	
 
	void LogError(std::back_insert_iterator<std::string> &output_iterator, const std::string_view message) const override
 
	void SurveyStacktrace(nlohmann::json &survey) const override
 
	{
 
		fmt::format_to(output_iterator,
 
			   "Crash reason:\n"
 
				" Signal:  {} ({})\n"
 
				" Message: {}\n\n",
 
				strsignal(this->signum),
 
				this->signum,
 
				message
 
		);
 
	}
 

	
 
	void LogStacktrace(std::back_insert_iterator<std::string> &output_iterator) const override
 
	{
 
		fmt::format_to(output_iterator, "Stacktrace:\n");
 
#if defined(__GLIBC__)
 
		void *trace[64];
 
		int trace_size = backtrace(trace, lengthof(trace));
 

	
 
		survey = nlohmann::json::array();
 

	
 
		char **messages = backtrace_symbols(trace, trace_size);
 
		for (int i = 0; i < trace_size; i++) {
 
			fmt::format_to(output_iterator, " [{:02}] {}\n", i, messages[i]);
 
			survey.push_back(messages[i]);
 
		}
 
		free(messages);
 
#else
 
		fmt::format_to(output_iterator, " Not supported.\n");
 
#endif
 
		fmt::format_to(output_iterator, "\n");
 
	}
 

	
 
#ifdef WITH_UNOFFICIAL_BREAKPAD
src/os/unix/survey_unix.cpp
Show inline comments
 
@@ -8,8 +8,7 @@
 
/** @file survey_unix.cpp Unix implementation of OS-specific survey information. */
 

	
 
#include "../../stdafx.h"
 

	
 
#include "../../3rdparty/nlohmann/json.hpp"
 
#include "../../survey.h"
 

	
 
#include <sys/utsname.h>
 
#include <thread>
 
@@ -17,8 +16,6 @@
 

	
 
#include "../../safeguards.h"
 

	
 
extern std::string SurveyMemoryToText(uint64_t memory);
 

	
 
void SurveyOS(nlohmann::json &json)
 
{
 
	struct utsname name;
src/os/windows/crashlog_win.cpp
Show inline comments
 
@@ -38,6 +38,33 @@
 
/** Exception code used for custom abort. */
 
static constexpr DWORD CUSTOM_ABORT_EXCEPTION = 0xE1212012;
 

	
 
/** A map between exception code and its name. */
 
static const std::map<DWORD, std::string> exception_code_to_name{
 
	{EXCEPTION_ACCESS_VIOLATION, "EXCEPTION_ACCESS_VIOLATION"},
 
	{EXCEPTION_ARRAY_BOUNDS_EXCEEDED, "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"},
 
	{EXCEPTION_BREAKPOINT, "EXCEPTION_BREAKPOINT"},
 
	{EXCEPTION_DATATYPE_MISALIGNMENT, "EXCEPTION_DATATYPE_MISALIGNMENT"},
 
	{EXCEPTION_FLT_DENORMAL_OPERAND, "EXCEPTION_FLT_DENORMAL_OPERAND"},
 
	{EXCEPTION_FLT_DIVIDE_BY_ZERO, "EXCEPTION_FLT_DIVIDE_BY_ZERO"},
 
	{EXCEPTION_FLT_INEXACT_RESULT, "EXCEPTION_FLT_INEXACT_RESULT"},
 
	{EXCEPTION_FLT_INVALID_OPERATION, "EXCEPTION_FLT_INVALID_OPERATION"},
 
	{EXCEPTION_FLT_OVERFLOW, "EXCEPTION_FLT_OVERFLOW"},
 
	{EXCEPTION_FLT_STACK_CHECK, "EXCEPTION_FLT_STACK_CHECK"},
 
	{EXCEPTION_FLT_UNDERFLOW, "EXCEPTION_FLT_UNDERFLOW"},
 
	{EXCEPTION_GUARD_PAGE, "EXCEPTION_GUARD_PAGE"},
 
	{EXCEPTION_ILLEGAL_INSTRUCTION, "EXCEPTION_ILLEGAL_INSTRUCTION"},
 
	{EXCEPTION_IN_PAGE_ERROR, "EXCEPTION_IN_PAGE_ERROR"},
 
	{EXCEPTION_INT_DIVIDE_BY_ZERO, "EXCEPTION_INT_DIVIDE_BY_ZERO"},
 
	{EXCEPTION_INT_OVERFLOW, "EXCEPTION_INT_OVERFLOW"},
 
	{EXCEPTION_INVALID_DISPOSITION, "EXCEPTION_INVALID_DISPOSITION"},
 
	{EXCEPTION_INVALID_HANDLE, "EXCEPTION_INVALID_HANDLE"},
 
	{EXCEPTION_NONCONTINUABLE_EXCEPTION, "EXCEPTION_NONCONTINUABLE_EXCEPTION"},
 
	{EXCEPTION_PRIV_INSTRUCTION, "EXCEPTION_PRIV_INSTRUCTION"},
 
	{EXCEPTION_SINGLE_STEP, "EXCEPTION_SINGLE_STEP"},
 
	{EXCEPTION_STACK_OVERFLOW, "EXCEPTION_STACK_OVERFLOW"},
 
	{STATUS_UNWIND_CONSOLIDATE, "STATUS_UNWIND_CONSOLIDATE"},
 
};
 

	
 
/**
 
 * Forcefully try to terminate the application.
 
 *
 
@@ -57,9 +84,17 @@ class CrashLogWindows : public CrashLog 
 
	/** Information about the encountered exception */
 
	EXCEPTION_POINTERS *ep;
 

	
 
	void LogOSVersion(std::back_insert_iterator<std::string> &output_iterator) const override;
 
	void LogError(std::back_insert_iterator<std::string> &output_iterator, const std::string_view message) const override;
 
	void LogStacktrace(std::back_insert_iterator<std::string> &output_iterator) const override;
 
	void SurveyCrash(nlohmann::json &survey) const override
 
	{
 
		survey["id"] = ep->ExceptionRecord->ExceptionCode;
 
		if (exception_code_to_name.count(ep->ExceptionRecord->ExceptionCode) > 0) {
 
			survey["reason"] = exception_code_to_name.at(ep->ExceptionRecord->ExceptionCode);
 
		} else {
 
			survey["reason"] = "Unknown exception code";
 
		}
 
	}
 

	
 
	void SurveyStacktrace(nlohmann::json &survey) const override;
 
public:
 

	
 
#ifdef WITH_UNOFFICIAL_BREAKPAD
 
@@ -136,41 +171,11 @@ public:
 

	
 
/* static */ CrashLogWindows *CrashLogWindows::current = nullptr;
 

	
 
/* virtual */ void CrashLogWindows::LogOSVersion(std::back_insert_iterator<std::string> &output_iterator) const
 
{
 
	_OSVERSIONINFOA os;
 
	os.dwOSVersionInfoSize = sizeof(os);
 
	GetVersionExA(&os);
 

	
 
	fmt::format_to(output_iterator,
 
			"Operating system:\n"
 
			" Name:     Windows\n"
 
			" Release:  {}.{}.{} ({})\n",
 
			os.dwMajorVersion,
 
			os.dwMinorVersion,
 
			os.dwBuildNumber,
 
			os.szCSDVersion
 
	);
 
}
 

	
 
/* virtual */ void CrashLogWindows::LogError(std::back_insert_iterator<std::string> &output_iterator, const std::string_view message) const
 
{
 
	fmt::format_to(output_iterator,
 
			"Crash reason:\n"
 
			" Exception: {:08X}\n"
 
			" Location:  {:X}\n"
 
			" Message:   {}\n\n",
 
			ep->ExceptionRecord->ExceptionCode,
 
			(size_t)ep->ExceptionRecord->ExceptionAddress,
 
			message
 
	);
 
}
 

	
 
#if defined(_MSC_VER)
 
static const uint MAX_SYMBOL_LEN = 512;
 
static const uint MAX_FRAMES     = 64;
 

	
 
/* virtual */ void CrashLogWindows::LogStacktrace(std::back_insert_iterator<std::string> &output_iterator) const
 
/* virtual */ void CrashLogWindows::SurveyStacktrace(nlohmann::json &survey) const
 
{
 
	DllLoader dbghelp(L"dbghelp.dll");
 
	struct ProcPtrs {
 
@@ -195,7 +200,7 @@ static const uint MAX_FRAMES     = 64;
 
		dbghelp.GetProcAddress("SymGetLineFromAddr64"),
 
	};
 

	
 
	fmt::format_to(output_iterator, "Stack trace:\n");
 
	survey = nlohmann::json::array();
 

	
 
	/* Try to load the functions from the DLL, if that fails because of a too old dbghelp.dll, just skip it. */
 
	if (dbghelp.Success()) {
 
@@ -246,7 +251,7 @@ static const uint MAX_FRAMES     = 64;
 
				hCur, GetCurrentThread(), &frame, &ctx, nullptr, proc.pSymFunctionTableAccess64, proc.pSymGetModuleBase64, nullptr)) break;
 

	
 
			if (frame.AddrPC.Offset == frame.AddrReturn.Offset) {
 
				fmt::format_to(output_iterator, " <infinite loop>\n");
 
				survey.push_back("<infinite loop>");
 
				break;
 
			}
 

	
 
@@ -260,33 +265,31 @@ static const uint MAX_FRAMES     = 64;
 
			}
 

	
 
			/* Print module and instruction pointer. */
 
			fmt::format_to(output_iterator, "[{:02}] {:20s} {:X}", num, mod_name, frame.AddrPC.Offset);
 
			std::string message = fmt::format("{:20s} {:X}",  mod_name, frame.AddrPC.Offset);
 

	
 
			/* Get symbol name and line info if possible. */
 
			DWORD64 offset;
 
			if (proc.pSymGetSymFromAddr64(hCur, frame.AddrPC.Offset, &offset, sym_info)) {
 
				fmt::format_to(output_iterator, " {} + {}", sym_info->Name, offset);
 
				message += fmt::format(" {} + {}", sym_info->Name, offset);
 

	
 
				DWORD line_offs;
 
				IMAGEHLP_LINE64 line;
 
				line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
 
				if (proc.pSymGetLineFromAddr64(hCur, frame.AddrPC.Offset, &line_offs, &line)) {
 
					fmt::format_to(output_iterator, " ({}:{})", line.FileName, line.LineNumber);
 
					message += fmt::format(" ({}:{})", line.FileName, line.LineNumber);
 
				}
 
			}
 
			fmt::format_to(output_iterator, "\n");
 

	
 
			survey.push_back(message);
 
		}
 

	
 
		proc.pSymCleanup(hCur);
 
	}
 

	
 
	fmt::format_to(output_iterator, "\n");
 
}
 
#else
 
/* virtual */ void CrashLogWindows::LogStacktrace(std::back_insert_iterator<std::string> &output_iterator) const
 
/* virtual */ void CrashLogWindows::SurveyStacktrace(nlohmann::json &survey) const
 
{
 
	fmt::format_to(output_iterator, "Stack trace:\n");
 
	fmt::format_to(output_iterator, " Not supported.\n");
 
	/* Not supported. */
 
}
 
#endif /* _MSC_VER */
 

	
 
@@ -430,7 +433,7 @@ static bool _expanded;
 

	
 
static const wchar_t _crash_desc[] =
 
	L"A serious fault condition occurred in the game. The game will shut down.\n"
 
	L"Please send crash.log, crash.dmp, and crash.sav to the developers.\n"
 
	L"Please send crash.json.log, crash.dmp, and crash.sav to the developers.\n"
 
	L"This will greatly help debugging.\n\n"
 
	L"https://github.com/OpenTTD/OpenTTD/issues\n\n"
 
	L"%s\n%s\n%s\n%s\n";
 
@@ -462,9 +465,10 @@ static INT_PTR CALLBACK CrashDialogFunc(
 
{
 
	switch (msg) {
 
		case WM_INITDIALOG: {
 
			size_t crashlog_length = CrashLogWindows::current->crashlog.size() + 1;
 
			std::string crashlog = CrashLogWindows::current->survey.dump(4);
 
			size_t crashlog_length = crashlog.size() + 1;
 
			/* Reserve extra space for LF to CRLF conversion. */
 
			crashlog_length += std::count(CrashLogWindows::current->crashlog.begin(), CrashLogWindows::current->crashlog.end(), '\n');
 
			crashlog_length += std::count(crashlog.begin(), crashlog.end(), '\n');
 

	
 
			const size_t filename_count = 4;
 
			const size_t filename_buf_length = MAX_PATH + 1;
 
@@ -486,7 +490,7 @@ static INT_PTR CALLBACK CrashDialogFunc(
 
			char *crashlog_dos_nl = reinterpret_cast<char *>(filename_buf + filename_buf_length * filename_count);
 

	
 
			/* Convert unix -> dos newlines because the edit box only supports that properly. */
 
			const char *crashlog_unix_nl = CrashLogWindows::current->crashlog.data();
 
			const char *crashlog_unix_nl = crashlog.data();
 
			char *p = crashlog_dos_nl;
 
			char32_t c;
 
			while ((c = Utf8Consume(&crashlog_unix_nl))) {
src/os/windows/survey_win.cpp
Show inline comments
 
@@ -10,15 +10,13 @@
 
#include "../../stdafx.h"
 

	
 
#include "../../3rdparty/fmt/format.h"
 
#include "../../3rdparty/nlohmann/json.hpp"
 
#include "../../survey.h"
 

	
 
#include <thread>
 
#include <windows.h>
 

	
 
#include "../../safeguards.h"
 

	
 
extern std::string SurveyMemoryToText(uint64_t memory);
 

	
 
void SurveyOS(nlohmann::json &json)
 
{
 
	_OSVERSIONINFOA os;
src/settings.cpp
Show inline comments
 
@@ -716,6 +716,12 @@ bool IntSettingDesc::IsSameValue(const I
 
	return item_value == object_value;
 
}
 

	
 
bool IntSettingDesc::IsDefaultValue(void *object) const
 
{
 
	int32_t object_value = this->Read(object);
 
	return this->def == object_value;
 
}
 

	
 
std::string StringSettingDesc::FormatValue(const void *object) const
 
{
 
	const std::string &str = this->Read(object);
 
@@ -742,12 +748,24 @@ bool StringSettingDesc::IsSameValue(cons
 
	return item->value->compare(str) == 0;
 
}
 

	
 
bool StringSettingDesc::IsDefaultValue(void *object) const
 
{
 
	const std::string &str = this->Read(object);
 
	return this->def == str;
 
}
 

	
 
bool ListSettingDesc::IsSameValue(const IniItem *item, void *object) const
 
{
 
	/* Checking for equality is way more expensive than just writing the value. */
 
	return false;
 
}
 

	
 
bool ListSettingDesc::IsDefaultValue(void *object) const
 
{
 
	/* Defaults of lists are often complicated, and hard to compare. */
 
	return false;
 
}
 

	
 
/**
 
 * Loads all items from a 'grpname' section into a list
 
 * The list parameter can be a nullptr pointer, in this case nothing will be
src/settings_internal.h
Show inline comments
 
@@ -130,6 +130,14 @@ struct SettingDesc {
 
	 * @return True if the value is definitely the same (might be false when the same).
 
	 */
 
	virtual bool IsSameValue(const IniItem *item, void *object) const = 0;
 

	
 
	/**
 
	 * Check whether the value is the same as the default value.
 
	 *
 
	 * @param object The object the setting is in.
 
	 * @return true iff the value is the default value.
 
	 */
 
	virtual bool IsDefaultValue(void *object) const = 0;
 
};
 

	
 
/** Base integer type, including boolean, settings. Only these are shown in the settings UI. */
 
@@ -215,6 +223,7 @@ struct IntSettingDesc : SettingDesc {
 
	std::string FormatValue(const void *object) const override;
 
	void ParseValue(const IniItem *item, void *object) const override;
 
	bool IsSameValue(const IniItem *item, void *object) const override;
 
	bool IsDefaultValue(void *object) const override;
 
	int32_t Read(const void *object) const;
 

	
 
private:
 
@@ -307,6 +316,7 @@ struct StringSettingDesc : SettingDesc {
 
	std::string FormatValue(const void *object) const override;
 
	void ParseValue(const IniItem *item, void *object) const override;
 
	bool IsSameValue(const IniItem *item, void *object) const override;
 
	bool IsDefaultValue(void *object) const override;
 
	const std::string &Read(const void *object) const;
 

	
 
private:
 
@@ -324,6 +334,7 @@ struct ListSettingDesc : SettingDesc {
 
	std::string FormatValue(const void *object) const override;
 
	void ParseValue(const IniItem *item, void *object) const override;
 
	bool IsSameValue(const IniItem *item, void *object) const override;
 
	bool IsDefaultValue(void *object) const override;
 
};
 

	
 
/** Placeholder for settings that have been removed, but might still linger in the savegame. */
 
@@ -334,6 +345,7 @@ struct NullSettingDesc : SettingDesc {
 
	std::string FormatValue(const void *object) const override { NOT_REACHED(); }
 
	void ParseValue(const IniItem *item, void *object) const override { NOT_REACHED(); }
 
	bool IsSameValue(const IniItem *item, void *object) const override { NOT_REACHED(); }
 
	bool IsDefaultValue(void *object) const override { NOT_REACHED(); }
 
};
 

	
 
typedef std::variant<IntSettingDesc, BoolSettingDesc, OneOfManySettingDesc, ManyOfManySettingDesc, StringSettingDesc, ListSettingDesc, NullSettingDesc> SettingVariant;
src/survey.cpp
Show inline comments
 
new file 100644
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file survey.cpp Functions to survey the current game / system, for crashlog and network-survey. */
 

	
 
#include "stdafx.h"
 

	
 
#include "survey.h"
 

	
 
#include "settings_table.h"
 
#include "network/network.h"
 
#include "rev.h"
 
#include "settings_type.h"
 
#include "timer/timer_game_tick.h"
 

	
 
#include "currency.h"
 
#include "fontcache.h"
 
#include "language.h"
 

	
 
#include "ai/ai_info.hpp"
 
#include "game/game.hpp"
 
#include "game/game_info.hpp"
 

	
 
#include "music/music_driver.hpp"
 
#include "sound/sound_driver.hpp"
 
#include "video/video_driver.hpp"
 

	
 
#include "base_media_base.h"
 
#include "blitter/factory.hpp"
 

	
 
#ifdef WITH_ALLEGRO
 
#	include <allegro.h>
 
#endif /* WITH_ALLEGRO */
 
#ifdef WITH_FONTCONFIG
 
#	include <fontconfig/fontconfig.h>
 
#endif /* WITH_FONTCONFIG */
 
#ifdef WITH_PNG
 
	/* pngconf.h, included by png.h doesn't like something in the
 
	 * freetype headers. As such it's not alphabetically sorted. */
 
#	include <png.h>
 
#endif /* WITH_PNG */
 
#ifdef WITH_FREETYPE
 
#	include <ft2build.h>
 
#	include FT_FREETYPE_H
 
#endif /* WITH_FREETYPE */
 
#ifdef WITH_HARFBUZZ
 
#	include <hb.h>
 
#endif /* WITH_HARFBUZZ */
 
#ifdef WITH_ICU_I18N
 
#	include <unicode/uversion.h>
 
#endif /* WITH_ICU_I18N */
 
#ifdef WITH_LIBLZMA
 
#	include <lzma.h>
 
#endif
 
#ifdef WITH_LZO
 
#include <lzo/lzo1x.h>
 
#endif
 
#if defined(WITH_SDL) || defined(WITH_SDL2)
 
#	include <SDL.h>
 
#endif /* WITH_SDL || WITH_SDL2 */
 
#ifdef WITH_ZLIB
 
# include <zlib.h>
 
#endif
 
#ifdef WITH_CURL
 
# include <curl/curl.h>
 
#endif
 

	
 
#include "safeguards.h"
 

	
 
NLOHMANN_JSON_SERIALIZE_ENUM(GRFStatus, {
 
	{GRFStatus::GCS_UNKNOWN, "unknown"},
 
	{GRFStatus::GCS_DISABLED, "disabled"},
 
	{GRFStatus::GCS_NOT_FOUND, "not found"},
 
	{GRFStatus::GCS_INITIALISED, "initialised"},
 
	{GRFStatus::GCS_ACTIVATED, "activated"},
 
})
 

	
 
/** Lookup table to convert a VehicleType to a string. */
 
static const std::string _vehicle_type_to_string[] = {
 
	"train",
 
	"roadveh",
 
	"ship",
 
	"aircraft",
 
};
 

	
 
/**
 
 * List of all the generic setting tables.
 
 *
 
 * There are a few tables that are special and not processed like the rest:
 
 * - _currency_settings
 
 * - _misc_settings
 
 * - _company_settings
 
 * - _win32_settings
 
 * As such, they are not part of this list.
 
 */
 
static auto &GenericSettingTables()
 
{
 
	static const SettingTable _generic_setting_tables[] = {
 
		_difficulty_settings,
 
		_economy_settings,
 
		_game_settings,
 
		_gui_settings,
 
		_linkgraph_settings,
 
		_locale_settings,
 
		_multimedia_settings,
 
		_network_settings,
 
		_news_display_settings,
 
		_pathfinding_settings,
 
		_script_settings,
 
		_world_settings,
 
	};
 
	return _generic_setting_tables;
 
}
 

	
 
/**
 
 * Convert a settings table to JSON.
 
 *
 
 * @param survey The JSON object.
 
 * @param table The settings table to convert.
 
 * @param object The object to get the settings from.
 
 * @param skip_if_default If true, skip any settings that are on their default value.
 
 */
 
static void SurveySettingsTable(nlohmann::json &survey, const SettingTable &table, void *object, bool skip_if_default)
 
{
 
	for (auto &desc : table) {
 
		const SettingDesc *sd = GetSettingDesc(desc);
 
		/* Skip any old settings we no longer save/load. */
 
		if (!SlIsObjectCurrentlyValid(sd->save.version_from, sd->save.version_to)) continue;
 

	
 
		auto name = sd->GetName();
 
		if (skip_if_default && sd->IsDefaultValue(object)) continue;
 
		survey[name] = sd->FormatValue(object);
 
	}
 
}
 

	
 
/**
 
 * Convert settings to JSON.
 
 *
 
 * @param survey The JSON object.
 
 */
 
void SurveySettings(nlohmann::json &survey, bool skip_if_default)
 
{
 
	SurveySettingsTable(survey, _misc_settings, nullptr, skip_if_default);
 
#if defined(_WIN32) && !defined(DEDICATED)
 
	SurveySettingsTable(survey, _win32_settings, nullptr, skip_if_default);
 
#endif
 
	for (auto &table : GenericSettingTables()) {
 
		SurveySettingsTable(survey, table, &_settings_game, skip_if_default);
 
	}
 
	SurveySettingsTable(survey, _currency_settings, &_custom_currency, skip_if_default);
 
	SurveySettingsTable(survey, _company_settings, &_settings_client.company, skip_if_default);
 
}
 

	
 
/**
 
 * Convert compiler information to JSON.
 
 *
 
 * @param survey The JSON object.
 
 */
 
void SurveyCompiler(nlohmann::json &survey)
 
{
 
#if defined(_MSC_VER)
 
	survey["name"] = "MSVC";
 
	survey["version"] = _MSC_VER;
 
#elif defined(__ICC) && defined(__GNUC__)
 
	survey["name"] = "ICC";
 
	survey["version"] = __ICC;
 
#	if defined(__GNUC__)
 
		survey["extra"] = fmt::format("GCC {}.{}.{} mode", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
 
#	endif
 
#elif defined(__GNUC__)
 
	survey["name"] = "GCC";
 
	survey["version"] = fmt::format("{}.{}.{}", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
 
#else
 
	survey["name"] = "unknown";
 
#endif
 

	
 
#if defined(__VERSION__)
 
	survey["extra"] = __VERSION__;
 
#endif
 
}
 

	
 
/**
 
 * Convert generic OpenTTD information to JSON.
 
 *
 
 * @param survey The JSON object.
 
 */
 
void SurveyOpenTTD(nlohmann::json &survey)
 
{
 
	survey["version"]["revision"] = std::string(_openttd_revision);
 
	survey["version"]["modified"] = _openttd_revision_modified;
 
	survey["version"]["tagged"] = _openttd_revision_tagged;
 
	survey["version"]["hash"] = std::string(_openttd_revision_hash);
 
	survey["version"]["newgrf"] = fmt::format("{:X}", _openttd_newgrf_version);
 
	survey["version"]["content"] = std::string(_openttd_content_version);
 
	survey["build_date"] = std::string(_openttd_build_date);
 
	survey["bits"] =
 
#ifdef POINTER_IS_64BIT
 
			64
 
#else
 
			32
 
#endif
 
		;
 
	survey["endian"] =
 
#if (TTD_ENDIAN == TTD_LITTLE_ENDIAN)
 
			"little"
 
#else
 
			"big"
 
#endif
 
		;
 
	survey["dedicated_build"] =
 
#ifdef DEDICATED
 
			"yes"
 
#else
 
			"no"
 
#endif
 
		;
 
}
 

	
 
/**
 
 * Convert generic game information to JSON.
 
 *
 
 * @param survey The JSON object.
 
 */
 
void SurveyConfiguration(nlohmann::json &survey)
 
{
 
	survey["network"] = _networking ? (_network_server ? "server" : "client") : "no";
 
	if (_current_language != nullptr) {
 
		survey["language"]["filename"] = _current_language->file.filename().string();
 
		survey["language"]["name"] = _current_language->name;
 
		survey["language"]["isocode"] = _current_language->isocode;
 
	}
 
	if (BlitterFactory::GetCurrentBlitter() != nullptr) {
 
		survey["blitter"] = BlitterFactory::GetCurrentBlitter()->GetName();
 
	}
 
	if (MusicDriver::GetInstance() != nullptr) {
 
		survey["music_driver"] = MusicDriver::GetInstance()->GetName();
 
	}
 
	if (SoundDriver::GetInstance() != nullptr) {
 
		survey["sound_driver"] = SoundDriver::GetInstance()->GetName();
 
	}
 
	if (VideoDriver::GetInstance() != nullptr) {
 
		survey["video_driver"] = VideoDriver::GetInstance()->GetName();
 
		survey["video_info"] = VideoDriver::GetInstance()->GetInfoString();
 
	}
 
	if (BaseGraphics::GetUsedSet() != nullptr) {
 
		survey["graphics_set"] = fmt::format("{}.{}", BaseGraphics::GetUsedSet()->name, BaseGraphics::GetUsedSet()->version);
 
	}
 
	if (BaseMusic::GetUsedSet() != nullptr) {
 
		survey["music_set"] = fmt::format("{}.{}", BaseMusic::GetUsedSet()->name, BaseMusic::GetUsedSet()->version);
 
	}
 
	if (BaseSounds::GetUsedSet() != nullptr) {
 
		survey["sound_set"] = fmt::format("{}.{}", BaseSounds::GetUsedSet()->name, BaseSounds::GetUsedSet()->version);
 
	}
 
}
 

	
 
/**
 
 * Convert font information to JSON.
 
 *
 
 * @param survey The JSON object.
 
 */
 
void SurveyFont(nlohmann::json &survey)
 
{
 
	survey["small"] = FontCache::Get(FS_SMALL)->GetFontName();
 
	survey["medium"] = FontCache::Get(FS_NORMAL)->GetFontName();
 
	survey["large"] = FontCache::Get(FS_LARGE)->GetFontName();
 
	survey["mono"] = FontCache::Get(FS_MONO)->GetFontName();
 
}
 

	
 
/**
 
 * Convert company information to JSON.
 
 *
 
 * @param survey The JSON object.
 
 */
 
void SurveyCompanies(nlohmann::json &survey)
 
{
 
	for (const Company *c : Company::Iterate()) {
 
		auto &company = survey[std::to_string(c->index)];
 
		if (c->ai_info == nullptr) {
 
			company["type"] = "human";
 
		} else {
 
			company["type"] = "ai";
 
			company["script"] = fmt::format("{}.{}", c->ai_info->GetName(), c->ai_info->GetVersion());
 
		}
 

	
 
		for (VehicleType type = VEH_BEGIN; type < VEH_COMPANY_END; type++) {
 
			uint amount = c->group_all[type].num_vehicle;
 
			company["vehicles"][_vehicle_type_to_string[type]] = amount;
 
		}
 

	
 
		company["infrastructure"]["road"] = c->infrastructure.GetRoadTotal();
 
		company["infrastructure"]["tram"] = c->infrastructure.GetTramTotal();
 
		company["infrastructure"]["rail"] = c->infrastructure.GetRailTotal();
 
		company["infrastructure"]["signal"] = c->infrastructure.signal;
 
		company["infrastructure"]["water"] = c->infrastructure.water;
 
		company["infrastructure"]["station"] = c->infrastructure.station;
 
		company["infrastructure"]["airport"] = c->infrastructure.airport;
 
	}
 
}
 

	
 
/**
 
 * Convert timer information to JSON.
 
 *
 
 * @param survey The JSON object.
 
 */
 
void SurveyTimers(nlohmann::json &survey)
 
{
 
	survey["ticks"] = TimerGameTick::counter;
 
	survey["seconds"] = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - _switch_mode_time).count();
 

	
 
	TimerGameCalendar::YearMonthDay ymd;
 
	TimerGameCalendar::ConvertDateToYMD(TimerGameCalendar::date, &ymd);
 
	survey["calendar"] = fmt::format("{:04}-{:02}-{:02} ({})", ymd.year, ymd.month + 1, ymd.day, TimerGameCalendar::date_fract);
 
}
 

	
 
/**
 
 * Convert GRF information to JSON.
 
 *
 
 * @param survey The JSON object.
 
 */
 
void SurveyGrfs(nlohmann::json &survey)
 
{
 
	for (GRFConfig *c = _grfconfig; c != nullptr; c = c->next) {
 
		auto grfid = fmt::format("{:08x}", BSWAP32(c->ident.grfid));
 
		auto &grf = survey[grfid];
 

	
 
		grf["md5sum"] = FormatArrayAsHex(c->ident.md5sum);
 
		grf["status"] = c->status;
 

	
 
		if ((c->palette & GRFP_GRF_MASK) == GRFP_GRF_UNSET) grf["palette"] = "unset";
 
		if ((c->palette & GRFP_GRF_MASK) == GRFP_GRF_DOS) grf["palette"] = "dos";
 
		if ((c->palette & GRFP_GRF_MASK) == GRFP_GRF_WINDOWS) grf["palette"] = "windows";
 
		if ((c->palette & GRFP_GRF_MASK) == GRFP_GRF_ANY) grf["palette"] = "any";
 

	
 
		if ((c->palette & GRFP_BLT_MASK) == GRFP_BLT_UNSET) grf["blitter"] = "unset";
 
		if ((c->palette & GRFP_BLT_MASK) == GRFP_BLT_32BPP) grf["blitter"] = "32bpp";
 

	
 
		grf["is_static"] = HasBit(c->flags, GCF_STATIC);
 

	
 
		std::vector<uint32_t> parameters;
 
		for (int i = 0; i < c->num_params; i++) {
 
			parameters.push_back(c->param[i]);
 
		}
 
		grf["parameters"] = parameters;
 
	}
 
}
 

	
 
/**
 
 * Convert game-script information to JSON.
 
 *
 
 * @param survey The JSON object.
 
 */
 
void SurveyGameScript(nlohmann::json &survey)
 
{
 
	if (Game::GetInfo() == nullptr) return;
 

	
 
	survey = fmt::format("{}.{}", Game::GetInfo()->GetName(), Game::GetInfo()->GetVersion());
 
}
 

	
 
/**
 
 * Convert compiled libraries information to JSON.
 
 *
 
 * @param survey The JSON object.
 
 */
 
void SurveyLibraries(nlohmann::json &survey)
 
{
 
#ifdef WITH_ALLEGRO
 
	survey["allegro"] = std::string(allegro_id);
 
#endif /* WITH_ALLEGRO */
 

	
 
#ifdef WITH_FONTCONFIG
 
	int version = FcGetVersion();
 
	survey["fontconfig"] = fmt::format("{}.{}.{}", version / 10000, (version / 100) % 100, version % 100);
 
#endif /* WITH_FONTCONFIG */
 

	
 
#ifdef WITH_FREETYPE
 
	FT_Library library;
 
	int major, minor, patch;
 
	FT_Init_FreeType(&library);
 
	FT_Library_Version(library, &major, &minor, &patch);
 
	FT_Done_FreeType(library);
 
	survey["freetype"] = fmt::format("{}.{}.{}", major, minor, patch);
 
#endif /* WITH_FREETYPE */
 

	
 
#if defined(WITH_HARFBUZZ)
 
	survey["harfbuzz"] = hb_version_string();
 
#endif /* WITH_HARFBUZZ */
 

	
 
#if defined(WITH_ICU_I18N)
 
	/* 4 times 0-255, separated by dots (.) and a trailing '\0' */
 
	char buf[4 * 3 + 3 + 1];
 
	UVersionInfo ver;
 
	u_getVersion(ver);
 
	u_versionToString(ver, buf);
 
	survey["icu_i18n"] = buf;
 
#endif /* WITH_ICU_I18N */
 

	
 
#ifdef WITH_LIBLZMA
 
	survey["lzma"] = lzma_version_string();
 
#endif
 

	
 
#ifdef WITH_LZO
 
	survey["lzo"] = lzo_version_string();
 
#endif
 

	
 
#ifdef WITH_PNG
 
	survey["png"] = png_get_libpng_ver(nullptr);
 
#endif /* WITH_PNG */
 

	
 
#ifdef WITH_SDL
 
	const SDL_version *sdl_v = SDL_Linked_Version();
 
	survey["sdl"] = fmt::format("{}.{}.{}", sdl_v->major, sdl_v->minor, sdl_v->patch);
 
#elif defined(WITH_SDL2)
 
	SDL_version sdl2_v;
 
	SDL_GetVersion(&sdl2_v);
 
	survey["sdl2"] = fmt::format("{}.{}.{}", sdl2_v.major, sdl2_v.minor, sdl2_v.patch);
 
#endif
 

	
 
#ifdef WITH_ZLIB
 
	survey["zlib"] = zlibVersion();
 
#endif
 

	
 
#ifdef WITH_CURL
 
	auto *curl_v = curl_version_info(CURLVERSION_NOW);
 
	survey["curl"] = curl_v->version;
 
	survey["curl_ssl"] = curl_v->ssl_version == nullptr ? "none" : curl_v->ssl_version;
 
#endif
 
}
 

	
 
/**
 
 * Change the bytes of memory into a textual version rounded up to the biggest unit.
 
 *
 
 * For example, 16751108096 would become 16 GiB.
 
 *
 
 * @param memory The bytes of memory.
 
 * @return std::string A textual representation.
 
 */
 
std::string SurveyMemoryToText(uint64_t memory)
 
{
 
	memory = memory / 1024; // KiB
 
	memory = CeilDiv(memory, 1024); // MiB
 

	
 
	/* Anything above 512 MiB we represent in GiB. */
 
	if (memory > 512) {
 
		return fmt::format("{} GiB", CeilDiv(memory, 1024));
 
	}
 

	
 
	/* Anything above 64 MiB we represent in a multiplier of 128 MiB. */
 
	if (memory > 64) {
 
		return fmt::format("{} MiB", Ceil(memory, 128));
 
	}
 

	
 
	/* Anything else in a multiplier of 4 MiB. */
 
	return fmt::format("{} MiB", Ceil(memory, 4));
 
}
src/survey.h
Show inline comments
 
new file 100644
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file survey.h Functions to survey the current game / system, for crashlog and network-survey. */
 

	
 
#ifndef SURVEY_H
 
#define SURVEY_H
 

	
 
#include "3rdparty/nlohmann/json.hpp"
 

	
 
std::string SurveyMemoryToText(uint64_t memory);
 

	
 
void SurveyCompanies(nlohmann::json &survey);
 
void SurveyCompiler(nlohmann::json &survey);
 
void SurveyConfiguration(nlohmann::json &survey);
 
void SurveyFont(nlohmann::json &survey);
 
void SurveyGameScript(nlohmann::json &survey);
 
void SurveyGrfs(nlohmann::json &survey);
 
void SurveyLibraries(nlohmann::json &survey);
 
void SurveyOpenTTD(nlohmann::json &survey);
 
void SurveySettings(nlohmann::json &survey, bool skip_if_default);
 
void SurveyTimers(nlohmann::json &survey);
 

	
 
/* Defined in os/<os>/survey_<os>.cpp. */
 
void SurveyOS(nlohmann::json &json);
 

	
 
#endif /* SURVEY_H */
0 comments (0 inline, 0 general)