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 }

Generated on Thu Oct 1 11:03:13 2009 for OpenTTD by  doxygen 1.5.6