/* $Id$ */ /* * This file is part of OpenTTD. * 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. * 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. * 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 . */ /** @file newgrf_config.cpp Finding NewGRFs and configuring them. */ #include "stdafx.h" #include "debug.h" #include "3rdparty/md5/md5.h" #include "newgrf.h" #include "gamelog.h" #include "network/network_func.h" #include "gfx_func.h" #include "newgrf_text.h" #include "fileio_func.h" #include "fios.h" /** * Create a new GRFConfig. * @param filename Set the filename of this GRFConfig to filename. The argument * is copied so the original string isn't needed after the constructor. */ GRFConfig::GRFConfig(const char *filename) : num_valid_params(lengthof(param)) { if (filename != NULL) this->filename = strdup(filename); } /** * Create a new GRFConfig that is a deep copy of an existing config. * @param config The GRFConfig object to make a copy of. */ GRFConfig::GRFConfig(const GRFConfig &config) : ident(config.ident), flags(config.flags & ~GCF_COPY), status(config.status), grf_bugs(config.grf_bugs), num_params(config.num_params), num_valid_params(config.num_valid_params), palette(config.palette) { MemCpyT(this->original_md5sum, config.original_md5sum, lengthof(this->original_md5sum)); MemCpyT(this->param, config.param, lengthof(this->param)); if (config.filename != NULL) this->filename = strdup(config.filename); this->name = DuplicateGRFText(config.name); this->info = DuplicateGRFText(config.info); if (config.error != NULL) this->error = new GRFError(*config.error); for (uint i = 0; i < config.param_info.Length(); i++) { if (config.param_info[i] == NULL) { *this->param_info.Append() = NULL; } else { *this->param_info.Append() = new GRFParameterInfo(*config.param_info[i]); } } } /** Cleanup a GRFConfig object. */ GRFConfig::~GRFConfig() { /* GCF_COPY as in NOT strdupped/alloced the filename and info */ if (!HasBit(this->flags, GCF_COPY)) { free(this->filename); CleanUpGRFText(this->info); delete this->error; } CleanUpGRFText(this->name); for (uint i = 0; i < this->param_info.Length(); i++) delete this->param_info[i]; } /** * Get the name of this grf. In case the name isn't known * the filename is returned. * @return The name of filename of this grf. */ const char *GRFConfig::GetName() const { const char *name = GetGRFStringFromGRFText(this->name); return StrEmpty(name) ? this->filename : name; } /** * Get the grf info. * @return A string with a description of this grf. */ const char *GRFConfig::GetDescription() const { return GetGRFStringFromGRFText(this->info); } /** * Set the palette of this GRFConfig to something suitable. * That is either the setting coming from the NewGRF or * the globally used palette. */ void GRFConfig::SetSuitablePalette() { PaletteType pal; switch (this->palette & GRFP_GRF_MASK) { case GRFP_GRF_DOS: pal = PAL_DOS; break; case GRFP_GRF_WINDOWS: pal = PAL_WINDOWS; break; default: pal = _use_palette; break; } SB(this->palette, GRFP_USE_BIT, 1, pal == PAL_WINDOWS ? GRFP_USE_WINDOWS : GRFP_USE_DOS); } GRFConfig *_all_grfs; GRFConfig *_grfconfig; GRFConfig *_grfconfig_newgame; GRFConfig *_grfconfig_static; /** * Construct a new GRFError. * @param severity The severity of this error. * @param message The actual error-string. */ GRFError::GRFError(StringID severity, StringID message) : message(message), severity(severity) { } /** * Create a new GRFError that is a deep copy of an existing error message. * @param error The GRFError object to make a copy of. */ GRFError::GRFError(const GRFError &error) : custom_message(error.custom_message), data(error.data), message(error.message), severity(error.severity), num_params(error.num_params) { if (error.custom_message != NULL) this->custom_message = strdup(error.custom_message); if (error.data != NULL) this->data = strdup(error.data); memcpy(this->param_value, error.param_value, sizeof(this->param_value)); } GRFError::~GRFError() { free(this->custom_message); free(this->data); } /** * Create a new empty GRFParameterInfo object. * @param nr The newgrf parameter that is changed. */ GRFParameterInfo::GRFParameterInfo(uint nr) : name(NULL), desc(NULL), type(PTYPE_UINT_ENUM), min_value(0), max_value(UINT32_MAX), param_nr(nr), first_bit(0), num_bit(32) {} /** * Create a new GRFParameterInfo object that is a deep copy of an existing * parameter info object. * @param info The GRFParameterInfo object to make a copy of. */ GRFParameterInfo::GRFParameterInfo(GRFParameterInfo &info) : name(DuplicateGRFText(info.name)), desc(DuplicateGRFText(info.desc)), type(info.type), min_value(info.min_value), max_value(info.max_value), param_nr(info.param_nr), first_bit(info.first_bit), num_bit(info.num_bit) { for (uint i = 0; i < info.value_names.Length(); i++) { SmallPair *data = info.value_names.Get(i); this->value_names.Insert(data->first, DuplicateGRFText(data->second)); } } /** Cleanup all parameter info. */ GRFParameterInfo::~GRFParameterInfo() { CleanUpGRFText(this->name); CleanUpGRFText(this->desc); for (uint i = 0; i < this->value_names.Length(); i++) { SmallPair *data = this->value_names.Get(i); CleanUpGRFText(data->second); } } /** * Update the palettes of the graphics from the config file. * This is needed because the config file gets read and parsed * before the palette is chosen (one can configure the base * graphics set governing the palette in the config after all). * As a result of this we update the settings from the config * once we have determined the palette. */ void UpdateNewGRFConfigPalette() { for (GRFConfig *c = _grfconfig_newgame; c != NULL; c = c->next) c->SetSuitablePalette(); for (GRFConfig *c = _grfconfig_static; c != NULL; c = c->next) c->SetSuitablePalette(); } /** Calculate the MD5 sum for a GRF, and store it in the config. * @param config GRF to compute. * @return MD5 sum was successfully computed */ static bool CalcGRFMD5Sum(GRFConfig *config) { FILE *f; Md5 checksum; uint8 buffer[1024]; size_t len, size; /* open the file */ f = FioFOpenFile(config->filename, "rb", DATA_DIR, &size); if (f == NULL) return false; /* calculate md5sum */ while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, f)) != 0 && size != 0) { size -= len; checksum.Append(buffer, len); } checksum.Finish(config->ident.md5sum); FioFCloseFile(f); return true; } /** Find the GRFID of a given grf, and calculate its md5sum. * @param config grf to fill. * @param is_static grf is static. * @return Operation was successfully completed. */ bool FillGRFDetails(GRFConfig *config, bool is_static) { if (!FioCheckFileExists(config->filename)) { config->status = GCS_NOT_FOUND; return false; } /* Find and load the Action 8 information */ LoadNewGRFFile(config, CONFIG_SLOT, GLS_FILESCAN); /* Skip if the grfid is 0 (not read) or 0xFFFFFFFF (ttdp system grf) */ if (config->ident.grfid == 0 || config->ident.grfid == 0xFFFFFFFF || config->IsOpenTTDBaseGRF()) return false; if (is_static) { /* Perform a 'safety scan' for static GRFs */ LoadNewGRFFile(config, 62, GLS_SAFETYSCAN); /* GCF_UNSAFE is set if GLS_SAFETYSCAN finds unsafe actions */ if (HasBit(config->flags, GCF_UNSAFE)) return false; } config->SetSuitablePalette(); return CalcGRFMD5Sum(config); } /** Clear a GRF Config list, freeing all nodes. * @param config Start of the list. * @post \a config is set to \c NULL. */ void ClearGRFConfigList(GRFConfig **config) { GRFConfig *c, *next; for (c = *config; c != NULL; c = next) { next = c->next; delete c; } *config = NULL; } /** Copy a GRF Config list * @param dst pointer to destination list * @param src pointer to source list values * @param init_only the copied GRF will be processed up to GLS_INIT * @return pointer to the last value added to the destination list */ GRFConfig **CopyGRFConfigList(GRFConfig **dst, const GRFConfig *src, bool init_only) { /* Clear destination as it will be overwritten */ ClearGRFConfigList(dst); for (; src != NULL; src = src->next) { GRFConfig *c = new GRFConfig(*src); ClrBit(c->flags, GCF_INIT_ONLY); if (init_only) SetBit(c->flags, GCF_INIT_ONLY); *dst = c; dst = &c->next; } return dst; } /** * Removes duplicates from lists of GRFConfigs. These duplicates * are introduced when the _grfconfig_static GRFs are appended * to the _grfconfig on a newgame or savegame. As the parameters * of the static GRFs could be different that the parameters of * the ones used non-statically. This can result in desyncs in * multiplayers, so the duplicate static GRFs have to be removed. * * This function _assumes_ that all static GRFs are placed after * the non-static GRFs. * * @param list the list to remove the duplicates from */ static void RemoveDuplicatesFromGRFConfigList(GRFConfig *list) { GRFConfig *prev; GRFConfig *cur; if (list == NULL) return; for (prev = list, cur = list->next; cur != NULL; prev = cur, cur = cur->next) { if (cur->ident.grfid != list->ident.grfid) continue; prev->next = cur->next; delete cur; cur = prev; // Just go back one so it continues as normal later on } RemoveDuplicatesFromGRFConfigList(list->next); } /** * Appends the static GRFs to a list of GRFs * @param dst the head of the list to add to */ void AppendStaticGRFConfigs(GRFConfig **dst) { GRFConfig **tail = dst; while (*tail != NULL) tail = &(*tail)->next; CopyGRFConfigList(tail, _grfconfig_static, false); RemoveDuplicatesFromGRFConfigList(*dst); } /** Appends an element to a list of GRFs * @param dst the head of the list to add to * @param el the new tail to be */ void AppendToGRFConfigList(GRFConfig **dst, GRFConfig *el) { GRFConfig **tail = dst; while (*tail != NULL) tail = &(*tail)->next; *tail = el; RemoveDuplicatesFromGRFConfigList(*dst); } /** Reset the current GRF Config to either blank or newgame settings. */ void ResetGRFConfig(bool defaults) { CopyGRFConfigList(&_grfconfig, _grfconfig_newgame, !defaults); AppendStaticGRFConfigs(&_grfconfig); } /** Check if all GRFs in the GRF config from a savegame can be loaded. * @param grfconfig GrfConfig to check * @return will return any of the following 3 values:
*
    *
  • GLC_ALL_GOOD: No problems occured, all GRF files were found and loaded *
  • GLC_COMPATIBLE: For one or more GRF's no exact match was found, but a * compatible GRF with the same grfid was found and used instead *
  • GLC_NOT_FOUND: For one or more GRF's no match was found at all *
*/ GRFListCompatibility IsGoodGRFConfigList(GRFConfig *grfconfig) { GRFListCompatibility res = GLC_ALL_GOOD; for (GRFConfig *c = grfconfig; c != NULL; c = c->next) { const GRFConfig *f = FindGRFConfig(c->ident.grfid, c->ident.md5sum); if (f == NULL) { char buf[256]; /* If we have not found the exactly matching GRF try to find one with the * same grfid, as it most likely is compatible */ f = FindGRFConfig(c->ident.grfid); if (f != NULL) { md5sumToString(buf, lastof(buf), c->ident.md5sum); DEBUG(grf, 1, "NewGRF %08X (%s) not found; checksum %s. Compatibility mode on", BSWAP32(c->ident.grfid), c->filename, buf); if (!HasBit(c->flags, GCF_COMPATIBLE)) { /* Preserve original_md5sum after it has been assigned */ SetBit(c->flags, GCF_COMPATIBLE); memcpy(c->original_md5sum, c->ident.md5sum, sizeof(c->original_md5sum)); } /* Non-found has precedence over compatibility load */ if (res != GLC_NOT_FOUND) res = GLC_COMPATIBLE; goto compatible_grf; } /* No compatible grf was found, mark it as disabled */ md5sumToString(buf, lastof(buf), c->ident.md5sum); DEBUG(grf, 0, "NewGRF %08X (%s) not found; checksum %s", BSWAP32(c->ident.grfid), c->filename, buf); c->status = GCS_NOT_FOUND; res = GLC_NOT_FOUND; } else { compatible_grf: DEBUG(grf, 1, "Loading GRF %08X from %s", BSWAP32(f->ident.grfid), f->filename); /* The filename could be the filename as in the savegame. As we need * to load the GRF here, we need the correct filename, so overwrite that * in any case and set the name and info when it is not set already. * When the GCF_COPY flag is set, it is certain that the filename is * already a local one, so there is no need to replace it. */ if (!HasBit(c->flags, GCF_COPY)) { free(c->filename); c->filename = strdup(f->filename); memcpy(c->ident.md5sum, f->ident.md5sum, sizeof(c->ident.md5sum)); if (c->name == NULL) c->name = DuplicateGRFText(f->name); if (c->info == NULL) c->info = DuplicateGRFText(f->info); c->error = NULL; } } } return res; } /** Helper for scanning for files with GRF as extension */ class GRFFileScanner : FileScanner { public: /* virtual */ bool AddFile(const char *filename, size_t basepath_length); /** Do the scan for GRFs. */ static uint DoScan() { GRFFileScanner fs; return fs.Scan(".grf", DATA_DIR); } }; bool GRFFileScanner::AddFile(const char *filename, size_t basepath_length) { GRFConfig *c = new GRFConfig(filename + basepath_length); bool added = true; if (FillGRFDetails(c, false)) { if (_all_grfs == NULL) { _all_grfs = c; } else { /* Insert file into list at a position determined by its * name, so the list is sorted as we go along */ GRFConfig **pd, *d; bool stop = false; for (pd = &_all_grfs; (d = *pd) != NULL; pd = &d->next) { if (c->ident.grfid == d->ident.grfid && memcmp(c->ident.md5sum, d->ident.md5sum, sizeof(c->ident.md5sum)) == 0) added = false; /* Because there can be multiple grfs with the same name, make sure we checked all grfs with the same name, * before inserting the entry. So insert a new grf at the end of all grfs with the same name, instead of * just after the first with the same name. Avoids doubles in the list. */ if (strcasecmp(c->GetName(), d->GetName()) <= 0) { stop = true; } else if (stop) { break; } } if (added) { c->next = d; *pd = c; } } } else { added = false; } if (!added) { /* File couldn't be opened, or is either not a NewGRF or is a * 'system' NewGRF or it's already known, so forget about it. */ delete c; } return added; } /** * Simple sorter for GRFS * @param p1 the first GRFConfig * * @param p2 the second GRFConfig * * @return the same strcmp would return for the name of the NewGRF. */ static int CDECL GRFSorter(GRFConfig * const *p1, GRFConfig * const *p2) { const GRFConfig *c1 = *p1; const GRFConfig *c2 = *p2; return strcasecmp(c1->GetName(), c2->GetName()); } /** Scan for all NewGRFs. */ void ScanNewGRFFiles() { ClearGRFConfigList(&_all_grfs); DEBUG(grf, 1, "Scanning for NewGRFs"); uint num = GRFFileScanner::DoScan(); DEBUG(grf, 1, "Scan complete, found %d files", num); if (num == 0 || _all_grfs == NULL) return; /* Sort the linked list using quicksort. * For that we first have to make an array, then sort and * then remake the linked list. */ GRFConfig **to_sort = MallocT(num); uint i = 0; for (GRFConfig *p = _all_grfs; p != NULL; p = p->next, i++) { to_sort[i] = p; } /* Number of files is not necessarily right */ num = i; QSortT(to_sort, num, &GRFSorter); for (i = 1; i < num; i++) { to_sort[i - 1]->next = to_sort[i]; } to_sort[num - 1]->next = NULL; _all_grfs = to_sort[0]; free(to_sort); #ifdef ENABLE_NETWORK NetworkAfterNewGRFScan(); #endif } /** Find a NewGRF in the scanned list. * @param grfid GRFID to look for, * @param md5sum Expected MD5 sum (set to \c NULL if not relevant). * @return The matching grf, if it exists in #_all_grfs, else \c NULL. */ const GRFConfig *FindGRFConfig(uint32 grfid, const uint8 *md5sum) { for (const GRFConfig *c = _all_grfs; c != NULL; c = c->next) { if (c->ident.HasGrfIdentifier(grfid, md5sum)) return c; } return NULL; } #ifdef ENABLE_NETWORK /** Structure for UnknownGRFs; this is a lightweight variant of GRFConfig */ struct UnknownGRF : public GRFIdentifier { UnknownGRF *next; char name[NETWORK_GRF_NAME_LENGTH]; }; /** * Finds the name of a NewGRF in the list of names for unknown GRFs. An * unknown GRF is a GRF where the .grf is not found during scanning. * * The names are resolved via UDP calls to servers that should know the name, * though the replies may not come. This leaves "" as name, though * that shouldn't matter _very_ much as they need GRF crawler or so to look * up the GRF anyway and that works better with the GRF ID. * * @param grfid the GRF ID part of the 'unique' GRF identifier * @param md5sum the MD5 checksum part of the 'unique' GRF identifier * @param create whether to create a new GRFConfig if the GRFConfig did not * exist in the fake list of GRFConfigs. * @return the GRFConfig with the given GRF ID and MD5 checksum or NULL when * it does not exist and create is false. This value must NEVER be * freed by the caller. */ char *FindUnknownGRFName(uint32 grfid, uint8 *md5sum, bool create) { UnknownGRF *grf; static UnknownGRF *unknown_grfs = NULL; for (grf = unknown_grfs; grf != NULL; grf = grf->next) { if (grf->grfid == grfid) { if (memcmp(md5sum, grf->md5sum, sizeof(grf->md5sum)) == 0) return grf->name; } } if (!create) return NULL; grf = CallocT(1); grf->grfid = grfid; grf->next = unknown_grfs; strecpy(grf->name, UNKNOWN_GRF_NAME_PLACEHOLDER, lastof(grf->name)); memcpy(grf->md5sum, md5sum, sizeof(grf->md5sum)); unknown_grfs = grf; return grf->name; } #endif /* ENABLE_NETWORK */ /** Retrieve a NewGRF from the current config by its grfid. * @param grfid grf to look for. * @param mask GRFID mask to allow for partial matching. * @return The grf config, if it exists, else \c NULL. */ GRFConfig *GetGRFConfig(uint32 grfid, uint32 mask) { GRFConfig *c; for (c = _grfconfig; c != NULL; c = c->next) { if ((c->ident.grfid & mask) == (grfid & mask)) return c; } return NULL; } /** Build a string containing space separated parameter values, and terminate */ char *GRFBuildParamList(char *dst, const GRFConfig *c, const char *last) { uint i; /* Return an empty string if there are no parameters */ if (c->num_params == 0) return strecpy(dst, "", last); for (i = 0; i < c->num_params; i++) { if (i > 0) dst = strecpy(dst, " ", last); dst += seprintf(dst, last, "%d", c->param[i]); } return dst; } /** Base GRF ID for OpenTTD's base graphics GRFs. */ static const uint32 OPENTTD_GRAPHICS_BASE_GRF_ID = BSWAP32(0xFF4F5400); /** * Checks whether this GRF is a OpenTTD base graphic GRF. * @return true if and only if it is a base GRF. */ bool GRFConfig::IsOpenTTDBaseGRF() const { return (this->ident.grfid & 0x00FFFFFF) == OPENTTD_GRAPHICS_BASE_GRF_ID; }