Codefix: Documentation comment in IndustryDirectoryWindow (#13059)
[openttd-github.git] / src / base_media_func.h
blob2d68caaa6b8b35ca88a8b020ddd5c17e3ca0951e
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"
17 #include "error_func.h"
19 extern void CheckExternalFiles();
21 /**
22 * Try to read a single piece of metadata and return false if it doesn't exist.
23 * @param name the name of the item to fetch.
25 #define fetch_metadata(name) \
26 item = metadata->GetItem(name); \
27 if (item == nullptr || !item->value.has_value() || item->value->empty()) { \
28 Debug(grf, 0, "Base " SET_TYPE "set detail loading: {} field missing.", name); \
29 Debug(grf, 0, " Is {} readable for the user running OpenTTD?", full_filename); \
30 return false; \
33 /**
34 * Read the set information from a loaded ini.
35 * @param ini the ini to read from
36 * @param path the path to this ini file (for filenames)
37 * @param full_filename the full filename of the loaded file (for error reporting purposes)
38 * @param allow_empty_filename empty filenames are valid
39 * @return true if loading was successful.
41 template <class T, size_t Tnum_files, bool Tsearch_in_tars>
42 bool BaseSet<T, Tnum_files, Tsearch_in_tars>::FillSetDetails(const IniFile &ini, const std::string &path, const std::string &full_filename, bool allow_empty_filename)
44 const IniGroup *metadata = ini.GetGroup("metadata");
45 if (metadata == nullptr) {
46 Debug(grf, 0, "Base " SET_TYPE "set detail loading: metadata missing.");
47 Debug(grf, 0, " Is {} readable for the user running OpenTTD?", full_filename);
48 return false;
50 const IniItem *item;
52 fetch_metadata("name");
53 this->name = *item->value;
55 fetch_metadata("description");
56 this->description[std::string{}] = *item->value;
58 item = metadata->GetItem("url");
59 if (item != nullptr) this->url = *item->value;
61 /* Add the translations of the descriptions too. */
62 for (const IniItem &titem : metadata->items) {
63 if (titem.name.compare(0, 12, "description.") != 0) continue;
65 this->description[titem.name.substr(12)] = titem.value.value_or("");
68 fetch_metadata("shortname");
69 for (uint i = 0; (*item->value)[i] != '\0' && i < 4; i++) {
70 this->shortname |= ((uint8_t)(*item->value)[i]) << (i * 8);
73 fetch_metadata("version");
74 this->version = atoi(item->value->c_str());
76 item = metadata->GetItem("fallback");
77 this->fallback = (item != nullptr && item->value && *item->value != "0" && *item->value != "false");
79 /* For each of the file types we want to find the file, MD5 checksums and warning messages. */
80 const IniGroup *files = ini.GetGroup("files");
81 const IniGroup *md5s = ini.GetGroup("md5s");
82 const IniGroup *origin = ini.GetGroup("origin");
83 for (uint i = 0; i < Tnum_files; i++) {
84 MD5File *file = &this->files[i];
85 /* Find the filename first. */
86 item = files != nullptr ? files->GetItem(BaseSet<T, Tnum_files, Tsearch_in_tars>::file_names[i]) : nullptr;
87 if (item == nullptr || (!item->value.has_value() && !allow_empty_filename)) {
88 Debug(grf, 0, "No " SET_TYPE " file for: {} (in {})", BaseSet<T, Tnum_files, Tsearch_in_tars>::file_names[i], full_filename);
89 return false;
92 if (!item->value.has_value()) {
93 file->filename.clear();
94 /* If we list no file, that file must be valid */
95 this->valid_files++;
96 this->found_files++;
97 continue;
100 const std::string &filename = item->value.value();
101 file->filename = path + filename;
103 /* Then find the MD5 checksum */
104 item = md5s != nullptr ? md5s->GetItem(filename) : nullptr;
105 if (item == nullptr || !item->value.has_value()) {
106 Debug(grf, 0, "No MD5 checksum specified for: {} (in {})", filename, full_filename);
107 return false;
109 const char *c = item->value->c_str();
110 for (size_t i = 0; i < file->hash.size() * 2; i++, c++) {
111 uint j;
112 if ('0' <= *c && *c <= '9') {
113 j = *c - '0';
114 } else if ('a' <= *c && *c <= 'f') {
115 j = *c - 'a' + 10;
116 } else if ('A' <= *c && *c <= 'F') {
117 j = *c - 'A' + 10;
118 } else {
119 Debug(grf, 0, "Malformed MD5 checksum specified for: {} (in {})", filename, full_filename);
120 return false;
122 if (i % 2 == 0) {
123 file->hash[i / 2] = j << 4;
124 } else {
125 file->hash[i / 2] |= j;
129 /* Then find the warning message when the file's missing */
130 item = origin != nullptr ? origin->GetItem(filename) : nullptr;
131 if (item == nullptr) item = origin != nullptr ? origin->GetItem("default") : nullptr;
132 if (item == nullptr || !item->value.has_value()) {
133 Debug(grf, 1, "No origin warning message specified for: {}", filename);
134 file->missing_warning.clear();
135 } else {
136 file->missing_warning = item->value.value();
139 file->check_result = T::CheckMD5(file, BASESET_DIR);
140 switch (file->check_result) {
141 case MD5File::CR_UNKNOWN:
142 break;
144 case MD5File::CR_MATCH:
145 this->valid_files++;
146 this->found_files++;
147 break;
149 case MD5File::CR_MISMATCH:
150 Debug(grf, 1, "MD5 checksum mismatch for: {} (in {})", filename, full_filename);
151 this->found_files++;
152 break;
154 case MD5File::CR_NO_FILE:
155 Debug(grf, 1, "The file {} specified in {} is missing", filename, full_filename);
156 break;
160 return true;
163 template <class Tbase_set>
164 bool BaseMedia<Tbase_set>::AddFile(const std::string &filename, size_t basepath_length, const std::string &)
166 bool ret = false;
167 Debug(grf, 1, "Checking {} for base " SET_TYPE " set", filename);
169 Tbase_set *set = new Tbase_set();
170 IniFile ini{};
171 std::string path{ filename, basepath_length };
172 ini.LoadFromDisk(path, BASESET_DIR);
174 auto psep = path.rfind(PATHSEPCHAR);
175 if (psep != std::string::npos) {
176 path.erase(psep + 1);
177 } else {
178 path.clear();
181 if (set->FillSetDetails(ini, path, filename)) {
182 Tbase_set *duplicate = nullptr;
183 for (Tbase_set *c = BaseMedia<Tbase_set>::available_sets; c != nullptr; c = c->next) {
184 if (c->name == set->name || c->shortname == set->shortname) {
185 duplicate = c;
186 break;
189 if (duplicate != nullptr) {
190 /* The more complete set takes precedence over the version number. */
191 if ((duplicate->valid_files == set->valid_files && duplicate->version >= set->version) ||
192 duplicate->valid_files > set->valid_files) {
193 Debug(grf, 1, "Not adding {} ({}) as base " SET_TYPE " set (duplicate, {})", set->name, set->version,
194 duplicate->valid_files > set->valid_files ? "less valid files" : "lower version");
195 set->next = BaseMedia<Tbase_set>::duplicate_sets;
196 BaseMedia<Tbase_set>::duplicate_sets = set;
197 } else {
198 Tbase_set **prev = &BaseMedia<Tbase_set>::available_sets;
199 while (*prev != duplicate) prev = &(*prev)->next;
201 *prev = set;
202 set->next = duplicate->next;
204 /* Keep baseset configuration, if compatible */
205 set->CopyCompatibleConfig(*duplicate);
207 /* If the duplicate set is currently used (due to rescanning this can happen)
208 * update the currently used set to the new one. This will 'lie' about the
209 * version number until a new game is started which isn't a big problem */
210 if (BaseMedia<Tbase_set>::used_set == duplicate) BaseMedia<Tbase_set>::used_set = set;
212 Debug(grf, 1, "Removing {} ({}) as base " SET_TYPE " set (duplicate, {})", duplicate->name, duplicate->version,
213 duplicate->valid_files < set->valid_files ? "less valid files" : "lower version");
214 duplicate->next = BaseMedia<Tbase_set>::duplicate_sets;
215 BaseMedia<Tbase_set>::duplicate_sets = duplicate;
216 ret = true;
218 } else {
219 Tbase_set **last = &BaseMedia<Tbase_set>::available_sets;
220 while (*last != nullptr) last = &(*last)->next;
222 *last = set;
223 ret = true;
225 if (ret) {
226 Debug(grf, 1, "Adding {} ({}) as base " SET_TYPE " set", set->name, set->version);
228 } else {
229 delete set;
232 return ret;
236 * Set the set to be used.
237 * @param set the set to use
238 * @return true if it could be loaded
240 template <class Tbase_set>
241 /* static */ bool BaseMedia<Tbase_set>::SetSet(const Tbase_set *set)
243 if (set == nullptr) {
244 if (!BaseMedia<Tbase_set>::DetermineBestSet()) return false;
245 } else {
246 BaseMedia<Tbase_set>::used_set = set;
248 CheckExternalFiles();
249 return true;
253 * Set the set to be used.
254 * @param name of the set to use
255 * @return true if it could be loaded
257 template <class Tbase_set>
258 /* static */ bool BaseMedia<Tbase_set>::SetSetByName(const std::string &name)
260 if (name.empty()) {
261 return SetSet(nullptr);
264 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != nullptr; s = s->next) {
265 if (name == s->name) {
266 return SetSet(s);
269 return false;
273 * Set the set to be used.
274 * @param shortname of the set to use
275 * @return true if it could be loaded
277 template <class Tbase_set>
278 /* static */ bool BaseMedia<Tbase_set>::SetSetByShortname(uint32_t shortname)
280 if (shortname == 0) {
281 return SetSet(nullptr);
284 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != nullptr; s = s->next) {
285 if (shortname == s->shortname) {
286 return SetSet(s);
289 return false;
293 * Returns a list with the sets.
294 * @param output_iterator The iterator to write the string to.
296 template <class Tbase_set>
297 /* static */ void BaseMedia<Tbase_set>::GetSetsList(std::back_insert_iterator<std::string> &output_iterator)
299 fmt::format_to(output_iterator, "List of " SET_TYPE " sets:\n");
300 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != nullptr; s = s->next) {
301 fmt::format_to(output_iterator, "{:>18}: {}", s->name, s->GetDescription({}));
302 int invalid = s->GetNumInvalid();
303 if (invalid != 0) {
304 int missing = s->GetNumMissing();
305 if (missing == 0) {
306 fmt::format_to(output_iterator, " ({} corrupt file{})\n", invalid, invalid == 1 ? "" : "s");
307 } else {
308 fmt::format_to(output_iterator, " (unusable: {} missing file{})\n", missing, missing == 1 ? "" : "s");
310 } else {
311 fmt::format_to(output_iterator, "\n");
314 fmt::format_to(output_iterator, "\n");
317 #include "network/core/tcp_content_type.h"
319 template <class Tbase_set> const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const Tbase_set *s)
321 for (; s != nullptr; s = s->next) {
322 if (s->GetNumMissing() != 0) continue;
324 if (s->shortname != ci->unique_id) continue;
325 if (!md5sum) return s->files[0].filename.c_str();
327 MD5Hash md5;
328 for (const auto &file : s->files) {
329 md5 ^= file.hash;
331 if (md5 == ci->md5sum) return s->files[0].filename.c_str();
333 return nullptr;
336 template <class Tbase_set>
337 /* static */ bool BaseMedia<Tbase_set>::HasSet(const ContentInfo *ci, bool md5sum)
339 return (TryGetBaseSetFile(ci, md5sum, BaseMedia<Tbase_set>::available_sets) != nullptr) ||
340 (TryGetBaseSetFile(ci, md5sum, BaseMedia<Tbase_set>::duplicate_sets) != nullptr);
344 * Count the number of available graphics sets.
345 * @return the number of sets
347 template <class Tbase_set>
348 /* static */ int BaseMedia<Tbase_set>::GetNumSets()
350 int n = 0;
351 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != nullptr; s = s->next) {
352 if (s != BaseMedia<Tbase_set>::used_set && s->GetNumMissing() != 0) continue;
353 n++;
355 return n;
359 * Get the index of the currently active graphics set
360 * @return the current set's index
362 template <class Tbase_set>
363 /* static */ int BaseMedia<Tbase_set>::GetIndexOfUsedSet()
365 int n = 0;
366 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != nullptr; s = s->next) {
367 if (s == BaseMedia<Tbase_set>::used_set) return n;
368 if (s->GetNumMissing() != 0) continue;
369 n++;
371 return -1;
375 * Get the name of the graphics set at the specified index
376 * @return the name of the set
378 template <class Tbase_set>
379 /* static */ const Tbase_set *BaseMedia<Tbase_set>::GetSet(int index)
381 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != nullptr; s = s->next) {
382 if (s != BaseMedia<Tbase_set>::used_set && s->GetNumMissing() != 0) continue;
383 if (index == 0) return s;
384 index--;
386 FatalError("Base" SET_TYPE "::GetSet(): index {} out of range", index);
390 * Return the used set.
391 * @return the used set.
393 template <class Tbase_set>
394 /* static */ const Tbase_set *BaseMedia<Tbase_set>::GetUsedSet()
396 return BaseMedia<Tbase_set>::used_set;
400 * Return the available sets.
401 * @return The available sets.
403 template <class Tbase_set>
404 /* static */ Tbase_set *BaseMedia<Tbase_set>::GetAvailableSets()
406 return BaseMedia<Tbase_set>::available_sets;
410 * Force instantiation of methods so we don't get linker errors.
411 * @param repl_type the type of the BaseMedia to instantiate
412 * @param set_type the type of the BaseSet to instantiate
414 #define INSTANTIATE_BASE_MEDIA_METHODS(repl_type, set_type) \
415 template const char *repl_type::GetExtension(); \
416 template bool repl_type::AddFile(const std::string &filename, size_t pathlength, const std::string &tar_filename); \
417 template bool repl_type::HasSet(const struct ContentInfo *ci, bool md5sum); \
418 template bool repl_type::SetSet(const set_type *set); \
419 template bool repl_type::SetSetByName(const std::string &name); \
420 template bool repl_type::SetSetByShortname(uint32_t shortname); \
421 template void repl_type::GetSetsList(std::back_insert_iterator<std::string> &output_iterator); \
422 template int repl_type::GetNumSets(); \
423 template int repl_type::GetIndexOfUsedSet(); \
424 template const set_type *repl_type::GetSet(int index); \
425 template const set_type *repl_type::GetUsedSet(); \
426 template bool repl_type::DetermineBestSet(); \
427 template set_type *repl_type::GetAvailableSets(); \
428 template const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const set_type *s);