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"
18 template <class Tbase_set
> /* static */ const char *BaseMedia
<Tbase_set
>::ini_set
;
19 template <class Tbase_set
> /* static */ const Tbase_set
*BaseMedia
<Tbase_set
>::used_set
;
20 template <class Tbase_set
> /* static */ Tbase_set
*BaseMedia
<Tbase_set
>::available_sets
;
21 template <class Tbase_set
> /* static */ Tbase_set
*BaseMedia
<Tbase_set
>::duplicate_sets
;
24 * Read the set information from a loaded ini.
25 * @param ini the ini to read from
26 * @param path the path to this ini file (for filenames)
27 * @param full_filename the full filename of the loaded file (for error reporting purposes)
28 * @param allow_empty_filename empty filenames are valid
29 * @return true if loading was successful.
31 template <class T
, size_t Tnum_files
>
32 bool BaseSet
<T
, Tnum_files
>::FillSetDetails (IniFile
*ini
, const char *path
, const char *full_filename
, bool allow_empty_filename
)
34 memset (this->files
, 0, sizeof(this->files
));
35 this->found_files
= 0;
36 this->valid_files
= 0;
38 const IniGroup
*metadata
= ini
->find ("metadata");
39 if (metadata
== NULL
) {
40 DEBUG (grf
, 0, "Base %sset detail loading: metadata group missing.", T::set_type
);
41 DEBUG (grf
, 0, " Is %s readable for the user running OpenTTD?", full_filename
);
47 item
= this->fetch_metadata (metadata
, "name", full_filename
);
48 if (item
== NULL
) return false;
49 this->set_name (item
->value
);
51 item
= this->fetch_metadata (metadata
, "description", full_filename
);
52 if (item
== NULL
) return false;
53 this->add_default_desc (item
->value
);
55 /* Add the translations of the descriptions too. */
56 for (IniItem::const_iterator item
= metadata
->cbegin(); item
!= metadata
->cend(); item
++) {
57 if (strncmp("description.", item
->get_name(), 12) != 0) continue;
59 this->add_desc (item
->get_name() + 12, item
->value
);
62 item
= this->fetch_metadata (metadata
, "shortname", full_filename
);
63 if (item
== NULL
) return false;
64 for (uint i
= 0; item
->value
[i
] != '\0' && i
< 4; i
++) {
65 this->shortname
|= ((uint8
)item
->value
[i
]) << (i
* 8);
68 item
= this->fetch_metadata (metadata
, "version", full_filename
);
69 if (item
== NULL
) return false;
70 this->version
= atoi(item
->value
);
72 item
= metadata
->find ("fallback");
73 this->fallback
= (item
!= NULL
&& strcmp(item
->value
, "0") != 0 && strcmp(item
->value
, "false") != 0);
75 /* For each of the file types we want to find the file, MD5 checksums and warning messages. */
76 const IniGroup
*files
= ini
->get_group ("files");
77 const IniGroup
*md5s
= ini
->get_group ("md5s");
78 const IniGroup
*origin
= ini
->get_group ("origin");
79 for (uint i
= 0; i
< Tnum_files
; i
++) {
80 FileDesc
*file
= &this->files
[i
];
81 /* Find the filename first. */
82 item
= files
->find (T::file_names
[i
]);
83 if (item
== NULL
|| (item
->value
== NULL
&& !allow_empty_filename
)) {
84 DEBUG(grf
, 0, "No %s file for: %s (in %s)", T::set_type
, T::file_names
[i
], full_filename
);
88 const char *filename
= item
->value
;
89 if (filename
== NULL
) {
90 file
->filename
= NULL
;
91 /* If we list no file, that file must be valid */
92 file
->status
= FileDesc::MATCH
;
98 file
->filename
= str_fmt("%s%s", path
, filename
);
100 /* Then find the MD5 checksum */
101 item
= md5s
->find (filename
);
102 if (item
== NULL
|| item
->value
== NULL
) {
103 DEBUG(grf
, 0, "No MD5 checksum specified for: %s (in %s)", filename
, full_filename
);
106 char *c
= item
->value
;
107 for (uint i
= 0; i
< sizeof(file
->hash
) * 2; i
++, c
++) {
109 if ('0' <= *c
&& *c
<= '9') {
111 } else if ('a' <= *c
&& *c
<= 'f') {
113 } else if ('A' <= *c
&& *c
<= 'F') {
116 DEBUG(grf
, 0, "Malformed MD5 checksum specified for: %s (in %s)", filename
, full_filename
);
120 file
->hash
[i
/ 2] = j
<< 4;
122 file
->hash
[i
/ 2] |= j
;
126 /* Then find the warning message when the file's missing */
127 item
= origin
->find (filename
);
128 if (item
== NULL
) item
= origin
->find ("default");
130 DEBUG(grf
, 1, "No origin warning message specified for: %s", filename
);
131 file
->missing_warning
= xstrdup("");
133 file
->missing_warning
= xstrdup(item
->value
);
136 FileDesc::Status status
= T::CheckMD5 (file
);
137 file
->status
= status
;
139 case FileDesc::MATCH
:
144 case FileDesc::MISMATCH
:
145 DEBUG(grf
, 1, "MD5 checksum mismatch for: %s (in %s)", filename
, full_filename
);
149 case FileDesc::MISSING
:
150 DEBUG(grf
, 1, "The file %s specified in %s is missing", filename
, full_filename
);
158 template <class Tbase_set
>
159 bool BaseMedia
<Tbase_set
>::Scanner::AddFile (const char *filename
, size_t basepath_length
, const char *tar_filename
)
162 DEBUG(grf
, 1, "Checking %s for base %s set", filename
, Tbase_set::set_type
);
164 Tbase_set
*set
= new Tbase_set();
166 char *path
= xstrdup(filename
+ basepath_length
);
167 char *psep
= strrchr(path
, PATHSEPCHAR
);
174 IniFile
ini (filename
, BASESET_DIR
);
176 if (set
->FillSetDetails (&ini
, path
, filename
)) {
177 Tbase_set
*duplicate
= NULL
;
178 for (Tbase_set
*c
= BaseMedia
<Tbase_set
>::available_sets
; c
!= NULL
; c
= c
->next
) {
179 if (strcmp(c
->get_name(), set
->get_name()) == 0 || c
->shortname
== set
->shortname
) {
184 if (duplicate
!= NULL
) {
185 /* The more complete set takes precedence over the version number. */
186 if ((duplicate
->valid_files
== set
->valid_files
&& duplicate
->version
>= set
->version
) ||
187 duplicate
->valid_files
> set
->valid_files
) {
188 DEBUG(grf
, 1, "Not adding %s (%i) as base %s set (duplicate, %s)",
189 set
->get_name(), set
->version
, Tbase_set::set_type
,
190 duplicate
->valid_files
> set
->valid_files
? "less valid files" : "lower version");
191 set
->next
= BaseMedia
<Tbase_set
>::duplicate_sets
;
192 BaseMedia
<Tbase_set
>::duplicate_sets
= set
;
194 Tbase_set
**prev
= &BaseMedia
<Tbase_set
>::available_sets
;
195 while (*prev
!= duplicate
) prev
= &(*prev
)->next
;
198 set
->next
= duplicate
->next
;
200 /* If the duplicate set is currently used (due to rescanning this can happen)
201 * update the currently used set to the new one. This will 'lie' about the
202 * version number until a new game is started which isn't a big problem */
203 if (BaseMedia
<Tbase_set
>::used_set
== duplicate
) BaseMedia
<Tbase_set
>::used_set
= set
;
205 DEBUG(grf
, 1, "Removing %s (%i) as base %s set (duplicate, %s)",
206 duplicate
->get_name(), duplicate
->version
, Tbase_set::set_type
,
207 duplicate
->valid_files
< set
->valid_files
? "less valid files" : "lower version");
208 duplicate
->next
= BaseMedia
<Tbase_set
>::duplicate_sets
;
209 BaseMedia
<Tbase_set
>::duplicate_sets
= duplicate
;
213 Tbase_set
**last
= &BaseMedia
<Tbase_set
>::available_sets
;
214 while (*last
!= NULL
) last
= &(*last
)->next
;
220 DEBUG(grf
, 1, "Adding %s (%i) as base %s set", set
->get_name(), set
->version
, Tbase_set::set_type
);
231 * Set the set to be used.
232 * @param name of the set to use
233 * @return true if it could be loaded
235 template <class Tbase_set
>
236 /* static */ bool BaseMedia
<Tbase_set
>::SetSet(const char *name
)
238 if (StrEmpty(name
)) {
239 return BaseMedia
<Tbase_set
>::DetermineBestSet();
242 for (const Tbase_set
*s
= BaseMedia
<Tbase_set
>::available_sets
; s
!= NULL
; s
= s
->next
) {
243 if (strcmp(name
, s
->get_name()) == 0) {
244 BaseMedia
<Tbase_set
>::used_set
= s
;
252 * Returns a list with the sets.
253 * @param buf where to print to
255 template <class Tbase_set
>
256 /* static */ void BaseMedia
<Tbase_set
>::GetSetsList (stringb
*buf
)
258 buf
->append_fmt ("List of %s sets:\n", Tbase_set::set_type
);
259 for (const Tbase_set
*s
= BaseMedia
<Tbase_set
>::available_sets
; s
!= NULL
; s
= s
->next
) {
260 buf
->append_fmt ("%18s: %s", s
->get_name(), s
->get_default_desc());
261 int invalid
= s
->GetNumInvalid();
263 int missing
= s
->GetNumMissing();
265 buf
->append_fmt (" (%i corrupt file%s)\n", invalid
, invalid
== 1 ? "" : "s");
267 buf
->append_fmt (" (unusable: %i missing file%s)\n", missing
, missing
== 1 ? "" : "s");
276 #if defined(ENABLE_NETWORK)
277 #include "network/network_content.h"
279 template <class Tbase_set
> const char *TryGetBaseSetFile(const ContentInfo
*ci
, bool md5sum
, const Tbase_set
*s
)
281 for (; s
!= NULL
; s
= s
->next
) {
282 if (s
->GetNumMissing() != 0) continue;
284 if (s
->shortname
!= ci
->unique_id
) continue;
285 if (!md5sum
) return s
->files
[0].filename
;
288 memset(md5
, 0, sizeof(md5
));
289 for (uint i
= 0; i
< Tbase_set::NUM_FILES
; i
++) {
290 for (uint j
= 0; j
< sizeof(md5
); j
++) {
291 md5
[j
] ^= s
->files
[i
].hash
[j
];
294 if (memcmp(md5
, ci
->md5sum
, sizeof(md5
)) == 0) return s
->files
[0].filename
;
299 template <class Tbase_set
>
300 /* static */ bool BaseMedia
<Tbase_set
>::HasSet(const ContentInfo
*ci
, bool md5sum
)
302 return (TryGetBaseSetFile(ci
, md5sum
, BaseMedia
<Tbase_set
>::available_sets
) != NULL
) ||
303 (TryGetBaseSetFile(ci
, md5sum
, BaseMedia
<Tbase_set
>::duplicate_sets
) != NULL
);
308 template <class Tbase_set
>
309 const char *TryGetBaseSetFile(const ContentInfo
*ci
, bool md5sum
, const Tbase_set
*s
)
314 template <class Tbase_set
>
315 /* static */ bool BaseMedia
<Tbase_set
>::HasSet(const ContentInfo
*ci
, bool md5sum
)
320 #endif /* ENABLE_NETWORK */
323 * Count the number of available graphics sets.
324 * @return the number of sets
326 template <class Tbase_set
>
327 /* static */ int BaseMedia
<Tbase_set
>::GetNumSets()
330 for (const Tbase_set
*s
= BaseMedia
<Tbase_set
>::available_sets
; s
!= NULL
; s
= s
->next
) {
331 if (s
!= BaseMedia
<Tbase_set
>::used_set
&& s
->GetNumMissing() != 0) continue;
338 * Get the index of the currently active graphics set
339 * @return the current set's index
341 template <class Tbase_set
>
342 /* static */ int BaseMedia
<Tbase_set
>::GetIndexOfUsedSet()
345 for (const Tbase_set
*s
= BaseMedia
<Tbase_set
>::available_sets
; s
!= NULL
; s
= s
->next
) {
346 if (s
== BaseMedia
<Tbase_set
>::used_set
) return n
;
347 if (s
->GetNumMissing() != 0) continue;
354 * Get the name of the graphics set at the specified index
355 * @return the name of the set
357 template <class Tbase_set
>
358 /* static */ const Tbase_set
*BaseMedia
<Tbase_set
>::GetSet(int index
)
360 for (const Tbase_set
*s
= BaseMedia
<Tbase_set
>::available_sets
; s
!= NULL
; s
= s
->next
) {
361 if (s
!= BaseMedia
<Tbase_set
>::used_set
&& s
->GetNumMissing() != 0) continue;
362 if (index
== 0) return s
;
365 error("Base%s::GetSet(): index %d out of range", Tbase_set::set_type
, index
);
369 * Return the used set.
370 * @return the used set.
372 template <class Tbase_set
>
373 /* static */ const Tbase_set
*BaseMedia
<Tbase_set
>::GetUsedSet()
375 return BaseMedia
<Tbase_set
>::used_set
;
379 * Return the available sets.
380 * @return The available sets.
382 template <class Tbase_set
>
383 /* static */ Tbase_set
*BaseMedia
<Tbase_set
>::GetAvailableSets()
385 return BaseMedia
<Tbase_set
>::available_sets
;
388 template <class Tbase_set
>
389 /* static */ bool BaseMedia
<Tbase_set
>::DetermineBestSet()
391 if (BaseMedia
<Tbase_set
>::used_set
!= NULL
) return true;
393 const Tbase_set
*best
= NULL
;
394 for (const Tbase_set
*c
= BaseMedia
<Tbase_set
>::available_sets
; c
!= NULL
; c
= c
->next
) {
395 /* Skip unusable sets */
396 if (c
->GetNumMissing() != 0) continue;
399 (best
->fallback
&& !c
->fallback
) ||
400 best
->valid_files
< c
->valid_files
||
401 (best
->valid_files
== c
->valid_files
&& (
402 (best
->shortname
== c
->shortname
&& best
->version
< c
->version
) ||
403 c
->IsPreferredTo (*best
)))) {
408 BaseMedia
<Tbase_set
>::used_set
= best
;
409 return BaseMedia
<Tbase_set
>::used_set
!= NULL
;
413 * Force instantiation of methods so we don't get linker errors.
414 * @param repl_type the type of the BaseMedia to instantiate
415 * @param set_type the type of the BaseSet to instantiate
417 #define INSTANTIATE_BASE_MEDIA_METHODS(repl_type, set_type) \
418 template const char *repl_type::ini_set; \
419 template bool repl_type::Scanner::AddFile(const char *filename, size_t pathlength, const char *tar_filename); \
420 template bool repl_type::HasSet(const struct ContentInfo *ci, bool md5sum); \
421 template bool repl_type::SetSet(const char *name); \
422 template void repl_type::GetSetsList (stringb *buf); \
423 template int repl_type::GetNumSets(); \
424 template int repl_type::GetIndexOfUsedSet(); \
425 template const set_type *repl_type::GetSet(int index); \
426 template const set_type *repl_type::GetUsedSet(); \
427 template bool repl_type::DetermineBestSet(); \
428 template set_type *repl_type::GetAvailableSets(); \
429 template const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const set_type *s);