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/>.
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"
16 #include "string_func.h"
17 #include "error_func.h"
19 extern void CheckExternalFiles();
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); \
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
);
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
);
92 if (!item
->value
.has_value()) {
93 file
->filename
.clear();
94 /* If we list no file, that file must be valid */
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
);
109 const char *c
= item
->value
->c_str();
110 for (size_t i
= 0; i
< file
->hash
.size() * 2; i
++, c
++) {
112 if ('0' <= *c
&& *c
<= '9') {
114 } else if ('a' <= *c
&& *c
<= 'f') {
116 } else if ('A' <= *c
&& *c
<= 'F') {
119 Debug(grf
, 0, "Malformed MD5 checksum specified for: {} (in {})", filename
, full_filename
);
123 file
->hash
[i
/ 2] = j
<< 4;
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();
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
:
144 case MD5File::CR_MATCH
:
149 case MD5File::CR_MISMATCH
:
150 Debug(grf
, 1, "MD5 checksum mismatch for: {} (in {})", filename
, full_filename
);
154 case MD5File::CR_NO_FILE
:
155 Debug(grf
, 1, "The file {} specified in {} is missing", filename
, full_filename
);
163 template <class Tbase_set
>
164 bool BaseMedia
<Tbase_set
>::AddFile(const std::string
&filename
, size_t basepath_length
, const std::string
&)
167 Debug(grf
, 1, "Checking {} for base " SET_TYPE
" set", filename
);
169 Tbase_set
*set
= new Tbase_set();
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);
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
) {
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
;
198 Tbase_set
**prev
= &BaseMedia
<Tbase_set
>::available_sets
;
199 while (*prev
!= duplicate
) prev
= &(*prev
)->next
;
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
;
219 Tbase_set
**last
= &BaseMedia
<Tbase_set
>::available_sets
;
220 while (*last
!= nullptr) last
= &(*last
)->next
;
226 Debug(grf
, 1, "Adding {} ({}) as base " SET_TYPE
" set", set
->name
, set
->version
);
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;
246 BaseMedia
<Tbase_set
>::used_set
= set
;
248 CheckExternalFiles();
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
)
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
) {
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
) {
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();
304 int missing
= s
->GetNumMissing();
306 fmt::format_to(output_iterator
, " ({} corrupt file{})\n", invalid
, invalid
== 1 ? "" : "s");
308 fmt::format_to(output_iterator
, " (unusable: {} missing file{})\n", missing
, missing
== 1 ? "" : "s");
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();
328 for (const auto &file
: s
->files
) {
331 if (md5
== ci
->md5sum
) return s
->files
[0].filename
.c_str();
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()
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;
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()
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;
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
;
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);