1 /*
2  * This file is part of OpenTTD.
3  * 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.
4  * 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.
5  * 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/>.
6  */
7 
8 /** @file gfxinit.cpp Initializing of the (GRF) graphics. */
9 
10 #include "stdafx.h"
11 #include "fios.h"
12 #include "newgrf.h"
13 #include "3rdparty/md5/md5.h"
14 #include "fontcache.h"
15 #include "gfx_func.h"
16 #include "transparency.h"
17 #include "blitter/factory.hpp"
18 #include "video/video_driver.hpp"
19 #include "window_func.h"
20 
21 /* The type of set we're replacing */
22 #define SET_TYPE "graphics"
23 #include "base_media_func.h"
24 
25 #include "table/sprites.h"
26 
27 #include "safeguards.h"
28 
29 #include "table/landscape_sprite.h"
30 
31 /** Offsets for loading the different "replacement" sprites in the files. */
32 static const SpriteID * const _landscape_spriteindexes[] = {
33 	_landscape_spriteindexes_arctic,
34 	_landscape_spriteindexes_tropic,
35 	_landscape_spriteindexes_toyland,
36 };
37 
38 /**
39  * Load an old fashioned GRF file.
40  * @param filename   The name of the file to open.
41  * @param load_index The offset of the first sprite.
42  * @param needs_palette_remap Whether the colours in the GRF file need a palette remap.
43  * @return The number of loaded sprites.
44  */
LoadGrfFile(const char * filename,uint load_index,bool needs_palette_remap)45 static uint LoadGrfFile(const char *filename, uint load_index, bool needs_palette_remap)
46 {
47 	uint load_index_org = load_index;
48 	uint sprite_id = 0;
49 
50 	SpriteFile &file = OpenCachedSpriteFile(filename, BASESET_DIR, needs_palette_remap);
51 
52 	Debug(sprite, 2, "Reading grf-file '{}'", filename);
53 
54 	byte container_ver = file.GetContainerVersion();
55 	if (container_ver == 0) usererror("Base grf '%s' is corrupt", filename);
56 	ReadGRFSpriteOffsets(file);
57 	if (container_ver >= 2) {
58 		/* Read compression. */
59 		byte compression = file.ReadByte();
60 		if (compression != 0) usererror("Unsupported compression format");
61 	}
62 
63 	while (LoadNextSprite(load_index, file, sprite_id)) {
64 		load_index++;
65 		sprite_id++;
66 		if (load_index >= MAX_SPRITES) {
67 			usererror("Too many sprites. Recompile with higher MAX_SPRITES value or remove some custom GRF files.");
68 		}
69 	}
70 	Debug(sprite, 2, "Currently {} sprites are loaded", load_index);
71 
72 	return load_index - load_index_org;
73 }
74 
75 /**
76  * Load an old fashioned GRF file to replace already loaded sprites.
77  * @param filename   The name of the file to open.
78  * @param index_tbl  The offsets of each of the sprites.
79  * @param needs_palette_remap Whether the colours in the GRF file need a palette remap.
80  * @return The number of loaded sprites.
81  */
LoadGrfFileIndexed(const char * filename,const SpriteID * index_tbl,bool needs_palette_remap)82 static void LoadGrfFileIndexed(const char *filename, const SpriteID *index_tbl, bool needs_palette_remap)
83 {
84 	uint start;
85 	uint sprite_id = 0;
86 
87 	SpriteFile &file = OpenCachedSpriteFile(filename, BASESET_DIR, needs_palette_remap);
88 
89 	Debug(sprite, 2, "Reading indexed grf-file '{}'", filename);
90 
91 	byte container_ver = file.GetContainerVersion();
92 	if (container_ver == 0) usererror("Base grf '%s' is corrupt", filename);
93 	ReadGRFSpriteOffsets(file);
94 	if (container_ver >= 2) {
95 		/* Read compression. */
96 		byte compression = file.ReadByte();
97 		if (compression != 0) usererror("Unsupported compression format");
98 	}
99 
100 	while ((start = *index_tbl++) != END) {
101 		uint end = *index_tbl++;
102 
103 		do {
104 			[[maybe_unused]] bool b = LoadNextSprite(start, file, sprite_id);
105 			assert(b);
106 			sprite_id++;
107 		} while (++start <= end);
108 	}
109 }
110 
111 /**
112  * Checks whether the MD5 checksums of the files are correct.
113  *
114  * @note Also checks sample.cat and other required non-NewGRF GRFs for corruption.
115  */
CheckExternalFiles()116 void CheckExternalFiles()
117 {
118 	if (BaseGraphics::GetUsedSet() == nullptr || BaseSounds::GetUsedSet() == nullptr) return;
119 
120 	const GraphicsSet *used_set = BaseGraphics::GetUsedSet();
121 
122 	Debug(grf, 1, "Using the {} base graphics set", used_set->name);
123 
124 	static const size_t ERROR_MESSAGE_LENGTH = 256;
125 	static const size_t MISSING_FILE_MESSAGE_LENGTH = 128;
126 
127 	/* Allocate for a message for each missing file and for one error
128 	 * message per set.
129 	 */
130 	char error_msg[MISSING_FILE_MESSAGE_LENGTH * (GraphicsSet::NUM_FILES + SoundsSet::NUM_FILES) + 2 * ERROR_MESSAGE_LENGTH];
131 	error_msg[0] = '\0';
132 	char *add_pos = error_msg;
133 	const char *last = lastof(error_msg);
134 
135 	if (used_set->GetNumInvalid() != 0) {
136 		/* Not all files were loaded successfully, see which ones */
137 		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. See section 4.1 of README.md.\n\nThe following files are corrupted or missing:\n", used_set->name.c_str());
138 		for (uint i = 0; i < GraphicsSet::NUM_FILES; i++) {
139 			MD5File::ChecksumResult res = GraphicsSet::CheckMD5(&used_set->files[i], BASESET_DIR);
140 			if (res != MD5File::CR_MATCH) add_pos += seprintf(add_pos, last, "\t%s is %s (%s)\n", used_set->files[i].filename, res == MD5File::CR_MISMATCH ? "corrupt" : "missing", used_set->files[i].missing_warning);
141 		}
142 		add_pos += seprintf(add_pos, last, "\n");
143 	}
144 
145 	const SoundsSet *sounds_set = BaseSounds::GetUsedSet();
146 	if (sounds_set->GetNumInvalid() != 0) {
147 		add_pos += seprintf(add_pos, last, "Trying to load sound set '%s', but it is incomplete. The game will probably not run correctly until you properly install this set or select another one. See section 4.1 of README.md.\n\nThe following files are corrupted or missing:\n", sounds_set->name.c_str());
148 
149 		static_assert(SoundsSet::NUM_FILES == 1);
150 		/* No need to loop each file, as long as there is only a single
151 		 * sound file. */
152 		add_pos += seprintf(add_pos, last, "\t%s is %s (%s)\n", sounds_set->files->filename, SoundsSet::CheckMD5(sounds_set->files, BASESET_DIR) == MD5File::CR_MISMATCH ? "corrupt" : "missing", sounds_set->files->missing_warning);
153 	}
154 
155 	if (add_pos != error_msg) ShowInfoF("%s", error_msg);
156 }
157 
158 /** Actually load the sprite tables. */
LoadSpriteTables()159 static void LoadSpriteTables()
160 {
161 	const GraphicsSet *used_set = BaseGraphics::GetUsedSet();
162 
163 	LoadGrfFile(used_set->files[GFT_BASE].filename, 0, PAL_DOS != used_set->palette);
164 
165 	/*
166 	 * The second basic file always starts at the given location and does
167 	 * contain a different amount of sprites depending on the "type"; DOS
168 	 * has a few sprites less. However, we do not care about those missing
169 	 * sprites as they are not shown anyway (logos in intro game).
170 	 */
171 	LoadGrfFile(used_set->files[GFT_LOGOS].filename, 4793, PAL_DOS != used_set->palette);
172 
173 	/*
174 	 * Load additional sprites for climates other than temperate.
175 	 * This overwrites some of the temperate sprites, such as foundations
176 	 * and the ground sprites.
177 	 */
178 	if (_settings_game.game_creation.landscape != LT_TEMPERATE) {
179 		LoadGrfFileIndexed(
180 			used_set->files[GFT_ARCTIC + _settings_game.game_creation.landscape - 1].filename,
181 			_landscape_spriteindexes[_settings_game.game_creation.landscape - 1],
182 			PAL_DOS != used_set->palette
183 		);
184 	}
185 
186 	/* Initialize the unicode to sprite mapping table */
187 	InitializeUnicodeGlyphMap();
188 
189 	/*
190 	 * Load the base and extra NewGRF with OTTD required graphics as first NewGRF.
191 	 * However, we do not want it to show up in the list of used NewGRFs,
192 	 * so we have to manually add it, and then remove it later.
193 	 */
194 	GRFConfig *top = _grfconfig;
195 
196 	/* Default extra graphics */
197 	static const char *master_filename = "OPENTTD.GRF";
198 	GRFConfig *master = new GRFConfig(master_filename);
199 	master->palette |= GRFP_GRF_DOS;
200 	FillGRFDetails(master, false, BASESET_DIR);
201 	ClrBit(master->flags, GCF_INIT_ONLY);
202 
203 	/* Baseset extra graphics */
204 	GRFConfig *extra = new GRFConfig(used_set->files[GFT_EXTRA].filename);
205 
206 	/* We know the palette of the base set, so if the base NewGRF is not
207 	 * setting one, use the palette of the base set and not the global
208 	 * one which might be the wrong palette for this base NewGRF.
209 	 * The value set here might be overridden via action14 later. */
210 	switch (used_set->palette) {
211 		case PAL_DOS:     extra->palette |= GRFP_GRF_DOS;     break;
212 		case PAL_WINDOWS: extra->palette |= GRFP_GRF_WINDOWS; break;
213 		default: break;
214 	}
215 	FillGRFDetails(extra, false, BASESET_DIR);
216 	ClrBit(extra->flags, GCF_INIT_ONLY);
217 
218 	extra->next = top;
219 	master->next = extra;
220 	_grfconfig = master;
221 
222 	LoadNewGRF(SPR_NEWGRFS_BASE, 2);
223 
224 	uint total_extra_graphics = SPR_NEWGRFS_BASE - SPR_OPENTTD_BASE;
225 	_missing_extra_graphics = GetSpriteCountForFile(master_filename, SPR_OPENTTD_BASE, SPR_NEWGRFS_BASE);
226 	Debug(sprite, 1, "{} extra sprites, {} from baseset, {} from fallback", total_extra_graphics, total_extra_graphics - _missing_extra_graphics, _missing_extra_graphics);
227 
228 	/* The original baseset extra graphics intentionally make use of the fallback graphics.
229 	 * Let's say everything which provides less than 500 sprites misses the rest intentionally. */
230 	if (500 + _missing_extra_graphics > total_extra_graphics) _missing_extra_graphics = 0;
231 
232 	/* Free and remove the top element. */
233 	delete extra;
234 	delete master;
235 	_grfconfig = top;
236 }
237 
238 
RealChangeBlitter(const char * repl_blitter)239 static void RealChangeBlitter(const char *repl_blitter)
240 {
241 	const char *cur_blitter = BlitterFactory::GetCurrentBlitter()->GetName();
242 	if (strcmp(cur_blitter, repl_blitter) == 0) return;
243 
244 	Debug(driver, 1, "Switching blitter from '{}' to '{}'... ", cur_blitter, repl_blitter);
245 	Blitter *new_blitter = BlitterFactory::SelectBlitter(repl_blitter);
246 	if (new_blitter == nullptr) NOT_REACHED();
247 	Debug(driver, 1, "Successfully switched to {}.", repl_blitter);
248 
249 	if (!VideoDriver::GetInstance()->AfterBlitterChange()) {
250 		/* Failed to switch blitter, let's hope we can return to the old one. */
251 		if (BlitterFactory::SelectBlitter(cur_blitter) == nullptr || !VideoDriver::GetInstance()->AfterBlitterChange()) usererror("Failed to reinitialize video driver. Specify a fixed blitter in the config");
252 	}
253 
254 	/* Clear caches that might have sprites for another blitter. */
255 	VideoDriver::GetInstance()->ClearSystemSprites();
256 	ClearFontCache();
257 	GfxClearSpriteCache();
258 	ReInitAllWindows(false);
259 }
260 
261 /**
262  * Check blitter needed by NewGRF config and switch if needed.
263  * @return False when nothing changed, true otherwise.
264  */
SwitchNewGRFBlitter()265 static bool SwitchNewGRFBlitter()
266 {
267 	/* Never switch if the blitter was specified by the user. */
268 	if (!_blitter_autodetected) return false;
269 
270 	/* Null driver => dedicated server => do nothing. */
271 	if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) return false;
272 
273 	/* Get preferred depth.
274 	 *  - depth_wanted_by_base: Depth required by the baseset, i.e. the majority of the sprites.
275 	 *  - depth_wanted_by_grf:  Depth required by some NewGRF.
276 	 * Both can force using a 32bpp blitter. depth_wanted_by_base is used to select
277 	 * between multiple 32bpp blitters, which perform differently with 8bpp sprites.
278 	 */
279 	uint depth_wanted_by_base = BaseGraphics::GetUsedSet()->blitter == BLT_32BPP ? 32 : 8;
280 	uint depth_wanted_by_grf = _support8bpp != S8BPP_NONE ? 8 : 32;
281 	for (GRFConfig *c = _grfconfig; c != nullptr; c = c->next) {
282 		if (c->status == GCS_DISABLED || c->status == GCS_NOT_FOUND || HasBit(c->flags, GCF_INIT_ONLY)) continue;
283 		if (c->palette & GRFP_BLT_32BPP) depth_wanted_by_grf = 32;
284 	}
285 	/* We need a 32bpp blitter for font anti-alias. */
286 	if (HasAntialiasedFonts()) depth_wanted_by_grf = 32;
287 
288 	/* Search the best blitter. */
289 	static const struct {
290 		const char *name;
291 		uint animation; ///< 0: no support, 1: do support, 2: both
292 		uint min_base_depth, max_base_depth, min_grf_depth, max_grf_depth;
293 	} replacement_blitters[] = {
294 		{ "8bpp-optimized",  2,  8,  8,  8,  8 },
295 		{ "40bpp-anim",      2,  8, 32,  8, 32 },
296 #ifdef WITH_SSE
297 		{ "32bpp-sse4",      0, 32, 32,  8, 32 },
298 		{ "32bpp-ssse3",     0, 32, 32,  8, 32 },
299 		{ "32bpp-sse2",      0, 32, 32,  8, 32 },
300 		{ "32bpp-sse4-anim", 1, 32, 32,  8, 32 },
301 #endif
302 		{ "32bpp-optimized", 0,  8, 32,  8, 32 },
303 #ifdef WITH_SSE
304 		{ "32bpp-sse2-anim", 1,  8, 32,  8, 32 },
305 #endif
306 		{ "32bpp-anim",      1,  8, 32,  8, 32 },
307 	};
308 
309 	const bool animation_wanted = HasBit(_display_opt, DO_FULL_ANIMATION);
310 	const char *cur_blitter = BlitterFactory::GetCurrentBlitter()->GetName();
311 
312 	for (uint i = 0; i < lengthof(replacement_blitters); i++) {
313 		if (animation_wanted && (replacement_blitters[i].animation == 0)) continue;
314 		if (!animation_wanted && (replacement_blitters[i].animation == 1)) continue;
315 
316 		if (!IsInsideMM(depth_wanted_by_base, replacement_blitters[i].min_base_depth, replacement_blitters[i].max_base_depth + 1)) continue;
317 		if (!IsInsideMM(depth_wanted_by_grf, replacement_blitters[i].min_grf_depth, replacement_blitters[i].max_grf_depth + 1)) continue;
318 		const char *repl_blitter = replacement_blitters[i].name;
319 
320 		if (strcmp(repl_blitter, cur_blitter) == 0) {
321 			return false;
322 		}
323 		if (BlitterFactory::GetBlitterFactory(repl_blitter) == nullptr) continue;
324 
325 		/* Inform the video driver we want to switch blitter as soon as possible. */
326 		VideoDriver::GetInstance()->QueueOnMainThread(std::bind(&RealChangeBlitter, repl_blitter));
327 		break;
328 	}
329 
330 	return true;
331 }
332 
333 /** Check whether we still use the right blitter, or use another (better) one. */
CheckBlitter()334 void CheckBlitter()
335 {
336 	if (!SwitchNewGRFBlitter()) return;
337 
338 	ClearFontCache();
339 	GfxClearSpriteCache();
340 	ReInitAllWindows(false);
341 }
342 
343 /** Initialise and load all the sprites. */
GfxLoadSprites()344 void GfxLoadSprites()
345 {
346 	Debug(sprite, 2, "Loading sprite set {}", _settings_game.game_creation.landscape);
347 
348 	SwitchNewGRFBlitter();
349 	VideoDriver::GetInstance()->ClearSystemSprites();
350 	ClearFontCache();
351 	GfxInitSpriteMem();
352 	LoadSpriteTables();
353 	GfxInitPalettes();
354 
355 	UpdateCursorSize();
356 }
357 
FillSetDetails(IniFile * ini,const char * path,const char * full_filename)358 bool GraphicsSet::FillSetDetails(IniFile *ini, const char *path, const char *full_filename)
359 {
360 	bool ret = this->BaseSet<GraphicsSet, MAX_GFT, true>::FillSetDetails(ini, path, full_filename, false);
361 	if (ret) {
362 		IniGroup *metadata = ini->GetGroup("metadata");
363 		IniItem *item;
364 
365 		fetch_metadata("palette");
366 		this->palette = ((*item->value)[0] == 'D' || (*item->value)[0] == 'd') ? PAL_DOS : PAL_WINDOWS;
367 
368 		/* Get optional blitter information. */
369 		item = metadata->GetItem("blitter", false);
370 		this->blitter = (item != nullptr && (*item->value)[0] == '3') ? BLT_32BPP : BLT_8BPP;
371 	}
372 	return ret;
373 }
374 
375 /**
376  * Calculate and check the MD5 hash of the supplied GRF.
377  * @param file The file get the hash of.
378  * @param subdir The sub directory to get the files from.
379  * @return
380  * - #CR_MATCH if the MD5 hash matches
381  * - #CR_MISMATCH if the MD5 does not match
382  * - #CR_NO_FILE if the file misses
383  */
CheckMD5(const MD5File * file,Subdirectory subdir)384 /* static */ MD5File::ChecksumResult GraphicsSet::CheckMD5(const MD5File *file, Subdirectory subdir)
385 {
386 	size_t size = 0;
387 	FILE *f = FioFOpenFile(file->filename, "rb", subdir, &size);
388 	if (f == nullptr) return MD5File::CR_NO_FILE;
389 
390 	size_t max = GRFGetSizeOfDataSection(f);
391 
392 	FioFCloseFile(f);
393 
394 	return file->CheckMD5(subdir, max);
395 }
396 
397 
398 /**
399  * Calculate and check the MD5 hash of the supplied filename.
400  * @param subdir The sub directory to get the files from
401  * @param max_size Only calculate the hash for this many bytes from the file start.
402  * @return
403  * - #CR_MATCH if the MD5 hash matches
404  * - #CR_MISMATCH if the MD5 does not match
405  * - #CR_NO_FILE if the file misses
406  */
CheckMD5(Subdirectory subdir,size_t max_size) const407 MD5File::ChecksumResult MD5File::CheckMD5(Subdirectory subdir, size_t max_size) const
408 {
409 	size_t size;
410 	FILE *f = FioFOpenFile(this->filename, "rb", subdir, &size);
411 
412 	if (f == nullptr) return CR_NO_FILE;
413 
414 	size = std::min(size, max_size);
415 
416 	Md5 checksum;
417 	uint8 buffer[1024];
418 	uint8 digest[16];
419 	size_t len;
420 
421 	while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, f)) != 0 && size != 0) {
422 		size -= len;
423 		checksum.Append(buffer, len);
424 	}
425 
426 	FioFCloseFile(f);
427 
428 	checksum.Finish(digest);
429 	return memcmp(this->hash, digest, sizeof(this->hash)) == 0 ? CR_MATCH : CR_MISMATCH;
430 }
431 
432 /** Names corresponding to the GraphicsFileType */
433 static const char * const _graphics_file_names[] = { "base", "logos", "arctic", "tropical", "toyland", "extra" };
434 
435 /** Implementation */
436 template <class T, size_t Tnum_files, bool Tsearch_in_tars>
437 /* static */ const char * const *BaseSet<T, Tnum_files, Tsearch_in_tars>::file_names = _graphics_file_names;
438 
439 template <class Tbase_set>
DetermineBestSet()440 /* static */ bool BaseMedia<Tbase_set>::DetermineBestSet()
441 {
442 	if (BaseMedia<Tbase_set>::used_set != nullptr) return true;
443 
444 	const Tbase_set *best = nullptr;
445 	for (const Tbase_set *c = BaseMedia<Tbase_set>::available_sets; c != nullptr; c = c->next) {
446 		/* Skip unusable sets */
447 		if (c->GetNumMissing() != 0) continue;
448 
449 		if (best == nullptr ||
450 				(best->fallback && !c->fallback) ||
451 				best->valid_files < c->valid_files ||
452 				(best->valid_files == c->valid_files && (
453 					(best->shortname == c->shortname && best->version < c->version) ||
454 					(best->palette != PAL_DOS && c->palette == PAL_DOS)))) {
455 			best = c;
456 		}
457 	}
458 
459 	BaseMedia<Tbase_set>::used_set = best;
460 	return BaseMedia<Tbase_set>::used_set != nullptr;
461 }
462 
463 template <class Tbase_set>
GetExtension()464 /* static */ const char *BaseMedia<Tbase_set>::GetExtension()
465 {
466 	return ".obg"; // OpenTTD Base Graphics
467 }
468 
469 INSTANTIATE_BASE_MEDIA_METHODS(BaseMedia<GraphicsSet>, GraphicsSet)
470