# gamelog.cpp

Go to the documentation of this file.

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