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
00141 case MD5File::CR_MISMATCH:
00142 this->found_files++;
00143 break;
00144
00145 case MD5File::CR_NO_FILE:
00146 break;
00147 }
00148 }
00149
00150 return true;
00151 }
00152
00153 template <class Tbase_set>
00154 bool BaseMedia<Tbase_set>::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
00155 {
00156 bool ret = false;
00157 DEBUG(grf, 1, "Checking %s for base " SET_TYPE " set", filename);
00158
00159 Tbase_set *set = new Tbase_set();
00160 IniFile *ini = new IniFile();
00161 ini->LoadFromDisk(filename, BASESET_DIR);
00162
00163 char *path = strdup(filename + basepath_length);
00164 char *psep = strrchr(path, PATHSEPCHAR);
00165 if (psep != NULL) {
00166 psep[1] = '\0';
00167 } else {
00168 *path = '\0';
00169 }
00170
00171 if (set->FillSetDetails(ini, path, filename)) {
00172 Tbase_set *duplicate = NULL;
00173 for (Tbase_set *c = BaseMedia<Tbase_set>::available_sets; c != NULL; c = c->next) {
00174 if (strcmp(c->name, set->name) == 0 || c->shortname == set->shortname) {
00175 duplicate = c;
00176 break;
00177 }
00178 }
00179 if (duplicate != NULL) {
00180
00181 if ((duplicate->valid_files == set->valid_files && duplicate->version >= set->version) ||
00182 duplicate->valid_files > set->valid_files) {
00183 DEBUG(grf, 1, "Not adding %s (%i) as base " SET_TYPE " set (duplicate)", set->name, set->version);
00184 set->next = BaseMedia<Tbase_set>::duplicate_sets;
00185 BaseMedia<Tbase_set>::duplicate_sets = set;
00186 } else {
00187 Tbase_set **prev = &BaseMedia<Tbase_set>::available_sets;
00188 while (*prev != duplicate) prev = &(*prev)->next;
00189
00190 *prev = set;
00191 set->next = duplicate->next;
00192
00193
00194
00195
00196 if (BaseMedia<Tbase_set>::used_set == duplicate) BaseMedia<Tbase_set>::used_set = set;
00197
00198 DEBUG(grf, 1, "Removing %s (%i) as base " SET_TYPE " set (duplicate)", duplicate->name, duplicate->version);
00199 duplicate->next = BaseMedia<Tbase_set>::duplicate_sets;
00200 BaseMedia<Tbase_set>::duplicate_sets = duplicate;
00201 ret = true;
00202 }
00203 } else {
00204 Tbase_set **last = &BaseMedia<Tbase_set>::available_sets;
00205 while (*last != NULL) last = &(*last)->next;
00206
00207 *last = set;
00208 ret = true;
00209 }
00210 if (ret) {
00211 DEBUG(grf, 1, "Adding %s (%i) as base " SET_TYPE " set", set->name, set->version);
00212 }
00213 } else {
00214 delete set;
00215 }
00216 free(path);
00217
00218 delete ini;
00219 return ret;
00220 }
00221
00227 template <class Tbase_set>
00228 bool BaseMedia<Tbase_set>::SetSet(const char *name)
00229 {
00230 extern void CheckExternalFiles();
00231
00232 if (StrEmpty(name)) {
00233 if (!BaseMedia<Tbase_set>::DetermineBestSet()) return false;
00234 CheckExternalFiles();
00235 return true;
00236 }
00237
00238 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
00239 if (strcmp(name, s->name) == 0) {
00240 BaseMedia<Tbase_set>::used_set = s;
00241 CheckExternalFiles();
00242 return true;
00243 }
00244 }
00245 return false;
00246 }
00247
00254 template <class Tbase_set>
00255 char *BaseMedia<Tbase_set>::GetSetsList(char *p, const char *last)
00256 {
00257 p += seprintf(p, last, "List of " SET_TYPE " sets:\n");
00258 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
00259 p += seprintf(p, last, "%18s: %s", s->name, s->GetDescription());
00260 int invalid = s->GetNumInvalid();
00261 if (invalid != 0) {
00262 int missing = s->GetNumMissing();
00263 if (missing == 0) {
00264 p += seprintf(p, last, " (%i corrupt file%s)\n", invalid, invalid == 1 ? "" : "s");
00265 } else {
00266 p += seprintf(p, last, " (unuseable: %i missing file%s)\n", missing, missing == 1 ? "" : "s");
00267 }
00268 } else {
00269 p += seprintf(p, last, "\n");
00270 }
00271 }
00272 p += seprintf(p, last, "\n");
00273
00274 return p;
00275 }
00276
00277 #if defined(ENABLE_NETWORK)
00278 #include "network/network_content.h"
00279
00280 template <class Tbase_set> const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const Tbase_set *s)
00281 {
00282 for (; s != NULL; s = s->next) {
00283 if (s->GetNumMissing() != 0) continue;
00284
00285 if (s->shortname != ci->unique_id) continue;
00286 if (!md5sum) return s->files[0].filename;
00287
00288 byte md5[16];
00289 memset(md5, 0, sizeof(md5));
00290 for (uint i = 0; i < Tbase_set::NUM_FILES; i++) {
00291 for (uint j = 0; j < sizeof(md5); j++) {
00292 md5[j] ^= s->files[i].hash[j];
00293 }
00294 }
00295 if (memcmp(md5, ci->md5sum, sizeof(md5)) == 0) return s->files[0].filename;
00296 }
00297 return NULL;
00298 }
00299
00300 template <class Tbase_set>
00301 bool BaseMedia<Tbase_set>::HasSet(const ContentInfo *ci, bool md5sum)
00302 {
00303 return (TryGetBaseSetFile(ci, md5sum, BaseMedia<Tbase_set>::available_sets) != NULL) ||
00304 (TryGetBaseSetFile(ci, md5sum, BaseMedia<Tbase_set>::duplicate_sets) != NULL);
00305 }
00306
00307 #else
00308
00309 template <class Tbase_set>
00310 const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const Tbase_set *s)
00311 {
00312 return NULL;
00313 }
00314
00315 template <class Tbase_set>
00316 bool BaseMedia<Tbase_set>::HasSet(const ContentInfo *ci, bool md5sum)
00317 {
00318 return false;
00319 }
00320
00321 #endif
00322
00327 template <class Tbase_set>
00328 int BaseMedia<Tbase_set>::GetNumSets()
00329 {
00330 int n = 0;
00331 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
00332 if (s != BaseMedia<Tbase_set>::used_set && s->GetNumMissing() != 0) continue;
00333 n++;
00334 }
00335 return n;
00336 }
00337
00342 template <class Tbase_set>
00343 int BaseMedia<Tbase_set>::GetIndexOfUsedSet()
00344 {
00345 int n = 0;
00346 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
00347 if (s == BaseMedia<Tbase_set>::used_set) return n;
00348 if (s->GetNumMissing() != 0) continue;
00349 n++;
00350 }
00351 return -1;
00352 }
00353
00358 template <class Tbase_set>
00359 const Tbase_set *BaseMedia<Tbase_set>::GetSet(int index)
00360 {
00361 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
00362 if (s != BaseMedia<Tbase_set>::used_set && s->GetNumMissing() != 0) continue;
00363 if (index == 0) return s;
00364 index--;
00365 }
00366 error("Base" SET_TYPE "::GetSet(): index %d out of range", index);
00367 }
00368
00373 template <class Tbase_set>
00374 const Tbase_set *BaseMedia<Tbase_set>::GetUsedSet()
00375 {
00376 return BaseMedia<Tbase_set>::used_set;
00377 }
00378
00383 template <class Tbase_set>
00384 Tbase_set *BaseMedia<Tbase_set>::GetAvailableSets()
00385 {
00386 return BaseMedia<Tbase_set>::available_sets;
00387 }
00388
00394 #define INSTANTIATE_BASE_MEDIA_METHODS(repl_type, set_type) \
00395 template const char *repl_type::ini_set; \
00396 template const char *repl_type::GetExtension(); \
00397 template bool repl_type::AddFile(const char *filename, size_t pathlength, const char *tar_filename); \
00398 template bool repl_type::HasSet(const struct ContentInfo *ci, bool md5sum); \
00399 template bool repl_type::SetSet(const char *name); \
00400 template char *repl_type::GetSetsList(char *p, const char *last); \
00401 template int repl_type::GetNumSets(); \
00402 template int repl_type::GetIndexOfUsedSet(); \
00403 template const set_type *repl_type::GetSet(int index); \
00404 template const set_type *repl_type::GetUsedSet(); \
00405 template bool repl_type::DetermineBestSet(); \
00406 template set_type *repl_type::GetAvailableSets(); \
00407 template const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const set_type *s);
00408