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/>.
11 * @file base_media_func.h Generic function implementations for base data (graphics, sounds).
14 #include "base_media_base.h"
19 template <class Tbase_set
> /* static */ const char *BaseMedia
<Tbase_set
>::ini_set
;
20 template <class Tbase_set
> /* static */ const Tbase_set
*BaseMedia
<Tbase_set
>::used_set
;
21 template <class Tbase_set
> /* static */ Tbase_set
*BaseMedia
<Tbase_set
>::available_sets
;
22 template <class Tbase_set
> /* static */ Tbase_set
*BaseMedia
<Tbase_set
>::duplicate_sets
;
25 * Try to read a single piece of metadata.
26 * @param metadata the metadata group to search in.
27 * @param name the name of the item to fetch.
28 * @param filename the name of the filename for debugging output.
29 * @return the associated item, or NULL if it doesn't exist.
32 static inline const IniItem
*fetch_metadata (const IniGroup
*metadata
,
33 const char *name
, const char *filename
)
35 const IniItem
*item
= metadata
->find (name
);
36 if (item
== NULL
|| StrEmpty (item
->value
)) {
37 DEBUG (grf
, 0, "Base %sset detail loading: %s field missing in %s.",
38 T::set_type
, name
, filename
);
45 * Read the set information from a loaded ini.
46 * @param ini the ini to read from
47 * @param path the path to this ini file (for filenames)
48 * @param full_filename the full filename of the loaded file (for error reporting purposes)
49 * @param allow_empty_filename empty filenames are valid
50 * @return true if loading was successful.
52 template <class T
, size_t Tnum_files
>
53 bool BaseSet
<T
, Tnum_files
>::FillSetDetails (IniFile
*ini
, const char *path
, const char *full_filename
, bool allow_empty_filename
)
55 memset (this->files
, 0, sizeof(this->files
));
56 this->found_files
= 0;
57 this->valid_files
= 0;
59 const IniGroup
*metadata
= ini
->find ("metadata");
60 if (metadata
== NULL
) {
61 DEBUG (grf
, 0, "Base %sset detail loading: metadata group missing.", T::set_type
);
62 DEBUG (grf
, 0, " Is %s readable for the user running OpenTTD?", full_filename
);
68 item
= fetch_metadata
<T
> (metadata
, "name", full_filename
);
69 if (item
== NULL
) return false;
70 this->set_name (item
->value
);
72 item
= fetch_metadata
<T
> (metadata
, "description", full_filename
);
73 if (item
== NULL
) return false;
74 this->add_default_desc (item
->value
);
76 /* Add the translations of the descriptions too. */
77 for (IniItem::const_iterator item
= metadata
->cbegin(); item
!= metadata
->cend(); item
++) {
78 if (strncmp("description.", item
->get_name(), 12) != 0) continue;
80 this->add_desc (item
->get_name() + 12, item
->value
);
83 item
= fetch_metadata
<T
> (metadata
, "shortname", full_filename
);
84 if (item
== NULL
) return false;
85 for (uint i
= 0; item
->value
[i
] != '\0' && i
< 4; i
++) {
86 this->shortname
|= ((uint8
)item
->value
[i
]) << (i
* 8);
89 item
= fetch_metadata
<T
> (metadata
, "version", full_filename
);
90 if (item
== NULL
) return false;
91 this->version
= atoi(item
->value
);
93 item
= metadata
->find ("fallback");
94 this->fallback
= (item
!= NULL
&& strcmp(item
->value
, "0") != 0 && strcmp(item
->value
, "false") != 0);
96 /* For each of the file types we want to find the file, MD5 checksums and warning messages. */
97 const IniGroup
*files
= ini
->get_group ("files");
98 const IniGroup
*md5s
= ini
->get_group ("md5s");
99 const IniGroup
*origin
= ini
->get_group ("origin");
100 for (uint i
= 0; i
< Tnum_files
; i
++) {
101 MD5File
*file
= &this->files
[i
];
102 /* Find the filename first. */
103 item
= files
->find (T::file_names
[i
]);
104 if (item
== NULL
|| (item
->value
== NULL
&& !allow_empty_filename
)) {
105 DEBUG(grf
, 0, "No %s file for: %s (in %s)", T::set_type
, T::file_names
[i
], full_filename
);
109 const char *filename
= item
->value
;
110 if (filename
== NULL
) {
111 file
->filename
= NULL
;
112 /* If we list no file, that file must be valid */
118 file
->filename
= str_fmt("%s%s", path
, filename
);
120 /* Then find the MD5 checksum */
121 item
= md5s
->find (filename
);
122 if (item
== NULL
|| item
->value
== NULL
) {
123 DEBUG(grf
, 0, "No MD5 checksum specified for: %s (in %s)", filename
, full_filename
);
126 char *c
= item
->value
;
127 for (uint i
= 0; i
< sizeof(file
->hash
) * 2; i
++, c
++) {
129 if ('0' <= *c
&& *c
<= '9') {
131 } else if ('a' <= *c
&& *c
<= 'f') {
133 } else if ('A' <= *c
&& *c
<= 'F') {
136 DEBUG(grf
, 0, "Malformed MD5 checksum specified for: %s (in %s)", filename
, full_filename
);
140 file
->hash
[i
/ 2] = j
<< 4;
142 file
->hash
[i
/ 2] |= j
;
146 /* Then find the warning message when the file's missing */
147 item
= origin
->find (filename
);
148 if (item
== NULL
) item
= origin
->find ("default");
150 DEBUG(grf
, 1, "No origin warning message specified for: %s", filename
);
151 file
->missing_warning
= xstrdup("");
153 file
->missing_warning
= xstrdup(item
->value
);
156 switch (T::CheckMD5(file
, BASESET_DIR
)) {
157 case MD5File::CR_MATCH
:
162 case MD5File::CR_MISMATCH
:
163 DEBUG(grf
, 1, "MD5 checksum mismatch for: %s (in %s)", filename
, full_filename
);
167 case MD5File::CR_NO_FILE
:
168 DEBUG(grf
, 1, "The file %s specified in %s is missing", filename
, full_filename
);
176 template <class Tbase_set
>
177 bool BaseMedia
<Tbase_set
>::Scanner::AddFile (const char *filename
, size_t basepath_length
, const char *tar_filename
)
180 DEBUG(grf
, 1, "Checking %s for base %s set", filename
, Tbase_set::set_type
);
182 Tbase_set
*set
= new Tbase_set();
184 char *path
= xstrdup(filename
+ basepath_length
);
185 char *psep
= strrchr(path
, PATHSEPCHAR
);
192 IniFile
ini (filename
, BASESET_DIR
);
194 if (set
->FillSetDetails (&ini
, path
, filename
)) {
195 Tbase_set
*duplicate
= NULL
;
196 for (Tbase_set
*c
= BaseMedia
<Tbase_set
>::available_sets
; c
!= NULL
; c
= c
->next
) {
197 if (strcmp(c
->get_name(), set
->get_name()) == 0 || c
->shortname
== set
->shortname
) {
202 if (duplicate
!= NULL
) {
203 /* The more complete set takes precedence over the version number. */
204 if ((duplicate
->valid_files
== set
->valid_files
&& duplicate
->version
>= set
->version
) ||
205 duplicate
->valid_files
> set
->valid_files
) {
206 DEBUG(grf
, 1, "Not adding %s (%i) as base %s set (duplicate, %s)",
207 set
->get_name(), set
->version
, Tbase_set::set_type
,
208 duplicate
->valid_files
> set
->valid_files
? "less valid files" : "lower version");
209 set
->next
= BaseMedia
<Tbase_set
>::duplicate_sets
;
210 BaseMedia
<Tbase_set
>::duplicate_sets
= set
;
212 Tbase_set
**prev
= &BaseMedia
<Tbase_set
>::available_sets
;
213 while (*prev
!= duplicate
) prev
= &(*prev
)->next
;
216 set
->next
= duplicate
->next
;
218 /* If the duplicate set is currently used (due to rescanning this can happen)
219 * update the currently used set to the new one. This will 'lie' about the
220 * version number until a new game is started which isn't a big problem */
221 if (BaseMedia
<Tbase_set
>::used_set
== duplicate
) BaseMedia
<Tbase_set
>::used_set
= set
;
223 DEBUG(grf
, 1, "Removing %s (%i) as base %s set (duplicate, %s)",
224 duplicate
->get_name(), duplicate
->version
, Tbase_set::set_type
,
225 duplicate
->valid_files
< set
->valid_files
? "less valid files" : "lower version");
226 duplicate
->next
= BaseMedia
<Tbase_set
>::duplicate_sets
;
227 BaseMedia
<Tbase_set
>::duplicate_sets
= duplicate
;
231 Tbase_set
**last
= &BaseMedia
<Tbase_set
>::available_sets
;
232 while (*last
!= NULL
) last
= &(*last
)->next
;
238 DEBUG(grf
, 1, "Adding %s (%i) as base %s set", set
->get_name(), set
->version
, Tbase_set::set_type
);
249 * Set the set to be used.
250 * @param name of the set to use
251 * @return true if it could be loaded
253 template <class Tbase_set
>
254 /* static */ bool BaseMedia
<Tbase_set
>::SetSet(const char *name
)
256 extern void CheckExternalFiles();
258 if (StrEmpty(name
)) {
259 if (!BaseMedia
<Tbase_set
>::DetermineBestSet()) return false;
260 CheckExternalFiles();
264 for (const Tbase_set
*s
= BaseMedia
<Tbase_set
>::available_sets
; s
!= NULL
; s
= s
->next
) {
265 if (strcmp(name
, s
->get_name()) == 0) {
266 BaseMedia
<Tbase_set
>::used_set
= s
;
267 CheckExternalFiles();
275 * Returns a list with the sets.
276 * @param buf where to print to
278 template <class Tbase_set
>
279 /* static */ void BaseMedia
<Tbase_set
>::GetSetsList (stringb
*buf
)
281 buf
->append_fmt ("List of %s sets:\n", Tbase_set::set_type
);
282 for (const Tbase_set
*s
= BaseMedia
<Tbase_set
>::available_sets
; s
!= NULL
; s
= s
->next
) {
283 buf
->append_fmt ("%18s: %s", s
->get_name(), s
->get_default_desc());
284 int invalid
= s
->GetNumInvalid();
286 int missing
= s
->GetNumMissing();
288 buf
->append_fmt (" (%i corrupt file%s)\n", invalid
, invalid
== 1 ? "" : "s");
290 buf
->append_fmt (" (unusable: %i missing file%s)\n", missing
, missing
== 1 ? "" : "s");
299 #if defined(ENABLE_NETWORK)
300 #include "network/network_content.h"
302 template <class Tbase_set
> const char *TryGetBaseSetFile(const ContentInfo
*ci
, bool md5sum
, const Tbase_set
*s
)
304 for (; s
!= NULL
; s
= s
->next
) {
305 if (s
->GetNumMissing() != 0) continue;
307 if (s
->shortname
!= ci
->unique_id
) continue;
308 if (!md5sum
) return s
->files
[0].filename
;
311 memset(md5
, 0, sizeof(md5
));
312 for (uint i
= 0; i
< Tbase_set::NUM_FILES
; i
++) {
313 for (uint j
= 0; j
< sizeof(md5
); j
++) {
314 md5
[j
] ^= s
->files
[i
].hash
[j
];
317 if (memcmp(md5
, ci
->md5sum
, sizeof(md5
)) == 0) return s
->files
[0].filename
;
322 template <class Tbase_set
>
323 /* static */ bool BaseMedia
<Tbase_set
>::HasSet(const ContentInfo
*ci
, bool md5sum
)
325 return (TryGetBaseSetFile(ci
, md5sum
, BaseMedia
<Tbase_set
>::available_sets
) != NULL
) ||
326 (TryGetBaseSetFile(ci
, md5sum
, BaseMedia
<Tbase_set
>::duplicate_sets
) != NULL
);
331 template <class Tbase_set
>
332 const char *TryGetBaseSetFile(const ContentInfo
*ci
, bool md5sum
, const Tbase_set
*s
)
337 template <class Tbase_set
>
338 /* static */ bool BaseMedia
<Tbase_set
>::HasSet(const ContentInfo
*ci
, bool md5sum
)
343 #endif /* ENABLE_NETWORK */
346 * Count the number of available graphics sets.
347 * @return the number of sets
349 template <class Tbase_set
>
350 /* static */ int BaseMedia
<Tbase_set
>::GetNumSets()
353 for (const Tbase_set
*s
= BaseMedia
<Tbase_set
>::available_sets
; s
!= NULL
; s
= s
->next
) {
354 if (s
!= BaseMedia
<Tbase_set
>::used_set
&& s
->GetNumMissing() != 0) continue;
361 * Get the index of the currently active graphics set
362 * @return the current set's index
364 template <class Tbase_set
>
365 /* static */ int BaseMedia
<Tbase_set
>::GetIndexOfUsedSet()
368 for (const Tbase_set
*s
= BaseMedia
<Tbase_set
>::available_sets
; s
!= NULL
; s
= s
->next
) {
369 if (s
== BaseMedia
<Tbase_set
>::used_set
) return n
;
370 if (s
->GetNumMissing() != 0) continue;
377 * Get the name of the graphics set at the specified index
378 * @return the name of the set
380 template <class Tbase_set
>
381 /* static */ const Tbase_set
*BaseMedia
<Tbase_set
>::GetSet(int index
)
383 for (const Tbase_set
*s
= BaseMedia
<Tbase_set
>::available_sets
; s
!= NULL
; s
= s
->next
) {
384 if (s
!= BaseMedia
<Tbase_set
>::used_set
&& s
->GetNumMissing() != 0) continue;
385 if (index
== 0) return s
;
388 error("Base%s::GetSet(): index %d out of range", Tbase_set::set_type
, index
);
392 * Return the used set.
393 * @return the used set.
395 template <class Tbase_set
>
396 /* static */ const Tbase_set
*BaseMedia
<Tbase_set
>::GetUsedSet()
398 return BaseMedia
<Tbase_set
>::used_set
;
402 * Return the available sets.
403 * @return The available sets.
405 template <class Tbase_set
>
406 /* static */ Tbase_set
*BaseMedia
<Tbase_set
>::GetAvailableSets()
408 return BaseMedia
<Tbase_set
>::available_sets
;
411 template <class Tbase_set
>
412 /* static */ bool BaseMedia
<Tbase_set
>::DetermineBestSet()
414 if (BaseMedia
<Tbase_set
>::used_set
!= NULL
) return true;
416 const Tbase_set
*best
= NULL
;
417 for (const Tbase_set
*c
= BaseMedia
<Tbase_set
>::available_sets
; c
!= NULL
; c
= c
->next
) {
418 /* Skip unusable sets */
419 if (c
->GetNumMissing() != 0) continue;
422 (best
->fallback
&& !c
->fallback
) ||
423 best
->valid_files
< c
->valid_files
||
424 (best
->valid_files
== c
->valid_files
&& (
425 (best
->shortname
== c
->shortname
&& best
->version
< c
->version
) ||
426 c
->IsPreferredTo (*best
)))) {
431 BaseMedia
<Tbase_set
>::used_set
= best
;
432 return BaseMedia
<Tbase_set
>::used_set
!= NULL
;
436 * Force instantiation of methods so we don't get linker errors.
437 * @param repl_type the type of the BaseMedia to instantiate
438 * @param set_type the type of the BaseSet to instantiate
440 #define INSTANTIATE_BASE_MEDIA_METHODS(repl_type, set_type) \
441 template const char *repl_type::ini_set; \
442 template bool repl_type::Scanner::AddFile(const char *filename, size_t pathlength, const char *tar_filename); \
443 template bool repl_type::HasSet(const struct ContentInfo *ci, bool md5sum); \
444 template bool repl_type::SetSet(const char *name); \
445 template void repl_type::GetSetsList (stringb *buf); \
446 template int repl_type::GetNumSets(); \
447 template int repl_type::GetIndexOfUsedSet(); \
448 template const set_type *repl_type::GetSet(int index); \
449 template const set_type *repl_type::GetUsedSet(); \
450 template bool repl_type::DetermineBestSet(); \
451 template set_type *repl_type::GetAvailableSets(); \
452 template const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const set_type *s);