OpenTTD

- Main Page
- Related Pages
- Modules
- Namespaces
- Data Structures
- Files

- File List
- Globals

base_media_func.h
Go to the documentation of this file.
1 /* $Id: base_media_func.h 26637 2014-06-09 17:43:59Z rubidium $ */
2
3 /*
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/>.
8 */
9
15 #include "base_media_base.h"
16 #include "debug.h"
17 #include "ini_type.h"
18 #include "string_func.h"
19
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;
24
29 #define fetch_metadata(name) \
30 item = metadata->GetItem(name, false); \
31 if (item == NULL || 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); \
34 return false; \
35 }
36
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)
47 {
48 memset(this, 0, sizeof(*this));
49
50 IniGroup *metadata = ini->GetGroup("metadata");
51 IniItem *item;
52
53 fetch_metadata("name");
54 this->name = stredup(item->value);
55
56 fetch_metadata("description");
57 this->description[stredup("")] = stredup(item->value);
58
59 /* Add the translations of the descriptions too. */
60 for (const IniItem *item = metadata->item; item != NULL; item = item->next) {
61 if (strncmp("description.", item->name, 12) != 0) continue;
62
63 this->description[stredup(item->name + 12)] = stredup(item->value);
64 }
65
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);
69 }
70
71 fetch_metadata("version");
72 this->version = atoi(item->value);
73
74 item = metadata->GetItem("fallback", false);
75 this->fallback = (item != NULL && strcmp(item->value, "0") != 0 && strcmp(item->value, "false") != 0);
76
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 == NULL || (item->value == NULL && !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);
87 return false;
88 }
89
90 const char *filename = item->value;
91 if (filename == NULL) {
92 file->filename = NULL;
93 /* If we list no file, that file must be valid */
94 this->valid_files++;
95 this->found_files++;
96 continue;
97 }
98
99 file->filename = str_fmt("%s%s", path, filename);
100
101 /* Then find the MD5 checksum */
102 item = md5s->GetItem(filename, false);
103 if (item == NULL || item->value == NULL) {
104 DEBUG(grf, 0, "No MD5 checksum specified for: %s (in %s)", filename, full_filename);
105 return false;
106 }
107 char *c = item->value;
108 for (uint i = 0; i < sizeof(file->hash) * 2; i++, c++) {
109 uint j;
110 if ('0' <= *c && *c <= '9') {
111 j = *c - '0';
112 } else if ('a' <= *c && *c <= 'f') {
113 j = *c - 'a' + 10;
114 } else if ('A' <= *c && *c <= 'F') {
115 j = *c - 'A' + 10;
116 } else {
117 DEBUG(grf, 0, "Malformed MD5 checksum specified for: %s (in %s)", filename, full_filename);
118 return false;
119 }
120 if (i % 2 == 0) {
121 file->hash[i / 2] = j << 4;
122 } else {
123 file->hash[i / 2] |= j;
124 }
125 }
126
127 /* Then find the warning message when the file's missing */
128 item = origin->GetItem(filename, false);
129 if (item == NULL) item = origin->GetItem("default", false);
130 if (item == NULL) {
131 DEBUG(grf, 1, "No origin warning message specified for: %s", filename);
132 file->missing_warning = stredup("");
133 } else {
134 file->missing_warning = stredup(item->value);
135 }
136
137 switch (T::CheckMD5(file, BASESET_DIR)) {
138 case MD5File::CR_MATCH:
139 this->valid_files++;
140 this->found_files++;
141 break;
142
143 case MD5File::CR_MISMATCH:
144 DEBUG(grf, 1, "MD5 checksum mismatch for: %s (in %s)", filename, full_filename);
145 this->found_files++;
146 break;
147
148 case MD5File::CR_NO_FILE:
149 DEBUG(grf, 1, "The file %s specified in %s is missing", filename, full_filename);
150 break;
151 }
152 }
153
154 return true;
155 }
156
157 template <class Tbase_set>
158 bool BaseMedia<Tbase_set>::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
159 {
160 bool ret = false;
161 DEBUG(grf, 1, "Checking %s for base " SET_TYPE " set", filename);
162
163 Tbase_set *set = new Tbase_set();
164 IniFile *ini = new IniFile();
165 ini->LoadFromDisk(filename, BASESET_DIR);
166
167 char *path = stredup(filename + basepath_length);
168 char *psep = strrchr(path, PATHSEPCHAR);
169 if (psep != NULL) {
170 psep[1] = '\0';
171 } else {
172 *path = '\0';
173 }
174
175 if (set->FillSetDetails(ini, path, filename)) {
176 Tbase_set *duplicate = NULL;
177 for (Tbase_set *c = BaseMedia<Tbase_set>::available_sets; c != NULL; c = c->next) {
178 if (strcmp(c->name, set->name) == 0 || c->shortname == set->shortname) {
179 duplicate = c;
180 break;
181 }
182 }
183 if (duplicate != NULL) {
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;
191 } else {
192 Tbase_set **prev = &BaseMedia<Tbase_set>::available_sets;
193 while (*prev != duplicate) prev = &(*prev)->next;
194
195 *prev = set;
196 set->next = duplicate->next;
197
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;
202
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;
207 ret = true;
208 }
209 } else {
210 Tbase_set **last = &BaseMedia<Tbase_set>::available_sets;
211 while (*last != NULL) last = &(*last)->next;
212
213 *last = set;
214 ret = true;
215 }
216 if (ret) {
217 DEBUG(grf, 1, "Adding %s (%i) as base " SET_TYPE " set", set->name, set->version);
218 }
219 } else {
220 delete set;
221 }
222 free(path);
223
224 delete ini;
225 return ret;
226 }
227
233 template <class Tbase_set>
234 /* static */ bool BaseMedia<Tbase_set>::SetSet(const char *name)
235 {
236 extern void CheckExternalFiles();
237
238 if (StrEmpty(name)) {
239 if (!BaseMedia<Tbase_set>::DetermineBestSet()) return false;
240 CheckExternalFiles();
241 return true;
242 }
243
244 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
245 if (strcmp(name, s->name) == 0) {
246 BaseMedia<Tbase_set>::used_set = s;
247 CheckExternalFiles();
248 return true;
249 }
250 }
251 return false;
252 }
253
260 template <class Tbase_set>
261 /* static */ char *BaseMedia<Tbase_set>::GetSetsList(char *p, const char *last)
262 {
263 p += seprintf(p, last, "List of " SET_TYPE " sets:\n");
264 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
265 p += seprintf(p, last, "%18s: %s", s->name, s->GetDescription());
266 int invalid = s->GetNumInvalid();
267 if (invalid != 0) {
268 int missing = s->GetNumMissing();
269 if (missing == 0) {
270 p += seprintf(p, last, " (%i corrupt file%s)\n", invalid, invalid == 1 ? "" : "s");
271 } else {
272 p += seprintf(p, last, " (unusable: %i missing file%s)\n", missing, missing == 1 ? "" : "s");
273 }
274 } else {
275 p += seprintf(p, last, "\n");
276 }
277 }
278 p += seprintf(p, last, "\n");
279
280 return p;
281 }
282
283 #if defined(ENABLE_NETWORK)
284 #include "network/network_content.h"
285
286 template <class Tbase_set> const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const Tbase_set *s)
287 {
288 for (; s != NULL; s = s->next) {
289 if (s->GetNumMissing() != 0) continue;
290
291 if (s->shortname != ci->unique_id) continue;
292 if (!md5sum) return s->files[0].filename;
293
294 byte md5[16];
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];
299 }
300 }
301 if (memcmp(md5, ci->md5sum, sizeof(md5)) == 0) return s->files[0].filename;
302 }
303 return NULL;
304 }
305
306 template <class Tbase_set>
307 /* static */ bool BaseMedia<Tbase_set>::HasSet(const ContentInfo *ci, bool md5sum)
308 {
309 return (TryGetBaseSetFile(ci, md5sum, BaseMedia<Tbase_set>::available_sets) != NULL) ||
310 (TryGetBaseSetFile(ci, md5sum, BaseMedia<Tbase_set>::duplicate_sets) != NULL);
311 }
312
313 #else
314
315 template <class Tbase_set>
316 const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const Tbase_set *s)
317 {
318 return NULL;
319 }
320
321 template <class Tbase_set>
322 /* static */ bool BaseMedia<Tbase_set>::HasSet(const ContentInfo *ci, bool md5sum)
323 {
324 return false;
325 }
326
327 #endif /* ENABLE_NETWORK */
328
333 template <class Tbase_set>
334 /* static */ int BaseMedia<Tbase_set>::GetNumSets()
335 {
336 int n = 0;
337 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
338 if (s != BaseMedia<Tbase_set>::used_set && s->GetNumMissing() != 0) continue;
339 n++;
340 }
341 return n;
342 }
343
348 template <class Tbase_set>
349 /* static */ int BaseMedia<Tbase_set>::GetIndexOfUsedSet()
350 {
351 int n = 0;
352 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
353 if (s == BaseMedia<Tbase_set>::used_set) return n;
354 if (s->GetNumMissing() != 0) continue;
355 n++;
356 }
357 return -1;
358 }
359
364 template <class Tbase_set>
365 /* static */ const Tbase_set *BaseMedia<Tbase_set>::GetSet(int index)
366 {
367 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
368 if (s != BaseMedia<Tbase_set>::used_set && s->GetNumMissing() != 0) continue;
369 if (index == 0) return s;
370 index--;
371 }
372 error("Base" SET_TYPE "::GetSet(): index %d out of range", index);
373 }
374
379 template <class Tbase_set>
380 /* static */ const Tbase_set *BaseMedia<Tbase_set>::GetUsedSet()
381 {
382 return BaseMedia<Tbase_set>::used_set;
383 }
384
389 template <class Tbase_set>
390 /* static */ Tbase_set *BaseMedia<Tbase_set>::GetAvailableSets()
391 {
392 return BaseMedia<Tbase_set>::available_sets;
393 }
394
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);
414