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_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
->cbegin(); entry
!= gamelog
->cend(); 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 class GamelogPrintBuffer
{
109 static const uint LENGTH
= 1024; ///< length of buffer for one line of text
111 char buffer
[LENGTH
]; ///< output buffer
112 uint offset
; ///< offset in buffer
115 GrfIDMapping grf_names
; ///< keep track of this so that inconsistencies can be detected
116 bool in_load
; ///< currently printing between GLOG_LOAD and GLOG_LOADED
122 void append(const char *s
, ...) WARN_FORMAT(2, 3) {
123 if (this->offset
>= this->LENGTH
) return;
128 this->offset
+= vsnprintf(this->buffer
+ this->offset
, this->LENGTH
- this->offset
, s
, va
);
132 void dump(GamelogPrintProc
*proc
) {
138 * Prints GRF ID, checksum and filename if found
139 * @param buf The buffer to write to
140 * @param grfid GRF ID
141 * @param md5sum array of md5sum to print, if known
142 * @param gc GrfConfig, if known
144 static void PrintGrfInfo(GamelogPrintBuffer
*buf
, uint grfid
, const uint8
*md5sum
, const GRFConfig
*gc
)
148 if (md5sum
!= NULL
) {
149 md5sumToString(txt
, lastof(txt
), md5sum
);
150 buf
->append("GRF ID %08X, checksum %s", BSWAP32(grfid
), txt
);
152 buf
->append("GRF ID %08X", BSWAP32(grfid
));
156 buf
->append(", filename: %s (md5sum matches)", gc
->filename
);
158 gc
= FindGRFConfig(grfid
, FGCM_ANY
);
160 buf
->append(", filename: %s (matches GRFID only)", gc
->filename
);
162 buf
->append(", unknown GRF");
168 * Prints active gamelog
169 * @param proc the procedure to draw with
171 void GamelogPrint(GamelogPrintProc
*proc
)
173 GamelogPrintBuffer buf
;
175 proc("---- gamelog start ----");
177 for (Gamelog::const_iterator entry
= _gamelog
.cbegin(); entry
!= _gamelog
.cend(); entry
++) {
179 (*entry
)->Print(&buf
);
183 proc("---- gamelog end ----");
187 static void GamelogPrintConsoleProc(const char *s
)
189 IConsolePrint(CC_WARNING
, s
);
192 /** Print the gamelog data to the console. */
193 void GamelogPrintConsole()
195 GamelogPrint(&GamelogPrintConsoleProc
);
198 static int _gamelog_print_level
= 0; ///< gamelog debug level we need to print stuff
200 static void GamelogPrintDebugProc(const char *s
)
202 DEBUG(gamelog
, _gamelog_print_level
, "%s", s
);
206 * Prints gamelog to debug output. Code is executed even when
207 * there will be no output. It is called very seldom, so it
208 * doesn't matter that much. At least it gives more uniform code...
209 * @param level debug level we need to print stuff
211 void GamelogPrintDebug(int level
)
213 _gamelog_print_level
= level
;
214 GamelogPrint(&GamelogPrintDebugProc
);
218 /* Gamelog entry types */
221 /* Gamelog entry base class for entries with tick information. */
223 void GamelogEntryTimed::PrependTick(GamelogPrintBuffer
*buf
) {
224 buf
->append("Tick %u: ", (uint
)this->tick
);
228 /* Gamelog entry for game start */
230 void GamelogEntryStart::Print(GamelogPrintBuffer
*buf
) {
231 this->PrependTick(buf
);
232 buf
->append("New game");
235 void GamelogAddStart()
237 _gamelog
.append(new GamelogEntryStart());
241 /* Gamelog entry after game start */
243 void GamelogEntryStarted::Print(GamelogPrintBuffer
*buf
) {
244 buf
->append(" Game started");
247 void GamelogAddStarted()
249 _gamelog
.append(new GamelogEntryStarted());
253 /** Gamelog entry for game load */
254 void GamelogEntryLoad::Print(GamelogPrintBuffer
*buf
) {
255 this->PrependTick(buf
);
256 buf
->append("Load game");
260 void GamelogAddLoad()
262 _gamelog
.append(new GamelogEntryLoad());
266 /* Gamelog entry after game load */
268 void GamelogEntryLoaded::Print(GamelogPrintBuffer
*buf
) {
269 buf
->append(" Game loaded");
270 buf
->in_load
= false;
273 void GamelogAddLoaded()
275 _gamelog
.append(new GamelogEntryLoaded());
279 /* Gamelog entry for mode switch between scenario editor and game */
281 void GamelogEntryMode::Print(GamelogPrintBuffer
*buf
) {
282 buf
->append(" New game mode %u, landscape %u",
283 (uint
)this->mode
, (uint
)this->landscape
);
287 * Logs a change in game mode (scenario editor or game)
289 void GamelogAddMode()
291 _gamelog
.append(new GamelogEntryMode());
295 * Finds last stored game mode or landscape.
296 * Any change is logged
298 void GamelogTestMode()
300 const GamelogEntryMode
*mode
= NULL
;
302 for (Gamelog::const_iterator entry
= _gamelog
.cbegin(); entry
!= _gamelog
.cend(); entry
++) {
303 if ((*entry
)->type
== GLOG_MODE
) {
304 mode
= (GamelogEntryMode
*)entry
->get();
308 if (mode
== NULL
|| mode
->mode
!= _game_mode
|| mode
->landscape
!= _settings_game
.game_creation
.landscape
) GamelogAddMode();
312 /* Gamelog entry for game revision string */
314 void GamelogEntryRevision::Print(GamelogPrintBuffer
*buf
) {
315 buf
->append(" Revision text changed to %s, savegame version %u, %smodified, newgrf version 0x%08x",
316 this->text
, this->slver
,
317 (this->modified
== 0) ? "not " : (this->modified
== 1) ? "maybe " : "",
322 * Logs a change in game revision
324 void GamelogAddRevision()
326 _gamelog
.append(new GamelogEntryRevision());
330 * Finds out if current revision is different than last revision stored in the savegame.
331 * Appends a revision entry when the revision string changed
333 void GamelogTestRevision()
335 const GamelogEntryRevision
*rev
= NULL
;
337 for (Gamelog::const_iterator entry
= _gamelog
.cbegin(); entry
!= _gamelog
.cend(); entry
++) {
338 if ((*entry
)->type
== GLOG_REVISION
) {
339 rev
= (GamelogEntryRevision
*)entry
->get();
343 if (rev
== NULL
|| strcmp(rev
->text
, _openttd_revision
) != 0 ||
344 rev
->modified
!= _openttd_revision_modified
||
345 rev
->newgrf
!= _openttd_newgrf_version
) {
346 GamelogAddRevision();
351 /* Gamelog entry for game revision string (legacy) */
353 void GamelogEntryLegacyRev::Print(GamelogPrintBuffer
*buf
) {
354 buf
->append(" Revision text changed to %s (legacy), savegame version %u, %smodified, newgrf version 0x%08x",
355 this->text
, this->slver
,
356 (this->modified
== 0) ? "not " : (this->modified
== 1) ? "maybe " : "",
361 /* Gamelog entry for savegames without log */
363 void GamelogEntryOldVer::Print(GamelogPrintBuffer
*buf
) {
364 switch (this->type
) {
366 buf
->append(" Conversion from TTO savegame");
370 buf
->append(" Conversion from TTD savegame");
375 buf
->append(" Conversion from %s TTDP savegame version %u.%u.%u.%u",
376 (this->type
== SGT_TTDP1
) ? "old" : "new",
377 GB(this->version
, 24, 8),
378 GB(this->version
, 20, 4),
379 GB(this->version
, 16, 4),
380 GB(this->version
, 0, 16));
383 default: NOT_REACHED();
385 buf
->append(" Conversion from OTTD savegame without gamelog, version %u, %u",
386 GB(this->version
, 8, 16), GB(this->version
, 0, 8));
392 * Logs loading from savegame without gamelog
394 void GamelogOldver(const SavegameTypeVersion
*stv
)
396 _gamelog
.append(new GamelogEntryOldVer(stv
));
400 /* Gamelog entry for emergency savegames. */
402 void GamelogEntryEmergency::Print(GamelogPrintBuffer
*buf
) {
403 this->PrependTick(buf
);
404 buf
->append("Emergency savegame");
408 * Logs an emergency savegame
410 void GamelogEmergency()
412 _gamelog
.append(new GamelogEntryEmergency());
416 * Finds out if current game is a loaded emergency savegame.
418 bool GamelogTestEmergency()
420 for (Gamelog::const_iterator entry
= _gamelog
.cbegin(); entry
!= _gamelog
.cend(); entry
++) {
421 if ((*entry
)->type
== GLOG_EMERGENCY
) return true;
428 /* Gamelog entry for settings change */
430 void GamelogEntrySetting::Print(GamelogPrintBuffer
*buf
) {
431 this->PrependTick(buf
);
432 buf
->append("Setting '%s' changed from %d to %d",
433 this->name
, this->oldval
, this->newval
);
437 * Logs change in game settings. Only non-networksafe settings are logged
438 * @param name setting name
439 * @param oldval old setting value
440 * @param newval new setting value
442 void GamelogSetting(const char *name
, int32 oldval
, int32 newval
)
444 _gamelog
.append(new GamelogEntrySetting(name
, oldval
, newval
));
448 /* Gamelog entry for cheating */
450 void GamelogEntryCheat::Print(GamelogPrintBuffer
*buf
) {
451 this->PrependTick(buf
);
452 buf
->append("Cheat used");
456 /* Gamelog entry for GRF config change begin */
458 void GamelogEntryGRFBegin::Print(GamelogPrintBuffer
*buf
) {
459 this->PrependTick(buf
);
460 buf
->append("GRF config change");
464 * Log GRF config change begin
466 void GamelogGRFBegin()
468 _gamelog
.append(new GamelogEntryGRFBegin());
472 /* Gamelog entry for GRF config change end */
474 void GamelogEntryGRFEnd::Print(GamelogPrintBuffer
*buf
) {
475 buf
->append(" GRF config change end");
479 * Log GRF config change end
483 _gamelog
.append(new GamelogEntryGRFEnd());
488 * Decides if GRF should be logged
489 * @param g grf to determine
490 * @return true iff GRF is not static and is loaded
492 static inline bool IsLoggableGrfConfig(const GRFConfig
*g
)
494 return !HasBit(g
->flags
, GCF_STATIC
) && g
->status
!= GCS_NOT_FOUND
;
498 /* Gamelog entry for GRF addition */
500 void GamelogEntryGRFAdd::Print(GamelogPrintBuffer
*buf
) {
501 const GRFConfig
*gc
= FindGRFConfig(this->grf
.grfid
, FGCM_EXACT
, this->grf
.md5sum
);
502 buf
->append(" Added NewGRF: ");
503 PrintGrfInfo(buf
, this->grf
.grfid
, this->grf
.md5sum
, gc
);
504 GrfIDMapping::Pair
*gm
= buf
->grf_names
.Find(this->grf
.grfid
);
505 if (gm
!= buf
->grf_names
.End() && !gm
->second
.was_missing
) buf
->append(" (inconsistency: already added)");
506 buf
->grf_names
[this->grf
.grfid
] = gc
;
510 * Logs adding of a GRF
511 * @param newg added GRF
513 void GamelogGRFAdd(const GRFConfig
*newg
)
515 if (!IsLoggableGrfConfig(newg
)) return;
517 _gamelog
.append(new GamelogEntryGRFAdd(&newg
->ident
));
521 * Logs adding of list of GRFs.
522 * Useful when old savegame is loaded or when new game is started
523 * @param newg head of GRF linked list
525 void GamelogGRFAddList(const GRFConfig
*newg
)
527 for (; newg
!= NULL
; newg
= newg
->next
) {
533 /* Gamelog entry for GRF removal */
535 void GamelogEntryGRFRemove::Print(GamelogPrintBuffer
*buf
) {
536 GrfIDMapping::Pair
*gm
= buf
->grf_names
.Find(this->grfid
);
537 buf
->append(buf
->in_load
? " Missing NewGRF: " : " Removed NewGRF: ");
538 PrintGrfInfo(buf
, this->grfid
, NULL
, gm
!= buf
->grf_names
.End() ? gm
->second
.gc
: NULL
);
539 if (gm
== buf
->grf_names
.End()) {
540 buf
->append(" (inconsistency: never added)");
541 } else if (buf
->in_load
) {
542 /* Missing grfs on load are not removed from the configuration */
543 gm
->second
.was_missing
= true;
545 buf
->grf_names
.Erase(gm
);
550 * Logs removal of a GRF
551 * @param grfid ID of removed GRF
553 void GamelogGRFRemove(uint32 grfid
)
555 _gamelog
.append(new GamelogEntryGRFRemove(grfid
));
559 /* Gamelog entry for compatible GRF load */
561 void GamelogEntryGRFCompat::Print(GamelogPrintBuffer
*buf
) {
562 const GRFConfig
*gc
= FindGRFConfig(this->grf
.grfid
, FGCM_EXACT
, this->grf
.md5sum
);
563 buf
->append(" Compatible NewGRF loaded: ");
564 PrintGrfInfo(buf
, this->grf
.grfid
, this->grf
.md5sum
, gc
);
565 if (!buf
->grf_names
.Contains(this->grf
.grfid
)) buf
->append(" (inconsistency: never added)");
566 buf
->grf_names
[this->grf
.grfid
] = gc
;
570 * Logs loading compatible GRF
571 * (the same ID, but different MD5 hash)
572 * @param newg new (updated) GRF
574 void GamelogGRFCompatible(const GRFIdentifier
*newg
)
576 _gamelog
.append(new GamelogEntryGRFCompat(newg
));
580 /* Gamelog entry for GRF parameter changes */
582 void GamelogEntryGRFParam::Print(GamelogPrintBuffer
*buf
) {
583 GrfIDMapping::Pair
*gm
= buf
->grf_names
.Find(this->grfid
);
584 buf
->append(" GRF parameter changed: ");
585 PrintGrfInfo(buf
, this->grfid
, NULL
, gm
!= buf
->grf_names
.End() ? gm
->second
.gc
: NULL
);
586 if (gm
== buf
->grf_names
.End()) buf
->append(" (inconsistency: never added)");
590 * Logs change in GRF parameters.
591 * Details about parameters changed are not stored
592 * @param grfid ID of GRF to store
594 static void GamelogGRFParameters(uint32 grfid
)
596 _gamelog
.append(new GamelogEntryGRFParam(grfid
));
600 /* Gamelog entry for GRF order change */
602 void GamelogEntryGRFMove::Print(GamelogPrintBuffer
*buf
) {
603 GrfIDMapping::Pair
*gm
= buf
->grf_names
.Find(this->grfid
);
604 buf
->append("GRF order changed: %08X moved %d places %s, ",
605 BSWAP32(this->grfid
), abs(this->offset
), this->offset
>= 0 ? "down" : "up");
606 PrintGrfInfo(buf
, this->grfid
, NULL
, gm
!= buf
->grf_names
.End() ? gm
->second
.gc
: NULL
);
607 if (gm
== buf
->grf_names
.End()) buf
->append(" (inconsistency: never added)");
611 * Logs changing GRF order
612 * @param grfid GRF that is moved
613 * @param offset how far it is moved, positive = moved down
615 static void GamelogGRFMove(uint32 grfid
, int32 offset
)
617 _gamelog
.append(new GamelogEntryGRFMove(grfid
, offset
));
621 /** List of GRFs using array of pointers instead of linked list */
624 const GRFConfig
*grf
[];
629 * @param grfc head of GRF linked list
631 static GRFList
*GenerateGRFList(const GRFConfig
*grfc
)
634 for (const GRFConfig
*g
= grfc
; g
!= NULL
; g
= g
->next
) {
635 if (IsLoggableGrfConfig(g
)) n
++;
638 GRFList
*list
= (GRFList
*)MallocT
<byte
>(sizeof(GRFList
) + n
* sizeof(GRFConfig
*));
641 for (const GRFConfig
*g
= grfc
; g
!= NULL
; g
= g
->next
) {
642 if (IsLoggableGrfConfig(g
)) list
->grf
[list
->n
++] = g
;
649 * Compares two NewGRF lists and logs any change
650 * @param oldc original GRF list
651 * @param newc new GRF list
653 void GamelogGRFUpdate(const GRFConfig
*oldc
, const GRFConfig
*newc
)
655 GRFList
*ol
= GenerateGRFList(oldc
);
656 GRFList
*nl
= GenerateGRFList(newc
);
660 while (o
< ol
->n
&& n
< nl
->n
) {
661 const GRFConfig
*og
= ol
->grf
[o
];
662 const GRFConfig
*ng
= nl
->grf
[n
];
664 if (og
->ident
.grfid
!= ng
->ident
.grfid
) {
666 for (oi
= 0; oi
< ol
->n
; oi
++) {
667 if (ol
->grf
[oi
]->ident
.grfid
== nl
->grf
[n
]->ident
.grfid
) break;
670 /* GRF was moved, this change has been logged already */
675 /* GRF couldn't be found in the OLD list, GRF was ADDED */
676 GamelogGRFAdd(nl
->grf
[n
++]);
679 for (ni
= 0; ni
< nl
->n
; ni
++) {
680 if (nl
->grf
[ni
]->ident
.grfid
== ol
->grf
[o
]->ident
.grfid
) break;
683 /* GRF was moved, this change has been logged already */
688 /* GRF couldn't be found in the NEW list, GRF was REMOVED */
689 GamelogGRFRemove(ol
->grf
[o
++]->ident
.grfid
);
695 assert(ni
> n
&& ni
< nl
->n
);
696 assert(oi
> o
&& oi
< ol
->n
);
698 ni
-= n
; // number of GRFs it was moved downwards
699 oi
-= o
; // number of GRFs it was moved upwards
701 if (ni
>= oi
) { // prefer the one that is moved further
702 /* GRF was moved down */
703 GamelogGRFMove(ol
->grf
[o
++]->ident
.grfid
, ni
);
705 GamelogGRFMove(nl
->grf
[n
++]->ident
.grfid
, -(int)oi
);
708 if (memcmp(og
->ident
.md5sum
, ng
->ident
.md5sum
, sizeof(og
->ident
.md5sum
)) != 0) {
709 /* md5sum changed, probably loading 'compatible' GRF */
710 GamelogGRFCompatible(&nl
->grf
[n
]->ident
);
713 if (og
->num_params
!= ng
->num_params
|| memcmp(og
->param
, ng
->param
, og
->num_params
* sizeof(og
->param
[0])) != 0) {
714 GamelogGRFParameters(ol
->grf
[o
]->ident
.grfid
);
722 while (o
< ol
->n
) GamelogGRFRemove(ol
->grf
[o
++]->ident
.grfid
); // remaining GRFs were removed ...
723 while (n
< nl
->n
) GamelogGRFAdd (nl
->grf
[n
++]); // ... or added
730 /* Gamelog entry for GRF bugs */
732 void GamelogEntryGRFBug::Print(GamelogPrintBuffer
*buf
) {
733 this->PrependTick(buf
);
734 GrfIDMapping::Pair
*gm
= buf
->grf_names
.Find(this->grfid
);
736 default: NOT_REACHED();
737 case GBUG_VEH_LENGTH
:
738 buf
->append("Rail vehicle changes length outside a depot: GRF ID %08X, internal ID 0x%X", BSWAP32(this->grfid
), (uint
)this->data
);
741 PrintGrfInfo(buf
, this->grfid
, NULL
, gm
!= buf
->grf_names
.End() ? gm
->second
.gc
: NULL
);
742 if (gm
== buf
->grf_names
.End()) buf
->append(" (inconsistency: never added)");
746 * Logs triggered GRF bug.
747 * @param grfid ID of problematic GRF
748 * @param bug type of bug, @see enum GRFBugs
749 * @param data additional data
751 static inline void GamelogGRFBug(uint32 grfid
, byte bug
, uint64 data
)
753 _gamelog
.append(new GamelogEntryGRFBug(grfid
, bug
, data
));
757 * Logs GRF bug - rail vehicle has different length after reversing.
758 * Ensures this is logged only once for each GRF and engine type
759 * This check takes some time, but it is called pretty seldom, so it
760 * doesn't matter that much (ideally it shouldn't be called at all).
761 * @param grfid the broken NewGRF
762 * @param internal_id the internal ID of whatever's broken in the NewGRF
763 * @return true iff a unique record was done
765 bool GamelogGRFBugReverse(uint32 grfid
, uint16 internal_id
)
767 for (Gamelog::const_iterator entry
= _gamelog
.cbegin(); entry
!= _gamelog
.end(); entry
++) {
768 if ((*entry
)->type
== GLOG_GRFBUG
) {
769 const GamelogEntryGRFBug
*bug
= (GamelogEntryGRFBug
*)entry
->get();
770 if (bug
->bug
== GBUG_VEH_LENGTH
&& bug
->grfid
== grfid
&& bug
->data
== internal_id
) {
776 GamelogGRFBug(grfid
, GBUG_VEH_LENGTH
, internal_id
);