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