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/>.
8 /** @file gamelog.cpp Definition of functions used for logging of important changes in the game */
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"
17 #include "date_func.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
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
;
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
;
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();
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
);
109 free(gamelog_action
);
113 * Resets and frees all memory allocated - used before loading or starting a new game
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
)
138 if (md5sum
!= nullptr) {
139 md5sumToString(txt
, lastof(txt
), md5sum
);
140 buf
+= seprintf(buf
, last
, "GRF ID %08X, checksum %s", BSWAP32(grfid
), txt
);
142 buf
+= seprintf(buf
, last
, "GRF ID %08X", BSWAP32(grfid
));
146 buf
+= seprintf(buf
, last
, ", filename: %s (md5sum matches)", gc
->filename
);
148 gc
= FindGRFConfig(grfid
, FGCM_ANY
);
150 buf
+= seprintf(buf
, last
, ", filename: %s (matches GRFID only)", gc
->filename
);
152 buf
+= seprintf(buf
, last
, ", unknown GRF");
159 /** Text messages for various logged actions */
160 static const char * const la_text
[] = {
163 "GRF config changed",
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.
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
)
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
]);
207 const LoggedChange
*lcend
= &la
->change
[la
->changes
];
209 for (const LoggedChange
*lc
= la
->change
; lc
!= lcend
; lc
++) {
213 default: NOT_REACHED();
215 buf
+= seprintf(buf
, lastof(buffer
), "New game mode: %u landscape: %u",
216 (uint
)lc
->mode
.mode
, (uint
)lc
->mode
.landscape
);
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;
229 buf
+= seprintf(buf
, lastof(buffer
), "modified, _openttd_newgrf_version = 0x%08x", lc
->revision
.newgrf
);
233 buf
+= seprintf(buf
, lastof(buffer
), "Conversion from ");
234 switch (lc
->oldver
.type
) {
235 default: NOT_REACHED();
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));
242 buf
+= seprintf(buf
, lastof(buffer
), "TTO savegame");
246 buf
+= seprintf(buf
, lastof(buffer
), "TTD savegame");
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));
263 buf
+= seprintf(buf
, lastof(buffer
), "Setting changed: %s : %d -> %d", lc
->setting
.name
, lc
->setting
.oldval
, lc
->setting
.newval
);
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
;
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!");
283 if (la
->at
== GLAT_LOAD
) {
284 /* Missing grfs on load are not removed from the configuration */
285 gm
->second
.was_missing
= true;
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
;
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!");
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!");
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
);
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!");
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", 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
++];
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
);
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)
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
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 char *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
);
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
) {
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
) {
582 GamelogStartAction(GLAT_GRFBUG
);
583 GamelogGRFBug(grfid
, GBUG_VEH_LENGTH
, internal_id
);
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
) {
690 /** List of GRFs using array of pointers instead of linked list */
693 const GRFConfig
*grf
[];
698 * @param grfc head of GRF linked list
700 static GRFList
*GenerateGRFList(const GRFConfig
*grfc
)
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
*));
710 for (const GRFConfig
*g
= grfc
; g
!= nullptr; g
= g
->next
) {
711 if (IsLoggableGrfConfig(g
)) list
->grf
[list
->n
++] = g
;
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
);
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
) {
735 for (oi
= 0; oi
< ol
->n
; oi
++) {
736 if (ol
->grf
[oi
]->ident
.grfid
== nl
->grf
[n
]->ident
.grfid
) break;
739 /* GRF was moved, this change has been logged already */
744 /* GRF couldn't be found in the OLD list, GRF was ADDED */
745 GamelogGRFAdd(nl
->grf
[n
++]);
748 for (ni
= 0; ni
< nl
->n
; ni
++) {
749 if (nl
->grf
[ni
]->ident
.grfid
== ol
->grf
[o
]->ident
.grfid
) break;
752 /* GRF was moved, this change has been logged already */
757 /* GRF couldn't be found in the NEW list, GRF was REMOVED */
758 GamelogGRFRemove(ol
->grf
[o
++]->ident
.grfid
);
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
);
774 GamelogGRFMove(nl
->grf
[n
++]->ident
.grfid
, -(int)oi
);
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
);
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
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
++) {
816 *last_ottd_rev
= lc
->revision
.newgrf
;
817 *ever_modified
= std::max(*ever_modified
, lc
->revision
.modified
);
821 *removed_newgrfs
= true;