Translations update
[openttd/fttd.git] / src / script / script_infolist.cpp
blob702742a87e630b7c4213f1f444cd2cd7ac708e38
1 /*
2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
6 */
8 /** @file script_infolist.cpp ScriptInfo list class and helpers. */
10 #include "../stdafx.h"
11 #include "../debug.h"
12 #include "../string.h"
14 #include "script_infolist.hpp"
15 #include "script_info.hpp"
17 ScriptInfoList::~ScriptInfoList()
19 iterator it = this->full_list.begin();
20 for (; it != this->full_list.end(); it++) {
21 free((*it).first);
22 delete (*it).second;
24 it = this->single_list.begin();
25 for (; it != this->single_list.end(); it++) {
26 free((*it).first);
29 this->full_list.clear();
30 this->single_list.clear();
33 void ScriptInfoList::RegisterScript (ScriptInfo *info, const char *name, bool dev_only)
35 sstring<1024> script_name;
36 script_name.copy (name);
37 script_name.tolower();
38 size_t original_length = script_name.length();
39 script_name.append_fmt (".%d", info->GetVersion());
41 /* Check if GetShortName follows the rules */
42 if (strlen(info->GetShortName()) != 4) {
43 DEBUG(script, 0, "The script '%s' returned a string from GetShortName() which is not four characaters. Unable to load the script.", info->GetName());
44 delete info;
45 return;
48 iterator iter = this->full_list.find (script_name.c_str());
49 if (iter != this->full_list.end()) {
50 /* This script was already registered */
51 const char *old_main = iter->second->GetMainScript();
52 const char *new_main = info->GetMainScript();
53 #ifdef WIN32
54 /* Windows doesn't care about the case */
55 if (strcasecmp (old_main, new_main) != 0) {
56 #else
57 if (strcmp (old_main, new_main) != 0) {
58 #endif
59 DEBUG(script, 1, "Registering two scripts with the same name and version");
60 DEBUG(script, 1, " 1: %s", old_main);
61 DEBUG(script, 1, " 2: %s", new_main);
62 DEBUG(script, 1, "The first is taking precedence.");
65 delete info;
66 return;
69 this->full_list[xstrdup(script_name.c_str())] = info;
71 script_name.truncate (original_length);
73 if (!dev_only || _settings_client.gui.ai_developer_tools) {
74 /* Add the script to the 'unique' script list, where only the highest version
75 * of the script is registered. */
76 iterator iter = this->single_list.find (script_name.c_str());
77 if (iter == this->single_list.end()) {
78 this->single_list[xstrdup(script_name.c_str())] = info;
79 } else if (iter->second->GetVersion() < info->GetVersion()) {
80 iter->second = info;
85 void ScriptInfoList::GetConsoleList (stringb *buf, const char *desc, bool newest_only) const
87 buf->append_fmt ("List of %s:\n", desc);
88 const List &list = newest_only ? this->single_list : this->full_list;
89 const_iterator it = list.begin();
90 for (; it != list.end(); it++) {
91 ScriptInfo *i = (*it).second;
92 buf->append_fmt ("%10s (v%d): %s\n", i->GetName(), i->GetVersion(), i->GetDescription());
94 buf->append ('\n');
97 ScriptInfo *ScriptInfoList::FindInfo (const char *name, int version, bool force_exact_match)
99 if (this->full_list.size() == 0) return NULL;
100 if (name == NULL) return NULL;
102 sstring<1024> script_name;
103 script_name.copy (name);
104 script_name.tolower();
106 if (version == -1) {
107 /* We want to load the latest version of this script; so find it */
108 ScriptInfoList::iterator iter = this->single_list.find (script_name.c_str());
109 if (iter != this->single_list.end()) return iter->second;
111 /* If we didn't find a match script, maybe the user included a version */
112 const char *e = strrchr (script_name.c_str(), '.');
113 if (e == NULL) return NULL;
114 version = atoi (e + 1);
115 script_name.truncate (e - script_name.c_str());
116 /* FALL THROUGH, like we were calling this function with a version. */
119 if (force_exact_match) {
120 /* Try to find a direct 'name.version' match */
121 size_t length = script_name.length();
122 script_name.append_fmt (".%d", version);
123 ScriptInfoList::iterator iter = this->full_list.find (script_name.c_str());
124 if (iter != this->full_list.end()) return iter->second;
125 script_name.truncate (length);
128 ScriptInfo *info = NULL;
129 int max_version = -1;
131 /* See if there is a compatible script which goes by that name, with
132 * the highest version which allows loading the requested version */
133 ScriptInfoList::iterator it = this->full_list.begin();
134 for (; it != this->full_list.end(); it++) {
135 ScriptInfo *i = it->second;
136 assert (dynamic_cast<ScriptVersionedInfo *>(i) != NULL);
137 if ((strcasecmp (script_name.c_str(), i->GetName()) == 0)
138 && static_cast<ScriptVersionedInfo *>(i)->CanLoadFromVersion(version)
139 && (max_version == -1 || i->GetVersion() > max_version)) {
140 max_version = i->GetVersion();
141 info = i;
145 return info;
148 ScriptInfo *ScriptInfoList::FindLibrary (const char *library, int version)
150 /* Internally we store libraries as 'library.version' */
151 char library_name[1024];
152 bstrfmt (library_name, "%s.%d", library, version);
153 strtolower (library_name);
155 /* Check if the library + version exists */
156 ScriptInfoList::iterator iter = this->full_list.find (library_name);
157 if (iter == this->full_list.end()) return NULL;
159 return iter->second;
162 #if defined(ENABLE_NETWORK)
163 #include "../network/network_content.h"
164 #include "../3rdparty/md5/md5.h"
165 #include "../tar_type.h"
167 /** Helper for creating a MD5sum of all files within of a script. */
168 struct ScriptFileChecksumCreator : FileScanner {
169 byte md5sum[16]; ///< The final md5sum.
170 Subdirectory dir; ///< The directory to look in.
173 * Initialise the md5sum to be all zeroes,
174 * so we can easily xor the data.
176 ScriptFileChecksumCreator(Subdirectory dir)
178 this->dir = dir;
179 memset(this->md5sum, 0, sizeof(this->md5sum));
182 /* Add the file and calculate the md5 sum. */
183 virtual bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
185 Md5 checksum;
186 uint8 buffer[1024];
187 size_t len, size;
188 byte tmp_md5sum[16];
190 /* Open the file ... */
191 FILE *f = FioFOpenFile(filename, "rb", this->dir, &size);
192 if (f == NULL) return false;
194 /* ... calculate md5sum... */
195 while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, f)) != 0 && size != 0) {
196 size -= len;
197 checksum.Append(buffer, len);
199 checksum.Finish(tmp_md5sum);
201 FioFCloseFile(f);
203 /* ... and xor it to the overall md5sum. */
204 for (uint i = 0; i < sizeof(md5sum); i++) this->md5sum[i] ^= tmp_md5sum[i];
206 return true;
211 * Check whether the script given in info is the same as in ci based
212 * on the shortname and md5 sum.
213 * @param ci The information to compare to.
214 * @param md5sum Whether to check the MD5 checksum.
215 * @param info The script to get the shortname and md5 sum from.
216 * @return True iff they're the same.
218 static bool IsSameScript(const ContentInfo *ci, bool md5sum, ScriptInfo *info, Subdirectory dir)
220 uint32 id = 0;
221 const char *str = info->GetShortName();
222 for (int j = 0; j < 4 && *str != '\0'; j++, str++) id |= *str << (8 * j);
224 if (id != ci->unique_id) return false;
225 if (!md5sum) return true;
227 ScriptFileChecksumCreator checksum(dir);
228 const char *tar_filename = info->GetTarFile();
229 TarList::iterator iter;
230 if (tar_filename != NULL && (iter = TarCache::cache[dir].tars.find(tar_filename)) != TarCache::cache[dir].tars.end()) {
231 /* The main script is in a tar file, so find all files that
232 * are in the same tar and add them to the MD5 checksumming. */
233 TarFileList::iterator tar;
234 FOR_ALL_TARS(tar, dir) {
235 /* Not in the same tar. */
236 if (tar->second.tar_filename != iter->first) continue;
238 /* Check the extension. */
239 const char *ext = strrchr(tar->first.c_str(), '.');
240 if (ext == NULL || strcasecmp(ext, ".nut") != 0) continue;
242 checksum.AddFile(tar->first.c_str(), 0, tar_filename);
244 } else {
245 const char *main = info->GetMainScript();
246 /* There'll always be at least 1 path separator character in a script
247 * main script name as the search algorithm requires the main script to
248 * be in a subdirectory of the script directory; so <dir>/<path>/main.nut. */
249 checksum.Scan (".nut", main, strrchr (main, PATHSEPCHAR), true);
252 return memcmp(ci->md5sum, checksum.md5sum, sizeof(ci->md5sum)) == 0;
255 ScriptInfo *ScriptInfoList::FindScript (const ContentInfo *ci, Subdirectory subdir, bool md5sum)
257 for (iterator it = this->full_list.begin(); it != this->full_list.end(); it++) {
258 if (IsSameScript (ci, md5sum, it->second, subdir)) return it->second;
260 return NULL;
263 bool ScriptInfoList::HasScript (const ContentInfo *ci, Subdirectory subdir, bool md5sum)
265 return this->FindScript (ci, subdir, md5sum) != NULL;
268 const char *ScriptInfoList::FindMainScript (const ContentInfo *ci, Subdirectory subdir, bool md5sum)
270 ScriptInfo *info = this->FindScript (ci, subdir, md5sum);
271 return (info != NULL) ? info->GetMainScript() : NULL;
274 #endif /* ENABLE_NETWORK */