Fix 03cc0d6: Mark level crossings dirty when removing road from them, not from bridge...
[openttd-github.git] / src / gamelog.cpp
blob7880d06e42246e1b73feb4c0b009449bf893fa89
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 gamelog.cpp Definition of functions used for logging of important changes in the game */
10 #include "stdafx.h"
11 #include "saveload/saveload.h"
12 #include "string_func.h"
13 #include "settings_type.h"
14 #include "gamelog_internal.h"
15 #include "console_func.h"
16 #include "debug.h"
17 #include "date_func.h"
18 #include "rev.h"
20 #include <stdarg.h>
22 #include "safeguards.h"
24 extern const SaveLoadVersion SAVEGAME_VERSION; ///< current savegame version
26 extern SavegameType _savegame_type; ///< type of savegame we are loading
28 extern uint32 _ttdp_version; ///< version of TTDP savegame (if applicable)
29 extern SaveLoadVersion _sl_version; ///< the major savegame version identifier
30 extern byte _sl_minor_version; ///< the minor savegame version, DO NOT USE!
33 static GamelogActionType _gamelog_action_type = GLAT_NONE; ///< action to record if anything changes
35 LoggedAction *_gamelog_action = nullptr; ///< first logged action
36 uint _gamelog_actions = 0; ///< number of actions
37 static LoggedAction *_current_action = nullptr; ///< current action we are logging, nullptr when there is no action active
40 /**
41 * Return the revision string for the current client version, for use in gamelog.
42 * The string returned is at most GAMELOG_REVISION_LENGTH bytes long.
44 static const char * GetGamelogRevisionString()
46 /* Allocate a buffer larger than necessary (git revision hash is 40 bytes) to avoid truncation later */
47 static char gamelog_revision[48] = { 0 };
48 static_assert(lengthof(gamelog_revision) > GAMELOG_REVISION_LENGTH);
50 if (IsReleasedVersion()) {
51 return _openttd_revision;
52 } else if (gamelog_revision[0] == 0) {
53 /* Prefix character indication revision status */
54 assert(_openttd_revision_modified < 3);
55 gamelog_revision[0] = "gum"[_openttd_revision_modified]; // g = "git", u = "unknown", m = "modified"
56 /* Append the revision hash */
57 strecat(gamelog_revision, _openttd_revision_hash, lastof(gamelog_revision));
58 /* Truncate string to GAMELOG_REVISION_LENGTH bytes */
59 gamelog_revision[GAMELOG_REVISION_LENGTH - 1] = '\0';
61 return gamelog_revision;
64 /**
65 * Stores information about new action, but doesn't allocate it
66 * Action is allocated only when there is at least one change
67 * @param at type of action
69 void GamelogStartAction(GamelogActionType at)
71 assert(_gamelog_action_type == GLAT_NONE); // do not allow starting new action without stopping the previous first
72 _gamelog_action_type = at;
75 /**
76 * Stops logging of any changes
78 void GamelogStopAction()
80 assert(_gamelog_action_type != GLAT_NONE); // nobody should try to stop if there is no action in progress
82 bool print = _current_action != nullptr;
84 _current_action = nullptr;
85 _gamelog_action_type = GLAT_NONE;
87 if (print) GamelogPrintDebug(5);
90 void GamelogStopAnyAction()
92 if (_gamelog_action_type != GLAT_NONE) GamelogStopAction();
95 /**
96 * Frees the memory allocated by a gamelog
98 void GamelogFree(LoggedAction *gamelog_action, uint gamelog_actions)
100 for (uint i = 0; i < gamelog_actions; i++) {
101 const LoggedAction *la = &gamelog_action[i];
102 for (uint j = 0; j < la->changes; j++) {
103 const LoggedChange *lc = &la->change[j];
104 if (lc->ct == GLCT_SETTING) free(lc->setting.name);
106 free(la->change);
109 free(gamelog_action);
113 * Resets and frees all memory allocated - used before loading or starting a new game
115 void GamelogReset()
117 assert(_gamelog_action_type == GLAT_NONE);
118 GamelogFree(_gamelog_action, _gamelog_actions);
120 _gamelog_action = nullptr;
121 _gamelog_actions = 0;
122 _current_action = nullptr;
126 * Prints GRF ID, checksum and filename if found
127 * @param buf The location in the buffer to draw
128 * @param last The end of the buffer
129 * @param grfid GRF ID
130 * @param md5sum array of md5sum to print, if known
131 * @param gc GrfConfig, if known
132 * @return The buffer location.
134 static char *PrintGrfInfo(char *buf, const char *last, uint grfid, const uint8 *md5sum, const GRFConfig *gc)
136 char txt[40];
138 if (md5sum != nullptr) {
139 md5sumToString(txt, lastof(txt), md5sum);
140 buf += seprintf(buf, last, "GRF ID %08X, checksum %s", BSWAP32(grfid), txt);
141 } else {
142 buf += seprintf(buf, last, "GRF ID %08X", BSWAP32(grfid));
145 if (gc != nullptr) {
146 buf += seprintf(buf, last, ", filename: %s (md5sum matches)", gc->filename);
147 } else {
148 gc = FindGRFConfig(grfid, FGCM_ANY);
149 if (gc != nullptr) {
150 buf += seprintf(buf, last, ", filename: %s (matches GRFID only)", gc->filename);
151 } else {
152 buf += seprintf(buf, last, ", unknown GRF");
155 return buf;
159 /** Text messages for various logged actions */
160 static const char * const la_text[] = {
161 "new game started",
162 "game loaded",
163 "GRF config changed",
164 "cheat was used",
165 "settings changed",
166 "GRF bug triggered",
167 "emergency savegame",
170 static_assert(lengthof(la_text) == GLAT_END);
173 * Information about the presence of a Grf at a certain point during gamelog history
174 * Note about missing Grfs:
175 * Changes to missing Grfs are not logged including manual removal of the Grf.
176 * So if the gamelog tells a Grf is missing we do not know whether it was readded or completely removed
177 * at some later point.
179 struct GRFPresence{
180 const GRFConfig *gc; ///< GRFConfig, if known
181 bool was_missing; ///< Grf was missing during some gameload in the past
183 GRFPresence(const GRFConfig *gc) : gc(gc), was_missing(false) {}
184 GRFPresence() = default;
186 typedef SmallMap<uint32, GRFPresence> GrfIDMapping;
189 * Prints active gamelog
190 * @param proc the procedure to draw with
192 void GamelogPrint(GamelogPrintProc *proc)
194 char buffer[1024];
195 GrfIDMapping grf_names;
197 proc("---- gamelog start ----");
199 const LoggedAction *laend = &_gamelog_action[_gamelog_actions];
201 for (const LoggedAction *la = _gamelog_action; la != laend; la++) {
202 assert((uint)la->at < GLAT_END);
204 seprintf(buffer, lastof(buffer), "Tick %u: %s", (uint)la->tick, la_text[(uint)la->at]);
205 proc(buffer);
207 const LoggedChange *lcend = &la->change[la->changes];
209 for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
210 char *buf = buffer;
212 switch (lc->ct) {
213 default: NOT_REACHED();
214 case GLCT_MODE:
215 buf += seprintf(buf, lastof(buffer), "New game mode: %u landscape: %u",
216 (uint)lc->mode.mode, (uint)lc->mode.landscape);
217 break;
219 case GLCT_REVISION:
220 buf += seprintf(buf, lastof(buffer), "Revision text changed to %s, savegame version %u, ",
221 lc->revision.text, lc->revision.slver);
223 switch (lc->revision.modified) {
224 case 0: buf += seprintf(buf, lastof(buffer), "not "); break;
225 case 1: buf += seprintf(buf, lastof(buffer), "maybe "); break;
226 default: break;
229 buf += seprintf(buf, lastof(buffer), "modified, _openttd_newgrf_version = 0x%08x", lc->revision.newgrf);
230 break;
232 case GLCT_OLDVER:
233 buf += seprintf(buf, lastof(buffer), "Conversion from ");
234 switch (lc->oldver.type) {
235 default: NOT_REACHED();
236 case SGT_OTTD:
237 buf += seprintf(buf, lastof(buffer), "OTTD savegame without gamelog: version %u, %u",
238 GB(lc->oldver.version, 8, 16), GB(lc->oldver.version, 0, 8));
239 break;
241 case SGT_TTO:
242 buf += seprintf(buf, lastof(buffer), "TTO savegame");
243 break;
245 case SGT_TTD:
246 buf += seprintf(buf, lastof(buffer), "TTD savegame");
247 break;
249 case SGT_TTDP1:
250 case SGT_TTDP2:
251 buf += seprintf(buf, lastof(buffer), "TTDP savegame, %s format",
252 lc->oldver.type == SGT_TTDP1 ? "old" : "new");
253 if (lc->oldver.version != 0) {
254 buf += seprintf(buf, lastof(buffer), ", TTDP version %u.%u.%u.%u",
255 GB(lc->oldver.version, 24, 8), GB(lc->oldver.version, 20, 4),
256 GB(lc->oldver.version, 16, 4), GB(lc->oldver.version, 0, 16));
258 break;
260 break;
262 case GLCT_SETTING:
263 buf += seprintf(buf, lastof(buffer), "Setting changed: %s : %d -> %d", lc->setting.name, lc->setting.oldval, lc->setting.newval);
264 break;
266 case GLCT_GRFADD: {
267 const GRFConfig *gc = FindGRFConfig(lc->grfadd.grfid, FGCM_EXACT, lc->grfadd.md5sum);
268 buf += seprintf(buf, lastof(buffer), "Added NewGRF: ");
269 buf = PrintGrfInfo(buf, lastof(buffer), lc->grfadd.grfid, lc->grfadd.md5sum, gc);
270 GrfIDMapping::Pair *gm = grf_names.Find(lc->grfrem.grfid);
271 if (gm != grf_names.End() && !gm->second.was_missing) buf += seprintf(buf, lastof(buffer), ". Gamelog inconsistency: GrfID was already added!");
272 grf_names[lc->grfadd.grfid] = gc;
273 break;
276 case GLCT_GRFREM: {
277 GrfIDMapping::Pair *gm = grf_names.Find(lc->grfrem.grfid);
278 buf += seprintf(buf, lastof(buffer), la->at == GLAT_LOAD ? "Missing NewGRF: " : "Removed NewGRF: ");
279 buf = PrintGrfInfo(buf, lastof(buffer), lc->grfrem.grfid, nullptr, gm != grf_names.End() ? gm->second.gc : nullptr);
280 if (gm == grf_names.End()) {
281 buf += seprintf(buf, lastof(buffer), ". Gamelog inconsistency: GrfID was never added!");
282 } else {
283 if (la->at == GLAT_LOAD) {
284 /* Missing grfs on load are not removed from the configuration */
285 gm->second.was_missing = true;
286 } else {
287 grf_names.Erase(gm);
290 break;
293 case GLCT_GRFCOMPAT: {
294 const GRFConfig *gc = FindGRFConfig(lc->grfadd.grfid, FGCM_EXACT, lc->grfadd.md5sum);
295 buf += seprintf(buf, lastof(buffer), "Compatible NewGRF loaded: ");
296 buf = PrintGrfInfo(buf, lastof(buffer), lc->grfcompat.grfid, lc->grfcompat.md5sum, gc);
297 if (!grf_names.Contains(lc->grfcompat.grfid)) buf += seprintf(buf, lastof(buffer), ". Gamelog inconsistency: GrfID was never added!");
298 grf_names[lc->grfcompat.grfid] = gc;
299 break;
302 case GLCT_GRFPARAM: {
303 GrfIDMapping::Pair *gm = grf_names.Find(lc->grfrem.grfid);
304 buf += seprintf(buf, lastof(buffer), "GRF parameter changed: ");
305 buf = PrintGrfInfo(buf, lastof(buffer), lc->grfparam.grfid, nullptr, gm != grf_names.End() ? gm->second.gc : nullptr);
306 if (gm == grf_names.End()) buf += seprintf(buf, lastof(buffer), ". Gamelog inconsistency: GrfID was never added!");
307 break;
310 case GLCT_GRFMOVE: {
311 GrfIDMapping::Pair *gm = grf_names.Find(lc->grfrem.grfid);
312 buf += seprintf(buf, lastof(buffer), "GRF order changed: %08X moved %d places %s",
313 BSWAP32(lc->grfmove.grfid), abs(lc->grfmove.offset), lc->grfmove.offset >= 0 ? "down" : "up" );
314 buf = PrintGrfInfo(buf, lastof(buffer), lc->grfmove.grfid, nullptr, gm != grf_names.End() ? gm->second.gc : nullptr);
315 if (gm == grf_names.End()) buf += seprintf(buf, lastof(buffer), ". Gamelog inconsistency: GrfID was never added!");
316 break;
319 case GLCT_GRFBUG: {
320 GrfIDMapping::Pair *gm = grf_names.Find(lc->grfrem.grfid);
321 switch (lc->grfbug.bug) {
322 default: NOT_REACHED();
323 case GBUG_VEH_LENGTH:
324 buf += seprintf(buf, lastof(buffer), "Rail vehicle changes length outside a depot: GRF ID %08X, internal ID 0x%X", BSWAP32(lc->grfbug.grfid), (uint)lc->grfbug.data);
325 break;
327 buf = PrintGrfInfo(buf, lastof(buffer), lc->grfbug.grfid, nullptr, gm != grf_names.End() ? gm->second.gc : nullptr);
328 if (gm == grf_names.End()) buf += seprintf(buf, lastof(buffer), ". Gamelog inconsistency: GrfID was never added!");
329 break;
332 case GLCT_EMERGENCY:
333 break;
336 proc(buffer);
340 proc("---- gamelog end ----");
344 static void GamelogPrintConsoleProc(const char *s)
346 IConsolePrint(CC_WARNING, s);
349 /** Print the gamelog data to the console. */
350 void GamelogPrintConsole()
352 GamelogPrint(&GamelogPrintConsoleProc);
355 static int _gamelog_print_level = 0; ///< gamelog debug level we need to print stuff
357 static void GamelogPrintDebugProc(const char *s)
359 Debug(gamelog, _gamelog_print_level, "{}", s);
364 * Prints gamelog to debug output. Code is executed even when
365 * there will be no output. It is called very seldom, so it
366 * doesn't matter that much. At least it gives more uniform code...
367 * @param level debug level we need to print stuff
369 void GamelogPrintDebug(int level)
371 _gamelog_print_level = level;
372 GamelogPrint(&GamelogPrintDebugProc);
377 * Allocates new LoggedChange and new LoggedAction if needed.
378 * If there is no action active, nullptr is returned.
379 * @param ct type of change
380 * @return new LoggedChange, or nullptr if there is no action active
382 static LoggedChange *GamelogChange(GamelogChangeType ct)
384 if (_current_action == nullptr) {
385 if (_gamelog_action_type == GLAT_NONE) return nullptr;
387 _gamelog_action = ReallocT(_gamelog_action, _gamelog_actions + 1);
388 _current_action = &_gamelog_action[_gamelog_actions++];
390 _current_action->at = _gamelog_action_type;
391 _current_action->tick = _tick_counter;
392 _current_action->change = nullptr;
393 _current_action->changes = 0;
396 _current_action->change = ReallocT(_current_action->change, _current_action->changes + 1);
398 LoggedChange *lc = &_current_action->change[_current_action->changes++];
399 lc->ct = ct;
401 return lc;
406 * Logs a emergency savegame
408 void GamelogEmergency()
410 /* Terminate any active action */
411 if (_gamelog_action_type != GLAT_NONE) GamelogStopAction();
412 GamelogStartAction(GLAT_EMERGENCY);
413 GamelogChange(GLCT_EMERGENCY);
414 GamelogStopAction();
418 * Finds out if current game is a loaded emergency savegame.
420 bool GamelogTestEmergency()
422 const LoggedChange *emergency = nullptr;
424 const LoggedAction *laend = &_gamelog_action[_gamelog_actions];
425 for (const LoggedAction *la = _gamelog_action; la != laend; la++) {
426 const LoggedChange *lcend = &la->change[la->changes];
427 for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
428 if (lc->ct == GLCT_EMERGENCY) emergency = lc;
432 return (emergency != nullptr);
436 * Logs a change in game revision
438 void GamelogRevision()
440 assert(_gamelog_action_type == GLAT_START || _gamelog_action_type == GLAT_LOAD);
442 LoggedChange *lc = GamelogChange(GLCT_REVISION);
443 if (lc == nullptr) return;
445 memset(lc->revision.text, 0, sizeof(lc->revision.text));
446 strecpy(lc->revision.text, GetGamelogRevisionString(), lastof(lc->revision.text));
447 lc->revision.slver = SAVEGAME_VERSION;
448 lc->revision.modified = _openttd_revision_modified;
449 lc->revision.newgrf = _openttd_newgrf_version;
453 * Logs a change in game mode (scenario editor or game)
455 void GamelogMode()
457 assert(_gamelog_action_type == GLAT_START || _gamelog_action_type == GLAT_LOAD || _gamelog_action_type == GLAT_CHEAT);
459 LoggedChange *lc = GamelogChange(GLCT_MODE);
460 if (lc == nullptr) return;
462 lc->mode.mode = _game_mode;
463 lc->mode.landscape = _settings_game.game_creation.landscape;
467 * Logs loading from savegame without gamelog
469 void GamelogOldver()
471 assert(_gamelog_action_type == GLAT_LOAD);
473 LoggedChange *lc = GamelogChange(GLCT_OLDVER);
474 if (lc == nullptr) return;
476 lc->oldver.type = _savegame_type;
477 lc->oldver.version = (_savegame_type == SGT_OTTD ? ((uint32)_sl_version << 8 | _sl_minor_version) : _ttdp_version);
481 * Logs change in game settings. Only non-networksafe settings are logged
482 * @param name setting name
483 * @param oldval old setting value
484 * @param newval new setting value
486 void GamelogSetting(const std::string &name, int32 oldval, int32 newval)
488 assert(_gamelog_action_type == GLAT_SETTING);
490 LoggedChange *lc = GamelogChange(GLCT_SETTING);
491 if (lc == nullptr) return;
493 lc->setting.name = stredup(name.c_str());
494 lc->setting.oldval = oldval;
495 lc->setting.newval = newval;
500 * Finds out if current revision is different than last revision stored in the savegame.
501 * Appends GLCT_REVISION when the revision string changed
503 void GamelogTestRevision()
505 const LoggedChange *rev = nullptr;
507 const LoggedAction *laend = &_gamelog_action[_gamelog_actions];
508 for (const LoggedAction *la = _gamelog_action; la != laend; la++) {
509 const LoggedChange *lcend = &la->change[la->changes];
510 for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
511 if (lc->ct == GLCT_REVISION) rev = lc;
515 if (rev == nullptr || strcmp(rev->revision.text, GetGamelogRevisionString()) != 0 ||
516 rev->revision.modified != _openttd_revision_modified ||
517 rev->revision.newgrf != _openttd_newgrf_version) {
518 GamelogRevision();
523 * Finds last stored game mode or landscape.
524 * Any change is logged
526 void GamelogTestMode()
528 const LoggedChange *mode = nullptr;
530 const LoggedAction *laend = &_gamelog_action[_gamelog_actions];
531 for (const LoggedAction *la = _gamelog_action; la != laend; la++) {
532 const LoggedChange *lcend = &la->change[la->changes];
533 for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
534 if (lc->ct == GLCT_MODE) mode = lc;
538 if (mode == nullptr || mode->mode.mode != _game_mode || mode->mode.landscape != _settings_game.game_creation.landscape) GamelogMode();
543 * Logs triggered GRF bug.
544 * @param grfid ID of problematic GRF
545 * @param bug type of bug, @see enum GRFBugs
546 * @param data additional data
548 static void GamelogGRFBug(uint32 grfid, byte bug, uint64 data)
550 assert(_gamelog_action_type == GLAT_GRFBUG);
552 LoggedChange *lc = GamelogChange(GLCT_GRFBUG);
553 if (lc == nullptr) return;
555 lc->grfbug.data = data;
556 lc->grfbug.grfid = grfid;
557 lc->grfbug.bug = bug;
561 * Logs GRF bug - rail vehicle has different length after reversing.
562 * Ensures this is logged only once for each GRF and engine type
563 * This check takes some time, but it is called pretty seldom, so it
564 * doesn't matter that much (ideally it shouldn't be called at all).
565 * @param grfid the broken NewGRF
566 * @param internal_id the internal ID of whatever's broken in the NewGRF
567 * @return true iff a unique record was done
569 bool GamelogGRFBugReverse(uint32 grfid, uint16 internal_id)
571 const LoggedAction *laend = &_gamelog_action[_gamelog_actions];
572 for (const LoggedAction *la = _gamelog_action; la != laend; la++) {
573 const LoggedChange *lcend = &la->change[la->changes];
574 for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
575 if (lc->ct == GLCT_GRFBUG && lc->grfbug.grfid == grfid &&
576 lc->grfbug.bug == GBUG_VEH_LENGTH && lc->grfbug.data == internal_id) {
577 return false;
582 GamelogStartAction(GLAT_GRFBUG);
583 GamelogGRFBug(grfid, GBUG_VEH_LENGTH, internal_id);
584 GamelogStopAction();
586 return true;
591 * Decides if GRF should be logged
592 * @param g grf to determine
593 * @return true iff GRF is not static and is loaded
595 static inline bool IsLoggableGrfConfig(const GRFConfig *g)
597 return !HasBit(g->flags, GCF_STATIC) && g->status != GCS_NOT_FOUND;
601 * Logs removal of a GRF
602 * @param grfid ID of removed GRF
604 void GamelogGRFRemove(uint32 grfid)
606 assert(_gamelog_action_type == GLAT_LOAD || _gamelog_action_type == GLAT_GRF);
608 LoggedChange *lc = GamelogChange(GLCT_GRFREM);
609 if (lc == nullptr) return;
611 lc->grfrem.grfid = grfid;
615 * Logs adding of a GRF
616 * @param newg added GRF
618 void GamelogGRFAdd(const GRFConfig *newg)
620 assert(_gamelog_action_type == GLAT_LOAD || _gamelog_action_type == GLAT_START || _gamelog_action_type == GLAT_GRF);
622 if (!IsLoggableGrfConfig(newg)) return;
624 LoggedChange *lc = GamelogChange(GLCT_GRFADD);
625 if (lc == nullptr) return;
627 lc->grfadd = newg->ident;
631 * Logs loading compatible GRF
632 * (the same ID, but different MD5 hash)
633 * @param newg new (updated) GRF
635 void GamelogGRFCompatible(const GRFIdentifier *newg)
637 assert(_gamelog_action_type == GLAT_LOAD || _gamelog_action_type == GLAT_GRF);
639 LoggedChange *lc = GamelogChange(GLCT_GRFCOMPAT);
640 if (lc == nullptr) return;
642 lc->grfcompat = *newg;
646 * Logs changing GRF order
647 * @param grfid GRF that is moved
648 * @param offset how far it is moved, positive = moved down
650 static void GamelogGRFMove(uint32 grfid, int32 offset)
652 assert(_gamelog_action_type == GLAT_GRF);
654 LoggedChange *lc = GamelogChange(GLCT_GRFMOVE);
655 if (lc == nullptr) return;
657 lc->grfmove.grfid = grfid;
658 lc->grfmove.offset = offset;
662 * Logs change in GRF parameters.
663 * Details about parameters changed are not stored
664 * @param grfid ID of GRF to store
666 static void GamelogGRFParameters(uint32 grfid)
668 assert(_gamelog_action_type == GLAT_GRF);
670 LoggedChange *lc = GamelogChange(GLCT_GRFPARAM);
671 if (lc == nullptr) return;
673 lc->grfparam.grfid = grfid;
677 * Logs adding of list of GRFs.
678 * Useful when old savegame is loaded or when new game is started
679 * @param newg head of GRF linked list
681 void GamelogGRFAddList(const GRFConfig *newg)
683 assert(_gamelog_action_type == GLAT_START || _gamelog_action_type == GLAT_LOAD);
685 for (; newg != nullptr; newg = newg->next) {
686 GamelogGRFAdd(newg);
690 /** List of GRFs using array of pointers instead of linked list */
691 struct GRFList {
692 uint n;
693 const GRFConfig *grf[];
697 * Generates GRFList
698 * @param grfc head of GRF linked list
700 static GRFList *GenerateGRFList(const GRFConfig *grfc)
702 uint n = 0;
703 for (const GRFConfig *g = grfc; g != nullptr; g = g->next) {
704 if (IsLoggableGrfConfig(g)) n++;
707 GRFList *list = (GRFList*)MallocT<byte>(sizeof(GRFList) + n * sizeof(GRFConfig*));
709 list->n = 0;
710 for (const GRFConfig *g = grfc; g != nullptr; g = g->next) {
711 if (IsLoggableGrfConfig(g)) list->grf[list->n++] = g;
714 return list;
718 * Compares two NewGRF lists and logs any change
719 * @param oldc original GRF list
720 * @param newc new GRF list
722 void GamelogGRFUpdate(const GRFConfig *oldc, const GRFConfig *newc)
724 GRFList *ol = GenerateGRFList(oldc);
725 GRFList *nl = GenerateGRFList(newc);
727 uint o = 0, n = 0;
729 while (o < ol->n && n < nl->n) {
730 const GRFConfig *og = ol->grf[o];
731 const GRFConfig *ng = nl->grf[n];
733 if (og->ident.grfid != ng->ident.grfid) {
734 uint oi, ni;
735 for (oi = 0; oi < ol->n; oi++) {
736 if (ol->grf[oi]->ident.grfid == nl->grf[n]->ident.grfid) break;
738 if (oi < o) {
739 /* GRF was moved, this change has been logged already */
740 n++;
741 continue;
743 if (oi == ol->n) {
744 /* GRF couldn't be found in the OLD list, GRF was ADDED */
745 GamelogGRFAdd(nl->grf[n++]);
746 continue;
748 for (ni = 0; ni < nl->n; ni++) {
749 if (nl->grf[ni]->ident.grfid == ol->grf[o]->ident.grfid) break;
751 if (ni < n) {
752 /* GRF was moved, this change has been logged already */
753 o++;
754 continue;
756 if (ni == nl->n) {
757 /* GRF couldn't be found in the NEW list, GRF was REMOVED */
758 GamelogGRFRemove(ol->grf[o++]->ident.grfid);
759 continue;
762 /* o < oi < ol->n
763 * n < ni < nl->n */
764 assert(ni > n && ni < nl->n);
765 assert(oi > o && oi < ol->n);
767 ni -= n; // number of GRFs it was moved downwards
768 oi -= o; // number of GRFs it was moved upwards
770 if (ni >= oi) { // prefer the one that is moved further
771 /* GRF was moved down */
772 GamelogGRFMove(ol->grf[o++]->ident.grfid, ni);
773 } else {
774 GamelogGRFMove(nl->grf[n++]->ident.grfid, -(int)oi);
776 } else {
777 if (memcmp(og->ident.md5sum, ng->ident.md5sum, sizeof(og->ident.md5sum)) != 0) {
778 /* md5sum changed, probably loading 'compatible' GRF */
779 GamelogGRFCompatible(&nl->grf[n]->ident);
782 if (og->num_params != ng->num_params || memcmp(og->param, ng->param, og->num_params * sizeof(og->param[0])) != 0) {
783 GamelogGRFParameters(ol->grf[o]->ident.grfid);
786 o++;
787 n++;
791 while (o < ol->n) GamelogGRFRemove(ol->grf[o++]->ident.grfid); // remaining GRFs were removed ...
792 while (n < nl->n) GamelogGRFAdd (nl->grf[n++]); // ... or added
794 free(ol);
795 free(nl);
799 * Get some basic information from the given gamelog.
800 * @param gamelog_action Pointer to the gamelog to extract information from.
801 * @param gamelog_actions Number of actions in the given gamelog.
802 * @param[out] last_ottd_rev OpenTTD NewGRF version from the binary that saved the savegame last.
803 * @param[out] ever_modified Max value of 'modified' from all binaries that ever saved this savegame.
804 * @param[out] removed_newgrfs Set to true if any NewGRFs have been removed.
806 void GamelogInfo(LoggedAction *gamelog_action, uint gamelog_actions, uint32 *last_ottd_rev, byte *ever_modified, bool *removed_newgrfs)
808 const LoggedAction *laend = &gamelog_action[gamelog_actions];
809 for (const LoggedAction *la = gamelog_action; la != laend; la++) {
810 const LoggedChange *lcend = &la->change[la->changes];
811 for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
812 switch (lc->ct) {
813 default: break;
815 case GLCT_REVISION:
816 *last_ottd_rev = lc->revision.newgrf;
817 *ever_modified = std::max(*ever_modified, lc->revision.modified);
818 break;
820 case GLCT_GRFREM:
821 *removed_newgrfs = true;
822 break;