4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
10 /** @file gamelog.cpp Definition of functions used for logging of important changes in the game */
13 #include "saveload/saveload.h"
14 #include "string_func.h"
15 #include "settings_type.h"
16 #include "gamelog_internal.h"
17 #include "console_func.h"
19 #include "date_func.h"
24 #include "safeguards.h"
26 extern const uint16 SAVEGAME_VERSION
; ///< current savegame version
28 extern SavegameType _savegame_type
; ///< type of savegame we are loading
30 extern uint32 _ttdp_version
; ///< version of TTDP savegame (if applicable)
31 extern uint16 _sl_version
; ///< the major savegame version identifier
32 extern byte _sl_minor_version
; ///< the minor savegame version, DO NOT USE!
35 static GamelogActionType _gamelog_action_type
= GLAT_NONE
; ///< action to record if anything changes
37 LoggedAction
*_gamelog_action
= NULL
; ///< first logged action
38 uint _gamelog_actions
= 0; ///< number of actions
39 static LoggedAction
*_current_action
= NULL
; ///< current action we are logging, NULL when there is no action active
43 * Stores information about new action, but doesn't allocate it
44 * Action is allocated only when there is at least one change
45 * @param at type of action
47 void GamelogStartAction(GamelogActionType at
)
49 assert(_gamelog_action_type
== GLAT_NONE
); // do not allow starting new action without stopping the previous first
50 _gamelog_action_type
= at
;
54 * Stops logging of any changes
56 void GamelogStopAction()
58 assert(_gamelog_action_type
!= GLAT_NONE
); // nobody should try to stop if there is no action in progress
60 bool print
= _current_action
!= NULL
;
62 _current_action
= NULL
;
63 _gamelog_action_type
= GLAT_NONE
;
65 if (print
) GamelogPrintDebug(5);
69 * Frees the memory allocated by a gamelog
71 void GamelogFree(LoggedAction
*gamelog_action
, uint gamelog_actions
)
73 for (uint i
= 0; i
< gamelog_actions
; i
++) {
74 const LoggedAction
*la
= &gamelog_action
[i
];
75 for (uint j
= 0; j
< la
->changes
; j
++) {
76 const LoggedChange
*lc
= &la
->change
[j
];
77 if (lc
->ct
== GLCT_SETTING
) free(lc
->setting
.name
);
86 * Resets and frees all memory allocated - used before loading or starting a new game
90 assert(_gamelog_action_type
== GLAT_NONE
);
91 GamelogFree(_gamelog_action
, _gamelog_actions
);
93 _gamelog_action
= NULL
;
95 _current_action
= NULL
;
99 * Prints GRF ID, checksum and filename if found
100 * @param buf The location in the buffer to draw
101 * @param last The end of the buffer
102 * @param grfid GRF ID
103 * @param md5sum array of md5sum to print, if known
104 * @param gc GrfConfig, if known
105 * @return The buffer location.
107 static char *PrintGrfInfo(char *buf
, const char *last
, uint grfid
, const uint8
*md5sum
, const GRFConfig
*gc
)
111 if (md5sum
!= NULL
) {
112 md5sumToString(txt
, lastof(txt
), md5sum
);
113 buf
+= seprintf(buf
, last
, "GRF ID %08X, checksum %s", BSWAP32(grfid
), txt
);
115 buf
+= seprintf(buf
, last
, "GRF ID %08X", BSWAP32(grfid
));
119 buf
+= seprintf(buf
, last
, ", filename: %s (md5sum matches)", gc
->filename
);
121 gc
= FindGRFConfig(grfid
, FGCM_ANY
);
123 buf
+= seprintf(buf
, last
, ", filename: %s (matches GRFID only)", gc
->filename
);
125 buf
+= seprintf(buf
, last
, ", unknown GRF");
132 /** Text messages for various logged actions */
133 static const char * const la_text
[] = {
136 "GRF config changed",
140 "emergency savegame",
143 assert_compile(lengthof(la_text
) == GLAT_END
);
146 * Information about the presence of a Grf at a certain point during gamelog history
147 * Note about missing Grfs:
148 * Changes to missing Grfs are not logged including manual removal of the Grf.
149 * So if the gamelog tells a Grf is missing we do not know whether it was readded or completely removed
150 * at some later point.
153 const GRFConfig
*gc
; ///< GRFConfig, if known
154 bool was_missing
; ///< Grf was missing during some gameload in the past
156 GRFPresence(const GRFConfig
*gc
) : gc(gc
), was_missing(false) {}
158 typedef SmallMap
<uint32
, GRFPresence
> GrfIDMapping
;
161 * Prints active gamelog
162 * @param proc the procedure to draw with
164 void GamelogPrint(GamelogPrintProc
*proc
)
167 GrfIDMapping grf_names
;
169 proc("---- gamelog start ----");
171 const LoggedAction
*laend
= &_gamelog_action
[_gamelog_actions
];
173 for (const LoggedAction
*la
= _gamelog_action
; la
!= laend
; la
++) {
174 assert((uint
)la
->at
< GLAT_END
);
176 seprintf(buffer
, lastof(buffer
), "Tick %u: %s", (uint
)la
->tick
, la_text
[(uint
)la
->at
]);
179 const LoggedChange
*lcend
= &la
->change
[la
->changes
];
181 for (const LoggedChange
*lc
= la
->change
; lc
!= lcend
; lc
++) {
185 default: NOT_REACHED();
187 buf
+= seprintf(buf
, lastof(buffer
), "New game mode: %u landscape: %u",
188 (uint
)lc
->mode
.mode
, (uint
)lc
->mode
.landscape
);
192 buf
+= seprintf(buf
, lastof(buffer
), "Revision text changed to %s, savegame version %u, ",
193 lc
->revision
.text
, lc
->revision
.slver
);
195 switch (lc
->revision
.modified
) {
196 case 0: buf
+= seprintf(buf
, lastof(buffer
), "not "); break;
197 case 1: buf
+= seprintf(buf
, lastof(buffer
), "maybe "); break;
201 buf
+= seprintf(buf
, lastof(buffer
), "modified, _openttd_newgrf_version = 0x%08x", lc
->revision
.newgrf
);
205 buf
+= seprintf(buf
, lastof(buffer
), "Conversion from ");
206 switch (lc
->oldver
.type
) {
207 default: NOT_REACHED();
209 buf
+= seprintf(buf
, lastof(buffer
), "OTTD savegame without gamelog: version %u, %u",
210 GB(lc
->oldver
.version
, 8, 16), GB(lc
->oldver
.version
, 0, 8));
214 buf
+= seprintf(buf
, lastof(buffer
), "TTO savegame");
218 buf
+= seprintf(buf
, lastof(buffer
), "TTD savegame");
223 buf
+= seprintf(buf
, lastof(buffer
), "TTDP savegame, %s format",
224 lc
->oldver
.type
== SGT_TTDP1
? "old" : "new");
225 if (lc
->oldver
.version
!= 0) {
226 buf
+= seprintf(buf
, lastof(buffer
), ", TTDP version %u.%u.%u.%u",
227 GB(lc
->oldver
.version
, 24, 8), GB(lc
->oldver
.version
, 20, 4),
228 GB(lc
->oldver
.version
, 16, 4), GB(lc
->oldver
.version
, 0, 16));
235 buf
+= seprintf(buf
, lastof(buffer
), "Setting changed: %s : %d -> %d", lc
->setting
.name
, lc
->setting
.oldval
, lc
->setting
.newval
);
239 const GRFConfig
*gc
= FindGRFConfig(lc
->grfadd
.grfid
, FGCM_EXACT
, lc
->grfadd
.md5sum
);
240 buf
+= seprintf(buf
, lastof(buffer
), "Added NewGRF: ");
241 buf
= PrintGrfInfo(buf
, lastof(buffer
), lc
->grfadd
.grfid
, lc
->grfadd
.md5sum
, gc
);
242 GrfIDMapping::Pair
*gm
= grf_names
.Find(lc
->grfrem
.grfid
);
243 if (gm
!= grf_names
.End() && !gm
->second
.was_missing
) buf
+= seprintf(buf
, lastof(buffer
), ". Gamelog inconsistency: GrfID was already added!");
244 grf_names
[lc
->grfadd
.grfid
] = gc
;
249 GrfIDMapping::Pair
*gm
= grf_names
.Find(lc
->grfrem
.grfid
);
250 buf
+= seprintf(buf
, lastof(buffer
), la
->at
== GLAT_LOAD
? "Missing NewGRF: " : "Removed NewGRF: ");
251 buf
= PrintGrfInfo(buf
, lastof(buffer
), lc
->grfrem
.grfid
, NULL
, gm
!= grf_names
.End() ? gm
->second
.gc
: NULL
);
252 if (gm
== grf_names
.End()) {
253 buf
+= seprintf(buf
, lastof(buffer
), ". Gamelog inconsistency: GrfID was never added!");
255 if (la
->at
== GLAT_LOAD
) {
256 /* Missing grfs on load are not removed from the configuration */
257 gm
->second
.was_missing
= true;
265 case GLCT_GRFCOMPAT
: {
266 const GRFConfig
*gc
= FindGRFConfig(lc
->grfadd
.grfid
, FGCM_EXACT
, lc
->grfadd
.md5sum
);
267 buf
+= seprintf(buf
, lastof(buffer
), "Compatible NewGRF loaded: ");
268 buf
= PrintGrfInfo(buf
, lastof(buffer
), lc
->grfcompat
.grfid
, lc
->grfcompat
.md5sum
, gc
);
269 if (!grf_names
.Contains(lc
->grfcompat
.grfid
)) buf
+= seprintf(buf
, lastof(buffer
), ". Gamelog inconsistency: GrfID was never added!");
270 grf_names
[lc
->grfcompat
.grfid
] = gc
;
274 case GLCT_GRFPARAM
: {
275 GrfIDMapping::Pair
*gm
= grf_names
.Find(lc
->grfrem
.grfid
);
276 buf
+= seprintf(buf
, lastof(buffer
), "GRF parameter changed: ");
277 buf
= PrintGrfInfo(buf
, lastof(buffer
), lc
->grfparam
.grfid
, NULL
, gm
!= grf_names
.End() ? gm
->second
.gc
: NULL
);
278 if (gm
== grf_names
.End()) buf
+= seprintf(buf
, lastof(buffer
), ". Gamelog inconsistency: GrfID was never added!");
283 GrfIDMapping::Pair
*gm
= grf_names
.Find(lc
->grfrem
.grfid
);
284 buf
+= seprintf(buf
, lastof(buffer
), "GRF order changed: %08X moved %d places %s",
285 BSWAP32(lc
->grfmove
.grfid
), abs(lc
->grfmove
.offset
), lc
->grfmove
.offset
>= 0 ? "down" : "up" );
286 buf
= PrintGrfInfo(buf
, lastof(buffer
), lc
->grfmove
.grfid
, NULL
, gm
!= grf_names
.End() ? gm
->second
.gc
: NULL
);
287 if (gm
== grf_names
.End()) buf
+= seprintf(buf
, lastof(buffer
), ". Gamelog inconsistency: GrfID was never added!");
292 GrfIDMapping::Pair
*gm
= grf_names
.Find(lc
->grfrem
.grfid
);
293 switch (lc
->grfbug
.bug
) {
294 default: NOT_REACHED();
295 case GBUG_VEH_LENGTH
:
296 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
);
299 buf
= PrintGrfInfo(buf
, lastof(buffer
), lc
->grfbug
.grfid
, NULL
, gm
!= grf_names
.End() ? gm
->second
.gc
: NULL
);
300 if (gm
== grf_names
.End()) buf
+= seprintf(buf
, lastof(buffer
), ". Gamelog inconsistency: GrfID was never added!");
312 proc("---- gamelog end ----");
316 static void GamelogPrintConsoleProc(const char *s
)
318 IConsolePrint(CC_WARNING
, s
);
321 /** Print the gamelog data to the console. */
322 void GamelogPrintConsole()
324 GamelogPrint(&GamelogPrintConsoleProc
);
327 static int _gamelog_print_level
= 0; ///< gamelog debug level we need to print stuff
329 static void GamelogPrintDebugProc(const char *s
)
331 DEBUG(gamelog
, _gamelog_print_level
, "%s", s
);
336 * Prints gamelog to debug output. Code is executed even when
337 * there will be no output. It is called very seldom, so it
338 * doesn't matter that much. At least it gives more uniform code...
339 * @param level debug level we need to print stuff
341 void GamelogPrintDebug(int level
)
343 _gamelog_print_level
= level
;
344 GamelogPrint(&GamelogPrintDebugProc
);
349 * Allocates new LoggedChange and new LoggedAction if needed.
350 * If there is no action active, NULL is returned.
351 * @param ct type of change
352 * @return new LoggedChange, or NULL if there is no action active
354 static LoggedChange
*GamelogChange(GamelogChangeType ct
)
356 if (_current_action
== NULL
) {
357 if (_gamelog_action_type
== GLAT_NONE
) return NULL
;
359 _gamelog_action
= ReallocT(_gamelog_action
, _gamelog_actions
+ 1);
360 _current_action
= &_gamelog_action
[_gamelog_actions
++];
362 _current_action
->at
= _gamelog_action_type
;
363 _current_action
->tick
= _tick_counter
;
364 _current_action
->change
= NULL
;
365 _current_action
->changes
= 0;
368 _current_action
->change
= ReallocT(_current_action
->change
, _current_action
->changes
+ 1);
370 LoggedChange
*lc
= &_current_action
->change
[_current_action
->changes
++];
378 * Logs a emergency savegame
380 void GamelogEmergency()
382 /* Terminate any active action */
383 if (_gamelog_action_type
!= GLAT_NONE
) GamelogStopAction();
384 GamelogStartAction(GLAT_EMERGENCY
);
385 GamelogChange(GLCT_EMERGENCY
);
390 * Finds out if current game is a loaded emergency savegame.
392 bool GamelogTestEmergency()
394 const LoggedChange
*emergency
= NULL
;
396 const LoggedAction
*laend
= &_gamelog_action
[_gamelog_actions
];
397 for (const LoggedAction
*la
= _gamelog_action
; la
!= laend
; la
++) {
398 const LoggedChange
*lcend
= &la
->change
[la
->changes
];
399 for (const LoggedChange
*lc
= la
->change
; lc
!= lcend
; lc
++) {
400 if (lc
->ct
== GLCT_EMERGENCY
) emergency
= lc
;
404 return (emergency
!= NULL
);
408 * Logs a change in game revision
410 void GamelogRevision()
412 assert(_gamelog_action_type
== GLAT_START
|| _gamelog_action_type
== GLAT_LOAD
);
414 LoggedChange
*lc
= GamelogChange(GLCT_REVISION
);
415 if (lc
== NULL
) return;
417 memset(lc
->revision
.text
, 0, sizeof(lc
->revision
.text
));
418 strecpy(lc
->revision
.text
, _openttd_revision
, lastof(lc
->revision
.text
));
419 lc
->revision
.slver
= SAVEGAME_VERSION
;
420 lc
->revision
.modified
= _openttd_revision_modified
;
421 lc
->revision
.newgrf
= _openttd_newgrf_version
;
425 * Logs a change in game mode (scenario editor or game)
429 assert(_gamelog_action_type
== GLAT_START
|| _gamelog_action_type
== GLAT_LOAD
|| _gamelog_action_type
== GLAT_CHEAT
);
431 LoggedChange
*lc
= GamelogChange(GLCT_MODE
);
432 if (lc
== NULL
) return;
434 lc
->mode
.mode
= _game_mode
;
435 lc
->mode
.landscape
= _settings_game
.game_creation
.landscape
;
439 * Logs loading from savegame without gamelog
443 assert(_gamelog_action_type
== GLAT_LOAD
);
445 LoggedChange
*lc
= GamelogChange(GLCT_OLDVER
);
446 if (lc
== NULL
) return;
448 lc
->oldver
.type
= _savegame_type
;
449 lc
->oldver
.version
= (_savegame_type
== SGT_OTTD
? ((uint32
)_sl_version
<< 8 | _sl_minor_version
) : _ttdp_version
);
453 * Logs change in game settings. Only non-networksafe settings are logged
454 * @param name setting name
455 * @param oldval old setting value
456 * @param newval new setting value
458 void GamelogSetting(const char *name
, int32 oldval
, int32 newval
)
460 assert(_gamelog_action_type
== GLAT_SETTING
);
462 LoggedChange
*lc
= GamelogChange(GLCT_SETTING
);
463 if (lc
== NULL
) return;
465 lc
->setting
.name
= stredup(name
);
466 lc
->setting
.oldval
= oldval
;
467 lc
->setting
.newval
= newval
;
472 * Finds out if current revision is different than last revision stored in the savegame.
473 * Appends GLCT_REVISION when the revision string changed
475 void GamelogTestRevision()
477 const LoggedChange
*rev
= NULL
;
479 const LoggedAction
*laend
= &_gamelog_action
[_gamelog_actions
];
480 for (const LoggedAction
*la
= _gamelog_action
; la
!= laend
; la
++) {
481 const LoggedChange
*lcend
= &la
->change
[la
->changes
];
482 for (const LoggedChange
*lc
= la
->change
; lc
!= lcend
; lc
++) {
483 if (lc
->ct
== GLCT_REVISION
) rev
= lc
;
487 if (rev
== NULL
|| strcmp(rev
->revision
.text
, _openttd_revision
) != 0 ||
488 rev
->revision
.modified
!= _openttd_revision_modified
||
489 rev
->revision
.newgrf
!= _openttd_newgrf_version
) {
495 * Finds last stored game mode or landscape.
496 * Any change is logged
498 void GamelogTestMode()
500 const LoggedChange
*mode
= NULL
;
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_MODE
) mode
= lc
;
510 if (mode
== NULL
|| mode
->mode
.mode
!= _game_mode
|| mode
->mode
.landscape
!= _settings_game
.game_creation
.landscape
) GamelogMode();
515 * Logs triggered GRF bug.
516 * @param grfid ID of problematic GRF
517 * @param bug type of bug, @see enum GRFBugs
518 * @param data additional data
520 static void GamelogGRFBug(uint32 grfid
, byte bug
, uint64 data
)
522 assert(_gamelog_action_type
== GLAT_GRFBUG
);
524 LoggedChange
*lc
= GamelogChange(GLCT_GRFBUG
);
525 if (lc
== NULL
) return;
527 lc
->grfbug
.data
= data
;
528 lc
->grfbug
.grfid
= grfid
;
529 lc
->grfbug
.bug
= bug
;
533 * Logs GRF bug - rail vehicle has different length after reversing.
534 * Ensures this is logged only once for each GRF and engine type
535 * This check takes some time, but it is called pretty seldom, so it
536 * doesn't matter that much (ideally it shouldn't be called at all).
537 * @param grfid the broken NewGRF
538 * @param internal_id the internal ID of whatever's broken in the NewGRF
539 * @return true iff a unique record was done
541 bool GamelogGRFBugReverse(uint32 grfid
, uint16 internal_id
)
543 const LoggedAction
*laend
= &_gamelog_action
[_gamelog_actions
];
544 for (const LoggedAction
*la
= _gamelog_action
; la
!= laend
; la
++) {
545 const LoggedChange
*lcend
= &la
->change
[la
->changes
];
546 for (const LoggedChange
*lc
= la
->change
; lc
!= lcend
; lc
++) {
547 if (lc
->ct
== GLCT_GRFBUG
&& lc
->grfbug
.grfid
== grfid
&&
548 lc
->grfbug
.bug
== GBUG_VEH_LENGTH
&& lc
->grfbug
.data
== internal_id
) {
554 GamelogStartAction(GLAT_GRFBUG
);
555 GamelogGRFBug(grfid
, GBUG_VEH_LENGTH
, internal_id
);
563 * Decides if GRF should be logged
564 * @param g grf to determine
565 * @return true iff GRF is not static and is loaded
567 static inline bool IsLoggableGrfConfig(const GRFConfig
*g
)
569 return !HasBit(g
->flags
, GCF_STATIC
) && g
->status
!= GCS_NOT_FOUND
;
573 * Logs removal of a GRF
574 * @param grfid ID of removed GRF
576 void GamelogGRFRemove(uint32 grfid
)
578 assert(_gamelog_action_type
== GLAT_LOAD
|| _gamelog_action_type
== GLAT_GRF
);
580 LoggedChange
*lc
= GamelogChange(GLCT_GRFREM
);
581 if (lc
== NULL
) return;
583 lc
->grfrem
.grfid
= grfid
;
587 * Logs adding of a GRF
588 * @param newg added GRF
590 void GamelogGRFAdd(const GRFConfig
*newg
)
592 assert(_gamelog_action_type
== GLAT_LOAD
|| _gamelog_action_type
== GLAT_START
|| _gamelog_action_type
== GLAT_GRF
);
594 if (!IsLoggableGrfConfig(newg
)) return;
596 LoggedChange
*lc
= GamelogChange(GLCT_GRFADD
);
597 if (lc
== NULL
) return;
599 lc
->grfadd
= newg
->ident
;
603 * Logs loading compatible GRF
604 * (the same ID, but different MD5 hash)
605 * @param newg new (updated) GRF
607 void GamelogGRFCompatible(const GRFIdentifier
*newg
)
609 assert(_gamelog_action_type
== GLAT_LOAD
|| _gamelog_action_type
== GLAT_GRF
);
611 LoggedChange
*lc
= GamelogChange(GLCT_GRFCOMPAT
);
612 if (lc
== NULL
) return;
614 lc
->grfcompat
= *newg
;
618 * Logs changing GRF order
619 * @param grfid GRF that is moved
620 * @param offset how far it is moved, positive = moved down
622 static void GamelogGRFMove(uint32 grfid
, int32 offset
)
624 assert(_gamelog_action_type
== GLAT_GRF
);
626 LoggedChange
*lc
= GamelogChange(GLCT_GRFMOVE
);
627 if (lc
== NULL
) return;
629 lc
->grfmove
.grfid
= grfid
;
630 lc
->grfmove
.offset
= offset
;
634 * Logs change in GRF parameters.
635 * Details about parameters changed are not stored
636 * @param grfid ID of GRF to store
638 static void GamelogGRFParameters(uint32 grfid
)
640 assert(_gamelog_action_type
== GLAT_GRF
);
642 LoggedChange
*lc
= GamelogChange(GLCT_GRFPARAM
);
643 if (lc
== NULL
) return;
645 lc
->grfparam
.grfid
= grfid
;
649 * Logs adding of list of GRFs.
650 * Useful when old savegame is loaded or when new game is started
651 * @param newg head of GRF linked list
653 void GamelogGRFAddList(const GRFConfig
*newg
)
655 assert(_gamelog_action_type
== GLAT_START
|| _gamelog_action_type
== GLAT_LOAD
);
657 for (; newg
!= NULL
; newg
= newg
->next
) {
662 /** List of GRFs using array of pointers instead of linked list */
665 const GRFConfig
*grf
[];
670 * @param grfc head of GRF linked list
672 static GRFList
*GenerateGRFList(const GRFConfig
*grfc
)
675 for (const GRFConfig
*g
= grfc
; g
!= NULL
; g
= g
->next
) {
676 if (IsLoggableGrfConfig(g
)) n
++;
679 GRFList
*list
= (GRFList
*)MallocT
<byte
>(sizeof(GRFList
) + n
* sizeof(GRFConfig
*));
682 for (const GRFConfig
*g
= grfc
; g
!= NULL
; g
= g
->next
) {
683 if (IsLoggableGrfConfig(g
)) list
->grf
[list
->n
++] = g
;
690 * Compares two NewGRF lists and logs any change
691 * @param oldc original GRF list
692 * @param newc new GRF list
694 void GamelogGRFUpdate(const GRFConfig
*oldc
, const GRFConfig
*newc
)
696 GRFList
*ol
= GenerateGRFList(oldc
);
697 GRFList
*nl
= GenerateGRFList(newc
);
701 while (o
< ol
->n
&& n
< nl
->n
) {
702 const GRFConfig
*og
= ol
->grf
[o
];
703 const GRFConfig
*ng
= nl
->grf
[n
];
705 if (og
->ident
.grfid
!= ng
->ident
.grfid
) {
707 for (oi
= 0; oi
< ol
->n
; oi
++) {
708 if (ol
->grf
[oi
]->ident
.grfid
== nl
->grf
[n
]->ident
.grfid
) break;
711 /* GRF was moved, this change has been logged already */
716 /* GRF couldn't be found in the OLD list, GRF was ADDED */
717 GamelogGRFAdd(nl
->grf
[n
++]);
720 for (ni
= 0; ni
< nl
->n
; ni
++) {
721 if (nl
->grf
[ni
]->ident
.grfid
== ol
->grf
[o
]->ident
.grfid
) break;
724 /* GRF was moved, this change has been logged already */
729 /* GRF couldn't be found in the NEW list, GRF was REMOVED */
730 GamelogGRFRemove(ol
->grf
[o
++]->ident
.grfid
);
736 assert(ni
> n
&& ni
< nl
->n
);
737 assert(oi
> o
&& oi
< ol
->n
);
739 ni
-= n
; // number of GRFs it was moved downwards
740 oi
-= o
; // number of GRFs it was moved upwards
742 if (ni
>= oi
) { // prefer the one that is moved further
743 /* GRF was moved down */
744 GamelogGRFMove(ol
->grf
[o
++]->ident
.grfid
, ni
);
746 GamelogGRFMove(nl
->grf
[n
++]->ident
.grfid
, -(int)oi
);
749 if (memcmp(og
->ident
.md5sum
, ng
->ident
.md5sum
, sizeof(og
->ident
.md5sum
)) != 0) {
750 /* md5sum changed, probably loading 'compatible' GRF */
751 GamelogGRFCompatible(&nl
->grf
[n
]->ident
);
754 if (og
->num_params
!= ng
->num_params
|| memcmp(og
->param
, ng
->param
, og
->num_params
* sizeof(og
->param
[0])) != 0) {
755 GamelogGRFParameters(ol
->grf
[o
]->ident
.grfid
);
763 while (o
< ol
->n
) GamelogGRFRemove(ol
->grf
[o
++]->ident
.grfid
); // remaining GRFs were removed ...
764 while (n
< nl
->n
) GamelogGRFAdd (nl
->grf
[n
++]); // ... or added
771 * Get some basic information from the given gamelog.
772 * @param gamelog_action Pointer to the gamelog to extract information from.
773 * @param gamelog_actions Number of actions in the given gamelog.
774 * @param [out] last_ottd_rev OpenTTD NewGRF version from the binary that saved the savegame last.
775 * @param [out] ever_modified Max value of 'modified' from all binaries that ever saved this savegame.
776 * @param [out] removed_newgrfs Set to true if any NewGRFs have been removed.
778 void GamelogInfo(LoggedAction
*gamelog_action
, uint gamelog_actions
, uint32
*last_ottd_rev
, byte
*ever_modified
, bool *removed_newgrfs
)
780 const LoggedAction
*laend
= &gamelog_action
[gamelog_actions
];
781 for (const LoggedAction
*la
= gamelog_action
; la
!= laend
; la
++) {
782 const LoggedChange
*lcend
= &la
->change
[la
->changes
];
783 for (const LoggedChange
*lc
= la
->change
; lc
!= lcend
; lc
++) {
788 *last_ottd_rev
= lc
->revision
.newgrf
;
789 *ever_modified
= max(*ever_modified
, lc
->revision
.modified
);
793 *removed_newgrfs
= true;