Codechange: Use cached town, station, industry names for list window sorting
[openttd-github.git] / src / gamelog.cpp
blobb325dc7fd355fb5d83700ab780221f9dc361564e
1 /*
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/>.
6 */
8 /** @file gamelog.cpp Definition of functions used for logging of important changes in the game */
10 #include "stdafx.h"
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"
16 #include "debug.h"
17 #include "date_func.h"
18 #include "rev.h"
20 #include <stdarg.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
40 /**
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;
64 /**
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;
75 /**
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 /**
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);
101 free(la->change);
104 free(gamelog_action);
108 * Resets and frees all memory allocated - used before loading or starting a new game
110 void GamelogReset()
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)
131 char txt[40];
133 if (md5sum != nullptr) {
134 md5sumToString(txt, lastof(txt), md5sum);
135 buf += seprintf(buf, last, "GRF ID %08X, checksum %s", BSWAP32(grfid), txt);
136 } else {
137 buf += seprintf(buf, last, "GRF ID %08X", BSWAP32(grfid));
140 if (gc != nullptr) {
141 buf += seprintf(buf, last, ", filename: %s (md5sum matches)", gc->filename);
142 } else {
143 gc = FindGRFConfig(grfid, FGCM_ANY);
144 if (gc != nullptr) {
145 buf += seprintf(buf, last, ", filename: %s (matches GRFID only)", gc->filename);
146 } else {
147 buf += seprintf(buf, last, ", unknown GRF");
150 return buf;
154 /** Text messages for various logged actions */
155 static const char * const la_text[] = {
156 "new game started",
157 "game loaded",
158 "GRF config changed",
159 "cheat was used",
160 "settings changed",
161 "GRF bug triggered",
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.
174 struct GRFPresence{
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)
189 char buffer[1024];
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]);
200 proc(buffer);
202 const LoggedChange *lcend = &la->change[la->changes];
204 for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
205 char *buf = buffer;
207 switch (lc->ct) {
208 default: NOT_REACHED();
209 case GLCT_MODE:
210 buf += seprintf(buf, lastof(buffer), "New game mode: %u landscape: %u",
211 (uint)lc->mode.mode, (uint)lc->mode.landscape);
212 break;
214 case GLCT_REVISION:
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;
221 default: break;
224 buf += seprintf(buf, lastof(buffer), "modified, _openttd_newgrf_version = 0x%08x", lc->revision.newgrf);
225 break;
227 case GLCT_OLDVER:
228 buf += seprintf(buf, lastof(buffer), "Conversion from ");
229 switch (lc->oldver.type) {
230 default: NOT_REACHED();
231 case SGT_OTTD:
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));
234 break;
236 case SGT_TTO:
237 buf += seprintf(buf, lastof(buffer), "TTO savegame");
238 break;
240 case SGT_TTD:
241 buf += seprintf(buf, lastof(buffer), "TTD savegame");
242 break;
244 case SGT_TTDP1:
245 case SGT_TTDP2:
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));
253 break;
255 break;
257 case GLCT_SETTING:
258 buf += seprintf(buf, lastof(buffer), "Setting changed: %s : %d -> %d", lc->setting.name, lc->setting.oldval, lc->setting.newval);
259 break;
261 case GLCT_GRFADD: {
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;
268 break;
271 case GLCT_GRFREM: {
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!");
277 } else {
278 if (la->at == GLAT_LOAD) {
279 /* Missing grfs on load are not removed from the configuration */
280 gm->second.was_missing = true;
281 } else {
282 grf_names.Erase(gm);
285 break;
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;
294 break;
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!");
302 break;
305 case GLCT_GRFMOVE: {
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!");
311 break;
314 case GLCT_GRFBUG: {
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);
320 break;
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!");
324 break;
327 case GLCT_EMERGENCY:
328 break;
331 proc(buffer);
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++];
394 lc->ct = ct;
396 return lc;
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);
409 GamelogStopAction();
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)
450 void GamelogMode()
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
464 void GamelogOldver()
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) {
513 GamelogRevision();
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) {
572 return false;
577 GamelogStartAction(GLAT_GRFBUG);
578 GamelogGRFBug(grfid, GBUG_VEH_LENGTH, internal_id);
579 GamelogStopAction();
581 return true;
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) {
681 GamelogGRFAdd(newg);
685 /** List of GRFs using array of pointers instead of linked list */
686 struct GRFList {
687 uint n;
688 const GRFConfig *grf[];
692 * Generates GRFList
693 * @param grfc head of GRF linked list
695 static GRFList *GenerateGRFList(const GRFConfig *grfc)
697 uint n = 0;
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*));
704 list->n = 0;
705 for (const GRFConfig *g = grfc; g != nullptr; g = g->next) {
706 if (IsLoggableGrfConfig(g)) list->grf[list->n++] = g;
709 return list;
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);
722 uint o = 0, n = 0;
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) {
729 uint oi, ni;
730 for (oi = 0; oi < ol->n; oi++) {
731 if (ol->grf[oi]->ident.grfid == nl->grf[n]->ident.grfid) break;
733 if (oi < o) {
734 /* GRF was moved, this change has been logged already */
735 n++;
736 continue;
738 if (oi == ol->n) {
739 /* GRF couldn't be found in the OLD list, GRF was ADDED */
740 GamelogGRFAdd(nl->grf[n++]);
741 continue;
743 for (ni = 0; ni < nl->n; ni++) {
744 if (nl->grf[ni]->ident.grfid == ol->grf[o]->ident.grfid) break;
746 if (ni < n) {
747 /* GRF was moved, this change has been logged already */
748 o++;
749 continue;
751 if (ni == nl->n) {
752 /* GRF couldn't be found in the NEW list, GRF was REMOVED */
753 GamelogGRFRemove(ol->grf[o++]->ident.grfid);
754 continue;
757 /* o < oi < ol->n
758 * n < ni < nl->n */
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);
768 } else {
769 GamelogGRFMove(nl->grf[n++]->ident.grfid, -(int)oi);
771 } else {
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);
781 o++;
782 n++;
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
789 free(ol);
790 free(nl);
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++) {
807 switch (lc->ct) {
808 default: break;
810 case GLCT_REVISION:
811 *last_ottd_rev = lc->revision.newgrf;
812 *ever_modified = max(*ever_modified, lc->revision.modified);
813 break;
815 case GLCT_GRFREM:
816 *removed_newgrfs = true;
817 break;