Codechange: Use cached town, station, industry names for list window sorting
[openttd-github.git] / src / base_media_func.h
blob01e184f8ca9d233c1c8e14d69568ef503b9621ca
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 */
8 /**
9 * @file base_media_func.h Generic function implementations for base data (graphics, sounds).
10 * @note You should _never_ include this file due to the SET_TYPE define.
13 #include "base_media_base.h"
14 #include "debug.h"
15 #include "ini_type.h"
16 #include "string_func.h"
18 /**
19 * Try to read a single piece of metadata and return false if it doesn't exist.
20 * @param name the name of the item to fetch.
22 #define fetch_metadata(name) \
23 item = metadata->GetItem(name, false); \
24 if (item == nullptr || StrEmpty(item->value)) { \
25 DEBUG(grf, 0, "Base " SET_TYPE "set detail loading: %s field missing.", name); \
26 DEBUG(grf, 0, " Is %s readable for the user running OpenTTD?", full_filename); \
27 return false; \
30 /**
31 * Read the set information from a loaded ini.
32 * @param ini the ini to read from
33 * @param path the path to this ini file (for filenames)
34 * @param full_filename the full filename of the loaded file (for error reporting purposes)
35 * @param allow_empty_filename empty filenames are valid
36 * @return true if loading was successful.
38 template <class T, size_t Tnum_files, bool Tsearch_in_tars>
39 bool BaseSet<T, Tnum_files, Tsearch_in_tars>::FillSetDetails(IniFile *ini, const char *path, const char *full_filename, bool allow_empty_filename)
41 IniGroup *metadata = ini->GetGroup("metadata");
42 IniItem *item;
44 fetch_metadata("name");
45 this->name = stredup(item->value);
47 fetch_metadata("description");
48 this->description[stredup("")] = stredup(item->value);
50 /* Add the translations of the descriptions too. */
51 for (const IniItem *item = metadata->item; item != nullptr; item = item->next) {
52 if (strncmp("description.", item->name, 12) != 0) continue;
54 this->description[stredup(item->name + 12)] = stredup(item->value);
57 fetch_metadata("shortname");
58 for (uint i = 0; item->value[i] != '\0' && i < 4; i++) {
59 this->shortname |= ((uint8)item->value[i]) << (i * 8);
62 fetch_metadata("version");
63 this->version = atoi(item->value);
65 item = metadata->GetItem("fallback", false);
66 this->fallback = (item != nullptr && strcmp(item->value, "0") != 0 && strcmp(item->value, "false") != 0);
68 /* For each of the file types we want to find the file, MD5 checksums and warning messages. */
69 IniGroup *files = ini->GetGroup("files");
70 IniGroup *md5s = ini->GetGroup("md5s");
71 IniGroup *origin = ini->GetGroup("origin");
72 for (uint i = 0; i < Tnum_files; i++) {
73 MD5File *file = &this->files[i];
74 /* Find the filename first. */
75 item = files->GetItem(BaseSet<T, Tnum_files, Tsearch_in_tars>::file_names[i], false);
76 if (item == nullptr || (item->value == nullptr && !allow_empty_filename)) {
77 DEBUG(grf, 0, "No " SET_TYPE " file for: %s (in %s)", BaseSet<T, Tnum_files, Tsearch_in_tars>::file_names[i], full_filename);
78 return false;
81 const char *filename = item->value;
82 if (filename == nullptr) {
83 file->filename = nullptr;
84 /* If we list no file, that file must be valid */
85 this->valid_files++;
86 this->found_files++;
87 continue;
90 file->filename = str_fmt("%s%s", path, filename);
92 /* Then find the MD5 checksum */
93 item = md5s->GetItem(filename, false);
94 if (item == nullptr || item->value == nullptr) {
95 DEBUG(grf, 0, "No MD5 checksum specified for: %s (in %s)", filename, full_filename);
96 return false;
98 char *c = item->value;
99 for (uint i = 0; i < sizeof(file->hash) * 2; i++, c++) {
100 uint j;
101 if ('0' <= *c && *c <= '9') {
102 j = *c - '0';
103 } else if ('a' <= *c && *c <= 'f') {
104 j = *c - 'a' + 10;
105 } else if ('A' <= *c && *c <= 'F') {
106 j = *c - 'A' + 10;
107 } else {
108 DEBUG(grf, 0, "Malformed MD5 checksum specified for: %s (in %s)", filename, full_filename);
109 return false;
111 if (i % 2 == 0) {
112 file->hash[i / 2] = j << 4;
113 } else {
114 file->hash[i / 2] |= j;
118 /* Then find the warning message when the file's missing */
119 item = origin->GetItem(filename, false);
120 if (item == nullptr) item = origin->GetItem("default", false);
121 if (item == nullptr) {
122 DEBUG(grf, 1, "No origin warning message specified for: %s", filename);
123 file->missing_warning = stredup("");
124 } else {
125 file->missing_warning = stredup(item->value);
128 file->check_result = T::CheckMD5(file, BASESET_DIR);
129 switch (file->check_result) {
130 case MD5File::CR_UNKNOWN:
131 break;
133 case MD5File::CR_MATCH:
134 this->valid_files++;
135 this->found_files++;
136 break;
138 case MD5File::CR_MISMATCH:
139 DEBUG(grf, 1, "MD5 checksum mismatch for: %s (in %s)", filename, full_filename);
140 this->found_files++;
141 break;
143 case MD5File::CR_NO_FILE:
144 DEBUG(grf, 1, "The file %s specified in %s is missing", filename, full_filename);
145 break;
149 return true;
152 template <class Tbase_set>
153 bool BaseMedia<Tbase_set>::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
155 bool ret = false;
156 DEBUG(grf, 1, "Checking %s for base " SET_TYPE " set", filename);
158 Tbase_set *set = new Tbase_set();
159 IniFile *ini = new IniFile();
160 char *path = stredup(filename + basepath_length);
161 ini->LoadFromDisk(path, BASESET_DIR);
163 char *psep = strrchr(path, PATHSEPCHAR);
164 if (psep != nullptr) {
165 psep[1] = '\0';
166 } else {
167 *path = '\0';
170 if (set->FillSetDetails(ini, path, filename)) {
171 Tbase_set *duplicate = nullptr;
172 for (Tbase_set *c = BaseMedia<Tbase_set>::available_sets; c != nullptr; c = c->next) {
173 if (strcmp(c->name, set->name) == 0 || c->shortname == set->shortname) {
174 duplicate = c;
175 break;
178 if (duplicate != nullptr) {
179 /* The more complete set takes precedence over the version number. */
180 if ((duplicate->valid_files == set->valid_files && duplicate->version >= set->version) ||
181 duplicate->valid_files > set->valid_files) {
182 DEBUG(grf, 1, "Not adding %s (%i) as base " SET_TYPE " set (duplicate, %s)", set->name, set->version,
183 duplicate->valid_files > set->valid_files ? "less valid files" : "lower version");
184 set->next = BaseMedia<Tbase_set>::duplicate_sets;
185 BaseMedia<Tbase_set>::duplicate_sets = set;
186 } else {
187 Tbase_set **prev = &BaseMedia<Tbase_set>::available_sets;
188 while (*prev != duplicate) prev = &(*prev)->next;
190 *prev = set;
191 set->next = duplicate->next;
193 /* If the duplicate set is currently used (due to rescanning this can happen)
194 * update the currently used set to the new one. This will 'lie' about the
195 * version number until a new game is started which isn't a big problem */
196 if (BaseMedia<Tbase_set>::used_set == duplicate) BaseMedia<Tbase_set>::used_set = set;
198 DEBUG(grf, 1, "Removing %s (%i) as base " SET_TYPE " set (duplicate, %s)", duplicate->name, duplicate->version,
199 duplicate->valid_files < set->valid_files ? "less valid files" : "lower version");
200 duplicate->next = BaseMedia<Tbase_set>::duplicate_sets;
201 BaseMedia<Tbase_set>::duplicate_sets = duplicate;
202 ret = true;
204 } else {
205 Tbase_set **last = &BaseMedia<Tbase_set>::available_sets;
206 while (*last != nullptr) last = &(*last)->next;
208 *last = set;
209 ret = true;
211 if (ret) {
212 DEBUG(grf, 1, "Adding %s (%i) as base " SET_TYPE " set", set->name, set->version);
214 } else {
215 delete set;
217 free(path);
219 delete ini;
220 return ret;
224 * Set the set to be used.
225 * @param name of the set to use
226 * @return true if it could be loaded
228 template <class Tbase_set>
229 /* static */ bool BaseMedia<Tbase_set>::SetSet(const char *name)
231 extern void CheckExternalFiles();
233 if (StrEmpty(name)) {
234 if (!BaseMedia<Tbase_set>::DetermineBestSet()) return false;
235 CheckExternalFiles();
236 return true;
239 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != nullptr; s = s->next) {
240 if (strcmp(name, s->name) == 0) {
241 BaseMedia<Tbase_set>::used_set = s;
242 CheckExternalFiles();
243 return true;
246 return false;
250 * Returns a list with the sets.
251 * @param p where to print to
252 * @param last the last character to print to
253 * @return the last printed character
255 template <class Tbase_set>
256 /* static */ char *BaseMedia<Tbase_set>::GetSetsList(char *p, const char *last)
258 p += seprintf(p, last, "List of " SET_TYPE " sets:\n");
259 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != nullptr; s = s->next) {
260 p += seprintf(p, last, "%18s: %s", s->name, s->GetDescription());
261 int invalid = s->GetNumInvalid();
262 if (invalid != 0) {
263 int missing = s->GetNumMissing();
264 if (missing == 0) {
265 p += seprintf(p, last, " (%i corrupt file%s)\n", invalid, invalid == 1 ? "" : "s");
266 } else {
267 p += seprintf(p, last, " (unusable: %i missing file%s)\n", missing, missing == 1 ? "" : "s");
269 } else {
270 p += seprintf(p, last, "\n");
273 p += seprintf(p, last, "\n");
275 return p;
278 #include "network/network_content.h"
280 template <class Tbase_set> const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const Tbase_set *s)
282 for (; s != nullptr; s = s->next) {
283 if (s->GetNumMissing() != 0) continue;
285 if (s->shortname != ci->unique_id) continue;
286 if (!md5sum) return s->files[0].filename;
288 byte md5[16];
289 memset(md5, 0, sizeof(md5));
290 for (uint i = 0; i < Tbase_set::NUM_FILES; i++) {
291 for (uint j = 0; j < sizeof(md5); j++) {
292 md5[j] ^= s->files[i].hash[j];
295 if (memcmp(md5, ci->md5sum, sizeof(md5)) == 0) return s->files[0].filename;
297 return nullptr;
300 template <class Tbase_set>
301 /* static */ bool BaseMedia<Tbase_set>::HasSet(const ContentInfo *ci, bool md5sum)
303 return (TryGetBaseSetFile(ci, md5sum, BaseMedia<Tbase_set>::available_sets) != nullptr) ||
304 (TryGetBaseSetFile(ci, md5sum, BaseMedia<Tbase_set>::duplicate_sets) != nullptr);
308 * Count the number of available graphics sets.
309 * @return the number of sets
311 template <class Tbase_set>
312 /* static */ int BaseMedia<Tbase_set>::GetNumSets()
314 int n = 0;
315 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != nullptr; s = s->next) {
316 if (s != BaseMedia<Tbase_set>::used_set && s->GetNumMissing() != 0) continue;
317 n++;
319 return n;
323 * Get the index of the currently active graphics set
324 * @return the current set's index
326 template <class Tbase_set>
327 /* static */ int BaseMedia<Tbase_set>::GetIndexOfUsedSet()
329 int n = 0;
330 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != nullptr; s = s->next) {
331 if (s == BaseMedia<Tbase_set>::used_set) return n;
332 if (s->GetNumMissing() != 0) continue;
333 n++;
335 return -1;
339 * Get the name of the graphics set at the specified index
340 * @return the name of the set
342 template <class Tbase_set>
343 /* static */ const Tbase_set *BaseMedia<Tbase_set>::GetSet(int index)
345 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != nullptr; s = s->next) {
346 if (s != BaseMedia<Tbase_set>::used_set && s->GetNumMissing() != 0) continue;
347 if (index == 0) return s;
348 index--;
350 error("Base" SET_TYPE "::GetSet(): index %d out of range", index);
354 * Return the used set.
355 * @return the used set.
357 template <class Tbase_set>
358 /* static */ const Tbase_set *BaseMedia<Tbase_set>::GetUsedSet()
360 return BaseMedia<Tbase_set>::used_set;
364 * Return the available sets.
365 * @return The available sets.
367 template <class Tbase_set>
368 /* static */ Tbase_set *BaseMedia<Tbase_set>::GetAvailableSets()
370 return BaseMedia<Tbase_set>::available_sets;
374 * Force instantiation of methods so we don't get linker errors.
375 * @param repl_type the type of the BaseMedia to instantiate
376 * @param set_type the type of the BaseSet to instantiate
378 #define INSTANTIATE_BASE_MEDIA_METHODS(repl_type, set_type) \
379 template const char *repl_type::ini_set; \
380 template const char *repl_type::GetExtension(); \
381 template bool repl_type::AddFile(const char *filename, size_t pathlength, const char *tar_filename); \
382 template bool repl_type::HasSet(const struct ContentInfo *ci, bool md5sum); \
383 template bool repl_type::SetSet(const char *name); \
384 template char *repl_type::GetSetsList(char *p, const char *last); \
385 template int repl_type::GetNumSets(); \
386 template int repl_type::GetIndexOfUsedSet(); \
387 template const set_type *repl_type::GetSet(int index); \
388 template const set_type *repl_type::GetUsedSet(); \
389 template bool repl_type::DetermineBestSet(); \
390 template set_type *repl_type::GetAvailableSets(); \
391 template const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const set_type *s);