diff --git a/src/survey.cpp b/src/survey.cpp
new file mode 100644
--- /dev/null
+++ b/src/survey.cpp
@@ -0,0 +1,458 @@
+/*
+ * 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 .
+ */
+
+/** @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
+#endif /* WITH_ALLEGRO */
+#ifdef WITH_FONTCONFIG
+# include
+#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
+#endif /* WITH_PNG */
+#ifdef WITH_FREETYPE
+# include
+# include FT_FREETYPE_H
+#endif /* WITH_FREETYPE */
+#ifdef WITH_HARFBUZZ
+# include
+#endif /* WITH_HARFBUZZ */
+#ifdef WITH_ICU_I18N
+# include
+#endif /* WITH_ICU_I18N */
+#ifdef WITH_LIBLZMA
+# include
+#endif
+#ifdef WITH_LZO
+#include
+#endif
+#if defined(WITH_SDL) || defined(WITH_SDL2)
+# include
+#endif /* WITH_SDL || WITH_SDL2 */
+#ifdef WITH_ZLIB
+# include
+#endif
+#ifdef WITH_CURL
+# include
+#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::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 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));
+}