Factor out OnClick dispatching in Window::HandleEditBoxKey
[openttd/fttd.git] / src / base_media_func.h
blob335380f8159b131f93b719b223b92682bd97f022
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 "string.h"
18 template <class Tbase_set> /* static */ const char *BaseMedia<Tbase_set>::ini_set;
19 template <class Tbase_set> /* static */ const Tbase_set *BaseMedia<Tbase_set>::used_set;
20 template <class Tbase_set> /* static */ Tbase_set *BaseMedia<Tbase_set>::available_sets;
21 template <class Tbase_set> /* static */ Tbase_set *BaseMedia<Tbase_set>::duplicate_sets;
23 /**
24 * Read the set information from a loaded ini.
25 * @param ini the ini to read from
26 * @param path the path to this ini file (for filenames)
27 * @param full_filename the full filename of the loaded file (for error reporting purposes)
28 * @param allow_empty_filename empty filenames are valid
29 * @return true if loading was successful.
31 template <class T, size_t Tnum_files>
32 bool BaseSet<T, Tnum_files>::FillSetDetails (IniFile *ini, const char *path, const char *full_filename, bool allow_empty_filename)
34 memset (this->files, 0, sizeof(this->files));
35 this->found_files = 0;
36 this->valid_files = 0;
38 const IniGroup *metadata = ini->find ("metadata");
39 if (metadata == NULL) {
40 DEBUG (grf, 0, "Base %sset detail loading: metadata group missing.", T::set_type);
41 DEBUG (grf, 0, " Is %s readable for the user running OpenTTD?", full_filename);
42 return false;
45 const IniItem *item;
47 item = this->fetch_metadata (metadata, "name", full_filename);
48 if (item == NULL) return false;
49 this->set_name (item->value);
51 item = this->fetch_metadata (metadata, "description", full_filename);
52 if (item == NULL) return false;
53 this->add_default_desc (item->value);
55 /* Add the translations of the descriptions too. */
56 for (IniItem::const_iterator item = metadata->cbegin(); item != metadata->cend(); item++) {
57 if (strncmp("description.", item->get_name(), 12) != 0) continue;
59 this->add_desc (item->get_name() + 12, item->value);
62 item = this->fetch_metadata (metadata, "shortname", full_filename);
63 if (item == NULL) return false;
64 for (uint i = 0; item->value[i] != '\0' && i < 4; i++) {
65 this->shortname |= ((uint8)item->value[i]) << (i * 8);
68 item = this->fetch_metadata (metadata, "version", full_filename);
69 if (item == NULL) return false;
70 this->version = atoi(item->value);
72 item = metadata->find ("fallback");
73 this->fallback = (item != NULL && strcmp(item->value, "0") != 0 && strcmp(item->value, "false") != 0);
75 /* For each of the file types we want to find the file, MD5 checksums and warning messages. */
76 const IniGroup *files = ini->get_group ("files");
77 const IniGroup *md5s = ini->get_group ("md5s");
78 const IniGroup *origin = ini->get_group ("origin");
79 for (uint i = 0; i < Tnum_files; i++) {
80 FileDesc *file = &this->files[i];
81 /* Find the filename first. */
82 item = files->find (T::file_names[i]);
83 if (item == NULL || (item->value == NULL && !allow_empty_filename)) {
84 DEBUG(grf, 0, "No %s file for: %s (in %s)", T::set_type, T::file_names[i], full_filename);
85 return false;
88 const char *filename = item->value;
89 if (filename == NULL) {
90 file->filename = NULL;
91 /* If we list no file, that file must be valid */
92 file->status = FileDesc::MATCH;
93 this->valid_files++;
94 this->found_files++;
95 continue;
98 file->filename = str_fmt("%s%s", path, filename);
100 /* Then find the MD5 checksum */
101 item = md5s->find (filename);
102 if (item == NULL || item->value == NULL) {
103 DEBUG(grf, 0, "No MD5 checksum specified for: %s (in %s)", filename, full_filename);
104 return false;
106 char *c = item->value;
107 for (uint i = 0; i < sizeof(file->hash) * 2; i++, c++) {
108 uint j;
109 if ('0' <= *c && *c <= '9') {
110 j = *c - '0';
111 } else if ('a' <= *c && *c <= 'f') {
112 j = *c - 'a' + 10;
113 } else if ('A' <= *c && *c <= 'F') {
114 j = *c - 'A' + 10;
115 } else {
116 DEBUG(grf, 0, "Malformed MD5 checksum specified for: %s (in %s)", filename, full_filename);
117 return false;
119 if (i % 2 == 0) {
120 file->hash[i / 2] = j << 4;
121 } else {
122 file->hash[i / 2] |= j;
126 /* Then find the warning message when the file's missing */
127 item = origin->find (filename);
128 if (item == NULL) item = origin->find ("default");
129 if (item == NULL) {
130 DEBUG(grf, 1, "No origin warning message specified for: %s", filename);
131 file->missing_warning = xstrdup("");
132 } else {
133 file->missing_warning = xstrdup(item->value);
136 FileDesc::Status status = T::CheckMD5 (file);
137 file->status = status;
138 switch (status) {
139 case FileDesc::MATCH:
140 this->valid_files++;
141 this->found_files++;
142 break;
144 case FileDesc::MISMATCH:
145 DEBUG(grf, 1, "MD5 checksum mismatch for: %s (in %s)", filename, full_filename);
146 this->found_files++;
147 break;
149 case FileDesc::MISSING:
150 DEBUG(grf, 1, "The file %s specified in %s is missing", filename, full_filename);
151 break;
155 return true;
158 template <class Tbase_set>
159 bool BaseMedia<Tbase_set>::Scanner::AddFile (const char *filename, size_t basepath_length, const char *tar_filename)
161 bool ret = false;
162 DEBUG(grf, 1, "Checking %s for base %s set", filename, Tbase_set::set_type);
164 Tbase_set *set = new Tbase_set();
166 char *path = xstrdup(filename + basepath_length);
167 char *psep = strrchr(path, PATHSEPCHAR);
168 if (psep != NULL) {
169 psep[1] = '\0';
170 } else {
171 *path = '\0';
174 IniFile ini (filename, BASESET_DIR);
176 if (set->FillSetDetails (&ini, path, filename)) {
177 Tbase_set *duplicate = NULL;
178 for (Tbase_set *c = BaseMedia<Tbase_set>::available_sets; c != NULL; c = c->next) {
179 if (strcmp(c->get_name(), set->get_name()) == 0 || c->shortname == set->shortname) {
180 duplicate = c;
181 break;
184 if (duplicate != NULL) {
185 /* The more complete set takes precedence over the version number. */
186 if ((duplicate->valid_files == set->valid_files && duplicate->version >= set->version) ||
187 duplicate->valid_files > set->valid_files) {
188 DEBUG(grf, 1, "Not adding %s (%i) as base %s set (duplicate, %s)",
189 set->get_name(), set->version, Tbase_set::set_type,
190 duplicate->valid_files > set->valid_files ? "less valid files" : "lower version");
191 set->next = BaseMedia<Tbase_set>::duplicate_sets;
192 BaseMedia<Tbase_set>::duplicate_sets = set;
193 } else {
194 Tbase_set **prev = &BaseMedia<Tbase_set>::available_sets;
195 while (*prev != duplicate) prev = &(*prev)->next;
197 *prev = set;
198 set->next = duplicate->next;
200 /* If the duplicate set is currently used (due to rescanning this can happen)
201 * update the currently used set to the new one. This will 'lie' about the
202 * version number until a new game is started which isn't a big problem */
203 if (BaseMedia<Tbase_set>::used_set == duplicate) BaseMedia<Tbase_set>::used_set = set;
205 DEBUG(grf, 1, "Removing %s (%i) as base %s set (duplicate, %s)",
206 duplicate->get_name(), duplicate->version, Tbase_set::set_type,
207 duplicate->valid_files < set->valid_files ? "less valid files" : "lower version");
208 duplicate->next = BaseMedia<Tbase_set>::duplicate_sets;
209 BaseMedia<Tbase_set>::duplicate_sets = duplicate;
210 ret = true;
212 } else {
213 Tbase_set **last = &BaseMedia<Tbase_set>::available_sets;
214 while (*last != NULL) last = &(*last)->next;
216 *last = set;
217 ret = true;
219 if (ret) {
220 DEBUG(grf, 1, "Adding %s (%i) as base %s set", set->get_name(), set->version, Tbase_set::set_type);
222 } else {
223 delete set;
225 free(path);
227 return ret;
231 * Set the set to be used.
232 * @param name of the set to use
233 * @return true if it could be loaded
235 template <class Tbase_set>
236 /* static */ bool BaseMedia<Tbase_set>::SetSet(const char *name)
238 if (StrEmpty(name)) {
239 return BaseMedia<Tbase_set>::DetermineBestSet();
242 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
243 if (strcmp(name, s->get_name()) == 0) {
244 BaseMedia<Tbase_set>::used_set = s;
245 return true;
248 return false;
252 * Returns a list with the sets.
253 * @param buf where to print to
255 template <class Tbase_set>
256 /* static */ void BaseMedia<Tbase_set>::GetSetsList (stringb *buf)
258 buf->append_fmt ("List of %s sets:\n", Tbase_set::set_type);
259 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
260 buf->append_fmt ("%18s: %s", s->get_name(), s->get_default_desc());
261 int invalid = s->GetNumInvalid();
262 if (invalid != 0) {
263 int missing = s->GetNumMissing();
264 if (missing == 0) {
265 buf->append_fmt (" (%i corrupt file%s)\n", invalid, invalid == 1 ? "" : "s");
266 } else {
267 buf->append_fmt (" (unusable: %i missing file%s)\n", missing, missing == 1 ? "" : "s");
269 } else {
270 buf->append ('\n');
273 buf->append ('\n');
276 #if defined(ENABLE_NETWORK)
277 #include "network/network_content.h"
279 template <class Tbase_set> const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const Tbase_set *s)
281 for (; s != NULL; s = s->next) {
282 if (s->GetNumMissing() != 0) continue;
284 if (s->shortname != ci->unique_id) continue;
285 if (!md5sum) return s->files[0].filename;
287 byte md5[16];
288 memset(md5, 0, sizeof(md5));
289 for (uint i = 0; i < Tbase_set::NUM_FILES; i++) {
290 for (uint j = 0; j < sizeof(md5); j++) {
291 md5[j] ^= s->files[i].hash[j];
294 if (memcmp(md5, ci->md5sum, sizeof(md5)) == 0) return s->files[0].filename;
296 return NULL;
299 template <class Tbase_set>
300 /* static */ bool BaseMedia<Tbase_set>::HasSet(const ContentInfo *ci, bool md5sum)
302 return (TryGetBaseSetFile(ci, md5sum, BaseMedia<Tbase_set>::available_sets) != NULL) ||
303 (TryGetBaseSetFile(ci, md5sum, BaseMedia<Tbase_set>::duplicate_sets) != NULL);
306 #else
308 template <class Tbase_set>
309 const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const Tbase_set *s)
311 return NULL;
314 template <class Tbase_set>
315 /* static */ bool BaseMedia<Tbase_set>::HasSet(const ContentInfo *ci, bool md5sum)
317 return false;
320 #endif /* ENABLE_NETWORK */
323 * Count the number of available graphics sets.
324 * @return the number of sets
326 template <class Tbase_set>
327 /* static */ int BaseMedia<Tbase_set>::GetNumSets()
329 int n = 0;
330 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
331 if (s != BaseMedia<Tbase_set>::used_set && s->GetNumMissing() != 0) continue;
332 n++;
334 return n;
338 * Get the index of the currently active graphics set
339 * @return the current set's index
341 template <class Tbase_set>
342 /* static */ int BaseMedia<Tbase_set>::GetIndexOfUsedSet()
344 int n = 0;
345 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
346 if (s == BaseMedia<Tbase_set>::used_set) return n;
347 if (s->GetNumMissing() != 0) continue;
348 n++;
350 return -1;
354 * Get the name of the graphics set at the specified index
355 * @return the name of the set
357 template <class Tbase_set>
358 /* static */ const Tbase_set *BaseMedia<Tbase_set>::GetSet(int index)
360 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
361 if (s != BaseMedia<Tbase_set>::used_set && s->GetNumMissing() != 0) continue;
362 if (index == 0) return s;
363 index--;
365 error("Base%s::GetSet(): index %d out of range", Tbase_set::set_type, index);
369 * Return the used set.
370 * @return the used set.
372 template <class Tbase_set>
373 /* static */ const Tbase_set *BaseMedia<Tbase_set>::GetUsedSet()
375 return BaseMedia<Tbase_set>::used_set;
379 * Return the available sets.
380 * @return The available sets.
382 template <class Tbase_set>
383 /* static */ Tbase_set *BaseMedia<Tbase_set>::GetAvailableSets()
385 return BaseMedia<Tbase_set>::available_sets;
388 template <class Tbase_set>
389 /* static */ bool BaseMedia<Tbase_set>::DetermineBestSet()
391 if (BaseMedia<Tbase_set>::used_set != NULL) return true;
393 const Tbase_set *best = NULL;
394 for (const Tbase_set *c = BaseMedia<Tbase_set>::available_sets; c != NULL; c = c->next) {
395 /* Skip unusable sets */
396 if (c->GetNumMissing() != 0) continue;
398 if (best == NULL ||
399 (best->fallback && !c->fallback) ||
400 best->valid_files < c->valid_files ||
401 (best->valid_files == c->valid_files && (
402 (best->shortname == c->shortname && best->version < c->version) ||
403 c->IsPreferredTo (*best)))) {
404 best = c;
408 BaseMedia<Tbase_set>::used_set = best;
409 return BaseMedia<Tbase_set>::used_set != NULL;
413 * Force instantiation of methods so we don't get linker errors.
414 * @param repl_type the type of the BaseMedia to instantiate
415 * @param set_type the type of the BaseSet to instantiate
417 #define INSTANTIATE_BASE_MEDIA_METHODS(repl_type, set_type) \
418 template const char *repl_type::ini_set; \
419 template bool repl_type::Scanner::AddFile(const char *filename, size_t pathlength, const char *tar_filename); \
420 template bool repl_type::HasSet(const struct ContentInfo *ci, bool md5sum); \
421 template bool repl_type::SetSet(const char *name); \
422 template void repl_type::GetSetsList (stringb *buf); \
423 template int repl_type::GetNumSets(); \
424 template int repl_type::GetIndexOfUsedSet(); \
425 template const set_type *repl_type::GetSet(int index); \
426 template const set_type *repl_type::GetUsedSet(); \
427 template bool repl_type::DetermineBestSet(); \
428 template set_type *repl_type::GetAvailableSets(); \
429 template const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const set_type *s);