1// tFile.cpp 
2// 
3// This file contains a class implementation of a file on a disk. It derives from tStream. By passing around tStreams 
4// any user code can be oblivious to the type of stream. It may be a file on disk, it may be a file in a custom 
5// filesystem, it may be a pipe, or even a network resource. 
6// 
7// A path can refer to either a file or a directory. All paths use forward slashes as the separator. Input paths can 
8// use backslashes, but consistency in using forward slashes is advised. Directory path specifications always end with 
9// a trailing slash. Without the trailing separator the path will be interpreted as a file. 
10// 
11// Copyright (c) 2004-2006, 2017, 2019-2024 Tristan Grimmer. 
12// Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby 
13// granted, provided that the above copyright notice and this permission notice appear in all copies. 
14// 
15// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL 
16// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
17// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 
18// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
19// PERFORMANCE OF THIS SOFTWARE. 
20 
21#include <Foundation/tPlatform.h> 
22#ifdef PLATFORM_WINDOWS 
23#include <io.h> 
24#include <Windows.h> 
25#include <shlobj.h> 
26#include <shlwapi.h> 
27#elif defined(PLATFORM_LINUX) 
28#include <limits.h> 
29#include <stdlib.h> 
30#include <unistd.h> 
31#include <sys/stat.h> 
32#include <sys/types.h> 
33#include <pwd.h> 
34#include <fstream> 
35#include <dirent.h> // For fast (C-style) directory entry queries. 
36#endif 
37#include <filesystem> 
38#include "System/tTime.h" 
39#include "System/tFile.h" 
40 
41 
42namespace tSystem 
43
44 // Conversions to windows-standard paths. Backwards slashes. Not seen externally. 
45 void tPathWin (tString& path); // "C:/Hello/There/" -> "C:\Hello\There\". "C:/Hello/There" -> "C:\Hello\There". 
46 void tPathWinDir (tString& path); // "C:/Hello/There/" -> "C:\Hello\There\". "C:/Hello/There" -> "C:\Hello\There\". 
47 void tPathWinFile(tString& path); // "C:/Hello/There/" -> "C:\Hello\There". "C:/Hello/There" -> "C:\Hello\There". 
48 
49 std::time_t tConvertToPosixTime(std::filesystem::file_time_type); 
50 #ifdef PLATFORM_WINDOWS 
51 std::time_t tConvertToPosixTime(FILETIME); 
52 #endif 
53 
54 void tPopulateFileInfo(tFileInfo& fileInfo, const std::filesystem::directory_entry&, const tString& filename); 
55 #ifdef PLATFORM_WINDOWS 
56 void tPopulateFileInfo(tFileInfo& fileInfo, Win32FindData&, const tString& filename); 
57 #endif 
58 
59 const int MaxExtensionsPerFileType = 4
60 struct FileTypeExts 
61
62 const char* Ext[MaxExtensionsPerFileType] = { nullptr, nullptr, nullptr, nullptr }; 
63 bool HasExt(const tString& ext) { for (int e = 0; e < MaxExtensionsPerFileType; e++) if (ext.IsEqualCI(str: Ext[e])) return true; return false; } 
64 }; 
65 
66 // It is important not to specify the array size here so we can static-assert. 
67 extern FileTypeExts FileTypeExtTable[]; 
68 
69 // Standard and Native implementations below. 
70 bool tFindDirs_Stndrd(tList<tStringItem>* dirs, tList<tFileInfo>* infos, const tString& dir, bool hidden); 
71 bool tFindDirs_Native(tList<tStringItem>* dirs, tList<tFileInfo>* infos, const tString& dir, bool hidden); 
72 
73 bool tFindFiles_Stndrd(tList<tStringItem>* files, tList<tFileInfo>* infos, const tString& dir, const tExtensions*, bool hidden); 
74 bool tFindFiles_Native(tList<tStringItem>* files, tList<tFileInfo>* infos, const tString& dir, const tExtensions*, bool hidden); 
75 
76 bool tFindDirsRec_Stndrd(tList<tStringItem>* dirs, tList<tFileInfo>* infos, const tString& dir, bool hidden); 
77 bool tFindDirsRec_Native(tList<tStringItem>* dirs, tList<tFileInfo>* infos, const tString& dir, bool hidden); 
78 
79 bool tFindFilesRec_Stndrd(tList<tStringItem>* files, tList<tFileInfo>* infos, const tString& dir, const tExtensions*, bool hidden); 
80 bool tFindFilesRec_Native(tList<tStringItem>* files, tList<tFileInfo>* infos, const tString& dir, const tExtensions*, bool hidden); 
81
82 
83 
84void tSystem::tPathStd(tString& path
85
86 path.Remove(rem: '"'); 
87 path.Replace(search: '\\', replace: '/'); 
88 bool network = (path.Left(count: 2) == "//"); 
89 if (network
90
91 path[0] = '\\'; path[1] = '\\'
92 int shareSep = path.FindChar(c: '/'); 
93 if (shareSep != -1
94 path[shareSep] = '\\'
95
96
97 
98 
99void tSystem::tPathStdDir(tString& path
100
101 tPathStd(path); 
102 if (path[path.Length() - 1] != '/'
103 path += "/"
104
105 
106 
107void tSystem::tPathStdFile(tString& path
108
109 tPathStd(path); 
110 if (path[path.Length()-1] == '/'
111 path.RemoveLast(); 
112
113 
114 
115inline void tSystem::tPathWin(tString& path
116
117 path.Replace(search: '/', replace: '\\'); 
118
119 
120 
121inline void tSystem::tPathWinDir(tString& path
122
123 tPathWin(path); 
124 if (path[path.Length() - 1] != '\\'
125 path += "\\"
126
127 
128 
129inline void tSystem::tPathWinFile(tString& path
130
131 tPathWin(path); 
132 if (path[path.Length()-1] == '\\'
133 path.RemoveLast(); 
134
135 
136 
137int tSystem::tGetFileSize(tFileHandle handle
138
139 if (!handle
140 return 0
141 
142 tFileSeek(handle, offsetBytes: 0, tSeekOrigin::End); 
143 int fileSize = tFileTell(handle); 
144 
145 tFileSeek(handle, offsetBytes: 0, tSeekOrigin::Beginning); // Go back to beginning. 
146 return fileSize
147
148 
149 
150int tSystem::tFileSeek(tFileHandle handle, int offsetBytes, tSeekOrigin seekOrigin
151
152 int origin = SEEK_SET
153 switch (seekOrigin
154
155 case tSeekOrigin::Beginning
156 origin = SEEK_SET
157 break
158 
159 case tSeekOrigin::Current
160 origin = SEEK_CUR
161 break
162 
163 case tSeekOrigin::End
164 origin = SEEK_END
165 break
166
167  
168 return fseek(stream: handle, off: long(offsetBytes), whence: origin); 
169
170 
171 
172tString tSystem::tGetFileFullName(const tString& file
173
174 tString filename(file); 
175  
176 #if defined(PLATFORM_WINDOWS) 
177 tPathWin(filename); 
178 char ret[_MAX_PATH + 1]; 
179 _fullpath(ret, file.Chr(), _MAX_PATH); 
180 tString retStr(ret); 
181 tPathStd(retStr); 
182  
183 #else 
184 tPathStd(path&: filename); 
185 char ret[PATH_MAX + 1]; 
186 ret[0] = '\0'
187 if (!filename.IsEmpty()) 
188 realpath(name: filename.Chr(), resolved: ret); 
189 tString retStr(ret); 
190 #endif 
191 
192 return retStr
193
194 
195 
196tString tSystem::tGetFileName(const tString& file
197
198 tString retStr(file); 
199 tPathStd(path&: retStr); 
200 if (retStr.FindChar(c: '/') != -1
201 return retStr.Right(marker: '/'); 
202 return retStr
203
204 
205 
206tString tSystem::tGetFileBaseName(const tString& file
207
208 tString r = tGetFileName(file); 
209 if (r.FindChar(c: '.') != -1
210 return r.Left(marker: '.'); 
211 return r
212
213 
214 
215tString tSystem::tGetSimplifiedPath(const tString& path, bool forceTreatAsDir
216
217 tString pth = path
218 tPathStd(path&: pth); 
219 
220 // We do support filenames at the end. However, if the name ends with a "." (or "..") we 
221 // know it is a folder and so add a trailing "/". 
222 if (pth[pth.Length()-1] == '.'
223 pth += "/"
224 
225 if (forceTreatAsDir && (pth[pth.Length()-1] != '/')) 
226 pth += "/"
227 
228 if (tIsDrivePath(path: pth)) 
229
230 if ((pth[0] >= 'a') && (pth[0] <= 'z')) 
231 pth[0] = 'A' + (pth[0] - 'a'); 
232
233 
234 // First we'll replace any "../" strings with "|". Note that pipe indicators are not allowed 
235 // in filenames so we can safely use them. 
236 int numUps = pth.Replace(search: "../", replace: "|"); 
237 
238 // Now we can remove any "./" strings since all that's left will be up-directory markers. 
239 pth.Remove(rem: "./"); 
240 if (!numUps
241 return pth
242 
243 // We need to preserve leading '..'s so that paths like ../../Hello/There/ will work. 
244 int numLeading = pth.RemoveLeading(theseChars: "|"); 
245 numUps -= numLeading
246 for (int nl = 0; nl < numLeading; nl++) 
247 pth = "../" + pth
248 
249 tString simp
250 for (int i = 0; i < numUps; i++) 
251
252 simp += pth.ExtractLeft(divider: '|'); 
253 simp = tGetUpDir(path: simp); 
254
255 
256 tString res = simp + pth
257 return res
258
259 
260 
261int tSystem::tComparePaths(const tString& pathA, const tString& pathB, bool forceSimplify
262
263 tString pthA = pathA; tPathStd(path&: pthA); 
264 tString pthB = pathB; tPathStd(path&: pthB); 
265 if (forceSimplify
266
267 pthA = tGetSimplifiedPath(path: pthA); 
268 pthB = tGetSimplifiedPath(path: pthB); 
269
270 
271 return tStd::tPstrcmp(a: pthA.Chars(), b: pthB.Chars()); 
272
273 
274 
275bool tSystem::tPathsEqual(const tString& pathA, const tString& pathB, bool forceSimplify
276
277 return (tComparePaths(pathA, pathB, forceSimplify) == 0) ? true : false
278
279 
280 
281tString tSystem::tGetAbsolutePath(const tString& path, const tString& basePath
282
283 tString pth(path); 
284 tPathStd(path&: pth); 
285 if (tIsRelativePath(path: pth)) 
286
287 if (basePath.IsEmpty()) 
288 pth = tGetCurrentDir() + pth
289 else 
290 pth = basePath + pth
291
292 
293 return tGetSimplifiedPath(path: pth); 
294
295 
296 
297tString tSystem::tGetRelativePath(const tString& basePath, const tString& path
298
299 // Below is the platform-agnostic implementation that doesn't use windows-only PathRelativePathTo. Only thing 
300 // plat-specific is no case-sensitivity on windows. First let's standardize both input paths to use the tacent 
301 // standard -- forward slashes and trailing-slash mandatory for directory paths. 
302 tString base(basePath); tPathStdDir(path&: base); 
303 tString full(path); tPathStdDir(path&: full); 
304 
305 // Early exit on trivial paths. 
306 if (full.IsEmpty()) return tString(); 
307 if (base.IsEmpty()) return full
308 
309 // Find a common prefix and extract if from both paths. 
310 int lenBase = base.Length(); 
311 int lenFull = full.Length(); 
312 
313 int last = 0
314 #if defined(PLATFORM_WINDOWS) 
315 // Case insensitive on windows. 
316 while (tStd::tChrlwr(base[last]) == tStd::tChrlwr(full[last])) 
317 #else 
318 while (base[last] == full[last]) 
319 #endif 
320 last++; 
321 
322 // It's ok to call extract with 0 if nothing was the same. It just extracts nothing. 
323 base.ExtractLeft(count: last); 
324 full.ExtractLeft(count: last); 
325 
326 // Now we count how many times we need to go up from base. 
327 tString rel
328 int numDirsUp = base.CountChar(c: '/'); 
329 for (int up = 0; up < numDirsUp; up++) 
330 rel += "../"
331 
332 // And now just append what's left in the full path to get us there. 
333 rel += full
334 return rel
335
336 
337 
338tString tSystem::tGetLinuxPath(const tString& path, const tString& mountPoint
339
340 tString pth(path); 
341 tPathStd(path&: pth); 
342 if (tIsAbsolutePath(path: pth) && (pth.Length() > 1) && (pth[1] == ':') && !mountPoint.IsEmpty()) 
343
344 tString mnt = mountPoint
345 tPathStdDir(path&: mnt); 
346 
347 char drive = tStd::tChrlwr(c: pth[0]); 
348 pth.ExtractLeft(count: 2); 
349 pth = mnt + tString(drive) + pth
350
351 return pth
352
353 
354 
355tString tSystem::tGetDir(const tString& path
356
357 tString ret(path); 
358 tPathStd(path&: ret); 
359 
360 // If string is empty or there is no filename on the end of the path just return what we have. 
361 if (ret.IsEmpty() || (ret[ret.Length()-1] == '/')) 
362 return ret
363 
364 int lastSlash = ret.FindChar(c: '/', reverse: true); 
365 
366 // If there is no path, treat it as if it were a stand-alone file and return the current directory. 
367 if (lastSlash == -1
368 return tString("./"); 
369 
370 // At this point, we know there was a slash and that it isn't the last character, so 
371 // we know we aren't going out of bounds when we insert our string terminator after the slash. 
372 ret.SetLength(length: lastSlash+1); 
373 return ret
374
375 
376 
377tString tSystem::tGetUpDir(const tString& path, int levels
378
379 if (path.IsEmpty()) 
380 return tString(); 
381 
382 tString ret(path); 
383 
384 bool isNetLoc = false
385 tPathStd(path&: ret); 
386 
387 // Can't go up from here. 
388 if (ret == "/"
389 return ret
390 if (tIsDrivePath(path: ret)) 
391
392 if (ret.Length() == 2
393 return ret + "/"
394 if ((ret.Length() == 3) && (ret[2] == '/')) 
395 return ret
396
397 
398 #ifdef PLATFORM_WINDOWS 
399 // Are we a network location starting with two slashes? 
400 if ((ret.Length() >= 2) && (ret[0] == '/') && (ret[1] == '/')) 
401 isNetLoc = true
402 #endif 
403 
404 if (isNetLoc
405
406 ret[0] = '\\'
407 ret[1] = '\\'
408
409 
410 tString upPath = ret
411 upPath.RemoveLast(); 
412 
413 for (int i = 0; i < levels; i++) 
414
415 int lastSlash = upPath.FindChar(c: '/', reverse: true); 
416 
417 if (isNetLoc && (upPath.CountChar(c: '/') == 1)) 
418 lastSlash = -1
419 
420 if (lastSlash == -1
421 return tString(); 
422 
423 upPath.SetLength(length: lastSlash); 
424
425 
426 upPath += "/"
427 
428 if (isNetLoc
429
430 ret[0] = '/'
431 ret[1] = '/'
432
433 return upPath
434
435 
436 
437bool tSystem::tFileExists(const tString& file
438
439 #if defined(PLATFORM_WINDOWS) 
440 tString filename(file); 
441 tPathWin(filename); 
442 
443 int length = filename.Length(); 
444 if (filename[ length - 1 ] == ':'
445 filename += "\\*"
446 
447 uint prevErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX); 
448 
449 Win32FindData fd; 
450 #ifdef TACENT_UTF16_API_CALLS  
451 tStringUTF16 fileUTF16(filename); 
452 WinHandle h = FindFirstFile(fileUTF16.GetLPWSTR(), &fd); 
453 #else 
454 WinHandle h = FindFirstFile(filename.Chr(), &fd); 
455 #endif 
456 SetErrorMode(prevErrorMode); 
457 if (h == INVALID_HANDLE_VALUE) 
458 return false
459 
460 FindClose(h); 
461 if (fd.dwFileAttributes & _A_SUBDIR) 
462 return false
463  
464 return true
465 
466 #else 
467 tString filename(file); 
468 tPathStd(path&: filename); 
469 
470 struct stat statbuf
471 return stat(file: filename.Chr(), buf: &statbuf) == 0
472 
473 #endif 
474
475 
476 
477bool tSystem::tDirExists(const tString& dir
478
479 if (dir.IsEmpty()) 
480 return false
481  
482 tString dirname = dir
483  
484 #if defined(PLATFORM_WINDOWS) 
485 tPathWinFile(dirname); 
486 
487 // Can't quite remember what the * does. Needs testing. 
488 int length = dirname.Length(); 
489 if (dirname[ length - 1 ] == ':'
490 dirname += "\\*"
491 
492 uint prevErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX); 
493 
494 Win32FindData fd; 
495 #ifdef TACENT_UTF16_API_CALLS 
496 tStringUTF16 dirUTF16(dirname); 
497 WinHandle h = FindFirstFile(dirUTF16.GetLPWSTR(), &fd); 
498 #else 
499 WinHandle h = FindFirstFile(dirname.Chr(), &fd); 
500 #endif 
501 
502 SetErrorMode(prevErrorMode); 
503 if (h == INVALID_HANDLE_VALUE) 
504 return false
505 
506 FindClose(h); 
507 if (fd.dwFileAttributes & _A_SUBDIR) 
508 return true
509 
510 return false
511 
512 #else 
513 tPathStdFile(path&: dirname); 
514 std::filesystem::file_status fstat = std::filesystem::status(p: dirname.Chr()); 
515 
516 return std::filesystem::is_directory(s: fstat); 
517 #endif 
518
519 
520 
521int tSystem::tGetFileSize(const tString& file
522
523 if (file.IsEmpty()) 
524 return 0
525 
526 #ifdef PLATFORM_WINDOWS 
527 tString filename(file); 
528 tPathWin(filename); 
529 uint prevErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX); 
530 
531 Win32FindData fd; 
532 #ifdef TACENT_UTF16_API_CALLS 
533 tStringUTF16 fileUTF16(file); 
534 WinHandle h = FindFirstFile(fileUTF16.GetLPWSTR(), &fd); 
535 #else 
536 WinHandle h = FindFirstFile(filename.Chr(), &fd); 
537 #endif 
538 
539 // If file doesn't exist, h will be invalid. 
540 if (h == INVALID_HANDLE_VALUE) 
541
542 SetErrorMode(prevErrorMode); 
543 return 0
544
545 
546 FindClose(h); 
547 SetErrorMode(prevErrorMode); 
548 return fd.nFileSizeLow; 
549 #else 
550 
551 tFileHandle handle = tOpenFile(file, mode: "rb"); 
552 int size = tGetFileSize(handle); 
553 tCloseFile(f: handle); 
554 
555 return size
556 #endif 
557
558 
559 
560bool tSystem::tIsReadOnly(const tString& path
561
562 tString pathname(path); 
563 
564 #if defined(PLATFORM_WINDOWS) 
565 tPathWinFile(pathname); 
566 
567 // The docs for this should be clearer! GetFileAttributes returns INVALID_FILE_ATTRIBUTES if it 
568 // fails. Rather dangerously, and undocumented, INVALID_FILE_ATTRIBUTES has a value of 0xFFFFFFFF. 
569 // This means that all attribute are apparently true! This is very lame. Thank goodness there aren't 
570 // 32 possible attributes, or there could be real problems. Too bad it didn't just return 0 on error... 
571 // especially since they specifically have a FILE_ATTRIBUTES_NORMAL flag that is non-zero! 
572 #ifdef TACENT_UTF16_API_CALLS 
573 tStringUTF16 path16(pathname); 
574 ulong attribs = GetFileAttributes(path16.GetLPWSTR()); 
575 #else 
576 ulong attribs = GetFileAttributes(pathname.Chr()); 
577 #endif 
578 if (attribs == INVALID_FILE_ATTRIBUTES) 
579 return false
580 
581 return (attribs & FILE_ATTRIBUTE_READONLY) ? true : false
582 
583 #else 
584 tPathStd(path&: pathname); 
585 
586 struct stat st
587 int errCode = stat(file: pathname.Chr(), buf: &st); 
588 if (errCode != 0
589 return false
590 
591 bool w = (st.st_mode & S_IWUSR) ? true : false
592 bool r = (st.st_mode & S_IRUSR) ? true : false
593 return r && !w
594 
595 #endif 
596
597 
598 
599bool tSystem::tSetReadOnly(const tString& path, bool readOnly
600
601 tString pathname(path); 
602 
603 #if defined(PLATFORM_WINDOWS) 
604 tPathWinFile(pathname); 
605 
606 #ifdef TACENT_UTF16_API_CALLS 
607 tStringUTF16 path16(pathname); 
608 ulong attribs = GetFileAttributes(path16.GetLPWSTR()); 
609 if (attribs == INVALID_FILE_ATTRIBUTES) 
610 return false
611 if (!(attribs & FILE_ATTRIBUTE_READONLY) && readOnly) 
612 SetFileAttributes(path16.GetLPWSTR(), attribs | FILE_ATTRIBUTE_READONLY); 
613 else if ((attribs & FILE_ATTRIBUTE_READONLY) && !readOnly) 
614 SetFileAttributes(path16.GetLPWSTR(), attribs & ~FILE_ATTRIBUTE_READONLY); 
615 attribs = GetFileAttributes(path16.GetLPWSTR()); 
616 
617 #else 
618 ulong attribs = GetFileAttributes(pathname.Chr()); 
619 if (attribs == INVALID_FILE_ATTRIBUTES) 
620 return false
621 if (!(attribs & FILE_ATTRIBUTE_READONLY) && readOnly) 
622 SetFileAttributes(pathname.Chr(), attribs | FILE_ATTRIBUTE_READONLY); 
623 else if ((attribs & FILE_ATTRIBUTE_READONLY) && !readOnly) 
624 SetFileAttributes(pathname.Chr(), attribs & ~FILE_ATTRIBUTE_READONLY); 
625 attribs = GetFileAttributes(pathname.Chr()); 
626 #endif 
627 
628 if (attribs == INVALID_FILE_ATTRIBUTES) 
629 return false
630 
631 if (!!(attribs & FILE_ATTRIBUTE_READONLY) == readOnly) 
632 return true
633 
634 return false
635 
636 #else 
637 tPathStd(path&: pathname); 
638  
639 struct stat st
640 int errCode = stat(file: pathname.Chr(), buf: &st); 
641 if (errCode != 0
642 return false
643  
644 uint32 permBits = st.st_mode
645 
646 // Set user R and clear user w. Leave rest unchanged. 
647 permBits |= S_IRUSR
648 permBits &= ~S_IWUSR
649 errCode = chmod(file: pathname.Chr(), mode: permBits); 
650  
651 return (errCode == 0); 
652 
653 #endif 
654
655 
656 
657bool tSystem::tIsHidden(const tString& path
658
659 if (path.IsEmpty()) 
660 return false
661 
662 #if defined(PLATFORM_LINUX) 
663 // In Linux it's all based on whether the filename starts with a dot. We ignore files called "." or ".." 
664 if (tIsFile(path)) 
665
666 tString fileName = tGetFileName(file: path); 
667 if ((fileName != ".") && (fileName != "..") && (fileName[0] == '.')) 
668 return true
669
670 else 
671
672 tString dirName = path
673 dirName.RemoveLast(); 
674 dirName = tGetFileName(file: dirName); 
675 if ((dirName != ".") && (dirName != "..") && (dirName[0] == '.')) 
676 return true
677
678 return false
679 
680 #elif defined(PLATFORM_WINDOWS) 
681 // In windows it's all based on the attribute. 
682 tString pathName(path); 
683 tPathWinFile(pathName); 
684 
685 #ifdef TACENT_UTF16_API_CALLS 
686 tStringUTF16 pathName16(pathName); 
687 ulong attribs = GetFileAttributes(pathName16.GetLPWSTR()); 
688 #else 
689 ulong attribs = GetFileAttributes(pathName.Chr()); 
690 #endif 
691 if (attribs == INVALID_FILE_ATTRIBUTES) 
692 return false
693 
694 return (attribs & FILE_ATTRIBUTE_HIDDEN) ? true : false
695 
696 #else 
697 return false
698 
699 #endif 
700
701 
702 
703#if defined(PLATFORM_WINDOWS) 
704bool tSystem::tSetHidden(const tString& path, bool hidden) 
705
706 tString pth(path); 
707 tPathWinFile(pth); 
708 
709 #ifdef TACENT_UTF16_API_CALLS 
710 tStringUTF16 pth16(pth); 
711 ulong attribs = GetFileAttributes(pth16.GetLPWSTR()); 
712 if (attribs == INVALID_FILE_ATTRIBUTES) 
713 return false
714 if (!(attribs & FILE_ATTRIBUTE_HIDDEN) && hidden) 
715 SetFileAttributes(pth16.GetLPWSTR(), attribs | FILE_ATTRIBUTE_HIDDEN); 
716 else if ((attribs & FILE_ATTRIBUTE_HIDDEN) && !hidden) 
717 SetFileAttributes(pth16.GetLPWSTR(), attribs & ~FILE_ATTRIBUTE_HIDDEN); 
718 attribs = GetFileAttributes(pth16.GetLPWSTR()); 
719 
720 #else 
721 ulong attribs = GetFileAttributes(pth.Chr()); 
722 if (attribs == INVALID_FILE_ATTRIBUTES) 
723 return false
724 if (!(attribs & FILE_ATTRIBUTE_HIDDEN) && hidden) 
725 SetFileAttributes(pth.Chr(), attribs | FILE_ATTRIBUTE_HIDDEN); 
726 else if ((attribs & FILE_ATTRIBUTE_HIDDEN) && !hidden) 
727 SetFileAttributes(pth.Chr(), attribs & ~FILE_ATTRIBUTE_HIDDEN); 
728 attribs = GetFileAttributes(pth.Chr()); 
729 #endif 
730 
731 if (attribs == INVALID_FILE_ATTRIBUTES) 
732 return false
733 
734 if (!!(attribs & FILE_ATTRIBUTE_HIDDEN) == hidden) 
735 return true
736 
737 return false
738
739 
740 
741bool tSystem::tIsSystem(const tString& file) 
742
743 tString filename(file); 
744 tPathWinFile(filename); 
745 
746 #ifdef TACENT_UTF16_API_CALLS 
747 tStringUTF16 filename16(filename); 
748 ulong attribs = GetFileAttributes(filename16.GetLPWSTR()); 
749 #else 
750 ulong attribs = GetFileAttributes(filename.Chr()); 
751 #endif 
752 
753 if (attribs == INVALID_FILE_ATTRIBUTES) 
754 return false
755 
756 return (attribs & FILE_ATTRIBUTE_SYSTEM) ? true : false
757
758 
759 
760bool tSystem::tSetSystem(const tString& file, bool system) 
761
762 tString filename(file); 
763 tPathWinFile(filename); 
764 
765 #ifdef TACENT_UTF16_API_CALLS 
766 tStringUTF16 file16(filename); 
767 ulong attribs = GetFileAttributes(file16.GetLPWSTR()); 
768 if (attribs == INVALID_FILE_ATTRIBUTES) 
769 return false
770 if (!(attribs & FILE_ATTRIBUTE_SYSTEM) && system) 
771 SetFileAttributes(file16.GetLPWSTR(), attribs | FILE_ATTRIBUTE_SYSTEM); 
772 else if ((attribs & FILE_ATTRIBUTE_SYSTEM) && !system) 
773 SetFileAttributes(file16.GetLPWSTR(), attribs & ~FILE_ATTRIBUTE_SYSTEM); 
774 attribs = GetFileAttributes(file16.GetLPWSTR()); 
775 
776 #else 
777 ulong attribs = GetFileAttributes(filename.Chr()); 
778 if (attribs == INVALID_FILE_ATTRIBUTES) 
779 return false
780 if (!(attribs & FILE_ATTRIBUTE_SYSTEM) && system) 
781 SetFileAttributes(filename.Chr(), attribs | FILE_ATTRIBUTE_SYSTEM); 
782 else if ((attribs & FILE_ATTRIBUTE_SYSTEM) && !system) 
783 SetFileAttributes(filename.Chr(), attribs & ~FILE_ATTRIBUTE_SYSTEM); 
784 attribs = GetFileAttributes(filename.Chr()); 
785 #endif 
786 
787 if (attribs == INVALID_FILE_ATTRIBUTES) 
788 return false
789 
790 if (!!(attribs & FILE_ATTRIBUTE_SYSTEM) == system) 
791 return true
792 
793 return false
794
795 
796 
797bool tSystem::tDriveExists(const tString& driveName) 
798
799 tString drive = driveName; 
800 drive.ToUpper(); 
801 
802 char driveLet = drive[0]; 
803 if ((driveLet > 'Z') || (driveLet < 'A')) 
804 return false
805 
806 ulong driveBits = GetLogicalDrives(); 
807 if (driveBits & (0x00000001 << (driveLet-'A'))) 
808 return true
809 
810 return false
811
812#endif // PLATFORM_WINDOWS 
813 
814 
815bool tSystem::tIsFileNewer(const tString& filea, const tString& fileb
816
817 #if defined(PLATFORM_WINDOWS) 
818 tString fileA(filea); 
819 tPathWin(fileA); 
820 
821 tString fileB(fileb); 
822 tPathWin(fileB); 
823 
824 Win32FindData fd; 
825 #ifdef TACENT_UTF16_API_CALLS 
826 tStringUTF16 fileA16(fileA); 
827 WinHandle h = FindFirstFile(fileA16.GetLPWSTR(), &fd); 
828 #else 
829 WinHandle h = FindFirstFile(fileA.Chr(), &fd); 
830 #endif 
831 if (h == INVALID_HANDLE_VALUE) 
832 throw tFileError("Invalid file handle for file: " + fileA); 
833 
834 WinFileTime timeA = fd.ftLastWriteTime; 
835 FindClose(h); 
836 
837 #ifdef TACENT_UTF16_API_CALLS 
838 tStringUTF16 fileB16(fileB); 
839 h = FindFirstFile(fileB16.GetLPWSTR(), &fd); 
840 #else 
841 h = FindFirstFile(fileB.Chr(), &fd); 
842 #endif 
843 if (h == INVALID_HANDLE_VALUE) 
844 throw tFileError("Invalid file handle for file: " + fileB); 
845 
846 WinFileTime timeB = fd.ftLastWriteTime; 
847 FindClose(h); 
848 
849 if (CompareFileTime(&timeA, &timeB) > 0
850 return true
851 
852 #elif defined(PLAYFORM_LINUX) 
853 tToDo("Implement tISFileNewer."); 
854 
855 #endif 
856 return false
857
858 
859 
860bool tSystem::tFilesIdentical(const tString& fileA, const tString& fileB
861
862 auto localCloseFiles = [](tFileHandle a, tFileHandle b
863
864 tCloseFile(f: a); 
865 tCloseFile(f: b); 
866 }; 
867 
868 tFileHandle fa = tOpenFile(file: fileA, mode: "rb"); 
869 tFileHandle fb = tOpenFile(file: fileB, mode: "rb"); 
870 if (!fa || !fb
871
872 localCloseFiles(fa, fb); 
873 return false
874
875 
876 int faSize = tGetFileSize(handle: fa); 
877 int fbSize = tGetFileSize(handle: fb); 
878 if (faSize != fbSize
879
880 localCloseFiles(fa, fb); 
881 return false
882
883 
884 uint8* bufA = new uint8[faSize]; 
885 uint8* bufB = new uint8[fbSize]; 
886 
887 int numReadA = tReadFile(handle: fa, buffer: bufA, sizeBytes: faSize); 
888 int numReadB = tReadFile(handle: fb, buffer: bufB, sizeBytes: fbSize); 
889 tAssert((faSize + fbSize) == (numReadA + numReadB)); 
890 
891 for (int i = 0; i < faSize; i++) 
892
893 if (bufA[i] != bufB[i]) 
894
895 localCloseFiles(fa, fb); 
896 delete[] bufA
897 delete[] bufB
898 return false
899
900
901 
902 localCloseFiles(fa, fb); 
903 delete[] bufA
904 delete[] bufB
905 return true
906
907 
908 
909bool tSystem::tCopyFile(const tString& destFile, const tString& srcFile, bool overWriteReadOnly
910
911 #if defined(PLATFORM_WINDOWS) 
912 
913 #ifdef TACENT_UTF16_API_CALLS 
914 tStringUTF16 src16(srcFile); 
915 tStringUTF16 dest16(destFile); 
916 int success = ::CopyFile(src16.GetLPWSTR(), dest16.GetLPWSTR(), 0); 
917 
918 #else 
919 int success = ::CopyFile(srcFile.Chr(), destFile.Chr(), 0); 
920 #endif 
921 
922 if (!success && overWriteReadOnly) 
923
924 tSetReadOnly(destFile, false); 
925 #ifdef TACENT_UTF16_API_CALLS 
926 success = ::CopyFile(src16.GetLPWSTR(), dest16.GetLPWSTR(), 0); 
927 #else 
928 success = ::CopyFile(srcFile.Chr(), destFile.Chr(), 0); 
929 #endif 
930
931 return success ? true : false
932 
933 #else 
934 std::filesystem::path pathFrom(srcFile.Chr()); 
935 std::filesystem::path pathTo(destFile.Chr()); 
936 bool success = std::filesystem::copy_file(from: pathFrom, to: pathTo); 
937 if (!success && overWriteReadOnly
938
939 tSetReadOnly(path: destFile, readOnly: false); 
940 success = std::filesystem::copy_file(from: pathFrom, to: pathTo); 
941
942  
943 return success
944 
945 #endif 
946
947 
948 
949bool tSystem::tRenameFile(const tString& dir, const tString& oldPathName, const tString& newPathName
950
951 #if defined(PLATFORM_WINDOWS) 
952 tString fullOldName = dir + oldPathName; 
953 tPathWin(fullOldName); 
954 
955 tString fullNewName = dir + newPathName; 
956 tPathWin(fullNewName); 
957 
958 #ifdef TACENT_UTF16_API_CALLS 
959 tStringUTF16 fullOldName16(fullOldName); 
960 tStringUTF16 fullNewName16(fullNewName); 
961 int success = ::MoveFile(fullOldName16.GetLPWSTR(), fullNewName16.GetLPWSTR()); 
962 
963 #else 
964 int success = ::MoveFile(fullOldName.Chr(), fullNewName.Chr()); 
965 #endif 
966 return success ? true : false
967 
968 #else 
969 tString fullOldName = dir + oldPathName
970 tPathStd(path&: fullOldName); 
971 std::filesystem::path oldp(fullOldName.Chr()); 
972 
973 tString fullNewName = dir + newPathName
974 tPathStd(path&: fullNewName); 
975 std::filesystem::path newp(fullNewName.Chr()); 
976 
977 std::error_code ec
978 std::filesystem::rename(from: oldp, to: newp, ec&: ec); 
979 return !bool(ec); 
980 
981 #endif 
982
983 
984 
985bool tSystem::tCreateFile(const tString& file
986
987 tFileHandle f = tOpenFile(file: file.Chr(), mode: "wt"); 
988 if (!f
989 return false
990 
991 tCloseFile(f); 
992 return true
993
994 
995 
996bool tSystem::tCreateFile(const tString& file, const tString& contents
997
998 uint32 len = contents.Length(); 
999 return tCreateFile(file, data: (uint8*)contents.Chr(), length: len); 
1000
1001 
1002 
1003bool tSystem::tCreateFile(const tString& file, uint8* data, int length
1004
1005 tFileHandle dst = tOpenFile(file: file.Chr(), mode: "wb"); 
1006 if (!dst
1007 return false
1008 
1009 // Sometimes this needs to be done, for some mysterious reason. 
1010 tFileSeek(handle: dst, offsetBytes: 0, seekOrigin: tSeekOrigin::Beginning); 
1011 
1012 // Write data and close file. 
1013 int numWritten = tWriteFile(handle: dst, buffer: data, sizeBytes: length); 
1014 tCloseFile(f: dst); 
1015 
1016 // Make sure it was created and an appropriate amount of bytes were written. 
1017 bool verify = tFileExists(file); 
1018 return verify && (numWritten >= length); 
1019
1020 
1021 
1022bool tSystem::tCreateFile(const tString& file, char8_t* data, int length, bool writeBOM
1023
1024 tFileHandle dst = tOpenFile(file: file.Chr(), mode: "wb"); 
1025 if (!dst
1026 return false
1027 tFileSeek(handle: dst, offsetBytes: 0, seekOrigin: tSeekOrigin::Beginning); 
1028 if (writeBOM
1029
1030 char8_t bom[4]; 
1031 int bomLen = tStd::tUTF8c(dst: bom, srcPoint: tStd::cCodepoint_BOM); 
1032 tAssert(bomLen == 3); 
1033 int bomWritten = tWriteFile(handle: dst, buffer: bom, length: 3); 
1034 if (bomWritten != bomLen
1035
1036 tCloseFile(f: dst); 
1037 return false
1038
1039
1040 
1041 // Write data and close file. 
1042 int numWritten = tWriteFile(handle: dst, buffer: data, length); 
1043 tCloseFile(f: dst); 
1044 
1045 // Make sure it was created and an appropriate amount of bytes were written. 
1046 bool verify = tFileExists(file); 
1047 return verify && (numWritten >= length); 
1048
1049 
1050 
1051bool tSystem::tCreateFile(const tString& file, char16_t* data, int length, bool writeBOM
1052
1053 tFileHandle dst = tOpenFile(file: file.Chr(), mode: "wb"); 
1054 if (!dst
1055 return false
1056 tFileSeek(handle: dst, offsetBytes: 0, seekOrigin: tSeekOrigin::Beginning); 
1057 if (writeBOM
1058
1059 char16_t bom = char16_t(tStd::cCodepoint_BOM); 
1060 int bomWritten = tWriteFile(handle: dst, buffer: &bom, length: 1); 
1061 if (bomWritten != 1
1062
1063 tCloseFile(f: dst); 
1064 return false
1065
1066
1067 // Write data and close file. 
1068 int numWritten = tWriteFile(handle: dst, buffer: data, length); 
1069 tCloseFile(f: dst); 
1070 
1071 // Make sure it was created and an appropriate amount of bytes were written. 
1072 bool verify = tFileExists(file); 
1073 return verify && (numWritten >= length); 
1074
1075 
1076 
1077bool tSystem::tCreateFile(const tString& file, char32_t* data, int length, bool writeBOM
1078
1079 tFileHandle dst = tOpenFile(file: file.Chr(), mode: "wb"); 
1080 if (!dst
1081 return false
1082 tFileSeek(handle: dst, offsetBytes: 0, seekOrigin: tSeekOrigin::Beginning); 
1083 if (writeBOM
1084
1085 int bomWritten = tWriteFile(handle: dst, buffer: &tStd::cCodepoint_BOM, length: 1); 
1086 if (bomWritten != 1
1087
1088 tCloseFile(f: dst); 
1089 return false
1090
1091
1092 
1093 // Write data and close file. 
1094 int numWritten = tWriteFile(handle: dst, buffer: data, length); 
1095 tCloseFile(f: dst); 
1096 
1097 // Make sure it was created and an appropriate amount of bytes were written. 
1098 bool verify = tFileExists(file); 
1099 return verify && (numWritten >= length); 
1100
1101 
1102 
1103bool tSystem::tDeleteFile(const tString& file, bool deleteReadOnly, bool useRecycleBin
1104
1105 #ifdef PLATFORM_WINDOWS 
1106 tString filename(file); 
1107 tPathWin(filename); 
1108 
1109 #ifdef TACENT_UTF16_API_CALLS 
1110 tStringUTF16 filename16(filename); 
1111 if (deleteReadOnly) 
1112 SetFileAttributes(filename16.GetLPWSTR(), FILE_ATTRIBUTE_NORMAL); 
1113 #else 
1114 if (deleteReadOnly) 
1115 SetFileAttributes(filename.Chr(), FILE_ATTRIBUTE_NORMAL); 
1116 #endif 
1117 
1118 if (!useRecycleBin) 
1119
1120 #ifdef TACENT_UTF16_API_CALLS 
1121 if (DeleteFile(filename16.GetLPWSTR())) 
1122 #else 
1123 if (DeleteFile(filename.Chr())) 
1124 #endif 
1125 return true
1126 else 
1127 return false
1128
1129 else 
1130
1131 tString filenamePlusChar = file + "Z"
1132 #ifdef TACENT_UTF16_API_CALLS 
1133 tStringUTF16 filenameDoubleNull16(filenamePlusChar); 
1134 *(filenameDoubleNull16.Units() + filenameDoubleNull16.Length() - 1) = 0
1135 #else 
1136 tString filenameDoubleNull(filenamePlusChar); 
1137 
1138 // This is ok. tString allows multiple nulls. 
1139 filenameDoubleNull[filenameDoubleNull.Length()-1] = '\0'
1140 #endif 
1141 
1142 SHFILEOPSTRUCT operation; 
1143 tStd::tMemset(&operation, 0, sizeof(operation)); 
1144 operation.wFunc = FO_DELETE; 
1145 #ifdef TACENT_UTF16_API_CALLS 
1146 operation.pFrom = filenameDoubleNull16.GetLPWSTR(); 
1147 #else 
1148 operation.pFrom = filenameDoubleNull.Chr(); 
1149 #endif 
1150 operation.fFlags = FOF_ALLOWUNDO | FOF_NO_UI | FOF_NORECURSION; 
1151 int errCode = SHFileOperation(&operation); 
1152 return errCode ? false : true
1153
1154 
1155 return true
1156 
1157 #else 
1158 if (!deleteReadOnly && tIsReadOnly(path: file)) 
1159 return true
1160 
1161 std::filesystem::path p(file.Chr()); 
1162 
1163 if (useRecycleBin
1164
1165 tString homeDir = tGetHomeDir(); 
1166 tString recycleDir = homeDir + ".local/share/Trash/files/"
1167 if (tDirExists(dir: recycleDir)) 
1168
1169 tString toFile = recycleDir + tGetFileName(file); 
1170 std::filesystem::path toPath(toFile.Chr()); 
1171 std::error_code ec
1172 std::filesystem::rename(from: p, to: toPath, ec&: ec); 
1173 return ec ? false : true
1174
1175 
1176 return false
1177
1178 
1179 return std::filesystem::remove(p: p); 
1180 #endif 
1181
1182 
1183 
1184uint8* tSystem::tLoadFile(const tString& file, uint8* buffer, int* fileSize, bool appendEOF
1185
1186 tFileHandle f = tOpenFile(file: file.Chr(), mode: "rb"); 
1187 if (!f
1188
1189 if (fileSize
1190 *fileSize = 0
1191 return nullptr
1192
1193 
1194 int size = tGetFileSize(handle: f); 
1195 if (fileSize
1196 *fileSize = size
1197 
1198 if (size == 0
1199
1200 // It is perfectly valid to load a file with no data (0 bytes big). 
1201 // In this case we always return 0 even if a non-zero buffer was passed in. 
1202 // The fileSize member will already be set if necessary. 
1203 tCloseFile(f); 
1204 return nullptr
1205
1206 
1207 bool bufferAllocatedHere = false
1208 if (!buffer
1209
1210 int bufSize = appendEOF ? size+1 : size
1211 buffer = new uint8[bufSize]; 
1212 bufferAllocatedHere = true
1213
1214 
1215 int numRead = tReadFile(handle: f, buffer, sizeBytes: size); // Load the entire thing into memory. 
1216 tAssert(numRead == size); 
1217 
1218 if (appendEOF
1219 buffer[numRead] = EOF
1220 
1221 tCloseFile(f); 
1222 return buffer
1223
1224 
1225 
1226bool tSystem::tLoadFile(const tString& file, tString& dst, char convertZeroesTo
1227
1228 if (!tFileExists(file)) 
1229
1230 dst.Clear(); 
1231 return false
1232
1233 
1234 int filesize = tGetFileSize(file); 
1235 if (filesize == 0
1236
1237 dst.Clear(); 
1238 return true
1239
1240 
1241 dst.SetLength(length: filesize, preserve: false); 
1242 uint8* check = tLoadFile(file, buffer: (uint8*)dst.Text()); 
1243 if ((check != (uint8*)dst.Text()) || !check
1244
1245 dst.Clear(); 
1246 return false
1247
1248 
1249 if (convertZeroesTo != '\0'
1250
1251 for (int i = 0; i < filesize; i++) 
1252 if (dst[i] == '\0'
1253 dst[i] = convertZeroesTo
1254
1255 
1256 return true
1257
1258 
1259 
1260uint8* tSystem::tLoadFileHead(const tString& file, int& bytesToRead, uint8* buffer
1261
1262 tFileHandle f = tOpenFile(file, mode: "rb"); 
1263 if (!f
1264
1265 bytesToRead = 0
1266 return buffer
1267
1268 
1269 int size = tGetFileSize(handle: f); 
1270 if (!size
1271
1272 tCloseFile(f); 
1273 bytesToRead = 0
1274 return buffer
1275
1276 
1277 bytesToRead = (size < bytesToRead) ? size : bytesToRead
1278 
1279 bool bufferAllocatedHere = false
1280 if (!buffer
1281
1282 buffer = new uint8[bytesToRead]; 
1283 bufferAllocatedHere = true
1284
1285 
1286 // Load the first bytesToRead into memory. We assume complete failure if the 
1287 // number we asked for was not returned. 
1288 int numRead = tReadFile(handle: f, buffer, sizeBytes: bytesToRead); 
1289 if (numRead != bytesToRead
1290
1291 if (bufferAllocatedHere
1292
1293 delete[] buffer
1294 buffer = 0
1295
1296 
1297 tCloseFile(f); 
1298 bytesToRead = 0
1299 return buffer
1300
1301 
1302 tCloseFile(f); 
1303 return buffer
1304
1305 
1306 
1307tString tSystem::tGetHomeDir() 
1308
1309 tString home
1310 #if defined(PLATFORM_LINUX) 
1311 const char* homeDir = getenv(name: "HOME"); 
1312 if (!homeDir
1313 homeDir = getpwuid(uid: getuid())->pw_dir;  
1314 if (!homeDir
1315 return home
1316 home.Set(homeDir); 
1317 if (home[home.Length()-1] != '/'
1318 home += '/'
1319 
1320 #elif defined(PLATFORM_WINDOWS) 
1321 wchar_t* pathBuffer = nullptr
1322 WinHResult result = SHGetKnownFolderPath(FOLDERID_Profile, 0, 0, &pathBuffer); 
1323 if ((result != S_OK) || !pathBuffer) 
1324 return home; 
1325 home.Set((char16_t*)pathBuffer); 
1326 CoTaskMemFree(pathBuffer); 
1327 tPathStdDir(home); 
1328 #endif 
1329 
1330 return home
1331
1332 
1333 
1334tString tSystem::tGetProgramDir() 
1335
1336 #if defined(PLATFORM_WINDOWS) 
1337 
1338 #ifdef TACENT_UTF16_API_CALLS 
1339 // No need to add one here. Reserving space does it for us. 
1340 tStringUTF16 result16(MAX_PATH); 
1341 // Except for windows XP (which I don't care about TBH), the result is always null-terminated. 
1342 ulong l = GetModuleFileName(0, result16.GetLPWSTR(), MAX_PATH); 
1343 result16.SetLength( tStd::tStrlen(result16.Chars()) ); 
1344 tString result(result16); 
1345 #else 
1346 char resBuf[MAX_PATH+1]; 
1347 ulong l = GetModuleFileName(0, resBuf, MAX_PATH); 
1348 tString result(resBuf); 
1349 #endif 
1350 
1351 tPathStd(result); 
1352 int bi = result.FindChar('/', true); 
1353 tAssert(bi != -1); 
1354 
1355 result.SetLength(bi + 1); 
1356 return result; 
1357 
1358 #elif defined(PLATFORM_LINUX) 
1359 char resBuf[PATH_MAX+1]; 
1360 int numWritten = readlink(path: "/proc/self/exe", buf: resBuf, PATH_MAX); 
1361 if ((numWritten == -1) || (numWritten > PATH_MAX)) 
1362 return tString(); 
1363 resBuf[numWritten] = '\0'
1364 
1365 tString result(resBuf); 
1366 int bi = result.FindChar(c: '/', reverse: true); 
1367 tAssert(bi != -1); 
1368 result.SetLength(length: bi + 1); 
1369 return result
1370 
1371 #else 
1372 return tString(); 
1373 
1374 #endif 
1375
1376 
1377 
1378tString tSystem::tGetProgramPath() 
1379
1380 #if defined(PLATFORM_WINDOWS) 
1381 
1382 #ifdef TACENT_UTF16_API_CALLS 
1383 tStringUTF16 result16(MAX_PATH); 
1384 ulong l = GetModuleFileName(0, result16.GetLPWSTR(), MAX_PATH); 
1385 result16.SetLength( tStd::tStrlen(result16.Chars()) ); 
1386 tString result(result16); 
1387 
1388 #else 
1389 char resBuf[MAX_PATH+1]; 
1390 ulong l = GetModuleFileName(0, resBuf, MAX_PATH); 
1391 tString result(resBuf); 
1392 #endif 
1393 
1394 tPathStd(result); 
1395 return result; 
1396 
1397 #elif defined(PLATFORM_LINUX) 
1398 char resBuf[PATH_MAX+1]; 
1399 int numWritten = readlink(path: "/proc/self/exe", buf: resBuf, PATH_MAX); 
1400 if ((numWritten == -1) || (numWritten > PATH_MAX)) 
1401 return tString(); 
1402 resBuf[numWritten] = '\0'
1403 tString result(resBuf); 
1404 return result
1405 
1406 #else 
1407 return tString(); 
1408 
1409 #endif 
1410
1411 
1412 
1413tString tSystem::tGetCurrentDir() 
1414
1415 #ifdef PLATFORM_WINDOWS 
1416 
1417 #ifdef TACENT_UTF16_API_CALLS 
1418 tStringUTF16 r16(MAX_PATH); 
1419 GetCurrentDirectory(MAX_PATH, r16.GetLPWSTR()); 
1420 r16.SetLength( tStd::tStrlen(r16.Chars()) ); 
1421 tString r(r16); 
1422 
1423 #else 
1424 char rbuf[MAX_PATH+1]; 
1425 GetCurrentDirectory(MAX_PATH, rbuf); 
1426 tString r(rbuf); 
1427 #endif 
1428 
1429 #else 
1430 char rbuf[PATH_MAX+1]; 
1431 getcwd(buf: rbuf, PATH_MAX); 
1432 tString r(rbuf); 
1433 #endif 
1434 
1435 tPathStdDir(path&: r); 
1436 return r
1437
1438 
1439 
1440bool tSystem::tSetCurrentDir(const tString& directory
1441
1442 if (directory.IsEmpty()) 
1443 return false
1444 
1445 tString dir = directory
1446 
1447 #ifdef PLATFORM_WINDOWS 
1448 tPathWin(dir); 
1449 tString cd; 
1450 
1451 // "." and ".." get left alone. 
1452 if ((dir == ".") || (dir == "..")) 
1453
1454 cd = dir; 
1455
1456 else 
1457
1458 if (dir.FindChar(':') != -1
1459 cd = dir; 
1460 else 
1461 cd = ".\\" + dir; 
1462 
1463 if (cd[cd.Length() - 1] != '\\'
1464 cd += "\\"
1465
1466 
1467 // So there is no dialog asking user to insert a floppy. 
1468 uint prevErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS); 
1469 #ifdef TACENT_UTF16_API_CALLS 
1470 tStringUTF16 cd16(cd); 
1471 int success = SetCurrentDirectory(cd16.GetLPWSTR()); 
1472 #else 
1473 int success = SetCurrentDirectory(cd.Chr()); 
1474 #endif 
1475 SetErrorMode(prevErrorMode); 
1476 
1477 return success ? true : false
1478 
1479 #else 
1480 tPathStd(path&: dir); 
1481 int errCode = chdir(path: dir.Chr()); 
1482 return (errCode == 0); 
1483 
1484 #endif 
1485
1486 
1487 
1488#if defined(PLATFORM_WINDOWS) 
1489tString tSystem::tGetWindowsDir() 
1490
1491 #ifdef TACENT_UTF16_API_CALLS 
1492 tStringUTF16 windir16(MAX_PATH); 
1493 GetWindowsDirectory(windir16.GetLPWSTR(), MAX_PATH); 
1494 windir16.SetLength( tStd::tStrlen(windir16.Chars()) ); 
1495 tString windir(windir16); 
1496 #else 
1497 char windirbuf[MAX_PATH+1]; 
1498 GetWindowsDirectory(windirbuf, MAX_PATH); 
1499 tString windir(windirbuf); 
1500 #endif 
1501 
1502 tPathStdDir(windir); 
1503 return windir; 
1504
1505 
1506 
1507tString tSystem::tGetSystemDir() 
1508
1509 #ifdef TACENT_UTF16_API_CALLS 
1510 tStringUTF16 sysdir16(MAX_PATH); 
1511 GetSystemDirectory(sysdir16.GetLPWSTR(), MAX_PATH); 
1512 sysdir16.SetLength( tStd::tStrlen(sysdir16.Chars()) ); 
1513 tString sysdir(sysdir16); 
1514 #else 
1515 char sysdirbuf[MAX_PATH+1]; 
1516 GetSystemDirectory(sysdirbuf, MAX_PATH); 
1517 tString sysdir(sysdirbuf); 
1518 #endif 
1519 
1520 tPathStdDir(sysdir); 
1521 return sysdir; 
1522
1523 
1524 
1525tString tSystem::tGetDesktopDir() 
1526
1527 tString desktop; 
1528 wchar_t* pathBuffer = nullptr
1529 WinHResult result = SHGetKnownFolderPath(FOLDERID_Desktop, 0, 0, &pathBuffer); 
1530 if ((result != S_OK) || !pathBuffer) 
1531 return desktop; 
1532 
1533 desktop.Set((char16_t*)pathBuffer); 
1534 CoTaskMemFree(pathBuffer); 
1535 
1536 tPathStdDir(desktop); 
1537 return desktop; 
1538
1539 
1540 
1541void tSystem::tGetDrives(tList<tStringItem>& drives) 
1542
1543 ulong ad = GetLogicalDrives(); 
1544 
1545 char driveLet = 'A'
1546 for (int i = 0; i < 26; i++) 
1547
1548 if (ad & 0x00000001
1549
1550 tStringItem* drive = new tStringItem(driveLet); 
1551 *drive += ":"
1552 drives.Append(drive); 
1553
1554 
1555 driveLet++; 
1556 ad = ad >> 1
1557
1558
1559 
1560 
1561bool tSystem::tGetDriveInfo(tDriveInfo& driveInfo, const tString& drv, bool getDisplayName, bool getStateVolumeSerial) 
1562
1563 tString drive = drv; 
1564 drive.ToUpper(); 
1565 driveInfo.Clear(); 
1566 
1567 if (drive.Length() == 1) // Assume string was of form "C" 
1568 drive += ":\\"
1569 else if (drive.Length() == 2) // Assume string was of form "C:" 
1570 drive += "\\"
1571 else // Assume string was of form "C:/" or "C:\" 
1572 tPathWin(drive); 
1573 
1574 // At this point drive if of form "C:\". 
1575 driveInfo.Letter = drive.Left(2); 
1576 
1577 #ifdef TACENT_UTF16_API_CALLS 
1578 tStringUTF16 drive16(drive); 
1579 uint driveType = GetDriveType(drive16.GetLPWSTR()); 
1580 #else 
1581 uint driveType = GetDriveType(drive.Chr()); 
1582 #endif 
1583 switch (driveType) 
1584
1585 case DRIVE_NO_ROOT_DIR: 
1586 return false
1587 
1588 case DRIVE_REMOVABLE: 
1589 if ((drive == "A:\\") || (drive == "B:\\")) 
1590 driveInfo.DriveType = tDriveType::Floppy; 
1591 else 
1592 driveInfo.DriveType = tDriveType::Removable; 
1593 break
1594 
1595 case DRIVE_FIXED: 
1596 driveInfo.DriveType = tDriveType::HardDisk; 
1597 break
1598 
1599 case DRIVE_REMOTE: 
1600 driveInfo.DriveType = tDriveType::Network; 
1601 break
1602 
1603 case DRIVE_CDROM: 
1604 driveInfo.DriveType = tDriveType::Optical; 
1605 break
1606 
1607 case DRIVE_RAMDISK: 
1608 driveInfo.DriveType = tDriveType::RamDisk; 
1609 break
1610 
1611 case DRIVE_UNKNOWN: 
1612 default
1613 driveInfo.DriveType = tDriveType::Unknown; 
1614 break
1615
1616 
1617 if (getDisplayName) 
1618
1619 // Here we try getting the name by using the shell api. It should give the 
1620 // same name as seen by windows explorer. 
1621 SHFILEINFO fileInfo; 
1622 fileInfo.szDisplayName[0] = '\0'
1623 SHGetFileInfo 
1624
1625 #ifdef TACENT_UTF16_API_CALLS 
1626 drive16.GetLPWSTR(), 
1627 #else 
1628 drive.Chr(), 
1629 #endif 
1630 0
1631 &fileInfo, 
1632 sizeof(SHFILEINFO), 
1633 SHGFI_DISPLAYNAME 
1634 ); 
1635 #ifdef TACENT_UTF16_API_CALLS 
1636 driveInfo.DisplayName.SetUTF16((char16_t*)fileInfo.szDisplayName); 
1637 #else 
1638 driveInfo.DisplayName = fileInfo.szDisplayName; 
1639 #endif 
1640
1641 
1642 if (getStateVolumeSerial) 
1643
1644 #ifdef TACENT_UTF16_API_CALLS 
1645 tStringUTF16 volumeInfoName(256); 
1646 #else 
1647 tString volumeInfoName(256); 
1648 #endif 
1649 ulong componentLength = 0
1650 ulong flags = 0
1651 ulong serial = 0
1652 int success = GetVolumeInformation 
1653
1654 #ifdef TACENT_UTF16_API_CALLS 
1655 drive16.GetLPWSTR(), 
1656 volumeInfoName.GetLPWSTR(), 
1657 #else 
1658 drive.Chr(), 
1659 volumeInfoName.Txt(), 
1660 #endif 
1661 256
1662 &serial, 
1663 &componentLength, 
1664 &flags, 
1665 0, // File system name not needed. 
1666 0 // Buffer for system name is 0 long. 
1667 ); 
1668 
1669 if (success) 
1670
1671 #ifdef TACENT_UTF16_API_CALLS 
1672 driveInfo.VolumeName.SetUTF16(volumeInfoName.Units()); 
1673 #else 
1674 driveInfo.VolumeName.Set(volumeInfoName.Units()); 
1675 #endif 
1676 driveInfo.SerialNumber = serial; 
1677 driveInfo.DriveState = tDriveState::Ready; 
1678
1679 else 
1680
1681 driveInfo.VolumeName.Clear(); 
1682 driveInfo.SerialNumber = 0
1683 driveInfo.DriveState = tDriveState::NotReady; 
1684
1685
1686 
1687 return true
1688
1689 
1690 
1691bool tSystem::tSetVolumeName(const tString& drv, const tString& newVolumeName) 
1692
1693 tString drive = drv; 
1694 drive.ToUpper(); 
1695 
1696 if (drive.Length() == 1) // Assume string was of form "C" 
1697 drive += ":\\"
1698 else if (drive.Length() == 2) // Assume string was of form "C:" 
1699 drive += "\\"
1700 else // Assume string was of form "C:/" or "C:\" 
1701 tPathWin(drive); 
1702 
1703 #ifdef TACENT_UTF16_API_CALLS 
1704 tStringUTF16 drive16(drive); 
1705 tStringUTF16 newVolumeName16(newVolumeName); 
1706 int success = SetVolumeLabel(drive16.GetLPWSTR(), newVolumeName16.GetLPWSTR()); 
1707 #else 
1708 int success = SetVolumeLabel(drive.Chr(), newVolumeName.Chr()); 
1709 #endif 
1710 
1711 return success ? true : false
1712
1713 
1714 
1715namespace tWindowsShares 
1716
1717 tString tGetDisplayName(LPITEMIDLIST pidl, IShellFolder*, DWORD type); 
1718 void tEnumerateRec(tSystem::tNetworkShareResult&, IShellFolder*, int levels, bool retrieveMachinesWithNoShares); 
1719 LPMALLOC Malloc; 
1720
1721 
1722 
1723tString tWindowsShares::tGetDisplayName(LPITEMIDLIST pidl, IShellFolder* folderInterface, DWORD type) 
1724
1725 STRRET strRet; 
1726 
1727 // Request the string as a char* although Windows will likely ignore the request. 
1728 strRet.uType = STRRET_CSTR; 
1729 
1730 // Call GetDisplayNameOf() to fill in the STRRET structure. 
1731 HRESULT hr = folderInterface->GetDisplayNameOf(pidl, type, &strRet); 
1732 if (!SUCCEEDED(hr)) 
1733 return tString(); 
1734 
1735 // Extract the string based on the value of the uType member of STRRET. 
1736 switch (strRet.uType) 
1737
1738 case STRRET_CSTR: 
1739 return tString(strRet.cStr); 
1740 
1741 case STRRET_WSTR: 
1742 return tString((char16_t*)strRet.pOleStr); 
1743  
1744 case STRRET_OFFSET : 
1745 return tString(((char*)pidl) + strRet.uOffset); 
1746
1747 return tString(); 
1748
1749 
1750 
1751void tWindowsShares::tEnumerateRec(tSystem::tNetworkShareResult& shareResults, IShellFolder* folderInterface, int depth, bool retrieveMachinesWithNoShares) 
1752
1753 LPITEMIDLIST pidl; 
1754 LPENUMIDLIST enumList; 
1755 DWORD enumFlags = SHCONTF_FOLDERS; // Could add | SHCONTF_NONFOLDERS to enumerate non-folders. 
1756 DWORD result = folderInterface->EnumObjects(0, enumFlags, &enumList); 
1757 if (result != NOERROR) 
1758 return
1759 
1760 tString displayName; 
1761 result = enumList->Next(1, &pidl, 0); // Get the pidl for the first item in the folder. 
1762 while (result != S_FALSE) 
1763
1764 int currentDepth = depth; 
1765 if (result != NOERROR) 
1766 break
1767 
1768 // There are a few possible ways to display the result. We use ForParsing. Examlpes: 
1769 // SHGDN_NORMAL -> ShareName (\\MACHINENAME) 
1770 // SHGDN_INFOLDER -> ShareName 
1771 // SHGDN_FORPARSING -> \\MACHINENAME\ShareName 
1772 displayName = tGetDisplayName(pidl, folderInterface, SHGDN_FORPARSING); 
1773 if (!displayName.IsEmpty()) 
1774
1775 tString padStr; 
1776 displayName = padStr + displayName; 
1777 
1778 // If we're at a leaf we need to add our result. 
1779 if ((currentDepth == 1) || retrieveMachinesWithNoShares) 
1780
1781 shareResults.ShareNames.Append(new tStringItem(displayName)); 
1782 shareResults.NumSharesFound++; 
1783
1784
1785 
1786 currentDepth--; 
1787 
1788 // See if this shell item is a folder. 
1789 DWORD attr = SFGAO_FOLDER; 
1790 folderInterface->GetAttributesOf(1, (LPCITEMIDLIST*)&pidl, &attr); 
1791 
1792 // Terminate recursion when depth reaches 0. 
1793 if ((currentDepth > 0) && (attr & SFGAO_FOLDER) == SFGAO_FOLDER) 
1794
1795 LPSHELLFOLDER shellFolder; 
1796 
1797 // Get the IShellFolder for the pidl. 
1798 int res = folderInterface->BindToObject(pidl, 0, IID_IShellFolder, (void**)&shellFolder); 
1799 if (res == NOERROR) 
1800
1801 tEnumerateRec(shareResults, shellFolder, currentDepth, retrieveMachinesWithNoShares); // Recurse. 
1802 shellFolder->Release(); 
1803
1804
1805 Malloc->Free(pidl); 
1806 result = enumList->Next(1, &pidl, 0); // Next pidl. 
1807
1808 
1809 enumList->Release(); 
1810
1811 
1812 
1813int tSystem::tGetNetworkShares(tNetworkShareResult& shareResults, bool retrieveMachinesWithNoShares) 
1814
1815 shareResults.Clear(); 
1816 SHGetMalloc(&tWindowsShares::Malloc); 
1817 
1818 // To enumerate everything you could use the desktop folder. In our case we only want the shares. 
1819 // To do this we use SHGetSpecialFolderLocation with the special class ID specifier CSIDL_NETWORK. 
1820 // LPSHELLFOLDER desktopFolder; SHGetDesktopFolder(&desktopFolder); 
1821 LPITEMIDLIST pidlSystem = nullptr
1822 HRESULT hr = SHGetSpecialFolderLocation(0, CSIDL_NETWORK, &pidlSystem); 
1823 if (!SUCCEEDED(hr)) 
1824
1825 shareResults.RequestComplete = true
1826 return 0
1827
1828 
1829 IShellFolder* shellFolder = nullptr
1830 LPCITEMIDLIST pidlRelative = nullptr
1831  
1832 hr = SHBindToObject(0, pidlSystem, 0, IID_IShellFolder, (void**)&shellFolder); 
1833 if (!SUCCEEDED(hr)) 
1834
1835 CoTaskMemFree(pidlSystem); 
1836 shareResults.RequestComplete = true
1837 return 0
1838
1839 tAssert(shellFolder && pidlSystem); 
1840 
1841 // To get network shares we need to go to a depth of 2. The first level contains the machine names. 
1842 // The second level contains the share names. 
1843 const int depth = 2
1844 tWindowsShares::tEnumerateRec(shareResults, shellFolder, depth, retrieveMachinesWithNoShares); 
1845 
1846 // Free all used memory. 
1847 // Free the IShellFolder for the special folder location (CSIDL_NETWORK). 
1848 // For desktop it would be desktopFolder->Release(); 
1849 shellFolder->Release(); 
1850 CoTaskMemFree(pidlSystem); 
1851 tWindowsShares::Malloc->Release(); 
1852 
1853 shareResults.RequestComplete = true
1854 return shareResults.NumSharesFound; 
1855
1856 
1857 
1858void tSystem::tExplodeShareName(tList<tStringItem>& exploded, const tString& shareName) 
1859
1860 exploded.Empty(); 
1861 tString share(shareName); 
1862 share.ExtractLeft("\\\\"); 
1863 tStd::tExplode(exploded, share, '\\'); 
1864
1865#endif // PLATFORM_WINDOWS 
1866 
1867 
1868// When more than one extension maps to the same filetype (like jpg and jpeg), always put the more common extension 
1869// first in the extensions array. It is important not to specify the array size here so we can static-assert that we 
1870// have entered the correct number of entries. 
1871tSystem::FileTypeExts tSystem::FileTypeExtTable[] = 
1872
1873// Extensions Filetype 
1874 { "tga" }, // TGA 
1875 { "bmp" }, // BMP 
1876 { "qoi" }, // QOI 
1877 { "png" }, // PNG 
1878 { "apng" }, // APNG 
1879 { "gif" }, // GIF 
1880 { "webp" }, // WEBP 
1881 { "xpm" }, // XPM 
1882 { "jpg", "jpeg" }, // JPG 
1883 { "tif", "tiff" }, // TIFF 
1884 { "dds" }, // DDS 
1885 { "ktx" }, // KTX 
1886 { "ktx2" }, // KTX2 
1887 { "pvr" }, // PVR 
1888 { "astc", "atc" }, // ASTC 
1889 { "pkm" }, // PKM 
1890 { "hdr", "rgbe" }, // HDR 
1891 { "exr" }, // EXR 
1892 { "pcx" }, // PCX 
1893 { "wbmp" }, // WBMP 
1894 { "wmf" }, // WMF 
1895 { "jp2" }, // JP2 
1896 { "jpc" }, // JPC 
1897 { "ico" }, // ICO 
1898 { "tac" }, // TAC 
1899 { "cfg" }, // CFG 
1900 { "ini" }, // INI 
1901 { "txt" }, // TXT 
1902}; 
1903tStaticAssert(tNumElements(tSystem::FileTypeExtTable) == int(tSystem::tFileType::NumFileTypes)); 
1904 
1905 
1906tString tSystem::tGetFileExtension(const tString& file
1907
1908 return file.Right(marker: '.'); 
1909
1910 
1911 
1912tSystem::tFileType tSystem::tGetFileTypeFromExtension(const tString& ext
1913
1914 if (ext.IsEmpty()) 
1915 return tFileType::Unknown
1916 
1917 for (int t = 0; t < int(tFileType::NumFileTypes); t++) 
1918 if (FileTypeExtTable[t].HasExt(ext)) 
1919 return tFileType(t); 
1920 
1921 return tFileType::Unknown
1922
1923 
1924 
1925tSystem::tFileType tSystem::tGetFileTypeFromExtension(const char* ext
1926
1927 // tString constructor can handle nullptr. 
1928 return tGetFileTypeFromExtension(ext: tString(ext)); 
1929
1930 
1931 
1932tSystem::tFileType tSystem::tGetFileType(const tString& file
1933
1934 if (file.IsEmpty()) 
1935 return tFileType::Unknown
1936 
1937 tString ext = tGetFileExtension(file); 
1938 return tGetFileTypeFromExtension(ext); 
1939
1940 
1941 
1942void tSystem::tGetExtensions(tList<tStringItem>& extensions, tFileType fileType
1943
1944 if (fileType == tFileType::Invalid
1945 return
1946 
1947 FileTypeExts& exts = FileTypeExtTable[ int(fileType) ]; 
1948 for (int e = 0; e < MaxExtensionsPerFileType; e++) 
1949 if (exts.Ext[e]) 
1950 extensions.Append(item: new tStringItem(exts.Ext[e])); 
1951
1952 
1953 
1954void tSystem::tGetExtension(tList<tStringItem>& extensions, tFileType fileType
1955
1956 if (fileType == tFileType::Unknown
1957 return
1958 
1959 FileTypeExts& exts = FileTypeExtTable[ int(fileType) ]; 
1960 if (exts.Ext[0]) 
1961 extensions.Append(item: new tStringItem(exts.Ext[0])); 
1962
1963 
1964 
1965tString tSystem::tGetExtension(tFileType fileType
1966
1967 if (fileType == tFileType::Unknown
1968 return tString(); 
1969 
1970 FileTypeExts& exts = FileTypeExtTable[ int(fileType) ]; 
1971 
1972 // The tString constructor can handle nullptr. 
1973 return tString(exts.Ext[0]); 
1974
1975 
1976 
1977std::time_t tSystem::tConvertToPosixTime(std::filesystem::file_time_type ftime
1978
1979 using namespace std::chrono
1980 
1981 // This is the new C++20 way, although different compilers have not implemented the same code paths. 
1982 // Using clock_cast seems a bit more portable than something like to_sys which is not implemented for all clocks 
1983 // on some systems (MSVC, for example, chose only to do it for the utc clock. In any case clock_cast is probably 
1984 // the way to go. Example non-portable code: Update: apparently not portable. Need the ifdefs. 
1985 #ifdef PLATFORM_WINDOWS 
1986 auto systemTimePoint = clock_cast<std::chrono::system_clock>(ftime); 
1987 return system_clock::to_time_t(systemTimePoint); 
1988 
1989 #else 
1990 
1991 // This is what we should be using, but couldn't get snap to work with it. Well, actually 
1992 // I could get it compiling by using core22 (it has recent enough compiler) but then there were glx issues.  
1993 // return system_clock::to_time_t(file_clock::to_sys(ftime)); 
1994 
1995 // This is the _old_ way. 
1996 auto sctp = time_point_cast<system_clock::duration>(t: ftime - std::filesystem::file_time_type::clock::now() + system_clock::now()); 
1997 return system_clock::to_time_t(t: sctp); 
1998 #endif 
1999
2000 
2001 
2002#ifdef PLATFORM_WINDOWS 
2003std::time_t tSystem::tConvertToPosixTime(FILETIME filetime) 
2004
2005 LARGE_INTEGER date; 
2006 date.HighPart = filetime.dwHighDateTime; 
2007 date.LowPart = filetime.dwLowDateTime; 
2008 
2009 // Milliseconds. 
2010 LARGE_INTEGER adjust; 
2011 adjust.QuadPart = 11644473600000 * 10000
2012  
2013 // Removes the diff between 1970 and 1601. 
2014 date.QuadPart -= adjust.QuadPart; 
2015 
2016 // Converts back from 100-nanoseconds (Windows) to seconds (Posix). 
2017 return date.QuadPart / 10000000
2018
2019#endif 
2020 
2021 
2022void tSystem::tPopulateFileInfo(tFileInfo& fileInfo, const std::filesystem::directory_entry& entry, const tString& filename
2023
2024 fileInfo.FileName = filename
2025 std::error_code errCode
2026 fileInfo.FileSize = entry.file_size(ec&: errCode); 
2027 if (errCode
2028
2029 fileInfo.FileSize = 0
2030 errCode.clear(); 
2031
2032 
2033 // CreationTime not vailable from std::filesystem. Needs to remain -1 (unset). 
2034 fileInfo.CreationTime = -1
2035 
2036 // ModificationTime. 
2037 std::filesystem::file_time_type ftime = entry.last_write_time(ec&: errCode); 
2038 if (!errCode
2039 fileInfo.ModificationTime = tConvertToPosixTime(ftime); 
2040 
2041 // AccessTime not vailable from std::filesystem. Needs to remain -1 (unset). 
2042 fileInfo.AccessTime = -1
2043 
2044 // ReadOnly flag. 
2045 std::filesystem::file_status status = entry.status(ec&: errCode); 
2046 if (!errCode
2047
2048 std::filesystem::perms pms = status.permissions(); 
2049 bool w = ((pms & std::filesystem::perms::owner_write) != std::filesystem::perms::none); 
2050 bool r = ((pms & std::filesystem::perms::owner_read) != std::filesystem::perms::none); 
2051 fileInfo.ReadOnly = (r && !w); 
2052
2053 
2054 // Hidden. 
2055 // For Linux it just checks for leading '.' do is fast. 
2056 // For Windows there's no way I can see to use std::filesystem to get this... so it uses WinAPI calls. 
2057 // It's actually not that important. We have a native implementation of FindDirs for Windows 
2058 // anyway. We don't (yet) have a native one for Linux, but at least this part is fast. 
2059 fileInfo.Hidden = tIsHidden(path: filename); 
2060 
2061 // Directoryness. 
2062 std::error_code dec
2063 fileInfo.Directory = entry.is_directory(ec&: dec); 
2064
2065 
2066 
2067#ifdef PLATFORM_WINDOWS 
2068void tSystem::tPopulateFileInfo(tFileInfo& fileInfo, Win32FindData& fd, const tString& filename) 
2069
2070 fileInfo.FileName = filename; 
2071 fileInfo.CreationTime = tConvertToPosixTime(fd.ftCreationTime); 
2072 fileInfo.ModificationTime = tConvertToPosixTime(fd.ftLastWriteTime); 
2073 fileInfo.AccessTime = tConvertToPosixTime(fd.ftLastAccessTime); 
2074 
2075 // Occasionally, a file does not have a valid modification or access time. The fileInfo struct 
2076 // may, erronously, contain a modification or access time that is smaller than the creation time! 
2077 // This happens, for example, with some files that have been transferred from a USB camera. 
2078 // In this case, we simply use the creation time for the modification or access times. 
2079 // Actually, this happens quite a bit, sometimes even if the times are valid! 
2080 // 
2081 // On seconds thought... we're going to leave the modification date alone. Although I don't agree 
2082 // with how the OS deals with this, I think the behaviour is best left untouched for mod time. 
2083 // Basically, a file copied from, say, a mem card will get a current creation time, but the mod date 
2084 // will be left at whatever the original file was. Silly, although perhaps a little defensible. 
2085 // We still correct any possible problems with access date though. 
2086 if (fileInfo.AccessTime < fileInfo.CreationTime) 
2087 fileInfo.AccessTime = fileInfo.CreationTime; 
2088 
2089 fileInfo.FileSize = fd.nFileSizeHigh; 
2090 fileInfo.FileSize <<= 32
2091 fileInfo.FileSize |= fd.nFileSizeLow; 
2092 
2093 fileInfo.ReadOnly = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) ? true : false
2094 fileInfo.Hidden = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) ? true : false
2095 fileInfo.Directory = (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? true : false
2096
2097#endif 
2098 
2099 
2100bool tSystem::tGetFileInfo(tFileInfo& fileInfo, const tString& path
2101
2102 // We want the info cleared in case an error or early-exit occurs. 
2103 fileInfo.Clear(); 
2104 tString file(path); 
2105 
2106 #ifdef PLATFORM_WINDOWS 
2107 // Seems like FindFirstFile cannot deal with a trailing backslash when 
2108 // trying to access directory information. We remove it here. 
2109 tPathWinFile(file); 
2110 Win32FindData fd; 
2111 #ifdef TACENT_UTF16_API_CALLS 
2112 tStringUTF16 file16(file); 
2113 WinHandle h = FindFirstFile(file16.GetLPWSTR(), &fd); 
2114 #else 
2115 WinHandle h = FindFirstFile(file.Chr(), &fd); 
2116 #endif 
2117 if (h == INVALID_HANDLE_VALUE) 
2118 return false
2119 
2120 // This fully fills in fileInfo, including the filename. 
2121 tPopulateFileInfo(fileInfo, fd, path); 
2122 FindClose(h); 
2123 return true
2124 
2125 #else 
2126 tPathStd(path&: file); 
2127 fileInfo.FileName = file
2128 fileInfo.Hidden = tIsHidden(path: file); // On Linux just looks for a leading . in filename. 
2129 
2130 struct stat statBuf
2131 int errCode = stat(file: file.Chr(), buf: &statBuf); 
2132 if (errCode
2133 return false
2134 
2135 // Figure out read-onlyness. 
2136 bool w = (statBuf.st_mode & S_IWUSR) ? true : false
2137 bool r = (statBuf.st_mode & S_IRUSR) ? true : false
2138 fileInfo.ReadOnly = (r && !w); 
2139 
2140 fileInfo.FileSize = statBuf.st_size
2141 fileInfo.Directory = ((statBuf.st_mode & S_IFMT) == S_IFDIR) ? true : false
2142 
2143 fileInfo.CreationTime = statBuf.st_ctime; // @todo I think this is not creation time. 
2144 fileInfo.ModificationTime = statBuf.st_mtime
2145 fileInfo.AccessTime = statBuf.st_atime
2146 if (fileInfo.AccessTime < fileInfo.CreationTime
2147 fileInfo.AccessTime = fileInfo.CreationTime
2148 
2149 return true
2150 #endif 
2151
2152 
2153 
2154#ifdef PLATFORM_WINDOWS 
2155bool tSystem::tGetFileDetails(tFileDetails& details, const tString& path) 
2156
2157 tString ffn = path; 
2158 tPathWinFile(ffn); 
2159 
2160 tString fileName = tSystem::tGetFileName(ffn); 
2161 tString fileDir = tSystem::tGetDir(ffn); 
2162 tPathWin(fileDir); 
2163 
2164 if ((fileName.Length() == 2) && (fileName[1] == ':')) 
2165
2166 fileName += "\\"
2167 fileDir = ""
2168
2169 
2170 // This interface is used for freeing up PIDLs. 
2171 WinLPMalloc mallocInterface = 0
2172 WinHResult result = SHGetMalloc(&mallocInterface); 
2173 if (!mallocInterface) 
2174 return false
2175 
2176 // Get the desktop interface. 
2177 IShellFolder* desktopInterface = 0
2178 result = SHGetDesktopFolder(&desktopInterface); 
2179 if (!desktopInterface) 
2180
2181 mallocInterface->Release(); 
2182 return false
2183
2184 
2185 // IShellFolder::ParseDisplayName requires the path name in Unicode wide characters. 
2186 WinOleChar olePath[WinMaxPath]; 
2187 MultiByteToWideChar(CP_ACP, WinMBPrecomposed, fileDir.Chr(), -1, olePath, WinMaxPath); 
2188 
2189 // Parse path for absolute PIDL, and connect to target folder. 
2190 WinLPItemIdList pidl = 0
2191 result = desktopInterface->ParseDisplayName(0, 0, olePath, 0, &pidl, 0); 
2192 if (result != S_OK) 
2193
2194 desktopInterface->Release(); 
2195 mallocInterface->Release(); 
2196 return false
2197
2198 
2199 IShellFolder2* shellFolder2 = 0
2200 result = desktopInterface->BindToObject 
2201
2202 pidl, 0
2203 IID_IShellFolder2, (void**)&shellFolder2 
2204 ); 
2205 
2206 // Release what we no longer need. 
2207 desktopInterface->Release(); 
2208 mallocInterface->Free(pidl); 
2209 
2210 if ((result != S_OK) || !shellFolder2) 
2211
2212 mallocInterface->Release(); 
2213 return false
2214
2215 
2216 WinOleChar unicodeName[WinMaxPath]; 
2217 MultiByteToWideChar(CP_ACP, WinMBPrecomposed, fileName.Chr(), -1, unicodeName, WinMaxPath); 
2218 WinLPItemIdList localPidl = 0
2219 result = shellFolder2->ParseDisplayName(0, 0, unicodeName, 0, &localPidl, 0); 
2220 if (result != S_OK) 
2221
2222 mallocInterface->Release(); 
2223 shellFolder2->Release(); 
2224 return false
2225
2226 
2227 int columnIndexArray[] = 
2228
2229 // 0, Name (Not Needed) 
2230 1, // Size / Type (Logical Drives) 
2231 2, // Type / Total Size (Logical Drives) 
2232 3, // Date Modified / Free Space (Logical Drives) 
2233 4, // Date Created / File System (Logical Drives) 
2234 5, // Date Accessed / Comments (Logical Drives) 
2235 6, // Attributes 
2236 // 7, Status (Not Needed) 
2237 // 8, Owner (Not Needed) 
2238 9, // Author 
2239 10, // Title 
2240 11, // Subject 
2241 12, // Category 
2242 13, // Pages 
2243 14, // Comments 
2244 15, // Copyright 
2245 16, // Artist 
2246 17, // Album Title 
2247 18, // Year 
2248 19, // Track Number 
2249 20, // Genre 
2250 21, // Duration 
2251 22, // Bit Rate 
2252 23, // Protected 
2253 24, // Camera Model 
2254 25, // Date Picture Taken 
2255 26, // Dimensions 
2256 // 27, Blank 
2257 // 28, Blank 
2258 29, // Episode Name 
2259 30, // Program Description 
2260 // 31, Blank 
2261 32, // Audio Sample Size 
2262 33, // Audio Sample Rate 
2263 34, // Channels 
2264 // 35, File State (Too Slow) 
2265 // 36, Rev (Useful but Too Slow) 
2266 // 37, Action (Too Slow) 
2267 38, // Company 
2268 39, // Description 
2269 40, // File VErsion 
2270 41, // Product Name 
2271 42 // Product Version 
2272 }; 
2273 
2274 const int maxDetailColumnsToTry = sizeof(columnIndexArray)/sizeof(*columnIndexArray); 
2275 for (int c = 0; c < maxDetailColumnsToTry; c++) 
2276
2277 int col = columnIndexArray[c]; 
2278 WinShellDetails shellDetail; 
2279 
2280 // Get title. 
2281 result = shellFolder2->GetDetailsOf(0, col, &shellDetail); 
2282 if (result == S_OK) 
2283
2284 #ifdef TACENT_UTF16_API_CALLS 
2285 tStringUTF16 title(33); 
2286 StrRetToBuf(&shellDetail.str, localPidl, title.GetLPWSTR(), 32); 
2287 title.SetLength( tStd::tStrlen(title.Chars()) ); 
2288 
2289 #else 
2290 char titlebuf[33]; 
2291 StrRetToBuf(&shellDetail.str, localPidl, titlebuf, 32); 
2292 tString title(titlebuf); 
2293 #endif 
2294 
2295 // Get detail. 
2296 result = shellFolder2->GetDetailsOf(localPidl, col, &shellDetail); 
2297 if (result == S_OK) 
2298
2299 #ifdef TACENT_UTF16_API_CALLS 
2300 tStringUTF16 detail(33); 
2301 StrRetToBuf(&shellDetail.str, localPidl, detail.GetLPWSTR(), 32); 
2302 detail.SetLength( tStd::tStrlen(detail.Chars()) ); 
2303 #else 
2304 char detailBuf[33]; 
2305 StrRetToBuf(&shellDetail.str, localPidl, detailBuf, 32); 
2306 tString detail(detailBuf); 
2307 #endif 
2308 
2309 // We only add the detail to the list if both title and detail are present. 
2310 if (title.IsValid() && detail.IsValid()) 
2311
2312 details.DetailTitles.Append(new tStringItem(title)); 
2313 details.Details.Append(new tStringItem(detail)); 
2314
2315
2316
2317
2318 
2319 mallocInterface->Free(localPidl); 
2320 
2321 // Release all remaining interface pointers. 
2322 mallocInterface->Release(); 
2323 shellFolder2->Release(); 
2324 
2325 return true
2326
2327 
2328 
2329void tSystem::tSetFileOpenAssoc(const tString& program, const tString& extension, const tString& programOptions) 
2330
2331 tString baseName = tSystem::tGetFileBaseName(program); 
2332 baseName.ToLower(); 
2333 
2334 tString keyString = "Software\\Classes\\Tacent_"
2335 keyString += baseName; 
2336 keyString += "\\shell\\open\\command"
2337 
2338 HKEY key; 
2339 #ifdef TACENT_UTF16_API_CALLS 
2340 tStringUTF16 keyString16(keyString); 
2341 if (RegCreateKeyEx(WinHKeyCurrentUser, keyString16.GetLPWSTR(), 0, 0, 0, WinKeySetValue, 0, &key, 0) == WinErrorSuccess) 
2342 #else 
2343 if (RegCreateKeyEx(WinHKeyCurrentUser, keyString.Chr(), 0, 0, 0, WinKeySetValue, 0, &key, 0) == WinErrorSuccess) 
2344 #endif 
2345
2346 // Create value string and set it. 
2347 tString options = programOptions; 
2348 if (options.IsEmpty()) 
2349 options = " "
2350 else 
2351 options = tString(" ") + options + " "
2352 tString valString = tString("\"") + tSystem::tGetSimplifiedPath(program) + "\"" + options + "\"%1\""
2353 tPathWin(valString); 
2354 #ifdef TACENT_UTF16_API_CALLS 
2355 RegSetValueEx(key, LPWSTR(u""), 0, REG_SZ, (uint8*)valString.Chr(), valString.Length()+1); 
2356 #else 
2357 RegSetValueEx(key, "", 0, REG_SZ, (uint8*)valString.Chr(), valString.Length()+1); 
2358 #endif 
2359 RegCloseKey(key); 
2360
2361 
2362 tString ext = extension; 
2363 ext.ToLower(); 
2364 keyString = "Software\\Classes\\."
2365 keyString += ext; 
2366 #ifdef TACENT_UTF16_API_CALLS 
2367 tStringUTF16 keyString16B(keyString); 
2368 if (RegCreateKeyEx(WinHKeyCurrentUser, keyString16B.GetLPWSTR(), 0, 0, 0, WinKeySetValue, 0, &key, 0) == WinErrorSuccess) 
2369 #else 
2370 if (RegCreateKeyEx(WinHKeyCurrentUser, keyString.Chr(), 0, 0, 0, WinKeySetValue, 0, &key, 0) == WinErrorSuccess) 
2371 #endif 
2372
2373 tString valString = "Tacent_"
2374 valString += baseName; 
2375 
2376 // REG_SZ means that the values in arg 5 is not UTF-16. 
2377 #ifdef TACENT_UTF16_API_CALLS 
2378 RegSetValueEx(key, LPWSTR(u""), 0, REG_SZ, (const BYTE*)valString.Chr(), valString.Length()+1); 
2379 #else 
2380 RegSetValueEx(key, "", 0, REG_SZ, (uint8*)valString.Chr(), valString.Length()+1); 
2381 #endif 
2382 RegCloseKey(key); 
2383
2384
2385 
2386 
2387void tSystem::tSetFileOpenAssoc(const tString& program, const tList<tStringItem>& extensions, const tString& programOptions) 
2388
2389 for (auto ext = extensions.First(); ext; ext = ext->Next()) 
2390 tSetFileOpenAssoc(program, *ext, programOptions); 
2391
2392 
2393 
2394tString tSystem::tGetFileOpenAssoc(const tString& extension) 
2395
2396 if (extension.IsEmpty()) 
2397 return tString(); 
2398 
2399 HKEY key; 
2400 tString ext = extension; 
2401 ext.ToLower(); 
2402 tString keyString = "Software\\Classes\\."
2403 keyString += ext; 
2404 tString appName; 
2405 char appNameBuf[128]; 
2406 #ifdef TACENT_UTF16_API_CALLS 
2407 tStringUTF16 keyString16A(keyString); 
2408 if (RegOpenKeyEx(WinHKeyCurrentUser, keyString16A.GetLPWSTR(), 0, KEY_QUERY_VALUE, &key) == WinErrorSuccess) 
2409 #else 
2410 if (RegOpenKeyEx(WinHKeyCurrentUser, keyString.Chr(), 0, KEY_QUERY_VALUE, &key) == WinErrorSuccess) 
2411 #endif 
2412
2413 ulong numBytesIO = 127
2414 #ifdef TACENT_UTF16_API_CALLS 
2415 RegGetValue(key, LPCWSTR(u""), 0, RRF_RT_REG_SZ | RRF_ZEROONFAILURE, 0, appNameBuf, &numBytesIO); 
2416 #else 
2417 RegGetValue(key, "", 0, RRF_RT_REG_SZ | RRF_ZEROONFAILURE, 0, appNameBuf, &numBytesIO); 
2418 #endif 
2419 appName.Set(appNameBuf); 
2420 RegCloseKey(key); 
2421
2422 
2423 if (appName.IsEmpty()) 
2424 return tString(); 
2425 
2426 keyString = "Software\\Classes\\"
2427 keyString += appName; 
2428 keyString += "\\shell\\open\\command"
2429 tString exeName; 
2430 char exeNameBuf[256]; 
2431 #ifdef TACENT_UTF16_API_CALLS 
2432 tStringUTF16 keyString16B(keyString); 
2433 if (RegOpenKeyEx(WinHKeyCurrentUser, keyString16B.GetLPWSTR(), 0, KEY_QUERY_VALUE, &key) == WinErrorSuccess) 
2434 #else 
2435 if (RegOpenKeyEx(WinHKeyCurrentUser, keyString.Chr(), 0, KEY_QUERY_VALUE, &key) == WinErrorSuccess) 
2436 #endif 
2437
2438 ulong numBytesIO = 255
2439 #ifdef TACENT_UTF16_API_CALLS 
2440 RegGetValue(key, LPCWSTR(u""), 0, RRF_RT_REG_SZ | RRF_ZEROONFAILURE, 0, exeNameBuf, &numBytesIO); 
2441 #else 
2442 RegGetValue(key, "", 0, RRF_RT_REG_SZ | RRF_ZEROONFAILURE, 0, exeNameBuf, &numBytesIO); 
2443 #endif 
2444 exeName.Set(exeNameBuf); 
2445 RegCloseKey(key); 
2446
2447 
2448 return exeName; 
2449
2450#endif // PLATFORM_WINDOWS 
2451 
2452 
2453bool tSystem::tFindDirs_Stndrd(tList<tStringItem>* dirs, tList<tFileInfo>* infos, const tString& dir, bool hidden
2454
2455 tString dirPath(dir); 
2456 if (dirPath.IsEmpty()) 
2457 dirPath = (char*)std::filesystem::current_path().u8string().c_str(); 
2458 
2459 std::error_code errorCode
2460 for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(dirPath.Text(), errorCode)) 
2461
2462 if (errorCode || (entry == std::filesystem::directory_entry())) 
2463
2464 errorCode.clear(); 
2465 continue
2466
2467 
2468 // For now we're skipping symlinks (they return false for is_directory). 
2469 std::error_code direc
2470 if (!entry.is_directory(ec&: direc)) 
2471 continue
2472 
2473 if (direc
2474 continue
2475 
2476 tString foundDir((char*)entry.path().u8string().c_str()); 
2477 tPathStdDir(path&: foundDir); 
2478 
2479 if (!hidden && tIsHidden(path: foundDir)) 
2480 continue
2481 
2482 if (dirs
2483 dirs->Append(item: new tStringItem(foundDir)); 
2484 
2485 if (infos
2486
2487 tFileInfo* fileInfo = new tFileInfo(); 
2488 tPopulateFileInfo(fileInfo&: *fileInfo, entry, filename: foundDir); 
2489 infos->Append(item: fileInfo); 
2490
2491
2492 
2493 return true
2494
2495 
2496 
2497bool tSystem::tFindDirs_Native(tList<tStringItem>* dirs, tList<tFileInfo>* infos, const tString& dir, bool hidden
2498
2499 #if defined(PLATFORM_WINDOWS) 
2500 // First lets massage fileName a little. 
2501 tString massagedName = dir; 
2502 if ((massagedName[massagedName.Length() - 1] == '/') || (massagedName[massagedName.Length() - 1] == '\\')) 
2503 massagedName += "*.*"
2504 
2505 Win32FindData fd; 
2506 #ifdef TACENT_UTF16_API_CALLS 
2507 tStringUTF16 massagedName16(massagedName); 
2508 WinHandle h = FindFirstFile(massagedName16.GetLPWSTR(), &fd); 
2509 #else 
2510 WinHandle h = FindFirstFile(massagedName.Chr(), &fd); 
2511 #endif 
2512 if (h == INVALID_HANDLE_VALUE) 
2513 return false
2514 
2515 tString path = tGetDir(massagedName); 
2516 do 
2517
2518 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 
2519
2520 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) || hidden) 
2521
2522 // If the directory name is not "." or ".." then it's a real directory. 
2523 // Note that you cannot just check for the first character not being "." Some directories (and files) 
2524 // may have a name that starts with a dot, especially if they were copied from a unix machine. 
2525 #ifdef TACENT_UTF16_API_CALLS 
2526 tString fn((char16_t*)fd.cFileName); 
2527 #else 
2528 tString fn(fd.cFileName); 
2529 #endif 
2530 if ((fn != ".") && (fn != "..")) 
2531
2532 if (dirs) 
2533 dirs->Append(new tStringItem(path + fn + "/")); 
2534 
2535 if (infos) 
2536
2537 tFileInfo* fileInfo = new tFileInfo(); 
2538 tPopulateFileInfo(*fileInfo, fd, tString(path + fn + "/")); 
2539 infos->Append(fileInfo); 
2540
2541
2542
2543
2544 } while (FindNextFile(h, &fd)); 
2545 
2546 FindClose(h); 
2547 if (GetLastError() != ERROR_NO_MORE_FILES) 
2548 return false
2549 return true
2550 
2551 #elif defined(PLATFORM_LINUX) 
2552 // @todo No Linux Native implementation. Use Standard. 
2553 return tFindDirs_Stndrd(dirs, infos, dir, hidden); 
2554 
2555 #else 
2556 tAssert(!"tFindDirs_Native not implemented for platform."); 
2557 return false
2558 
2559 #endif 
2560
2561 
2562 
2563bool tSystem::tFindDirs(tList<tStringItem>& dirs, const tString& dir, bool hidden, Backend backend
2564
2565 switch (backend
2566
2567 case Backend::Stndrd: return tFindDirs_Stndrd(dirs: &dirs, infos: nullptr, dir, hidden); 
2568 case Backend::Native: return tFindDirs_Native(dirs: &dirs, infos: nullptr, dir, hidden); 
2569
2570 return false
2571
2572 
2573 
2574bool tSystem::tFindDirs(tList<tFileInfo>& dirs, const tString& dir, bool hidden, Backend backend
2575
2576 switch (backend
2577
2578 case Backend::Stndrd: return tFindDirs_Stndrd(dirs: nullptr, infos: &dirs, dir, hidden); 
2579 case Backend::Native: return tFindDirs_Native(dirs: nullptr, infos: &dirs, dir, hidden); 
2580
2581 return false
2582
2583 
2584 
2585bool tSystem::tFindFiles_Stndrd(tList<tStringItem>* files, tList<tFileInfo>* infos, const tString& dir, const tExtensions* extensions, bool hidden
2586
2587 if (extensions && extensions->IsEmpty()) 
2588 return false
2589 
2590 // Use current directory if no dirPath supplied. 
2591 tString dirPath(dir); 
2592 if (dirPath.IsEmpty()) 
2593 dirPath = (char*)std::filesystem::current_path().u8string().c_str(); 
2594 
2595 if (dirPath.IsEmpty()) 
2596 return false
2597 
2598 std::error_code errorCode
2599 for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(dirPath.Text(), errorCode)) 
2600
2601 if (errorCode || (entry == std::filesystem::directory_entry())) 
2602
2603 errorCode.clear(); 
2604 continue
2605
2606 
2607 std::error_code rec
2608 if (!entry.is_regular_file(ec&: rec)) 
2609 continue
2610 if (rec
2611 continue
2612 
2613 tString foundFile((char*)entry.path().u8string().c_str()); 
2614 tPathStdFile(path&: foundFile); 
2615 tString foundExt = tGetFileExtension(file: foundFile); 
2616 
2617 // If no extension match continue. 
2618 if (extensions && !extensions->Contains(searchExt: foundExt)) 
2619 continue
2620 
2621 if (!hidden && tIsHidden(path: foundFile)) 
2622 continue
2623 
2624 if (files
2625 files->Append(item: new tStringItem(foundFile)); 
2626 
2627 if (infos
2628
2629 tFileInfo* newFileInfo = new tFileInfo(); 
2630 tGetFileInfo(fileInfo&: *newFileInfo, path: foundFile); 
2631 infos->Append(item: newFileInfo); 
2632
2633
2634 
2635 return true
2636
2637 
2638 
2639bool tSystem::tFindFiles_Native(tList<tStringItem>* files, tList<tFileInfo>* infos, const tString& dir, const tExtensions* extensions, bool hidden
2640
2641 if (extensions && extensions->IsEmpty()) 
2642 return false
2643 
2644 tString dirStr(dir); 
2645 if (dirStr.IsEmpty()) 
2646 dirStr = tGetCurrentDir(); 
2647 
2648 #ifdef PLATFORM_WINDOWS 
2649 // FindFirstFile etc seem to like backslashes better. 
2650 tPathWinDir(dirStr); 
2651 
2652 // There's some complexity here with windows, but it's still very fast. We need to loop through all the 
2653 // extensions doing the FindFirstFile business, while modifying the path appropriately for each one. 
2654 // Insert a special empty extension if extensions is null. This will cause all file types to be included. 
2655 tExtensions exts; 
2656 if (extensions) 
2657 exts.Add(*extensions); 
2658 else 
2659 exts.Extensions.Append(new tStringItem()); 
2660 
2661 bool allOk = true
2662 for (tStringItem* extItem = exts.First(); extItem; extItem = extItem->Next()) 
2663
2664 tString ext(*extItem); 
2665 tString path = dirStr + "*."
2666 if (ext.IsEmpty()) 
2667 path += "*"
2668 else 
2669 path += ext; 
2670 
2671 Win32FindData fd; 
2672 #ifdef TACENT_UTF16_API_CALLS 
2673 tStringUTF16 path16(path); 
2674 WinHandle h = FindFirstFile(path16.GetLPWSTR(), &fd); 
2675 #else 
2676 WinHandle h = FindFirstFile(path.Chr(), &fd); 
2677 #endif 
2678 if (h == INVALID_HANDLE_VALUE) 
2679
2680 allOk = false
2681 continue
2682
2683 
2684 do 
2685
2686 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) 
2687
2688 // It's not a directory... so it's actually a real file. 
2689 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) || hidden) 
2690
2691 #ifdef TACENT_UTF16_API_CALLS 
2692 tString fdFilename((char16_t*)fd.cFileName); 
2693 #else 
2694 tString fdFilename(fd.cFileName); 
2695 #endif 
2696 tString foundName = dirStr + fdFilename; 
2697 tPathStd(foundName); 
2698 
2699 tStringItem* newName = files ? new tStringItem(foundName) : nullptr
2700 tFileInfo* newInfo = infos ? new tFileInfo() : nullptr
2701 if (newInfo) 
2702 tPopulateFileInfo(*newInfo, fd, foundName); 
2703 
2704 // Holy obscure and annoying FindFirstFile bug! FindFirstFile("*.abc", ...) will also find 
2705 // files like file.abcd. This isn't correct I guess we have to check the extension here. 
2706 // FileMask is required to specify an extension, even if it is ".*" 
2707 if (path[path.Length() - 1] != '*'
2708
2709 tString foundExtension = tGetFileExtension(fdFilename); 
2710 if (ext.IsEqualCI(foundExtension)) 
2711
2712 if (files) 
2713 files->Append(newName); 
2714 if (infos) 
2715 infos->Append(newInfo); 
2716
2717
2718 else 
2719
2720 if (files) 
2721 files->Append(newName); 
2722 if (infos) 
2723 infos->Append(newInfo); 
2724
2725
2726
2727 } while (FindNextFile(h, &fd)); 
2728 
2729 FindClose(h); 
2730 if (GetLastError() != ERROR_NO_MORE_FILES) 
2731 return false;  
2732
2733 return allOk; 
2734 
2735 #elif defined(PLATFORM_LINUX) 
2736 tPathStdDir(path&: dirStr); 
2737 DIR* dirEnt = opendir(name: dirStr.Chr()); 
2738 if (dirStr.IsEmpty() || !dirEnt
2739 return false
2740 
2741 for (struct dirent* entry = readdir(dirp: dirEnt); entry; entry = readdir(dirp: dirEnt)) 
2742
2743 // Definitely skip directories. 
2744 if (entry->d_type == DT_DIR
2745 continue
2746 
2747 // Sometimes it seems that d_type for a file is set to unknown. 
2748 // Noticed this under Linux when some files are in mounted directories. 
2749 if ((entry->d_type != DT_REG) && (entry->d_type != DT_UNKNOWN)) 
2750 continue
2751 
2752 tString foundFile((char*)entry->d_name); 
2753 foundFile = dirStr + foundFile
2754 tString foundExt = tGetFileExtension(file: foundFile); 
2755 
2756 // If extension list present and no match continue. 
2757 if (extensions && !extensions->Contains(searchExt: foundExt)) 
2758 continue
2759 
2760 if (!hidden && tIsHidden(path: foundFile)) 
2761 continue
2762 
2763 if (files
2764 files->Append(item: new tStringItem(foundFile)); 
2765 
2766 if (infos
2767
2768 tFileInfo* newFileInfo = new tFileInfo(); 
2769 
2770 // @todo If we had a linux native populate file info, we would call it here. 
2771 tGetFileInfo(fileInfo&: *newFileInfo, path: foundFile); 
2772 infos->Append(item: newFileInfo); 
2773
2774
2775 closedir(dirp: dirEnt); 
2776 return true
2777 
2778 #endif 
2779
2780 
2781 
2782bool tSystem::tFindFiles(tList<tStringItem>& files, const tString& dir, bool hidden, Backend backend
2783
2784 switch (backend
2785
2786 // A nullptr for extensions will return all types. 
2787 case Backend::Stndrd: return tFindFiles_Stndrd(files: &files, infos: nullptr, dir, extensions: nullptr, hidden); 
2788 case Backend::Native: return tFindFiles_Native(files: &files, infos: nullptr, dir, extensions: nullptr, hidden); 
2789
2790 return false
2791
2792 
2793 
2794bool tSystem::tFindFiles(tList<tStringItem>& files, const tString& dir, const tString& ext, bool hidden, Backend backend
2795
2796 tExtensions extensions
2797 if (!ext.IsEmpty()) 
2798 extensions.Add(ext); 
2799 
2800 switch (backend
2801
2802 // A valid but empty tExtensions will return false which is what we want. 
2803 case Backend::Stndrd: return tFindFiles_Stndrd(files: &files, infos: nullptr, dir, extensions: &extensions, hidden); 
2804 case Backend::Native: return tFindFiles_Native(files: &files, infos: nullptr, dir, extensions: &extensions, hidden); 
2805
2806 return false
2807
2808 
2809 
2810bool tSystem::tFindFiles(tList<tStringItem>& files, const tString& dir, const tExtensions& extensions, bool hidden, Backend backend
2811
2812 switch (backend
2813
2814 // A valid but empty tExtensions will return false which is what we want. 
2815 case Backend::Stndrd: return tFindFiles_Stndrd(files: &files, infos: nullptr, dir, extensions: &extensions, hidden); 
2816 case Backend::Native: return tFindFiles_Native(files: &files, infos: nullptr, dir, extensions: &extensions, hidden); 
2817
2818 return false
2819
2820 
2821 
2822bool tSystem::tFindFiles(tList<tFileInfo>& files, const tString& dir, bool hidden, Backend backend
2823
2824 switch (backend
2825
2826 // A nullptr for extensions will return all types. 
2827 case Backend::Stndrd: return tFindFiles_Stndrd(files: nullptr, infos: &files, dir, extensions: nullptr, hidden); 
2828 case Backend::Native: return tFindFiles_Native(files: nullptr, infos: &files, dir, extensions: nullptr, hidden); 
2829
2830 return false
2831
2832 
2833 
2834bool tSystem::tFindFiles(tList<tFileInfo>& files, const tString& dir, const tString& ext, bool hidden, Backend backend
2835
2836 tExtensions extensions
2837 if (!ext.IsEmpty()) 
2838 extensions.Add(ext); 
2839 
2840 switch (backend
2841
2842 // A valid but empty tExtensions will return false which is what we want. 
2843 case Backend::Stndrd: return tFindFiles_Stndrd(files: nullptr, infos: &files, dir, extensions: &extensions, hidden); 
2844 case Backend::Native: return tFindFiles_Native(files: nullptr, infos: &files, dir, extensions: &extensions, hidden); 
2845
2846 return false
2847
2848 
2849 
2850bool tSystem::tFindFiles(tList<tFileInfo>& files, const tString& dir, const tExtensions& extensions, bool hidden, Backend backend
2851
2852 switch (backend
2853
2854 // A valid but empty tExtensions will return false which is what we want. 
2855 case Backend::Stndrd: return tFindFiles_Stndrd(files: nullptr, infos: &files, dir, extensions: &extensions, hidden); 
2856 case Backend::Native: return tFindFiles_Native(files: nullptr, infos: &files, dir, extensions: &extensions, hidden); 
2857
2858 return false
2859
2860 
2861 
2862bool tSystem::tFindDirsRec_Stndrd(tList<tStringItem>* dirs, tList<tFileInfo>* infos, const tString& dir, bool hidden
2863
2864 tString dirPath(dir); 
2865 if (dirPath.IsEmpty()) 
2866 dirPath = (char*)std::filesystem::current_path().u8string().c_str(); 
2867 
2868 // The std::filesystem API has a recursive iterator so we use it. 
2869 std::error_code errorCode
2870 for (const std::filesystem::directory_entry& entry : std::filesystem::recursive_directory_iterator(dirPath.Chr(), errorCode)) 
2871
2872 // If error or entry is default entry, continue. Probably don't need to clear errorCode but just in case. 
2873 if (errorCode || (entry == std::filesystem::directory_entry())) 
2874
2875 errorCode.clear(); 
2876 continue
2877
2878 
2879 std::error_code direc
2880 if (!entry.is_directory(ec&: direc)) 
2881 continue
2882 if (direc
2883 continue
2884 
2885 tString foundDir((char*)entry.path().u8string().c_str()); 
2886 tPathStdDir(path&: foundDir); 
2887 
2888 // All directories end in a slash in tacent. 
2889 if (foundDir[foundDir.Length()-1] != '/'
2890 foundDir += "/"
2891 
2892 if (!hidden && tIsHidden(path: foundDir)) 
2893 continue
2894 
2895 if (dirs
2896 dirs->Append(item: new tStringItem(foundDir)); 
2897 
2898 if (infos
2899
2900 tFileInfo* fileInfo = new tFileInfo(); 
2901 tPopulateFileInfo(fileInfo&: *fileInfo, entry, filename: foundDir); 
2902 infos->Append(item: fileInfo); 
2903
2904
2905 
2906 return true
2907
2908 
2909 
2910bool tSystem::tFindDirsRec_Native(tList<tStringItem>* dirs, tList<tFileInfo>* infos, const tString& dir, bool hidden
2911
2912 // Populate current dir dirs/infos. 
2913 tList<tStringItem> currdirs
2914 tList<tFileInfo> currinfos
2915 tFindDirs_Native(dirs: &currdirs, infos: &currinfos, dir, hidden); 
2916 
2917 if (dirs
2918
2919 for (tStringItem* ds = currdirs.First(); ds; ds = ds->Next()) 
2920 dirs->Append(item: new tStringItem(*ds)); 
2921
2922 
2923 if (infos
2924
2925 while (tFileInfo* di = currinfos.Remove()) 
2926 infos->Append(item: di); 
2927
2928 
2929 // Recurse. 
2930 for (tStringItem* d = currdirs.First(); d; d = d->Next()) 
2931 tFindDirsRec_Native(dirs, infos, dir: *d, hidden); 
2932 
2933 return true
2934
2935 
2936 
2937bool tSystem::tFindDirsRec(tList<tStringItem>& dirs, const tString& dir, bool hidden, Backend backend
2938
2939 switch (backend
2940
2941 case Backend::Stndrd: return tFindDirsRec_Stndrd(dirs: &dirs, infos: nullptr, dir, hidden); 
2942 case Backend::Native: return tFindDirsRec_Native(dirs: &dirs, infos: nullptr, dir, hidden); 
2943
2944 return false
2945
2946 
2947 
2948bool tSystem::tFindDirsRec(tList<tFileInfo>& dirs, const tString& dir, bool hidden, Backend backend
2949
2950 switch (backend
2951
2952 case Backend::Stndrd: return tFindDirsRec_Stndrd(dirs: nullptr, infos: &dirs, dir, hidden); 
2953 case Backend::Native: return tFindDirsRec_Native(dirs: nullptr, infos: &dirs, dir, hidden); 
2954
2955 return false
2956
2957 
2958 
2959bool tSystem::tFindFilesRec_Stndrd(tList<tStringItem>* files, tList<tFileInfo>* infos, const tString& dir, const tExtensions* extensions, bool hidden
2960
2961 if (extensions && extensions->IsEmpty()) 
2962 return false
2963 
2964 // Use current directory if no dirPath supplied. 
2965 tString dirPath(dir); 
2966 if (dirPath.IsEmpty()) 
2967 dirPath = (char*)std::filesystem::current_path().u8string().c_str(); 
2968 
2969 if (dirPath.IsEmpty()) 
2970 return false
2971 
2972 std::error_code errorCode
2973 for (const std::filesystem::directory_entry& entry : std::filesystem::recursive_directory_iterator(dirPath.Text(), errorCode)) 
2974
2975 if (errorCode || (entry == std::filesystem::directory_entry())) 
2976
2977 errorCode.clear(); 
2978 continue
2979
2980 
2981 std::error_code rec
2982 if (!entry.is_regular_file(ec&: rec)) 
2983 continue
2984 if (rec
2985 continue
2986 
2987 tString foundFile((char*)entry.path().u8string().c_str()); 
2988 tPathStdFile(path&: foundFile); 
2989 tString foundExt = tGetFileExtension(file: foundFile); 
2990 
2991 // If no extension match continue. 
2992 if (extensions && !extensions->Contains(searchExt: foundExt)) 
2993 continue
2994 
2995 if (!hidden && tIsHidden(path: foundFile)) 
2996 continue
2997 
2998 if (files
2999 files->Append(item: new tStringItem(foundFile)); 
3000 
3001 if (infos
3002
3003 tFileInfo* newFileInfo = new tFileInfo(); 
3004 tGetFileInfo(fileInfo&: *newFileInfo, path: foundFile); 
3005 infos->Append(item: newFileInfo); 
3006
3007
3008 
3009 return true
3010
3011 
3012 
3013bool tSystem::tFindFilesRec_Native(tList<tStringItem>* files, tList<tFileInfo>* infos, const tString& dir, const tExtensions* extensions, bool hidden
3014
3015 // Populate current dir files/infos. 
3016 tFindFiles_Native(files, infos, dir, extensions, hidden); 
3017 
3018 // Recurse. 
3019 tList<tStringItem> currdirs
3020 tFindDirs_Native(dirs: &currdirs, infos: nullptr, dir, hidden); 
3021 for (tStringItem* d = currdirs.First(); d; d = d->Next()) 
3022 tFindFilesRec_Native(files, infos, dir: *d, extensions, hidden); 
3023 
3024 return true
3025
3026 
3027 
3028bool tSystem::tFindFilesRec(tList<tStringItem>& files, const tString& dir, bool hidden, Backend backend
3029
3030 switch (backend
3031
3032 // A nullptr for extensions will return all types. 
3033 case Backend::Stndrd: return tFindFilesRec_Stndrd(files: &files, infos: nullptr, dir, extensions: nullptr, hidden); 
3034 case Backend::Native: return tFindFilesRec_Native(files: &files, infos: nullptr, dir, extensions: nullptr, hidden); 
3035
3036 return false
3037
3038 
3039 
3040bool tSystem::tFindFilesRec(tList<tStringItem>& files, const tString& dir, const tString& ext, bool hidden, Backend backend
3041
3042 tExtensions extensions
3043 if (!ext.IsEmpty()) 
3044 extensions.Add(ext); 
3045 
3046 switch (backend
3047
3048 // A valid but empty tExtensions will return false which is what we want. 
3049 case Backend::Stndrd: return tFindFilesRec_Stndrd(files: &files, infos: nullptr, dir, extensions: &extensions, hidden); 
3050 case Backend::Native: return tFindFilesRec_Native(files: &files, infos: nullptr, dir, extensions: &extensions, hidden); 
3051
3052 return false
3053
3054 
3055 
3056bool tSystem::tFindFilesRec(tList<tStringItem>& files, const tString& dir, const tExtensions& extensions, bool hidden, Backend backend
3057
3058 switch (backend
3059
3060 // A valid but empty tExtensions will return false which is what we want. 
3061 case Backend::Stndrd: return tFindFilesRec_Stndrd(files: &files, infos: nullptr, dir, extensions: &extensions, hidden); 
3062 case Backend::Native: return tFindFilesRec_Native(files: &files, infos: nullptr, dir, extensions: &extensions, hidden); 
3063
3064 return false
3065
3066 
3067 
3068bool tSystem::tFindFilesRec(tList<tFileInfo>& files, const tString& dir, bool hidden, Backend backend
3069
3070 switch (backend
3071
3072 // A nullptr for extensions will return all types. 
3073 case Backend::Stndrd: return tFindFilesRec_Stndrd(files: nullptr, infos: &files, dir, extensions: nullptr, hidden); 
3074 case Backend::Native: return tFindFilesRec_Native(files: nullptr, infos: &files, dir, extensions: nullptr, hidden); 
3075
3076 return false
3077
3078 
3079 
3080bool tSystem::tFindFilesRec(tList<tFileInfo>& files, const tString& dir, const tString& ext, bool hidden, Backend backend
3081
3082 tExtensions extensions
3083 if (!ext.IsEmpty()) 
3084 extensions.Add(ext); 
3085 
3086 switch (backend
3087
3088 // A valid but empty tExtensions will return false which is what we want. 
3089 case Backend::Stndrd: return tFindFilesRec_Stndrd(files: nullptr, infos: &files, dir, extensions: &extensions, hidden); 
3090 case Backend::Native: return tFindFilesRec_Native(files: nullptr, infos: &files, dir, extensions: &extensions, hidden); 
3091
3092 return false
3093
3094 
3095 
3096bool tSystem::tFindFilesRec(tList<tFileInfo>& files, const tString& dir, const tExtensions& extensions, bool hidden, Backend backend
3097
3098 switch (backend
3099
3100 // A valid but empty tExtensions will return false which is what we want. 
3101 case Backend::Stndrd: return tFindFilesRec_Stndrd(files: nullptr, infos: &files, dir, extensions: &extensions, hidden); 
3102 case Backend::Native: return tFindFilesRec_Native(files: nullptr, infos: &files, dir, extensions: &extensions, hidden); 
3103
3104 return false
3105
3106 
3107 
3108bool tSystem::tCreateDir(const tString& dir
3109
3110 tString dirPath = dir
3111  
3112 #if defined(PLATFORM_WINDOWS) 
3113 tPathWin(dirPath); 
3114 
3115 #ifdef TACENT_UTF16_API_CALLS 
3116 tStringUTF16 dirPath16(dirPath); 
3117 bool success = ::CreateDirectory(dirPath16.GetLPWSTR(), 0) ? true : false
3118 #else 
3119 bool success = ::CreateDirectory(dirPath.Chr(), 0) ? true : false
3120 
3121 #endif 
3122 if (!success) 
3123 success = tDirExists(dirPath); 
3124 
3125 return success; 
3126 
3127 #else 
3128 tPathStdFile(path&: dirPath); 
3129 bool success = std::filesystem::create_directory(p: dirPath.Chr()); 
3130 if (!success
3131 return tDirExists(dir: dirPath); 
3132 
3133 return success
3134 
3135 #endif 
3136
3137 
3138 
3139bool tSystem::tCreateDirs(const tString& dirs
3140
3141 tString dirsPath = dirs
3142 tPathStdFile(path&: dirsPath); 
3143  
3144 bool success = std::filesystem::create_directories(p: dirsPath.Chr()); 
3145 if (!success
3146 return tDirExists(dir: dirsPath); 
3147 
3148 return success
3149
3150 
3151 
3152bool tSystem::tDeleteDir(const tString& dir, bool deleteReadOnly
3153
3154 #ifdef PLATFORM_WINDOWS 
3155 // Are we done before we even begin? 
3156 if (!tDirExists(dir)) 
3157 return false
3158 
3159 tList<tStringItem> fileList; 
3160 tFindFiles(fileList, dir); 
3161 tStringItem* file = fileList.First(); 
3162 while (file) 
3163
3164 tDeleteFile(*file, deleteReadOnly); // We don't really care whether it succeeded or not. 
3165 file = file->Next(); 
3166
3167 
3168 fileList.Empty(); // Clean up the file list. 
3169 tString directory(dir); 
3170 tPathWin(directory); 
3171 
3172 Win32FindData fd; 
3173 #ifdef TACENT_UTF16_API_CALLS 
3174 tStringUTF16 directoryMod16(directory + "*.*"); 
3175 WinHandle h = FindFirstFile(directoryMod16.GetLPWSTR(), &fd); 
3176 #else 
3177 WinHandle h = FindFirstFile((directory + "*.*").Chr(), &fd); 
3178 #endif 
3179 if (h == INVALID_HANDLE_VALUE) 
3180 return true
3181 
3182 do 
3183
3184 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 
3185
3186 // If the directory name is not "." or ".." then it's a real directory. 
3187 // Note that you cannot just check for the first character not being "." Some directories (and files) 
3188 // may have a name that starts with a dot, especially if they were copied from a unix machine. 
3189 #ifdef TACENT_UTF16_API_CALLS 
3190 tString fn((char16_t*)fd.cFileName); 
3191 #else 
3192 tString fn(fd.cFileName); 
3193 #endif 
3194 if ((fn != ".") && (fn != "..")) 
3195 tDeleteDir(dir + fn + "/", deleteReadOnly); 
3196
3197 } while (FindNextFile(h, &fd)); 
3198 
3199 bool deleteFilesOK = (GetLastError() == ERROR_NO_MORE_FILES) ? true : false
3200 FindClose(h); 
3201 
3202 #ifdef TACENT_UTF16_API_CALLS 
3203 tStringUTF16 directory16(directory); 
3204 if (deleteReadOnly) 
3205 SetFileAttributes(directory16.GetLPWSTR(), FILE_ATTRIBUTE_NORMAL); // Directories can be read-only too. 
3206 #else 
3207 if (deleteReadOnly) 
3208 SetFileAttributes(directory.Chr(), FILE_ATTRIBUTE_NORMAL); // Directories can be read-only too. 
3209 #endif 
3210 
3211 bool success = false
3212 for (int delTry = 0; delTry < 32; delTry++) 
3213
3214 #ifdef TACENT_UTF16_API_CALLS 
3215 tStringUTF16 dir16(dir); 
3216 if (RemoveDirectory(dir16.GetLPWSTR())) 
3217 #else 
3218 if (RemoveDirectory(dir.Chr())) 
3219 #endif 
3220
3221 success = true
3222 break
3223
3224 
3225 // In some cases we might need to wait just a little and try again. This can even take up to 10 seconds or so. 
3226 // This seems to happen a lot when the target manager is streaming music, say, from the folder. 
3227 else if (GetLastError() == ERROR_DIR_NOT_EMPTY) 
3228
3229 tSystem::tSleep(500); 
3230
3231 else 
3232
3233 tSystem::tSleep(10); 
3234
3235
3236 
3237 if (!success || !deleteFilesOK) 
3238 return false
3239 
3240 #else 
3241 // Are we done before we even begin? 
3242 if (!tDirExists(dir)) 
3243 return false
3244 
3245 if (tIsReadOnly(path: dir) && !deleteReadOnly
3246 return true
3247 
3248 std::filesystem::path p(dir.Chr()); 
3249 std::error_code ec
3250 uintmax_t numRemoved = std::filesystem::remove_all(p: p, ec&: ec); 
3251 if (ec
3252 return false
3253 
3254 #endif 
3255 
3256 return true
3257
3258 
3259 
3260uint32 tSystem::tHashFileFast32(const tString& filename, uint32 iv
3261
3262 int dataSize = 0
3263 uint8* data = tLoadFile(file: filename, buffer: nullptr, fileSize: &dataSize); 
3264 if (!data
3265 return iv
3266 
3267 uint32 hash = tHash::tHashDataFast32(data, length: dataSize, iv); 
3268 delete[] data
3269 return hash
3270
3271 
3272 
3273uint32 tSystem::tHashFile32(const tString& filename, uint32 iv
3274
3275 int dataSize = 0
3276 uint8* data = tLoadFile(file: filename, buffer: nullptr, fileSize: &dataSize); 
3277 if (!data
3278 return iv
3279 
3280 uint32 hash = tHash::tHashData32(data, length: dataSize, iv); 
3281 delete[] data
3282 return hash
3283
3284 
3285 
3286uint64 tSystem::tHashFile64(const tString& filename, uint64 iv
3287
3288 int dataSize = 0
3289 uint8* data = tLoadFile(file: filename, buffer: nullptr, fileSize: &dataSize); 
3290 if (!data
3291 return iv
3292 
3293 uint64 hash = tHash::tHashData64(data, length: dataSize, iv); 
3294 delete[] data
3295 return hash
3296
3297 
3298 
3299tuint256 tSystem::tHashFile256(const tString& filename, tuint256 iv
3300
3301 int dataSize = 0
3302 uint8* data = tLoadFile(file: filename, buffer: nullptr, fileSize: &dataSize); 
3303 if (!data
3304 return iv
3305 
3306 tuint256 hash = tHash::tHashData256(data, length: dataSize, iv); 
3307 delete[] data
3308 return hash
3309
3310 
3311 
3312tuint128 tSystem::tHashFileMD5(const tString& filename, tuint128 iv
3313
3314 int dataSize = 0
3315 uint8* data = tLoadFile(file: filename, buffer: nullptr, fileSize: &dataSize); 
3316 if (!data
3317 return iv
3318 
3319 tuint128 hash = tHash::tHashDataMD5(data, length: dataSize, iv); 
3320 delete[] data
3321 return hash
3322
3323 
3324 
3325tuint256 tSystem::tHashFileSHA256(const tString& filename, tuint256 iv
3326
3327 int dataSize = 0
3328 uint8* data = tLoadFile(file: filename, buffer: nullptr, fileSize: &dataSize); 
3329 if (!data
3330 return iv
3331 
3332 tuint256 hash = tHash::tHashDataSHA256(data, length: dataSize, iv); 
3333 delete[] data
3334 return hash
3335
3336