# gamelog.cpp

Go to the documentation of this file.

    00001 /* $Id: gamelog.cpp 17596 2009-09-20 23:11:01Z smatz $ */
    00002
    00003 /*
    00004  * This file is part of OpenTTD.
    00005  * 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.
    00006  * 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.
    00007  * 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/>.
    00008  */
    00009
    00012 #include "stdafx.h"
    00013 #include "openttd.h"
    00014 #include "saveload/saveload.h"
    00015 #include "core/alloc_func.hpp"
    00016 #include "variables.h"
    00017 #include "string_func.h"
    00018 #include "settings_type.h"
    00019 #include "gamelog.h"
    00020 #include "gamelog_internal.h"
    00021 #include "console_func.h"
    00022 #include "debug.h"
    00023 #include "rev.h"
    00024
    00025 #include <stdarg.h>
    00026
    00027 extern const uint16 SAVEGAME_VERSION;
    00028
    00029 extern SavegameType _savegame_type;
    00030
    00031 extern uint32 _ttdp_version;
    00032 extern uint16 _sl_version;
    00033 extern byte   _sl_minor_version;
    00034
    00035
    00036 static GamelogActionType _gamelog_action_type = GLAT_NONE;
    00037
    00038 LoggedAction *_gamelog_action = NULL;
    00039 uint _gamelog_actions         = 0;
    00040 static LoggedAction *_current_action = NULL;
    00041
    00042
    00047 void GamelogStartAction(GamelogActionType at)
    00048 {
    00049   assert(_gamelog_action_type == GLAT_NONE); // do not allow starting new action without stopping the previous first
    00050   _gamelog_action_type = at;
    00051 }
    00052
    00055 void GamelogStopAction()
    00056 {
    00057   assert(_gamelog_action_type != GLAT_NONE); // nobody should try to stop if there is no action in progress
    00058
    00059   bool print = _current_action != NULL;
    00060
    00061   _current_action = NULL;
    00062   _gamelog_action_type = GLAT_NONE;
    00063
    00064   if (print) GamelogPrintDebug(5);
    00065 }
    00066
    00069 void GamelogReset()
    00070 {
    00071   assert(_gamelog_action_type == GLAT_NONE);
    00072
    00073   for (uint i = 0; i < _gamelog_actions; i++) {
    00074     const LoggedAction *la = &_gamelog_action[i];
    00075     for (uint j = 0; j < la->changes; j++) {
    00076       const LoggedChange *lc = &la->change[j];
    00077       if (lc->ct == GLCT_SETTING) free(lc->setting.name);
    00078     }
    00079     free(la->change);
    00080   }
    00081
    00082   free(_gamelog_action);
    00083
    00084   _gamelog_action  = NULL;
    00085   _gamelog_actions = 0;
    00086   _current_action  = NULL;
    00087 }
    00088
    00089 enum {
    00090   GAMELOG_BUF_LEN = 1024
    00091 };
    00092
    00093 static int _dbgofs = 0;
    00094
    00095 static void AddDebugText(char *buf, const char *s, ...) WARN_FORMAT(2, 3);
    00096
    00097 static void AddDebugText(char *buf, const char *s, ...)
    00098 {
    00099   if (GAMELOG_BUF_LEN <= _dbgofs) return;
    00100
    00101   va_list va;
    00102
    00103   va_start(va, s);
    00104   _dbgofs += vsnprintf(buf + _dbgofs, GAMELOG_BUF_LEN - _dbgofs, s, va);
    00105   va_end(va);
    00106 }
    00107
    00108
    00113 static void PrintGrfFilename(char *buf, uint grfid)
    00114 {
    00115   const GRFConfig *gc = FindGRFConfig(grfid);
    00116
    00117   if (gc == NULL) return;
    00118
    00119   AddDebugText(buf, ", filename: %s", gc->filename);
    00120 }
    00121
    00127 static void PrintGrfInfo(char *buf, uint grfid, const uint8 *md5sum)
    00128 {
    00129   char txt[40];
    00130
    00131   md5sumToString(txt, lastof(txt), md5sum);
    00132
    00133   AddDebugText(buf, "GRF ID %08X, checksum %s", BSWAP32(grfid), txt);
    00134
    00135   PrintGrfFilename(buf, grfid);
    00136
    00137   return;
    00138 }
    00139
    00140
    00142 static const char * const la_text[] = {
    00143   "new game started",
    00144   "game loaded",
    00145   "GRF config changed",
    00146   "cheat was used",
    00147   "settings changed",
    00148   "GRF bug triggered",
    00149   "emergency savegame",
    00150 };
    00151
    00152 assert_compile(lengthof(la_text) == GLAT_END);
    00153
    00154
    00159 void GamelogPrint(GamelogPrintProc *proc)
    00160 {
    00161   char buf[GAMELOG_BUF_LEN];
    00162
    00163   proc("---- gamelog start ----");
    00164
    00165   const LoggedAction *laend = &_gamelog_action[_gamelog_actions];
    00166
    00167   for (const LoggedAction *la = _gamelog_action; la != laend; la++) {
    00168     assert((uint)la->at < GLAT_END);
    00169
    00170     snprintf(buf, GAMELOG_BUF_LEN, "Tick %u: %s", (uint)la->tick, la_text[(uint)la->at]);
    00171     proc(buf);
    00172
    00173     const LoggedChange *lcend = &la->change[la->changes];
    00174
    00175     for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
    00176       _dbgofs = 0;
    00177       AddDebugText(buf, "     ");
    00178
    00179       switch (lc->ct) {
    00180         default: NOT_REACHED();
    00181         case GLCT_MODE:
    00182           AddDebugText(buf, "New game mode: %u landscape: %u",
    00183             (uint)lc->mode.mode, (uint)lc->mode.landscape);
    00184           break;
    00185
    00186         case GLCT_REVISION:
    00187           AddDebugText(buf, "Revision text changed to %s, savegame version %u, ",
    00188             lc->revision.text, lc->revision.slver);
    00189
    00190           switch (lc->revision.modified) {
    00191             case 0: AddDebugText(buf, "not "); break;
    00192             case 1: AddDebugText(buf, "maybe "); break;
    00193             default: break;
    00194           }
    00195
    00196           AddDebugText(buf, "modified, _openttd_newgrf_version = 0x%08x", lc->revision.newgrf);
    00197           break;
    00198
    00199         case GLCT_OLDVER:
    00200           AddDebugText(buf, "Conversion from ");
    00201           switch (lc->oldver.type) {
    00202             default: NOT_REACHED();
    00203             case SGT_OTTD:
    00204               AddDebugText(buf, "OTTD savegame without gamelog: version %u, %u",
    00205                 GB(lc->oldver.version, 8, 16), GB(lc->oldver.version, 0, 8));
    00206               break;
    00207
    00208             case SGT_TTO:
    00209               AddDebugText(buf, "TTO savegame");
    00210               break;
    00211
    00212             case SGT_TTD:
    00213               AddDebugText(buf, "TTD savegame");
    00214               break;
    00215
    00216             case SGT_TTDP1:
    00217             case SGT_TTDP2:
    00218               AddDebugText(buf, "TTDP savegame, %s format",
    00219                 lc->oldver.type == SGT_TTDP1 ? "old" : "new");
    00220               if (lc->oldver.version != 0) {
    00221                 AddDebugText(buf, ", TTDP version %u.%u.%u.%u",
    00222                   GB(lc->oldver.version, 24, 8), GB(lc->oldver.version, 20, 4),
    00223                   GB(lc->oldver.version, 16, 4), GB(lc->oldver.version, 0, 16));
    00224               }
    00225               break;
    00226           }
    00227           break;
    00228
    00229         case GLCT_SETTING:
    00230           AddDebugText(buf, "Setting changed: %s : %d -> %d", lc->setting.name, lc->setting.oldval, lc->setting.newval);
    00231           break;
    00232
    00233         case GLCT_GRFADD:
    00234           AddDebugText(buf, "Added NewGRF: ");
    00235           PrintGrfInfo(buf, lc->grfadd.grfid, lc->grfadd.md5sum);
    00236           break;
    00237
    00238         case GLCT_GRFREM:
    00239           AddDebugText(buf, "Removed NewGRF: %08X", BSWAP32(lc->grfrem.grfid));
    00240           PrintGrfFilename(buf, lc->grfrem.grfid);
    00241           break;
    00242
    00243         case GLCT_GRFCOMPAT:
    00244           AddDebugText(buf, "Compatible NewGRF loaded: ");
    00245           PrintGrfInfo(buf, lc->grfcompat.grfid, lc->grfcompat.md5sum);
    00246           break;
    00247
    00248         case GLCT_GRFPARAM:
    00249           AddDebugText(buf, "GRF parameter changed: %08X", BSWAP32(lc->grfparam.grfid));
    00250           PrintGrfFilename(buf, lc->grfparam.grfid);
    00251           break;
    00252
    00253         case GLCT_GRFMOVE:
    00254           AddDebugText(buf, "GRF order changed: %08X moved %d places %s",
    00255             BSWAP32(lc->grfmove.grfid), abs(lc->grfmove.offset), lc->grfmove.offset >= 0 ? "down" : "up" );
    00256           PrintGrfFilename(buf, lc->grfmove.grfid);
    00257           break;
    00258
    00259         case GLCT_GRFBUG:
    00260           switch (lc->grfbug.bug) {
    00261             default: NOT_REACHED();
    00262             case GBUG_VEH_LENGTH:
    00263               AddDebugText(buf, "Rail vehicle changes length outside a depot: GRF ID %08X, internal ID 0x%X", BSWAP32(lc->grfbug.grfid), (uint)lc->grfbug.data);
    00264               PrintGrfFilename(buf, lc->grfbug.grfid);
    00265               break;
    00266           }
    00267
    00268         case GLCT_EMERGENCY:
    00269           break;
    00270       }
    00271
    00272       proc(buf);
    00273     }
    00274   }
    00275
    00276   proc("---- gamelog end ----");
    00277 }
    00278
    00279
    00280 static void GamelogPrintConsoleProc(const char *s)
    00281 {
    00282   IConsolePrint(CC_WARNING, s);
    00283 }
    00284
    00285 void GamelogPrintConsole()
    00286 {
    00287   GamelogPrint(&GamelogPrintConsoleProc);
    00288 }
    00289
    00290 static int _gamelog_print_level = 0;
    00291
    00292 static void GamelogPrintDebugProc(const char *s)
    00293 {
    00294   DEBUG(gamelog, _gamelog_print_level, "%s", s);
    00295 }
    00296
    00297
    00303 void GamelogPrintDebug(int level)
    00304 {
    00305   _gamelog_print_level = level;
    00306   GamelogPrint(&GamelogPrintDebugProc);
    00307 }
    00308
    00309
    00315 static LoggedChange *GamelogChange(GamelogChangeType ct)
    00316 {
    00317   if (_current_action == NULL) {
    00318     if (_gamelog_action_type == GLAT_NONE) return NULL;
    00319
    00320     _gamelog_action  = ReallocT(_gamelog_action, _gamelog_actions + 1);
    00321     _current_action  = &_gamelog_action[_gamelog_actions++];
    00322
    00323     _current_action->at      = _gamelog_action_type;
    00324     _current_action->tick    = _tick_counter;
    00325     _current_action->change  = NULL;
    00326     _current_action->changes = 0;
    00327   }
    00328
    00329   _current_action->change = ReallocT(_current_action->change, _current_action->changes + 1);
    00330
    00331   LoggedChange *lc = &_current_action->change[_current_action->changes++];
    00332   lc->ct = ct;
    00333
    00334   return lc;
    00335 }
    00336
    00337
    00340 void GamelogEmergency()
    00341 {
    00342   /* Terminate any active action */
    00343   if (_gamelog_action_type != GLAT_NONE) GamelogStopAction();
    00344   GamelogStartAction(GLAT_EMERGENCY);
    00345   GamelogChange(GLCT_EMERGENCY);
    00346   GamelogStopAction();
    00347 }
    00348
    00351 bool GamelogTestEmergency()
    00352 {
    00353   const LoggedChange *emergency = NULL;
    00354
    00355   const LoggedAction *laend = &_gamelog_action[_gamelog_actions];
    00356   for (const LoggedAction *la = _gamelog_action; la != laend; la++) {
    00357     const LoggedChange *lcend = &la->change[la->changes];
    00358     for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
    00359       if (lc->ct == GLCT_EMERGENCY) emergency = lc;
    00360     }
    00361   }
    00362
    00363   return (emergency != NULL);
    00364 }
    00365
    00368 void GamelogRevision()
    00369 {
    00370   assert(_gamelog_action_type == GLAT_START || _gamelog_action_type == GLAT_LOAD);
    00371
    00372   LoggedChange *lc = GamelogChange(GLCT_REVISION);
    00373   if (lc == NULL) return;
    00374
    00375   memset(lc->revision.text, 0, sizeof(lc->revision.text));
    00376   strecpy(lc->revision.text, _openttd_revision, lastof(lc->revision.text));
    00377   lc->revision.slver = SAVEGAME_VERSION;
    00378   lc->revision.modified = _openttd_revision_modified;
    00379   lc->revision.newgrf = _openttd_newgrf_version;
    00380 }
    00381
    00384 void GamelogMode()
    00385 {
    00386   assert(_gamelog_action_type == GLAT_START || _gamelog_action_type == GLAT_LOAD || _gamelog_action_type == GLAT_CHEAT);
    00387
    00388   LoggedChange *lc = GamelogChange(GLCT_MODE);
    00389   if (lc == NULL) return;
    00390
    00391   lc->mode.mode      = _game_mode;
    00392   lc->mode.landscape = _settings_game.game_creation.landscape;
    00393 }
    00394
    00397 void GamelogOldver()
    00398 {
    00399   assert(_gamelog_action_type == GLAT_LOAD);
    00400
    00401   LoggedChange *lc = GamelogChange(GLCT_OLDVER);
    00402   if (lc == NULL) return;
    00403
    00404   lc->oldver.type = _savegame_type;
    00405   lc->oldver.version = (_savegame_type == SGT_OTTD ? ((uint32)_sl_version << 8 | _sl_minor_version) : _ttdp_version);
    00406 }
    00407
    00413 void GamelogSetting(const char *name, int32 oldval, int32 newval)
    00414 {
    00415   assert(_gamelog_action_type == GLAT_SETTING);
    00416
    00417   LoggedChange *lc = GamelogChange(GLCT_SETTING);
    00418   if (lc == NULL) return;
    00419
    00420   lc->setting.name = strdup(name);
    00421   lc->setting.oldval = oldval;
    00422   lc->setting.newval = newval;
    00423 }
    00424
    00425
    00429 void GamelogTestRevision()
    00430 {
    00431   const LoggedChange *rev = NULL;
    00432
    00433   const LoggedAction *laend = &_gamelog_action[_gamelog_actions];
    00434   for (const LoggedAction *la = _gamelog_action; la != laend; la++) {
    00435     const LoggedChange *lcend = &la->change[la->changes];
    00436     for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
    00437       if (lc->ct == GLCT_REVISION) rev = lc;
    00438     }
    00439   }
    00440
    00441   if (rev == NULL || strcmp(rev->revision.text, _openttd_revision) != 0 ||
    00442       rev->revision.modified != _openttd_revision_modified ||
    00443       rev->revision.newgrf != _openttd_newgrf_version) {
    00444     GamelogRevision();
    00445   }
    00446 }
    00447
    00451 void GamelogTestMode()
    00452 {
    00453   const LoggedChange *mode = NULL;
    00454
    00455   const LoggedAction *laend = &_gamelog_action[_gamelog_actions];
    00456   for (const LoggedAction *la = _gamelog_action; la != laend; la++) {
    00457     const LoggedChange *lcend = &la->change[la->changes];
    00458     for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
    00459       if (lc->ct == GLCT_MODE) mode = lc;
    00460     }
    00461   }
    00462
    00463   if (mode == NULL || mode->mode.mode != _game_mode || mode->mode.landscape != _settings_game.game_creation.landscape) GamelogMode();
    00464 }
    00465
    00466
    00472 static void GamelogGRFBug(uint32 grfid, byte bug, uint64 data)
    00473 {
    00474   assert(_gamelog_action_type == GLAT_GRFBUG);
    00475
    00476   LoggedChange *lc = GamelogChange(GLCT_GRFBUG);
    00477   if (lc == NULL) return;
    00478
    00479   lc->grfbug.data  = data;
    00480   lc->grfbug.grfid = grfid;
    00481   lc->grfbug.bug   = bug;
    00482 }
    00483
    00492 bool GamelogGRFBugReverse(uint32 grfid, uint16 internal_id)
    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_GRFBUG && lc->grfbug.grfid == grfid &&
    00499           lc->grfbug.bug == GBUG_VEH_LENGTH && lc->grfbug.data == internal_id) {
    00500         return false;
    00501       }
    00502     }
    00503   }
    00504
    00505   GamelogStartAction(GLAT_GRFBUG);
    00506   GamelogGRFBug(grfid, GBUG_VEH_LENGTH, internal_id);
    00507   GamelogStopAction();
    00508
    00509   return true;
    00510 }
    00511
    00512
    00517 static inline bool IsLoggableGrfConfig(const GRFConfig *g)
    00518 {
    00519   return !HasBit(g->flags, GCF_STATIC) && g->status != GCS_NOT_FOUND;
    00520 }
    00521
    00525 void GamelogGRFRemove(uint32 grfid)
    00526 {
    00527   assert(_gamelog_action_type == GLAT_LOAD || _gamelog_action_type == GLAT_GRF);
    00528
    00529   LoggedChange *lc = GamelogChange(GLCT_GRFREM);
    00530   if (lc == NULL) return;
    00531
    00532   lc->grfrem.grfid = grfid;
    00533 }
    00534
    00538 void GamelogGRFAdd(const GRFConfig *newg)
    00539 {
    00540   assert(_gamelog_action_type == GLAT_LOAD || _gamelog_action_type == GLAT_START || _gamelog_action_type == GLAT_GRF);
    00541
    00542   if (!IsLoggableGrfConfig(newg)) return;
    00543
    00544   LoggedChange *lc = GamelogChange(GLCT_GRFADD);
    00545   if (lc == NULL) return;
    00546
    00547   memcpy(&lc->grfadd, newg, sizeof(GRFIdentifier));
    00548 }
    00549
    00554 void GamelogGRFCompatible(const GRFIdentifier *newg)
    00555 {
    00556   assert(_gamelog_action_type == GLAT_LOAD || _gamelog_action_type == GLAT_GRF);
    00557
    00558   LoggedChange *lc = GamelogChange(GLCT_GRFCOMPAT);
    00559   if (lc == NULL) return;
    00560
    00561   memcpy(&lc->grfcompat, newg, sizeof(GRFIdentifier));
    00562 }
    00563
    00568 static void GamelogGRFMove(uint32 grfid, int32 offset)
    00569 {
    00570   assert(_gamelog_action_type == GLAT_GRF);
    00571
    00572   LoggedChange *lc = GamelogChange(GLCT_GRFMOVE);
    00573   if (lc == NULL) return;
    00574
    00575   lc->grfmove.grfid  = grfid;
    00576   lc->grfmove.offset = offset;
    00577 }
    00578
    00583 static void GamelogGRFParameters(uint32 grfid)
    00584 {
    00585   assert(_gamelog_action_type == GLAT_GRF);
    00586
    00587   LoggedChange *lc = GamelogChange(GLCT_GRFPARAM);
    00588   if (lc == NULL) return;
    00589
    00590   lc->grfparam.grfid = grfid;
    00591 }
    00592
    00597 void GamelogGRFAddList(const GRFConfig *newg)
    00598 {
    00599   assert(_gamelog_action_type == GLAT_START || _gamelog_action_type == GLAT_LOAD);
    00600
    00601   for (; newg != NULL; newg = newg->next) {
    00602     GamelogGRFAdd(newg);
    00603   }
    00604 }
    00605
    00607 struct GRFList {
    00608   uint n;
    00609   const GRFConfig *grf[];
    00610 };
    00611
    00615 static GRFList *GenerateGRFList(const GRFConfig *grfc)
    00616 {
    00617   uint n = 0;
    00618   for (const GRFConfig *g = grfc; g != NULL; g = g->next) {
    00619     if (IsLoggableGrfConfig(g)) n++;
    00620   }
    00621
    00622   GRFList *list = (GRFList*)MallocT<byte>(sizeof(GRFList) + n * sizeof(GRFConfig*));
    00623
    00624   list->n = 0;
    00625   for (const GRFConfig *g = grfc; g != NULL; g = g->next) {
    00626     if (IsLoggableGrfConfig(g)) list->grf[list->n++] = g;
    00627   }
    00628
    00629   return list;
    00630 }
    00631
    00636 void GamelogGRFUpdate(const GRFConfig *oldc, const GRFConfig *newc)
    00637 {
    00638   GRFList *ol = GenerateGRFList(oldc);
    00639   GRFList *nl = GenerateGRFList(newc);
    00640
    00641   uint o = 0, n = 0;
    00642
    00643   while (o < ol->n && n < nl->n) {
    00644     const GRFConfig *og = ol->grf[o];
    00645     const GRFConfig *ng = nl->grf[n];
    00646
    00647     if (og->grfid != ng->grfid) {
    00648       uint oi, ni;
    00649       for (oi = 0; oi < ol->n; oi++) {
    00650         if (ol->grf[oi]->grfid == nl->grf[n]->grfid) break;
    00651       }
    00652       if (oi < o) {
    00653         /* GRF was moved, this change has been logged already */
    00654         n++;
    00655         continue;
    00656       }
    00657       if (oi == ol->n) {
    00658         /* GRF couldn't be found in the OLD list, GRF was ADDED */
    00659         GamelogGRFAdd(nl->grf[n++]);
    00660         continue;
    00661       }
    00662       for (ni = 0; ni < nl->n; ni++) {
    00663         if (nl->grf[ni]->grfid == ol->grf[o]->grfid) break;
    00664       }
    00665       if (ni < n) {
    00666         /* GRF was moved, this change has been logged already */
    00667         o++;
    00668         continue;
    00669       }
    00670       if (ni == nl->n) {
    00671         /* GRF couldn't be found in the NEW list, GRF was REMOVED */
    00672         GamelogGRFRemove(ol->grf[o++]->grfid);
    00673         continue;
    00674       }
    00675
    00676       /* o < oi < ol->n
    00677        * n < ni < nl->n */
    00678       assert(ni > n && ni < nl->n);
    00679       assert(oi > o && oi < ol->n);
    00680
    00681       ni -= n; // number of GRFs it was moved downwards
    00682       oi -= o; // number of GRFs it was moved upwards
    00683
    00684       if (ni >= oi) { // prefer the one that is moved further
    00685         /* GRF was moved down */
    00686         GamelogGRFMove(ol->grf[o++]->grfid, ni);
    00687       } else {
    00688         GamelogGRFMove(nl->grf[n++]->grfid, -(int)oi);
    00689       }
    00690     } else {
    00691       if (memcmp(og->md5sum, ng->md5sum, sizeof(og->md5sum)) != 0) {
    00692         /* md5sum changed, probably loading 'compatible' GRF */
    00693         GamelogGRFCompatible(nl->grf[n]);
    00694       }
    00695
    00696       if (og->num_params != ng->num_params || memcmp(og->param, ng->param, og->num_params * sizeof(og->param[0])) != 0) {
    00697         GamelogGRFParameters(ol->grf[o]->grfid);
    00698       }
    00699
    00700       o++;
    00701       n++;
    00702     }
    00703   }
    00704
    00705   while (o < ol->n) GamelogGRFRemove(ol->grf[o++]->grfid); // remaining GRFs were removed ...
    00706   while (n < nl->n) GamelogGRFAdd   (nl->grf[n++]);    // ... or added
    00707
    00708   free(ol);
    00709   free(nl);
    00710 }
    00711
    00717 void GamelogGetOriginalGRFMD5Checksum(uint32 grfid, byte *md5sum)
    00718 {
    00719   const LoggedAction *la = &_gamelog_action[_gamelog_actions - 1];
    00720   /* There should always be a "start game" action */
    00721   assert(_gamelog_actions > 0);
    00722
    00723   do {
    00724     const LoggedChange *lc = &la->change[la->changes - 1];
    00725     /* There should always be at least one change per action */
    00726     assert(la->changes > 0);
    00727
    00728     do {
    00729       if (lc->ct == GLCT_GRFADD && lc->grfadd.grfid == grfid) {
    00730         memcpy(md5sum, lc->grfadd.md5sum, sizeof(lc->grfadd.md5sum));
    00731         return;
    00732       }
    00733     } while (lc-- != la->change);
    00734   } while (la-- != _gamelog_action);
    00735
    00736   NOT_REACHED();
    00737 }