tron@2186: /* $Id$ */ tron@2186: belugas@6179: /** @file gfxinit.cpp */ belugas@6179: truelight@0: #include "stdafx.h" Darkvater@1891: #include "openttd.h" tron@1299: #include "debug.h" tron@2163: #include "functions.h" truelight@0: #include "gfx.h" tron@2340: #include "gfxinit.h" tron@1349: #include "spritecache.h" tron@1363: #include "table/sprites.h" truelight@0: #include "fileio.h" rubidium@7805: #include "fios.h" Darkvater@4200: #include "string.h" pasky@463: #include "newgrf.h" dominik@961: #include "md5.h" tron@2159: #include "variables.h" peter1138@5156: #include "fontcache.h" tron@4472: #include truelight@0: rubidium@6248: struct MD5File { belugas@6179: const char * filename; ///< filename belugas@6179: md5_byte_t hash[16]; ///< md5 sum of the file rubidium@6248: }; dominik@961: rubidium@6248: struct FileList { rubidium@7882: MD5File basic[2]; ///< GRF files that always have to be loaded rubidium@7882: MD5File landscape[3]; ///< Landscape specific grf files rubidium@7882: MD5File sound; ///< Sound samples rubidium@7882: MD5File chars; ///< GRF File with character replacements rubidium@7882: MD5File openttd; ///< GRF File with OTTD specific graphics rubidium@6248: }; truelight@0: dominik@961: #include "table/files.h" truelight@0: #include "table/landscape_sprite.h" truelight@0: celestar@2187: static const SpriteID * const _landscape_spriteindexes[] = { truelight@0: _landscape_spriteindexes_1, truelight@0: _landscape_spriteindexes_2, truelight@0: _landscape_spriteindexes_3, truelight@0: }; truelight@0: rubidium@7841: static uint LoadGrfFile(const char *filename, uint load_index, int file_index) truelight@0: { tron@2342: uint load_index_org = load_index; truelight@6908: uint sprite_id = 0; truelight@0: truelight@0: FioOpenFile(file_index, filename); darkvater@365: Darkvater@5380: DEBUG(sprite, 2, "Reading grf-file '%s'", filename); truelight@0: truelight@6908: while (LoadNextSprite(load_index, file_index, sprite_id)) { truelight@0: load_index++; truelight@6908: sprite_id++; celestar@2187: if (load_index >= MAX_SPRITES) { celestar@2187: error("Too many sprites. Recompile with higher MAX_SPRITES value or remove some custom GRF files."); truelight@0: } truelight@0: } Darkvater@5380: DEBUG(sprite, 2, "Currently %i sprites are loaded", load_index); truelight@0: darkvater@365: return load_index - load_index_org; darkvater@365: } darkvater@365: tron@2342: rubidium@7772: void LoadSpritesIndexed(int file_index, uint *sprite_id, const SpriteID *index_tbl) rubidium@7772: { rubidium@7772: uint start; rubidium@7772: while ((start = *index_tbl++) != END) { rubidium@7772: uint end = *index_tbl++; rubidium@7772: rubidium@7772: if (start == SKIP) { // skip sprites (amount in second var) rubidium@7772: SkipSprites(end); rubidium@7772: (*sprite_id) += end; rubidium@7772: } else { // load sprites and use indexes from start to end rubidium@7772: do { rubidium@7772: #ifdef NDEBUG rubidium@7772: LoadNextSprite(start, file_index, *sprite_id); rubidium@7772: #else rubidium@7772: bool b = LoadNextSprite(start, file_index, *sprite_id); rubidium@7772: assert(b); rubidium@7772: #endif rubidium@7772: (*sprite_id)++; rubidium@7772: } while (++start <= end); rubidium@7772: } rubidium@7772: } rubidium@7772: } rubidium@7772: tron@2342: static void LoadGrfIndexed(const char* filename, const SpriteID* index_tbl, int file_index) darkvater@365: { truelight@6908: uint sprite_id = 0; dominik@614: darkvater@365: FioOpenFile(file_index, filename); darkvater@365: Darkvater@5380: DEBUG(sprite, 2, "Reading indexed grf-file '%s'", filename); truelight@0: rubidium@7772: LoadSpritesIndexed(file_index, &sprite_id, index_tbl); truelight@184: } truelight@0: truelight@0: rubidium@7841: /** rubidium@7841: * Calculate and check the MD5 hash of the supplied filename. rubidium@7841: * @param file filename and expected MD5 hash for the given filename. rubidium@7841: * @return true if the checksum is correct. rubidium@7841: */ rubidium@7841: static bool FileMD5(const MD5File file) dominik@614: { truelight@7574: size_t size; truelight@7574: FILE *f = FioFOpenFile(file.filename, "rb", DATA_DIR, &size); bjarni@5482: truelight@862: if (f != NULL) { tron@2028: md5_state_t filemd5state; tron@2028: md5_byte_t buffer[1024]; tron@2028: md5_byte_t digest[16]; tron@2028: size_t len; tron@2028: dominik@961: md5_init(&filemd5state); truelight@7574: while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, f)) != 0 && size != 0) { truelight@7574: size -= len; dominik@961: md5_append(&filemd5state, buffer, len); truelight@7574: } dominik@961: truelight@7592: FioFCloseFile(f); tron@1019: dominik@961: md5_finish(&filemd5state, digest); rubidium@7841: return memcmp(file.hash, digest, sizeof(file.hash)) == 0; dominik@961: } else { // file not found dominik@961: return false; tron@1019: } dominik@961: } dominik@961: rubidium@7841: /** rubidium@7841: * Determine the palette that has to be used. rubidium@7841: * - forced DOS palette via command line -> leave it that way rubidium@7841: * - all Windows files present -> Windows palette rubidium@7841: * - all DOS files present -> DOS palette rubidium@7841: * - no Windows files present and any DOS file present -> DOS palette rubidium@7841: * - otherwise -> Windows palette rubidium@7841: */ rubidium@7841: static void DeterminePalette() dominik@961: { rubidium@7841: if (_use_dos_palette) return; rubidium@7841: rubidium@7841: /* Count of files from the different versions. */ tron@1355: uint dos = 0; tron@1355: uint win = 0; dominik@961: rubidium@7841: for (uint i = 0; i < lengthof(files_dos.basic); i++) if (FioCheckFileExists(files_dos.basic[i].filename)) dos++; rubidium@7841: for (uint i = 0; i < lengthof(files_dos.landscape); i++) if (FioCheckFileExists(files_dos.landscape[i].filename)) dos++; dominik@961: rubidium@7841: for (uint i = 0; i < lengthof(files_win.basic); i++) if (FioCheckFileExists(files_win.basic[i].filename)) win++; rubidium@7841: for (uint i = 0; i < lengthof(files_win.landscape); i++) if (FioCheckFileExists(files_win.landscape[i].filename)) win++; peter1138@4934: rubidium@7841: if (win == 5) { dominik@961: _use_dos_palette = false; tron@1381: } else if (dos == 5 || (win == 0 && dos > 0)) { dominik@614: _use_dos_palette = true; tron@1380: } else { dominik@961: _use_dos_palette = false; dominik@614: } dominik@614: } dominik@614: rubidium@7841: /** rubidium@7841: * Checks whether the MD5 checksums of the files are correct. rubidium@7841: * rubidium@7841: * @note Also checks sample.cat and other required non-NewGRF GRFs for corruption. rubidium@7841: */ rubidium@7841: void CheckExternalFiles() rubidium@7841: { rubidium@7841: DeterminePalette(); rubidium@7841: rubidium@7841: static const size_t ERROR_MESSAGE_LENGTH = 128; rubidium@7841: const FileList *files = _use_dos_palette ? &files_dos : &files_win; rubidium@7882: char error_msg[ERROR_MESSAGE_LENGTH * (lengthof(files->basic) + lengthof(files->landscape) + 3)]; rubidium@7841: error_msg[0] = '\0'; rubidium@7841: char *add_pos = error_msg; rubidium@7841: rubidium@7841: for (uint i = 0; i < lengthof(files->basic); i++) { rubidium@7841: if (!FileMD5(files->basic[i])) { rubidium@7841: add_pos += snprintf(add_pos, ERROR_MESSAGE_LENGTH, "Your '%s' file is corrupted or missing! You can find '%s' on your Transport Tycoon Deluxe CD-ROM.\n", files->basic[i].filename, files->basic[i].filename); rubidium@7841: } rubidium@7841: } rubidium@7841: rubidium@7841: for (uint i = 0; i < lengthof(files->landscape); i++) { rubidium@7841: if (!FileMD5(files->landscape[i])) { rubidium@7841: add_pos += snprintf(add_pos, ERROR_MESSAGE_LENGTH, "Your '%s' file is corrupted or missing! You can find '%s' on your Transport Tycoon Deluxe CD-ROM.\n", files->landscape[i].filename, files->landscape[i].filename); rubidium@7841: } rubidium@7841: } rubidium@7841: rubidium@7882: if (!FileMD5(files_win.sound) && !FileMD5(files_dos.sound)) { rubidium@7841: add_pos += snprintf(add_pos, ERROR_MESSAGE_LENGTH, "Your 'sample.cat' file is corrupted or missing! You can find 'sample.cat' on your Transport Tycoon Deluxe CD-ROM.\n"); rubidium@7841: } rubidium@7841: rubidium@7882: if (!FileMD5(files->chars)) { rubidium@7882: add_pos += snprintf(add_pos, ERROR_MESSAGE_LENGTH, "Your '%s' file is corrupted or missing! The file was part of your installation.\n", files->chars.filename); rubidium@7882: } rubidium@7882: rubidium@7882: if (!FileMD5(files->openttd)) { rubidium@7882: add_pos += snprintf(add_pos, ERROR_MESSAGE_LENGTH, "Your '%s' file is corrupted or missing! The file was part of your installation.\n", files->openttd.filename); rubidium@7841: } rubidium@7841: rubidium@7841: if (add_pos != error_msg) ShowInfoF(error_msg); rubidium@7841: } rubidium@7841: tron@2342: tron@2353: static const SpriteID trg1idx[] = { belugas@6179: 0, 1, ///< Mouse cursor, ZZZ tron@2353: /* Medium font */ belugas@6179: 2, 92, ///< ' ' till 'z' tron@2588: SKIP, 36, rubidium@6729: 160, 160, ///< Move Ÿ to the correct position belugas@6179: 98, 98, ///< Up arrow tron@2588: 131, 133, belugas@6179: SKIP, 1, ///< skip currency sign tron@2588: 135, 135, tron@2588: SKIP, 1, tron@2588: 137, 137, tron@2588: SKIP, 1, tron@2588: 139, 139, belugas@6179: 140, 140, ///< @todo Down arrow tron@2588: 141, 141, belugas@6179: 142, 142, ///< @todo Check mark belugas@6179: 143, 143, ///< @todo Cross tron@2588: 144, 144, belugas@6179: 145, 145, ///< @todo Right arrow tron@2588: 146, 149, belugas@6179: 118, 122, ///< Transport markers tron@2588: SKIP, 2, tron@2588: 157, 157, belugas@6179: 114, 115, ///< Small up/down arrows tron@2588: SKIP, 1, tron@2588: 161, 225, tron@2353: /* Small font */ belugas@6179: 226, 316, ///< ' ' till 'z' tron@2588: SKIP, 36, rubidium@6729: 384, 384, ///< Move Ÿ to the correct position belugas@6179: 322, 322, ///< Up arrow tron@2588: 355, 357, belugas@6179: SKIP, 1, ///< skip currency sign tron@2588: 359, 359, tron@2588: SKIP, 1, tron@2588: 361, 361, tron@2588: SKIP, 1, tron@2588: 363, 363, belugas@6179: 364, 364, ////< @todo Down arrow tron@2588: 365, 366, tron@2588: SKIP, 1, tron@2588: 368, 368, belugas@6179: 369, 369, ///< @todo Right arrow tron@2588: 370, 373, tron@2588: SKIP, 7, tron@2588: 381, 381, tron@2588: SKIP, 3, tron@2588: 385, 449, tron@2353: /* Big font */ belugas@6179: 450, 540, ///< ' ' till 'z' tron@2588: SKIP, 36, rubidium@6729: 608, 608, ///< Move Ÿ to the correct position tron@2588: SKIP, 1, tron@2588: 579, 581, tron@2588: SKIP, 1, tron@2588: 583, 583, tron@2588: SKIP, 5, tron@2588: 589, 589, tron@2588: SKIP, 15, tron@2588: 605, 605, tron@2588: SKIP, 3, tron@2588: 609, 625, tron@2588: SKIP, 1, tron@2588: 627, 632, tron@2588: SKIP, 1, tron@2588: 634, 639, tron@2588: SKIP, 1, tron@2588: 641, 657, tron@2588: SKIP, 1, tron@2588: 659, 664, tron@2588: SKIP, 2, tron@2588: 667, 671, tron@2588: SKIP, 1, tron@2588: 673, 673, tron@2353: /* Graphics */ tron@2588: 674, 4792, tron@2588: END tron@2353: }; tron@2353: rubidium@7882: /** Replace some letter sprites with some other letters */ rubidium@7882: static const SpriteID _chars_grf_indexes[] = { rubidium@7882: 134, 134, ///< euro symbol medium size rubidium@7882: 582, 582, ///< euro symbol large size rubidium@7882: 358, 358, ///< euro symbol tiny rubidium@6729: 648, 648, ///< nordic char: æ rubidium@6729: 616, 616, ///< nordic char: Æ rubidium@6729: 666, 666, ///< nordic char: ø rubidium@6729: 634, 634, ///< nordic char: Ø rubidium@6729: 382, 383, ///< Œ œ tiny rubidium@6729: 158, 159, ///< Œ œ medium rubidium@6729: 606, 607, ///< Œ œ large rubidium@6729: 360, 360, ///< Š tiny rubidium@6729: 362, 362, ///< š tiny rubidium@6729: 136, 136, ///< Š medium rubidium@6729: 138, 138, ///< š medium rubidium@6729: 584, 584, ///< Š large rubidium@6729: 586, 586, ///< š large rubidium@6729: 626, 626, ///< Ð large rubidium@6729: 658, 658, ///< ð large rubidium@6729: 374, 374, ///< Ž tiny rubidium@6729: 378, 378, ///< ž tiny rubidium@6729: 150, 150, ///< Ž medium rubidium@6729: 154, 154, ///< ž medium rubidium@6729: 598, 598, ///< Ž large rubidium@6729: 602, 602, ///< ž large rubidium@6729: 640, 640, ///< Þ large rubidium@6729: 672, 672, ///< þ large rubidium@6729: 380, 380, ///< º tiny rubidium@6729: 156, 156, ///< º medium rubidium@6729: 604, 604, ///< º large belugas@6179: 317, 320, ///< { | } ~ tiny belugas@6179: 93, 96, ///< { | } ~ medium belugas@6179: 541, 544, ///< { | } ~ large rubidium@6729: 585, 585, ///< § large rubidium@6729: 587, 587, ///< © large rubidium@6729: 592, 592, ///< ® large rubidium@6729: 594, 597, ///< ° ± ² ³ large rubidium@6729: 633, 633, ///< × large rubidium@6729: 665, 665, ///< ÷ large rubidium@6729: 377, 377, ///< · small rubidium@6729: 153, 153, ///< · medium rubidium@6729: 601, 601, ///< · large tron@2588: END tron@2353: }; tron@2353: tron@2342: rubidium@6247: static void LoadSpriteTables() truelight@0: { rubidium@7841: const FileList *files = _use_dos_palette ? &files_dos : &files_win; rubidium@7805: uint i = FIRST_GRF_SLOT; dominik@614: rubidium@7805: LoadGrfIndexed(files->basic[0].filename, trg1idx, i++); tron@2407: DupSprite( 2, 130); // non-breaking space medium tron@2407: DupSprite(226, 354); // non-breaking space tiny tron@2407: DupSprite(450, 578); // non-breaking space large tron@2353: rubidium@7841: /* rubidium@7841: * The second basic file always starts at the given location and does rubidium@7841: * contain a different amount of sprites depending on the "type"; DOS rubidium@7841: * has a few sprites less. However, we do not care about those missing rubidium@7841: * sprites as they are not shown anyway (logos in intro game). rubidium@7841: */ rubidium@7841: LoadGrfFile(files->basic[1].filename, 4793, i++); truelight@0: rubidium@7841: /* rubidium@7841: * Load additional sprites for climates other than temperate. rubidium@7841: * This overwrites some of the temperate sprites, such as foundations rubidium@7841: * and the ground sprites. rubidium@7841: */ belugas@6357: if (_opt.landscape != LT_TEMPERATE) { tron@2309: LoadGrfIndexed( peter1138@5151: files->landscape[_opt.landscape - 1].filename, peter1138@5151: _landscape_spriteindexes[_opt.landscape - 1], tron@2309: i++ tron@2309: ); tron@2309: } truelight@0: rubidium@7882: LoadGrfIndexed(files->chars.filename, _chars_grf_indexes, i++); glx@6780: peter1138@5156: /* Initialize the unicode to sprite mapping table */ peter1138@5156: InitializeUnicodeGlyphMap(); peter1138@5156: rubidium@7882: /* rubidium@7882: * Load the base NewGRF with OTTD required graphics as first NewGRF. rubidium@7882: * However, we do not want it to show up in the list of used NewGRFs, rubidium@7882: * so we have to manually add it, and then remove it later. rubidium@7882: */ rubidium@7882: GRFConfig *top = _grfconfig; rubidium@7882: GRFConfig *master = CallocT(1); rubidium@7882: master->filename = strdup(files->openttd.filename); rubidium@7882: FillGRFDetails(master, false); skidd13@7929: ClrBit(master->flags, GCF_INIT_ONLY); rubidium@7882: master->next = top; rubidium@7882: _grfconfig = master; rubidium@7882: rubidium@7882: LoadNewGRF(SPR_NEWGRFS_BASE, i); rubidium@7882: rubidium@7882: /* Free and remove the top element. */ rubidium@7882: ClearGRFConfig(&master); rubidium@7882: _grfconfig = top; truelight@0: } truelight@0: truelight@0: rubidium@6247: void GfxLoadSprites() tron@1093: { Darkvater@5380: DEBUG(sprite, 2, "Loading sprite set %d", _opt.landscape); truelight@0: peter1138@5151: GfxInitSpriteMem(); peter1138@5151: LoadSpriteTables(); peter1138@5151: GfxInitPalettes(); truelight@0: }