1 /* $Id: script_scanner.cpp 25546 2013-06-30 20:40:49Z rubidium $ */
4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
10 /** @file script_scanner.cpp Allows scanning for scripts. */
12 #include "../stdafx.h"
14 #include "../string_func.h"
15 #include "../settings_type.h"
17 #include "../script/squirrel.hpp"
18 #include "script_scanner.hpp"
19 #include "script_info.hpp"
21 #if defined(ENABLE_NETWORK)
22 #include "../network/network_content.h"
23 #include "../3rdparty/md5/md5.h"
24 #include "../tar_type.h"
25 #endif /* ENABLE_NETWORK */
27 #include "../safeguards.h"
29 bool ScriptScanner::AddFile(const char *filename
, size_t basepath_length
, const char *tar_filename
)
31 free(this->main_script
);
32 this->main_script
= stredup(filename
);
33 if (this->main_script
== NULL
) return false;
36 if (tar_filename
!= NULL
) {
37 this->tar_file
= stredup(tar_filename
);
38 if (this->tar_file
== NULL
) return false;
40 this->tar_file
= NULL
;
43 const char *end
= this->main_script
+ strlen(this->main_script
) + 1;
44 char *p
= strrchr(this->main_script
, PATHSEPCHAR
);
46 p
= this->main_script
;
48 /* Skip over the path separator character. We don't need that. */
52 strecpy(p
, "main.nut", end
);
54 if (!FioCheckFileExists(filename
, this->subdir
) || !FioCheckFileExists(this->main_script
, this->subdir
)) return false;
57 this->engine
->LoadScript(filename
);
62 ScriptScanner::ScriptScanner() :
69 void ScriptScanner::ResetEngine()
71 this->engine
->Reset();
72 this->engine
->SetGlobalPointer(this);
73 this->RegisterAPI(this->engine
);
76 void ScriptScanner::Initialize(const char *name
)
78 this->engine
= new Squirrel(name
);
85 ScriptScanner::~ScriptScanner()
89 free(this->main_script
);
94 void ScriptScanner::RescanDir()
96 /* Forget about older scans */
99 /* Scan for scripts */
100 this->Scan(this->GetFileName(), this->GetDirectory());
103 void ScriptScanner::Reset()
105 ScriptInfoList::iterator it
= this->info_list
.begin();
106 for (; it
!= this->info_list
.end(); it
++) {
110 it
= this->info_single_list
.begin();
111 for (; it
!= this->info_single_list
.end(); it
++) {
115 this->info_list
.clear();
116 this->info_single_list
.clear();
119 void ScriptScanner::RegisterScript(ScriptInfo
*info
)
121 char script_original_name
[1024];
122 this->GetScriptName(info
, script_original_name
, lastof(script_original_name
));
123 strtolower(script_original_name
);
125 char script_name
[1024];
126 seprintf(script_name
, lastof(script_name
), "%s.%d", script_original_name
, info
->GetVersion());
128 /* Check if GetShortName follows the rules */
129 if (strlen(info
->GetShortName()) != 4) {
130 DEBUG(script
, 0, "The script '%s' returned a string from GetShortName() which is not four characaters. Unable to load the script.", info
->GetName());
135 if (this->info_list
.find(script_name
) != this->info_list
.end()) {
136 /* This script was already registered */
138 /* Windows doesn't care about the case */
139 if (strcasecmp(this->info_list
[script_name
]->GetMainScript(), info
->GetMainScript()) == 0) {
141 if (strcmp(this->info_list
[script_name
]->GetMainScript(), info
->GetMainScript()) == 0) {
147 DEBUG(script
, 1, "Registering two scripts with the same name and version");
148 DEBUG(script
, 1, " 1: %s", this->info_list
[script_name
]->GetMainScript());
149 DEBUG(script
, 1, " 2: %s", info
->GetMainScript());
150 DEBUG(script
, 1, "The first is taking precedence.");
156 this->info_list
[stredup(script_name
)] = info
;
158 if (!info
->IsDeveloperOnly() || _settings_client
.gui
.ai_developer_tools
) {
159 /* Add the script to the 'unique' script list, where only the highest version
160 * of the script is registered. */
161 if (this->info_single_list
.find(script_original_name
) == this->info_single_list
.end()) {
162 this->info_single_list
[stredup(script_original_name
)] = info
;
163 } else if (this->info_single_list
[script_original_name
]->GetVersion() < info
->GetVersion()) {
164 this->info_single_list
[script_original_name
] = info
;
169 char *ScriptScanner::GetConsoleList(char *p
, const char *last
, bool newest_only
) const
171 p
+= seprintf(p
, last
, "List of %s:\n", this->GetScannerName());
172 const ScriptInfoList
&list
= newest_only
? this->info_single_list
: this->info_list
;
173 ScriptInfoList::const_iterator it
= list
.begin();
174 for (; it
!= list
.end(); it
++) {
175 ScriptInfo
*i
= (*it
).second
;
176 p
+= seprintf(p
, last
, "%10s (v%d): %s\n", i
->GetName(), i
->GetVersion(), i
->GetDescription());
178 p
+= seprintf(p
, last
, "\n");
183 #if defined(ENABLE_NETWORK)
185 /** Helper for creating a MD5sum of all files within of a script. */
186 struct ScriptFileChecksumCreator
: FileScanner
{
187 byte md5sum
[16]; ///< The final md5sum.
188 Subdirectory dir
; ///< The directory to look in.
191 * Initialise the md5sum to be all zeroes,
192 * so we can easily xor the data.
194 ScriptFileChecksumCreator(Subdirectory dir
)
197 memset(this->md5sum
, 0, sizeof(this->md5sum
));
200 /* Add the file and calculate the md5 sum. */
201 virtual bool AddFile(const char *filename
, size_t basepath_length
, const char *tar_filename
)
208 /* Open the file ... */
209 FILE *f
= FioFOpenFile(filename
, "rb", this->dir
, &size
);
210 if (f
== NULL
) return false;
212 /* ... calculate md5sum... */
213 while ((len
= fread(buffer
, 1, (size
> sizeof(buffer
)) ? sizeof(buffer
) : size
, f
)) != 0 && size
!= 0) {
215 checksum
.Append(buffer
, len
);
217 checksum
.Finish(tmp_md5sum
);
221 /* ... and xor it to the overall md5sum. */
222 for (uint i
= 0; i
< sizeof(md5sum
); i
++) this->md5sum
[i
] ^= tmp_md5sum
[i
];
229 * Check whether the script given in info is the same as in ci based
230 * on the shortname and md5 sum.
231 * @param ci The information to compare to.
232 * @param md5sum Whether to check the MD5 checksum.
233 * @param info The script to get the shortname and md5 sum from.
234 * @return True iff they're the same.
236 static bool IsSameScript(const ContentInfo
*ci
, bool md5sum
, ScriptInfo
*info
, Subdirectory dir
)
239 const char *str
= info
->GetShortName();
240 for (int j
= 0; j
< 4 && *str
!= '\0'; j
++, str
++) id
|= *str
<< (8 * j
);
242 if (id
!= ci
->unique_id
) return false;
243 if (!md5sum
) return true;
245 ScriptFileChecksumCreator
checksum(dir
);
246 const char *tar_filename
= info
->GetTarFile();
247 TarList::iterator iter
;
248 if (tar_filename
!= NULL
&& (iter
= _tar_list
[dir
].find(tar_filename
)) != _tar_list
[dir
].end()) {
249 /* The main script is in a tar file, so find all files that
250 * are in the same tar and add them to the MD5 checksumming. */
251 TarFileList::iterator tar
;
252 FOR_ALL_TARS(tar
, dir
) {
253 /* Not in the same tar. */
254 if (tar
->second
.tar_filename
!= iter
->first
) continue;
256 /* Check the extension. */
257 const char *ext
= strrchr(tar
->first
.c_str(), '.');
258 if (ext
== NULL
|| strcasecmp(ext
, ".nut") != 0) continue;
260 checksum
.AddFile(tar
->first
.c_str(), 0, tar_filename
);
264 strecpy(path
, info
->GetMainScript(), lastof(path
));
265 /* There'll always be at least 1 path separator character in a script
266 * main script name as the search algorithm requires the main script to
267 * be in a subdirectory of the script directory; so <dir>/<path>/main.nut. */
268 *strrchr(path
, PATHSEPCHAR
) = '\0';
269 checksum
.Scan(".nut", path
);
272 return memcmp(ci
->md5sum
, checksum
.md5sum
, sizeof(ci
->md5sum
)) == 0;
275 bool ScriptScanner::HasScript(const ContentInfo
*ci
, bool md5sum
)
277 for (ScriptInfoList::iterator it
= this->info_list
.begin(); it
!= this->info_list
.end(); it
++) {
278 if (IsSameScript(ci
, md5sum
, (*it
).second
, this->GetDirectory())) return true;
283 const char *ScriptScanner::FindMainScript(const ContentInfo
*ci
, bool md5sum
)
285 for (ScriptInfoList::iterator it
= this->info_list
.begin(); it
!= this->info_list
.end(); it
++) {
286 if (IsSameScript(ci
, md5sum
, (*it
).second
, this->GetDirectory())) return (*it
).second
->GetMainScript();
291 #endif /* ENABLE_NETWORK */