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"
15 #include "settings_type.h"
16 #include "gamelog_entries.h"
17 #include "console_func.h"
19 #include "date_func.h"
25 Gamelog _gamelog
; ///< gamelog
28 * Resets and frees all memory allocated - used before loading or starting a new game
37 * Get some basic information from the given gamelog.
38 * @param gamelog Pointer to the gamelog to extract information from.
39 * @param [out] last_rev NewGRF version from the binary that last saved the savegame.
40 * @param [out] ever_modified Max value of 'modified' from all binaries that ever saved this savegame.
41 * @param [out] removed_newgrfs Set to true if any NewGRFs have been removed.
43 void GamelogInfo(const Gamelog
*gamelog
, uint32
*last_rev
, byte
*ever_modified
, bool *removed_newgrfs
)
45 for (Gamelog::const_iterator entry
= gamelog
->begin(); entry
!= gamelog
->end(); entry
++) {
46 switch ((*entry
)->type
) {
50 const GamelogEntryRevision
*rev
= (GamelogEntryRevision
*)entry
->get();
51 *last_rev
= rev
->newgrf
;
52 *ever_modified
= max(*ever_modified
, rev
->modified
);
57 *removed_newgrfs
= true;
64 GamelogEntry
*GamelogEntryByType(uint type
)
67 case GLOG_START
: return new GamelogEntryStart();
68 case GLOG_STARTED
: return new GamelogEntryStarted();
69 case GLOG_LOAD
: return new GamelogEntryLoad();
70 case GLOG_LOADED
: return new GamelogEntryLoaded();
71 case GLOG_MODE
: return new GamelogEntryMode();
72 case GLOG_REVISION
: return new GamelogEntryRevision();
73 case GLOG_LEGACYREV
: return new GamelogEntryLegacyRev();
74 case GLOG_OLDVER
: return new GamelogEntryOldVer();
75 case GLOG_EMERGENCY
: return new GamelogEntryEmergency();
76 case GLOG_SETTING
: return new GamelogEntrySetting();
77 case GLOG_CHEAT
: return new GamelogEntryCheat();
78 case GLOG_GRFBEGIN
: return new GamelogEntryGRFBegin();
79 case GLOG_GRFEND
: return new GamelogEntryGRFEnd();
80 case GLOG_GRFADD
: return new GamelogEntryGRFAdd();
81 case GLOG_GRFREM
: return new GamelogEntryGRFRemove();
82 case GLOG_GRFCOMPAT
: return new GamelogEntryGRFCompat();
83 case GLOG_GRFPARAM
: return new GamelogEntryGRFParam();
84 case GLOG_GRFMOVE
: return new GamelogEntryGRFMove();
85 case GLOG_GRFBUG
: return new GamelogEntryGRFBug();
86 default: NOT_REACHED();
92 * Information about the presence of a Grf at a certain point during gamelog history
93 * Note about missing Grfs:
94 * Changes to missing Grfs are not logged including manual removal of the Grf.
95 * So if the gamelog tells a Grf is missing we do not know whether it was readded or completely removed
96 * at some later point.
99 const GRFConfig
*gc
; ///< GRFConfig, if known
100 bool was_missing
; ///< Grf was missing during some gameload in the past
102 GRFPresence(const GRFConfig
*gc
) : gc(gc
), was_missing(false) {}
104 typedef SmallMap
<uint32
, GRFPresence
> GrfIDMapping
;
107 /** Gamelog print buffer */
108 struct GamelogPrintBuffer
: sstring
<1024> {
109 GrfIDMapping grf_names
; ///< keep track of this so that inconsistencies can be detected
110 bool in_load
; ///< currently printing between GLOG_LOAD and GLOG_LOADED
114 * Prints GRF ID, checksum and filename if found
115 * @param buf The buffer to write to
116 * @param grfid GRF ID
117 * @param md5sum array of md5sum to print, if known
118 * @param gc GrfConfig, if known
120 static void PrintGrfInfo(GamelogPrintBuffer
*buf
, uint grfid
, const uint8
*md5sum
, const GRFConfig
*gc
)
122 if (md5sum
!= NULL
) {
124 md5sumToString (txt
, md5sum
);
125 buf
->append_fmt ("GRF ID %08X, checksum %s", BSWAP32(grfid
), txt
);
127 buf
->append_fmt ("GRF ID %08X", BSWAP32(grfid
));
131 buf
->append_fmt (", filename: %s (md5sum matches)", gc
->filename
);
133 gc
= FindGRFConfig(grfid
, FGCM_ANY
);
135 buf
->append_fmt (", filename: %s (matches GRFID only)", gc
->filename
);
137 buf
->append(", unknown GRF");
143 * Prints active gamelog
144 * @param proc the procedure to draw with
146 void GamelogPrint (GamelogPrintProc
*proc
, void *data
)
148 GamelogPrintBuffer buf
;
150 proc ("---- gamelog start ----", data
);
152 for (Gamelog::const_iterator entry
= _gamelog
.begin(); entry
!= _gamelog
.end(); entry
++) {
154 (*entry
)->Print(&buf
);
155 proc (buf
.c_str(), data
);
158 proc ("---- gamelog end ----", data
);
162 static void GamelogPrintConsoleProc (const char *s
, void*)
164 IConsolePrint(CC_WARNING
, s
);
167 /** Print the gamelog data to the console. */
168 void GamelogPrintConsole()
170 GamelogPrint (&GamelogPrintConsoleProc
, NULL
);
173 static void GamelogPrintDebugProc (const char *s
, void *level
)
175 DEBUG(gamelog
, *(int*)level
, "%s", s
);
179 * Prints gamelog to debug output. Code is executed even when
180 * there will be no output. It is called very seldom, so it
181 * doesn't matter that much. At least it gives more uniform code...
182 * @param level debug level we need to print stuff
184 void GamelogPrintDebug(int level
)
186 GamelogPrint (&GamelogPrintDebugProc
, &level
);
190 /* Gamelog entry types */
193 /* Gamelog entry base class for entries with tick information. */
195 void GamelogEntryTimed::PrependTick(GamelogPrintBuffer
*buf
) {
196 buf
->append_fmt ("Tick %u: ", (uint
)this->tick
);
200 /* Gamelog entry for game start */
202 void GamelogEntryStart::Print(GamelogPrintBuffer
*buf
) {
203 this->PrependTick(buf
);
204 buf
->append("New game");
207 void GamelogAddStart()
209 _gamelog
.append(new GamelogEntryStart());
213 /* Gamelog entry after game start */
215 void GamelogEntryStarted::Print(GamelogPrintBuffer
*buf
) {
216 buf
->append(" Game started");
219 void GamelogAddStarted()
221 _gamelog
.append(new GamelogEntryStarted());
225 /** Gamelog entry for game load */
226 void GamelogEntryLoad::Print(GamelogPrintBuffer
*buf
) {
227 this->PrependTick(buf
);
228 buf
->append("Load game");
232 void GamelogAddLoad()
234 _gamelog
.append(new GamelogEntryLoad());
238 /* Gamelog entry after game load */
240 void GamelogEntryLoaded::Print(GamelogPrintBuffer
*buf
) {
241 buf
->append(" Game loaded");
242 buf
->in_load
= false;
245 void GamelogAddLoaded()
247 _gamelog
.append(new GamelogEntryLoaded());
251 /* Gamelog entry for mode switch between scenario editor and game */
253 void GamelogEntryMode::Print(GamelogPrintBuffer
*buf
) {
254 buf
->append_fmt (" New game mode %u, landscape %u",
255 (uint
)this->mode
, (uint
)this->landscape
);
259 * Logs a change in game mode (scenario editor or game)
261 void GamelogAddMode()
263 _gamelog
.append(new GamelogEntryMode());
267 * Finds last stored game mode or landscape.
268 * Any change is logged
270 void GamelogTestMode()
272 const GamelogEntryMode
*mode
= NULL
;
274 for (Gamelog::const_iterator entry
= _gamelog
.begin(); entry
!= _gamelog
.end(); entry
++) {
275 if ((*entry
)->type
== GLOG_MODE
) {
276 mode
= (GamelogEntryMode
*)entry
->get();
280 if (mode
== NULL
|| mode
->mode
!= _game_mode
|| mode
->landscape
!= _settings_game
.game_creation
.landscape
) GamelogAddMode();
284 /* Gamelog entry for game revision string */
286 void GamelogEntryRevision::Print(GamelogPrintBuffer
*buf
) {
287 buf
->append_fmt (" Revision text changed to %s, savegame version %u, %smodified, newgrf version 0x%08x",
288 this->text
, this->slver
,
289 (this->modified
== 0) ? "not " : (this->modified
== 1) ? "maybe " : "",
294 * Logs a change in game revision
296 void GamelogAddRevision()
298 _gamelog
.append(new GamelogEntryRevision());
302 * Finds out if current revision is different than last revision stored in the savegame.
303 * Appends a revision entry when the revision string changed
305 void GamelogTestRevision()
307 const GamelogEntryRevision
*rev
= NULL
;
309 for (Gamelog::const_iterator entry
= _gamelog
.begin(); entry
!= _gamelog
.end(); entry
++) {
310 if ((*entry
)->type
== GLOG_REVISION
) {
311 rev
= (GamelogEntryRevision
*)entry
->get();
315 if (rev
== NULL
|| strcmp(rev
->text
, _openttd_revision
) != 0 ||
316 rev
->modified
!= _openttd_revision_modified
||
317 rev
->newgrf
!= _openttd_newgrf_version
) {
318 GamelogAddRevision();
323 /* Gamelog entry for game revision string (legacy) */
325 void GamelogEntryLegacyRev::Print(GamelogPrintBuffer
*buf
) {
326 buf
->append_fmt (" Revision text changed to %s (legacy), savegame version %u, %smodified, newgrf version 0x%08x",
327 this->text
, this->slver
,
328 (this->modified
== 0) ? "not " : (this->modified
== 1) ? "maybe " : "",
333 /* Gamelog entry for savegames without log */
335 void GamelogEntryOldVer::Print(GamelogPrintBuffer
*buf
) {
336 switch (this->savetype
) {
338 buf
->append(" Conversion from TTO savegame");
342 buf
->append(" Conversion from TTD savegame");
347 buf
->append_fmt (" Conversion from %s TTDP savegame version %u.%u.%u.%u",
348 (this->savetype
== SGT_TTDP1
) ? "old" : "new",
349 GB(this->version
, 24, 8),
350 GB(this->version
, 20, 4),
351 GB(this->version
, 16, 4),
352 GB(this->version
, 0, 16));
355 default: NOT_REACHED();
357 buf
->append_fmt (" Conversion from OTTD savegame without gamelog, version %u, %u",
358 GB(this->version
, 8, 16), GB(this->version
, 0, 8));
364 * Logs loading from savegame without gamelog
366 void GamelogOldver(const SavegameTypeVersion
*stv
)
368 _gamelog
.append(new GamelogEntryOldVer(stv
));
372 /* Gamelog entry for emergency savegames. */
374 void GamelogEntryEmergency::Print(GamelogPrintBuffer
*buf
) {
375 this->PrependTick(buf
);
376 buf
->append("Emergency savegame");
380 * Logs an emergency savegame
382 void GamelogEmergency()
384 _gamelog
.append(new GamelogEntryEmergency());
388 * Finds out if current game is a loaded emergency savegame.
390 bool GamelogTestEmergency()
392 for (Gamelog::const_iterator entry
= _gamelog
.begin(); entry
!= _gamelog
.end(); entry
++) {
393 if ((*entry
)->type
== GLOG_EMERGENCY
) return true;
400 /* Gamelog entry for settings change */
402 void GamelogEntrySetting::Print(GamelogPrintBuffer
*buf
) {
403 this->PrependTick(buf
);
404 buf
->append_fmt ("Setting '%s' changed from %d to %d",
405 this->name
, this->oldval
, this->newval
);
409 * Logs change in game settings. Only non-networksafe settings are logged
410 * @param name setting name
411 * @param oldval old setting value
412 * @param newval new setting value
414 void GamelogSetting(const char *name
, int32 oldval
, int32 newval
)
416 _gamelog
.append(new GamelogEntrySetting(name
, oldval
, newval
));
420 /* Gamelog entry for cheating */
422 void GamelogEntryCheat::Print(GamelogPrintBuffer
*buf
) {
423 this->PrependTick(buf
);
424 buf
->append("Cheat used");
428 /* Gamelog entry for GRF config change begin */
430 void GamelogEntryGRFBegin::Print(GamelogPrintBuffer
*buf
) {
431 this->PrependTick(buf
);
432 buf
->append("GRF config change");
436 * Log GRF config change begin
438 void GamelogGRFBegin()
440 _gamelog
.append(new GamelogEntryGRFBegin());
444 /* Gamelog entry for GRF config change end */
446 void GamelogEntryGRFEnd::Print(GamelogPrintBuffer
*buf
) {
447 buf
->append(" GRF config change end");
451 * Log GRF config change end
455 _gamelog
.append(new GamelogEntryGRFEnd());
460 * Decides if GRF should be logged
461 * @param g grf to determine
462 * @return true iff GRF is not static and is loaded
464 static inline bool IsLoggableGrfConfig(const GRFConfig
*g
)
466 return !HasBit(g
->flags
, GCF_STATIC
) && g
->status
!= GCS_NOT_FOUND
;
470 /* Gamelog entry for GRF addition */
472 void GamelogEntryGRFAdd::Print(GamelogPrintBuffer
*buf
) {
473 const GRFConfig
*gc
= FindGRFConfig(this->grf
.grfid
, FGCM_EXACT
, this->grf
.md5sum
);
474 buf
->append(" Added NewGRF: ");
475 PrintGrfInfo(buf
, this->grf
.grfid
, this->grf
.md5sum
, gc
);
476 GrfIDMapping::Pair
*gm
= buf
->grf_names
.Find(this->grf
.grfid
);
477 if (gm
!= buf
->grf_names
.End() && !gm
->second
.was_missing
) buf
->append(" (inconsistency: already added)");
478 buf
->grf_names
[this->grf
.grfid
] = gc
;
482 * Logs adding of a GRF
483 * @param newg added GRF
485 void GamelogGRFAdd(const GRFConfig
*newg
)
487 if (!IsLoggableGrfConfig(newg
)) return;
489 _gamelog
.append(new GamelogEntryGRFAdd(&newg
->ident
));
493 * Logs adding of list of GRFs.
494 * Useful when old savegame is loaded or when new game is started
495 * @param newg head of GRF linked list
497 void GamelogGRFAddList(const GRFConfig
*newg
)
499 for (; newg
!= NULL
; newg
= newg
->next
) {
505 /* Gamelog entry for GRF removal */
507 void GamelogEntryGRFRemove::Print(GamelogPrintBuffer
*buf
) {
508 GrfIDMapping::Pair
*gm
= buf
->grf_names
.Find(this->grfid
);
509 buf
->append(buf
->in_load
? " Missing NewGRF: " : " Removed NewGRF: ");
510 PrintGrfInfo(buf
, this->grfid
, NULL
, gm
!= buf
->grf_names
.End() ? gm
->second
.gc
: NULL
);
511 if (gm
== buf
->grf_names
.End()) {
512 buf
->append(" (inconsistency: never added)");
513 } else if (buf
->in_load
) {
514 /* Missing grfs on load are not removed from the configuration */
515 gm
->second
.was_missing
= true;
517 buf
->grf_names
.Erase(gm
);
522 * Logs removal of a GRF
523 * @param grfid ID of removed GRF
525 void GamelogGRFRemove(uint32 grfid
)
527 _gamelog
.append(new GamelogEntryGRFRemove(grfid
));
531 /* Gamelog entry for compatible GRF load */
533 void GamelogEntryGRFCompat::Print(GamelogPrintBuffer
*buf
) {
534 const GRFConfig
*gc
= FindGRFConfig(this->grf
.grfid
, FGCM_EXACT
, this->grf
.md5sum
);
535 buf
->append(" Compatible NewGRF loaded: ");
536 PrintGrfInfo(buf
, this->grf
.grfid
, this->grf
.md5sum
, gc
);
537 if (!buf
->grf_names
.Contains(this->grf
.grfid
)) buf
->append(" (inconsistency: never added)");
538 buf
->grf_names
[this->grf
.grfid
] = gc
;
542 * Logs loading compatible GRF
543 * (the same ID, but different MD5 hash)
544 * @param newg new (updated) GRF
546 void GamelogGRFCompatible(const GRFIdentifier
*newg
)
548 _gamelog
.append(new GamelogEntryGRFCompat(newg
));
552 /* Gamelog entry for GRF parameter changes */
554 void GamelogEntryGRFParam::Print(GamelogPrintBuffer
*buf
) {
555 GrfIDMapping::Pair
*gm
= buf
->grf_names
.Find(this->grfid
);
556 buf
->append(" GRF parameter changed: ");
557 PrintGrfInfo(buf
, this->grfid
, NULL
, gm
!= buf
->grf_names
.End() ? gm
->second
.gc
: NULL
);
558 if (gm
== buf
->grf_names
.End()) buf
->append(" (inconsistency: never added)");
562 * Logs change in GRF parameters.
563 * Details about parameters changed are not stored
564 * @param grfid ID of GRF to store
566 static void GamelogGRFParameters(uint32 grfid
)
568 _gamelog
.append(new GamelogEntryGRFParam(grfid
));
572 /* Gamelog entry for GRF order change */
574 void GamelogEntryGRFMove::Print(GamelogPrintBuffer
*buf
) {
575 GrfIDMapping::Pair
*gm
= buf
->grf_names
.Find(this->grfid
);
576 buf
->append_fmt ("GRF order changed: %08X moved %d places %s, ",
577 BSWAP32(this->grfid
), abs(this->offset
), this->offset
>= 0 ? "down" : "up");
578 PrintGrfInfo(buf
, this->grfid
, NULL
, gm
!= buf
->grf_names
.End() ? gm
->second
.gc
: NULL
);
579 if (gm
== buf
->grf_names
.End()) buf
->append(" (inconsistency: never added)");
583 * Logs changing GRF order
584 * @param grfid GRF that is moved
585 * @param offset how far it is moved, positive = moved down
587 static void GamelogGRFMove(uint32 grfid
, int32 offset
)
589 _gamelog
.append(new GamelogEntryGRFMove(grfid
, offset
));
593 /** List of GRFs using array of pointers instead of linked list */
596 const GRFConfig
*grf
[];
601 * @param grfc head of GRF linked list
603 static GRFList
*GenerateGRFList(const GRFConfig
*grfc
)
606 for (const GRFConfig
*g
= grfc
; g
!= NULL
; g
= g
->next
) {
607 if (IsLoggableGrfConfig(g
)) n
++;
610 GRFList
*list
= (GRFList
*) xmalloc (sizeof(GRFList
) + n
* sizeof(GRFConfig
*));
613 for (const GRFConfig
*g
= grfc
; g
!= NULL
; g
= g
->next
) {
614 if (IsLoggableGrfConfig(g
)) list
->grf
[list
->n
++] = g
;
621 * Compares two NewGRF lists and logs any change
622 * @param oldc original GRF list
623 * @param newc new GRF list
625 void GamelogGRFUpdate(const GRFConfig
*oldc
, const GRFConfig
*newc
)
627 GRFList
*ol
= GenerateGRFList(oldc
);
628 GRFList
*nl
= GenerateGRFList(newc
);
632 while (o
< ol
->n
&& n
< nl
->n
) {
633 const GRFConfig
*og
= ol
->grf
[o
];
634 const GRFConfig
*ng
= nl
->grf
[n
];
636 if (og
->ident
.grfid
!= ng
->ident
.grfid
) {
638 for (oi
= 0; oi
< ol
->n
; oi
++) {
639 if (ol
->grf
[oi
]->ident
.grfid
== nl
->grf
[n
]->ident
.grfid
) break;
642 /* GRF was moved, this change has been logged already */
647 /* GRF couldn't be found in the OLD list, GRF was ADDED */
648 GamelogGRFAdd(nl
->grf
[n
++]);
651 for (ni
= 0; ni
< nl
->n
; ni
++) {
652 if (nl
->grf
[ni
]->ident
.grfid
== ol
->grf
[o
]->ident
.grfid
) break;
655 /* GRF was moved, this change has been logged already */
660 /* GRF couldn't be found in the NEW list, GRF was REMOVED */
661 GamelogGRFRemove(ol
->grf
[o
++]->ident
.grfid
);
667 assert(ni
> n
&& ni
< nl
->n
);
668 assert(oi
> o
&& oi
< ol
->n
);
670 ni
-= n
; // number of GRFs it was moved downwards
671 oi
-= o
; // number of GRFs it was moved upwards
673 if (ni
>= oi
) { // prefer the one that is moved further
674 /* GRF was moved down */
675 GamelogGRFMove(ol
->grf
[o
++]->ident
.grfid
, ni
);
677 GamelogGRFMove(nl
->grf
[n
++]->ident
.grfid
, -(int)oi
);
680 if (memcmp(og
->ident
.md5sum
, ng
->ident
.md5sum
, sizeof(og
->ident
.md5sum
)) != 0) {
681 /* md5sum changed, probably loading 'compatible' GRF */
682 GamelogGRFCompatible(&nl
->grf
[n
]->ident
);
685 if (og
->num_params
!= ng
->num_params
|| memcmp(og
->param
, ng
->param
, og
->num_params
* sizeof(og
->param
[0])) != 0) {
686 GamelogGRFParameters(ol
->grf
[o
]->ident
.grfid
);
694 while (o
< ol
->n
) GamelogGRFRemove(ol
->grf
[o
++]->ident
.grfid
); // remaining GRFs were removed ...
695 while (n
< nl
->n
) GamelogGRFAdd (nl
->grf
[n
++]); // ... or added
702 /* Gamelog entry for GRF bugs */
704 void GamelogEntryGRFBug::Print(GamelogPrintBuffer
*buf
) {
705 this->PrependTick(buf
);
706 GrfIDMapping::Pair
*gm
= buf
->grf_names
.Find(this->grfid
);
708 default: NOT_REACHED();
709 case GBUG_VEH_LENGTH
:
710 buf
->append_fmt ("Rail vehicle changes length outside a depot: GRF ID %08X, internal ID 0x%X", BSWAP32(this->grfid
), (uint
)this->data
);
713 PrintGrfInfo(buf
, this->grfid
, NULL
, gm
!= buf
->grf_names
.End() ? gm
->second
.gc
: NULL
);
714 if (gm
== buf
->grf_names
.End()) buf
->append(" (inconsistency: never added)");
718 * Logs triggered GRF bug.
719 * @param grfid ID of problematic GRF
720 * @param bug type of bug, @see enum GRFBugs
721 * @param data additional data
723 static inline void GamelogGRFBug(uint32 grfid
, byte bug
, uint64 data
)
725 _gamelog
.append(new GamelogEntryGRFBug(grfid
, bug
, data
));
729 * Logs GRF bug - rail vehicle has different length after reversing.
730 * Ensures this is logged only once for each GRF and engine type
731 * This check takes some time, but it is called pretty seldom, so it
732 * doesn't matter that much (ideally it shouldn't be called at all).
733 * @param grfid the broken NewGRF
734 * @param internal_id the internal ID of whatever's broken in the NewGRF
735 * @return true iff a unique record was done
737 bool GamelogGRFBugReverse(uint32 grfid
, uint16 internal_id
)
739 for (Gamelog::const_iterator entry
= _gamelog
.begin(); entry
!= _gamelog
.end(); entry
++) {
740 if ((*entry
)->type
== GLOG_GRFBUG
) {
741 const GamelogEntryGRFBug
*bug
= (GamelogEntryGRFBug
*)entry
->get();
742 if (bug
->bug
== GBUG_VEH_LENGTH
&& bug
->grfid
== grfid
&& bug
->data
== internal_id
) {
748 GamelogGRFBug(grfid
, GBUG_VEH_LENGTH
, internal_id
);