00001
00002
00003
00004
00005
00006
00007
00008
00009
00015 #include "base_media_base.h"
00016 #include "debug.h"
00017 #include "ini_type.h"
00018 #include "string_func.h"
00019
00020 template <class Tbase_set> const char *BaseMedia<Tbase_set>::ini_set;
00021 template <class Tbase_set> const Tbase_set *BaseMedia<Tbase_set>::used_set;
00022 template <class Tbase_set> Tbase_set *BaseMedia<Tbase_set>::available_sets;
00023 template <class Tbase_set> Tbase_set *BaseMedia<Tbase_set>::duplicate_sets;
00024
00029 #define fetch_metadata(name) \
00030 item = metadata->GetItem(name, false); \
00031 if (item == NULL || StrEmpty(item->value)) { \
00032 DEBUG(grf, 0, "Base " SET_TYPE "set detail loading: %s field missing.", name); \
00033 DEBUG(grf, 0, " Is %s readable for the user running OpenTTD?", full_filename); \
00034 return false; \
00035 }
00036
00045 template <class T, size_t Tnum_files, bool Tsearch_in_tars>
00046 bool BaseSet<T, Tnum_files, Tsearch_in_tars>::FillSetDetails(IniFile *ini, const char *path, const char *full_filename, bool allow_empty_filename)
00047 {
00048 memset(this, 0, sizeof(*this));
00049
00050 IniGroup *metadata = ini->GetGroup("metadata");
00051 IniItem *item;
00052
00053 fetch_metadata("name");
00054 this->name = strdup(item->value);
00055
00056 fetch_metadata("description");
00057 this->description[strdup("")] = strdup(item->value);
00058
00059
00060 for (const IniItem *item = metadata->item; item != NULL; item = item->next) {
00061 if (strncmp("description.", item->name, 12) != 0) continue;
00062
00063 this->description[strdup(item->name + 12)] = strdup(item->value);
00064 }
00065
00066 fetch_metadata("shortname");
00067 for (uint i = 0; item->value[i] != '\0' && i < 4; i++) {
00068 this->shortname |= ((uint8)item->value[i]) << (i * 8);
00069 }
00070
00071 fetch_metadata("version");
00072 this->version = atoi(item->value);
00073
00074 item = metadata->GetItem("fallback", false);
00075 this->fallback = (item != NULL && strcmp(item->value, "0") != 0 && strcmp(item->value, "false") != 0);
00076
00077
00078 IniGroup *files = ini->GetGroup("files");
00079 IniGroup *md5s = ini->GetGroup("md5s");
00080 IniGroup *origin = ini->GetGroup("origin");
00081 for (uint i = 0; i < Tnum_files; i++) {
00082 MD5File *file = &this->files[i];
00083
00084 item = files->GetItem(BaseSet<T, Tnum_files, Tsearch_in_tars>::file_names[i], false);
00085 if (item == NULL || (item->value == NULL && !allow_empty_filename)) {
00086 DEBUG(grf, 0, "No " SET_TYPE " file for: %s (in %s)", BaseSet<T, Tnum_files, Tsearch_in_tars>::file_names[i], full_filename);
00087 return false;
00088 }
00089
00090 const char *filename = item->value;
00091 if (filename == NULL) {
00092 file->filename = NULL;
00093
00094 this->valid_files++;
00095 this->found_files++;
00096 continue;
00097 }
00098
00099 file->filename = str_fmt("%s%s", path, filename);
00100
00101
00102 item = md5s->GetItem(filename, false);
00103 if (item == NULL) {
00104 DEBUG(grf, 0, "No MD5 checksum specified for: %s (in %s)", filename, full_filename);
00105 return false;
00106 }
00107 char *c = item->value;
00108 for (uint i = 0; i < sizeof(file->hash) * 2; i++, c++) {
00109 uint j;
00110 if ('0' <= *c && *c <= '9') {
00111 j = *c - '0';
00112 } else if ('a' <= *c && *c <= 'f') {
00113 j = *c - 'a' + 10;
00114 } else if ('A' <= *c && *c <= 'F') {
00115 j = *c - 'A' + 10;
00116 } else {
00117 DEBUG(grf, 0, "Malformed MD5 checksum specified for: %s (in %s)", filename, full_filename);
00118 return false;
00119 }
00120 if (i % 2 == 0) {
00121 file->hash[i / 2] = j << 4;
00122 } else {
00123 file->hash[i / 2] |= j;
00124 }
00125 }
00126
00127
00128 item = filename == NULL ? NULL : origin->GetItem(filename, false);
00129 if (item == NULL) item = origin->GetItem("default", false);
00130 if (item == NULL) {
00131 DEBUG(grf, 1, "No origin warning message specified for: %s", filename);
00132 file->missing_warning = strdup("");
00133 } else {
00134 file->missing_warning = strdup(item->value);
00135 }
00136
00137 switch (T::CheckMD5(file, BASESET_DIR)) {
00138 case MD5File::CR_MATCH:
00139 this->valid_files++;
00140 this->found_files++;
00141 break;
00142
00143 case MD5File::CR_MISMATCH:
00144 DEBUG(grf, 1, "MD5 checksum mismatch for: %s (in %s)", filename, full_filename);
00145 this->found_files++;
00146 break;
00147
00148 case MD5File::CR_NO_FILE:
00149 DEBUG(grf, 1, "The file %s specified in %s is missing", filename, full_filename);
00150 break;
00151 }
00152 }
00153
00154 return true;
00155 }
00156
00157 template <class Tbase_set>
00158 bool BaseMedia<Tbase_set>::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
00159 {
00160 bool ret = false;
00161 DEBUG(grf, 1, "Checking %s for base " SET_TYPE " set", filename);
00162
00163 Tbase_set *set = new Tbase_set();
00164 IniFile *ini = new IniFile();
00165 ini->LoadFromDisk(filename, BASESET_DIR);
00166
00167 char *path = strdup(filename + basepath_length);
00168 char *psep = strrchr(path, PATHSEPCHAR);
00169 if (psep != NULL) {
00170 psep[1] = '\0';
00171 } else {
00172 *path = '\0';
00173 }
00174
00175 if (set->FillSetDetails(ini, path, filename)) {
00176 Tbase_set *duplicate = NULL;
00177 for (Tbase_set *c = BaseMedia<Tbase_set>::available_sets; c != NULL; c = c->next) {
00178 if (strcmp(c->name, set->name) == 0 || c->shortname == set->shortname) {
00179 duplicate = c;
00180 break;
00181 }
00182 }
00183 if (duplicate != NULL) {
00184
00185 if ((duplicate->valid_files == set->valid_files && duplicate->version >= set->version) ||
00186 duplicate->valid_files > set->valid_files) {
00187 DEBUG(grf, 1, "Not adding %s (%i) as base " SET_TYPE " set (duplicate, %s)", set->name, set->version,
00188 duplicate->valid_files > set->valid_files ? "less valid files" : "lower version");
00189 set->next = BaseMedia<Tbase_set>::duplicate_sets;
00190 BaseMedia<Tbase_set>::duplicate_sets = set;
00191 } else {
00192 Tbase_set **prev = &BaseMedia<Tbase_set>::available_sets;
00193 while (*prev != duplicate) prev = &(*prev)->next;
00194
00195 *prev = set;
00196 set->next = duplicate->next;
00197
00198
00199
00200
00201 if (BaseMedia<Tbase_set>::used_set == duplicate) BaseMedia<Tbase_set>::used_set = set;
00202
00203 DEBUG(grf, 1, "Removing %s (%i) as base " SET_TYPE " set (duplicate, %s)", duplicate->name, duplicate->version,
00204 duplicate->valid_files < set->valid_files ? "less valid files" : "lower version");
00205 duplicate->next = BaseMedia<Tbase_set>::duplicate_sets;
00206 BaseMedia<Tbase_set>::duplicate_sets = duplicate;
00207 ret = true;
00208 }
00209 } else {
00210 Tbase_set **last = &BaseMedia<Tbase_set>::available_sets;
00211 while (*last != NULL) last = &(*last)->next;
00212
00213 *last = set;
00214 ret = true;
00215 }
00216 if (ret) {
00217 DEBUG(grf, 1, "Adding %s (%i) as base " SET_TYPE " set", set->name, set->version);
00218 }
00219 } else {
00220 delete set;
00221 }
00222 free(path);
00223
00224 delete ini;
00225 return ret;
00226 }
00227
00233 template <class Tbase_set>
00234 bool BaseMedia<Tbase_set>::SetSet(const char *name)
00235 {
00236 extern void CheckExternalFiles();
00237
00238 if (StrEmpty(name)) {
00239 if (!BaseMedia<Tbase_set>::DetermineBestSet()) return false;
00240 CheckExternalFiles();
00241 return true;
00242 }
00243
00244 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
00245 if (strcmp(name, s->name) == 0) {
00246 BaseMedia<Tbase_set>::used_set = s;
00247 CheckExternalFiles();
00248 return true;
00249 }
00250 }
00251 return false;
00252 }
00253
00260 template <class Tbase_set>
00261 char *BaseMedia<Tbase_set>::GetSetsList(char *p, const char *last)
00262 {
00263 p += seprintf(p, last, "List of " SET_TYPE " sets:\n");
00264 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
00265 p += seprintf(p, last, "%18s: %s", s->name, s->GetDescription());
00266 int invalid = s->GetNumInvalid();
00267 if (invalid != 0) {
00268 int missing = s->GetNumMissing();
00269 if (missing == 0) {
00270 p += seprintf(p, last, " (%i corrupt file%s)\n", invalid, invalid == 1 ? "" : "s");
00271 } else {
00272 p += seprintf(p, last, " (unusable: %i missing file%s)\n", missing, missing == 1 ? "" : "s");
00273 }
00274 } else {
00275 p += seprintf(p, last, "\n");
00276 }
00277 }
00278 p += seprintf(p, last, "\n");
00279
00280 return p;
00281 }
00282
00283 #if defined(ENABLE_NETWORK)
00284 #include "network/network_content.h"
00285
00286 template <class Tbase_set> const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const Tbase_set *s)
00287 {
00288 for (; s != NULL; s = s->next) {
00289 if (s->GetNumMissing() != 0) continue;
00290
00291 if (s->shortname != ci->unique_id) continue;
00292 if (!md5sum) return s->files[0].filename;
00293
00294 byte md5[16];
00295 memset(md5, 0, sizeof(md5));
00296 for (uint i = 0; i < Tbase_set::NUM_FILES; i++) {
00297 for (uint j = 0; j < sizeof(md5); j++) {
00298 md5[j] ^= s->files[i].hash[j];
00299 }
00300 }
00301 if (memcmp(md5, ci->md5sum, sizeof(md5)) == 0) return s->files[0].filename;
00302 }
00303 return NULL;
00304 }
00305
00306 template <class Tbase_set>
00307 bool BaseMedia<Tbase_set>::HasSet(const ContentInfo *ci, bool md5sum)
00308 {
00309 return (TryGetBaseSetFile(ci, md5sum, BaseMedia<Tbase_set>::available_sets) != NULL) ||
00310 (TryGetBaseSetFile(ci, md5sum, BaseMedia<Tbase_set>::duplicate_sets) != NULL);
00311 }
00312
00313 #else
00314
00315 template <class Tbase_set>
00316 const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const Tbase_set *s)
00317 {
00318 return NULL;
00319 }
00320
00321 template <class Tbase_set>
00322 bool BaseMedia<Tbase_set>::HasSet(const ContentInfo *ci, bool md5sum)
00323 {
00324 return false;
00325 }
00326
00327 #endif
00328
00333 template <class Tbase_set>
00334 int BaseMedia<Tbase_set>::GetNumSets()
00335 {
00336 int n = 0;
00337 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
00338 if (s != BaseMedia<Tbase_set>::used_set && s->GetNumMissing() != 0) continue;
00339 n++;
00340 }
00341 return n;
00342 }
00343
00348 template <class Tbase_set>
00349 int BaseMedia<Tbase_set>::GetIndexOfUsedSet()
00350 {
00351 int n = 0;
00352 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
00353 if (s == BaseMedia<Tbase_set>::used_set) return n;
00354 if (s->GetNumMissing() != 0) continue;
00355 n++;
00356 }
00357 return -1;
00358 }
00359
00364 template <class Tbase_set>
00365 const Tbase_set *BaseMedia<Tbase_set>::GetSet(int index)
00366 {
00367 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
00368 if (s != BaseMedia<Tbase_set>::used_set && s->GetNumMissing() != 0) continue;
00369 if (index == 0) return s;
00370 index--;
00371 }
00372 error("Base" SET_TYPE "::GetSet(): index %d out of range", index);
00373 }
00374
00379 template <class Tbase_set>
00380 const Tbase_set *BaseMedia<Tbase_set>::GetUsedSet()
00381 {
00382 return BaseMedia<Tbase_set>::used_set;
00383 }
00384
00389 template <class Tbase_set>
00390 Tbase_set *BaseMedia<Tbase_set>::GetAvailableSets()
00391 {
00392 return BaseMedia<Tbase_set>::available_sets;
00393 }
00394
00400 #define INSTANTIATE_BASE_MEDIA_METHODS(repl_type, set_type) \
00401 template const char *repl_type::ini_set; \
00402 template const char *repl_type::GetExtension(); \
00403 template bool repl_type::AddFile(const char *filename, size_t pathlength, const char *tar_filename); \
00404 template bool repl_type::HasSet(const struct ContentInfo *ci, bool md5sum); \
00405 template bool repl_type::SetSet(const char *name); \
00406 template char *repl_type::GetSetsList(char *p, const char *last); \
00407 template int repl_type::GetNumSets(); \
00408 template int repl_type::GetIndexOfUsedSet(); \
00409 template const set_type *repl_type::GetSet(int index); \
00410 template const set_type *repl_type::GetUsedSet(); \
00411 template bool repl_type::DetermineBestSet(); \
00412 template set_type *repl_type::GetAvailableSets(); \
00413 template const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const set_type *s);
00414