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 assert_compile(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);
91 * Frees the memory allocated by a gamelog
93 void GamelogFree(LoggedAction
*gamelog_action
, uint gamelog_actions
)
95 for (uint i
= 0; i
< gamelog_actions
; i
++) {
96 const LoggedAction
*la
= &gamelog_action
[i
];
97 for (uint j
= 0; j
< la
->changes
; j
++) {
98 const LoggedChange
*lc
= &la
->change
[j
];
99 if (lc
->ct
== GLCT_SETTING
) free(lc
->setting
.name
);
104 free(gamelog_action
);
108 * Resets and frees all memory allocated - used before loading or starting a new game
112 assert(_gamelog_action_type
== GLAT_NONE
);
113 GamelogFree(_gamelog_action
, _gamelog_actions
);
115 _gamelog_action
= nullptr;
116 _gamelog_actions
= 0;
117 _current_action
= nullptr;
121 * Prints GRF ID, checksum and filename if found
122 * @param buf The location in the buffer to draw
123 * @param last The end of the buffer
124 * @param grfid GRF ID
125 * @param md5sum array of md5sum to print, if known
126 * @param gc GrfConfig, if known
127 * @return The buffer location.
129 static char *PrintGrfInfo(char *buf
, const char *last
, uint grfid
, const uint8
*md5sum
, const GRFConfig
*gc
)
133 if (md5sum
!= nullptr) {
134 md5sumToString(txt
, lastof(txt
), md5sum
);
135 buf
+= seprintf(buf
, last
, "GRF ID %08X, checksum %s", BSWAP32(grfid
), txt
);
137 buf
+= seprintf(buf
, last
, "GRF ID %08X", BSWAP32(grfid
));
141 buf
+= seprintf(buf
, last
, ", filename: %s (md5sum matches)", gc
->filename
);
143 gc
= FindGRFConfig(grfid
, FGCM_ANY
);
145 buf
+= seprintf(buf
, last
, ", filename: %s (matches GRFID only)", gc
->filename
);
147 buf
+= seprintf(buf
, last
, ", unknown GRF");
154 /** Text messages for various logged actions */
155 static const char * const la_text
[] = {
158 "GRF config changed",
162 "emergency savegame",
165 assert_compile(lengthof(la_text
) == GLAT_END
);
168 * Information about the presence of a Grf at a certain point during gamelog history
169 * Note about missing Grfs:
170 * Changes to missing Grfs are not logged including manual removal of the Grf.
171 * So if the gamelog tells a Grf is missing we do not know whether it was readded or completely removed
172 * at some later point.
175 const GRFConfig
*gc
; ///< GRFConfig, if known
176 bool was_missing
; ///< Grf was missing during some gameload in the past
178 GRFPresence(const GRFConfig
*gc
) : gc(gc
), was_missing(false) {}
179 GRFPresence() = default;
181 typedef SmallMap
<uint32
, GRFPresence
> GrfIDMapping
;
184 * Prints active gamelog
185 * @param proc the procedure to draw with
187 void GamelogPrint(GamelogPrintProc
*proc
)
190 GrfIDMapping grf_names
;
192 proc("---- gamelog start ----");
194 const LoggedAction
*laend
= &_gamelog_action
[_gamelog_actions
];
196 for (const LoggedAction
*la
= _gamelog_action
; la
!= laend
; la
++) {
197 assert((uint
)la
->at
< GLAT_END
);
199 seprintf(buffer
, lastof(buffer
), "Tick %u: %s", (uint
)la
->tick
, la_text
[(uint
)la
->at
]);
202 const LoggedChange
*lcend
= &la
->change
[la
->changes
];
204 for (const LoggedChange
*lc
= la
->change
; lc
!= lcend
; lc
++) {
208 default: NOT_REACHED();
210 buf
+= seprintf(buf
, lastof(buffer
), "New game mode: %u landscape: %u",
211 (uint
)lc
->mode
.mode
, (uint
)lc
->mode
.landscape
);
215 buf
+= seprintf(buf
, lastof(buffer
), "Revision text changed to %s, savegame version %u, ",
216 lc
->revision
.text
, lc
->revision
.slver
);
218 switch (lc
->revision
.modified
) {
219 case 0: buf
+= seprintf(buf
, lastof(buffer
), "not "); break;
220 case 1: buf
+= seprintf(buf
, lastof(buffer
), "maybe "); break;
224 buf
+= seprintf(buf
, lastof(buffer
), "modified, _openttd_newgrf_version = 0x%08x", lc
->revision
.newgrf
);
228 buf
+= seprintf(buf
, lastof(buffer
), "Conversion from ");
229 switch (lc
->oldver
.type
) {
230 default: NOT_REACHED();
232 buf
+= seprintf(buf
, lastof(buffer
), "OTTD savegame without gamelog: version %u, %u",
233 GB(lc
->oldver
.version
, 8, 16), GB(lc
->oldver
.version
, 0, 8));
237 buf
+= seprintf(buf
, lastof(buffer
), "TTO savegame");
241 buf
+= seprintf(buf
, lastof(buffer
), "TTD savegame");
246 buf
+= seprintf(buf
, lastof(buffer
), "TTDP savegame, %s format",
247 lc
->oldver
.type
== SGT_TTDP1
? "old" : "new");
248 if (lc
->oldver
.version
!= 0) {
249 buf
+= seprintf(buf
, lastof(buffer
), ", TTDP version %u.%u.%u.%u",
250 GB(lc
->oldver
.version
, 24, 8), GB(lc
->oldver
.version
, 20, 4),
251 GB(lc
->oldver
.version
, 16, 4), GB(lc
->oldver
.version
, 0, 16));
258 buf
+= seprintf(buf
, lastof(buffer
), "Setting changed: %s : %d -> %d", lc
->setting
.name
, lc
->setting
.oldval
, lc
->setting
.newval
);
262 const GRFConfig
*gc
= FindGRFConfig(lc
->grfadd
.grfid
, FGCM_EXACT
, lc
->grfadd
.md5sum
);
263 buf
+= seprintf(buf
, lastof(buffer
), "Added NewGRF: ");
264 buf
= PrintGrfInfo(buf
, lastof(buffer
), lc
->grfadd
.grfid
, lc
->grfadd
.md5sum
, gc
);
265 GrfIDMapping::Pair
*gm
= grf_names
.Find(lc
->grfrem
.grfid
);
266 if (gm
!= grf_names
.End() && !gm
->second
.was_missing
) buf
+= seprintf(buf
, lastof(buffer
), ". Gamelog inconsistency: GrfID was already added!");
267 grf_names
[lc
->grfadd
.grfid
] = gc
;
272 GrfIDMapping::Pair
*gm
= grf_names
.Find(lc
->grfrem
.grfid
);
273 buf
+= seprintf(buf
, lastof(buffer
), la
->at
== GLAT_LOAD
? "Missing NewGRF: " : "Removed NewGRF: ");
274 buf
= PrintGrfInfo(buf
, lastof(buffer
), lc
->grfrem
.grfid
, nullptr, gm
!= grf_names
.End() ? gm
->second
.gc
: nullptr);
275 if (gm
== grf_names
.End()) {
276 buf
+= seprintf(buf
, lastof(buffer
), ". Gamelog inconsistency: GrfID was never added!");
278 if (la
->at
== GLAT_LOAD
) {
279 /* Missing grfs on load are not removed from the configuration */
280 gm
->second
.was_missing
= true;
288 case GLCT_GRFCOMPAT
: {
289 const GRFConfig
*gc
= FindGRFConfig(lc
->grfadd
.grfid
, FGCM_EXACT
, lc
->grfadd
.md5sum
);
290 buf
+= seprintf(buf
, lastof(buffer
), "Compatible NewGRF loaded: ");
291 buf
= PrintGrfInfo(buf
, lastof(buffer
), lc
->grfcompat
.grfid
, lc
->grfcompat
.md5sum
, gc
);
292 if (!grf_names
.Contains(lc
->grfcompat
.grfid
)) buf
+= seprintf(buf
, lastof(buffer
), ". Gamelog inconsistency: GrfID was never added!");
293 grf_names
[lc
->grfcompat
.grfid
] = gc
;
297 case GLCT_GRFPARAM
: {
298 GrfIDMapping::Pair
*gm
= grf_names
.Find(lc
->grfrem
.grfid
);
299 buf
+= seprintf(buf
, lastof(buffer
), "GRF parameter changed: ");
300 buf
= PrintGrfInfo(buf
, lastof(buffer
), lc
->grfparam
.grfid
, nullptr, gm
!= grf_names
.End() ? gm
->second
.gc
: nullptr);
301 if (gm
== grf_names
.End()) buf
+= seprintf(buf
, lastof(buffer
), ". Gamelog inconsistency: GrfID was never added!");
306 GrfIDMapping::Pair
*gm
= grf_names
.Find(lc
->grfrem
.grfid
);
307 buf
+= seprintf(buf
, lastof(buffer
), "GRF order changed: %08X moved %d places %s",
308 BSWAP32(lc
->grfmove
.grfid
), abs(lc
->grfmove
.offset
), lc
->grfmove
.offset
>= 0 ? "down" : "up" );
309 buf
= PrintGrfInfo(buf
, lastof(buffer
), lc
->grfmove
.grfid
, nullptr, gm
!= grf_names
.End() ? gm
->second
.gc
: nullptr);
310 if (gm
== grf_names
.End()) buf
+= seprintf(buf
, lastof(buffer
), ". Gamelog inconsistency: GrfID was never added!");
315 GrfIDMapping::Pair
*gm
= grf_names
.Find(lc
->grfrem
.grfid
);
316 switch (lc
->grfbug
.bug
) {
317 default: NOT_REACHED();
318 case GBUG_VEH_LENGTH
:
319 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
);
322 buf
= PrintGrfInfo(buf
, lastof(buffer
), lc
->grfbug
.grfid
, nullptr, gm
!= grf_names
.End() ? gm
->second
.gc
: nullptr);
323 if (gm
== grf_names
.End()) buf
+= seprintf(buf
, lastof(buffer
), ". Gamelog inconsistency: GrfID was never added!");
335 proc("---- gamelog end ----");
339 static void GamelogPrintConsoleProc(const char *s
)
341 IConsolePrint(CC_WARNING
, s
);
344 /** Print the gamelog data to the console. */
345 void GamelogPrintConsole()
347 GamelogPrint(&GamelogPrintConsoleProc
);
350 static int _gamelog_print_level
= 0; ///< gamelog debug level we need to print stuff
352 static void GamelogPrintDebugProc(const char *s
)
354 DEBUG(gamelog
, _gamelog_print_level
, "%s", s
);
359 * Prints gamelog to debug output. Code is executed even when
360 * there will be no output. It is called very seldom, so it
361 * doesn't matter that much. At least it gives more uniform code...
362 * @param level debug level we need to print stuff
364 void GamelogPrintDebug(int level
)
366 _gamelog_print_level
= level
;
367 GamelogPrint(&GamelogPrintDebugProc
);
372 * Allocates new LoggedChange and new LoggedAction if needed.
373 * If there is no action active, nullptr is returned.
374 * @param ct type of change
375 * @return new LoggedChange, or nullptr if there is no action active
377 static LoggedChange
*GamelogChange(GamelogChangeType ct
)
379 if (_current_action
== nullptr) {
380 if (_gamelog_action_type
== GLAT_NONE
) return nullptr;
382 _gamelog_action
= ReallocT(_gamelog_action
, _gamelog_actions
+ 1);
383 _current_action
= &_gamelog_action
[_gamelog_actions
++];
385 _current_action
->at
= _gamelog_action_type
;
386 _current_action
->tick
= _tick_counter
;
387 _current_action
->change
= nullptr;
388 _current_action
->changes
= 0;
391 _current_action
->change
= ReallocT(_current_action
->change
, _current_action
->changes
+ 1);
393 LoggedChange
*lc
= &_current_action
->change
[_current_action
->changes
++];
401 * Logs a emergency savegame
403 void GamelogEmergency()
405 /* Terminate any active action */
406 if (_gamelog_action_type
!= GLAT_NONE
) GamelogStopAction();
407 GamelogStartAction(GLAT_EMERGENCY
);
408 GamelogChange(GLCT_EMERGENCY
);
413 * Finds out if current game is a loaded emergency savegame.
415 bool GamelogTestEmergency()
417 const LoggedChange
*emergency
= nullptr;
419 const LoggedAction
*laend
= &_gamelog_action
[_gamelog_actions
];
420 for (const LoggedAction
*la
= _gamelog_action
; la
!= laend
; la
++) {
421 const LoggedChange
*lcend
= &la
->change
[la
->changes
];
422 for (const LoggedChange
*lc
= la
->change
; lc
!= lcend
; lc
++) {
423 if (lc
->ct
== GLCT_EMERGENCY
) emergency
= lc
;
427 return (emergency
!= nullptr);
431 * Logs a change in game revision
433 void GamelogRevision()
435 assert(_gamelog_action_type
== GLAT_START
|| _gamelog_action_type
== GLAT_LOAD
);
437 LoggedChange
*lc
= GamelogChange(GLCT_REVISION
);
438 if (lc
== nullptr) return;
440 memset(lc
->revision
.text
, 0, sizeof(lc
->revision
.text
));
441 strecpy(lc
->revision
.text
, GetGamelogRevisionString(), lastof(lc
->revision
.text
));
442 lc
->revision
.slver
= SAVEGAME_VERSION
;
443 lc
->revision
.modified
= _openttd_revision_modified
;
444 lc
->revision
.newgrf
= _openttd_newgrf_version
;
448 * Logs a change in game mode (scenario editor or game)
452 assert(_gamelog_action_type
== GLAT_START
|| _gamelog_action_type
== GLAT_LOAD
|| _gamelog_action_type
== GLAT_CHEAT
);
454 LoggedChange
*lc
= GamelogChange(GLCT_MODE
);
455 if (lc
== nullptr) return;
457 lc
->mode
.mode
= _game_mode
;
458 lc
->mode
.landscape
= _settings_game
.game_creation
.landscape
;
462 * Logs loading from savegame without gamelog
466 assert(_gamelog_action_type
== GLAT_LOAD
);
468 LoggedChange
*lc
= GamelogChange(GLCT_OLDVER
);
469 if (lc
== nullptr) return;
471 lc
->oldver
.type
= _savegame_type
;
472 lc
->oldver
.version
= (_savegame_type
== SGT_OTTD
? ((uint32
)_sl_version
<< 8 | _sl_minor_version
) : _ttdp_version
);
476 * Logs change in game settings. Only non-networksafe settings are logged
477 * @param name setting name
478 * @param oldval old setting value
479 * @param newval new setting value
481 void GamelogSetting(const char *name
, int32 oldval
, int32 newval
)
483 assert(_gamelog_action_type
== GLAT_SETTING
);
485 LoggedChange
*lc
= GamelogChange(GLCT_SETTING
);
486 if (lc
== nullptr) return;
488 lc
->setting
.name
= stredup(name
);
489 lc
->setting
.oldval
= oldval
;
490 lc
->setting
.newval
= newval
;
495 * Finds out if current revision is different than last revision stored in the savegame.
496 * Appends GLCT_REVISION when the revision string changed
498 void GamelogTestRevision()
500 const LoggedChange
*rev
= nullptr;
502 const LoggedAction
*laend
= &_gamelog_action
[_gamelog_actions
];
503 for (const LoggedAction
*la
= _gamelog_action
; la
!= laend
; la
++) {
504 const LoggedChange
*lcend
= &la
->change
[la
->changes
];
505 for (const LoggedChange
*lc
= la
->change
; lc
!= lcend
; lc
++) {
506 if (lc
->ct
== GLCT_REVISION
) rev
= lc
;
510 if (rev
== nullptr || strcmp(rev
->revision
.text
, GetGamelogRevisionString()) != 0 ||
511 rev
->revision
.modified
!= _openttd_revision_modified
||
512 rev
->revision
.newgrf
!= _openttd_newgrf_version
) {
518 * Finds last stored game mode or landscape.
519 * Any change is logged
521 void GamelogTestMode()
523 const LoggedChange
*mode
= nullptr;
525 const LoggedAction
*laend
= &_gamelog_action
[_gamelog_actions
];
526 for (const LoggedAction
*la
= _gamelog_action
; la
!= laend
; la
++) {
527 const LoggedChange
*lcend
= &la
->change
[la
->changes
];
528 for (const LoggedChange
*lc
= la
->change
; lc
!= lcend
; lc
++) {
529 if (lc
->ct
== GLCT_MODE
) mode
= lc
;
533 if (mode
== nullptr || mode
->mode
.mode
!= _game_mode
|| mode
->mode
.landscape
!= _settings_game
.game_creation
.landscape
) GamelogMode();
538 * Logs triggered GRF bug.
539 * @param grfid ID of problematic GRF
540 * @param bug type of bug, @see enum GRFBugs
541 * @param data additional data
543 static void GamelogGRFBug(uint32 grfid
, byte bug
, uint64 data
)
545 assert(_gamelog_action_type
== GLAT_GRFBUG
);
547 LoggedChange
*lc
= GamelogChange(GLCT_GRFBUG
);
548 if (lc
== nullptr) return;
550 lc
->grfbug
.data
= data
;
551 lc
->grfbug
.grfid
= grfid
;
552 lc
->grfbug
.bug
= bug
;
556 * Logs GRF bug - rail vehicle has different length after reversing.
557 * Ensures this is logged only once for each GRF and engine type
558 * This check takes some time, but it is called pretty seldom, so it
559 * doesn't matter that much (ideally it shouldn't be called at all).
560 * @param grfid the broken NewGRF
561 * @param internal_id the internal ID of whatever's broken in the NewGRF
562 * @return true iff a unique record was done
564 bool GamelogGRFBugReverse(uint32 grfid
, uint16 internal_id
)
566 const LoggedAction
*laend
= &_gamelog_action
[_gamelog_actions
];
567 for (const LoggedAction
*la
= _gamelog_action
; la
!= laend
; la
++) {
568 const LoggedChange
*lcend
= &la
->change
[la
->changes
];
569 for (const LoggedChange
*lc
= la
->change
; lc
!= lcend
; lc
++) {
570 if (lc
->ct
== GLCT_GRFBUG
&& lc
->grfbug
.grfid
== grfid
&&
571 lc
->grfbug
.bug
== GBUG_VEH_LENGTH
&& lc
->grfbug
.data
== internal_id
) {
577 GamelogStartAction(GLAT_GRFBUG
);
578 GamelogGRFBug(grfid
, GBUG_VEH_LENGTH
, internal_id
);
586 * Decides if GRF should be logged
587 * @param g grf to determine
588 * @return true iff GRF is not static and is loaded
590 static inline bool IsLoggableGrfConfig(const GRFConfig
*g
)
592 return !HasBit(g
->flags
, GCF_STATIC
) && g
->status
!= GCS_NOT_FOUND
;
596 * Logs removal of a GRF
597 * @param grfid ID of removed GRF
599 void GamelogGRFRemove(uint32 grfid
)
601 assert(_gamelog_action_type
== GLAT_LOAD
|| _gamelog_action_type
== GLAT_GRF
);
603 LoggedChange
*lc
= GamelogChange(GLCT_GRFREM
);
604 if (lc
== nullptr) return;
606 lc
->grfrem
.grfid
= grfid
;
610 * Logs adding of a GRF
611 * @param newg added GRF
613 void GamelogGRFAdd(const GRFConfig
*newg
)
615 assert(_gamelog_action_type
== GLAT_LOAD
|| _gamelog_action_type
== GLAT_START
|| _gamelog_action_type
== GLAT_GRF
);
617 if (!IsLoggableGrfConfig(newg
)) return;
619 LoggedChange
*lc
= GamelogChange(GLCT_GRFADD
);
620 if (lc
== nullptr) return;
622 lc
->grfadd
= newg
->ident
;
626 * Logs loading compatible GRF
627 * (the same ID, but different MD5 hash)
628 * @param newg new (updated) GRF
630 void GamelogGRFCompatible(const GRFIdentifier
*newg
)
632 assert(_gamelog_action_type
== GLAT_LOAD
|| _gamelog_action_type
== GLAT_GRF
);
634 LoggedChange
*lc
= GamelogChange(GLCT_GRFCOMPAT
);
635 if (lc
== nullptr) return;
637 lc
->grfcompat
= *newg
;
641 * Logs changing GRF order
642 * @param grfid GRF that is moved
643 * @param offset how far it is moved, positive = moved down
645 static void GamelogGRFMove(uint32 grfid
, int32 offset
)
647 assert(_gamelog_action_type
== GLAT_GRF
);
649 LoggedChange
*lc
= GamelogChange(GLCT_GRFMOVE
);
650 if (lc
== nullptr) return;
652 lc
->grfmove
.grfid
= grfid
;
653 lc
->grfmove
.offset
= offset
;
657 * Logs change in GRF parameters.
658 * Details about parameters changed are not stored
659 * @param grfid ID of GRF to store
661 static void GamelogGRFParameters(uint32 grfid
)
663 assert(_gamelog_action_type
== GLAT_GRF
);
665 LoggedChange
*lc
= GamelogChange(GLCT_GRFPARAM
);
666 if (lc
== nullptr) return;
668 lc
->grfparam
.grfid
= grfid
;
672 * Logs adding of list of GRFs.
673 * Useful when old savegame is loaded or when new game is started
674 * @param newg head of GRF linked list
676 void GamelogGRFAddList(const GRFConfig
*newg
)
678 assert(_gamelog_action_type
== GLAT_START
|| _gamelog_action_type
== GLAT_LOAD
);
680 for (; newg
!= nullptr; newg
= newg
->next
) {
685 /** List of GRFs using array of pointers instead of linked list */
688 const GRFConfig
*grf
[];
693 * @param grfc head of GRF linked list
695 static GRFList
*GenerateGRFList(const GRFConfig
*grfc
)
698 for (const GRFConfig
*g
= grfc
; g
!= nullptr; g
= g
->next
) {
699 if (IsLoggableGrfConfig(g
)) n
++;
702 GRFList
*list
= (GRFList
*)MallocT
<byte
>(sizeof(GRFList
) + n
* sizeof(GRFConfig
*));
705 for (const GRFConfig
*g
= grfc
; g
!= nullptr; g
= g
->next
) {
706 if (IsLoggableGrfConfig(g
)) list
->grf
[list
->n
++] = g
;
713 * Compares two NewGRF lists and logs any change
714 * @param oldc original GRF list
715 * @param newc new GRF list
717 void GamelogGRFUpdate(const GRFConfig
*oldc
, const GRFConfig
*newc
)
719 GRFList
*ol
= GenerateGRFList(oldc
);
720 GRFList
*nl
= GenerateGRFList(newc
);
724 while (o
< ol
->n
&& n
< nl
->n
) {
725 const GRFConfig
*og
= ol
->grf
[o
];
726 const GRFConfig
*ng
= nl
->grf
[n
];
728 if (og
->ident
.grfid
!= ng
->ident
.grfid
) {
730 for (oi
= 0; oi
< ol
->n
; oi
++) {
731 if (ol
->grf
[oi
]->ident
.grfid
== nl
->grf
[n
]->ident
.grfid
) break;
734 /* GRF was moved, this change has been logged already */
739 /* GRF couldn't be found in the OLD list, GRF was ADDED */
740 GamelogGRFAdd(nl
->grf
[n
++]);
743 for (ni
= 0; ni
< nl
->n
; ni
++) {
744 if (nl
->grf
[ni
]->ident
.grfid
== ol
->grf
[o
]->ident
.grfid
) break;
747 /* GRF was moved, this change has been logged already */
752 /* GRF couldn't be found in the NEW list, GRF was REMOVED */
753 GamelogGRFRemove(ol
->grf
[o
++]->ident
.grfid
);
759 assert(ni
> n
&& ni
< nl
->n
);
760 assert(oi
> o
&& oi
< ol
->n
);
762 ni
-= n
; // number of GRFs it was moved downwards
763 oi
-= o
; // number of GRFs it was moved upwards
765 if (ni
>= oi
) { // prefer the one that is moved further
766 /* GRF was moved down */
767 GamelogGRFMove(ol
->grf
[o
++]->ident
.grfid
, ni
);
769 GamelogGRFMove(nl
->grf
[n
++]->ident
.grfid
, -(int)oi
);
772 if (memcmp(og
->ident
.md5sum
, ng
->ident
.md5sum
, sizeof(og
->ident
.md5sum
)) != 0) {
773 /* md5sum changed, probably loading 'compatible' GRF */
774 GamelogGRFCompatible(&nl
->grf
[n
]->ident
);
777 if (og
->num_params
!= ng
->num_params
|| memcmp(og
->param
, ng
->param
, og
->num_params
* sizeof(og
->param
[0])) != 0) {
778 GamelogGRFParameters(ol
->grf
[o
]->ident
.grfid
);
786 while (o
< ol
->n
) GamelogGRFRemove(ol
->grf
[o
++]->ident
.grfid
); // remaining GRFs were removed ...
787 while (n
< nl
->n
) GamelogGRFAdd (nl
->grf
[n
++]); // ... or added
794 * Get some basic information from the given gamelog.
795 * @param gamelog_action Pointer to the gamelog to extract information from.
796 * @param gamelog_actions Number of actions in the given gamelog.
797 * @param[out] last_ottd_rev OpenTTD NewGRF version from the binary that saved the savegame last.
798 * @param[out] ever_modified Max value of 'modified' from all binaries that ever saved this savegame.
799 * @param[out] removed_newgrfs Set to true if any NewGRFs have been removed.
801 void GamelogInfo(LoggedAction
*gamelog_action
, uint gamelog_actions
, uint32
*last_ottd_rev
, byte
*ever_modified
, bool *removed_newgrfs
)
803 const LoggedAction
*laend
= &gamelog_action
[gamelog_actions
];
804 for (const LoggedAction
*la
= gamelog_action
; la
!= laend
; la
++) {
805 const LoggedChange
*lcend
= &la
->change
[la
->changes
];
806 for (const LoggedChange
*lc
= la
->change
; lc
!= lcend
; lc
++) {
811 *last_ottd_rev
= lc
->revision
.newgrf
;
812 *ever_modified
= max(*ever_modified
, lc
->revision
.modified
);
816 *removed_newgrfs
= true;