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).
12 * @note You should _never_ include this file due to the SET_TYPE define.
15 #include "base_media_base.h"
18 #include "string_func.h"
20 template <class Tbase_set
> /* static */ const char *BaseMedia
<Tbase_set
>::ini_set
;
21 template <class Tbase_set
> /* static */ const Tbase_set
*BaseMedia
<Tbase_set
>::used_set
;
22 template <class Tbase_set
> /* static */ Tbase_set
*BaseMedia
<Tbase_set
>::available_sets
;
23 template <class Tbase_set
> /* static */ Tbase_set
*BaseMedia
<Tbase_set
>::duplicate_sets
;
26 * Try to read a single piece of metadata and return false if it doesn't exist.
27 * @param name the name of the item to fetch.
29 #define fetch_metadata(name) \
30 item = metadata->GetItem(name, false); \
31 if (item == nullptr || StrEmpty(item->value)) { \
32 DEBUG(grf, 0, "Base " SET_TYPE "set detail loading: %s field missing.", name); \
33 DEBUG(grf, 0, " Is %s readable for the user running OpenTTD?", full_filename); \
38 * Read the set information from a loaded ini.
39 * @param ini the ini to read from
40 * @param path the path to this ini file (for filenames)
41 * @param full_filename the full filename of the loaded file (for error reporting purposes)
42 * @param allow_empty_filename empty filenames are valid
43 * @return true if loading was successful.
45 template <class T
, size_t Tnum_files
, bool Tsearch_in_tars
>
46 bool BaseSet
<T
, Tnum_files
, Tsearch_in_tars
>::FillSetDetails(IniFile
*ini
, const char *path
, const char *full_filename
, bool allow_empty_filename
)
48 memset(this, 0, sizeof(*this));
50 IniGroup
*metadata
= ini
->GetGroup("metadata");
53 fetch_metadata("name");
54 this->name
= stredup(item
->value
);
56 fetch_metadata("description");
57 this->description
[stredup("")] = stredup(item
->value
);
59 /* Add the translations of the descriptions too. */
60 for (const IniItem
*item
= metadata
->item
; item
!= nullptr; item
= item
->next
) {
61 if (strncmp("description.", item
->name
, 12) != 0) continue;
63 this->description
[stredup(item
->name
+ 12)] = stredup(item
->value
);
66 fetch_metadata("shortname");
67 for (uint i
= 0; item
->value
[i
] != '\0' && i
< 4; i
++) {
68 this->shortname
|= ((uint8
)item
->value
[i
]) << (i
* 8);
71 fetch_metadata("version");
72 this->version
= atoi(item
->value
);
74 item
= metadata
->GetItem("fallback", false);
75 this->fallback
= (item
!= nullptr && strcmp(item
->value
, "0") != 0 && strcmp(item
->value
, "false") != 0);
77 /* For each of the file types we want to find the file, MD5 checksums and warning messages. */
78 IniGroup
*files
= ini
->GetGroup("files");
79 IniGroup
*md5s
= ini
->GetGroup("md5s");
80 IniGroup
*origin
= ini
->GetGroup("origin");
81 for (uint i
= 0; i
< Tnum_files
; i
++) {
82 MD5File
*file
= &this->files
[i
];
83 /* Find the filename first. */
84 item
= files
->GetItem(BaseSet
<T
, Tnum_files
, Tsearch_in_tars
>::file_names
[i
], false);
85 if (item
== nullptr || (item
->value
== nullptr && !allow_empty_filename
)) {
86 DEBUG(grf
, 0, "No " SET_TYPE
" file for: %s (in %s)", BaseSet
<T
, Tnum_files
, Tsearch_in_tars
>::file_names
[i
], full_filename
);
90 const char *filename
= item
->value
;
91 if (filename
== nullptr) {
92 file
->filename
= nullptr;
93 /* If we list no file, that file must be valid */
99 file
->filename
= str_fmt("%s%s", path
, filename
);
101 /* Then find the MD5 checksum */
102 item
= md5s
->GetItem(filename
, false);
103 if (item
== nullptr || item
->value
== nullptr) {
104 DEBUG(grf
, 0, "No MD5 checksum specified for: %s (in %s)", filename
, full_filename
);
107 char *c
= item
->value
;
108 for (uint i
= 0; i
< sizeof(file
->hash
) * 2; i
++, c
++) {
110 if ('0' <= *c
&& *c
<= '9') {
112 } else if ('a' <= *c
&& *c
<= 'f') {
114 } else if ('A' <= *c
&& *c
<= 'F') {
117 DEBUG(grf
, 0, "Malformed MD5 checksum specified for: %s (in %s)", filename
, full_filename
);
121 file
->hash
[i
/ 2] = j
<< 4;
123 file
->hash
[i
/ 2] |= j
;
127 /* Then find the warning message when the file's missing */
128 item
= origin
->GetItem(filename
, false);
129 if (item
== nullptr) item
= origin
->GetItem("default", false);
130 if (item
== nullptr) {
131 DEBUG(grf
, 1, "No origin warning message specified for: %s", filename
);
132 file
->missing_warning
= stredup("");
134 file
->missing_warning
= stredup(item
->value
);
137 switch (T::CheckMD5(file
, BASESET_DIR
)) {
138 case MD5File::CR_MATCH
:
143 case MD5File::CR_MISMATCH
:
144 DEBUG(grf
, 1, "MD5 checksum mismatch for: %s (in %s)", filename
, full_filename
);
148 case MD5File::CR_NO_FILE
:
149 DEBUG(grf
, 1, "The file %s specified in %s is missing", filename
, full_filename
);
157 template <class Tbase_set
>
158 bool BaseMedia
<Tbase_set
>::AddFile(const char *filename
, size_t basepath_length
, const char *tar_filename
)
161 DEBUG(grf
, 1, "Checking %s for base " SET_TYPE
" set", filename
);
163 Tbase_set
*set
= new Tbase_set();
164 IniFile
*ini
= new IniFile();
165 ini
->LoadFromDisk(filename
, BASESET_DIR
);
167 char *path
= stredup(filename
+ basepath_length
);
168 char *psep
= strrchr(path
, PATHSEPCHAR
);
169 if (psep
!= nullptr) {
175 if (set
->FillSetDetails(ini
, path
, filename
)) {
176 Tbase_set
*duplicate
= nullptr;
177 for (Tbase_set
*c
= BaseMedia
<Tbase_set
>::available_sets
; c
!= nullptr; c
= c
->next
) {
178 if (strcmp(c
->name
, set
->name
) == 0 || c
->shortname
== set
->shortname
) {
183 if (duplicate
!= nullptr) {
184 /* The more complete set takes precedence over the version number. */
185 if ((duplicate
->valid_files
== set
->valid_files
&& duplicate
->version
>= set
->version
) ||
186 duplicate
->valid_files
> set
->valid_files
) {
187 DEBUG(grf
, 1, "Not adding %s (%i) as base " SET_TYPE
" set (duplicate, %s)", set
->name
, set
->version
,
188 duplicate
->valid_files
> set
->valid_files
? "less valid files" : "lower version");
189 set
->next
= BaseMedia
<Tbase_set
>::duplicate_sets
;
190 BaseMedia
<Tbase_set
>::duplicate_sets
= set
;
192 Tbase_set
**prev
= &BaseMedia
<Tbase_set
>::available_sets
;
193 while (*prev
!= duplicate
) prev
= &(*prev
)->next
;
196 set
->next
= duplicate
->next
;
198 /* If the duplicate set is currently used (due to rescanning this can happen)
199 * update the currently used set to the new one. This will 'lie' about the
200 * version number until a new game is started which isn't a big problem */
201 if (BaseMedia
<Tbase_set
>::used_set
== duplicate
) BaseMedia
<Tbase_set
>::used_set
= set
;
203 DEBUG(grf
, 1, "Removing %s (%i) as base " SET_TYPE
" set (duplicate, %s)", duplicate
->name
, duplicate
->version
,
204 duplicate
->valid_files
< set
->valid_files
? "less valid files" : "lower version");
205 duplicate
->next
= BaseMedia
<Tbase_set
>::duplicate_sets
;
206 BaseMedia
<Tbase_set
>::duplicate_sets
= duplicate
;
210 Tbase_set
**last
= &BaseMedia
<Tbase_set
>::available_sets
;
211 while (*last
!= nullptr) last
= &(*last
)->next
;
217 DEBUG(grf
, 1, "Adding %s (%i) as base " SET_TYPE
" set", set
->name
, set
->version
);
229 * Set the set to be used.
230 * @param name of the set to use
231 * @return true if it could be loaded
233 template <class Tbase_set
>
234 /* static */ bool BaseMedia
<Tbase_set
>::SetSet(const char *name
)
236 extern void CheckExternalFiles();
238 if (StrEmpty(name
)) {
239 if (!BaseMedia
<Tbase_set
>::DetermineBestSet()) return false;
240 CheckExternalFiles();
244 for (const Tbase_set
*s
= BaseMedia
<Tbase_set
>::available_sets
; s
!= nullptr; s
= s
->next
) {
245 if (strcmp(name
, s
->name
) == 0) {
246 BaseMedia
<Tbase_set
>::used_set
= s
;
247 CheckExternalFiles();
255 * Returns a list with the sets.
256 * @param p where to print to
257 * @param last the last character to print to
258 * @return the last printed character
260 template <class Tbase_set
>
261 /* static */ char *BaseMedia
<Tbase_set
>::GetSetsList(char *p
, const char *last
)
263 p
+= seprintf(p
, last
, "List of " SET_TYPE
" sets:\n");
264 for (const Tbase_set
*s
= BaseMedia
<Tbase_set
>::available_sets
; s
!= nullptr; s
= s
->next
) {
265 p
+= seprintf(p
, last
, "%18s: %s", s
->name
, s
->GetDescription());
266 int invalid
= s
->GetNumInvalid();
268 int missing
= s
->GetNumMissing();
270 p
+= seprintf(p
, last
, " (%i corrupt file%s)\n", invalid
, invalid
== 1 ? "" : "s");
272 p
+= seprintf(p
, last
, " (unusable: %i missing file%s)\n", missing
, missing
== 1 ? "" : "s");
275 p
+= seprintf(p
, last
, "\n");
278 p
+= seprintf(p
, last
, "\n");
283 #if defined(ENABLE_NETWORK)
284 #include "network/network_content.h"
286 template <class Tbase_set
> const char *TryGetBaseSetFile(const ContentInfo
*ci
, bool md5sum
, const Tbase_set
*s
)
288 for (; s
!= nullptr; s
= s
->next
) {
289 if (s
->GetNumMissing() != 0) continue;
291 if (s
->shortname
!= ci
->unique_id
) continue;
292 if (!md5sum
) return s
->files
[0].filename
;
295 memset(md5
, 0, sizeof(md5
));
296 for (uint i
= 0; i
< Tbase_set::NUM_FILES
; i
++) {
297 for (uint j
= 0; j
< sizeof(md5
); j
++) {
298 md5
[j
] ^= s
->files
[i
].hash
[j
];
301 if (memcmp(md5
, ci
->md5sum
, sizeof(md5
)) == 0) return s
->files
[0].filename
;
306 template <class Tbase_set
>
307 /* static */ bool BaseMedia
<Tbase_set
>::HasSet(const ContentInfo
*ci
, bool md5sum
)
309 return (TryGetBaseSetFile(ci
, md5sum
, BaseMedia
<Tbase_set
>::available_sets
) != nullptr) ||
310 (TryGetBaseSetFile(ci
, md5sum
, BaseMedia
<Tbase_set
>::duplicate_sets
) != nullptr);
315 template <class Tbase_set
>
316 const char *TryGetBaseSetFile(const ContentInfo
*ci
, bool md5sum
, const Tbase_set
*s
)
321 template <class Tbase_set
>
322 /* static */ bool BaseMedia
<Tbase_set
>::HasSet(const ContentInfo
*ci
, bool md5sum
)
327 #endif /* ENABLE_NETWORK */
330 * Count the number of available graphics sets.
331 * @return the number of sets
333 template <class Tbase_set
>
334 /* static */ int BaseMedia
<Tbase_set
>::GetNumSets()
337 for (const Tbase_set
*s
= BaseMedia
<Tbase_set
>::available_sets
; s
!= nullptr; s
= s
->next
) {
338 if (s
!= BaseMedia
<Tbase_set
>::used_set
&& s
->GetNumMissing() != 0) continue;
345 * Get the index of the currently active graphics set
346 * @return the current set's index
348 template <class Tbase_set
>
349 /* static */ int BaseMedia
<Tbase_set
>::GetIndexOfUsedSet()
352 for (const Tbase_set
*s
= BaseMedia
<Tbase_set
>::available_sets
; s
!= nullptr; s
= s
->next
) {
353 if (s
== BaseMedia
<Tbase_set
>::used_set
) return n
;
354 if (s
->GetNumMissing() != 0) continue;
361 * Get the name of the graphics set at the specified index
362 * @return the name of the set
364 template <class Tbase_set
>
365 /* static */ const Tbase_set
*BaseMedia
<Tbase_set
>::GetSet(int index
)
367 for (const Tbase_set
*s
= BaseMedia
<Tbase_set
>::available_sets
; s
!= nullptr; s
= s
->next
) {
368 if (s
!= BaseMedia
<Tbase_set
>::used_set
&& s
->GetNumMissing() != 0) continue;
369 if (index
== 0) return s
;
372 error("Base" SET_TYPE
"::GetSet(): index %d out of range", index
);
376 * Return the used set.
377 * @return the used set.
379 template <class Tbase_set
>
380 /* static */ const Tbase_set
*BaseMedia
<Tbase_set
>::GetUsedSet()
382 return BaseMedia
<Tbase_set
>::used_set
;
386 * Return the available sets.
387 * @return The available sets.
389 template <class Tbase_set
>
390 /* static */ Tbase_set
*BaseMedia
<Tbase_set
>::GetAvailableSets()
392 return BaseMedia
<Tbase_set
>::available_sets
;
396 * Force instantiation of methods so we don't get linker errors.
397 * @param repl_type the type of the BaseMedia to instantiate
398 * @param set_type the type of the BaseSet to instantiate
400 #define INSTANTIATE_BASE_MEDIA_METHODS(repl_type, set_type) \
401 template const char *repl_type::ini_set; \
402 template const char *repl_type::GetExtension(); \
403 template bool repl_type::AddFile(const char *filename, size_t pathlength, const char *tar_filename); \
404 template bool repl_type::HasSet(const struct ContentInfo *ci, bool md5sum); \
405 template bool repl_type::SetSet(const char *name); \
406 template char *repl_type::GetSetsList(char *p, const char *last); \
407 template int repl_type::GetNumSets(); \
408 template int repl_type::GetIndexOfUsedSet(); \
409 template const set_type *repl_type::GetSet(int index); \
410 template const set_type *repl_type::GetUsedSet(); \
411 template bool repl_type::DetermineBestSet(); \
412 template set_type *repl_type::GetAvailableSets(); \
413 template const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const set_type *s);