Update: Translations from eints
[openttd-github.git] / src / crashlog.cpp
blob04129d7c3bd02da2f5185c394b6a3dd4a89690d2
1 /*
2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
6 */
8 /** @file crashlog.cpp Implementation of generic function to be called to log a crash */
10 #include "stdafx.h"
11 #include "crashlog.h"
12 #include "survey.h"
13 #include "gamelog.h"
14 #include "map_func.h"
15 #include "music/music_driver.hpp"
16 #include "sound/sound_driver.hpp"
17 #include "video/video_driver.hpp"
18 #include "saveload/saveload.h"
19 #include "screenshot.h"
20 #include "network/network_survey.h"
21 #include "news_gui.h"
22 #include "fileio_func.h"
23 #include "fileio_type.h"
25 #include "company_func.h"
26 #include "3rdparty/fmt/chrono.h"
27 #include "3rdparty/fmt/std.h"
28 #include "core/format.hpp"
30 #include "safeguards.h"
32 /* static */ std::string CrashLog::message{};
34 /** The version of the schema of the JSON information. */
35 constexpr uint8_t CRASHLOG_SURVEY_VERSION = 1;
37 /**
38 * Writes the gamelog data to the buffer.
39 * @param output_iterator Iterator to write the output to.
41 static void SurveyGamelog(nlohmann::json &json)
43 json = nlohmann::json::array();
45 _gamelog.Print([&json](const std::string &s) {
46 json.push_back(s);
47 });
50 /**
51 * Writes up to 32 recent news messages to the buffer, with the most recent first.
52 * @param output_iterator Iterator to write the output to.
54 static void SurveyRecentNews(nlohmann::json &json)
56 json = nlohmann::json::array();
58 int i = 0;
59 for (const auto &news : GetNews()) {
60 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(news.date);
61 json.push_back(fmt::format("({}-{:02}-{:02}) StringID: {}, Type: {}, Ref1: {}, {}, Ref2: {}, {}",
62 ymd.year, ymd.month + 1, ymd.day, news.string_id, news.type,
63 news.reftype1, news.ref1, news.reftype2, news.ref2));
64 if (++i > 32) break;
68 /**
69 * Create a timestamped filename.
70 * @param ext The extension for the filename.
71 * @param with_dir Whether to prepend the filename with the personal directory.
72 * @return The filename
74 std::string CrashLog::CreateFileName(const char *ext, bool with_dir) const
76 static std::string crashname;
78 if (crashname.empty()) {
79 crashname = fmt::format("crash{:%Y%m%d%H%M%S}", fmt::gmtime(time(nullptr)));
81 return fmt::format("{}{}{}", with_dir ? _personal_dir : std::string{}, crashname, ext);
84 /**
85 * Fill the crash log buffer with all data of a crash log.
87 void CrashLog::FillCrashLog()
89 /* Reminder: this JSON is read in an automated fashion.
90 * If any structural changes are applied, please bump the version. */
91 this->survey["schema"] = CRASHLOG_SURVEY_VERSION;
92 this->survey["date"] = fmt::format("{:%Y-%m-%d %H:%M:%S} (UTC)", fmt::gmtime(time(nullptr)));
94 /* If no internal reason was logged, it must be a crash. */
95 if (CrashLog::message.empty()) {
96 this->SurveyCrash(this->survey["crash"]);
97 } else {
98 this->survey["crash"]["reason"] = CrashLog::message;
99 CrashLog::message.clear();
102 if (!this->TryExecute("stacktrace", [this]() { this->SurveyStacktrace(this->survey["stacktrace"]); return true; })) {
103 this->survey["stacktrace"] = "crashed while gathering information";
106 if (!this->TryExecute("session", [this]() { SurveyGameSession(this->survey["session"]); return true; })) {
107 this->survey["session"] = "crashed while gathering information";
111 auto &info = this->survey["info"];
112 if (!this->TryExecute("os", [&info]() { SurveyOS(info["os"]); return true; })) {
113 info["os"] = "crashed while gathering information";
115 if (!this->TryExecute("openttd", [&info]() { SurveyOpenTTD(info["openttd"]); return true; })) {
116 info["openttd"] = "crashed while gathering information";
118 if (!this->TryExecute("configuration", [&info]() { SurveyConfiguration(info["configuration"]); return true; })) {
119 info["configuration"] = "crashed while gathering information";
121 if (!this->TryExecute("font", [&info]() { SurveyFont(info["font"]); return true; })) {
122 info["font"] = "crashed while gathering information";
124 if (!this->TryExecute("compiler", [&info]() { SurveyCompiler(info["compiler"]); return true; })) {
125 info["compiler"] = "crashed while gathering information";
127 if (!this->TryExecute("libraries", [&info]() { SurveyLibraries(info["libraries"]); return true; })) {
128 info["libraries"] = "crashed while gathering information";
130 if (!this->TryExecute("plugins", [&info]() { SurveyPlugins(info["plugins"]); return true; })) {
131 info["plugins"] = "crashed while gathering information";
136 auto &game = this->survey["game"];
137 game["local_company"] = _local_company;
138 game["current_company"] = _current_company;
140 if (!this->TryExecute("timers", [&game]() { SurveyTimers(game["timers"]); return true; })) {
141 game["libraries"] = "crashed while gathering information";
143 if (!this->TryExecute("companies", [&game]() { SurveyCompanies(game["companies"]); return true; })) {
144 game["companies"] = "crashed while gathering information";
146 if (!this->TryExecute("settings", [&game]() { SurveySettings(game["settings_changed"], true); return true; })) {
147 game["settings"] = "crashed while gathering information";
149 if (!this->TryExecute("grfs", [&game]() { SurveyGrfs(game["grfs"]); return true; })) {
150 game["grfs"] = "crashed while gathering information";
152 if (!this->TryExecute("game_script", [&game]() { SurveyGameScript(game["game_script"]); return true; })) {
153 game["game_script"] = "crashed while gathering information";
155 if (!this->TryExecute("gamelog", [&game]() { SurveyGamelog(game["gamelog"]); return true; })) {
156 game["gamelog"] = "crashed while gathering information";
158 if (!this->TryExecute("news", [&game]() { SurveyRecentNews(game["news"]); return true; })) {
159 game["news"] = "crashed while gathering information";
164 void CrashLog::PrintCrashLog() const
166 fmt::print(" OpenTTD version:\n");
167 fmt::print(" Version: {}\n", this->survey["info"]["openttd"]["version"]["revision"].get<std::string>());
168 fmt::print(" Hash: {}\n", this->survey["info"]["openttd"]["version"]["hash"].get<std::string>());
169 fmt::print(" NewGRF ver: {}\n", this->survey["info"]["openttd"]["version"]["newgrf"].get<std::string>());
170 fmt::print(" Content ver: {}\n", this->survey["info"]["openttd"]["version"]["content"].get<std::string>());
171 fmt::print("\n");
173 fmt::print(" Crash:\n");
174 fmt::print(" Reason: {}\n", this->survey["crash"]["reason"].get<std::string>());
175 fmt::print("\n");
177 fmt::print(" Stacktrace:\n");
178 for (const auto &line : this->survey["stacktrace"]) {
179 fmt::print(" {}\n", line.get<std::string>());
181 fmt::print("\n");
185 * Write the crash log to a file.
186 * @note The filename will be written to \c crashlog_filename.
187 * @return true when the crash log was successfully written.
189 bool CrashLog::WriteCrashLog()
191 this->crashlog_filename = this->CreateFileName(".json.log");
193 auto file = FioFOpenFile(this->crashlog_filename, "w", NO_DIRECTORY);
194 if (!file.has_value()) return false;
196 std::string survey_json = this->survey.dump(4);
198 size_t len = survey_json.size();
199 size_t written = fwrite(survey_json.data(), 1, len, *file);
201 return len == written;
205 * Write the (crash) dump to a file.
207 * @note Sets \c crashdump_filename when there is a successful return.
208 * @return True iff the crashdump was successfully created.
210 /* virtual */ bool CrashLog::WriteCrashDump()
212 fmt::print("No method to create a crash.dmp available.\n");
213 return false;
217 * Write the (crash) savegame to a file.
218 * @note The filename will be written to \c savegame_filename.
219 * @return true when the crash save was successfully made.
221 bool CrashLog::WriteSavegame()
223 /* If the map doesn't exist, saving will fail too. If the map got
224 * initialised, there is a big chance the rest is initialised too. */
225 if (!Map::IsInitialized()) return false;
227 try {
228 _gamelog.Emergency();
230 this->savegame_filename = this->CreateFileName(".sav");
232 /* Don't do a threaded saveload. */
233 return SaveOrLoad(this->savegame_filename, SLO_SAVE, DFT_GAME_FILE, NO_DIRECTORY, false) == SL_OK;
234 } catch (...) {
235 return false;
240 * Write the (crash) screenshot to a file.
241 * @note The filename will be written to \c screenshot_filename.
242 * @return std::nullopt when the crash screenshot could not be made, otherwise the filename.
244 bool CrashLog::WriteScreenshot()
246 /* Don't draw when we have invalid screen size */
247 if (_screen.width < 1 || _screen.height < 1 || _screen.dst_ptr == nullptr) return false;
249 std::string filename = this->CreateFileName("", false);
250 bool res = MakeScreenshot(SC_CRASHLOG, filename);
251 if (res) this->screenshot_filename = _full_screenshot_path;
252 return res;
256 * Send the survey result, noting it was a crash.
258 void CrashLog::SendSurvey() const
260 if (_game_mode == GM_NORMAL) {
261 _survey.Transmit(NetworkSurveyHandler::Reason::CRASH, true);
266 * Makes the crash log, writes it to a file and then subsequently tries
267 * to make a crash dump and crash savegame. It uses DEBUG to write
268 * information like paths to the console.
270 void CrashLog::MakeCrashLog()
272 /* Don't keep looping logging crashes. */
273 static bool crashlogged = false;
274 if (crashlogged) return;
275 crashlogged = true;
277 fmt::print("Crash encountered, generating crash log...\n");
278 this->FillCrashLog();
279 fmt::print("Crash log generated.\n\n");
281 fmt::print("Crash in summary:\n");
282 this->TryExecute("crashlog", [this]() { this->PrintCrashLog(); return true; });
284 fmt::print("Writing crash log to disk...\n");
285 bool ret = this->TryExecute("crashlog", [this]() { return this->WriteCrashLog(); });
286 if (ret) {
287 fmt::print("Crash log written to {}. Please add this file to any bug reports.\n\n", this->crashlog_filename);
288 } else {
289 fmt::print("Writing crash log failed. Please attach the output above to any bug reports.\n\n");
290 this->crashlog_filename = "(failed to write crash log)";
293 fmt::print("Writing crash dump to disk...\n");
294 ret = this->TryExecute("crashdump", [this]() { return this->WriteCrashDump(); });
295 if (ret) {
296 fmt::print("Crash dump written to {}. Please add this file to any bug reports.\n\n", this->crashdump_filename);
297 } else {
298 fmt::print("Writing crash dump failed.\n\n");
299 this->crashdump_filename = "(failed to write crash dump)";
302 fmt::print("Writing crash savegame...\n");
303 ret = this->TryExecute("savegame", [this]() { return this->WriteSavegame(); });
304 if (ret) {
305 fmt::print("Crash savegame written to {}. Please add this file and the last (auto)save to any bug reports.\n\n", this->savegame_filename);
306 } else {
307 fmt::print("Writing crash savegame failed. Please attach the last (auto)save to any bug reports.\n\n");
308 this->savegame_filename = "(failed to write crash savegame)";
311 fmt::print("Writing crash screenshot...\n");
312 ret = this->TryExecute("screenshot", [this]() { return this->WriteScreenshot(); });
313 if (ret) {
314 fmt::print("Crash screenshot written to {}. Please add this file to any bug reports.\n\n", this->screenshot_filename);
315 } else {
316 fmt::print("Writing crash screenshot failed.\n\n");
317 this->screenshot_filename = "(failed to write crash screenshot)";
320 this->TryExecute("survey", [this]() { this->SendSurvey(); return true; });
324 * Sets a message for the error message handler.
325 * @param message The error message of the error.
327 /* static */ void CrashLog::SetErrorMessage(const std::string &message)
329 CrashLog::message = message;
333 * Try to close the sound/video stuff so it doesn't keep lingering around
334 * incorrect video states or so, e.g. keeping dpmi disabled.
336 /* static */ void CrashLog::AfterCrashLogCleanup()
338 if (MusicDriver::GetInstance() != nullptr) MusicDriver::GetInstance()->Stop();
339 if (SoundDriver::GetInstance() != nullptr) SoundDriver::GetInstance()->Stop();
340 if (VideoDriver::GetInstance() != nullptr) VideoDriver::GetInstance()->Stop();