00001
00002
00005 #include "../stdafx.h"
00006 #include "../debug.h"
00007 #include "../string_func.h"
00008 #include "../fileio_func.h"
00009 #include "../fios.h"
00010 #include "../network/network.h"
00011 #include "../core/random_func.hpp"
00012 #include <sys/stat.h>
00013
00014 #include <squirrel.h>
00015 #include "../script/squirrel.hpp"
00016 #include "../script/squirrel_helper.hpp"
00017 #include "../script/squirrel_class.hpp"
00018 #include "ai.hpp"
00019 #include "ai_info.hpp"
00020 #include "ai_scanner.hpp"
00021 #include "api/ai_controller.hpp"
00022
00023 void AIScanner::ScanDir(const char *dirname, bool library_scan, bool library_recursive)
00024 {
00025 extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
00026 extern bool FiosIsHiddenFile(const struct dirent *ent);
00027
00028 char d_name[MAX_PATH];
00029 char temp_script[1024];
00030 struct stat sb;
00031 struct dirent *dirent;
00032 DIR *dir;
00033
00034 dir = ttd_opendir(dirname);
00035
00036 if (dir == NULL) return;
00037
00038
00039 while ((dirent = readdir(dir)) != NULL) {
00040 ttd_strlcpy(d_name, FS2OTTD(dirent->d_name), sizeof(d_name));
00041
00042
00043 if (!FiosIsValidFile(dirname, dirent, &sb)) continue;
00044 if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue;
00045 if (FiosIsHiddenFile(dirent) && strncasecmp(d_name, PERSONAL_DIR, strlen(d_name)) != 0) continue;
00046
00047
00048 ttd_strlcpy(temp_script, dirname, sizeof(temp_script));
00049 ttd_strlcat(temp_script, d_name, sizeof(temp_script));
00050
00051 if (S_ISDIR(sb.st_mode)) {
00052
00053 if (library_scan && !library_recursive) {
00054 ttd_strlcat(temp_script, PATHSEP, sizeof(temp_script));
00055 ScanDir(temp_script, library_scan, true);
00056 continue;
00057 }
00058 } else if (S_ISREG(sb.st_mode)) {
00059
00060 char *ext = strrchr(d_name, '.');
00061 if (ext == NULL || strcasecmp(ext, ".tar") != 0) continue;
00062
00063 if (library_recursive) continue;
00064
00065
00066 const char *first_dir = FioTarFirstDir(temp_script);
00067 if (first_dir == NULL) continue;
00068
00069 ttd_strlcat(temp_script, PATHSEP, sizeof(temp_script));
00070 ttd_strlcat(temp_script, first_dir, sizeof(temp_script));
00071 FioTarAddLink(temp_script, first_dir);
00072 } else {
00073
00074 continue;
00075 }
00076
00077
00078 if (temp_script[strlen(temp_script) - 1] != PATHSEPCHAR) ttd_strlcat(temp_script, PATHSEP, sizeof(temp_script));
00079
00080 if (!library_scan) {
00081 char info_script[MAX_PATH];
00082 ttd_strlcpy(info_script, temp_script, sizeof(info_script));
00083 ttd_strlcpy(main_script, temp_script, sizeof(main_script));
00084
00085
00086 ttd_strlcat(info_script, "info.nut", sizeof(info_script));
00087 ttd_strlcat(main_script, "main.nut", sizeof(main_script));
00088 if (!FioCheckFileExists(info_script, AI_DIR) || !FioCheckFileExists(main_script, AI_DIR)) continue;
00089
00090 DEBUG(ai, 6, "Loading AI at location '%s'", main_script);
00091
00092 this->engine->ResetCrashed();
00093 this->engine->LoadScript(info_script);
00094 } else {
00095 char library_script[MAX_PATH];
00096 ttd_strlcpy(library_script, temp_script, sizeof(library_script));
00097 ttd_strlcpy(main_script, temp_script, sizeof(main_script));
00098
00099
00100 ttd_strlcat(library_script, "library.nut", sizeof(library_script));
00101 ttd_strlcat(main_script, "main.nut", sizeof(main_script));
00102 if (!FioCheckFileExists(library_script, AI_LIBRARY_DIR) || !FioCheckFileExists(main_script, AI_LIBRARY_DIR)) continue;
00103
00104 DEBUG(ai, 6, "Loading AI Library at location '%s'", main_script);
00105
00106 this->engine->ResetCrashed();
00107 this->engine->LoadScript(library_script);
00108 }
00109 }
00110 closedir(dir);
00111 }
00112
00113 void AIScanner::ScanAIDir()
00114 {
00115 char buf[MAX_PATH];
00116 Searchpath sp;
00117
00118 extern void ScanForTarFiles();
00119 ScanForTarFiles();
00120
00121 FOR_ALL_SEARCHPATHS(sp) {
00122 FioAppendDirectory(buf, MAX_PATH, sp, AI_DIR);
00123 if (FileExists(buf)) this->ScanDir(buf, false);
00124 FioAppendDirectory(buf, MAX_PATH, sp, AI_LIBRARY_DIR);
00125 if (FileExists(buf)) this->ScanDir(buf, true);
00126 }
00127 }
00128
00129 void AIScanner::RescanAIDir()
00130 {
00131 this->ScanAIDir();
00132 }
00133
00134 AIScanner::AIScanner() :
00135 info_dummy(NULL)
00136 {
00137 this->engine = new Squirrel();
00138 this->main_script[0] = '\0';
00139
00140
00141 DefSQClass <AIInfo> SQAIInfo("AIInfo");
00142 SQAIInfo.PreRegister(engine);
00143 SQAIInfo.AddConstructor<void (AIInfo::*)(), 1>(engine, "x");
00144 SQAIInfo.DefSQAdvancedMethod(this->engine, &AIInfo::AddSetting, "AddSetting");
00145 SQAIInfo.DefSQAdvancedMethod(this->engine, &AIInfo::AddLabels, "AddLabels");
00146 SQAIInfo.DefSQConst(engine, AICONFIG_RANDOM, "AICONFIG_RANDOM");
00147 SQAIInfo.DefSQConst(engine, AICONFIG_BOOLEAN, "AICONFIG_BOOLEAN");
00148 SQAIInfo.PostRegister(engine);
00149 this->engine->AddMethod("RegisterAI", &AIInfo::Constructor, 2, "tx");
00150 this->engine->AddMethod("RegisterDummyAI", &AIInfo::DummyConstructor, 2, "tx");
00151
00152
00153 this->engine->AddClassBegin("AILibrary");
00154 this->engine->AddClassEnd();
00155 this->engine->AddMethod("RegisterLibrary", &AILibrary::Constructor, 2, "tx");
00156
00157
00158 this->engine->SetGlobalPointer(this);
00159
00160
00161 this->ScanAIDir();
00162
00163
00164 this->engine->ResetCrashed();
00165 extern void AI_CreateAIInfoDummy(HSQUIRRELVM vm);
00166 AI_CreateAIInfoDummy(this->engine->GetVM());
00167 }
00168
00169 AIScanner::~AIScanner()
00170 {
00171 AIInfoList::iterator it = this->info_list.begin();
00172 for (; it != this->info_list.end(); it++) {
00173 free((void *)(*it).first);
00174 delete (*it).second;
00175 }
00176 it = this->info_single_list.begin();
00177 for (; it != this->info_single_list.end(); it++) {
00178 free((void *)(*it).first);
00179 }
00180 AILibraryList::iterator lit = this->library_list.begin();
00181 for (; lit != this->library_list.end(); lit++) {
00182 free((void *)(*lit).first);
00183 delete (*lit).second;
00184 }
00185
00186 delete this->info_dummy;
00187 delete this->engine;
00188 }
00189
00190 bool AIScanner::ImportLibrary(const char *library, const char *class_name, int version, HSQUIRRELVM vm, AIController *controller)
00191 {
00192
00193 char library_name[1024];
00194 snprintf(library_name, sizeof(library_name), "%s.%d", library, version);
00195 strtolower(library_name);
00196
00197
00198 AILibraryList::iterator iter = this->library_list.find(library_name);
00199 if (iter == this->library_list.end()) {
00200 char error[1024];
00201
00202
00203 iter = this->library_list.find(library);
00204 if (iter == this->library_list.end()) {
00205 snprintf(error, sizeof(error), "couldn't find library '%s'", library);
00206 } else {
00207 snprintf(error, sizeof(error), "couldn't find library '%s' version %d. The latest version available is %d", library, version, (*iter).second->GetVersion());
00208 }
00209 sq_throwerror(vm, OTTD2FS(error));
00210 return false;
00211 }
00212
00213
00214 HSQOBJECT parent;
00215 sq_getstackobj(vm, 1, &parent);
00216
00217 char fake_class[1024];
00218 int next_number;
00219
00220 if (!controller->LoadedLibrary(library_name, &next_number, &fake_class[0], sizeof(fake_class))) {
00221
00222 snprintf(fake_class, sizeof(fake_class), "_internalNA%d", next_number);
00223
00224
00225 sq_pushroottable(vm);
00226 sq_pushstring(vm, OTTD2FS(fake_class), -1);
00227 sq_newclass(vm, SQFalse);
00228
00229 if (!Squirrel::LoadScript(vm, (*iter).second->GetMainScript(), false)) {
00230 char error[1024];
00231 snprintf(error, sizeof(error), "there was a compile error when importing '%s' version %d", library, version);
00232 sq_throwerror(vm, OTTD2FS(error));
00233 return false;
00234 }
00235
00236 sq_newslot(vm, -3, SQFalse);
00237 sq_pop(vm, 1);
00238
00239 controller->AddLoadedLibrary(library_name, fake_class);
00240 }
00241
00242
00243 sq_pushroottable(vm);
00244 sq_pushstring(vm, OTTD2FS(fake_class), -1);
00245 if (SQ_FAILED(sq_get(vm, -2))) {
00246 sq_throwerror(vm, _SC("internal error assigning library class"));
00247 return false;
00248 }
00249 sq_pushstring(vm, OTTD2FS((*iter).second->GetInstanceName()), -1);
00250 if (SQ_FAILED(sq_get(vm, -2))) {
00251 char error[1024];
00252 snprintf(error, sizeof(error), "unable to find class '%s' in the library '%s' version %d", (*iter).second->GetInstanceName(), library, version);
00253 sq_throwerror(vm, OTTD2FS(error));
00254 return false;
00255 }
00256 HSQOBJECT obj;
00257 sq_getstackobj(vm, -1, &obj);
00258 sq_pop(vm, 3);
00259
00260 if (StrEmpty(class_name)) {
00261 sq_pushobject(vm, obj);
00262 return true;
00263 }
00264
00265
00266 sq_pushobject(vm, parent);
00267 sq_pushstring(vm, OTTD2FS(class_name), -1);
00268 sq_pushobject(vm, obj);
00269 sq_newclass(vm, SQTrue);
00270 sq_newslot(vm, -3, SQFalse);
00271 sq_pop(vm, 1);
00272
00273 sq_pushobject(vm, obj);
00274 return true;
00275 }
00276
00277 void AIScanner::RegisterLibrary(AILibrary *library)
00278 {
00279 char library_name[1024];
00280 snprintf(library_name, sizeof(library_name), "%s.%s.%d", library->GetCategory(), library->GetInstanceName(), library->GetVersion());
00281 strtolower(library_name);
00282
00283 if (this->library_list.find(library_name) != this->library_list.end()) {
00284
00285 #ifdef WIN32
00286
00287 if (strcasecmp(this->library_list[library_name]->GetMainScript(), library->GetMainScript()) == 0) {
00288 #else
00289 if (strcmp(this->library_list[library_name]->GetMainScript(), library->GetMainScript()) == 0) {
00290 #endif
00291 delete library;
00292 return;
00293 }
00294
00295 DEBUG(ai, 0, "Registering two libraries with the same name and version");
00296 DEBUG(ai, 0, " 1: %s", this->library_list[library_name]->GetMainScript());
00297 DEBUG(ai, 0, " 2: %s", library->GetMainScript());
00298 DEBUG(ai, 0, "The first is taking precedence.");
00299
00300 delete library;
00301 return;
00302 }
00303
00304 this->library_list[strdup(library_name)] = library;
00305 }
00306
00307 void AIScanner::RegisterAI(AIInfo *info)
00308 {
00309 char ai_name[1024];
00310 snprintf(ai_name, sizeof(ai_name), "%s.%d", info->GetInstanceName(), info->GetVersion());
00311 strtolower(ai_name);
00312
00313
00314 if (strlen(info->GetShortName()) != 4) {
00315 DEBUG(ai, 0, "The AI '%s' returned a string from GetShortName() which is not four characaters. Unable to load the AI.", info->GetInstanceName());
00316 delete info;
00317 return;
00318 }
00319
00320 if (this->info_list.find(ai_name) != this->info_list.end()) {
00321
00322 #ifdef WIN32
00323
00324 if (strcasecmp(this->info_list[ai_name]->GetMainScript(), info->GetMainScript()) == 0) {
00325 #else
00326 if (strcmp(this->info_list[ai_name]->GetMainScript(), info->GetMainScript()) == 0) {
00327 #endif
00328 delete info;
00329 return;
00330 }
00331
00332 DEBUG(ai, 0, "Registering two AIs with the same name and version");
00333 DEBUG(ai, 0, " 1: %s", this->info_list[ai_name]->GetMainScript());
00334 DEBUG(ai, 0, " 2: %s", info->GetMainScript());
00335 DEBUG(ai, 0, "The first is taking precedence.");
00336
00337 delete info;
00338 return;
00339 }
00340
00341 this->info_list[strdup(ai_name)] = info;
00342
00343
00344
00345 snprintf(ai_name, sizeof(ai_name), "%s", info->GetInstanceName());
00346 strtolower(ai_name);
00347 if (this->info_single_list.find(ai_name) == this->info_single_list.end()) {
00348 this->info_single_list[strdup(ai_name)] = info;
00349 } else if (this->info_single_list[ai_name]->GetVersion() < info->GetVersion()) {
00350 this->info_single_list[ai_name] = info;
00351 }
00352 }
00353
00354 AIInfo *AIScanner::SelectRandomAI()
00355 {
00356 if (this->info_single_list.size() == 0) {
00357 DEBUG(ai, 0, "No suitable AI found, loading 'dummy' AI.");
00358 return this->info_dummy;
00359 }
00360
00361
00362 uint pos;
00363 if (_networking) pos = InteractiveRandomRange((uint16)this->info_single_list.size());
00364 else pos = RandomRange((uint16)this->info_single_list.size());
00365
00366
00367 AIInfoList::iterator it = this->info_single_list.begin();
00368 for (; pos > 0; pos--) it++;
00369 AIInfoList::iterator first_it = it;
00370 return (*it).second;
00371 }
00372
00373 AIInfo *AIScanner::FindInfo(const char *nameParam, int versionParam)
00374 {
00375 if (this->info_list.size() == 0) return NULL;
00376 if (nameParam == NULL) return NULL;
00377
00378 char ai_name[1024];
00379 ttd_strlcpy(ai_name, nameParam, sizeof(ai_name));
00380 strtolower(ai_name);
00381
00382 AIInfo *info = NULL;
00383 int version = -1;
00384
00385 if (versionParam == -1) {
00386
00387 if (this->info_single_list.find(ai_name) != this->info_single_list.end()) return this->info_single_list[ai_name];
00388
00389
00390 char *e = strrchr(ai_name, '.');
00391 if (e == NULL) return NULL;
00392 *e = '\0';
00393 e++;
00394 versionParam = atoi(e);
00395
00396 }
00397
00398
00399 char ai_name_tmp[1024];
00400 snprintf(ai_name_tmp, sizeof(ai_name_tmp), "%s.%d", ai_name, versionParam);
00401 strtolower(ai_name_tmp);
00402 if (this->info_list.find(ai_name_tmp) != this->info_list.end()) return this->info_list[ai_name_tmp];
00403
00404
00405
00406 AIInfoList::iterator it = this->info_list.begin();
00407 for (; it != this->info_list.end(); it++) {
00408 char ai_name_compare[1024];
00409 snprintf(ai_name_compare, sizeof(ai_name_compare), "%s", (*it).second->GetInstanceName());
00410 strtolower(ai_name_compare);
00411
00412 if (strcasecmp(ai_name, ai_name_compare) == 0 && (*it).second->CanLoadFromVersion(versionParam)) {
00413 version = (*it).second->GetVersion();
00414 info = (*it).second;
00415 }
00416 }
00417
00418 return info;
00419 }
00420
00421 char *AIScanner::GetAIConsoleList(char *p, const char *last)
00422 {
00423 p += seprintf(p, last, "List of AIs:\n");
00424 AIInfoList::iterator it = this->info_list.begin();
00425 for (; it != this->info_list.end(); it++) {
00426 AIInfo *i = (*it).second;
00427 p += seprintf(p, last, "%10s (v%d): %s\n", i->GetInstanceName(), i->GetVersion(), i->GetDescription());
00428 }
00429 p += seprintf(p, last, "\n");
00430
00431 return p;
00432 }
00433
00434 #if defined(ENABLE_NETWORK)
00435 #include "../network/network_content.h"
00436 #include "../md5.h"
00437 #include "../tar_type.h"
00438
00440 struct AIFileChecksumCreator : FileScanner {
00441 byte md5sum[16];
00442
00447 AIFileChecksumCreator()
00448 {
00449 memset(this->md5sum, 0, sizeof(this->md5sum));
00450 }
00451
00452
00453 virtual bool AddFile(const char *filename, size_t basepath_length)
00454 {
00455 Md5 checksum;
00456 uint8 buffer[1024];
00457 size_t len, size;
00458 byte tmp_md5sum[16];
00459
00460
00461 FILE *f = FioFOpenFile(filename, "rb", DATA_DIR, &size);
00462 if (f == NULL) return false;
00463
00464
00465 while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, f)) != 0 && size != 0) {
00466 size -= len;
00467 checksum.Append(buffer, len);
00468 }
00469 checksum.Finish(tmp_md5sum);
00470
00471 FioFCloseFile(f);
00472
00473
00474 for (uint i = 0; i < sizeof(md5sum); i++) this->md5sum[i] ^= tmp_md5sum[i];
00475
00476 return true;
00477 }
00478 };
00479
00488 static bool IsSameAI(const ContentInfo *ci, bool md5sum, AIFileInfo *info)
00489 {
00490 uint32 id = 0;
00491 const char *str = info->GetShortName();
00492 for (int j = 0; j < 4 && *str != '\0'; j++, str++) id |= *str << (8 * j);
00493
00494 if (id != ci->unique_id) return false;
00495 if (!md5sum) return true;
00496
00497 AIFileChecksumCreator checksum;
00498 char path[MAX_PATH];
00499 strecpy(path, info->GetMainScript(), lastof(path));
00500
00501
00502
00503 *strrchr(path, PATHSEPCHAR) = '\0';
00504 *strrchr(path, PATHSEPCHAR) = '\0';
00505 TarList::iterator iter = _tar_list.find(path);
00506
00507 if (iter != _tar_list.end()) {
00508
00509
00510 TarFileList::iterator tar;
00511 FOR_ALL_TARS(tar) {
00512
00513 if (tar->second.tar_filename != iter->first) continue;
00514
00515
00516 const char *ext = strrchr(tar->first.c_str(), '.');
00517 if (ext == NULL || strcasecmp(ext, ".nut") != 0) continue;
00518
00519
00520 seprintf(path, lastof(path), "%s%c%s", tar->second.tar_filename, PATHSEPCHAR, tar->first.c_str());
00521 checksum.AddFile(path, 0);
00522 }
00523 } else {
00524
00525
00526 path[strlen(path)] = PATHSEPCHAR;
00527 checksum.Scan(".nut", path);
00528 }
00529
00530 return memcmp(ci->md5sum, checksum.md5sum, sizeof(ci->md5sum)) == 0;
00531 }
00532
00539 bool AIScanner::HasAI(const ContentInfo *ci, bool md5sum)
00540 {
00541 switch (ci->type) {
00542 case CONTENT_TYPE_AI:
00543 for (AIInfoList::iterator it = this->info_list.begin(); it != this->info_list.end(); it++) {
00544 if (IsSameAI(ci, md5sum, (*it).second)) return true;
00545 }
00546 return false;
00547
00548 case CONTENT_TYPE_AI_LIBRARY:
00549 for (AILibraryList::iterator it = this->library_list.begin(); it != this->library_list.end(); it++) {
00550 if (IsSameAI(ci, md5sum, (*it).second)) return true;
00551 }
00552 return false;
00553
00554 default:
00555 NOT_REACHED();
00556 }
00557 }
00558
00565 bool AI::HasAI(const ContentInfo *ci, bool md5sum)
00566 {
00567 return AI::ai_scanner->HasAI(ci, md5sum);
00568 }
00569
00570 #endif