summaryrefslogtreecommitdiff
path: root/src/base_media_base.h
blob: 0b006efbea70ac2d47aa01c9e282560fcd2eb3fc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
/*
 * This file is part of OpenTTD.
 * 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.
 * 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.
 * 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/>.
 */

/** @file base_media_base.h Generic functions for replacing base data (graphics, sounds). */

#ifndef BASE_MEDIA_BASE_H
#define BASE_MEDIA_BASE_H

#include "fileio_func.h"
#include "core/smallmap_type.hpp"
#include "gfx_type.h"
#include "textfile_type.h"
#include "textfile_gui.h"

/* Forward declare these; can't do 'struct X' in functions as older GCCs barf on that */
struct IniFile;
struct ContentInfo;

/** Structure holding filename and MD5 information about a single file */
struct MD5File {
	/** The result of a checksum check */
	enum ChecksumResult {
		CR_UNKNOWN,  ///< The file has not been checked yet
		CR_MATCH,    ///< The file did exist and the md5 checksum did match
		CR_MISMATCH, ///< The file did exist, just the md5 checksum did not match
		CR_NO_FILE,  ///< The file did not exist
	};

	const char *filename;        ///< filename
	uint8 hash[16];              ///< md5 sum of the file
	const char *missing_warning; ///< warning when this file is missing
	ChecksumResult check_result; ///< cached result of md5 check

	ChecksumResult CheckMD5(Subdirectory subdir, size_t max_size) const;
};

/**
 * Information about a single base set.
 * @tparam T the real class we're going to be
 * @tparam Tnum_files the number of files in the set
 * @tparam Tsearch_in_tars whether to search in the tars or not
 */
template <class T, size_t Tnum_files, bool Tsearch_in_tars>
struct BaseSet {
	typedef SmallMap<const char *, const char *> TranslatedStrings;

	/** Number of files in this set */
	static const size_t NUM_FILES = Tnum_files;

	/** Whether to search in the tars or not. */
	static const bool SEARCH_IN_TARS = Tsearch_in_tars;

	/** Internal names of the files in this set. */
	static const char * const *file_names;

	const char *name;              ///< The name of the base set
	TranslatedStrings description; ///< Description of the base set
	uint32 shortname;              ///< Four letter short variant of the name
	uint32 version;                ///< The version of this base set
	bool fallback;                 ///< This set is a fallback set, i.e. it should be used only as last resort

	MD5File files[NUM_FILES];      ///< All files part of this set
	uint found_files;              ///< Number of the files that could be found
	uint valid_files;              ///< Number of the files that could be found and are valid

	T *next;                       ///< The next base set in this list

	/** Free everything we allocated */
	~BaseSet()
	{
		free(this->name);

		for (auto &pair : this->description) {
			free(pair.first);
			free(pair.second);
		}

		for (uint i = 0; i < NUM_FILES; i++) {
			free(this->files[i].filename);
			free(this->files[i].missing_warning);
		}

		delete this->next;
	}

	/**
	 * Get the number of missing files.
	 * @return the number
	 */
	int GetNumMissing() const
	{
		return Tnum_files - this->found_files;
	}

	/**
	 * Get the number of invalid files.
	 * @note a missing file is invalid too!
	 * @return the number
	 */
	int GetNumInvalid() const
	{
		return Tnum_files - this->valid_files;
	}

	bool FillSetDetails(IniFile *ini, const char *path, const char *full_filename, bool allow_empty_filename = true);

	/**
	 * Get the description for the given ISO code.
	 * It falls back to the first two characters of the ISO code in case
	 * no match could be made with the full ISO code. If even then the
	 * matching fails the default is returned.
	 * @param isocode the isocode to search for
	 * @return the description
	 */
	const char *GetDescription(const char *isocode = nullptr) const
	{
		if (isocode != nullptr) {
			/* First the full ISO code */
			for (const auto &pair : this->description) {
				if (strcmp(pair.first, isocode) == 0) return pair.second;
			}
			/* Then the first two characters */
			for (const auto &pair : this->description) {
				if (strncmp(pair.first, isocode, 2) == 0) return pair.second;
			}
		}
		/* Then fall back */
		return this->description.front().second;
	}

	/**
	 * Calculate and check the MD5 hash of the supplied file.
	 * @param file The file get the hash of.
	 * @param subdir The sub directory to get the files from.
	 * @return
	 * - #CR_MATCH if the MD5 hash matches
	 * - #CR_MISMATCH if the MD5 does not match
	 * - #CR_NO_FILE if the file misses
	 */
	static MD5File::ChecksumResult CheckMD5(const MD5File *file, Subdirectory subdir)
	{
		return file->CheckMD5(subdir, SIZE_MAX);
	}

	/**
	 * Search a textfile file next to this base media.
	 * @param type The type of the textfile to search for.
	 * @return The filename for the textfile, \c nullptr otherwise.
	 */
	const char *GetTextfile(TextfileType type) const
	{
		for (uint i = 0; i < NUM_FILES; i++) {
			const char *textfile = ::GetTextfile(type, BASESET_DIR, this->files[i].filename);
			if (textfile != nullptr) {
				return textfile;
			}
		}
		return nullptr;
	}
};

/**
 * Base for all base media (graphics, sounds)
 * @tparam Tbase_set the real set we're going to be
 */
template <class Tbase_set>
class BaseMedia : FileScanner {
protected:
	static Tbase_set *available_sets; ///< All available sets
	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.
	static const Tbase_set *used_set; ///< The currently used set

	bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename) override;

	/**
	 * Get the extension that is used to identify this set.
	 * @return the extension
	 */
	static const char *GetExtension();
public:
	/** The set as saved in the config file. */
	static const char *ini_set;

	/**
	 * Determine the graphics pack that has to be used.
	 * The one with the most correct files wins.
	 * @return true if a best set has been found.
	 */
	static bool DetermineBestSet();

	/** Do the scan for files. */
	static uint FindSets()
	{
		BaseMedia<Tbase_set> fs;
		/* Searching in tars is only done in the old "data" directories basesets. */
		uint num = fs.Scan(GetExtension(), Tbase_set::SEARCH_IN_TARS ? OLD_DATA_DIR : OLD_GM_DIR, Tbase_set::SEARCH_IN_TARS);
		return num + fs.Scan(GetExtension(), BASESET_DIR, Tbase_set::SEARCH_IN_TARS);
	}

	static Tbase_set *GetAvailableSets();

	static bool SetSet(const char *name);
	static char *GetSetsList(char *p, const char *last);
	static int GetNumSets();
	static int GetIndexOfUsedSet();
	static const Tbase_set *GetSet(int index);
	static const Tbase_set *GetUsedSet();

	/**
	 * Check whether we have an set with the exact characteristics as ci.
	 * @param ci the characteristics to search on (shortname and md5sum)
	 * @param md5sum whether to check the MD5 checksum
	 * @return true iff we have an set matching.
	 */
	static bool HasSet(const ContentInfo *ci, bool md5sum);
};

template <class Tbase_set> /* static */ const char *BaseMedia<Tbase_set>::ini_set;
template <class Tbase_set> /* static */ const Tbase_set *BaseMedia<Tbase_set>::used_set;
template <class Tbase_set> /* static */ Tbase_set *BaseMedia<Tbase_set>::available_sets;
template <class Tbase_set> /* static */ Tbase_set *BaseMedia<Tbase_set>::duplicate_sets;

/**
 * Check whether there's a base set matching some information.
 * @param ci The content info to compare it to.
 * @param md5sum Should the MD5 checksum be tested as well?
 * @param s The list with sets.
 * @return The filename of the first file of the base set, or \c nullptr if there is no match.
 */
template <class Tbase_set>
const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const Tbase_set *s);

/** Types of graphics in the base graphics set */
enum GraphicsFileType {
	GFT_BASE,     ///< Base sprites for all climates
	GFT_LOGOS,    ///< Logos, landscape icons and original terrain generator sprites
	GFT_ARCTIC,   ///< Landscape replacement sprites for arctic
	GFT_TROPICAL, ///< Landscape replacement sprites for tropical
	GFT_TOYLAND,  ///< Landscape replacement sprites for toyland
	GFT_EXTRA,    ///< Extra sprites that were not part of the original sprites
	MAX_GFT,      ///< We are looking for this amount of GRFs
};

/** Blitter type for base graphics sets. */
enum BlitterType {
	BLT_8BPP,       ///< Base set has 8 bpp sprites only.
	BLT_32BPP,      ///< Base set has both 8 bpp and 32 bpp sprites.
};

/** All data of a graphics set. */
struct GraphicsSet : BaseSet<GraphicsSet, MAX_GFT, true> {
	PaletteType palette;       ///< Palette of this graphics set
	BlitterType blitter;       ///< Blitter of this graphics set

	bool FillSetDetails(struct IniFile *ini, const char *path, const char *full_filename);

	static MD5File::ChecksumResult CheckMD5(const MD5File *file, Subdirectory subdir);
};

/** All data/functions related with replacing the base graphics. */
class BaseGraphics : public BaseMedia<GraphicsSet> {
public:
};

/** All data of a sounds set. */
struct SoundsSet : BaseSet<SoundsSet, 1, true> {
};

/** All data/functions related with replacing the base sounds */
class BaseSounds : public BaseMedia<SoundsSet> {
public:
};

/** Maximum number of songs in the 'class' playlists. */
static const uint NUM_SONGS_CLASS     = 10;
/** Number of classes for songs */
static const uint NUM_SONG_CLASSES    = 3;
/** Maximum number of songs in the full playlist; theme song + the classes */
static const uint NUM_SONGS_AVAILABLE = 1 + NUM_SONG_CLASSES * NUM_SONGS_CLASS;

/** Maximum number of songs in the (custom) playlist */
static const uint NUM_SONGS_PLAYLIST  = 32;

/* Functions to read DOS music CAT files, similar to but not quite the same as sound effect CAT files */
char *GetMusicCatEntryName(const char *filename, size_t entrynum);
byte *GetMusicCatEntryData(const char *filename, size_t entrynum, size_t &entrylen);

enum MusicTrackType {
	MTT_STANDARDMIDI, ///< Standard MIDI file
	MTT_MPSMIDI,      ///< MPS GM driver MIDI format (contained in a CAT file)
};

/** Metadata about a music track. */
struct MusicSongInfo {
	char songname[32];       ///< name of song displayed in UI
	byte tracknr;            ///< track number of song displayed in UI
	const char *filename;    ///< file on disk containing song (when used in MusicSet class, this pointer is owned by MD5File object for the file)
	MusicTrackType filetype; ///< decoder required for song file
	int cat_index;           ///< entry index in CAT file, for filetype==MTT_MPSMIDI
	bool loop;               ///< song should play in a tight loop if possible, never ending
	int override_start;      ///< MIDI ticks to skip over in beginning
	int override_end;        ///< MIDI tick to end the song at (0 if no override)
};

/** All data of a music set. */
struct MusicSet : BaseSet<MusicSet, NUM_SONGS_AVAILABLE, false> {
	/** Data about individual songs in set. */
	MusicSongInfo songinfo[NUM_SONGS_AVAILABLE];
	/** Number of valid songs in set. */
	byte num_available;

	bool FillSetDetails(struct IniFile *ini, const char *path, const char *full_filename);
};

/** All data/functions related with replacing the base music */
class BaseMusic : public BaseMedia<MusicSet> {
public:
};

#endif /* BASE_MEDIA_BASE_H */