Changeset - r27914:bfd64f30ccdf
[Not reviewed]
master
0 12 2
Patric Stout - 8 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
 
@@ -429,12 +429,14 @@ add_files(
 
    subsidy.cpp
 
    subsidy_base.h
 
    subsidy_cmd.h
 
    subsidy_func.h
 
    subsidy_gui.cpp
 
    subsidy_type.h
 
    survey.cpp
 
    survey.h
 
    tar_type.h
 
    terraform_cmd.cpp
 
    terraform_cmd.h
 
    terraform_gui.cpp
 
    terraform_gui.h
 
    textbuf.cpp
src/crashlog.cpp
Show inline comments
 
@@ -6,303 +6,66 @@
 
 */
 

	
 
/** @file crashlog.cpp Implementation of generic function to be called to log a crash */
 

	
 
#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");
 
}
 

	
 
/**
 
 * Create a timestamped filename.
 
 * @param ext           The extension for the filename.
 
 * @param with_dir      Whether to prepend the filename with the personal directory.
 
@@ -317,50 +80,119 @@ std::string CrashLog::CreateFileName(con
 
	}
 
	return fmt::format("{}{}{}", with_dir ? _personal_dir : std::string{}, crashname, ext);
 
}
 

	
 
/**
 
 * 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");
 
}
 

	
 
/**
 
 * Write the crash log to a file.
 
 * @note The filename will be written to \c crashlog_filename.
 
 * @return true when the crash log was successfully written.
 
 */
 
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;
 
}
 

	
 
/**
 
@@ -411,12 +243,15 @@ bool CrashLog::WriteScreenshot()
 
	std::string filename = this->CreateFileName("", false);
 
	bool res = MakeScreenshot(SC_CRASHLOG, filename);
 
	if (res) this->screenshot_filename = _full_screenshot_path;
 
	return res;
 
}
 

	
 
/**
 
 * Send the survey result, noting it was a crash.
 
 */
 
void CrashLog::SendSurvey() const
 
{
 
	if (_game_mode == GM_NORMAL) {
 
		_survey.Transmit(NetworkSurveyHandler::Reason::CRASH, true);
 
	}
 
}
 
@@ -430,19 +265,18 @@ void CrashLog::MakeCrashLog()
 
{
 
	/* Don't keep looping logging crashes. */
 
	static bool crashlogged = false;
 
	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(); });
 
	if (ret) {
 
		fmt::print("Crash log written to {}. Please add this file to any bug reports.\n\n", this->crashlog_filename);
 
	} else {
src/crashlog.h
Show inline comments
 
@@ -7,78 +7,64 @@
 

	
 
/** @file crashlog.h Functions to be called to log a crash */
 

	
 
#ifndef CRASHLOG_H
 
#define CRASHLOG_H
 

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

	
 
/**
 
 * Helper class for creating crash logs.
 
 */
 
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,
 
	 * catch it and return false. This function should, in theory, never not return, even
 
	 * in the worst conditions.
 
	 *
 
	 * @param section_name The name of the section to be executed. Printed when a crash happens.
 
	 * @param func The function to call.
 
	 * @return true iff the function returned true.
 
	 */
 
	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();
 

	
 
	void SendSurvey() const;
 

	
src/network/network_survey.cpp
Show inline comments
 
@@ -9,32 +9,15 @@
 

	
 
#include "../stdafx.h"
 
#include "network_survey.h"
 
#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"
 

	
 
extern std::string _savegame_id;
 

	
 
NetworkSurveyHandler _survey = {};
 
@@ -43,300 +26,12 @@ NLOHMANN_JSON_SERIALIZE_ENUM(NetworkSurv
 
	{NetworkSurveyHandler::Reason::PREVIEW, "preview"},
 
	{NetworkSurveyHandler::Reason::LEAVE, "leave"},
 
	{NetworkSurveyHandler::Reason::EXIT, "exit"},
 
	{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.
 
 *
 
 * @param reason The reason for sending the survey.
 
 * @param for_preview Whether the payload is meant for preview. This indents the result, and filters out the id/key.
 
 * @return std::string The JSON payload as string for the survey.
 
@@ -345,12 +40,13 @@ std::string NetworkSurveyHandler::Create
 
{
 
	nlohmann::json survey;
 

	
 
	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. */
 
	survey["key"] = for_preview ? "(redacted)" : SURVEY_KEY;
 
#else
 
	survey["key"] = "";
 
@@ -359,19 +55,21 @@ std::string NetworkSurveyHandler::Create
 
	{
 
		auto &info = survey["info"];
 
		SurveyOS(info["os"]);
 
		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"]);
 
	}
 

	
 
	/* For preview, we indent with 4 whitespaces to make things more readable. */
 
	int indent = for_preview ? 4 : -1;
src/os/macosx/crashlog_osx.cpp
Show inline comments
 
@@ -46,59 +46,30 @@ static constexpr int _signals_to_handle[
 
 * OSX implementation for the crash logger.
 
 */
 
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
 
	static bool MinidumpCallback(const char* dump_dir, const char* minidump_id, void* context, bool succeeded)
 
	{
 
		CrashLogOSX *crashlog = reinterpret_cast<CrashLogOSX *>(context);
 
@@ -150,13 +121,13 @@ public:
 
	void DisplayCrashDialog() const
 
	{
 
		static const char crash_title[] =
 
			"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{}",
 
				 this->crashlog_filename, this->crashdump_filename, this->savegame_filename, this->screenshot_filename);
 

	
 
		ShowMacDialog(crash_title, message.c_str(), "Quit");
src/os/macosx/survey_osx.cpp
Show inline comments
 
@@ -7,22 +7,20 @@
 

	
 
/** @file survey_osx.cpp OSX implementation of OS-specific survey information. */
 

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

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

	
 
#include <mach-o/arch.h>
 
#include <thread>
 

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

	
 
extern std::string SurveyMemoryToText(uint64_t memory);
 

	
 
void SurveyOS(nlohmann::json &json)
 
{
 
	int ver_maj, ver_min, ver_bug;
 
	GetMacOSVersion(&ver_maj, &ver_min, &ver_bug);
 

	
 
	const NXArchInfo *arch = NXGetLocalArchInfo();
src/os/unix/crashlog_unix.cpp
Show inline comments
 
@@ -46,61 +46,32 @@ static constexpr int _signals_to_handle[
 
 * Unix implementation for the crash logger.
 
 */
 
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
 
	static bool MinidumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded)
 
	{
 
		CrashLogUnix *crashlog = reinterpret_cast<CrashLogUnix *>(context);
src/os/unix/survey_unix.cpp
Show inline comments
 
@@ -5,23 +5,20 @@
 
 * 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_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>
 
#include <unistd.h>
 

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

	
 
extern std::string SurveyMemoryToText(uint64_t memory);
 

	
 
void SurveyOS(nlohmann::json &json)
 
{
 
	struct utsname name;
 
	if (uname(&name) < 0) {
 
		json["os"] = "Unix";
 
		return;
src/os/windows/crashlog_win.cpp
Show inline comments
 
@@ -35,12 +35,39 @@
 

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

	
 
/** 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.
 
 *
 
 * @param exit_code The exit code to return.
 
 */
 
static void NORETURN ImmediateExitProcess(uint exit_code)
 
@@ -54,15 +81,23 @@ static void NORETURN ImmediateExitProces
 
 * Windows implementation for the crash logger.
 
 */
 
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
 
	static bool MinidumpCallback(const wchar_t *dump_dir, const wchar_t *minidump_id, void *context, EXCEPTION_POINTERS *exinfo, MDRawAssertionInfo *assertion, bool succeeded)
 
	{
 
		CrashLogWindows *crashlog = reinterpret_cast<CrashLogWindows *>(context);
 
@@ -133,47 +168,17 @@ public:
 
	/** Points to the current crash log. */
 
	static CrashLogWindows *current;
 
};
 

	
 
/* 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 {
 
		BOOL (WINAPI * pSymInitialize)(HANDLE, PCSTR, BOOL);
 
		BOOL (WINAPI * pSymSetOptions)(DWORD);
 
		BOOL (WINAPI * pSymCleanup)(HANDLE);
 
@@ -192,13 +197,13 @@ static const uint MAX_FRAMES     = 64;
 
		dbghelp.GetProcAddress("SymGetModuleBase64"),
 
		dbghelp.GetProcAddress("SymGetModuleInfo64"),
 
		dbghelp.GetProcAddress("SymGetSymFromAddr64"),
 
		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()) {
 
		/* Initialize symbol handler. */
 
		HANDLE hCur = GetCurrentProcess();
 
		proc.pSymInitialize(hCur, nullptr, TRUE);
 
@@ -243,13 +248,13 @@ static const uint MAX_FRAMES     = 64;
 
#else
 
				IMAGE_FILE_MACHINE_I386,
 
#endif
 
				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;
 
			}
 

	
 
			/* Get module name. */
 
			const char *mod_name = "???";
 

	
 
@@ -257,39 +262,37 @@ static const uint MAX_FRAMES     = 64;
 
			module.SizeOfStruct = sizeof(module);
 
			if (proc.pSymGetModuleInfo64(hCur, frame.AddrPC.Offset, &module)) {
 
				mod_name = module.ModuleName;
 
			}
 

	
 
			/* 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 */
 

	
 
extern bool CloseConsoleLogIfActive();
 
static void ShowCrashlogWindow();
 

	
 
@@ -427,13 +430,13 @@ static void CDECL CustomAbort(int signal
 
/* The crash log GUI */
 

	
 
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";
 

	
 
static const wchar_t * const _expand_texts[] = {L"S&how report >>", L"&Hide report <<" };
 

	
 
@@ -459,15 +462,16 @@ static void SetWndSize(HWND wnd, int mod
 
}
 

	
 
static INT_PTR CALLBACK CrashDialogFunc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
 
{
 
	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;
 
			const size_t crash_desc_buf_length = lengthof(_crash_desc) + filename_buf_length * filename_count + 1;
 

	
 
			/* We need to put the crash-log in a separate buffer because the default
 
@@ -483,13 +487,13 @@ static INT_PTR CALLBACK CrashDialogFunc(
 
			wchar_t *crash_desc_buf = reinterpret_cast<wchar_t *>(raw_buffer);
 
			wchar_t *crashlog_buf = crash_desc_buf + crash_desc_buf_length;
 
			wchar_t *filename_buf = crashlog_buf + crashlog_length;
 
			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))) {
 
				if (c == '\n') p += Utf8Encode(p, '\r');
 
				p += Utf8Encode(p, c);
 
			}
src/os/windows/survey_win.cpp
Show inline comments
 
@@ -7,21 +7,19 @@
 

	
 
/** @file survey_win.cpp Windows implementation of OS-specific survey information. */
 

	
 
#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;
 
	os.dwOSVersionInfoSize = sizeof(os);
 
	GetVersionExA(&os);
 

	
src/settings.cpp
Show inline comments
 
@@ -713,12 +713,18 @@ bool IntSettingDesc::IsSameValue(const I
 
{
 
	int32_t item_value = (int32_t)this->ParseValue(item->value->c_str());
 
	int32_t object_value = this->Read(object);
 
	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);
 
	switch (GetVarMemType(this->save.conv)) {
 
		case SLE_VAR_STR: return str;
 

	
 
@@ -739,18 +745,30 @@ bool StringSettingDesc::IsSameValue(cons
 
	if (GetVarMemType(this->save.conv) == SLE_VAR_STRQ) return false;
 

	
 
	const std::string &str = this->Read(object);
 
	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
 
 * saved and a callback function should be defined that will take over the
 
 * list-handling and store the data itself somewhere.
 
 * @param ini IniFile handle to the ini file with the source data
src/settings_internal.h
Show inline comments
 
@@ -127,12 +127,20 @@ struct SettingDesc {
 
	 * the value might be the same as in the Ini item.
 
	 * @param item The Ini item with the content of this setting.
 
	 * @param object The object the setting is in.
 
	 * @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. */
 
struct IntSettingDesc : SettingDesc {
 
	/**
 
	 * A check to be performed before the setting gets changed. The passed integer may be
 
@@ -212,12 +220,13 @@ struct IntSettingDesc : SettingDesc {
 
	void MakeValueValidAndWrite(const void *object, int32_t value) const;
 

	
 
	virtual size_t ParseValue(const char *str) const;
 
	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:
 
	void MakeValueValid(int32_t &value) const;
 
	void Write(const void *object, int32_t value) const;
 
};
 
@@ -304,12 +313,13 @@ struct StringSettingDesc : SettingDesc {
 
	bool IsStringSetting() const override { return true; }
 
	void ChangeValue(const void *object, std::string &newval) const;
 

	
 
	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:
 
	void MakeValueValid(std::string &str) const;
 
	void Write(const void *object, const std::string &str) const;
 
};
 
@@ -321,22 +331,24 @@ struct ListSettingDesc : SettingDesc {
 

	
 
	const char *def;        ///< default value given when none is present
 

	
 
	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. */
 
struct NullSettingDesc : SettingDesc {
 
	NullSettingDesc(const SaveLoad &save) :
 
		SettingDesc(save, SF_NOT_IN_CONFIG, false) {}
 

	
 
	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;
 

	
 
/**
 
 * Helper to convert the type of the iterated settings description to a pointer to it.
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)