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 strcpy(this->main_script, "%_dummy");
00166 extern void AI_CreateAIInfoDummy(HSQUIRRELVM vm);
00167 AI_CreateAIInfoDummy(this->engine->GetVM());
00168 }
00169
00170 AIScanner::~AIScanner()
00171 {
00172 AIInfoList::iterator it = this->info_list.begin();
00173 for (; it != this->info_list.end(); it++) {
00174 free((void *)(*it).first);
00175 delete (*it).second;
00176 }
00177 it = this->info_single_list.begin();
00178 for (; it != this->info_single_list.end(); it++) {
00179 free((void *)(*it).first);
00180 }
00181 AILibraryList::iterator lit = this->library_list.begin();
00182 for (; lit != this->library_list.end(); lit++) {
00183 free((void *)(*lit).first);
00184 delete (*lit).second;
00185 }
00186
00187 delete this->info_dummy;
00188 delete this->engine;
00189 }
00190
00191 bool AIScanner::ImportLibrary(const char *library, const char *class_name, int version, HSQUIRRELVM vm, AIController *controller)
00192 {
00193
00194 char library_name[1024];
00195 snprintf(library_name, sizeof(library_name), "%s.%d", library, version);
00196 strtolower(library_name);
00197
00198
00199 AILibraryList::iterator iter = this->library_list.find(library_name);
00200 if (iter == this->library_list.end()) {
00201 char error[1024];
00202
00203
00204 iter = this->library_list.find(library);
00205 if (iter == this->library_list.end()) {
00206 snprintf(error, sizeof(error), "couldn't find library '%s'", library);
00207 } else {
00208 snprintf(error, sizeof(error), "couldn't find library '%s' version %d. The latest version available is %d", library, version, (*iter).second->GetVersion());
00209 }
00210 sq_throwerror(vm, OTTD2FS(error));
00211 return false;
00212 }
00213
00214
00215 HSQOBJECT parent;
00216 sq_getstackobj(vm, 1, &parent);
00217
00218 char fake_class[1024];
00219 int next_number;
00220
00221 if (!controller->LoadedLibrary(library_name, &next_number, &fake_class[0], sizeof(fake_class))) {
00222
00223 snprintf(fake_class, sizeof(fake_class), "_internalNA%d", next_number);
00224
00225
00226 sq_pushroottable(vm);
00227 sq_pushstring(vm, OTTD2FS(fake_class), -1);
00228 sq_newclass(vm, SQFalse);
00229
00230 if (!Squirrel::LoadScript(vm, (*iter).second->GetMainScript(), false)) {
00231 char error[1024];
00232 snprintf(error, sizeof(error), "there was a compile error when importing '%s' version %d", library, version);
00233 sq_throwerror(vm, OTTD2FS(error));
00234 return false;
00235 }
00236
00237 sq_newslot(vm, -3, SQFalse);
00238 sq_pop(vm, 1);
00239
00240 controller->AddLoadedLibrary(library_name, fake_class);
00241 }
00242
00243
00244 sq_pushroottable(vm);
00245 sq_pushstring(vm, OTTD2FS(fake_class), -1);
00246 if (SQ_FAILED(sq_get(vm, -2))) {
00247 sq_throwerror(vm, _SC("internal error assigning library class"));
00248 return false;
00249 }
00250 sq_pushstring(vm, OTTD2FS((*iter).second->GetInstanceName()), -1);
00251 if (SQ_FAILED(sq_get(vm, -2))) {
00252 char error[1024];
00253 snprintf(error, sizeof(error), "unable to find class '%s' in the library '%s' version %d", (*iter).second->GetInstanceName(), library, version);
00254 sq_throwerror(vm, OTTD2FS(error));
00255 return false;
00256 }
00257 HSQOBJECT obj;
00258 sq_getstackobj(vm, -1, &obj);
00259 sq_pop(vm, 3);
00260
00261 if (StrEmpty(class_name)) {
00262 sq_pushobject(vm, obj);
00263 return true;
00264 }
00265
00266
00267 sq_pushobject(vm, parent);
00268 sq_pushstring(vm, OTTD2FS(class_name), -1);
00269 sq_pushobject(vm, obj);
00270 sq_newclass(vm, SQTrue);
00271 sq_newslot(vm, -3, SQFalse);
00272 sq_pop(vm, 1);
00273
00274 sq_pushobject(vm, obj);
00275 return true;
00276 }
00277
00278 void AIScanner::RegisterLibrary(AILibrary *library)
00279 {
00280 char library_name[1024];
00281 snprintf(library_name, sizeof(library_name), "%s.%s.%d", library->GetCategory(), library->GetInstanceName(), library->GetVersion());
00282 strtolower(library_name);
00283
00284 if (this->library_list.find(library_name) != this->library_list.end()) {
00285
00286 #ifdef WIN32
00287
00288 if (strcasecmp(this->library_list[library_name]->GetMainScript(), library->GetMainScript()) == 0) {
00289 #else
00290 if (strcmp(this->library_list[library_name]->GetMainScript(), library->GetMainScript()) == 0) {
00291 #endif
00292 delete library;
00293 return;
00294 }
00295
00296 DEBUG(ai, 0, "Registering two libraries with the same name and version");
00297 DEBUG(ai, 0, " 1: %s", this->library_list[library_name]->GetMainScript());
00298 DEBUG(ai, 0, " 2: %s", library->GetMainScript());
00299 DEBUG(ai, 0, "The first is taking precedence.");
00300
00301 delete library;
00302 return;
00303 }
00304
00305 this->library_list[strdup(library_name)] = library;
00306 }
00307
00308 void AIScanner::RegisterAI(AIInfo *info)
00309 {
00310 char ai_name[1024];
00311 snprintf(ai_name, sizeof(ai_name), "%s.%d", info->GetName(), info->GetVersion());
00312 strtolower(ai_name);
00313
00314
00315 if (strlen(info->GetShortName()) != 4) {
00316 DEBUG(ai, 0, "The AI '%s' returned a string from GetShortName() which is not four characaters. Unable to load the AI.", info->GetName());
00317 delete info;
00318 return;
00319 }
00320
00321 if (this->info_list.find(ai_name) != this->info_list.end()) {
00322
00323 #ifdef WIN32
00324
00325 if (strcasecmp(this->info_list[ai_name]->GetMainScript(), info->GetMainScript()) == 0) {
00326 #else
00327 if (strcmp(this->info_list[ai_name]->GetMainScript(), info->GetMainScript()) == 0) {
00328 #endif
00329 delete info;
00330 return;
00331 }
00332
00333 DEBUG(ai, 0, "Registering two AIs with the same name and version");
00334 DEBUG(ai, 0, " 1: %s", this->info_list[ai_name]->GetMainScript());
00335 DEBUG(ai, 0, " 2: %s", info->GetMainScript());
00336 DEBUG(ai, 0, "The first is taking precedence.");
00337
00338 delete info;
00339 return;
00340 }
00341
00342 this->info_list[strdup(ai_name)] = info;
00343
00344
00345
00346 snprintf(ai_name, sizeof(ai_name), "%s", info->GetName());
00347 strtolower(ai_name);
00348 if (this->info_single_list.find(ai_name) == this->info_single_list.end()) {
00349 this->info_single_list[strdup(ai_name)] = info;
00350 } else if (this->info_single_list[ai_name]->GetVersion() < info->GetVersion()) {
00351 this->info_single_list[ai_name] = info;
00352 }
00353 }
00354
00355 AIInfo *AIScanner::SelectRandomAI()
00356 {
00357 if (this->info_single_list.size() == 0) {
00358 DEBUG(ai, 0, "No suitable AI found, loading 'dummy' AI.");
00359 return this->info_dummy;
00360 }
00361
00362
00363 uint pos;
00364 if (_networking) pos = InteractiveRandomRange((uint16)this->info_single_list.size());
00365 else pos = RandomRange((uint16)this->info_single_list.size());
00366
00367
00368 AIInfoList::iterator it = this->info_single_list.begin();
00369 for (; pos > 0; pos--) it++;
00370 AIInfoList::iterator first_it = it;
00371 return (*it).second;
00372 }
00373
00374 AIInfo *AIScanner::FindInfo(const char *nameParam, int versionParam)
00375 {
00376 if (this->info_list.size() == 0) return NULL;
00377 if (nameParam == NULL) return NULL;
00378
00379 char ai_name[1024];
00380 ttd_strlcpy(ai_name, nameParam, sizeof(ai_name));
00381 strtolower(ai_name);
00382
00383 AIInfo *info = NULL;
00384 int version = -1;
00385
00386 if (versionParam == -1) {
00387
00388 if (this->info_single_list.find(ai_name) != this->info_single_list.end()) return this->info_single_list[ai_name];
00389
00390
00391 char *e = strrchr(ai_name, '.');
00392 if (e == NULL) return NULL;
00393 *e = '\0';
00394 e++;
00395 versionParam = atoi(e);
00396
00397 }
00398
00399
00400 char ai_name_tmp[1024];
00401 snprintf(ai_name_tmp, sizeof(ai_name_tmp), "%s.%d", ai_name, versionParam);
00402 strtolower(ai_name_tmp);
00403 if (this->info_list.find(ai_name_tmp) != this->info_list.end()) return this->info_list[ai_name_tmp];
00404
00405
00406
00407 AIInfoList::iterator it = this->info_list.begin();
00408 for (; it != this->info_list.end(); it++) {
00409 char ai_name_compare[1024];
00410 snprintf(ai_name_compare, sizeof(ai_name_compare), "%s", (*it).second->GetName());
00411 strtolower(ai_name_compare);
00412
00413 if (strcasecmp(ai_name, ai_name_compare) == 0 && (*it).second->CanLoadFromVersion(versionParam)) {
00414 version = (*it).second->GetVersion();
00415 info = (*it).second;
00416 }
00417 }
00418
00419 return info;
00420 }
00421
00422 char *AIScanner::GetAIConsoleList(char *p, const char *last)
00423 {
00424 p += seprintf(p, last, "List of AIs:\n");
00425 AIInfoList::iterator it = this->info_list.begin();
00426 for (; it != this->info_list.end(); it++) {
00427 AIInfo *i = (*it).second;
00428 p += seprintf(p, last, "%10s (v%d): %s\n", i->GetName(), i->GetVersion(), i->GetDescription());
00429 }
00430 p += seprintf(p, last, "\n");
00431
00432 return p;
00433 }
00434
00435 #if defined(ENABLE_NETWORK)
00436 #include "../network/network_content.h"
00437 #include "../md5.h"
00438 #include "../tar_type.h"
00439
00441 struct AIFileChecksumCreator : FileScanner {
00442 byte md5sum[16];
00443
00448 AIFileChecksumCreator()
00449 {
00450 memset(this->md5sum, 0, sizeof(this->md5sum));
00451 }
00452
00453
00454 virtual bool AddFile(const char *filename, size_t basepath_length)
00455 {
00456 Md5 checksum;
00457 uint8 buffer[1024];
00458 size_t len, size;
00459 byte tmp_md5sum[16];
00460
00461
00462 FILE *f = FioFOpenFile(filename, "rb", DATA_DIR, &size);
00463 if (f == NULL) return false;
00464
00465
00466 while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, f)) != 0 && size != 0) {
00467 size -= len;
00468 checksum.Append(buffer, len);
00469 }
00470 checksum.Finish(tmp_md5sum);
00471
00472 FioFCloseFile(f);
00473
00474
00475 for (uint i = 0; i < sizeof(md5sum); i++) this->md5sum[i] ^= tmp_md5sum[i];
00476
00477 return true;
00478 }
00479 };
00480
00489 static bool IsSameAI(const ContentInfo *ci, bool md5sum, AIFileInfo *info)
00490 {
00491 uint32 id = 0;
00492 const char *str = info->GetShortName();
00493 for (int j = 0; j < 4 && *str != '\0'; j++, str++) id |= *str << (8 * j);
00494
00495 if (id != ci->unique_id) return false;
00496 if (!md5sum) return true;
00497
00498 AIFileChecksumCreator checksum;
00499 char path[MAX_PATH];
00500 strecpy(path, info->GetMainScript(), lastof(path));
00501
00502
00503
00504 *strrchr(path, PATHSEPCHAR) = '\0';
00505 *strrchr(path, PATHSEPCHAR) = '\0';
00506 TarList::iterator iter = _tar_list.find(path);
00507
00508 if (iter != _tar_list.end()) {
00509
00510
00511 TarFileList::iterator tar;
00512 FOR_ALL_TARS(tar) {
00513
00514 if (tar->second.tar_filename != iter->first) continue;
00515
00516
00517 const char *ext = strrchr(tar->first.c_str(), '.');
00518 if (ext == NULL || strcasecmp(ext, ".nut") != 0) continue;
00519
00520
00521 seprintf(path, lastof(path), "%s%c%s", tar->second.tar_filename, PATHSEPCHAR, tar->first.c_str());
00522 checksum.AddFile(path, 0);
00523 }
00524 } else {
00525
00526
00527 path[strlen(path)] = PATHSEPCHAR;
00528 checksum.Scan(".nut", path);
00529 }
00530
00531 return memcmp(ci->md5sum, checksum.md5sum, sizeof(ci->md5sum)) == 0;
00532 }
00533
00540 bool AIScanner::HasAI(const ContentInfo *ci, bool md5sum)
00541 {
00542 switch (ci->type) {
00543 case CONTENT_TYPE_AI:
00544 for (AIInfoList::iterator it = this->info_list.begin(); it != this->info_list.end(); it++) {
00545 if (IsSameAI(ci, md5sum, (*it).second)) return true;
00546 }
00547 return false;
00548
00549 case CONTENT_TYPE_AI_LIBRARY:
00550 for (AILibraryList::iterator it = this->library_list.begin(); it != this->library_list.end(); it++) {
00551 if (IsSameAI(ci, md5sum, (*it).second)) return true;
00552 }
00553 return false;
00554
00555 default:
00556 NOT_REACHED();
00557 }
00558 }
00559
00566 bool AI::HasAI(const ContentInfo *ci, bool md5sum)
00567 {
00568 return AI::ai_scanner->HasAI(ci, md5sum);
00569 }
00570
00571 #endif