OpenTTD: src/fileio.cpp Source File

OpenTTD

- Main Page
- Related Pages
- Modules
- Namespaces
- Data Structures
- Files

- File List
- Globals

fileio.cpp
Go to the documentation of this file.
1 /* $Id: fileio.cpp 27954 2017-12-27 22:07:59Z frosch $ */
2
3 /*
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/>.
8 */
9
12 #include "stdafx.h"
13 #include "fileio_func.h"
14 #include "debug.h"
15 #include "fios.h"
16 #include "string_func.h"
17 #include "tar_type.h"
18 #ifdef WIN32
19 #include <windows.h>
20 # define access _taccess
21 #elif defined(__HAIKU__)
22 #include <Path.h>
23 #include <storage/FindDirectory.h>
24 #else
25 #include <unistd.h>
26 #include <pwd.h>
27 #endif
28 #include <sys/stat.h>
29 #include <algorithm>
30
31 #ifdef WITH_XDG_BASEDIR
32 #include "basedir.h"
33 #endif
34
35 #include "safeguards.h"
36
38 #define FIO_BUFFER_SIZE 512
39
41 struct Fio {
42 byte *buffer, *buffer_end;
43 size_t pos;
44 FILE *cur_fh;
45 const char *filename;
46 FILE *handles[MAX_FILE_SLOTS];
47 byte buffer_start[FIO_BUFFER_SIZE];
48 const char *filenames[MAX_FILE_SLOTS];
49 char *shortnames[MAX_FILE_SLOTS];
50 #if defined(LIMITED_FDS)
51 uint open_handles;
52 uint usage_count[MAX_FILE_SLOTS];
53 #endif /* LIMITED_FDS */
54 };
55
56 static Fio _fio;
57
59 static bool _do_scan_working_directory = true;
60
61 extern char *_config_file;
62 extern char *_highscore_file;
63
68 size_t FioGetPos()
69 {
70 return _fio.pos + (_fio.buffer - _fio.buffer_end);
71 }
72
78 const char *FioGetFilename(uint8 slot)
79 {
80 return _fio.shortnames[slot];
81 }
82
88 void FioSeekTo(size_t pos, int mode)
89 {
90 if (mode == SEEK_CUR) pos += FioGetPos();
91 _fio.buffer = _fio.buffer_end = _fio.buffer_start + FIO_BUFFER_SIZE;
92 _fio.pos = pos;
93 if (fseek(_fio.cur_fh, _fio.pos, SEEK_SET) < 0) {
94 DEBUG(misc, 0, "Seeking in %s failed", _fio.filename);
95 }
96 }
97
98 #if defined(LIMITED_FDS)
99 static void FioRestoreFile(int slot)
100 {
101 /* Do we still have the file open, or should we reopen it? */
102 if (_fio.handles[slot] == NULL) {
103 DEBUG(misc, 6, "Restoring file '%s' in slot '%d' from disk", _fio.filenames[slot], slot);
104 FioOpenFile(slot, _fio.filenames[slot]);
105 }
106 _fio.usage_count[slot]++;
107 }
108 #endif /* LIMITED_FDS */
109
115 void FioSeekToFile(uint8 slot, size_t pos)
116 {
117 FILE *f;
118 #if defined(LIMITED_FDS)
119 /* Make sure we have this file open */
120 FioRestoreFile(slot);
121 #endif /* LIMITED_FDS */
122 f = _fio.handles[slot];
123 assert(f != NULL);
124 _fio.cur_fh = f;
125 _fio.filename = _fio.filenames[slot];
126 FioSeekTo(pos, SEEK_SET);
127 }
128
133 byte FioReadByte()
134 {
135 if (_fio.buffer == _fio.buffer_end) {
136 _fio.buffer = _fio.buffer_start;
137 size_t size = fread(_fio.buffer, 1, FIO_BUFFER_SIZE, _fio.cur_fh);
138 _fio.pos += size;
139 _fio.buffer_end = _fio.buffer_start + size;
140
141 if (size == 0) return 0;
142 }
143 return *_fio.buffer++;
144 }
145
150 void FioSkipBytes(int n)
151 {
152 for (;;) {
153 int m = min(_fio.buffer_end - _fio.buffer, n);
154 _fio.buffer += m;
155 n -= m;
156 if (n == 0) break;
157 FioReadByte();
158 n--;
159 }
160 }
161
166 uint16 FioReadWord()
167 {
168 byte b = FioReadByte();
169 return (FioReadByte() << 8) | b;
170 }
171
176 uint32 FioReadDword()
177 {
178 uint b = FioReadWord();
179 return (FioReadWord() << 16) | b;
180 }
181
187 void FioReadBlock(void *ptr, size_t size)
188 {
189 FioSeekTo(FioGetPos(), SEEK_SET);
190 _fio.pos += fread(ptr, 1, size, _fio.cur_fh);
191 }
192
197 static inline void FioCloseFile(int slot)
198 {
199 if (_fio.handles[slot] != NULL) {
200 fclose(_fio.handles[slot]);
201
202 free(_fio.shortnames[slot]);
203 _fio.shortnames[slot] = NULL;
204
205 _fio.handles[slot] = NULL;
206 #if defined(LIMITED_FDS)
207 _fio.open_handles--;
208 #endif /* LIMITED_FDS */
209 }
210 }
211
213 void FioCloseAll()
214 {
215 for (int i = 0; i != lengthof(_fio.handles); i++) {
216 FioCloseFile(i);
217 }
218 }
219
220 #if defined(LIMITED_FDS)
221 static void FioFreeHandle()
222 {
223 /* If we are about to open a file that will exceed the limit, close a file */
224 if (_fio.open_handles + 1 == LIMITED_FDS) {
225 uint i, count;
226 int slot;
227
228 count = UINT_MAX;
229 slot = -1;
230 /* Find the file that is used the least */
231 for (i = 0; i < lengthof(_fio.handles); i++) {
232 if (_fio.handles[i] != NULL && _fio.usage_count[i] < count) {
233 count = _fio.usage_count[i];
234 slot = i;
235 }
236 }
237 assert(slot != -1);
238 DEBUG(misc, 6, "Closing filehandler '%s' in slot '%d' because of fd-limit", _fio.filenames[slot], slot);
239 FioCloseFile(slot);
240 }
241 }
242 #endif /* LIMITED_FDS */
243
250 void FioOpenFile(int slot, const char *filename, Subdirectory subdir)
251 {
252 FILE *f;
253
254 #if defined(LIMITED_FDS)
255 FioFreeHandle();
256 #endif /* LIMITED_FDS */
257 f = FioFOpenFile(filename, "rb", subdir);
258 if (f == NULL) usererror("Cannot open file '%s'", filename);
259 long pos = ftell(f);
260 if (pos < 0) usererror("Cannot read file '%s'", filename);
261
262 FioCloseFile(slot); // if file was opened before, close it
263 _fio.handles[slot] = f;
264 _fio.filenames[slot] = filename;
265
266 /* Store the filename without path and extension */
267 const char *t = strrchr(filename, PATHSEPCHAR);
268 _fio.shortnames[slot] = stredup(t == NULL ? filename : t);
269 char *t2 = strrchr(_fio.shortnames[slot], '.');
270 if (t2 != NULL) *t2 = '\0';
271 strtolower(_fio.shortnames[slot]);
272
273 #if defined(LIMITED_FDS)
274 _fio.usage_count[slot] = 0;
275 _fio.open_handles++;
276 #endif /* LIMITED_FDS */
277 FioSeekToFile(slot, (uint32)pos);
278 }
279
280 static const char * const _subdirs[] = {
281 "",
282 "save" PATHSEP,
283 "save" PATHSEP "autosave" PATHSEP,
284 "scenario" PATHSEP,
285 "scenario" PATHSEP "heightmap" PATHSEP,
286 "gm" PATHSEP,
287 "data" PATHSEP,
288 "baseset" PATHSEP,
289 "newgrf" PATHSEP,
290 "lang" PATHSEP,
291 "ai" PATHSEP,
292 "ai" PATHSEP "library" PATHSEP,
293 "game" PATHSEP,
294 "game" PATHSEP "library" PATHSEP,
295 "screenshot" PATHSEP,
296 };
297 assert_compile(lengthof(_subdirs) == NUM_SUBDIRS);
298
299 const char *_searchpaths[NUM_SEARCHPATHS];
300 TarList _tar_list[NUM_SUBDIRS];
301 TarFileList _tar_filelist[NUM_SUBDIRS];
302
303 typedef std::map<std::string, std::string> TarLinkList;
304 static TarLinkList _tar_linklist[NUM_SUBDIRS];
305
312 bool FioCheckFileExists(const char *filename, Subdirectory subdir)
313 {
314 FILE *f = FioFOpenFile(filename, "rb", subdir);
315 if (f == NULL) return false;
316
317 FioFCloseFile(f);
318 return true;
319 }
320
326 bool FileExists(const char *filename)
327 {
328 #if defined(WINCE)
329 /* There is always one platform that doesn't support basic commands... */
330 HANDLE hand = CreateFile(OTTD2FS(filename), 0, 0, NULL, OPEN_EXISTING, 0, NULL);
331 if (hand == INVALID_HANDLE_VALUE) return 1;
332 CloseHandle(hand);
333 return 0;
334 #else
335 return access(OTTD2FS(filename), 0) == 0;
336 #endif
337 }
338
342 void FioFCloseFile(FILE *f)
343 {
344 fclose(f);
345 }
346
347 char *FioGetFullPath(char *buf, const char *last, Searchpath sp, Subdirectory subdir, const char *filename)
348 {
349 assert(subdir < NUM_SUBDIRS);
350 assert(sp < NUM_SEARCHPATHS);
351
352 seprintf(buf, last, "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
353 return buf;
354 }
355
364 char *FioFindFullPath(char *buf, const char *last, Subdirectory subdir, const char *filename)
365 {
366 Searchpath sp;
367 assert(subdir < NUM_SUBDIRS);
368
369 FOR_ALL_SEARCHPATHS(sp) {
370 FioGetFullPath(buf, last, sp, subdir, filename);
371 if (FileExists(buf)) return buf;
372 #if !defined(WIN32)
373 /* Be, as opening files, aware that sometimes the filename
374 * might be in uppercase when it is in lowercase on the
375 * disk. Of course Windows doesn't care about casing. */
376 if (strtolower(buf + strlen(_searchpaths[sp]) - 1) && FileExists(buf)) return buf;
377 #endif
378 }
379
380 return NULL;
381 }
382
383 char *FioAppendDirectory(char *buf, const char *last, Searchpath sp, Subdirectory subdir)
384 {
385 assert(subdir < NUM_SUBDIRS);
386 assert(sp < NUM_SEARCHPATHS);
387
388 seprintf(buf, last, "%s%s", _searchpaths[sp], _subdirs[subdir]);
389 return buf;
390 }
391
392 char *FioGetDirectory(char *buf, const char *last, Subdirectory subdir)
393 {
394 Searchpath sp;
395
396 /* Find and return the first valid directory */
397 FOR_ALL_SEARCHPATHS(sp) {
398 char *ret = FioAppendDirectory(buf, last, sp, subdir);
399 if (FileExists(buf)) return ret;
400 }
401
402 /* Could not find the directory, fall back to a base path */
403 strecpy(buf, _personal_dir, last);
404
405 return buf;
406 }
407
408 static FILE *FioFOpenFileSp(const char *filename, const char *mode, Searchpath sp, Subdirectory subdir, size_t *filesize)
409 {
410 #if defined(WIN32) && defined(UNICODE)
411 /* fopen is implemented as a define with ellipses for
412 * Unicode support (prepend an L). As we are not sending
413 * a string, but a variable, it 'renames' the variable,
414 * so make that variable to makes it compile happily */
415 wchar_t Lmode[5];
416 MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode, lengthof(Lmode));
417 #endif
418 FILE *f = NULL;
419 char buf[MAX_PATH];
420
421 if (subdir == NO_DIRECTORY) {
422 strecpy(buf, filename, lastof(buf));
423 } else {
424 seprintf(buf, lastof(buf), "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
425 }
426
427 #if defined(WIN32)
428 if (mode[0] == 'r' && GetFileAttributes(OTTD2FS(buf)) == INVALID_FILE_ATTRIBUTES) return NULL;
429 #endif
430
431 f = fopen(buf, mode);
432 #if !defined(WIN32)
433 if (f == NULL && strtolower(buf + ((subdir == NO_DIRECTORY) ? 0 : strlen(_searchpaths[sp]) - 1))) {
434 f = fopen(buf, mode);
435 }
436 #endif
437 if (f != NULL && filesize != NULL) {
438 /* Find the size of the file */
439 fseek(f, 0, SEEK_END);
440 *filesize = ftell(f);
441 fseek(f, 0, SEEK_SET);
442 }
443 return f;
444 }
445
453 FILE *FioFOpenFileTar(TarFileListEntry *entry, size_t *filesize)
454 {
455 FILE *f = fopen(entry->tar_filename, "rb");
456 if (f == NULL) return f;
457
458 if (fseek(f, entry->position, SEEK_SET) < 0) {
459 fclose(f);
460 return NULL;
461 }
462
463 if (filesize != NULL) *filesize = entry->size;
464 return f;
465 }
466
474 FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir, size_t *filesize)
475 {
476 FILE *f = NULL;
477 Searchpath sp;
478
479 assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY);
480
481 FOR_ALL_SEARCHPATHS(sp) {
482 f = FioFOpenFileSp(filename, mode, sp, subdir, filesize);
483 if (f != NULL || subdir == NO_DIRECTORY) break;
484 }
485
486 /* We can only use .tar in case of data-dir, and read-mode */
487 if (f == NULL && mode[0] == 'r' && subdir != NO_DIRECTORY) {
488 static const uint MAX_RESOLVED_LENGTH = 2 * (100 + 100 + 155) + 1; // Enough space to hold two filenames plus link. See 'TarHeader'.
489 char resolved_name[MAX_RESOLVED_LENGTH];
490
491 /* Filenames in tars are always forced to be lowercase */
492 strecpy(resolved_name, filename, lastof(resolved_name));
493 strtolower(resolved_name);
494
495 size_t resolved_len = strlen(resolved_name);
496
497 /* Resolve ONE directory link */
498 for (TarLinkList::iterator link = _tar_linklist[subdir].begin(); link != _tar_linklist[subdir].end(); link++) {
499 const std::string &src = link->first;
500 size_t len = src.length();
501 if (resolved_len >= len && resolved_name[len - 1] == PATHSEPCHAR && strncmp(src.c_str(), resolved_name, len) == 0) {
502 /* Apply link */
503 char resolved_name2[MAX_RESOLVED_LENGTH];
504 const std::string &dest = link->second;
505 strecpy(resolved_name2, &(resolved_name[len]), lastof(resolved_name2));
506 strecpy(resolved_name, dest.c_str(), lastof(resolved_name));
507 strecpy(&(resolved_name[dest.length()]), resolved_name2, lastof(resolved_name));
508 break; // Only resolve one level
509 }
510 }
511
512 TarFileList::iterator it = _tar_filelist[subdir].find(resolved_name);
513 if (it != _tar_filelist[subdir].end()) {
514 f = FioFOpenFileTar(&((*it).second), filesize);
515 }
516 }
517
518 /* Sometimes a full path is given. To support
519 * the 'subdirectory' must be 'removed'. */
520 if (f == NULL && subdir != NO_DIRECTORY) {
521 switch (subdir) {
522 case BASESET_DIR:
523 f = FioFOpenFile(filename, mode, OLD_GM_DIR, filesize);
524 if (f != NULL) break;
525 FALLTHROUGH;
526 case NEWGRF_DIR:
527 f = FioFOpenFile(filename, mode, OLD_DATA_DIR, filesize);
528 break;
529
530 default:
531 f = FioFOpenFile(filename, mode, NO_DIRECTORY, filesize);
532 break;
533 }
534 }
535
536 return f;
537 }
538
543 static void FioCreateDirectory(const char *name)
544 {
545 /* Ignore directory creation errors; they'll surface later on, and most
546 * of the time they are 'directory already exists' errors anyhow. */
547 #if defined(WIN32) || defined(WINCE)
548 CreateDirectory(OTTD2FS(name), NULL);
549 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
550 mkdir(OTTD2FS(name));
551 #elif defined(__MORPHOS__) || defined(__AMIGAOS__)
552 char buf[MAX_PATH];
553 strecpy(buf, name, lastof(buf));
554
555 size_t len = strlen(name) - 1;
556 if (buf[len] == '/') {
557 buf[len] = '\0'; // Kill pathsep, so mkdir() will not fail
558 }
559
560 mkdir(OTTD2FS(buf), 0755);
561 #else
562 mkdir(OTTD2FS(name), 0755);
563 #endif
564 }
565
573 bool AppendPathSeparator(char *buf, const char *last)
574 {
575 size_t s = strlen(buf);
576
577 /* Length of string + path separator + '\0' */
578 if (s != 0 && buf[s - 1] != PATHSEPCHAR) {
579 if (&buf[s] >= last) return false;
580
581 seprintf(buf + s, last, "%c", PATHSEPCHAR);
582 }
583
584 return true;
585 }
586
592 const char *FioTarFirstDir(const char *tarname, Subdirectory subdir)
593 {
594 TarList::iterator it = _tar_list[subdir].find(tarname);
595 if (it == _tar_list[subdir].end()) return NULL;
596 return (*it).second.dirname;
597 }
598
599 static void TarAddLink(const std::string &srcParam, const std::string &destParam, Subdirectory subdir)
600 {
601 std::string src = srcParam;
602 std::string dest = destParam;
603 /* Tar internals assume lowercase */
604 std::transform(src.begin(), src.end(), src.begin(), tolower);
605 std::transform(dest.begin(), dest.end(), dest.begin(), tolower);
606
607 TarFileList::iterator dest_file = _tar_filelist[subdir].find(dest);
608 if (dest_file != _tar_filelist[subdir].end()) {
609 /* Link to file. Process the link like the destination file. */
610 _tar_filelist[subdir].insert(TarFileList::value_type(src, dest_file->second));
611 } else {
612 /* Destination file not found. Assume 'link to directory'
613 * Append PATHSEPCHAR to 'src' and 'dest' if needed */
614 const std::string src_path = ((*src.rbegin() == PATHSEPCHAR) ? src : src + PATHSEPCHAR);
615 const std::string dst_path = (dest.length() == 0 ? "" : ((*dest.rbegin() == PATHSEPCHAR) ? dest : dest + PATHSEPCHAR));
616 _tar_linklist[subdir].insert(TarLinkList::value_type(src_path, dst_path));
617 }
618 }
619
620 void FioTarAddLink(const char *src, const char *dest, Subdirectory subdir)
621 {
622 TarAddLink(src, dest, subdir);
623 }
624
630 static void SimplifyFileName(char *name)
631 {
632 /* Force lowercase */
633 strtolower(name);
634
635 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
636 #if (PATHSEPCHAR != '/')
637 for (char *n = name; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
638 #endif
639 }
640
646 uint TarScanner::DoScan(Subdirectory sd)
647 {
648 _tar_filelist[sd].clear();
649 _tar_list[sd].clear();
650 uint num = this->Scan(".tar", sd, false);
651 if (sd == BASESET_DIR || sd == NEWGRF_DIR) num += this->Scan(".tar", OLD_DATA_DIR, false);
652 return num;
653 }
654
655 /* static */ uint TarScanner::DoScan(TarScanner::Mode mode)
656 {
657 DEBUG(misc, 1, "Scanning for tars");
658 TarScanner fs;
659 uint num = 0;
660 if (mode & TarScanner::BASESET) {
661 num += fs.DoScan(BASESET_DIR);
662 }
663 if (mode & TarScanner::NEWGRF) {
664 num += fs.DoScan(NEWGRF_DIR);
665 }
666 if (mode & TarScanner::AI) {
667 num += fs.DoScan(AI_DIR);
668 num += fs.DoScan(AI_LIBRARY_DIR);
669 }
670 if (mode & TarScanner::GAME) {
671 num += fs.DoScan(GAME_DIR);
672 num += fs.DoScan(GAME_LIBRARY_DIR);
673 }
674 if (mode & TarScanner::SCENARIO) {
675 num += fs.DoScan(SCENARIO_DIR);
676 num += fs.DoScan(HEIGHTMAP_DIR);
677 }
678 DEBUG(misc, 1, "Scan complete, found %d files", num);
679 return num;
680 }
681
688 bool TarScanner::AddFile(Subdirectory sd, const char *filename)
689 {
690 this->subdir = sd;
691 return this->AddFile(filename, 0);
692 }
693
694 bool TarScanner::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
695 {
696 /* No tar within tar. */
697 assert(tar_filename == NULL);
698
699 /* The TAR-header, repeated for every file */
700 struct TarHeader {
701 char name[100];
702 char mode[8];
703 char uid[8];
704 char gid[8];
705 char size[12];
706 char mtime[12];
707 char chksum[8];
708 char typeflag;
709 char linkname[100];
710 char magic[6];
711 char version[2];
712 char uname[32];
713 char gname[32];
714 char devmajor[8];
715 char devminor[8];
716 char prefix[155];
717
718 char unused[12];
719 };
720
721 /* Check if we already seen this file */
722 TarList::iterator it = _tar_list[this->subdir].find(filename);
723 if (it != _tar_list[this->subdir].end()) return false;
724
725 FILE *f = fopen(filename, "rb");
726 /* Although the file has been found there can be
727 * a number of reasons we cannot open the file.
728 * Most common case is when we simply have not
729 * been given read access. */
730 if (f == NULL) return false;
731
732 const char *dupped_filename = stredup(filename);
733 _tar_list[this->subdir][filename].filename = dupped_filename;
734 _tar_list[this->subdir][filename].dirname = NULL;
735
736 TarLinkList links;
737
738 TarHeader th;
739 char buf[sizeof(th.name) + 1], *end;
740 char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1];
741 char link[sizeof(th.linkname) + 1];
742 char dest[sizeof(th.prefix) + 1 + sizeof(th.name) + 1 + 1 + sizeof(th.linkname) + 1];
743 size_t num = 0, pos = 0;
744
745 /* Make a char of 512 empty bytes */
746 char empty[512];
747 memset(&empty[0], 0, sizeof(empty));
748
749 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
750 size_t num_bytes_read = fread(&th, 1, 512, f);
751 if (num_bytes_read != 512) break;
752 pos += num_bytes_read;
753
754 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
755 if (strncmp(th.magic, "ustar", 5) != 0 && memcmp(&th.magic, &empty[0], 512 - offsetof(TarHeader, magic)) != 0) {
756 /* If we have only zeros in the block, it can be an end-of-file indicator */
757 if (memcmp(&th, &empty[0], 512) == 0) continue;
758
759 DEBUG(misc, 0, "The file '%s' isn't a valid tar-file", filename);
760 fclose(f);
761 return false;
762 }
763
764 name[0] = '\0';
765
766 /* The prefix contains the directory-name */
767 if (th.prefix[0] != '\0') {
768 strecpy(name, th.prefix, lastof(name));
769 strecat(name, PATHSEP, lastof(name));
770 }
771
772 /* Copy the name of the file in a safe way at the end of 'name' */
773 strecat(name, th.name, lastof(name));
774
775 /* Calculate the size of the file.. for some strange reason this is stored as a string */
776 strecpy(buf, th.size, lastof(buf));
777 size_t skip = strtoul(buf, &end, 8);
778
779 switch (th.typeflag) {
780 case '\0':
781 case '0': { // regular file
782 /* Ignore empty files */
783 if (skip == 0) break;
784
785 if (strlen(name) == 0) break;
786
787 /* Store this entry in the list */
788 TarFileListEntry entry;
789 entry.tar_filename = dupped_filename;
790 entry.size = skip;
791 entry.position = pos;
792
793 /* Convert to lowercase and our PATHSEPCHAR */
794 SimplifyFileName(name);
795
796 DEBUG(misc, 6, "Found file in tar: %s (" PRINTF_SIZE " bytes, " PRINTF_SIZE " offset)", name, skip, pos);
797 if (_tar_filelist[this->subdir].insert(TarFileList::value_type(name, entry)).second) num++;
798
799 break;
800 }
801
802 case '1': // hard links
803 case '2': { // symbolic links
804 /* Copy the destination of the link in a safe way at the end of 'linkname' */
805 strecpy(link, th.linkname, lastof(link));
806
807 if (strlen(name) == 0 || strlen(link) == 0) break;
808
809 /* Convert to lowercase and our PATHSEPCHAR */
810 SimplifyFileName(name);
811 SimplifyFileName(link);
812
813 /* Only allow relative links */
814 if (link[0] == PATHSEPCHAR) {
815 DEBUG(misc, 1, "Ignoring absolute link in tar: %s -> %s", name, link);
816 break;
817 }
818
819 /* Process relative path.
820 * Note: The destination of links must not contain any directory-links. */
821 strecpy(dest, name, lastof(dest));
822 char *destpos = strrchr(dest, PATHSEPCHAR);
823 if (destpos == NULL) destpos = dest;
824 *destpos = '\0';
825
826 char *pos = link;
827 while (*pos != '\0') {
828 char *next = strchr(pos, PATHSEPCHAR);
829 if (next == NULL) {
830 next = pos + strlen(pos);
831 } else {
832 /* Terminate the substring up to the path separator character. */
833 *next++= '\0';
834 }
835
836 if (strcmp(pos, ".") == 0) {
837 /* Skip '.' (current dir) */
838 } else if (strcmp(pos, "..") == 0) {
839 /* level up */
840 if (dest[0] == '\0') {
841 DEBUG(misc, 1, "Ignoring link pointing outside of data directory: %s -> %s", name, link);
842 break;
843 }
844
845 /* Truncate 'dest' after last PATHSEPCHAR.
846 * This assumes that the truncated part is a real directory and not a link. */
847 destpos = strrchr(dest, PATHSEPCHAR);
848 if (destpos == NULL) destpos = dest;
849 *destpos = '\0';
850 } else {
851 /* Append at end of 'dest' */
852 if (destpos != dest) destpos = strecpy(destpos, PATHSEP, lastof(dest));
853 destpos = strecpy(destpos, pos, lastof(dest));
854 }
855
856 if (destpos >= lastof(dest)) {
857 DEBUG(misc, 0, "The length of a link in tar-file '%s' is too large (malformed?)", filename);
858 fclose(f);
859 return false;
860 }
861
862 pos = next;
863 }
864
865 /* Store links in temporary list */
866 DEBUG(misc, 6, "Found link in tar: %s -> %s", name, dest);
867 links.insert(TarLinkList::value_type(name, dest));
868
869 break;
870 }
871
872 case '5': // directory
873 /* Convert to lowercase and our PATHSEPCHAR */
874 SimplifyFileName(name);
875
876 /* Store the first directory name we detect */
877 DEBUG(misc, 6, "Found dir in tar: %s", name);
878 if (_tar_list[this->subdir][filename].dirname == NULL) _tar_list[this->subdir][filename].dirname = stredup(name);
879 break;
880
881 default:
882 /* Ignore other types */
883 break;
884 }
885
886 /* Skip to the next block.. */
887 skip = Align(skip, 512);
888 if (fseek(f, skip, SEEK_CUR) < 0) {
889 DEBUG(misc, 0, "The file '%s' can't be read as a valid tar-file", filename);
890 fclose(f);
891 return false;
892 }
893 pos += skip;
894 }
895
896 DEBUG(misc, 1, "Found tar '%s' with " PRINTF_SIZE " new files", filename, num);
897 fclose(f);
898
899 /* Resolve file links and store directory links.
900 * We restrict usage of links to two cases:
901 * 1) Links to directories:
902 * Both the source path and the destination path must NOT contain any further links.
903 * When resolving files at most one directory link is resolved.
904 * 2) Links to files:
905 * The destination path must NOT contain any links.
906 * The source path may contain one directory link.
907 */
908 for (TarLinkList::iterator link = links.begin(); link != links.end(); link++) {
909 const std::string &src = link->first;
910 const std::string &dest = link->second;
911 TarAddLink(src, dest, this->subdir);
912 }
913
914 return true;
915 }
916
924 bool ExtractTar(const char *tar_filename, Subdirectory subdir)
925 {
926 TarList::iterator it = _tar_list[subdir].find(tar_filename);
927 /* We don't know the file. */
928 if (it == _tar_list[subdir].end()) return false;
929
930 const char *dirname = (*it).second.dirname;
931
932 /* The file doesn't have a sub directory! */
933 if (dirname == NULL) return false;
934
935 char filename[MAX_PATH];
936 strecpy(filename, tar_filename, lastof(filename));
937 char *p = strrchr(filename, PATHSEPCHAR);
938 /* The file's path does not have a separator? */
939 if (p == NULL) return false;
940
941 p++;
942 strecpy(p, dirname, lastof(filename));
943 DEBUG(misc, 8, "Extracting %s to directory %s", tar_filename, filename);
944 FioCreateDirectory(filename);
945
946 for (TarFileList::iterator it2 = _tar_filelist[subdir].begin(); it2 != _tar_filelist[subdir].end(); it2++) {
947 if (strcmp((*it2).second.tar_filename, tar_filename) != 0) continue;
948
949 strecpy(p, (*it2).first.c_str(), lastof(filename));
950
951 DEBUG(misc, 9, " extracting %s", filename);
952
953 /* First open the file in the .tar. */
954 size_t to_copy = 0;
955 FILE *in = FioFOpenFileTar(&(*it2).second, &to_copy);
956 if (in == NULL) {
957 DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, tar_filename);
958 return false;
959 }
960
961 /* Now open the 'output' file. */
962 FILE *out = fopen(filename, "wb");
963 if (out == NULL) {
964 DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, filename);
965 fclose(in);
966 return false;
967 }
968
969 /* Now read from the tar and write it into the file. */
970 char buffer[4096];
971 size_t read;
972 for (; to_copy != 0; to_copy -= read) {
973 read = fread(buffer, 1, min(to_copy, lengthof(buffer)), in);
974 if (read <= 0 || fwrite(buffer, 1, read, out) != read) break;
975 }
976
977 /* Close everything up. */
978 fclose(in);
979 fclose(out);
980
981 if (to_copy != 0) {
982 DEBUG(misc, 6, "Extracting %s failed; still %i bytes to copy", filename, (int)to_copy);
983 return false;
984 }
985 }
986
987 DEBUG(misc, 9, " extraction successful");
988 return true;
989 }
990
991 #if defined(WIN32) || defined(WINCE)
992
997 extern void DetermineBasePaths(const char *exe);
998 #else /* defined(WIN32) || defined(WINCE) */
999
1007 static bool ChangeWorkingDirectoryToExecutable(const char *exe)
1008 {
1009 char tmp[MAX_PATH];
1010 strecpy(tmp, exe, lastof(tmp));
1011
1012 bool success = false;
1013 #ifdef WITH_COCOA
1014 char *app_bundle = strchr(tmp, '.');
1015 while (app_bundle != NULL && strncasecmp(app_bundle, ".app", 4) != 0) app_bundle = strchr(&app_bundle[1], '.');
1016
1017 if (app_bundle != NULL) *app_bundle = '\0';
1018 #endif /* WITH_COCOA */
1019 char *s = strrchr(tmp, PATHSEPCHAR);
1020 if (s != NULL) {
1021 *s = '\0';
1022 #if defined(__DJGPP__)
1023 /* If we want to go to the root, we can't use cd C:, but we must use '/' */
1024 if (s > tmp && *(s - 1) == ':') chdir("/");
1025 #endif
1026 if (chdir(tmp) != 0) {
1027 DEBUG(misc, 0, "Directory with the binary does not exist?");
1028 } else {
1029 success = true;
1030 }
1031 }
1032 return success;
1033 }
1034
1045 bool DoScanWorkingDirectory()
1046 {
1047 /* No working directory, so nothing to do. */
1048 if (_searchpaths[SP_WORKING_DIR] == NULL) return false;
1049
1050 /* Working directory is root, so do nothing. */
1051 if (strcmp(_searchpaths[SP_WORKING_DIR], PATHSEP) == 0) return false;
1052
1053 /* No personal/home directory, so the working directory won't be that. */
1054 if (_searchpaths[SP_PERSONAL_DIR] == NULL) return true;
1055
1056 char tmp[MAX_PATH];
1057 seprintf(tmp, lastof(tmp), "%s%s", _searchpaths[SP_WORKING_DIR], PERSONAL_DIR);
1058 AppendPathSeparator(tmp, lastof(tmp));
1059 return strcmp(tmp, _searchpaths[SP_PERSONAL_DIR]) != 0;
1060 }
1061
1066 void DetermineBasePaths(const char *exe)
1067 {
1068 char tmp[MAX_PATH];
1069 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1070 const char *xdg_data_home = xdgDataHome(NULL);
1071 seprintf(tmp, lastof(tmp), "%s" PATHSEP "%s", xdg_data_home,
1072 PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR);
1073 free(xdg_data_home);
1074
1075 AppendPathSeparator(tmp, lastof(tmp));
1076 _searchpaths[SP_PERSONAL_DIR_XDG] = stredup(tmp);
1077 #endif
1078 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2) || !defined(WITH_PERSONAL_DIR)
1079 _searchpaths[SP_PERSONAL_DIR] = NULL;
1080 #else
1081 #ifdef __HAIKU__
1082 BPath path;
1083 find_directory(B_USER_SETTINGS_DIRECTORY, &path);
1084 const char *homedir = stredup(path.Path());
1085 #else
1086 /* getenv is highly unsafe; duplicate it as soon as possible,
1087 * or at least before something else touches the environment
1088 * variables in any way. It can also contain all kinds of
1089 * unvalidated data we rather not want internally. */
1090 const char *homedir = getenv("HOME");
1091 if (homedir != NULL) {
1092 homedir = stredup(homedir);
1093 }
1094
1095 if (homedir == NULL) {
1096 const struct passwd *pw = getpwuid(getuid());
1097 homedir = (pw == NULL) ? NULL : stredup(pw->pw_dir);
1098 }
1099 #endif
1100
1101 if (homedir != NULL) {
1102 ValidateString(homedir);
1103 seprintf(tmp, lastof(tmp), "%s" PATHSEP "%s", homedir, PERSONAL_DIR);
1104 AppendPathSeparator(tmp, lastof(tmp));
1105
1106 _searchpaths[SP_PERSONAL_DIR] = stredup(tmp);
1107 free(homedir);
1108 } else {
1109 _searchpaths[SP_PERSONAL_DIR] = NULL;
1110 }
1111 #endif
1112
1113 #if defined(WITH_SHARED_DIR)
1114 seprintf(tmp, lastof(tmp), "%s", SHARED_DIR);
1115 AppendPathSeparator(tmp, lastof(tmp));
1116 _searchpaths[SP_SHARED_DIR] = stredup(tmp);
1117 #else
1118 _searchpaths[SP_SHARED_DIR] = NULL;
1119 #endif
1120
1121 #if defined(__MORPHOS__) || defined(__AMIGA__)
1122 _searchpaths[SP_WORKING_DIR] = NULL;
1123 #else
1124 if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
1125 AppendPathSeparator(tmp, lastof(tmp));
1126 _searchpaths[SP_WORKING_DIR] = stredup(tmp);
1127 #endif
1128
1129 _do_scan_working_directory = DoScanWorkingDirectory();
1130
1131 /* Change the working directory to that one of the executable */
1132 if (ChangeWorkingDirectoryToExecutable(exe)) {
1133 if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
1134 AppendPathSeparator(tmp, lastof(tmp));
1135 _searchpaths[SP_BINARY_DIR] = stredup(tmp);
1136 } else {
1137 _searchpaths[SP_BINARY_DIR] = NULL;
1138 }
1139
1140 if (_searchpaths[SP_WORKING_DIR] != NULL) {
1141 /* Go back to the current working directory. */
1142 if (chdir(_searchpaths[SP_WORKING_DIR]) != 0) {
1143 DEBUG(misc, 0, "Failed to return to working directory!");
1144 }
1145 }
1146
1147 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2)
1148 _searchpaths[SP_INSTALLATION_DIR] = NULL;
1149 #else
1150 seprintf(tmp, lastof(tmp), "%s", GLOBAL_DATA_DIR);
1151 AppendPathSeparator(tmp, lastof(tmp));
1152 _searchpaths[SP_INSTALLATION_DIR] = stredup(tmp);
1153 #endif
1154 #ifdef WITH_COCOA
1155 extern void cocoaSetApplicationBundleDir();
1156 cocoaSetApplicationBundleDir();
1157 #else
1158 _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL;
1159 #endif
1160 }
1161 #endif /* defined(WIN32) || defined(WINCE) */
1162
1163 const char *_personal_dir;
1164
1171 void DeterminePaths(const char *exe)
1172 {
1173 DetermineBasePaths(exe);
1174
1175 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1176 char config_home[MAX_PATH];
1177
1178 const char *xdg_config_home = xdgConfigHome(NULL);
1179 seprintf(config_home, lastof(config_home), "%s" PATHSEP "%s", xdg_config_home,
1180 PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR);
1181 free(xdg_config_home);
1182
1183 AppendPathSeparator(config_home, lastof(config_home));
1184 #endif
1185
1186 Searchpath sp;
1187 FOR_ALL_SEARCHPATHS(sp) {
1188 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1189 DEBUG(misc, 4, "%s added as search path", _searchpaths[sp]);
1190 }
1191
1192 char *config_dir;
1193 if (_config_file != NULL) {
1194 config_dir = stredup(_config_file);
1195 char *end = strrchr(config_dir, PATHSEPCHAR);
1196 if (end == NULL) {
1197 config_dir[0] = '\0';
1198 } else {
1199 end[1] = '\0';
1200 }
1201 } else {
1202 char personal_dir[MAX_PATH];
1203 if (FioFindFullPath(personal_dir, lastof(personal_dir), BASE_DIR, "openttd.cfg") != NULL) {
1204 char *end = strrchr(personal_dir, PATHSEPCHAR);
1205 if (end != NULL) end[1] = '\0';
1206 config_dir = stredup(personal_dir);
1207 _config_file = str_fmt("%sopenttd.cfg", config_dir);
1208 } else {
1209 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1210 /* No previous configuration file found. Use the configuration folder from XDG. */
1211 config_dir = config_home;
1212 #else
1213 static const Searchpath new_openttd_cfg_order[] = {
1214 SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
1215 };
1216
1217 config_dir = NULL;
1218 for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) {
1219 if (IsValidSearchPath(new_openttd_cfg_order[i])) {
1220 config_dir = stredup(_searchpaths[new_openttd_cfg_order[i]]);
1221 break;
1222 }
1223 }
1224 assert(config_dir != NULL);
1225 #endif
1226 _config_file = str_fmt("%sopenttd.cfg", config_dir);
1227 }
1228 }
1229
1230 DEBUG(misc, 3, "%s found as config directory", config_dir);
1231
1232 _highscore_file = str_fmt("%shs.dat", config_dir);
1233 extern char *_hotkeys_file;
1234 _hotkeys_file = str_fmt("%shotkeys.cfg", config_dir);
1235 extern char *_windows_file;
1236 _windows_file = str_fmt("%swindows.cfg", config_dir);
1237
1238 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1239 if (config_dir == config_home) {
1240 /* We are using the XDG configuration home for the config file,
1241 * then store the rest in the XDG data home folder. */
1242 _personal_dir = _searchpaths[SP_PERSONAL_DIR_XDG];
1243 FioCreateDirectory(_personal_dir);
1244 } else
1245 #endif
1246 {
1247 _personal_dir = config_dir;
1248 }
1249
1250 /* Make the necessary folders */
1251 #if !defined(__MORPHOS__) && !defined(__AMIGA__) && defined(WITH_PERSONAL_DIR)
1252 FioCreateDirectory(config_dir);
1253 if (config_dir != _personal_dir) FioCreateDirectory(_personal_dir);
1254 #endif
1255
1256 DEBUG(misc, 3, "%s found as personal directory", _personal_dir);
1257
1258 static const Subdirectory default_subdirs[] = {
1259 SAVE_DIR, AUTOSAVE_DIR, SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR, SCREENSHOT_DIR
1260 };
1261
1262 for (uint i = 0; i < lengthof(default_subdirs); i++) {
1263 char *dir = str_fmt("%s%s", _personal_dir, _subdirs[default_subdirs[i]]);
1264 FioCreateDirectory(dir);
1265 free(dir);
1266 }
1267
1268 /* If we have network we make a directory for the autodownloading of content */
1269 _searchpaths[SP_AUTODOWNLOAD_DIR] = str_fmt("%s%s", _personal_dir, "content_download" PATHSEP);
1270 #ifdef ENABLE_NETWORK
1271 FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
1272
1273 /* Create the directory for each of the types of content */
1274 const Subdirectory dirs[] = { SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR };
1275 for (uint i = 0; i < lengthof(dirs); i++) {
1276 char *tmp = str_fmt("%s%s", _searchpaths[SP_AUTODOWNLOAD_DIR], _subdirs[dirs[i]]);
1277 FioCreateDirectory(tmp);
1278 free(tmp);
1279 }
1280
1281 extern char *_log_file;
1282 _log_file = str_fmt("%sopenttd.log", _personal_dir);
1283 #else /* ENABLE_NETWORK */
1284 /* If we don't have networking, we don't need to make the directory. But
1285 * if it exists we keep it, otherwise remove it from the search paths. */
1286 if (!FileExists(_searchpaths[SP_AUTODOWNLOAD_DIR])) {
1287 free(_searchpaths[SP_AUTODOWNLOAD_DIR]);
1288 _searchpaths[SP_AUTODOWNLOAD_DIR] = NULL;
1289 }
1290 #endif /* ENABLE_NETWORK */
1291 }
1292
1297 void SanitizeFilename(char *filename)
1298 {
1299 for (; *filename != '\0'; filename++) {
1300 switch (*filename) {
1301 /* The following characters are not allowed in filenames
1302 * on at least one of the supported operating systems: */
1303 case ':': case '\\': case '*': case '?': case '/':
1304 case '<': case '>': case '|': case '"':
1305 *filename = '_';
1306 break;
1307 }
1308 }
1309 }
1310
1319 void *ReadFileToMem(const char *filename, size_t *lenp, size_t maxsize)
1320 {
1321 FILE *in = fopen(filename, "rb");
1322 if (in == NULL) return NULL;
1323
1324 fseek(in, 0, SEEK_END);
1325 size_t len = ftell(in);
1326 fseek(in, 0, SEEK_SET);
1327 if (len > maxsize) {
1328 fclose(in);
1329 return NULL;
1330 }
1331 byte *mem = MallocT<byte>(len + 1);
1332 mem[len] = 0;
1333 if (fread(mem, len, 1, in) != 1) {
1334 fclose(in);
1335 free(mem);
1336 return NULL;
1337 }
1338 fclose(in);
1339
1340 *lenp = len;
1341 return mem;
1342 }
1343
1350 static bool MatchesExtension(const char *extension, const char *filename)
1351 {
1352 if (extension == NULL) return true;
1353
1354 const char *ext = strrchr(filename, extension[0]);
1355 return ext != NULL && strcasecmp(ext, extension) == 0;
1356 }
1357
1367 static uint ScanPath(FileScanner *fs, const char *extension, const char *path, size_t basepath_length, bool recursive)
1368 {
1369 extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
1370
1371 uint num = 0;
1372 struct stat sb;
1373 struct dirent *dirent;
1374 DIR *dir;
1375
1376 if (path == NULL || (dir = ttd_opendir(path)) == NULL) return 0;
1377
1378 while ((dirent = readdir(dir)) != NULL) {
1379 const char *d_name = FS2OTTD(dirent->d_name);
1380 char filename[MAX_PATH];
1381
1382 if (!FiosIsValidFile(path, dirent, &sb)) continue;
1383
1384 seprintf(filename, lastof(filename), "%s%s", path, d_name);
1385
1386 if (S_ISDIR(sb.st_mode)) {
1387 /* Directory */
1388 if (!recursive) continue;
1389 if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue;
1390 if (!AppendPathSeparator(filename, lastof(filename))) continue;
1391 num += ScanPath(fs, extension, filename, basepath_length, recursive);
1392 } else if (S_ISREG(sb.st_mode)) {
1393 /* File */
1394 if (MatchesExtension(extension, filename) && fs->AddFile(filename, basepath_length, NULL)) num++;
1395 }
1396 }
1397
1398 closedir(dir);
1399
1400 return num;
1401 }
1402
1409 static uint ScanTar(FileScanner *fs, const char *extension, TarFileList::iterator tar)
1410 {
1411 uint num = 0;
1412 const char *filename = (*tar).first.c_str();
1413
1414 if (MatchesExtension(extension, filename) && fs->AddFile(filename, 0, (*tar).second.tar_filename)) num++;
1415
1416 return num;
1417 }
1418
1428 uint FileScanner::Scan(const char *extension, Subdirectory sd, bool tars, bool recursive)
1429 {
1430 this->subdir = sd;
1431
1432 Searchpath sp;
1433 char path[MAX_PATH];
1434 TarFileList::iterator tar;
1435 uint num = 0;
1436
1437 FOR_ALL_SEARCHPATHS(sp) {
1438 /* Don't search in the working directory */
1439 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1440
1441 FioAppendDirectory(path, lastof(path), sp, sd);
1442 num += ScanPath(this, extension, path, strlen(path), recursive);
1443 }
1444
1445 if (tars && sd != NO_DIRECTORY) {
1446 FOR_ALL_TARS(tar, sd) {
1447 num += ScanTar(this, extension, tar);
1448 }
1449 }
1450
1451 switch (sd) {
1452 case BASESET_DIR:
1453 num += this->Scan(extension, OLD_GM_DIR, tars, recursive);
1454 FALLTHROUGH;
1455 case NEWGRF_DIR:
1456 num += this->Scan(extension, OLD_DATA_DIR, tars, recursive);
1457 break;
1458
1459 default: break;
1460 }
1461
1462 return num;
1463 }
1464
1473 uint FileScanner::Scan(const char *extension, const char *directory, bool recursive)
1474 {
1475 char path[MAX_PATH];
1476 strecpy(path, directory, lastof(path));
1477 if (!AppendPathSeparator(path, lastof(path))) return 0;
1478 return ScanPath(this, extension, path, strlen(path), recursive);
1479 }