# gfxinit.cpp

Go to the documentation of this file.

    00001 /* $Id: gfxinit.cpp 17302 2009-08-28 17:17:08Z rubidium $ */
    00002
    00005 #include "stdafx.h"
    00006 #include "debug.h"
    00007 #include "spritecache.h"
    00008 #include "fileio_func.h"
    00009 #include "fios.h"
    00010 #include "newgrf.h"
    00011 #include "md5.h"
    00012 #include "fontcache.h"
    00013 #include "gfx_func.h"
    00014 #include "settings_type.h"
    00015 #include "string_func.h"
    00016 #include "ini_type.h"
    00017
    00018 #include "table/sprites.h"
    00019 #include "table/palette_convert.h"
    00020
    00022 PaletteType _use_palette = PAL_AUTODETECT;
    00024 bool _palette_remap_grf[MAX_FILE_SLOTS];
    00026 const byte *_palette_remap = NULL;
    00028 const byte *_palette_reverse_remap = NULL;
    00029
    00030 char *_ini_graphics_set;
    00031
    00033 struct MD5File {
    00034   const char *filename;
    00035   uint8 hash[16];
    00036   const char *missing_warning;
    00037 };
    00038
    00040 enum GraphicsFileType {
    00041   GFT_BASE,
    00042   GFT_LOGOS,
    00043   GFT_ARCTIC,
    00044   GFT_TROPICAL,
    00045   GFT_TOYLAND,
    00046   GFT_EXTRA,
    00047   MAX_GFT
    00048 };
    00049
    00051 struct GraphicsSet {
    00052   const char *name;
    00053   const char *description;
    00054   uint32 shortname;
    00055   uint32 version;
    00056   PaletteType palette;
    00057
    00058   MD5File files[MAX_GFT];
    00059   uint found_grfs;
    00060
    00061   GraphicsSet *next;
    00062
    00064   ~GraphicsSet()
    00065   {
    00066     free((void*)this->name);
    00067     free((void*)this->description);
    00068     for (uint i = 0; i < MAX_GFT; i++) {
    00069       free((void*)this->files[i].filename);
    00070       free((void*)this->files[i].missing_warning);
    00071     }
    00072
    00073     delete this->next;
    00074   }
    00075 };
    00076
    00078 static GraphicsSet *_available_graphics_sets = NULL;
    00080 static const GraphicsSet *_used_graphics_set = NULL;
    00081
    00082 #include "table/files.h"
    00083 #include "table/landscape_sprite.h"
    00084
    00085 static const SpriteID * const _landscape_spriteindexes[] = {
    00086   _landscape_spriteindexes_1,
    00087   _landscape_spriteindexes_2,
    00088   _landscape_spriteindexes_3,
    00089 };
    00090
    00091 static uint LoadGrfFile(const char *filename, uint load_index, int file_index)
    00092 {
    00093   uint load_index_org = load_index;
    00094   uint sprite_id = 0;
    00095
    00096   FioOpenFile(file_index, filename);
    00097
    00098   DEBUG(sprite, 2, "Reading grf-file '%s'", filename);
    00099
    00100   while (LoadNextSprite(load_index, file_index, sprite_id)) {
    00101     load_index++;
    00102     sprite_id++;
    00103     if (load_index >= MAX_SPRITES) {
    00104       usererror("Too many sprites. Recompile with higher MAX_SPRITES value or remove some custom GRF files.");
    00105     }
    00106   }
    00107   DEBUG(sprite, 2, "Currently %i sprites are loaded", load_index);
    00108
    00109   return load_index - load_index_org;
    00110 }
    00111
    00112
    00113 void LoadSpritesIndexed(int file_index, uint *sprite_id, const SpriteID *index_tbl)
    00114 {
    00115   uint start;
    00116   while ((start = *index_tbl++) != END) {
    00117     uint end = *index_tbl++;
    00118
    00119     do {
    00120       bool b = LoadNextSprite(start, file_index, *sprite_id);
    00121       assert(b);
    00122       (*sprite_id)++;
    00123     } while (++start <= end);
    00124   }
    00125 }
    00126
    00127 static void LoadGrfIndexed(const char *filename, const SpriteID *index_tbl, int file_index)
    00128 {
    00129   uint sprite_id = 0;
    00130
    00131   FioOpenFile(file_index, filename);
    00132
    00133   DEBUG(sprite, 2, "Reading indexed grf-file '%s'", filename);
    00134
    00135   LoadSpritesIndexed(file_index, &sprite_id, index_tbl);
    00136 }
    00137
    00138
    00144 static bool FileMD5(const MD5File file)
    00145 {
    00146   size_t size;
    00147   FILE *f = FioFOpenFile(file.filename, "rb", DATA_DIR, &size);
    00148
    00149   if (f != NULL) {
    00150     Md5 checksum;
    00151     uint8 buffer[1024];
    00152     uint8 digest[16];
    00153     size_t len;
    00154
    00155     while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, f)) != 0 && size != 0) {
    00156       size -= len;
    00157       checksum.Append(buffer, len);
    00158     }
    00159
    00160     FioFCloseFile(f);
    00161
    00162     checksum.Finish(digest);
    00163     return memcmp(file.hash, digest, sizeof(file.hash)) == 0;
    00164   } else { // file not found
    00165     return false;
    00166   }
    00167 }
    00168
    00173 static bool DetermineGraphicsPack()
    00174 {
    00175   if (_used_graphics_set != NULL) return true;
    00176
    00177   const GraphicsSet *best = _available_graphics_sets;
    00178   for (const GraphicsSet *c = _available_graphics_sets; c != NULL; c = c->next) {
    00179     if (best->found_grfs < c->found_grfs ||
    00180         (best->found_grfs == c->found_grfs && (
    00181           (best->shortname == c->shortname && best->version < c->version) ||
    00182           (best->palette != _use_palette && c->palette == _use_palette)))) {
    00183       best = c;
    00184     }
    00185   }
    00186
    00187   _used_graphics_set = best;
    00188   return _used_graphics_set != NULL;
    00189 }
    00190
    00191 extern void UpdateNewGRFConfigPalette();
    00192
    00198 static void DeterminePalette()
    00199 {
    00200   assert(_used_graphics_set != NULL);
    00201   if (_use_palette >= MAX_PAL) _use_palette = _used_graphics_set->palette;
    00202
    00203   switch (_use_palette) {
    00204     case PAL_DOS:
    00205       _palette_remap = _palmap_w2d;
    00206       _palette_reverse_remap = _palmap_d2w;
    00207       break;
    00208
    00209     case PAL_WINDOWS:
    00210       _palette_remap = _palmap_d2w;
    00211       _palette_reverse_remap = _palmap_w2d;
    00212       break;
    00213
    00214     default:
    00215       NOT_REACHED();
    00216   }
    00217
    00218   UpdateNewGRFConfigPalette();
    00219 }
    00220
    00226 void CheckExternalFiles()
    00227 {
    00228   DeterminePalette();
    00229
    00230   DEBUG(grf, 1, "Using the %s base graphics set with the %s palette", _used_graphics_set->name, _use_palette == PAL_DOS ? "DOS" : "Windows");
    00231
    00232   static const size_t ERROR_MESSAGE_LENGTH = 256;
    00233   static const size_t MISSING_FILE_MESSAGE_LENGTH = 128;
    00234
    00235   /* Allocate for a message for each missing file and for one error
    00236    * message per set.
    00237    */
    00238   char error_msg[MISSING_FILE_MESSAGE_LENGTH * (MAX_GFT + 1) + 2 * ERROR_MESSAGE_LENGTH];
    00239   error_msg[0] = '\0';
    00240   char *add_pos = error_msg;
    00241   const char *last = lastof(error_msg);
    00242
    00243   if (_used_graphics_set->found_grfs != MAX_GFT) {
    00244     /* Not all files were loaded succesfully, see which ones */
    00245     add_pos += seprintf(add_pos, last, "Trying to load graphics set '%s', but it is incomplete. The game will probably not run correctly until you properly install this set or select another one.\n\nThe following files are corrupted or missing:\n", _used_graphics_set->name);
    00246     for (uint i = 0; i < lengthof(_used_graphics_set->files); i++) {
    00247       if (!FileMD5(_used_graphics_set->files[i])) {
    00248         add_pos += seprintf(add_pos, last, "\t%s (%s)\n", _used_graphics_set->files[i].filename, _used_graphics_set->files[i].missing_warning);
    00249       }
    00250     }
    00251     add_pos += seprintf(add_pos, last, "\n");
    00252   }
    00253
    00254   bool sound = false;
    00255   for (uint i = 0; !sound && i < lengthof(_sound_sets); i++) {
    00256     sound = FileMD5(_sound_sets[i]);
    00257   }
    00258
    00259   if (!sound) {
    00260     add_pos += seprintf(add_pos, last, "Trying to load sound set, but it is incomplete. The game will probably not run correctly until you properly install this set or select another one.\n\nThe following files are corrupted or missing:\n");
    00261     add_pos += seprintf(add_pos, last, "\tsample.cat (You can find it on your Transport Tycoon Deluxe CD-ROM.)\n");
    00262   }
    00263
    00264   if (add_pos != error_msg) ShowInfoF("%s", error_msg);
    00265 }
    00266
    00267
    00268 static void LoadSpriteTables()
    00269 {
    00270   memset(_palette_remap_grf, 0, sizeof(_palette_remap_grf));
    00271   uint i = FIRST_GRF_SLOT;
    00272
    00273   _palette_remap_grf[i] = (_use_palette != _used_graphics_set->palette);
    00274   LoadGrfFile(_used_graphics_set->files[GFT_BASE].filename, 0, i++);
    00275
    00276   /*
    00277    * The second basic file always starts at the given location and does
    00278    * contain a different amount of sprites depending on the "type"; DOS
    00279    * has a few sprites less. However, we do not care about those missing
    00280    * sprites as they are not shown anyway (logos in intro game).
    00281    */
    00282   _palette_remap_grf[i] = (_use_palette != _used_graphics_set->palette);
    00283   LoadGrfFile(_used_graphics_set->files[GFT_LOGOS].filename, 4793, i++);
    00284
    00285   /*
    00286    * Load additional sprites for climates other than temperate.
    00287    * This overwrites some of the temperate sprites, such as foundations
    00288    * and the ground sprites.
    00289    */
    00290   if (_settings_game.game_creation.landscape != LT_TEMPERATE) {
    00291     _palette_remap_grf[i] = (_use_palette != _used_graphics_set->palette);
    00292     LoadGrfIndexed(
    00293       _used_graphics_set->files[GFT_ARCTIC + _settings_game.game_creation.landscape - 1].filename,
    00294       _landscape_spriteindexes[_settings_game.game_creation.landscape - 1],
    00295       i++
    00296     );
    00297   }
    00298
    00299   /* Initialize the unicode to sprite mapping table */
    00300   InitializeUnicodeGlyphMap();
    00301
    00302   /*
    00303    * Load the base NewGRF with OTTD required graphics as first NewGRF.
    00304    * However, we do not want it to show up in the list of used NewGRFs,
    00305    * so we have to manually add it, and then remove it later.
    00306    */
    00307   GRFConfig *top = _grfconfig;
    00308   GRFConfig *master = CallocT<GRFConfig>(1);
    00309   master->filename = strdup(_used_graphics_set->files[GFT_EXTRA].filename);
    00310   FillGRFDetails(master, false);
    00311   master->windows_paletted = (_used_graphics_set->palette == PAL_WINDOWS);
    00312   ClrBit(master->flags, GCF_INIT_ONLY);
    00313   master->next = top;
    00314   _grfconfig = master;
    00315
    00316   LoadNewGRF(SPR_NEWGRFS_BASE, i);
    00317
    00318   /* Free and remove the top element. */
    00319   ClearGRFConfig(&master);
    00320   _grfconfig = top;
    00321 }
    00322
    00323
    00324 void GfxLoadSprites()
    00325 {
    00326   DEBUG(sprite, 2, "Loading sprite set %d", _settings_game.game_creation.landscape);
    00327
    00328   GfxInitSpriteMem();
    00329   LoadSpriteTables();
    00330   GfxInitPalettes();
    00331 }
    00332
    00337 #define fetch_metadata(name) \
    00338   item = metadata->GetItem(name, false); \
    00339   if (item == NULL || strlen(item->value) == 0) { \
    00340     DEBUG(grf, 0, "Base graphics set detail loading: %s field missing", name); \
    00341     return false; \
    00342   }
    00343
    00345 static const char *_gft_names[MAX_GFT] = { "base", "logos", "arctic", "tropical", "toyland", "extra" };
    00346
    00354 static bool FillGraphicsSetDetails(GraphicsSet *graphics, IniFile *ini, const char *path)
    00355 {
    00356   memset(graphics, 0, sizeof(*graphics));
    00357
    00358   IniGroup *metadata = ini->GetGroup("metadata");
    00359   IniItem *item;
    00360
    00361   fetch_metadata("name");
    00362   graphics->name = strdup(item->value);
    00363
    00364   fetch_metadata("description");
    00365   graphics->description = strdup(item->value);
    00366
    00367   fetch_metadata("shortname");
    00368   for (uint i = 0; item->value[i] != '\0' && i < 4; i++) {
    00369     graphics->shortname |= ((uint8)item->value[i]) << (i * 8);
    00370   }
    00371
    00372   fetch_metadata("version");
    00373   graphics->version = atoi(item->value);
    00374
    00375   fetch_metadata("palette");
    00376   graphics->palette = (*item->value == 'D' || *item->value == 'd') ? PAL_DOS : PAL_WINDOWS;
    00377
    00378   /* For each of the graphics file types we want to find the file, MD5 checksums and warning messages. */
    00379   IniGroup *files  = ini->GetGroup("files");
    00380   IniGroup *md5s   = ini->GetGroup("md5s");
    00381   IniGroup *origin = ini->GetGroup("origin");
    00382   for (uint i = 0; i < MAX_GFT; i++) {
    00383     MD5File *file = &graphics->files[i];
    00384     /* Find the filename first. */
    00385     item = files->GetItem(_gft_names[i], false);
    00386     if (item == NULL) {
    00387       DEBUG(grf, 0, "No graphics file for: %s", _gft_names[i]);
    00388       return false;
    00389     }
    00390
    00391     const char *filename = item->value;
    00392     file->filename = MallocT<char>(strlen(filename) + strlen(path) + 1);
    00393     sprintf((char*)file->filename, "%s%s", path, filename);
    00394
    00395     /* Then find the MD5 checksum */
    00396     item = md5s->GetItem(filename, false);
    00397     if (item == NULL) {
    00398       DEBUG(grf, 0, "No MD5 checksum specified for: %s", filename);
    00399       return false;
    00400     }
    00401     char *c = item->value;
    00402     for (uint i = 0; i < sizeof(file->hash) * 2; i++, c++) {
    00403       uint j;
    00404       if ('0' <= *c && *c <= '9') {
    00405         j = *c - '0';
    00406       } else if ('a' <= *c && *c <= 'f') {
    00407         j = *c - 'a' + 10;
    00408       } else if ('A' <= *c && *c <= 'F') {
    00409         j = *c - 'A' + 10;
    00410       } else {
    00411         DEBUG(grf, 0, "Malformed MD5 checksum specified for: %s", filename);
    00412         return false;
    00413       }
    00414       if (i % 2 == 0) {
    00415         file->hash[i / 2] = j << 4;
    00416       } else {
    00417         file->hash[i / 2] |= j;
    00418       }
    00419     }
    00420
    00421     /* Then find the warning message when the file's missing */
    00422     item = origin->GetItem(filename, false);
    00423     if (item == NULL) item = origin->GetItem("default", false);
    00424     if (item == NULL) {
    00425       DEBUG(grf, 1, "No origin warning message specified for: %s", filename);
    00426       file->missing_warning = strdup("");
    00427     } else {
    00428       file->missing_warning = strdup(item->value);
    00429     }
    00430
    00431     if (FileMD5(*file)) graphics->found_grfs++;
    00432   }
    00433
    00434   return true;
    00435 }
    00436
    00438 class OBGFileScanner : FileScanner {
    00439 public:
    00440   /* virtual */ bool AddFile(const char *filename, size_t basepath_length);
    00441
    00443   static uint DoScan()
    00444   {
    00445     OBGFileScanner fs;
    00446     return fs.Scan(".obg", DATA_DIR);
    00447   }
    00448 };
    00449
    00456 bool OBGFileScanner::AddFile(const char *filename, size_t basepath_length)
    00457 {
    00458   bool ret = false;
    00459   DEBUG(grf, 1, "Checking %s for base graphics set", filename);
    00460
    00461   GraphicsSet *graphics = new GraphicsSet();;
    00462   IniFile *ini = new IniFile();
    00463   ini->LoadFromDisk(filename);
    00464
    00465   char *path = strdup(filename + basepath_length);
    00466   char *psep = strrchr(path, PATHSEPCHAR);
    00467   if (psep != NULL) {
    00468     psep[1] = '\0';
    00469   } else {
    00470     *path = '\0';
    00471   }
    00472
    00473   if (FillGraphicsSetDetails(graphics, ini, path)) {
    00474     GraphicsSet *duplicate = NULL;
    00475     for (GraphicsSet *c = _available_graphics_sets; c != NULL; c = c->next) {
    00476       if (strcmp(c->name, graphics->name) == 0 || c->shortname == graphics->shortname) {
    00477         duplicate = c;
    00478         break;
    00479       }
    00480     }
    00481     if (duplicate != NULL) {
    00482       /* The more complete graphics set takes precedence over the version number. */
    00483       if ((duplicate->found_grfs == graphics->found_grfs && duplicate->version >= graphics->version) ||
    00484           duplicate->found_grfs > graphics->found_grfs) {
    00485         DEBUG(grf, 1, "Not adding %s (%i) as base graphics set (duplicate)", graphics->name, graphics->version);
    00486         delete graphics;
    00487       } else {
    00488         GraphicsSet **prev = &_available_graphics_sets;
    00489         while (*prev != duplicate) prev = &(*prev)->next;
    00490
    00491         *prev = graphics;
    00492         graphics->next = duplicate->next;
    00493         /* don't allow recursive delete of all remaining items */
    00494         duplicate->next = NULL;
    00495
    00496         /* If the duplicate set is currently used (due to rescanning this can happen)
    00497          * update the currently used set to the new one. This will 'lie' about the
    00498          * version number until a new game is started which isn't a big problem */
    00499         if (_used_graphics_set == duplicate) _used_graphics_set = graphics;
    00500
    00501         DEBUG(grf, 1, "Removing %s (%i) as base graphics set (duplicate)", duplicate->name, duplicate->version);
    00502         delete duplicate;
    00503         ret = true;
    00504       }
    00505     } else {
    00506       GraphicsSet **last = &_available_graphics_sets;
    00507       while (*last != NULL) last = &(*last)->next;
    00508
    00509       *last = graphics;
    00510       ret = true;
    00511     }
    00512     if (ret) {
    00513       DEBUG(grf, 1, "Adding %s (%i) as base graphics set", graphics->name, graphics->version);
    00514     }
    00515   } else {
    00516     delete graphics;
    00517   }
    00518   free(path);
    00519
    00520   delete ini;
    00521   return ret;
    00522 }
    00523
    00524
    00525
    00527 void FindGraphicsSets()
    00528 {
    00529   DEBUG(grf, 1, "Scanning for Graphics sets");
    00530   OBGFileScanner::DoScan();
    00531 }
    00532
    00538 bool SetGraphicsSet(const char *name)
    00539 {
    00540   if (StrEmpty(name)) {
    00541     if (!DetermineGraphicsPack()) return false;
    00542     CheckExternalFiles();
    00543     return true;
    00544   }
    00545
    00546   for (const GraphicsSet *g = _available_graphics_sets; g != NULL; g = g->next) {
    00547     if (strcmp(name, g->name) == 0) {
    00548       _used_graphics_set = g;
    00549       CheckExternalFiles();
    00550       return true;
    00551     }
    00552   }
    00553   return false;
    00554 }
    00555
    00562 char *GetGraphicsSetsList(char *p, const char *last)
    00563 {
    00564   p += seprintf(p, last, "List of graphics sets:\n");
    00565   for (const GraphicsSet *g = _available_graphics_sets; g != NULL; g = g->next) {
    00566     if (g->found_grfs <= 1) continue;
    00567
    00568     p += seprintf(p, last, "%18s: %s", g->name, g->description);
    00569     int difference = MAX_GFT - g->found_grfs;
    00570     if (difference != 0) {
    00571       p += seprintf(p, last, " (missing %i file%s)\n", difference, difference == 1 ? "" : "s");
    00572     } else {
    00573       p += seprintf(p, last, "\n");
    00574     }
    00575   }
    00576   p += seprintf(p, last, "\n");
    00577
    00578   return p;
    00579 }
    00580
    00581 #if defined(ENABLE_NETWORK)
    00582 #include "network/network_content.h"
    00583
    00590 bool HasGraphicsSet(const ContentInfo *ci, bool md5sum)
    00591 {
    00592   assert(ci->type == CONTENT_TYPE_BASE_GRAPHICS);
    00593   for (const GraphicsSet *g = _available_graphics_sets; g != NULL; g = g->next) {
    00594     if (g->found_grfs <= 1) continue;
    00595
    00596     if (g->shortname != ci->unique_id) continue;
    00597     if (!md5sum) return true;
    00598
    00599     byte md5[16];
    00600     memset(md5, 0, sizeof(md5));
    00601     for (uint i = 0; i < MAX_GFT; i++) {
    00602       for (uint j = 0; j < sizeof(md5); j++) {
    00603         md5[j] ^= g->files[i].hash[j];
    00604       }
    00605     }
    00606     if (memcmp(md5, ci->md5sum, sizeof(md5)) == 0) return true;
    00607   }
    00608
    00609   return false;
    00610 }
    00611
    00612 #endif /* ENABLE_NETWORK */
    00613
    00617 int GetNumGraphicsSets()
    00618 {
    00619   int n = 0;
    00620   for (const GraphicsSet *g = _available_graphics_sets; g != NULL; g = g->next) {
    00621     if (g != _used_graphics_set && g->found_grfs <= 1) continue;
    00622     n++;
    00623   }
    00624   return n;
    00625 }
    00626
    00630 int GetIndexOfCurrentGraphicsSet()
    00631 {
    00632   int n = 0;
    00633   for (const GraphicsSet *g = _available_graphics_sets; g != NULL; g = g->next) {
    00634     if (g == _used_graphics_set) return n;
    00635     if (g->found_grfs <= 1) continue;
    00636     n++;
    00637   }
    00638   return -1;
    00639 }
    00640
    00644 const char *GetGraphicsSetName(int index)
    00645 {
    00646   for (const GraphicsSet *g = _available_graphics_sets; g != NULL; g = g->next) {
    00647     if (g != _used_graphics_set && g->found_grfs <= 1) continue;
    00648     if (index == 0) return g->name;
    00649     index--;
    00650   }
    00651   error("GetGraphicsSetName: index %d out of range", index);
    00652 }