Add: Draw network password indicator lock in company drop down list. (#7079)
[openttd-github.git] / src / base_media_func.h
blobf7afca0edb22ca9f379ac064f7265272bd0c7bdf
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).
12 * @note You should _never_ include this file due to the SET_TYPE define.
15 #include "base_media_base.h"
16 #include "debug.h"
17 #include "ini_type.h"
18 #include "string_func.h"
20 /**
21 * Try to read a single piece of metadata and return false if it doesn't exist.
22 * @param name the name of the item to fetch.
24 #define fetch_metadata(name) \
25 item = metadata->GetItem(name, false); \
26 if (item == NULL || StrEmpty(item->value)) { \
27 DEBUG(grf, 0, "Base " SET_TYPE "set detail loading: %s field missing.", name); \
28 DEBUG(grf, 0, " Is %s readable for the user running OpenTTD?", full_filename); \
29 return false; \
32 /**
33 * Read the set information from a loaded ini.
34 * @param ini the ini to read from
35 * @param path the path to this ini file (for filenames)
36 * @param full_filename the full filename of the loaded file (for error reporting purposes)
37 * @param allow_empty_filename empty filenames are valid
38 * @return true if loading was successful.
40 template <class T, size_t Tnum_files, bool Tsearch_in_tars>
41 bool BaseSet<T, Tnum_files, Tsearch_in_tars>::FillSetDetails(IniFile *ini, const char *path, const char *full_filename, bool allow_empty_filename)
43 IniGroup *metadata = ini->GetGroup("metadata");
44 IniItem *item;
46 fetch_metadata("name");
47 this->name = stredup(item->value);
49 fetch_metadata("description");
50 this->description[stredup("")] = stredup(item->value);
52 /* Add the translations of the descriptions too. */
53 for (const IniItem *item = metadata->item; item != NULL; item = item->next) {
54 if (strncmp("description.", item->name, 12) != 0) continue;
56 this->description[stredup(item->name + 12)] = stredup(item->value);
59 fetch_metadata("shortname");
60 for (uint i = 0; item->value[i] != '\0' && i < 4; i++) {
61 this->shortname |= ((uint8)item->value[i]) << (i * 8);
64 fetch_metadata("version");
65 this->version = atoi(item->value);
67 item = metadata->GetItem("fallback", false);
68 this->fallback = (item != NULL && strcmp(item->value, "0") != 0 && strcmp(item->value, "false") != 0);
70 /* For each of the file types we want to find the file, MD5 checksums and warning messages. */
71 IniGroup *files = ini->GetGroup("files");
72 IniGroup *md5s = ini->GetGroup("md5s");
73 IniGroup *origin = ini->GetGroup("origin");
74 for (uint i = 0; i < Tnum_files; i++) {
75 MD5File *file = &this->files[i];
76 /* Find the filename first. */
77 item = files->GetItem(BaseSet<T, Tnum_files, Tsearch_in_tars>::file_names[i], false);
78 if (item == NULL || (item->value == NULL && !allow_empty_filename)) {
79 DEBUG(grf, 0, "No " SET_TYPE " file for: %s (in %s)", BaseSet<T, Tnum_files, Tsearch_in_tars>::file_names[i], full_filename);
80 return false;
83 const char *filename = item->value;
84 if (filename == NULL) {
85 file->filename = NULL;
86 /* If we list no file, that file must be valid */
87 this->valid_files++;
88 this->found_files++;
89 continue;
92 file->filename = str_fmt("%s%s", path, filename);
94 /* Then find the MD5 checksum */
95 item = md5s->GetItem(filename, false);
96 if (item == NULL || item->value == NULL) {
97 DEBUG(grf, 0, "No MD5 checksum specified for: %s (in %s)", filename, full_filename);
98 return false;
100 char *c = item->value;
101 for (uint i = 0; i < sizeof(file->hash) * 2; i++, c++) {
102 uint j;
103 if ('0' <= *c && *c <= '9') {
104 j = *c - '0';
105 } else if ('a' <= *c && *c <= 'f') {
106 j = *c - 'a' + 10;
107 } else if ('A' <= *c && *c <= 'F') {
108 j = *c - 'A' + 10;
109 } else {
110 DEBUG(grf, 0, "Malformed MD5 checksum specified for: %s (in %s)", filename, full_filename);
111 return false;
113 if (i % 2 == 0) {
114 file->hash[i / 2] = j << 4;
115 } else {
116 file->hash[i / 2] |= j;
120 /* Then find the warning message when the file's missing */
121 item = origin->GetItem(filename, false);
122 if (item == NULL) item = origin->GetItem("default", false);
123 if (item == NULL) {
124 DEBUG(grf, 1, "No origin warning message specified for: %s", filename);
125 file->missing_warning = stredup("");
126 } else {
127 file->missing_warning = stredup(item->value);
130 file->check_result = T::CheckMD5(file, BASESET_DIR);
131 switch (file->check_result) {
132 case MD5File::CR_UNKNOWN:
133 break;
135 case MD5File::CR_MATCH:
136 this->valid_files++;
137 this->found_files++;
138 break;
140 case MD5File::CR_MISMATCH:
141 DEBUG(grf, 1, "MD5 checksum mismatch for: %s (in %s)", filename, full_filename);
142 this->found_files++;
143 break;
145 case MD5File::CR_NO_FILE:
146 DEBUG(grf, 1, "The file %s specified in %s is missing", filename, full_filename);
147 break;
151 return true;
154 template <class Tbase_set>
155 bool BaseMedia<Tbase_set>::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
157 bool ret = false;
158 DEBUG(grf, 1, "Checking %s for base " SET_TYPE " set", filename);
160 Tbase_set *set = new Tbase_set();
161 IniFile *ini = new IniFile();
162 ini->LoadFromDisk(filename, BASESET_DIR);
164 char *path = stredup(filename + basepath_length);
165 char *psep = strrchr(path, PATHSEPCHAR);
166 if (psep != NULL) {
167 psep[1] = '\0';
168 } else {
169 *path = '\0';
172 if (set->FillSetDetails(ini, path, filename)) {
173 Tbase_set *duplicate = NULL;
174 for (Tbase_set *c = BaseMedia<Tbase_set>::available_sets; c != NULL; c = c->next) {
175 if (strcmp(c->name, set->name) == 0 || c->shortname == set->shortname) {
176 duplicate = c;
177 break;
180 if (duplicate != NULL) {
181 /* The more complete set takes precedence over the version number. */
182 if ((duplicate->valid_files == set->valid_files && duplicate->version >= set->version) ||
183 duplicate->valid_files > set->valid_files) {
184 DEBUG(grf, 1, "Not adding %s (%i) as base " SET_TYPE " set (duplicate, %s)", set->name, set->version,
185 duplicate->valid_files > set->valid_files ? "less valid files" : "lower version");
186 set->next = BaseMedia<Tbase_set>::duplicate_sets;
187 BaseMedia<Tbase_set>::duplicate_sets = set;
188 } else {
189 Tbase_set **prev = &BaseMedia<Tbase_set>::available_sets;
190 while (*prev != duplicate) prev = &(*prev)->next;
192 *prev = set;
193 set->next = duplicate->next;
195 /* If the duplicate set is currently used (due to rescanning this can happen)
196 * update the currently used set to the new one. This will 'lie' about the
197 * version number until a new game is started which isn't a big problem */
198 if (BaseMedia<Tbase_set>::used_set == duplicate) BaseMedia<Tbase_set>::used_set = set;
200 DEBUG(grf, 1, "Removing %s (%i) as base " SET_TYPE " set (duplicate, %s)", duplicate->name, duplicate->version,
201 duplicate->valid_files < set->valid_files ? "less valid files" : "lower version");
202 duplicate->next = BaseMedia<Tbase_set>::duplicate_sets;
203 BaseMedia<Tbase_set>::duplicate_sets = duplicate;
204 ret = true;
206 } else {
207 Tbase_set **last = &BaseMedia<Tbase_set>::available_sets;
208 while (*last != NULL) last = &(*last)->next;
210 *last = set;
211 ret = true;
213 if (ret) {
214 DEBUG(grf, 1, "Adding %s (%i) as base " SET_TYPE " set", set->name, set->version);
216 } else {
217 delete set;
219 free(path);
221 delete ini;
222 return ret;
226 * Set the set to be used.
227 * @param name of the set to use
228 * @return true if it could be loaded
230 template <class Tbase_set>
231 /* static */ bool BaseMedia<Tbase_set>::SetSet(const char *name)
233 extern void CheckExternalFiles();
235 if (StrEmpty(name)) {
236 if (!BaseMedia<Tbase_set>::DetermineBestSet()) return false;
237 CheckExternalFiles();
238 return true;
241 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
242 if (strcmp(name, s->name) == 0) {
243 BaseMedia<Tbase_set>::used_set = s;
244 CheckExternalFiles();
245 return true;
248 return false;
252 * Returns a list with the sets.
253 * @param p where to print to
254 * @param last the last character to print to
255 * @return the last printed character
257 template <class Tbase_set>
258 /* static */ char *BaseMedia<Tbase_set>::GetSetsList(char *p, const char *last)
260 p += seprintf(p, last, "List of " SET_TYPE " sets:\n");
261 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
262 p += seprintf(p, last, "%18s: %s", s->name, s->GetDescription());
263 int invalid = s->GetNumInvalid();
264 if (invalid != 0) {
265 int missing = s->GetNumMissing();
266 if (missing == 0) {
267 p += seprintf(p, last, " (%i corrupt file%s)\n", invalid, invalid == 1 ? "" : "s");
268 } else {
269 p += seprintf(p, last, " (unusable: %i missing file%s)\n", missing, missing == 1 ? "" : "s");
271 } else {
272 p += seprintf(p, last, "\n");
275 p += seprintf(p, last, "\n");
277 return p;
280 #if defined(ENABLE_NETWORK)
281 #include "network/network_content.h"
283 template <class Tbase_set> const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const Tbase_set *s)
285 for (; s != NULL; s = s->next) {
286 if (s->GetNumMissing() != 0) continue;
288 if (s->shortname != ci->unique_id) continue;
289 if (!md5sum) return s->files[0].filename;
291 byte md5[16];
292 memset(md5, 0, sizeof(md5));
293 for (uint i = 0; i < Tbase_set::NUM_FILES; i++) {
294 for (uint j = 0; j < sizeof(md5); j++) {
295 md5[j] ^= s->files[i].hash[j];
298 if (memcmp(md5, ci->md5sum, sizeof(md5)) == 0) return s->files[0].filename;
300 return NULL;
303 template <class Tbase_set>
304 /* static */ bool BaseMedia<Tbase_set>::HasSet(const ContentInfo *ci, bool md5sum)
306 return (TryGetBaseSetFile(ci, md5sum, BaseMedia<Tbase_set>::available_sets) != NULL) ||
307 (TryGetBaseSetFile(ci, md5sum, BaseMedia<Tbase_set>::duplicate_sets) != NULL);
310 #else
312 template <class Tbase_set>
313 const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const Tbase_set *s)
315 return NULL;
318 template <class Tbase_set>
319 /* static */ bool BaseMedia<Tbase_set>::HasSet(const ContentInfo *ci, bool md5sum)
321 return false;
324 #endif /* ENABLE_NETWORK */
327 * Count the number of available graphics sets.
328 * @return the number of sets
330 template <class Tbase_set>
331 /* static */ int BaseMedia<Tbase_set>::GetNumSets()
333 int n = 0;
334 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
335 if (s != BaseMedia<Tbase_set>::used_set && s->GetNumMissing() != 0) continue;
336 n++;
338 return n;
342 * Get the index of the currently active graphics set
343 * @return the current set's index
345 template <class Tbase_set>
346 /* static */ int BaseMedia<Tbase_set>::GetIndexOfUsedSet()
348 int n = 0;
349 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
350 if (s == BaseMedia<Tbase_set>::used_set) return n;
351 if (s->GetNumMissing() != 0) continue;
352 n++;
354 return -1;
358 * Get the name of the graphics set at the specified index
359 * @return the name of the set
361 template <class Tbase_set>
362 /* static */ const Tbase_set *BaseMedia<Tbase_set>::GetSet(int index)
364 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
365 if (s != BaseMedia<Tbase_set>::used_set && s->GetNumMissing() != 0) continue;
366 if (index == 0) return s;
367 index--;
369 error("Base" SET_TYPE "::GetSet(): index %d out of range", index);
373 * Return the used set.
374 * @return the used set.
376 template <class Tbase_set>
377 /* static */ const Tbase_set *BaseMedia<Tbase_set>::GetUsedSet()
379 return BaseMedia<Tbase_set>::used_set;
383 * Return the available sets.
384 * @return The available sets.
386 template <class Tbase_set>
387 /* static */ Tbase_set *BaseMedia<Tbase_set>::GetAvailableSets()
389 return BaseMedia<Tbase_set>::available_sets;
393 * Force instantiation of methods so we don't get linker errors.
394 * @param repl_type the type of the BaseMedia to instantiate
395 * @param set_type the type of the BaseSet to instantiate
397 #define INSTANTIATE_BASE_MEDIA_METHODS(repl_type, set_type) \
398 template const char *repl_type::ini_set; \
399 template const char *repl_type::GetExtension(); \
400 template bool repl_type::AddFile(const char *filename, size_t pathlength, const char *tar_filename); \
401 template bool repl_type::HasSet(const struct ContentInfo *ci, bool md5sum); \
402 template bool repl_type::SetSet(const char *name); \
403 template char *repl_type::GetSetsList(char *p, const char *last); \
404 template int repl_type::GetNumSets(); \
405 template int repl_type::GetIndexOfUsedSet(); \
406 template const set_type *repl_type::GetSet(int index); \
407 template const set_type *repl_type::GetUsedSet(); \
408 template bool repl_type::DetermineBestSet(); \
409 template set_type *repl_type::GetAvailableSets(); \
410 template const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const set_type *s);