00001
00002
00003
00004
00005
00006
00007
00008
00009
00012 #include "stdafx.h"
00013 #include "saveload/saveload.h"
00014 #include "string_func.h"
00015 #include "settings_type.h"
00016 #include "gamelog_internal.h"
00017 #include "console_func.h"
00018 #include "debug.h"
00019 #include "date_func.h"
00020 #include "rev.h"
00021
00022 #include <stdarg.h>
00023
00024 extern const uint16 SAVEGAME_VERSION;
00025
00026 extern SavegameType _savegame_type;
00027
00028 extern uint32 _ttdp_version;
00029 extern uint16 _sl_version;
00030 extern byte _sl_minor_version;
00031
00032
00033 static GamelogActionType _gamelog_action_type = GLAT_NONE;
00034
00035 LoggedAction *_gamelog_action = NULL;
00036 uint _gamelog_actions = 0;
00037 static LoggedAction *_current_action = NULL;
00038
00039
00045 void GamelogStartAction(GamelogActionType at)
00046 {
00047 assert(_gamelog_action_type == GLAT_NONE);
00048 _gamelog_action_type = at;
00049 }
00050
00054 void GamelogStopAction()
00055 {
00056 assert(_gamelog_action_type != GLAT_NONE);
00057
00058 bool print = _current_action != NULL;
00059
00060 _current_action = NULL;
00061 _gamelog_action_type = GLAT_NONE;
00062
00063 if (print) GamelogPrintDebug(5);
00064 }
00065
00069 void GamelogFree(LoggedAction *gamelog_action, uint gamelog_actions)
00070 {
00071 for (uint i = 0; i < gamelog_actions; i++) {
00072 const LoggedAction *la = &gamelog_action[i];
00073 for (uint j = 0; j < la->changes; j++) {
00074 const LoggedChange *lc = &la->change[j];
00075 if (lc->ct == GLCT_SETTING) free(lc->setting.name);
00076 }
00077 free(la->change);
00078 }
00079
00080 free(gamelog_action);
00081 }
00082
00086 void GamelogReset()
00087 {
00088 assert(_gamelog_action_type == GLAT_NONE);
00089 GamelogFree(_gamelog_action, _gamelog_actions);
00090
00091 _gamelog_action = NULL;
00092 _gamelog_actions = 0;
00093 _current_action = NULL;
00094 }
00095
00096 static const uint GAMELOG_BUF_LEN = 1024;
00097
00098 static uint _dbgofs = 0;
00099
00100 static void AddDebugText(char *buf, const char *s, ...) WARN_FORMAT(2, 3);
00101
00102 static void AddDebugText(char *buf, const char *s, ...)
00103 {
00104 if (GAMELOG_BUF_LEN <= _dbgofs) return;
00105
00106 va_list va;
00107
00108 va_start(va, s);
00109 _dbgofs += vsnprintf(buf + _dbgofs, GAMELOG_BUF_LEN - _dbgofs, s, va);
00110 va_end(va);
00111 }
00112
00113
00121 static void PrintGrfInfo(char *buf, uint grfid, const uint8 *md5sum, const GRFConfig *gc)
00122 {
00123 char txt[40];
00124
00125 if (md5sum != NULL) {
00126 md5sumToString(txt, lastof(txt), md5sum);
00127 AddDebugText(buf, "GRF ID %08X, checksum %s", BSWAP32(grfid), txt);
00128 } else {
00129 AddDebugText(buf, "GRF ID %08X", BSWAP32(grfid));
00130 }
00131
00132 if (gc != NULL) {
00133 AddDebugText(buf, ", filename: %s (md5sum matches)", gc->filename);
00134 } else {
00135 gc = FindGRFConfig(grfid, FGCM_ANY);
00136 if (gc != NULL) {
00137 AddDebugText(buf, ", filename: %s (matches GRFID only)", gc->filename);
00138 } else {
00139 AddDebugText(buf, ", unknown GRF");
00140 }
00141 }
00142 return;
00143 }
00144
00145
00147 static const char * const la_text[] = {
00148 "new game started",
00149 "game loaded",
00150 "GRF config changed",
00151 "cheat was used",
00152 "settings changed",
00153 "GRF bug triggered",
00154 "emergency savegame",
00155 };
00156
00157 assert_compile(lengthof(la_text) == GLAT_END);
00158
00166 struct GRFPresence{
00167 const GRFConfig *gc;
00168 bool was_missing;
00169
00170 GRFPresence(const GRFConfig *gc) : gc(gc), was_missing(false) {}
00171 };
00172 typedef SmallMap<uint32, GRFPresence> GrfIDMapping;
00173
00178 void GamelogPrint(GamelogPrintProc *proc)
00179 {
00180 char buf[GAMELOG_BUF_LEN];
00181 GrfIDMapping grf_names;
00182
00183 proc("---- gamelog start ----");
00184
00185 const LoggedAction *laend = &_gamelog_action[_gamelog_actions];
00186
00187 for (const LoggedAction *la = _gamelog_action; la != laend; la++) {
00188 assert((uint)la->at < GLAT_END);
00189
00190 snprintf(buf, GAMELOG_BUF_LEN, "Tick %u: %s", (uint)la->tick, la_text[(uint)la->at]);
00191 proc(buf);
00192
00193 const LoggedChange *lcend = &la->change[la->changes];
00194
00195 for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
00196 _dbgofs = 0;
00197 AddDebugText(buf, " ");
00198
00199 switch (lc->ct) {
00200 default: NOT_REACHED();
00201 case GLCT_MODE:
00202 AddDebugText(buf, "New game mode: %u landscape: %u",
00203 (uint)lc->mode.mode, (uint)lc->mode.landscape);
00204 break;
00205
00206 case GLCT_REVISION:
00207 AddDebugText(buf, "Revision text changed to %s, savegame version %u, ",
00208 lc->revision.text, lc->revision.slver);
00209
00210 switch (lc->revision.modified) {
00211 case 0: AddDebugText(buf, "not "); break;
00212 case 1: AddDebugText(buf, "maybe "); break;
00213 default: break;
00214 }
00215
00216 AddDebugText(buf, "modified, _openttd_newgrf_version = 0x%08x", lc->revision.newgrf);
00217 break;
00218
00219 case GLCT_OLDVER:
00220 AddDebugText(buf, "Conversion from ");
00221 switch (lc->oldver.type) {
00222 default: NOT_REACHED();
00223 case SGT_OTTD:
00224 AddDebugText(buf, "OTTD savegame without gamelog: version %u, %u",
00225 GB(lc->oldver.version, 8, 16), GB(lc->oldver.version, 0, 8));
00226 break;
00227
00228 case SGT_TTO:
00229 AddDebugText(buf, "TTO savegame");
00230 break;
00231
00232 case SGT_TTD:
00233 AddDebugText(buf, "TTD savegame");
00234 break;
00235
00236 case SGT_TTDP1:
00237 case SGT_TTDP2:
00238 AddDebugText(buf, "TTDP savegame, %s format",
00239 lc->oldver.type == SGT_TTDP1 ? "old" : "new");
00240 if (lc->oldver.version != 0) {
00241 AddDebugText(buf, ", TTDP version %u.%u.%u.%u",
00242 GB(lc->oldver.version, 24, 8), GB(lc->oldver.version, 20, 4),
00243 GB(lc->oldver.version, 16, 4), GB(lc->oldver.version, 0, 16));
00244 }
00245 break;
00246 }
00247 break;
00248
00249 case GLCT_SETTING:
00250 AddDebugText(buf, "Setting changed: %s : %d -> %d", lc->setting.name, lc->setting.oldval, lc->setting.newval);
00251 break;
00252
00253 case GLCT_GRFADD: {
00254 const GRFConfig *gc = FindGRFConfig(lc->grfadd.grfid, FGCM_EXACT, lc->grfadd.md5sum);
00255 AddDebugText(buf, "Added NewGRF: ");
00256 PrintGrfInfo(buf, lc->grfadd.grfid, lc->grfadd.md5sum, gc);
00257 GrfIDMapping::Pair *gm = grf_names.Find(lc->grfrem.grfid);
00258 if (gm != grf_names.End() && !gm->second.was_missing) AddDebugText(buf, ". Gamelog inconsistency: GrfID was already added!");
00259 grf_names[lc->grfadd.grfid] = gc;
00260 break;
00261 }
00262
00263 case GLCT_GRFREM: {
00264 GrfIDMapping::Pair *gm = grf_names.Find(lc->grfrem.grfid);
00265 AddDebugText(buf, la->at == GLAT_LOAD ? "Missing NewGRF: " : "Removed NewGRF: ");
00266 PrintGrfInfo(buf, lc->grfrem.grfid, NULL, gm != grf_names.End() ? gm->second.gc : NULL);
00267 if (gm == grf_names.End()) {
00268 AddDebugText(buf, ". Gamelog inconsistency: GrfID was never added!");
00269 } else {
00270 if (la->at == GLAT_LOAD) {
00271
00272 gm->second.was_missing = true;
00273 } else {
00274 grf_names.Erase(gm);
00275 }
00276 }
00277 break;
00278 }
00279
00280 case GLCT_GRFCOMPAT: {
00281 const GRFConfig *gc = FindGRFConfig(lc->grfadd.grfid, FGCM_EXACT, lc->grfadd.md5sum);
00282 AddDebugText(buf, "Compatible NewGRF loaded: ");
00283 PrintGrfInfo(buf, lc->grfcompat.grfid, lc->grfcompat.md5sum, gc);
00284 if (!grf_names.Contains(lc->grfcompat.grfid)) AddDebugText(buf, ". Gamelog inconsistency: GrfID was never added!");
00285 grf_names[lc->grfcompat.grfid] = gc;
00286 break;
00287 }
00288
00289 case GLCT_GRFPARAM: {
00290 GrfIDMapping::Pair *gm = grf_names.Find(lc->grfrem.grfid);
00291 AddDebugText(buf, "GRF parameter changed: ");
00292 PrintGrfInfo(buf, lc->grfparam.grfid, NULL, gm != grf_names.End() ? gm->second.gc : NULL);
00293 if (gm == grf_names.End()) AddDebugText(buf, ". Gamelog inconsistency: GrfID was never added!");
00294 break;
00295 }
00296
00297 case GLCT_GRFMOVE: {
00298 GrfIDMapping::Pair *gm = grf_names.Find(lc->grfrem.grfid);
00299 AddDebugText(buf, "GRF order changed: %08X moved %d places %s",
00300 BSWAP32(lc->grfmove.grfid), abs(lc->grfmove.offset), lc->grfmove.offset >= 0 ? "down" : "up" );
00301 PrintGrfInfo(buf, lc->grfmove.grfid, NULL, gm != grf_names.End() ? gm->second.gc : NULL);
00302 if (gm == grf_names.End()) AddDebugText(buf, ". Gamelog inconsistency: GrfID was never added!");
00303 break;
00304 }
00305
00306 case GLCT_GRFBUG: {
00307 GrfIDMapping::Pair *gm = grf_names.Find(lc->grfrem.grfid);
00308 switch (lc->grfbug.bug) {
00309 default: NOT_REACHED();
00310 case GBUG_VEH_LENGTH:
00311 AddDebugText(buf, "Rail vehicle changes length outside a depot: GRF ID %08X, internal ID 0x%X", BSWAP32(lc->grfbug.grfid), (uint)lc->grfbug.data);
00312 break;
00313 }
00314 PrintGrfInfo(buf, lc->grfbug.grfid, NULL, gm != grf_names.End() ? gm->second.gc : NULL);
00315 if (gm == grf_names.End()) AddDebugText(buf, ". Gamelog inconsistency: GrfID was never added!");
00316 break;
00317 }
00318
00319 case GLCT_EMERGENCY:
00320 break;
00321 }
00322
00323 proc(buf);
00324 }
00325 }
00326
00327 proc("---- gamelog end ----");
00328 }
00329
00330
00331 static void GamelogPrintConsoleProc(const char *s)
00332 {
00333 IConsolePrint(CC_WARNING, s);
00334 }
00335
00337 void GamelogPrintConsole()
00338 {
00339 GamelogPrint(&GamelogPrintConsoleProc);
00340 }
00341
00342 static int _gamelog_print_level = 0;
00343
00344 static void GamelogPrintDebugProc(const char *s)
00345 {
00346 DEBUG(gamelog, _gamelog_print_level, "%s", s);
00347 }
00348
00349
00356 void GamelogPrintDebug(int level)
00357 {
00358 _gamelog_print_level = level;
00359 GamelogPrint(&GamelogPrintDebugProc);
00360 }
00361
00362
00369 static LoggedChange *GamelogChange(GamelogChangeType ct)
00370 {
00371 if (_current_action == NULL) {
00372 if (_gamelog_action_type == GLAT_NONE) return NULL;
00373
00374 _gamelog_action = ReallocT(_gamelog_action, _gamelog_actions + 1);
00375 _current_action = &_gamelog_action[_gamelog_actions++];
00376
00377 _current_action->at = _gamelog_action_type;
00378 _current_action->tick = _tick_counter;
00379 _current_action->change = NULL;
00380 _current_action->changes = 0;
00381 }
00382
00383 _current_action->change = ReallocT(_current_action->change, _current_action->changes + 1);
00384
00385 LoggedChange *lc = &_current_action->change[_current_action->changes++];
00386 lc->ct = ct;
00387
00388 return lc;
00389 }
00390
00391
00395 void GamelogEmergency()
00396 {
00397
00398 if (_gamelog_action_type != GLAT_NONE) GamelogStopAction();
00399 GamelogStartAction(GLAT_EMERGENCY);
00400 GamelogChange(GLCT_EMERGENCY);
00401 GamelogStopAction();
00402 }
00403
00407 bool GamelogTestEmergency()
00408 {
00409 const LoggedChange *emergency = NULL;
00410
00411 const LoggedAction *laend = &_gamelog_action[_gamelog_actions];
00412 for (const LoggedAction *la = _gamelog_action; la != laend; la++) {
00413 const LoggedChange *lcend = &la->change[la->changes];
00414 for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
00415 if (lc->ct == GLCT_EMERGENCY) emergency = lc;
00416 }
00417 }
00418
00419 return (emergency != NULL);
00420 }
00421
00425 void GamelogRevision()
00426 {
00427 assert(_gamelog_action_type == GLAT_START || _gamelog_action_type == GLAT_LOAD);
00428
00429 LoggedChange *lc = GamelogChange(GLCT_REVISION);
00430 if (lc == NULL) return;
00431
00432 memset(lc->revision.text, 0, sizeof(lc->revision.text));
00433 strecpy(lc->revision.text, _openttd_revision, lastof(lc->revision.text));
00434 lc->revision.slver = SAVEGAME_VERSION;
00435 lc->revision.modified = _openttd_revision_modified;
00436 lc->revision.newgrf = _openttd_newgrf_version;
00437 }
00438
00442 void GamelogMode()
00443 {
00444 assert(_gamelog_action_type == GLAT_START || _gamelog_action_type == GLAT_LOAD || _gamelog_action_type == GLAT_CHEAT);
00445
00446 LoggedChange *lc = GamelogChange(GLCT_MODE);
00447 if (lc == NULL) return;
00448
00449 lc->mode.mode = _game_mode;
00450 lc->mode.landscape = _settings_game.game_creation.landscape;
00451 }
00452
00456 void GamelogOldver()
00457 {
00458 assert(_gamelog_action_type == GLAT_LOAD);
00459
00460 LoggedChange *lc = GamelogChange(GLCT_OLDVER);
00461 if (lc == NULL) return;
00462
00463 lc->oldver.type = _savegame_type;
00464 lc->oldver.version = (_savegame_type == SGT_OTTD ? ((uint32)_sl_version << 8 | _sl_minor_version) : _ttdp_version);
00465 }
00466
00473 void GamelogSetting(const char *name, int32 oldval, int32 newval)
00474 {
00475 assert(_gamelog_action_type == GLAT_SETTING);
00476
00477 LoggedChange *lc = GamelogChange(GLCT_SETTING);
00478 if (lc == NULL) return;
00479
00480 lc->setting.name = strdup(name);
00481 lc->setting.oldval = oldval;
00482 lc->setting.newval = newval;
00483 }
00484
00485
00490 void GamelogTestRevision()
00491 {
00492 const LoggedChange *rev = NULL;
00493
00494 const LoggedAction *laend = &_gamelog_action[_gamelog_actions];
00495 for (const LoggedAction *la = _gamelog_action; la != laend; la++) {
00496 const LoggedChange *lcend = &la->change[la->changes];
00497 for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
00498 if (lc->ct == GLCT_REVISION) rev = lc;
00499 }
00500 }
00501
00502 if (rev == NULL || strcmp(rev->revision.text, _openttd_revision) != 0 ||
00503 rev->revision.modified != _openttd_revision_modified ||
00504 rev->revision.newgrf != _openttd_newgrf_version) {
00505 GamelogRevision();
00506 }
00507 }
00508
00513 void GamelogTestMode()
00514 {
00515 const LoggedChange *mode = NULL;
00516
00517 const LoggedAction *laend = &_gamelog_action[_gamelog_actions];
00518 for (const LoggedAction *la = _gamelog_action; la != laend; la++) {
00519 const LoggedChange *lcend = &la->change[la->changes];
00520 for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
00521 if (lc->ct == GLCT_MODE) mode = lc;
00522 }
00523 }
00524
00525 if (mode == NULL || mode->mode.mode != _game_mode || mode->mode.landscape != _settings_game.game_creation.landscape) GamelogMode();
00526 }
00527
00528
00535 static void GamelogGRFBug(uint32 grfid, byte bug, uint64 data)
00536 {
00537 assert(_gamelog_action_type == GLAT_GRFBUG);
00538
00539 LoggedChange *lc = GamelogChange(GLCT_GRFBUG);
00540 if (lc == NULL) return;
00541
00542 lc->grfbug.data = data;
00543 lc->grfbug.grfid = grfid;
00544 lc->grfbug.bug = bug;
00545 }
00546
00556 bool GamelogGRFBugReverse(uint32 grfid, uint16 internal_id)
00557 {
00558 const LoggedAction *laend = &_gamelog_action[_gamelog_actions];
00559 for (const LoggedAction *la = _gamelog_action; la != laend; la++) {
00560 const LoggedChange *lcend = &la->change[la->changes];
00561 for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
00562 if (lc->ct == GLCT_GRFBUG && lc->grfbug.grfid == grfid &&
00563 lc->grfbug.bug == GBUG_VEH_LENGTH && lc->grfbug.data == internal_id) {
00564 return false;
00565 }
00566 }
00567 }
00568
00569 GamelogStartAction(GLAT_GRFBUG);
00570 GamelogGRFBug(grfid, GBUG_VEH_LENGTH, internal_id);
00571 GamelogStopAction();
00572
00573 return true;
00574 }
00575
00576
00582 static inline bool IsLoggableGrfConfig(const GRFConfig *g)
00583 {
00584 return !HasBit(g->flags, GCF_STATIC) && g->status != GCS_NOT_FOUND;
00585 }
00586
00591 void GamelogGRFRemove(uint32 grfid)
00592 {
00593 assert(_gamelog_action_type == GLAT_LOAD || _gamelog_action_type == GLAT_GRF);
00594
00595 LoggedChange *lc = GamelogChange(GLCT_GRFREM);
00596 if (lc == NULL) return;
00597
00598 lc->grfrem.grfid = grfid;
00599 }
00600
00605 void GamelogGRFAdd(const GRFConfig *newg)
00606 {
00607 assert(_gamelog_action_type == GLAT_LOAD || _gamelog_action_type == GLAT_START || _gamelog_action_type == GLAT_GRF);
00608
00609 if (!IsLoggableGrfConfig(newg)) return;
00610
00611 LoggedChange *lc = GamelogChange(GLCT_GRFADD);
00612 if (lc == NULL) return;
00613
00614 lc->grfadd = newg->ident;
00615 }
00616
00622 void GamelogGRFCompatible(const GRFIdentifier *newg)
00623 {
00624 assert(_gamelog_action_type == GLAT_LOAD || _gamelog_action_type == GLAT_GRF);
00625
00626 LoggedChange *lc = GamelogChange(GLCT_GRFCOMPAT);
00627 if (lc == NULL) return;
00628
00629 lc->grfcompat = *newg;
00630 }
00631
00637 static void GamelogGRFMove(uint32 grfid, int32 offset)
00638 {
00639 assert(_gamelog_action_type == GLAT_GRF);
00640
00641 LoggedChange *lc = GamelogChange(GLCT_GRFMOVE);
00642 if (lc == NULL) return;
00643
00644 lc->grfmove.grfid = grfid;
00645 lc->grfmove.offset = offset;
00646 }
00647
00653 static void GamelogGRFParameters(uint32 grfid)
00654 {
00655 assert(_gamelog_action_type == GLAT_GRF);
00656
00657 LoggedChange *lc = GamelogChange(GLCT_GRFPARAM);
00658 if (lc == NULL) return;
00659
00660 lc->grfparam.grfid = grfid;
00661 }
00662
00668 void GamelogGRFAddList(const GRFConfig *newg)
00669 {
00670 assert(_gamelog_action_type == GLAT_START || _gamelog_action_type == GLAT_LOAD);
00671
00672 for (; newg != NULL; newg = newg->next) {
00673 GamelogGRFAdd(newg);
00674 }
00675 }
00676
00678 struct GRFList {
00679 uint n;
00680 const GRFConfig *grf[];
00681 };
00682
00687 static GRFList *GenerateGRFList(const GRFConfig *grfc)
00688 {
00689 uint n = 0;
00690 for (const GRFConfig *g = grfc; g != NULL; g = g->next) {
00691 if (IsLoggableGrfConfig(g)) n++;
00692 }
00693
00694 GRFList *list = (GRFList*)MallocT<byte>(sizeof(GRFList) + n * sizeof(GRFConfig*));
00695
00696 list->n = 0;
00697 for (const GRFConfig *g = grfc; g != NULL; g = g->next) {
00698 if (IsLoggableGrfConfig(g)) list->grf[list->n++] = g;
00699 }
00700
00701 return list;
00702 }
00703
00709 void GamelogGRFUpdate(const GRFConfig *oldc, const GRFConfig *newc)
00710 {
00711 GRFList *ol = GenerateGRFList(oldc);
00712 GRFList *nl = GenerateGRFList(newc);
00713
00714 uint o = 0, n = 0;
00715
00716 while (o < ol->n && n < nl->n) {
00717 const GRFConfig *og = ol->grf[o];
00718 const GRFConfig *ng = nl->grf[n];
00719
00720 if (og->ident.grfid != ng->ident.grfid) {
00721 uint oi, ni;
00722 for (oi = 0; oi < ol->n; oi++) {
00723 if (ol->grf[oi]->ident.grfid == nl->grf[n]->ident.grfid) break;
00724 }
00725 if (oi < o) {
00726
00727 n++;
00728 continue;
00729 }
00730 if (oi == ol->n) {
00731
00732 GamelogGRFAdd(nl->grf[n++]);
00733 continue;
00734 }
00735 for (ni = 0; ni < nl->n; ni++) {
00736 if (nl->grf[ni]->ident.grfid == ol->grf[o]->ident.grfid) break;
00737 }
00738 if (ni < n) {
00739
00740 o++;
00741 continue;
00742 }
00743 if (ni == nl->n) {
00744
00745 GamelogGRFRemove(ol->grf[o++]->ident.grfid);
00746 continue;
00747 }
00748
00749
00750
00751 assert(ni > n && ni < nl->n);
00752 assert(oi > o && oi < ol->n);
00753
00754 ni -= n;
00755 oi -= o;
00756
00757 if (ni >= oi) {
00758
00759 GamelogGRFMove(ol->grf[o++]->ident.grfid, ni);
00760 } else {
00761 GamelogGRFMove(nl->grf[n++]->ident.grfid, -(int)oi);
00762 }
00763 } else {
00764 if (memcmp(og->ident.md5sum, ng->ident.md5sum, sizeof(og->ident.md5sum)) != 0) {
00765
00766 GamelogGRFCompatible(&nl->grf[n]->ident);
00767 }
00768
00769 if (og->num_params != ng->num_params || memcmp(og->param, ng->param, og->num_params * sizeof(og->param[0])) != 0) {
00770 GamelogGRFParameters(ol->grf[o]->ident.grfid);
00771 }
00772
00773 o++;
00774 n++;
00775 }
00776 }
00777
00778 while (o < ol->n) GamelogGRFRemove(ol->grf[o++]->ident.grfid);
00779 while (n < nl->n) GamelogGRFAdd (nl->grf[n++]);
00780
00781 free(ol);
00782 free(nl);
00783 }
00784
00793 void GamelogInfo(LoggedAction *gamelog_action, uint gamelog_actions, uint32 *last_ottd_rev, byte *ever_modified, bool *removed_newgrfs)
00794 {
00795 const LoggedAction *laend = &gamelog_action[gamelog_actions];
00796 for (const LoggedAction *la = gamelog_action; la != laend; la++) {
00797 const LoggedChange *lcend = &la->change[la->changes];
00798 for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
00799 switch (lc->ct) {
00800 default: break;
00801
00802 case GLCT_REVISION:
00803 *last_ottd_rev = lc->revision.newgrf;
00804 *ever_modified = max(*ever_modified, lc->revision.modified);
00805 break;
00806
00807 case GLCT_GRFREM:
00808 *removed_newgrfs = true;
00809 break;
00810 }
00811 }
00812 }
00813 }