1 // cmdlib.c (mostly borrowed from the Q2 source)
2
3 #ifdef _WIN32
4 #include <direct.h>
5 #include <io.h>
6 #else
7 #include <unistd.h>
8 #include <sys/types.h>
9 #include <pwd.h>
10 #if !defined(__sun)
11 #include <fts.h>
12 #endif
13 #endif
14 #include "doomtype.h"
15 #include "cmdlib.h"
16 #include "i_system.h"
17 #include "v_text.h"
18
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <time.h>
22
23 /*
24 progdir will hold the path up to the game directory, including the slash
25
26 f:\quake\
27 /raid/quake/
28
29 gamedir will hold progdir + the game directory (id1, id2, etc)
30
31 */
32
33 FString progdir;
34
35 //==========================================================================
36 //
37 // IsSeperator
38 //
39 // Returns true if the character is a path seperator.
40 //
41 //==========================================================================
42
IsSeperator(int c)43 static inline bool IsSeperator (int c)
44 {
45 if (c == '/')
46 return true;
47 #ifdef WIN32
48 if (c == '\\' || c == ':')
49 return true;
50 #endif
51 return false;
52 }
53
54 //==========================================================================
55 //
56 // FixPathSeperator
57 //
58 // Convert backslashes to forward slashes.
59 //
60 //==========================================================================
61
FixPathSeperator(char * path)62 void FixPathSeperator (char *path)
63 {
64 while (*path)
65 {
66 if (*path == '\\')
67 *path = '/';
68 path++;
69 }
70 }
71
72 //==========================================================================
73 //
74 // copystring
75 //
76 // Replacement for strdup that uses new instead of malloc.
77 //
78 //==========================================================================
79
copystring(const char * s)80 char *copystring (const char *s)
81 {
82 char *b;
83 if (s)
84 {
85 size_t len = strlen (s) + 1;
86 b = new char[len];
87 memcpy (b, s, len);
88 }
89 else
90 {
91 b = new char[1];
92 b[0] = '\0';
93 }
94 return b;
95 }
96
97 //============================================================================
98 //
99 // ncopystring
100 //
101 // If the string has no content, returns NULL. Otherwise, returns a copy.
102 //
103 //============================================================================
104
ncopystring(const char * string)105 char *ncopystring (const char *string)
106 {
107 if (string == NULL || string[0] == 0)
108 {
109 return NULL;
110 }
111 return copystring (string);
112 }
113
114 //==========================================================================
115 //
116 // ReplaceString
117 //
118 // Do not use in new code.
119 //
120 //==========================================================================
121
ReplaceString(char ** ptr,const char * str)122 void ReplaceString (char **ptr, const char *str)
123 {
124 if (*ptr)
125 {
126 if (*ptr == str)
127 return;
128 delete[] *ptr;
129 }
130 *ptr = copystring (str);
131 }
132
133 /*
134 =============================================================================
135
136 MISC FUNCTIONS
137
138 =============================================================================
139 */
140
141
142 //==========================================================================
143 //
144 // Q_filelength
145 //
146 //==========================================================================
147
Q_filelength(FILE * f)148 int Q_filelength (FILE *f)
149 {
150 int pos;
151 int end;
152
153 pos = ftell (f);
154 fseek (f, 0, SEEK_END);
155 end = ftell (f);
156 fseek (f, pos, SEEK_SET);
157
158 return end;
159 }
160
161
162 //==========================================================================
163 //
164 // FileExists
165 //
166 // Returns true if the given path exists and is a readable file.
167 //
168 //==========================================================================
169
FileExists(const char * filename)170 bool FileExists (const char *filename)
171 {
172 struct stat buff;
173
174 // [RH] Empty filenames are never there
175 if (filename == NULL || *filename == 0)
176 return false;
177
178 return stat(filename, &buff) == 0 && !(buff.st_mode & S_IFDIR);
179 }
180
181 //==========================================================================
182 //
183 // DirEntryExists
184 //
185 // Returns true if the given path exists, be it a directory or a file.
186 //
187 //==========================================================================
188
DirEntryExists(const char * pathname)189 bool DirEntryExists(const char *pathname)
190 {
191 if (pathname == NULL || *pathname == 0)
192 return false;
193
194 struct stat info;
195 return stat(pathname, &info) == 0;
196 }
197
198 //==========================================================================
199 //
200 // DefaultExtension -- char array version
201 //
202 // Appends the extension to a pathname if it does not already have one.
203 //
204 //==========================================================================
205
DefaultExtension(char * path,const char * extension)206 void DefaultExtension (char *path, const char *extension)
207 {
208 char *src;
209 //
210 // if path doesn't have a .EXT, append extension
211 // (extension should include the .)
212 //
213 src = path + strlen(path) - 1;
214
215 while (src != path && !IsSeperator(*src))
216 {
217 if (*src == '.')
218 return; // it has an extension
219 src--;
220 }
221
222 strcat (path, extension);
223 }
224
225 //==========================================================================
226 //
227 // DefaultExtension -- FString version
228 //
229 // Appends the extension to a pathname if it does not already have one.
230 //
231 //==========================================================================
232
DefaultExtension(FString & path,const char * extension)233 void DefaultExtension (FString &path, const char *extension)
234 {
235 const char *src = &path[int(path.Len())-1];
236
237 while (src != &path[0] && !IsSeperator(*src))
238 {
239 if (*src == '.')
240 return; // it has an extension
241 src--;
242 }
243
244 path += extension;
245 }
246
247
248 //==========================================================================
249 //
250 // ExtractFilePath
251 //
252 // Returns the directory part of a pathname.
253 //
254 // FIXME: should include the slash, otherwise
255 // backing to an empty path will be wrong when appending a slash
256 //
257 //==========================================================================
258
ExtractFilePath(const char * path)259 FString ExtractFilePath (const char *path)
260 {
261 const char *src;
262
263 src = path + strlen(path) - 1;
264
265 //
266 // back up until a \ or the start
267 //
268 while (src != path && !IsSeperator(*(src-1)))
269 src--;
270
271 return FString(path, src - path);
272 }
273
274 //==========================================================================
275 //
276 // ExtractFileBase
277 //
278 // Returns the file part of a pathname, optionally including the extension.
279 //
280 //==========================================================================
281
ExtractFileBase(const char * path,bool include_extension)282 FString ExtractFileBase (const char *path, bool include_extension)
283 {
284 const char *src, *dot;
285
286 src = path + strlen(path) - 1;
287
288 if (src >= path)
289 {
290 // back up until a / or the start
291 while (src != path && !IsSeperator(*(src-1)))
292 src--;
293
294 // Check for files with drive specification but no path
295 #if defined(_WIN32) || defined(DOS)
296 if (src == path && src[0] != 0)
297 {
298 if (src[1] == ':')
299 src += 2;
300 }
301 #endif
302
303 if (!include_extension)
304 {
305 dot = src;
306 while (*dot && *dot != '.')
307 {
308 dot++;
309 }
310 return FString(src, dot - src);
311 }
312 else
313 {
314 return FString(src);
315 }
316 }
317 return FString();
318 }
319
320
321 //==========================================================================
322 //
323 // ParseHex
324 //
325 //==========================================================================
326
ParseHex(const char * hex)327 int ParseHex (const char *hex)
328 {
329 const char *str;
330 int num;
331
332 num = 0;
333 str = hex;
334
335 while (*str)
336 {
337 num <<= 4;
338 if (*str >= '0' && *str <= '9')
339 num += *str-'0';
340 else if (*str >= 'a' && *str <= 'f')
341 num += 10 + *str-'a';
342 else if (*str >= 'A' && *str <= 'F')
343 num += 10 + *str-'A';
344 else {
345 Printf ("Bad hex number: %s\n",hex);
346 return 0;
347 }
348 str++;
349 }
350
351 return num;
352 }
353
354 //==========================================================================
355 //
356 // ParseNum
357 //
358 //==========================================================================
359
ParseNum(const char * str)360 int ParseNum (const char *str)
361 {
362 if (str[0] == '$')
363 return ParseHex (str+1);
364 if (str[0] == '0' && str[1] == 'x')
365 return ParseHex (str+2);
366 return atol (str);
367 }
368
369 //==========================================================================
370 //
371 // IsNum
372 //
373 // [RH] Returns true if the specified string is a valid decimal number
374 //
375 //==========================================================================
376
IsNum(const char * str)377 bool IsNum (const char *str)
378 {
379 while (*str)
380 {
381 if (((*str < '0') || (*str > '9')) && (*str != '-'))
382 {
383 return false;
384 }
385 str++;
386 }
387 return true;
388 }
389
390 //==========================================================================
391 //
392 // CheckWildcards
393 //
394 // [RH] Checks if text matches the wildcard pattern using ? or *
395 //
396 //==========================================================================
397
CheckWildcards(const char * pattern,const char * text)398 bool CheckWildcards (const char *pattern, const char *text)
399 {
400 if (pattern == NULL || text == NULL)
401 return true;
402
403 while (*pattern)
404 {
405 if (*pattern == '*')
406 {
407 char stop = tolower (*++pattern);
408 while (*text && tolower(*text) != stop)
409 {
410 text++;
411 }
412 if (*text && tolower(*text) == stop)
413 {
414 if (CheckWildcards (pattern, text++))
415 {
416 return true;
417 }
418 pattern--;
419 }
420 }
421 else if (*pattern == '?' || tolower(*pattern) == tolower(*text))
422 {
423 pattern++;
424 text++;
425 }
426 else
427 {
428 return false;
429 }
430 }
431 return (*pattern | *text) == 0;
432 }
433
434 //==========================================================================
435 //
436 // FormatGUID
437 //
438 // [RH] Print a GUID to a text buffer using the standard format.
439 //
440 //==========================================================================
441
FormatGUID(char * buffer,size_t buffsize,const GUID & guid)442 void FormatGUID (char *buffer, size_t buffsize, const GUID &guid)
443 {
444 mysnprintf (buffer, buffsize, "{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
445 (uint32)guid.Data1, guid.Data2, guid.Data3,
446 guid.Data4[0], guid.Data4[1],
447 guid.Data4[2], guid.Data4[3],
448 guid.Data4[4], guid.Data4[5],
449 guid.Data4[6], guid.Data4[7]);
450 }
451
452 //==========================================================================
453 //
454 // myasctime
455 //
456 // [RH] Returns the current local time as ASCII, even if it's too early
457 //
458 //==========================================================================
459
myasctime()460 const char *myasctime ()
461 {
462 time_t clock;
463 struct tm *lt;
464
465 time (&clock);
466 lt = localtime (&clock);
467 if (lt != NULL)
468 {
469 return asctime (lt);
470 }
471 else
472 {
473 return "Pre Jan 01 00:00:00 1970\n";
474 }
475 }
476
477 //==========================================================================
478 //
479 // CreatePath
480 //
481 // Creates a directory including all levels necessary
482 //
483 //==========================================================================
484 #ifdef _WIN32
DoCreatePath(const char * fn)485 void DoCreatePath(const char *fn)
486 {
487 char drive[_MAX_DRIVE];
488 char path[PATH_MAX];
489 char p[PATH_MAX];
490 int i;
491
492 _splitpath(fn,drive,path,NULL,NULL);
493 _makepath(p,drive,path,NULL,NULL);
494 i=(int)strlen(p);
495 if (p[i-1]=='/' || p[i-1]=='\\') p[i-1]=0;
496 if (*path) DoCreatePath(p);
497 _mkdir(p);
498 }
499
CreatePath(const char * fn)500 void CreatePath(const char *fn)
501 {
502 char c = fn[strlen(fn)-1];
503
504 if (c != '\\' && c != '/')
505 {
506 FString name(fn);
507 name += '/';
508 DoCreatePath(name);
509 }
510 else
511 {
512 DoCreatePath(fn);
513 }
514 }
515 #else
CreatePath(const char * fn)516 void CreatePath(const char *fn)
517 {
518 char *copy, *p;
519
520 if (fn[0] == '/' && fn[1] == '\0')
521 {
522 return;
523 }
524 p = copy = strdup(fn);
525 do
526 {
527 p = strchr(p + 1, '/');
528 if (p != NULL)
529 {
530 *p = '\0';
531 }
532 struct stat info;
533 if (stat(copy, &info) == 0)
534 {
535 if (info.st_mode & S_IFDIR)
536 goto exists;
537 }
538 if (mkdir(copy, 0755) == -1)
539 { // failed
540 free(copy);
541 return;
542 }
543 exists: if (p != NULL)
544 {
545 *p = '/';
546 }
547 } while (p);
548 free(copy);
549 }
550 #endif
551
552 //==========================================================================
553 //
554 // strbin -- In-place version
555 //
556 // [RH] Replaces the escape sequences in a string with actual escaped characters.
557 // This operation is done in-place. The result is the new length of the string.
558 //
559 //==========================================================================
560
strbin(char * str)561 int strbin (char *str)
562 {
563 char *start = str;
564 char *p = str, c;
565 int i;
566
567 while ( (c = *p++) ) {
568 if (c != '\\') {
569 *str++ = c;
570 } else {
571 switch (*p) {
572 case 'a':
573 *str++ = '\a';
574 break;
575 case 'b':
576 *str++ = '\b';
577 break;
578 case 'c':
579 *str++ = '\034'; // TEXTCOLOR_ESCAPE
580 break;
581 case 'f':
582 *str++ = '\f';
583 break;
584 case 'n':
585 *str++ = '\n';
586 break;
587 case 't':
588 *str++ = '\t';
589 break;
590 case 'r':
591 *str++ = '\r';
592 break;
593 case 'v':
594 *str++ = '\v';
595 break;
596 case '?':
597 *str++ = '\?';
598 break;
599 case '\n':
600 break;
601 case 'x':
602 case 'X':
603 c = 0;
604 for (i = 0; i < 2; i++)
605 {
606 p++;
607 if (*p >= '0' && *p <= '9')
608 c = (c << 4) + *p-'0';
609 else if (*p >= 'a' && *p <= 'f')
610 c = (c << 4) + 10 + *p-'a';
611 else if (*p >= 'A' && *p <= 'F')
612 c = (c << 4) + 10 + *p-'A';
613 else
614 {
615 p--;
616 break;
617 }
618 }
619 *str++ = c;
620 break;
621 case '0':
622 case '1':
623 case '2':
624 case '3':
625 case '4':
626 case '5':
627 case '6':
628 case '7':
629 c = 0;
630 for (i = 0; i < 3; i++) {
631 c <<= 3;
632 if (*p >= '0' && *p <= '7')
633 c += *p-'0';
634 else
635 break;
636 p++;
637 }
638 *str++ = c;
639 break;
640 default:
641 *str++ = *p;
642 break;
643 }
644 p++;
645 }
646 }
647 *str = 0;
648 return int(str - start);
649 }
650
651 //==========================================================================
652 //
653 // strbin1 -- String-creating version
654 //
655 // [RH] Replaces the escape sequences in a string with actual escaped characters.
656 // The result is a new string.
657 //
658 //==========================================================================
659
strbin1(const char * start)660 FString strbin1 (const char *start)
661 {
662 FString result;
663 const char *p = start;
664 char c;
665 int i;
666
667 while ( (c = *p++) ) {
668 if (c != '\\') {
669 result << c;
670 } else {
671 switch (*p) {
672 case 'a':
673 result << '\a';
674 break;
675 case 'b':
676 result << '\b';
677 break;
678 case 'c':
679 result << '\034'; // TEXTCOLOR_ESCAPE
680 break;
681 case 'f':
682 result << '\f';
683 break;
684 case 'n':
685 result << '\n';
686 break;
687 case 't':
688 result << '\t';
689 break;
690 case 'r':
691 result << '\r';
692 break;
693 case 'v':
694 result << '\v';
695 break;
696 case '?':
697 result << '\?';
698 break;
699 case '\n':
700 break;
701 case 'x':
702 case 'X':
703 c = 0;
704 for (i = 0; i < 2; i++)
705 {
706 p++;
707 if (*p >= '0' && *p <= '9')
708 c = (c << 4) + *p-'0';
709 else if (*p >= 'a' && *p <= 'f')
710 c = (c << 4) + 10 + *p-'a';
711 else if (*p >= 'A' && *p <= 'F')
712 c = (c << 4) + 10 + *p-'A';
713 else
714 {
715 p--;
716 break;
717 }
718 }
719 result << c;
720 break;
721 case '0':
722 case '1':
723 case '2':
724 case '3':
725 case '4':
726 case '5':
727 case '6':
728 case '7':
729 c = 0;
730 for (i = 0; i < 3; i++) {
731 c <<= 3;
732 if (*p >= '0' && *p <= '7')
733 c += *p-'0';
734 else
735 break;
736 p++;
737 }
738 result << c;
739 break;
740 default:
741 result << *p;
742 break;
743 }
744 p++;
745 }
746 }
747 return result;
748 }
749
750 //==========================================================================
751 //
752 // CleanseString
753 //
754 // Does some mild sanity checking on a string: If it ends with an incomplete
755 // color escape, the escape is removed.
756 //
757 //==========================================================================
758
CleanseString(char * str)759 char *CleanseString(char *str)
760 {
761 char *escape = strrchr(str, TEXTCOLOR_ESCAPE);
762 if (escape != NULL)
763 {
764 if (escape[1] == '\0')
765 {
766 *escape = '\0';
767 }
768 else if (escape[1] == '[')
769 {
770 char *close = strchr(escape + 2, ']');
771 if (close == NULL)
772 {
773 *escape = '\0';
774 }
775 }
776 }
777 return str;
778 }
779
780 //==========================================================================
781 //
782 // ExpandEnvVars
783 //
784 // Expands environment variable references in a string. Intended primarily
785 // for use with IWAD search paths in config files.
786 //
787 //==========================================================================
788
ExpandEnvVars(const char * searchpathstring)789 FString ExpandEnvVars(const char *searchpathstring)
790 {
791 static const char envvarnamechars[] =
792 "01234567890"
793 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
794 "_"
795 "abcdefghijklmnopqrstuvwxyz";
796
797 if (searchpathstring == NULL)
798 return FString("");
799
800 const char *dollar = strchr(searchpathstring, '$');
801 if (dollar == NULL)
802 {
803 return FString(searchpathstring);
804 }
805
806 const char *nextchars = searchpathstring;
807 FString out = FString(searchpathstring, dollar - searchpathstring);
808 while ( (dollar != NULL) && (*nextchars != 0) )
809 {
810 size_t length = strspn(dollar + 1, envvarnamechars);
811 if (length != 0)
812 {
813 FString varname = FString(dollar + 1, length);
814 if (stricmp(varname, "progdir") == 0)
815 {
816 out += progdir;
817 }
818 else
819 {
820 char *varvalue = getenv(varname);
821 if ( (varvalue != NULL) && (strlen(varvalue) != 0) )
822 {
823 out += varvalue;
824 }
825 }
826 }
827 else
828 {
829 out += '$';
830 }
831 nextchars = dollar + length + 1;
832 dollar = strchr(nextchars, '$');
833 if (dollar != NULL)
834 {
835 out += FString(nextchars, dollar - nextchars);
836 }
837 }
838 if (*nextchars != 0)
839 {
840 out += nextchars;
841 }
842 return out;
843 }
844
845 //==========================================================================
846 //
847 // NicePath
848 //
849 // Handles paths with leading ~ characters on Unix as well as environment
850 // variable substitution. On Windows, this is identical to ExpandEnvVars.
851 //
852 //==========================================================================
853
NicePath(const char * path)854 FString NicePath(const char *path)
855 {
856 #ifdef _WIN32
857 return ExpandEnvVars(path);
858 #else
859 if (path == NULL || *path == '\0')
860 {
861 return FString("");
862 }
863 if (*path != '~')
864 {
865 return ExpandEnvVars(path);
866 }
867
868 passwd *pwstruct;
869 const char *slash;
870
871 if (path[1] == '/' || path[1] == '\0')
872 { // Get my home directory
873 pwstruct = getpwuid(getuid());
874 slash = path + 1;
875 }
876 else
877 { // Get somebody else's home directory
878 slash = strchr(path, '/');
879 if (slash == NULL)
880 {
881 slash = path + strlen(path);
882 }
883 FString who(path, slash - path);
884 pwstruct = getpwnam(who);
885 }
886 if (pwstruct == NULL)
887 {
888 return ExpandEnvVars(path);
889 }
890 FString where(pwstruct->pw_dir);
891 if (*slash != '\0')
892 {
893 where += ExpandEnvVars(slash);
894 }
895 return where;
896 #endif
897 }
898
899
900 #ifdef _WIN32
901
902 //==========================================================================
903 //
904 // ScanDirectory
905 //
906 //==========================================================================
907
ScanDirectory(TArray<FFileList> & list,const char * dirpath)908 void ScanDirectory(TArray<FFileList> &list, const char *dirpath)
909 {
910 struct _finddata_t fileinfo;
911 intptr_t handle;
912 FString dirmatch;
913
914 dirmatch << dirpath << "*";
915
916 if ((handle = _findfirst(dirmatch, &fileinfo)) == -1)
917 {
918 I_Error("Could not scan '%s': %s\n", dirpath, strerror(errno));
919 }
920 else
921 {
922 do
923 {
924 if (fileinfo.attrib & _A_HIDDEN)
925 {
926 // Skip hidden files and directories. (Prevents SVN bookkeeping
927 // info from being included.)
928 continue;
929 }
930
931 if (fileinfo.attrib & _A_SUBDIR)
932 {
933 if (fileinfo.name[0] == '.' &&
934 (fileinfo.name[1] == '\0' ||
935 (fileinfo.name[1] == '.' && fileinfo.name[2] == '\0')))
936 {
937 // Do not record . and .. directories.
938 continue;
939 }
940
941 FFileList *fl = &list[list.Reserve(1)];
942 fl->Filename << dirpath << fileinfo.name;
943 fl->isDirectory = true;
944 FString newdir = fl->Filename;
945 newdir << "/";
946 ScanDirectory(list, newdir);
947 }
948 else
949 {
950 FFileList *fl = &list[list.Reserve(1)];
951 fl->Filename << dirpath << fileinfo.name;
952 fl->isDirectory = false;
953 }
954 }
955 while (_findnext(handle, &fileinfo) == 0);
956 _findclose(handle);
957 }
958 }
959
960 #elif defined(__sun) || defined(__linux__)
961
962 //==========================================================================
963 //
964 // ScanDirectory
965 // Solaris version
966 //
967 // Given NULL-terminated array of directory paths, create trees for them.
968 //
969 //==========================================================================
970
ScanDirectory(TArray<FFileList> & list,const char * dirpath)971 void ScanDirectory(TArray<FFileList> &list, const char *dirpath)
972 {
973 DIR *directory = opendir(dirpath);
974 if(directory == NULL)
975 return;
976
977 struct dirent *file;
978 while((file = readdir(directory)) != NULL)
979 {
980 if(file->d_name[0] == '.') //File is hidden or ./.. directory so ignore it.
981 continue;
982
983 FFileList *fl = &list[list.Reserve(1)];
984 fl->Filename << dirpath << file->d_name;
985
986 struct stat fileStat;
987 stat(fl->Filename, &fileStat);
988 fl->isDirectory = S_ISDIR(fileStat.st_mode);
989
990 if(fl->isDirectory)
991 {
992 FString newdir = fl->Filename;
993 newdir += "/";
994 ScanDirectory(list, newdir);
995 continue;
996 }
997 }
998
999 closedir(directory);
1000 }
1001
1002 #else
1003
1004 //==========================================================================
1005 //
1006 // ScanDirectory
1007 // 4.4BSD version
1008 //
1009 //==========================================================================
1010
ScanDirectory(TArray<FFileList> & list,const char * dirpath)1011 void ScanDirectory(TArray<FFileList> &list, const char *dirpath)
1012 {
1013 char * const argv[] = {new char[strlen(dirpath)+1], NULL };
1014 memcpy(argv[0], dirpath, strlen(dirpath)+1);
1015 FTS *fts;
1016 FTSENT *ent;
1017
1018 fts = fts_open(argv, FTS_LOGICAL, NULL);
1019 if (fts == NULL)
1020 {
1021 I_Error("Failed to start directory traversal: %s\n", strerror(errno));
1022 delete[] argv[0];
1023 return;
1024 }
1025 while ((ent = fts_read(fts)) != NULL)
1026 {
1027 if (ent->fts_info == FTS_D && ent->fts_name[0] == '.')
1028 {
1029 // Skip hidden directories. (Prevents SVN bookkeeping
1030 // info from being included.)
1031 fts_set(fts, ent, FTS_SKIP);
1032 }
1033 if (ent->fts_info == FTS_D && ent->fts_level == 0)
1034 {
1035 FFileList *fl = &list[list.Reserve(1)];
1036 fl->Filename = ent->fts_path;
1037 fl->isDirectory = true;
1038 }
1039 if (ent->fts_info == FTS_F)
1040 {
1041 // We're only interested in remembering files.
1042 FFileList *fl = &list[list.Reserve(1)];
1043 fl->Filename = ent->fts_path;
1044 fl->isDirectory = false;
1045 }
1046 }
1047 fts_close(fts);
1048 delete[] argv[0];
1049 }
1050 #endif
1051