Translations update
[openttd/fttd.git] / src / base_media_func.h
blob72c5426ffaf5cf0c036e06ab6890628adc237f19
1 /* $Id$ */
3 /*
4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
8 */
10 /**
11 * @file base_media_func.h Generic function implementations for base data (graphics, sounds).
14 #include "base_media_base.h"
15 #include "debug.h"
16 #include "ini_type.h"
17 #include "string.h"
19 template <class Tbase_set> /* static */ const char *BaseMedia<Tbase_set>::ini_set;
20 template <class Tbase_set> /* static */ const Tbase_set *BaseMedia<Tbase_set>::used_set;
21 template <class Tbase_set> /* static */ Tbase_set *BaseMedia<Tbase_set>::available_sets;
22 template <class Tbase_set> /* static */ Tbase_set *BaseMedia<Tbase_set>::duplicate_sets;
24 /**
25 * Try to read a single piece of metadata.
26 * @param metadata the metadata group to search in.
27 * @param name the name of the item to fetch.
28 * @param filename the name of the filename for debugging output.
29 * @return the associated item, or NULL if it doesn't exist.
31 template <typename T>
32 static inline const IniItem *fetch_metadata (const IniGroup *metadata,
33 const char *name, const char *filename)
35 const IniItem *item = metadata->find (name);
36 if (item == NULL || StrEmpty (item->value)) {
37 DEBUG (grf, 0, "Base %sset detail loading: %s field missing in %s.",
38 T::set_type, name, filename);
39 return NULL;
41 return item;
44 /**
45 * Read the set information from a loaded ini.
46 * @param ini the ini to read from
47 * @param path the path to this ini file (for filenames)
48 * @param full_filename the full filename of the loaded file (for error reporting purposes)
49 * @param allow_empty_filename empty filenames are valid
50 * @return true if loading was successful.
52 template <class T, size_t Tnum_files>
53 bool BaseSet<T, Tnum_files>::FillSetDetails (IniFile *ini, const char *path, const char *full_filename, bool allow_empty_filename)
55 memset (this->files, 0, sizeof(this->files));
56 this->found_files = 0;
57 this->valid_files = 0;
59 const IniGroup *metadata = ini->find ("metadata");
60 if (metadata == NULL) {
61 DEBUG (grf, 0, "Base %sset detail loading: metadata group missing.", T::set_type);
62 DEBUG (grf, 0, " Is %s readable for the user running OpenTTD?", full_filename);
63 return false;
66 const IniItem *item;
68 item = fetch_metadata<T> (metadata, "name", full_filename);
69 if (item == NULL) return false;
70 this->set_name (item->value);
72 item = fetch_metadata<T> (metadata, "description", full_filename);
73 if (item == NULL) return false;
74 this->add_default_desc (item->value);
76 /* Add the translations of the descriptions too. */
77 for (IniItem::const_iterator item = metadata->cbegin(); item != metadata->cend(); item++) {
78 if (strncmp("description.", item->get_name(), 12) != 0) continue;
80 this->add_desc (item->get_name() + 12, item->value);
83 item = fetch_metadata<T> (metadata, "shortname", full_filename);
84 if (item == NULL) return false;
85 for (uint i = 0; item->value[i] != '\0' && i < 4; i++) {
86 this->shortname |= ((uint8)item->value[i]) << (i * 8);
89 item = fetch_metadata<T> (metadata, "version", full_filename);
90 if (item == NULL) return false;
91 this->version = atoi(item->value);
93 item = metadata->find ("fallback");
94 this->fallback = (item != NULL && strcmp(item->value, "0") != 0 && strcmp(item->value, "false") != 0);
96 /* For each of the file types we want to find the file, MD5 checksums and warning messages. */
97 const IniGroup *files = ini->get_group ("files");
98 const IniGroup *md5s = ini->get_group ("md5s");
99 const IniGroup *origin = ini->get_group ("origin");
100 for (uint i = 0; i < Tnum_files; i++) {
101 MD5File *file = &this->files[i];
102 /* Find the filename first. */
103 item = files->find (T::file_names[i]);
104 if (item == NULL || (item->value == NULL && !allow_empty_filename)) {
105 DEBUG(grf, 0, "No %s file for: %s (in %s)", T::set_type, T::file_names[i], full_filename);
106 return false;
109 const char *filename = item->value;
110 if (filename == NULL) {
111 file->filename = NULL;
112 /* If we list no file, that file must be valid */
113 this->valid_files++;
114 this->found_files++;
115 continue;
118 file->filename = str_fmt("%s%s", path, filename);
120 /* Then find the MD5 checksum */
121 item = md5s->find (filename);
122 if (item == NULL || item->value == NULL) {
123 DEBUG(grf, 0, "No MD5 checksum specified for: %s (in %s)", filename, full_filename);
124 return false;
126 char *c = item->value;
127 for (uint i = 0; i < sizeof(file->hash) * 2; i++, c++) {
128 uint j;
129 if ('0' <= *c && *c <= '9') {
130 j = *c - '0';
131 } else if ('a' <= *c && *c <= 'f') {
132 j = *c - 'a' + 10;
133 } else if ('A' <= *c && *c <= 'F') {
134 j = *c - 'A' + 10;
135 } else {
136 DEBUG(grf, 0, "Malformed MD5 checksum specified for: %s (in %s)", filename, full_filename);
137 return false;
139 if (i % 2 == 0) {
140 file->hash[i / 2] = j << 4;
141 } else {
142 file->hash[i / 2] |= j;
146 /* Then find the warning message when the file's missing */
147 item = origin->find (filename);
148 if (item == NULL) item = origin->find ("default");
149 if (item == NULL) {
150 DEBUG(grf, 1, "No origin warning message specified for: %s", filename);
151 file->missing_warning = xstrdup("");
152 } else {
153 file->missing_warning = xstrdup(item->value);
156 switch (T::CheckMD5(file, BASESET_DIR)) {
157 case MD5File::CR_MATCH:
158 this->valid_files++;
159 this->found_files++;
160 break;
162 case MD5File::CR_MISMATCH:
163 DEBUG(grf, 1, "MD5 checksum mismatch for: %s (in %s)", filename, full_filename);
164 this->found_files++;
165 break;
167 case MD5File::CR_NO_FILE:
168 DEBUG(grf, 1, "The file %s specified in %s is missing", filename, full_filename);
169 break;
173 return true;
176 template <class Tbase_set>
177 bool BaseMedia<Tbase_set>::Scanner::AddFile (const char *filename, size_t basepath_length, const char *tar_filename)
179 bool ret = false;
180 DEBUG(grf, 1, "Checking %s for base %s set", filename, Tbase_set::set_type);
182 Tbase_set *set = new Tbase_set();
184 char *path = xstrdup(filename + basepath_length);
185 char *psep = strrchr(path, PATHSEPCHAR);
186 if (psep != NULL) {
187 psep[1] = '\0';
188 } else {
189 *path = '\0';
192 IniFile ini (filename, BASESET_DIR);
194 if (set->FillSetDetails (&ini, path, filename)) {
195 Tbase_set *duplicate = NULL;
196 for (Tbase_set *c = BaseMedia<Tbase_set>::available_sets; c != NULL; c = c->next) {
197 if (strcmp(c->get_name(), set->get_name()) == 0 || c->shortname == set->shortname) {
198 duplicate = c;
199 break;
202 if (duplicate != NULL) {
203 /* The more complete set takes precedence over the version number. */
204 if ((duplicate->valid_files == set->valid_files && duplicate->version >= set->version) ||
205 duplicate->valid_files > set->valid_files) {
206 DEBUG(grf, 1, "Not adding %s (%i) as base %s set (duplicate, %s)",
207 set->get_name(), set->version, Tbase_set::set_type,
208 duplicate->valid_files > set->valid_files ? "less valid files" : "lower version");
209 set->next = BaseMedia<Tbase_set>::duplicate_sets;
210 BaseMedia<Tbase_set>::duplicate_sets = set;
211 } else {
212 Tbase_set **prev = &BaseMedia<Tbase_set>::available_sets;
213 while (*prev != duplicate) prev = &(*prev)->next;
215 *prev = set;
216 set->next = duplicate->next;
218 /* If the duplicate set is currently used (due to rescanning this can happen)
219 * update the currently used set to the new one. This will 'lie' about the
220 * version number until a new game is started which isn't a big problem */
221 if (BaseMedia<Tbase_set>::used_set == duplicate) BaseMedia<Tbase_set>::used_set = set;
223 DEBUG(grf, 1, "Removing %s (%i) as base %s set (duplicate, %s)",
224 duplicate->get_name(), duplicate->version, Tbase_set::set_type,
225 duplicate->valid_files < set->valid_files ? "less valid files" : "lower version");
226 duplicate->next = BaseMedia<Tbase_set>::duplicate_sets;
227 BaseMedia<Tbase_set>::duplicate_sets = duplicate;
228 ret = true;
230 } else {
231 Tbase_set **last = &BaseMedia<Tbase_set>::available_sets;
232 while (*last != NULL) last = &(*last)->next;
234 *last = set;
235 ret = true;
237 if (ret) {
238 DEBUG(grf, 1, "Adding %s (%i) as base %s set", set->get_name(), set->version, Tbase_set::set_type);
240 } else {
241 delete set;
243 free(path);
245 return ret;
249 * Set the set to be used.
250 * @param name of the set to use
251 * @return true if it could be loaded
253 template <class Tbase_set>
254 /* static */ bool BaseMedia<Tbase_set>::SetSet(const char *name)
256 extern void CheckExternalFiles();
258 if (StrEmpty(name)) {
259 if (!BaseMedia<Tbase_set>::DetermineBestSet()) return false;
260 CheckExternalFiles();
261 return true;
264 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
265 if (strcmp(name, s->get_name()) == 0) {
266 BaseMedia<Tbase_set>::used_set = s;
267 CheckExternalFiles();
268 return true;
271 return false;
275 * Returns a list with the sets.
276 * @param buf where to print to
278 template <class Tbase_set>
279 /* static */ void BaseMedia<Tbase_set>::GetSetsList (stringb *buf)
281 buf->append_fmt ("List of %s sets:\n", Tbase_set::set_type);
282 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
283 buf->append_fmt ("%18s: %s", s->get_name(), s->get_default_desc());
284 int invalid = s->GetNumInvalid();
285 if (invalid != 0) {
286 int missing = s->GetNumMissing();
287 if (missing == 0) {
288 buf->append_fmt (" (%i corrupt file%s)\n", invalid, invalid == 1 ? "" : "s");
289 } else {
290 buf->append_fmt (" (unusable: %i missing file%s)\n", missing, missing == 1 ? "" : "s");
292 } else {
293 buf->append ('\n');
296 buf->append ('\n');
299 #if defined(ENABLE_NETWORK)
300 #include "network/network_content.h"
302 template <class Tbase_set> const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const Tbase_set *s)
304 for (; s != NULL; s = s->next) {
305 if (s->GetNumMissing() != 0) continue;
307 if (s->shortname != ci->unique_id) continue;
308 if (!md5sum) return s->files[0].filename;
310 byte md5[16];
311 memset(md5, 0, sizeof(md5));
312 for (uint i = 0; i < Tbase_set::NUM_FILES; i++) {
313 for (uint j = 0; j < sizeof(md5); j++) {
314 md5[j] ^= s->files[i].hash[j];
317 if (memcmp(md5, ci->md5sum, sizeof(md5)) == 0) return s->files[0].filename;
319 return NULL;
322 template <class Tbase_set>
323 /* static */ bool BaseMedia<Tbase_set>::HasSet(const ContentInfo *ci, bool md5sum)
325 return (TryGetBaseSetFile(ci, md5sum, BaseMedia<Tbase_set>::available_sets) != NULL) ||
326 (TryGetBaseSetFile(ci, md5sum, BaseMedia<Tbase_set>::duplicate_sets) != NULL);
329 #else
331 template <class Tbase_set>
332 const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const Tbase_set *s)
334 return NULL;
337 template <class Tbase_set>
338 /* static */ bool BaseMedia<Tbase_set>::HasSet(const ContentInfo *ci, bool md5sum)
340 return false;
343 #endif /* ENABLE_NETWORK */
346 * Count the number of available graphics sets.
347 * @return the number of sets
349 template <class Tbase_set>
350 /* static */ int BaseMedia<Tbase_set>::GetNumSets()
352 int n = 0;
353 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
354 if (s != BaseMedia<Tbase_set>::used_set && s->GetNumMissing() != 0) continue;
355 n++;
357 return n;
361 * Get the index of the currently active graphics set
362 * @return the current set's index
364 template <class Tbase_set>
365 /* static */ int BaseMedia<Tbase_set>::GetIndexOfUsedSet()
367 int n = 0;
368 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
369 if (s == BaseMedia<Tbase_set>::used_set) return n;
370 if (s->GetNumMissing() != 0) continue;
371 n++;
373 return -1;
377 * Get the name of the graphics set at the specified index
378 * @return the name of the set
380 template <class Tbase_set>
381 /* static */ const Tbase_set *BaseMedia<Tbase_set>::GetSet(int index)
383 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
384 if (s != BaseMedia<Tbase_set>::used_set && s->GetNumMissing() != 0) continue;
385 if (index == 0) return s;
386 index--;
388 error("Base%s::GetSet(): index %d out of range", Tbase_set::set_type, index);
392 * Return the used set.
393 * @return the used set.
395 template <class Tbase_set>
396 /* static */ const Tbase_set *BaseMedia<Tbase_set>::GetUsedSet()
398 return BaseMedia<Tbase_set>::used_set;
402 * Return the available sets.
403 * @return The available sets.
405 template <class Tbase_set>
406 /* static */ Tbase_set *BaseMedia<Tbase_set>::GetAvailableSets()
408 return BaseMedia<Tbase_set>::available_sets;
411 template <class Tbase_set>
412 /* static */ bool BaseMedia<Tbase_set>::DetermineBestSet()
414 if (BaseMedia<Tbase_set>::used_set != NULL) return true;
416 const Tbase_set *best = NULL;
417 for (const Tbase_set *c = BaseMedia<Tbase_set>::available_sets; c != NULL; c = c->next) {
418 /* Skip unusable sets */
419 if (c->GetNumMissing() != 0) continue;
421 if (best == NULL ||
422 (best->fallback && !c->fallback) ||
423 best->valid_files < c->valid_files ||
424 (best->valid_files == c->valid_files && (
425 (best->shortname == c->shortname && best->version < c->version) ||
426 c->IsPreferredTo (*best)))) {
427 best = c;
431 BaseMedia<Tbase_set>::used_set = best;
432 return BaseMedia<Tbase_set>::used_set != NULL;
436 * Force instantiation of methods so we don't get linker errors.
437 * @param repl_type the type of the BaseMedia to instantiate
438 * @param set_type the type of the BaseSet to instantiate
440 #define INSTANTIATE_BASE_MEDIA_METHODS(repl_type, set_type) \
441 template const char *repl_type::ini_set; \
442 template bool repl_type::Scanner::AddFile(const char *filename, size_t pathlength, const char *tar_filename); \
443 template bool repl_type::HasSet(const struct ContentInfo *ci, bool md5sum); \
444 template bool repl_type::SetSet(const char *name); \
445 template void repl_type::GetSetsList (stringb *buf); \
446 template int repl_type::GetNumSets(); \
447 template int repl_type::GetIndexOfUsedSet(); \
448 template const set_type *repl_type::GetSet(int index); \
449 template const set_type *repl_type::GetUsedSet(); \
450 template bool repl_type::DetermineBestSet(); \
451 template set_type *repl_type::GetAvailableSets(); \
452 template const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const set_type *s);