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 base_media_base.h Generic functions for replacing base data (graphics, sounds). */
9 
10 #ifndef BASE_MEDIA_BASE_H
11 #define BASE_MEDIA_BASE_H
12 
13 #include "fileio_func.h"
14 #include "core/smallmap_type.hpp"
15 #include "gfx_type.h"
16 #include "textfile_type.h"
17 #include "textfile_gui.h"
18 #include <unordered_map>
19 
20 /* Forward declare these; can't do 'struct X' in functions as older GCCs barf on that */
21 struct IniFile;
22 struct ContentInfo;
23 
24 /** Structure holding filename and MD5 information about a single file */
25 struct MD5File {
26 	/** The result of a checksum check */
27 	enum ChecksumResult {
28 		CR_UNKNOWN,  ///< The file has not been checked yet
29 		CR_MATCH,    ///< The file did exist and the md5 checksum did match
30 		CR_MISMATCH, ///< The file did exist, just the md5 checksum did not match
31 		CR_NO_FILE,  ///< The file did not exist
32 	};
33 
34 	const char *filename;        ///< filename
35 	uint8 hash[16];              ///< md5 sum of the file
36 	const char *missing_warning; ///< warning when this file is missing
37 	ChecksumResult check_result; ///< cached result of md5 check
38 
39 	ChecksumResult CheckMD5(Subdirectory subdir, size_t max_size) const;
40 };
41 
42 /**
43  * Information about a single base set.
44  * @tparam T the real class we're going to be
45  * @tparam Tnum_files the number of files in the set
46  * @tparam Tsearch_in_tars whether to search in the tars or not
47  */
48 template <class T, size_t Tnum_files, bool Tsearch_in_tars>
49 struct BaseSet {
50 	typedef std::unordered_map<std::string, std::string> TranslatedStrings;
51 
52 	/** Number of files in this set */
53 	static const size_t NUM_FILES = Tnum_files;
54 
55 	/** Whether to search in the tars or not. */
56 	static const bool SEARCH_IN_TARS = Tsearch_in_tars;
57 
58 	/** Internal names of the files in this set. */
59 	static const char * const *file_names;
60 
61 	std::string name;              ///< The name of the base set
62 	TranslatedStrings description; ///< Description of the base set
63 	uint32 shortname;              ///< Four letter short variant of the name
64 	uint32 version;                ///< The version of this base set
65 	bool fallback;                 ///< This set is a fallback set, i.e. it should be used only as last resort
66 
67 	MD5File files[NUM_FILES];      ///< All files part of this set
68 	uint found_files;              ///< Number of the files that could be found
69 	uint valid_files;              ///< Number of the files that could be found and are valid
70 
71 	T *next;                       ///< The next base set in this list
72 
73 	/** Free everything we allocated */
~BaseSetBaseSet74 	~BaseSet()
75 	{
76 		for (uint i = 0; i < NUM_FILES; i++) {
77 			free(this->files[i].filename);
78 			free(this->files[i].missing_warning);
79 		}
80 
81 		delete this->next;
82 	}
83 
84 	/**
85 	 * Get the number of missing files.
86 	 * @return the number
87 	 */
GetNumMissingBaseSet88 	int GetNumMissing() const
89 	{
90 		return Tnum_files - this->found_files;
91 	}
92 
93 	/**
94 	 * Get the number of invalid files.
95 	 * @note a missing file is invalid too!
96 	 * @return the number
97 	 */
GetNumInvalidBaseSet98 	int GetNumInvalid() const
99 	{
100 		return Tnum_files - this->valid_files;
101 	}
102 
103 	bool FillSetDetails(IniFile *ini, const char *path, const char *full_filename, bool allow_empty_filename = true);
104 
105 	/**
106 	 * Get the description for the given ISO code.
107 	 * It falls back to the first two characters of the ISO code in case
108 	 * no match could be made with the full ISO code. If even then the
109 	 * matching fails the default is returned.
110 	 * @param isocode the isocode to search for
111 	 * @return the description
112 	 */
GetDescriptionBaseSet113 	const char *GetDescription(const std::string &isocode) const
114 	{
115 		if (!isocode.empty()) {
116 			/* First the full ISO code */
117 			auto desc = this->description.find(isocode);
118 			if (desc != this->description.end()) return desc->second.c_str();
119 
120 			/* Then the first two characters */
121 			desc = this->description.find(isocode.substr(0, 2));
122 			if (desc != this->description.end()) return desc->second.c_str();
123 		}
124 		/* Then fall back */
125 		return this->description.at(std::string{}).c_str();
126 	}
127 
128 	/**
129 	 * Calculate and check the MD5 hash of the supplied file.
130 	 * @param file The file get the hash of.
131 	 * @param subdir The sub directory to get the files from.
132 	 * @return
133 	 * - #CR_MATCH if the MD5 hash matches
134 	 * - #CR_MISMATCH if the MD5 does not match
135 	 * - #CR_NO_FILE if the file misses
136 	 */
CheckMD5BaseSet137 	static MD5File::ChecksumResult CheckMD5(const MD5File *file, Subdirectory subdir)
138 	{
139 		return file->CheckMD5(subdir, SIZE_MAX);
140 	}
141 
142 	/**
143 	 * Search a textfile file next to this base media.
144 	 * @param type The type of the textfile to search for.
145 	 * @return The filename for the textfile, \c nullptr otherwise.
146 	 */
GetTextfileBaseSet147 	const char *GetTextfile(TextfileType type) const
148 	{
149 		for (uint i = 0; i < NUM_FILES; i++) {
150 			const char *textfile = ::GetTextfile(type, BASESET_DIR, this->files[i].filename);
151 			if (textfile != nullptr) {
152 				return textfile;
153 			}
154 		}
155 		return nullptr;
156 	}
157 };
158 
159 /**
160  * Base for all base media (graphics, sounds)
161  * @tparam Tbase_set the real set we're going to be
162  */
163 template <class Tbase_set>
164 class BaseMedia : FileScanner {
165 protected:
166 	static Tbase_set *available_sets; ///< All available sets
167 	static Tbase_set *duplicate_sets; ///< All sets that aren't available, but needed for not downloading base sets when a newer version than the one on BaNaNaS is loaded.
168 	static const Tbase_set *used_set; ///< The currently used set
169 
170 	bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename) override;
171 
172 	/**
173 	 * Get the extension that is used to identify this set.
174 	 * @return the extension
175 	 */
176 	static const char *GetExtension();
177 public:
178 	/** The set as saved in the config file. */
179 	static std::string ini_set;
180 
181 	/**
182 	 * Determine the graphics pack that has to be used.
183 	 * The one with the most correct files wins.
184 	 * @return true if a best set has been found.
185 	 */
186 	static bool DetermineBestSet();
187 
188 	/** Do the scan for files. */
FindSets()189 	static uint FindSets()
190 	{
191 		BaseMedia<Tbase_set> fs;
192 		/* Searching in tars is only done in the old "data" directories basesets. */
193 		uint num = fs.Scan(GetExtension(), Tbase_set::SEARCH_IN_TARS ? OLD_DATA_DIR : OLD_GM_DIR, Tbase_set::SEARCH_IN_TARS);
194 		return num + fs.Scan(GetExtension(), BASESET_DIR, Tbase_set::SEARCH_IN_TARS);
195 	}
196 
197 	static Tbase_set *GetAvailableSets();
198 
199 	static bool SetSet(const std::string &name);
200 	static char *GetSetsList(char *p, const char *last);
201 	static int GetNumSets();
202 	static int GetIndexOfUsedSet();
203 	static const Tbase_set *GetSet(int index);
204 	static const Tbase_set *GetUsedSet();
205 
206 	/**
207 	 * Check whether we have an set with the exact characteristics as ci.
208 	 * @param ci the characteristics to search on (shortname and md5sum)
209 	 * @param md5sum whether to check the MD5 checksum
210 	 * @return true iff we have an set matching.
211 	 */
212 	static bool HasSet(const ContentInfo *ci, bool md5sum);
213 };
214 
215 template <class Tbase_set> /* static */ std::string BaseMedia<Tbase_set>::ini_set;
216 template <class Tbase_set> /* static */ const Tbase_set *BaseMedia<Tbase_set>::used_set;
217 template <class Tbase_set> /* static */ Tbase_set *BaseMedia<Tbase_set>::available_sets;
218 template <class Tbase_set> /* static */ Tbase_set *BaseMedia<Tbase_set>::duplicate_sets;
219 
220 /**
221  * Check whether there's a base set matching some information.
222  * @param ci The content info to compare it to.
223  * @param md5sum Should the MD5 checksum be tested as well?
224  * @param s The list with sets.
225  * @return The filename of the first file of the base set, or \c nullptr if there is no match.
226  */
227 template <class Tbase_set>
228 const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const Tbase_set *s);
229 
230 /** Types of graphics in the base graphics set */
231 enum GraphicsFileType {
232 	GFT_BASE,     ///< Base sprites for all climates
233 	GFT_LOGOS,    ///< Logos, landscape icons and original terrain generator sprites
234 	GFT_ARCTIC,   ///< Landscape replacement sprites for arctic
235 	GFT_TROPICAL, ///< Landscape replacement sprites for tropical
236 	GFT_TOYLAND,  ///< Landscape replacement sprites for toyland
237 	GFT_EXTRA,    ///< Extra sprites that were not part of the original sprites
238 	MAX_GFT,      ///< We are looking for this amount of GRFs
239 };
240 
241 /** Blitter type for base graphics sets. */
242 enum BlitterType {
243 	BLT_8BPP,       ///< Base set has 8 bpp sprites only.
244 	BLT_32BPP,      ///< Base set has both 8 bpp and 32 bpp sprites.
245 };
246 
247 /** All data of a graphics set. */
248 struct GraphicsSet : BaseSet<GraphicsSet, MAX_GFT, true> {
249 	PaletteType palette;       ///< Palette of this graphics set
250 	BlitterType blitter;       ///< Blitter of this graphics set
251 
252 	bool FillSetDetails(struct IniFile *ini, const char *path, const char *full_filename);
253 
254 	static MD5File::ChecksumResult CheckMD5(const MD5File *file, Subdirectory subdir);
255 };
256 
257 /** All data/functions related with replacing the base graphics. */
258 class BaseGraphics : public BaseMedia<GraphicsSet> {
259 public:
260 };
261 
262 /** All data of a sounds set. */
263 struct SoundsSet : BaseSet<SoundsSet, 1, true> {
264 };
265 
266 /** All data/functions related with replacing the base sounds */
267 class BaseSounds : public BaseMedia<SoundsSet> {
268 public:
269 };
270 
271 /** Maximum number of songs in the 'class' playlists. */
272 static const uint NUM_SONGS_CLASS     = 10;
273 /** Number of classes for songs */
274 static const uint NUM_SONG_CLASSES    = 3;
275 /** Maximum number of songs in the full playlist; theme song + the classes */
276 static const uint NUM_SONGS_AVAILABLE = 1 + NUM_SONG_CLASSES * NUM_SONGS_CLASS;
277 
278 /** Maximum number of songs in the (custom) playlist */
279 static const uint NUM_SONGS_PLAYLIST  = 32;
280 
281 /* Functions to read DOS music CAT files, similar to but not quite the same as sound effect CAT files */
282 char *GetMusicCatEntryName(const char *filename, size_t entrynum);
283 byte *GetMusicCatEntryData(const char *filename, size_t entrynum, size_t &entrylen);
284 
285 enum MusicTrackType {
286 	MTT_STANDARDMIDI, ///< Standard MIDI file
287 	MTT_MPSMIDI,      ///< MPS GM driver MIDI format (contained in a CAT file)
288 };
289 
290 /** Metadata about a music track. */
291 struct MusicSongInfo {
292 	char songname[32];       ///< name of song displayed in UI
293 	byte tracknr;            ///< track number of song displayed in UI
294 	const char *filename;    ///< file on disk containing song (when used in MusicSet class, this pointer is owned by MD5File object for the file)
295 	MusicTrackType filetype; ///< decoder required for song file
296 	int cat_index;           ///< entry index in CAT file, for filetype==MTT_MPSMIDI
297 	bool loop;               ///< song should play in a tight loop if possible, never ending
298 	int override_start;      ///< MIDI ticks to skip over in beginning
299 	int override_end;        ///< MIDI tick to end the song at (0 if no override)
300 };
301 
302 /** All data of a music set. */
303 struct MusicSet : BaseSet<MusicSet, NUM_SONGS_AVAILABLE, false> {
304 	/** Data about individual songs in set. */
305 	MusicSongInfo songinfo[NUM_SONGS_AVAILABLE];
306 	/** Number of valid songs in set. */
307 	byte num_available;
308 
309 	bool FillSetDetails(struct IniFile *ini, const char *path, const char *full_filename);
310 };
311 
312 /** All data/functions related with replacing the base music */
313 class BaseMusic : public BaseMedia<MusicSet> {
314 public:
315 };
316 
317 #endif /* BASE_MEDIA_BASE_H */
318