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/>.
8 /** @file script_infolist.cpp ScriptInfo list class and helpers. */
10 #include "../stdafx.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
++) {
24 it
= this->single_list
.begin();
25 for (; it
!= this->single_list
.end(); it
++) {
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());
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();
54 /* Windows doesn't care about the case */
55 if (strcasecmp (old_main
, new_main
) != 0) {
57 if (strcmp (old_main
, new_main
) != 0) {
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.");
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()) {
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());
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();
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();
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
;
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
)
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
)
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) {
197 checksum
.Append(buffer
, len
);
199 checksum
.Finish(tmp_md5sum
);
203 /* ... and xor it to the overall md5sum. */
204 for (uint i
= 0; i
< sizeof(md5sum
); i
++) this->md5sum
[i
] ^= tmp_md5sum
[i
];
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
)
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
);
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
;
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 */