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, 2020 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#endif 
36#include <filesystem> 
37#include "System/tTime.h" 
38#include "System/tFile.h" 
39 
40 
41namespace tSystem 
42
43 std::time_t tFileTimeToStdTime(std::filesystem::file_time_type tp); 
44 
45 #ifdef PLATFORM_WINDOWS 
46 std::time_t tFileTimeToPosixEpoch(FILETIME); 
47 #endif 
48
49 
50 
51tFileHandle tSystem::tOpenFile(const char* filename, const char* mode
52
53 return fopen(filename, mode); 
54
55 
56 
57void tSystem::tCloseFile(tFileHandle f
58
59 if (!f
60 return
61 
62 fclose(f); 
63
64 
65 
66int tSystem::tReadFile(tFileHandle f, void* buffer, int sizeBytes
67
68 // Load the entire thing into memory. 
69 int numRead = int(fread((char*)buffer, 1, sizeBytes, f)); 
70 return numRead
71
72 
73 
74int tSystem::tWriteFile(tFileHandle f, const void* buffer, int sizeBytes
75
76 // Load the entire thing into memory. 
77 int numRead = int(fwrite((char*)buffer, 1, sizeBytes, f)); 
78 return numRead
79
80 
81 
82int tSystem::tFileTell(tFileHandle handle
83
84 return int(ftell(handle)); 
85
86 
87 
88int tSystem::tFileSeek(tFileHandle handle, int offsetBytes, tSeekOrigin seekOrigin
89
90 int origin = SEEK_SET
91 switch (seekOrigin
92
93 case tSeekOrigin::Beginning
94 origin = SEEK_SET
95 break
96 
97 case tSeekOrigin::Current
98 origin = SEEK_CUR
99 break
100 
101 case tSeekOrigin::End
102 origin = SEEK_END
103 break
104
105  
106 return fseek(handle, long(offsetBytes), origin); 
107
108 
109 
110int tSystem::tGetFileSize(tFileHandle file
111
112 if (!file
113 return 0
114 
115 tFileSeek(file, 0, tSeekOrigin::End); 
116 int fileSize = tFileTell(file); 
117 
118 tFileSeek(file, 0, tSeekOrigin::Beginning); // Go back to beginning. 
119 return fileSize
120
121 
122 
123int tSystem::tGetFileSize(const tString& filename
124
125 #ifdef PLATFORM_WINDOWS 
126 if (filename.IsEmpty()) 
127 return 0
128 
129 tString file(filename); 
130 file.Replace('/', '\\'); 
131 uint prevErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX); 
132 
133 Win32FindData fd; 
134 WinHandle h = FindFirstFile(file, &fd); 
135 
136 // If file doesn't exist, h will be invalid. 
137 if (h == INVALID_HANDLE_VALUE) 
138
139 SetErrorMode(prevErrorMode); 
140 return 0
141
142 
143 FindClose(h); 
144 SetErrorMode(prevErrorMode); 
145 return fd.nFileSizeLow; 
146 #else 
147 
148 tFileHandle fd = tOpenFile(filename, "rb"); 
149 int size = tGetFileSize(fd); 
150 tCloseFile(fd); 
151 
152 return size
153 #endif 
154
155 
156 
157tSystem::tFileType tSystem::tGetFileType(const tString& file
158
159 if (file.IsEmpty()) 
160 return tFileType::Unknown
161 
162 tString ext = tGetFileExtension(file); 
163 return tGetFileTypeFromExtension(ext); 
164
165 
166 
167tSystem::tFileType tSystem::tGetFileTypeFromExtension(const tString& e
168
169 tString ext(e); 
170 if (ext.IsEmpty()) 
171 return tFileType::Unknown
172 
173 ext.ToLower(); 
174 
175 struct ExtTypePair 
176
177 const char* Ext
178 tFileType Type
179 }; 
180  
181 ExtTypePair extToType[] = 
182
183 { "tga", tFileType::TGA }, 
184 { "bmp", tFileType::BMP }, 
185 { "png", tFileType::PNG }, 
186 { "apng", tFileType::APNG }, 
187 { "gif", tFileType::GIF }, 
188 { "webp", tFileType::WEBP }, 
189 { "xpm", tFileType::XPM }, 
190 { "jpg", tFileType::JPG }, 
191 { "jpeg", tFileType::JPG }, 
192 { "tif", tFileType::TIFF }, 
193 { "tiff", tFileType::TIFF }, 
194 { "dds", tFileType::DDS }, 
195 { "hdr", tFileType::HDR }, 
196 { "rgbe", tFileType::HDR }, 
197 { "exr", tFileType::EXR }, 
198 { "pcx", tFileType::PCX }, 
199 { "wbmp", tFileType::WBMP }, 
200 { "wmf", tFileType::WMF }, 
201 { "jp2", tFileType::JP2 }, 
202 { "jpc", tFileType::JPC }, 
203 { "ico", tFileType::ICO }, 
204 { "tex", tFileType::TEX }, 
205 { "img", tFileType::IMG }, 
206 { "cub", tFileType::CUB }, 
207 { "tac", tFileType::TAC }, 
208 { "tim", tFileType::TAC }, 
209 { "cfg", tFileType::CFG }, 
210 }; 
211 int numExtensions = sizeof(extToType)/sizeof(*extToType); 
212 
213 for (int e = 0; e < numExtensions; e++) 
214 if (ext == extToType[e].Ext
215 return extToType[e].Type
216 
217 return tFileType::Unknown;  
218
219 
220 
221bool tSystem::tFileExists(const tString& filename
222
223 #if defined(PLATFORM_WINDOWS) 
224 tString file(filename); 
225 file.Replace('/', '\\'); 
226 
227 int length = file.Length(); 
228 if (file[ length - 1 ] == ':'
229 file += "\\*"
230 
231 uint prevErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX); 
232 
233 Win32FindData fd; 
234 WinHandle h = FindFirstFile(file, &fd); 
235 SetErrorMode(prevErrorMode); 
236 if (h == INVALID_HANDLE_VALUE) 
237 return false
238 
239 FindClose(h); 
240 if (fd.dwFileAttributes & _A_SUBDIR) 
241 return false
242  
243 return true
244 
245 #else 
246 tString file(filename); 
247 file.Replace('\\', '/'); 
248 
249 struct stat statbuf
250 return stat(file.Chars(), &statbuf) == 0
251 
252 #endif 
253
254 
255 
256bool tSystem::tDirExists(const tString& dirname
257
258 if (dirname.IsEmpty()) 
259 return false
260  
261 tString dir = dirname
262  
263 #if defined(PLATFORM_WINDOWS) 
264 dir.Replace('/', '\\'); 
265 int length = dir.Length(); 
266 if (dir[ length - 1 ] == '\\'
267 dir[ length - 1 ] = '\0'
268 
269 length = dir.Length(); 
270 
271 // Can't quite remember what the * does. Needs testing. 
272 if (dir[ length - 1 ] == ':'
273 dir += "\\*"
274 
275 uint prevErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX); 
276 
277 Win32FindData fd; 
278 WinHandle h = FindFirstFile(dir, &fd); 
279 SetErrorMode(prevErrorMode); 
280 if (h == INVALID_HANDLE_VALUE) 
281 return false
282 
283 FindClose(h); 
284 if (fd.dwFileAttributes & _A_SUBDIR) 
285 return true
286 
287 return false
288 
289 #else 
290 dir.Replace('\\', '/'); 
291  
292 if (dir[dir.Length()-1] == '/'
293 dir[dir.Length()-1] = '\0'
294 
295 std::filesystem::file_status fstat = std::filesystem::status(dir.Chars()); 
296  
297 return std::filesystem::is_directory(fstat); 
298 #endif 
299
300 
301 
302#if defined(PLATFORM_WINDOWS) 
303bool tSystem::tDriveExists(const tString& driveLetter) 
304
305 tString drive = driveLetter; 
306 drive.ToUpper(); 
307 
308 char driveLet = drive[0]; 
309 if ((driveLet > 'Z') || (driveLet < 'A')) 
310 return false
311 
312 ulong driveBits = GetLogicalDrives(); 
313 if (driveBits & (0x00000001 << (driveLet-'A'))) 
314 return true
315 
316 return false
317
318#endif 
319 
320 
321bool tSystem::tIsFileNewer(const tString& filenameA, const tString& filenameB
322
323 #if defined(PLATFORM_WINDOWS) 
324 tString fileA(filenameA); 
325 fileA.Replace('/', '\\'); 
326 
327 tString fileB(filenameB); 
328 fileB.Replace('/', '\\'); 
329 
330 Win32FindData fd; 
331 WinHandle h = FindFirstFile(fileA, &fd); 
332 if (h == INVALID_HANDLE_VALUE) 
333 throw tFileError("Invalid file handle for file: " + fileA); 
334 
335 FileTime timeA = fd.ftLastWriteTime; 
336 FindClose(h); 
337 
338 h = FindFirstFile(fileB, &fd); 
339 if (h == INVALID_HANDLE_VALUE) 
340 throw tFileError("Invalid file handle for file: " + fileB); 
341 
342 FileTime timeB = fd.ftLastWriteTime; 
343 FindClose(h); 
344 
345 if (CompareFileTime(&timeA, &timeB) > 0
346 return true
347 
348 #elif defined(PLAYFORM_LINUX) 
349 tToDo("Implement tISFileNewer."); 
350 
351 #endif 
352 return false
353
354 
355 
356std::time_t tSystem::tFileTimeToStdTime(std::filesystem::file_time_type tp
357
358 using namespace std::chrono
359 auto sctp = time_point_cast<system_clock::duration>(tp - std::filesystem::file_time_type::clock::now() + system_clock::now()); 
360 return system_clock::to_time_t(sctp); 
361
362 
363 
364#ifdef PLATFORM_WINDOWS 
365std::time_t tSystem::tFileTimeToPosixEpoch(FILETIME filetime) 
366
367 LARGE_INTEGER date; 
368 date.HighPart = filetime.dwHighDateTime; 
369 date.LowPart = filetime.dwLowDateTime; 
370 
371 // Milliseconds. 
372 LARGE_INTEGER adjust; 
373 adjust.QuadPart = 11644473600000 * 10000
374  
375 // Removes the diff between 1970 and 1601. 
376 date.QuadPart -= adjust.QuadPart; 
377 
378 // Converts back from 100-nanoseconds (Windows) to seconds (Posix). 
379 return date.QuadPart / 10000000
380
381#endif 
382 
383 
384bool tSystem::tGetFileInfo(tFileInfo& fileInfo, const tString& fileName
385
386 fileInfo.Clear(); 
387 fileInfo.FileName = fileName
388 
389 tString file(fileName); 
390 #ifdef PLATFORM_WINDOWS 
391 file.Replace('/', '\\'); 
392 
393 // Seems like FindFirstFile cannot deal with a trailing backslash when 
394 // trying to access directory information. We remove it here. 
395 int l = file.Length(); 
396 if (file[l-1] == '\\'
397 file[l-1] = '\0'
398 
399 #else 
400 file.Replace('\\', '/'); 
401 
402 #endif 
403 
404 #ifdef PLATFORM_WINDOWS 
405 Win32FindData fd; 
406 WinHandle h = FindFirstFile(file, &fd); 
407 if (h == INVALID_HANDLE_VALUE) 
408 return false
409 FindClose(h); 
410 
411 fileInfo.CreationTime = tFileTimeToPosixEpoch(fd.ftCreationTime); 
412 fileInfo.ModificationTime = tFileTimeToPosixEpoch(fd.ftLastWriteTime); 
413 fileInfo.AccessTime = tFileTimeToPosixEpoch(fd.ftLastAccessTime); 
414 
415 // Occasionally, a file does not have a valid modification or access time. The fileInfo struct 
416 // may, erronously, contain a modification or access time that is smaller than the creation time! 
417 // This happens, for example, with some files that have been transferred from a USB camera. 
418 // In this case, we simply use the creation time for the modification or access times. 
419 // Actually, this happens quite a bit, sometimes even if the times are valid! 
420 // 
421 // On seconds thought... we're going to leave the modification date alone. Although I don't agree 
422 // with how the OS deals with this, I think the behaviour is best left untouched for mod time. 
423 // Basically, a file copied from, say, a mem card will get a current creation time, but the mod date 
424 // will be left at whatever the original file was. Silly, although perhaps a little defensible. 
425 // We still correct any possible problems with access date though. 
426 if (fileInfo.AccessTime < fileInfo.CreationTime) 
427 fileInfo.AccessTime = fileInfo.CreationTime; 
428 
429 tGetFileSize(file); 
430 fileInfo.FileSize = fd.nFileSizeHigh; 
431 fileInfo.FileSize <<= 32
432 fileInfo.FileSize |= fd.nFileSizeLow; 
433 
434 fileInfo.ReadOnly = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) ? true : false
435 fileInfo.Hidden = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) ? true : false
436 fileInfo.Directory = (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? true : false
437 return true
438 
439 #else 
440  
441 fileInfo.ReadOnly = tIsReadOnly(file); 
442 fileInfo.Hidden = tIsHidden(file); 
443 
444 struct stat statBuf
445 int errCode = stat(file.Chars(), &statBuf); 
446 if (errCode
447 return false
448  
449 fileInfo.FileSize = statBuf.st_size
450 fileInfo.Directory = ((statBuf.st_mode & S_IFMT) == S_IFDIR) ? true : false
451 
452 fileInfo.CreationTime = statBuf.st_ctime; // @todo I think this is not creation time. 
453 fileInfo.ModificationTime = statBuf.st_mtime
454 fileInfo.AccessTime = statBuf.st_atime
455 if (fileInfo.AccessTime < fileInfo.CreationTime
456 fileInfo.AccessTime = fileInfo.CreationTime
457 
458 return true
459 #endif 
460
461 
462 
463#ifdef PLATFORM_WINDOWS 
464bool tSystem::tGetFileDetails(tFileDetails& details, const tString& fullFileName) 
465
466 tString ffn = fullFileName; 
467 ffn.Replace('/', '\\'); 
468 if (ffn[ ffn.Length() - 1 ] == '\\'
469 ffn[ ffn.Length() - 1 ] = '\0'
470 
471 tString fileName = tSystem::tGetFileName(ffn); 
472 tString fileDir = tSystem::tGetDir(ffn); 
473 fileDir.Replace('/', '\\'); 
474 
475 if ((fileName.Length() == 2) && (fileName[1] == ':')) 
476
477 fileName += "\\"
478 fileDir = ""
479
480 
481 // This interface is used for freeing up PIDLs. 
482 lpMalloc mallocInterface = 0
483 hResult result = SHGetMalloc(&mallocInterface); 
484 if (!mallocInterface) 
485 return false
486 
487 // Get the desktop interface. 
488 IShellFolder* desktopInterface = 0
489 result = SHGetDesktopFolder(&desktopInterface); 
490 if (!desktopInterface) 
491
492 mallocInterface->Release(); 
493 return false
494
495 
496 // IShellFolder::ParseDisplayName requires the path name in Unicode wide characters. 
497 OleChar olePath[MaxPath]; 
498 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, fileDir.ConstText(), -1, olePath, MaxPath); 
499 
500 // Parse path for absolute PIDL, and connect to target folder. 
501 lpItemIdList pidl = 0
502 result = desktopInterface->ParseDisplayName(0, 0, olePath, 0, &pidl, 0); 
503 if (result != S_OK) 
504
505 desktopInterface->Release(); 
506 mallocInterface->Release(); 
507 return false
508
509 
510 IShellFolder2* shellFolder2 = 0
511 result = desktopInterface->BindToObject 
512
513 pidl, 0
514 IID_IShellFolder2, (void**)&shellFolder2 
515 ); 
516 
517 // Release what we no longer need. 
518 desktopInterface->Release(); 
519 mallocInterface->Free(pidl); 
520 
521 if ((result != S_OK) || !shellFolder2) 
522
523 mallocInterface->Release(); 
524 return false
525
526 
527 OleChar unicodeName[MaxPath]; 
528 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, fileName.ConstText(), -1, unicodeName, MaxPath); 
529 lpItemIdList localPidl = 0
530 result = shellFolder2->ParseDisplayName(0, 0, unicodeName, 0, &localPidl, 0); 
531 if (result != S_OK) 
532
533 mallocInterface->Release(); 
534 shellFolder2->Release(); 
535 return false
536
537 
538 int columnIndexArray[] = 
539
540 // 0, Name (Not Needed) 
541 1, // Size / Type (Logical Drives) 
542 2, // Type / Total Size (Logical Drives) 
543 3, // Date Modified / Free Space (Logical Drives) 
544 4, // Date Created / File System (Logical Drives) 
545 5, // Date Accessed / Comments (Logical Drives) 
546 6, // Attributes 
547 // 7, Status (Not Needed) 
548 // 8, Owner (Not Needed) 
549 9, // Author 
550 10, // Title 
551 11, // Subject 
552 12, // Category 
553 13, // Pages 
554 14, // Comments 
555 15, // Copyright 
556 16, // Artist 
557 17, // Album Title 
558 18, // Year 
559 19, // Track Number 
560 20, // Genre 
561 21, // Duration 
562 22, // Bit Rate 
563 23, // Protected 
564 24, // Camera Model 
565 25, // Date Picture Taken 
566 26, // Dimensions 
567 // 27, Blank 
568 // 28, Blank 
569 29, // Episode Name 
570 30, // Program Description 
571 // 31, Blank 
572 32, // Audio Sample Size 
573 33, // Audio Sample Rate 
574 34, // Channels 
575 // 35, File State (Too Slow) 
576 // 36, Rev (Useful but Too Slow) 
577 // 37, Action (Too Slow) 
578 38, // Company 
579 39, // Description 
580 40, // File VErsion 
581 41, // Product Name 
582 42 // Product Version 
583 }; 
584 
585 const int maxDetailColumnsToTry = sizeof(columnIndexArray)/sizeof(*columnIndexArray); 
586 for (int c = 0; c < maxDetailColumnsToTry; c++) 
587
588 int col = columnIndexArray[c]; 
589 SHELLDETAILS shellDetail; 
590 
591 // Get title. 
592 result = shellFolder2->GetDetailsOf(0, col, &shellDetail); 
593 if (result == S_OK) 
594
595 tString title(33); 
596 StrRetToBuf(&shellDetail.str, localPidl, title.Text(), 32); 
597 
598 // Get detail. 
599 tString detail(33); 
600 result = shellFolder2->GetDetailsOf(localPidl, col, &shellDetail); 
601 if (result == S_OK) 
602
603 StrRetToBuf(&shellDetail.str, localPidl, detail.Text(), 32); 
604 
605 // We only add the detail to the list if both title and detail are present. 
606 if (!title.IsEmpty() && !detail.IsEmpty()) 
607
608 details.DetailTitles.Append(new tStringItem(title)); 
609 details.Details.Append(new tStringItem(detail)); 
610
611
612
613
614 
615 mallocInterface->Free(localPidl); 
616 
617 // Release all remaining interface pointers. 
618 mallocInterface->Release(); 
619 shellFolder2->Release(); 
620 
621 return true
622
623 
624 
625void tSystem::tSetFileOpenAssoc(const tString& program, const tString& extension, const tString& programOptions) 
626
627 tString baseName = tSystem::tGetFileBaseName(program); 
628 baseName.ToLower(); 
629 
630 tString keyString = "Software\\Classes\\Tacent_"
631 keyString += baseName; 
632 keyString += "\\shell\\open\\command"
633 
634 HKEY key; 
635 if (RegCreateKeyEx(HKEY_CURRENT_USER, keyString.ConstText(), 0, 0, 0, KEY_SET_VALUE, 0, &key, 0) == ERROR_SUCCESS) 
636
637 // Create value string and set it. 
638 tString options = programOptions; 
639 if (options.IsEmpty()) 
640 options = " "
641 else 
642 options = tString(" ") + options + " "
643 tString valString = tString("\"") + tSystem::tGetSimplifiedPath(program) + "\"" + options + "\"%1\""
644 valString.Replace('/', '\\'); 
645 RegSetValueEx(key, "", 0, REG_SZ, (uint8*)valString.ConstText(), valString.Length()+1); 
646 RegCloseKey(key); 
647
648 
649 tString ext = extension; 
650 ext.ToLower(); 
651 keyString = "Software\\Classes\\."
652 keyString += ext; 
653 if (RegCreateKeyEx(HKEY_CURRENT_USER, keyString.ConstText(), 0, 0, 0, KEY_SET_VALUE, 0, &key, 0) == ERROR_SUCCESS) 
654
655 tString valString = "Tacent_"
656 valString += baseName; 
657 RegSetValueEx(key, "", 0, REG_SZ, (uint8*)valString.ConstText(), valString.Length()+1); 
658 RegCloseKey(key); 
659
660
661 
662 
663void tSystem::tSetFileOpenAssoc(const tString& program, const tList<tStringItem>& extensions, const tString& programOptions) 
664
665 for (auto ext = extensions.First(); ext; ext = ext->Next()) 
666 tSetFileOpenAssoc(program, *ext, programOptions); 
667
668 
669 
670tString tSystem::tGetFileOpenAssoc(const tString& extension) 
671
672 if (extension.IsEmpty()) 
673 return tString(); 
674 
675 HKEY key; 
676 tString ext = extension; 
677 ext.ToLower(); 
678 tString keyString = "Software\\Classes\\."
679 keyString += ext; 
680 tString appName(127); 
681 if (RegOpenKeyEx(HKEY_CURRENT_USER, keyString.ConstText(), 0, KEY_QUERY_VALUE, &key) == ERROR_SUCCESS) 
682
683 ulong numBytesIO = 127
684 RegGetValue(key, "", 0, RRF_RT_REG_SZ | RRF_ZEROONFAILURE, 0, appName.Text(), &numBytesIO); 
685 RegCloseKey(key); 
686
687 
688 if (appName.IsEmpty()) 
689 return tString(); 
690 
691 keyString = "Software\\Classes\\"
692 keyString += appName; 
693 keyString += "\\shell\\open\\command"
694 tString exeName(255); 
695 if (RegOpenKeyEx(HKEY_CURRENT_USER, keyString.ConstText(), 0, KEY_QUERY_VALUE, &key) == ERROR_SUCCESS) 
696
697 ulong numBytesIO = 255
698 RegGetValue(key, "", 0, RRF_RT_REG_SZ | RRF_ZEROONFAILURE, 0, exeName.Text(), &numBytesIO); 
699 RegCloseKey(key); 
700
701 
702 return exeName; 
703
704#endif // PLATFORM_WINDOWS 
705 
706 
707tString tSystem::tGetSimplifiedPath(const tString& srcPath, bool forceTreatAsDir
708
709 tString path = srcPath
710 path.Replace('\\', '/'); 
711 
712 // We do support filenames at the end. However, if the name ends with a "." (or "..") we 
713 // know it is a folder and so add a trailing "/". 
714 if (path[path.Length()-1] == '.'
715 path += "/"
716 
717 if (forceTreatAsDir && (path[path.Length()-1] != '/')) 
718 path += "/"
719 
720 if (tIsDrivePath(path)) 
721
722 if ((path[0] >= 'a') && (path[0] <= 'z')) 
723 path[0] = 'A' + (path[0] - 'a'); 
724
725 
726 // First we'll replace any "../" strings with "|". Note that pipe indicators are not allowed 
727 // in filenames so we can safely use them. 
728 int numUps = path.Replace("../", "|"); 
729 
730 // Now we can remove any "./" strings since all that's left will be up-directory markers. 
731 path.Remove("./"); 
732 if (!numUps
733 return path
734 
735 // We need to preserve leading '..'s so that paths like ../../Hello/There/ will work. 
736 int numLeading = path.RemoveLeading("|"); 
737 numUps -= numLeading
738 for (int nl = 0; nl < numLeading; nl++) 
739 path = "../" + path
740 
741 tString simp
742 for (int i = 0; i < numUps; i++) 
743
744 simp += path.ExtractLeft('|'); 
745 simp = tGetUpDir(simp); 
746
747 
748 tString res = simp + path
749 return res
750
751 
752 
753bool tSystem::tIsDrivePath(const tString& path
754
755 if ((path.Length() > 1) && (path[1] == ':')) 
756 return true
757 
758 return false
759
760 
761 
762bool tSystem::tIsAbsolutePath(const tString& path
763
764 if (tIsDrivePath(path)) 
765 return true
766 
767 if ((path.Length() > 0) && ((path[0] == '/') || (path[0] == '\\'))) 
768 return true
769 
770 return false
771
772 
773 
774tString tSystem::tGetFileName(const tString& filename
775
776 tString retStr(filename); 
777 retStr.Replace('\\', '/'); 
778 return retStr.Right('/'); 
779
780 
781 
782tString tSystem::tGetFileBaseName(const tString& filename
783
784 tString r = tGetFileName(filename); 
785 return r.Left('.'); 
786
787 
788 
789tString tSystem::tGetDir(const tString& path
790
791 tString ret(path); 
792 ret.Replace('\\', '/'); 
793 
794 // If string is empty or there is no filename on the end of the path just return what we have. 
795 if (ret.IsEmpty() || (ret[ret.Length()-1] == '/')) 
796 return ret
797 
798 int lastSlash = ret.FindChar('/', true); 
799 
800 // If there is no path, treat it as if it were a stand-alone file and return the current directory. 
801 if (lastSlash == -1
802 return tString("./"); 
803 
804 // At this point, we know there was a slash and that it isn't the last character, so 
805 // we know we aren't going out of bounds when we insert our string terminator after the slash. 
806 ret[ lastSlash + 1 ] = '\0'
807 
808 return ret
809
810 
811 
812tString tSystem::tGetUpDir(const tString& path, int levels
813
814 if (path.IsEmpty()) 
815 return tString(); 
816 
817 tString ret(path); 
818 
819 bool isNetLoc = false
820 ret.Replace('\\', '/'); 
821 
822 // Can't go up from here. 
823 if (ret == "/"
824 return ret
825 if (tIsDrivePath(ret)) 
826
827 if (ret.Length() == 2
828 return ret + "/"
829 if ((ret.Length() == 3) && (ret[2] == '/')) 
830 return ret
831
832 
833 #ifdef PLATFORM_WINDOWS 
834 // Are we a network location starting with two slashes? 
835 if ((ret.Length() >= 2) && (ret[0] == '/') && (ret[1] == '/')) 
836 isNetLoc = true
837 #endif 
838 
839 if (isNetLoc
840
841 ret[0] = '\\'
842 ret[1] = '\\'
843
844 
845 tString upPath = ret
846 upPath[ upPath.Length() - 1 ] = '\0'
847 
848 for (int i = 0; i < levels; i++) 
849
850 int lastSlash = upPath.FindChar('/', true); 
851 
852 if (isNetLoc && (upPath.CountChar('/') == 1)) 
853 lastSlash = -1
854 
855 if (lastSlash == -1
856 return tString(); 
857 
858 upPath[lastSlash] = '\0'
859
860 
861 upPath += "/"
862 
863 if (isNetLoc
864
865 ret[0] = '/'
866 ret[1] = '/'
867
868 return upPath
869
870 
871 
872tString tSystem::tGetRelativePath(const tString& basePath, const tString& path
873
874 #if defined(PLATFORM_WINDOWS) 
875 tString relLoc(MAX_PATH); 
876 tAssert(basePath[ basePath.Length() - 1 ] == '/'); 
877 bool isDir = (path[ path.Length() - 1 ] == '/') ? true : false
878 
879 tString basePathMod = basePath; 
880 basePathMod.Replace('/', '\\'); 
881 
882 tString pathMod = path; 
883 pathMod.Replace('/', '\\'); 
884 
885 int success = PathRelativePathTo 
886
887 relLoc.Text(), basePathMod, FILE_ATTRIBUTE_DIRECTORY, 
888 pathMod, isDir ? FILE_ATTRIBUTE_DIRECTORY : 0 
889 ); 
890 
891 if (!success) 
892 return tString(); 
893 
894 relLoc.Replace('\\', '/'); 
895 
896 if (relLoc[0] == '/'
897 return relLoc.ConstText() + 1
898 else 
899 return relLoc; 
900 
901 #else 
902 tString refPath(basePath); 
903 tString absPath(path); 
904  
905 int sizer = refPath.Length()+1
906 int sizea = absPath.Length()+1
907 if (sizea <= 1
908 return tString(); 
909 if (sizer<= 1
910 return absPath
911 
912 // From stackoverflow cuz I don't feel like thinking. 
913 // https://stackoverflow.com/questions/36173695/how-to-retrieve-filepath-relatively-to-a-given-directory-in-c  
914 char relPath[1024]; 
915 relPath[0] = '\0'
916 char* pathr = refPath.Text(); 
917 char* patha = absPath.Text(); 
918 int inc = 0
919 
920 for (; (inc < sizea) && (inc < sizer); inc += tStd::tStrlen(patha+inc)+1
921
922 char* tokena = tStd::tStrchr(patha+inc, '/'); 
923 char* tokenr = tStd::tStrchr(pathr+inc, '/'); 
924  
925 if (tokena) *tokena = '\0'
926 if (tokenr) *tokenr = '\0'
927 if (tStd::tStrcmp(patha+inc, pathr+inc) != 0
928 break
929
930 
931 if (inc < sizea
932 tStd::tStrcat(relPath, absPath.Text()+inc); 
933  
934 tString ret(relPath); 
935 if (ret[ret.Length()-1] != '/'
936 ret += '/'
937  
938 return ret
939 #endif 
940
941 
942 
943tString tSystem::tGetAbsolutePath(const tString& pth, const tString& basePath
944
945 tString path(pth); 
946 path.Replace('\\', '/'); 
947 if (tIsRelativePath(path)) 
948
949 if (basePath.IsEmpty()) 
950 path = tGetCurrentDir() + path
951 else 
952 path = basePath + path
953
954 
955 return tGetSimplifiedPath(path); 
956
957 
958 
959tString tSystem::tGetLinuxPath(const tString& pth, const tString& mountPoint
960
961 tString path(pth); 
962 path.Replace('\\', '/'); 
963 if (tIsAbsolutePath(path) && (path.Length() > 1) && (path[1] == ':') && !mountPoint.IsEmpty()) 
964
965 tString mnt = mountPoint
966 mnt.Replace('\\', '/'); 
967 if (mnt[ mnt.Length() - 1 ] != '/'
968 mnt += "/"
969 
970 char drive = tStd::tChrlwr(path[0]); 
971 path.ExtractLeft(2); 
972 path = mnt + tString(drive) + path
973
974 return path
975
976 
977 
978tString tSystem::tGetFileExtension(const tString& filename)  
979
980 tString ext = filename.Right('.');  
981 if(ext == filename
982 ext.Clear(); 
983 
984 return ext
985
986 
987 
988tString tSystem::tGetFileFullName(const tString& filename
989
990 tString file(filename); 
991  
992 #if defined(PLATFORM_WINDOWS) 
993 file.Replace('/', '\\'); 
994 tString ret(_MAX_PATH + 1); 
995 _fullpath(ret.Text(), file, _MAX_PATH); 
996 ret.Replace('\\', '/'); 
997  
998 #else 
999 
1000 file.Replace('\\', '/'); 
1001 tString ret(PATH_MAX + 1); 
1002 realpath(file, ret.Text());  
1003 #endif 
1004 
1005 return ret
1006
1007 
1008 
1009uint32 tSystem::tHashFileFast32(const tString& filename, uint32 iv
1010
1011 int dataSize = 0
1012 uint8* data = tLoadFile(filename, nullptr, &dataSize); 
1013 if (!data
1014 return iv
1015 
1016 uint32 hash = tHash::tHashDataFast32(data, dataSize, iv); 
1017 delete[] data
1018 return hash
1019
1020 
1021 
1022uint32 tSystem::tHashFile32(const tString& filename, uint32 iv
1023
1024 int dataSize = 0
1025 uint8* data = tLoadFile(filename, nullptr, &dataSize); 
1026 if (!data
1027 return iv
1028 
1029 uint32 hash = tHash::tHashData32(data, dataSize, iv); 
1030 delete[] data
1031 return hash
1032
1033 
1034 
1035uint64 tSystem::tHashFile64(const tString& filename, uint64 iv
1036
1037 int dataSize = 0
1038 uint8* data = tLoadFile(filename, nullptr, &dataSize); 
1039 if (!data
1040 return iv
1041 
1042 uint64 hash = tHash::tHashData64(data, dataSize, iv); 
1043 delete[] data
1044 return hash
1045
1046 
1047 
1048tuint128 tSystem::tHashFileMD5(const tString& filename, tuint128 iv
1049
1050 int dataSize = 0
1051 uint8* data = tLoadFile(filename, nullptr, &dataSize); 
1052 if (!data
1053 return iv
1054 
1055 tuint128 hash = tHash::tHashData128(data, dataSize, iv); 
1056 delete[] data
1057 return hash
1058
1059 
1060 
1061tuint256 tSystem::tHashFile256(const tString& filename, tuint256 iv
1062
1063 int dataSize = 0
1064 uint8* data = tLoadFile(filename, nullptr, &dataSize); 
1065 if (!data
1066 return iv
1067 
1068 tuint256 hash = tHash::tHashData256(data, dataSize, iv); 
1069 delete[] data
1070 return hash
1071
1072 
1073 
1074bool tSystem::tIsReadOnly(const tString& fileName
1075
1076 tString file(fileName); 
1077 
1078 #if defined(PLATFORM_WINDOWS) 
1079 file.Replace('/', '\\'); 
1080 int length = file.Length(); 
1081 if ((file[length - 1] == '/') || (file[length - 1] == '\\')) 
1082 file[length - 1] = '\0'
1083 
1084 // The docs for this should be clearer! GetFileAttributes returns INVALID_FILE_ATTRIBUTES if it 
1085 // fails. Rather dangerously, and undocumented, INVALID_FILE_ATTRIBUTES has a value of 0xFFFFFFFF. 
1086 // This means that all attribute are apparently true! This is very lame. Thank goodness there aren't 
1087 // 32 possible attributes, or there could be real problems. Too bad it didn't just return 0 on error... 
1088 // especially since they specifically have a FILE_ATTRIBUTES_NORMAL flag that is non-zero! 
1089 ulong attribs = GetFileAttributes(file); 
1090 if (attribs == INVALID_FILE_ATTRIBUTES) 
1091 return false
1092 
1093 return (attribs & FILE_ATTRIBUTE_READONLY) ? true : false
1094 
1095 #else 
1096 file.Replace('\\', '/'); 
1097 
1098 struct stat st
1099 int errCode = stat(file, &st); 
1100 if (errCode != 0
1101 return false
1102 
1103 bool w = (st.st_mode & S_IWUSR) ? true : false
1104 bool r = (st.st_mode & S_IRUSR) ? true : false
1105 return r && !w
1106 
1107 #endif 
1108
1109 
1110 
1111bool tSystem::tSetReadOnly(const tString& fileName, bool readOnly
1112
1113 tString file(fileName); 
1114  
1115 #if defined(PLATFORM_WINDOWS)  
1116 file.Replace('/', '\\'); 
1117 int length = file.Length(); 
1118 if ((file[length - 1] == '/') || (file[length - 1] == '\\')) 
1119 file[length - 1] = '\0'
1120 
1121 ulong attribs = GetFileAttributes(file); 
1122 if (attribs == INVALID_FILE_ATTRIBUTES) 
1123 return false
1124 
1125 if (!(attribs & FILE_ATTRIBUTE_READONLY) && readOnly) 
1126 SetFileAttributes(file, attribs | FILE_ATTRIBUTE_READONLY); 
1127 else if ((attribs & FILE_ATTRIBUTE_READONLY) && !readOnly) 
1128 SetFileAttributes(file, attribs & ~FILE_ATTRIBUTE_READONLY); 
1129 
1130 attribs = GetFileAttributes(file); 
1131 if (attribs == INVALID_FILE_ATTRIBUTES) 
1132 return false
1133 
1134 if (!!(attribs & FILE_ATTRIBUTE_READONLY) == readOnly) 
1135 return true
1136 
1137 return false
1138 
1139 #else 
1140 file.Replace('\\', '/'); 
1141  
1142 struct stat st
1143 int errCode = stat(file, &st); 
1144 if (errCode != 0
1145 return false
1146  
1147 uint32 permBits = st.st_mode
1148 
1149 // Set user R and clear user w. Leave rest unchanged. 
1150 permBits |= S_IRUSR
1151 permBits &= ~S_IWUSR
1152 errCode = chmod(file, permBits); 
1153  
1154 return (errCode == 0); 
1155 
1156 #endif 
1157
1158 
1159 
1160bool tSystem::tIsHidden(const tString& path
1161
1162 // Even in windows treat files starting with a dot as hidden. 
1163 tString fileName = tGetFileName(path); 
1164 if ((fileName != ".") && (fileName != "..") && (fileName[0] == '.')) 
1165 return true
1166 
1167 // In windows also check the attribute. 
1168 #if defined(PLATFORM_WINDOWS) 
1169 tString file(path); 
1170 file.Replace('/', '\\'); 
1171 int length = file.Length(); 
1172 if ((file[length - 1] == '/') || (file[length - 1] == '\\')) 
1173 file[length - 1] = '\0'
1174 
1175 ulong attribs = GetFileAttributes(file); 
1176 if (attribs == INVALID_FILE_ATTRIBUTES) 
1177 return false
1178 
1179 return (attribs & FILE_ATTRIBUTE_HIDDEN) ? true : false
1180 
1181 #else 
1182 return false
1183 
1184 #endif 
1185
1186 
1187 
1188#if defined(PLATFORM_WINDOWS) 
1189bool tSystem::tSetHidden(const tString& fileName, bool hidden) 
1190
1191 tString file(fileName); 
1192 file.Replace('/', '\\'); 
1193 int length = file.Length(); 
1194 if ((file[length - 1] == '/') || (file[length - 1] == '\\')) 
1195 file[length - 1] = '\0'
1196 
1197 ulong attribs = GetFileAttributes(file); 
1198 if (attribs == INVALID_FILE_ATTRIBUTES) 
1199 return false
1200 
1201 if (!(attribs & FILE_ATTRIBUTE_HIDDEN) && hidden) 
1202 SetFileAttributes(file, attribs | FILE_ATTRIBUTE_HIDDEN); 
1203 else if ((attribs & FILE_ATTRIBUTE_HIDDEN) && !hidden) 
1204 SetFileAttributes(file, attribs & ~FILE_ATTRIBUTE_HIDDEN); 
1205 
1206 attribs = GetFileAttributes(file); 
1207 if (attribs == INVALID_FILE_ATTRIBUTES) 
1208 return false
1209 
1210 if (!!(attribs & FILE_ATTRIBUTE_HIDDEN) == hidden) 
1211 return true
1212 
1213 return false
1214
1215 
1216 
1217bool tSystem::tIsSystem(const tString& fileName) 
1218
1219 tString file(fileName); 
1220 file.Replace('/', '\\'); 
1221 int length = file.Length(); 
1222 if ((file[length - 1] == '/') || (file[length - 1] == '\\')) 
1223 file[length - 1] = '\0'
1224 
1225 ulong attribs = GetFileAttributes(file); 
1226 if (attribs == INVALID_FILE_ATTRIBUTES) 
1227 return false
1228 
1229 return (attribs & FILE_ATTRIBUTE_SYSTEM) ? true : false
1230
1231 
1232 
1233bool tSystem::tSetSystem(const tString& fileName, bool system) 
1234
1235 tString file(fileName); 
1236 file.Replace('/', '\\'); 
1237 int length = file.Length(); 
1238 if ((file[length - 1] == '/') || (file[length - 1] == '\\')) 
1239 file[length - 1] = '\0'
1240 
1241 ulong attribs = GetFileAttributes(file); 
1242 if (attribs == INVALID_FILE_ATTRIBUTES) 
1243 return false
1244 
1245 if (!(attribs & FILE_ATTRIBUTE_SYSTEM) && system) 
1246 SetFileAttributes(file, attribs | FILE_ATTRIBUTE_SYSTEM); 
1247 else if ((attribs & FILE_ATTRIBUTE_SYSTEM) && !system) 
1248 SetFileAttributes(file, attribs & ~FILE_ATTRIBUTE_SYSTEM); 
1249 
1250 attribs = GetFileAttributes(file); 
1251 if (attribs == INVALID_FILE_ATTRIBUTES) 
1252 return false
1253 
1254 if (!!(attribs & FILE_ATTRIBUTE_SYSTEM) == system) 
1255 return true
1256 
1257 return false
1258
1259 
1260 
1261void tSystem::tGetDrives(tList<tStringItem>& drives) 
1262
1263 ulong ad = GetLogicalDrives(); 
1264 
1265 char driveLet = 'A'
1266 for (int i = 0; i < 26; i++) 
1267
1268 if (ad & 0x00000001
1269
1270 tStringItem* drive = new tStringItem(driveLet); 
1271 *drive += ":"
1272 drives.Append(drive); 
1273
1274 
1275 driveLet++; 
1276 ad = ad >> 1
1277
1278
1279 
1280 
1281bool tSystem::tGetDriveInfo(tDriveInfo& driveInfo, const tString& drive, bool getDisplayName, bool getVolumeAndSerial) 
1282
1283 tString driveRoot = drive; 
1284 driveRoot.ToUpper(); 
1285 
1286 if (driveRoot.Length() == 1) // Assume string was of form "C" 
1287 driveRoot += ":\\"
1288 else if (driveRoot.Length() == 2) // Assume string was of form "C:" 
1289 driveRoot += "\\"
1290 else // Assume string was of form "C:/" or "C:\" 
1291 driveRoot.Replace('/', '\\'); 
1292 
1293 uint driveType = GetDriveType(driveRoot); 
1294 switch (driveType) 
1295
1296 case DRIVE_NO_ROOT_DIR: 
1297 return false
1298 
1299 case DRIVE_REMOVABLE: 
1300 if ((driveRoot == "A:\\") || (driveRoot == "B:\\")) 
1301 driveInfo.DriveType = tDriveType::Floppy; 
1302 else 
1303 driveInfo.DriveType = tDriveType::Removable; 
1304 break
1305 
1306 case DRIVE_FIXED: 
1307 driveInfo.DriveType = tDriveType::HardDisk; 
1308 break
1309 
1310 case DRIVE_REMOTE: 
1311 driveInfo.DriveType = tDriveType::Network; 
1312 break
1313 
1314 case DRIVE_CDROM: 
1315 driveInfo.DriveType = tDriveType::Optical; 
1316 break
1317 
1318 case DRIVE_RAMDISK: 
1319 driveInfo.DriveType = tDriveType::RamDisk; 
1320 break
1321 
1322 case DRIVE_UNKNOWN: 
1323 default
1324 driveInfo.DriveType = tDriveType::Unknown; 
1325 break
1326
1327 
1328 if (getDisplayName) 
1329
1330 // Here we try getting the name by using the shell api. It should give the 
1331 // same name as seen by windows explorer. 
1332 SHFILEINFO fileInfo; 
1333 fileInfo.szDisplayName[0] = '\0'
1334 SHGetFileInfo 
1335
1336 driveRoot.ConstText(), 
1337 0
1338 &fileInfo, 
1339 sizeof(SHFILEINFO), 
1340 SHGFI_DISPLAYNAME 
1341 ); 
1342 driveInfo.DisplayName = fileInfo.szDisplayName; 
1343
1344 
1345 if (getVolumeAndSerial) 
1346
1347 tString volumeInfoName(256); 
1348 ulong componentLength = 0
1349 ulong flags = 0
1350 ulong serial = 0
1351 int success = GetVolumeInformation 
1352
1353 driveRoot, 
1354 volumeInfoName.Text(), 
1355 256
1356 &serial, 
1357 &componentLength, 
1358 &flags, 
1359 0, // File system name not needed. 
1360 0 // Buffer for system name is 0 long. 
1361 ); 
1362 
1363 driveInfo.VolumeName = volumeInfoName; 
1364 driveInfo.SerialNumber = serial; 
1365
1366 
1367 return true
1368
1369 
1370 
1371bool tSystem::tSetVolumeName(const tString& drive, const tString& newVolumeName) 
1372
1373 tString driveRoot = drive; 
1374 driveRoot.ToUpper(); 
1375 
1376 if (driveRoot.Length() == 1) // Assume string was of form "C" 
1377 driveRoot += ":\\"
1378 else if (driveRoot.Length() == 2) // Assume string was of form "C:" 
1379 driveRoot += "\\"
1380 else // Assume string was of form "C:/" or "C:\" 
1381 driveRoot.Replace('/', '\\'); 
1382 
1383 int success = SetVolumeLabel(driveRoot.ConstText(), newVolumeName.ConstText()); 
1384 return success ? true : false
1385
1386 
1387 
1388tString tSystem::tGetWindowsDir() 
1389
1390 tString windir(MAX_PATH); 
1391 GetWindowsDirectory(windir.Text(), MAX_PATH); 
1392 windir.Replace('\\', '/'); 
1393 if (windir[windir.Length()-1] != '/'
1394 windir += "/"
1395 
1396 return windir; 
1397
1398 
1399 
1400tString tSystem::tGetSystemDir() 
1401
1402 tString sysdir(MAX_PATH); 
1403 GetSystemDirectory(sysdir.Text(), MAX_PATH); 
1404 sysdir.Replace('\\', '/'); 
1405 if (sysdir[sysdir.Length()-1] != '/'
1406 sysdir += "/"
1407 
1408 return sysdir; 
1409
1410#endif 
1411 
1412 
1413#ifdef PLATFORM_LINUX 
1414tString tSystem::tGetHomeDir() 
1415
1416 const char* homeDir = getenv("HOME"); 
1417 if (!homeDir
1418 homeDir = getpwuid(getuid())->pw_dir
1419  
1420 if (!homeDir
1421 return tString(); 
1422  
1423 tString res(homeDir); 
1424 if (res[res.Length()-1] != '/'
1425 res += '/'
1426 return res
1427
1428#endif 
1429 
1430 
1431tString tSystem::tGetProgramDir() 
1432
1433 #if defined(PLATFORM_WINDOWS) 
1434 tString result(MAX_PATH + 1); 
1435 ulong l = GetModuleFileName(0, result.Text(), MAX_PATH); 
1436 result.Replace('\\', '/'); 
1437 
1438 int bi = result.FindChar('/', true); 
1439 tAssert(bi != -1); 
1440 
1441 result[bi + 1] = '\0'
1442 return result; 
1443 
1444 #elif defined(PLATFORM_LINUX) 
1445 tString result(PATH_MAX+1); 
1446 readlink("/proc/self/exe", result.Text(), PATH_MAX); 
1447  
1448 int bi = result.FindChar('/', true); 
1449 tAssert(bi != -1); 
1450 result[bi + 1] = '\0'
1451 return result
1452 
1453 #else 
1454 return tString(); 
1455 
1456 #endif 
1457
1458 
1459 
1460tString tSystem::tGetProgramPath() 
1461
1462 #if defined(PLATFORM_WINDOWS) 
1463 tString result(MAX_PATH + 1); 
1464 ulong l = GetModuleFileName(0, result.Text(), MAX_PATH); 
1465 result.Replace('\\', '/'); 
1466 return result; 
1467 
1468 #elif defined(PLATFORM_LINUX) 
1469 tString result(PATH_MAX+1); 
1470 readlink("/proc/self/exe", result.Text(), PATH_MAX); 
1471 return result
1472 
1473 #else 
1474 return tString(); 
1475 
1476 #endif 
1477
1478 
1479 
1480tString tSystem::tGetCurrentDir() 
1481
1482 #ifdef PLATFORM_WINDOWS 
1483 tString r(MAX_PATH + 1); 
1484 GetCurrentDirectory(MAX_PATH, r.Text()); 
1485 
1486 #else 
1487 tString r(PATH_MAX + 1); 
1488 getcwd(r.Text(), PATH_MAX); 
1489 
1490 #endif 
1491 r.Replace('\\', '/'); 
1492 int l = r.Length(); 
1493 if (r[l - 1] != '/'
1494 r += "/"
1495 
1496 return r
1497
1498 
1499 
1500bool tSystem::tSetCurrentDir(const tString& directory
1501
1502 if (directory.IsEmpty()) 
1503 return false
1504 
1505 tString dir = directory
1506 
1507 #ifdef PLATFORM_WINDOWS 
1508 dir.Replace('/', '\\'); 
1509 tString cd; 
1510 
1511 // "." and ".." get left alone. 
1512 if ((dir == ".") || (dir == "..")) 
1513
1514 cd = dir; 
1515
1516 else 
1517
1518 if (dir.FindChar(':') != -1
1519 cd = dir; 
1520 else 
1521 cd = ".\\" + dir; 
1522 
1523 if (cd[cd.Length() - 1] != '\\'
1524 cd += "\\"
1525
1526 
1527 // So there is no dialog asking user to insert a floppy. 
1528 uint prevErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS); 
1529 int success = SetCurrentDirectory(cd); 
1530 SetErrorMode(prevErrorMode); 
1531 
1532 return success ? true : false
1533 
1534 #else 
1535 dir.Replace('\\', '/'); 
1536 int errCode = chdir(dir.Chars()); 
1537 return (errCode == 0); 
1538 
1539 #endif 
1540
1541 
1542 
1543bool tSystem::tFilesIdentical(const tString& fileA, const tString& fileB
1544
1545 auto localCloseFiles = [](tFileHandle a, tFileHandle b
1546
1547 tCloseFile(a); 
1548 tCloseFile(b); 
1549 }; 
1550 
1551 tFileHandle fa = tOpenFile(fileA, "rb"); 
1552 tFileHandle fb = tOpenFile(fileB, "rb"); 
1553 if (!fa || !fb
1554
1555 localCloseFiles(fa, fb); 
1556 return false
1557
1558 
1559 int faSize = tGetFileSize(fa); 
1560 int fbSize = tGetFileSize(fb); 
1561 if (faSize != fbSize
1562
1563 localCloseFiles(fa, fb); 
1564 return false
1565
1566 
1567 uint8* bufA = new uint8[faSize]; 
1568 uint8* bufB = new uint8[fbSize]; 
1569 
1570 int numReadA = tReadFile(fa, bufA, faSize); 
1571 int numReadB = tReadFile(fb, bufB, fbSize); 
1572 tAssert((faSize + fbSize) == (numReadA + numReadB)); 
1573 
1574 for (int i = 0; i < faSize; i++) 
1575
1576 if (bufA[i] != bufB[i]) 
1577
1578 localCloseFiles(fa, fb); 
1579 delete[] bufA
1580 delete[] bufB
1581 return false
1582
1583
1584 
1585 localCloseFiles(fa, fb); 
1586 delete[] bufA
1587 delete[] bufB
1588 return true
1589
1590 
1591 
1592bool tSystem::tCreateFile(const tString& file
1593
1594 tFileHandle f = tOpenFile(file.ConstText(), "wt"); 
1595 if (!f
1596 return false
1597 
1598 tCloseFile(f); 
1599 return true
1600
1601 
1602 
1603bool tSystem::tCreateFile(const tString& filename, const tString& contents
1604
1605 uint32 len = contents.Length(); 
1606 return tCreateFile(filename, (uint8*)contents.ConstText(), len); 
1607
1608 
1609 
1610bool tSystem::tCreateFile(const tString& filename, uint8* data, int dataLength
1611
1612 tFileHandle dst = tOpenFile(filename.ConstText(), "wb"); 
1613 if (!dst
1614 return false
1615 
1616 // Sometimes this needs to be done, for some mysterious reason. 
1617 tFileSeek(dst, 0, tSeekOrigin::Beginning); 
1618 
1619 // Write data and close file. 
1620 int numWritten = tWriteFile(dst, data, dataLength); 
1621 tCloseFile(dst); 
1622 
1623 // Make sure it was created and an appropriate amount of bytes were written. 
1624 bool verify = tFileExists(filename); 
1625 return verify && (numWritten >= dataLength); 
1626
1627 
1628 
1629bool tSystem::tCreateDir(const tString& dir
1630
1631 tString dirPath = dir
1632  
1633 #if defined(PLATFORM_WINDOWS) 
1634 dirPath.Replace('/', '\\'); 
1635 
1636 bool success = ::CreateDirectory(dirPath.ConstText(), 0) ? true : false
1637 if (!success) 
1638 success = tDirExists(dirPath.ConstText()); 
1639 
1640 return success; 
1641 
1642 #else 
1643 dirPath.Replace('\\', '/'); 
1644 if (dirPath[dirPath.Length()-1] == '/'
1645 dirPath[dirPath.Length()-1] = '\0'
1646 bool ok = std::filesystem::create_directory(dirPath.Chars()); 
1647 if (!ok
1648 return tDirExists(dirPath.Chars()); 
1649 
1650 return ok
1651 
1652 #endif 
1653
1654 
1655 
1656bool tSystem::tLoadFile(const tString& filename, tString& dst, char convertZeroesTo
1657
1658 if (!tFileExists(filename)) 
1659
1660 dst.Clear(); 
1661 return false
1662
1663 
1664 int filesize = tGetFileSize(filename); 
1665 if (filesize == 0
1666
1667 dst.Clear(); 
1668 return true
1669
1670 
1671 dst.Reserve(filesize); 
1672 uint8* check = tLoadFile(filename, (uint8*)dst.Text()); 
1673 if ((check != (uint8*)dst.Text()) || !check
1674 return false
1675 
1676 if (convertZeroesTo != '\0'
1677
1678 for (int i = 0; i < filesize; i++) 
1679 if (dst[i] == '\0'
1680 dst[i] = convertZeroesTo
1681
1682 
1683 return true
1684
1685 
1686 
1687uint8* tSystem::tLoadFile(const tString& filename, uint8* buffer, int* fileSize, bool appendEOF
1688
1689 tFileHandle f = tOpenFile(filename.ConstText(), "rb"); 
1690 if (!f
1691
1692 if (fileSize
1693 *fileSize = 0
1694 return nullptr
1695
1696 
1697 int size = tGetFileSize(f); 
1698 if (fileSize
1699 *fileSize = size
1700 
1701 if (size == 0
1702
1703 // It is perfectly valid to load a file with no data (0 bytes big). 
1704 // In this case we always return 0 even if a non-zero buffer was passed in. 
1705 // The fileSize member will already be set if necessary. 
1706 tCloseFile(f); 
1707 return nullptr
1708
1709 
1710 bool bufferAllocatedHere = false
1711 if (!buffer
1712
1713 int bufSize = appendEOF ? size+1 : size
1714 buffer = new uint8[bufSize]; 
1715 bufferAllocatedHere = true
1716
1717 
1718 int numRead = tReadFile(f, buffer, size); // Load the entire thing into memory. 
1719 tAssert(numRead == size); 
1720 
1721 if (appendEOF
1722 buffer[numRead] = EOF
1723 
1724 tCloseFile(f); 
1725 return buffer
1726
1727 
1728 
1729uint8* tSystem::tLoadFileHead(const tString& fileName, int& bytesToRead, uint8* buffer
1730
1731 tFileHandle f = tOpenFile(fileName, "rb"); 
1732 if (!f
1733
1734 bytesToRead = 0
1735 return buffer
1736
1737 
1738 int size = tGetFileSize(f); 
1739 if (!size
1740
1741 tCloseFile(f); 
1742 bytesToRead = 0
1743 return buffer
1744
1745 
1746 bytesToRead = (size < bytesToRead) ? size : bytesToRead
1747 
1748 bool bufferAllocatedHere = false
1749 if (!buffer
1750
1751 buffer = new uint8[bytesToRead]; 
1752 bufferAllocatedHere = true
1753
1754 
1755 // Load the first bytesToRead into memory. We assume complete failure if the 
1756 // number we asked for was not returned. 
1757 int numRead = tReadFile(f, buffer, bytesToRead); 
1758 if (numRead != bytesToRead
1759
1760 if (bufferAllocatedHere
1761
1762 delete[] buffer
1763 buffer = 0
1764
1765 
1766 tCloseFile(f); 
1767 bytesToRead = 0
1768 return buffer
1769
1770 
1771 tCloseFile(f); 
1772 return buffer
1773
1774 
1775 
1776bool tSystem::tCopyFile(const tString& dest, const tString& src, bool overWriteReadOnly
1777
1778 #if defined(PLATFORM_WINDOWS) 
1779 int success = ::CopyFile(src, dest, 0); 
1780 if (!success && overWriteReadOnly) 
1781
1782 tSetReadOnly(dest, false); 
1783 success = ::CopyFile(src, dest, 0); 
1784
1785 
1786 return success ? true : false
1787 
1788 #else 
1789 std::filesystem::path pathFrom(src.Chars()); 
1790 std::filesystem::path pathTo(dest.Chars()); 
1791 bool success = std::filesystem::copy_file(pathFrom, pathTo); 
1792 if (!success && overWriteReadOnly
1793
1794 tSetReadOnly(dest, false); 
1795 success = std::filesystem::copy_file(pathFrom, pathTo); 
1796
1797  
1798 return success
1799 
1800 #endif 
1801
1802 
1803 
1804bool tSystem::tRenameFile(const tString& dir, const tString& oldName, const tString& newName
1805
1806 #if defined(PLATFORM_WINDOWS) 
1807 tString fullOldName = dir + oldName; 
1808 fullOldName.Replace('/', '\\'); 
1809 
1810 tString fullNewName = dir + newName; 
1811 fullNewName.Replace('/', '\\'); 
1812 
1813 int success = ::MoveFile(fullOldName, fullNewName); 
1814 return success ? true : false
1815 
1816 #else 
1817 tString fullOldName = dir + oldName
1818 fullOldName.Replace('\\', '/'); 
1819 std::filesystem::path oldp(fullOldName.Chars()); 
1820 
1821 tString fullNewName = dir + newName
1822 fullNewName.Replace('\\', '/'); 
1823 std::filesystem::path newp(fullNewName.Chars()); 
1824 
1825 std::error_code ec
1826 std::filesystem::rename(oldp, newp, ec); 
1827 return !bool(ec); 
1828 
1829 #endif 
1830
1831 
1832 
1833bool tSystem::tFindDirs(tList<tStringItem>& foundDirs, const tString& dir, bool includeHidden
1834
1835 #ifdef PLATFORM_WINDOWS 
1836 
1837 // First lets massage fileName a little. 
1838 tString massagedName = dir; 
1839 if ((massagedName[massagedName.Length() - 1] == '/') || (massagedName[massagedName.Length() - 1] == '\\')) 
1840 massagedName += "*.*"
1841 
1842 Win32FindData fd; 
1843 WinHandle h = FindFirstFile(massagedName, &fd); 
1844 if (h == INVALID_HANDLE_VALUE) 
1845 return false
1846 
1847 tString path = tGetDir(massagedName); 
1848 do 
1849
1850 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 
1851
1852 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) || includeHidden) 
1853
1854 // If the directory name is not "." or ".." then it's a real directory. 
1855 // Note that you cannot just check for the first character not being "." Some directories (and files) 
1856 // may have a name that starts with a dot, especially if they were copied from a unix machine. 
1857 tString fn(fd.cFileName); 
1858 if ((fn != ".") && (fn != "..")) 
1859 foundDirs.Append(new tStringItem(path + fd.cFileName + "/")); 
1860
1861
1862 } while (FindNextFile(h, &fd)); 
1863 
1864 FindClose(h); 
1865 if (GetLastError() != ERROR_NO_MORE_FILES) 
1866 return false
1867 
1868 #else 
1869 tString dirPath(dir); 
1870 if (dirPath.IsEmpty()) 
1871 dirPath = std::filesystem::current_path().u8string().c_str(); 
1872 
1873 std::error_code errorCode
1874 for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(dirPath.Text(), errorCode)) 
1875
1876 if (errorCode || (entry == std::filesystem::directory_entry())) 
1877
1878 errorCode.clear(); 
1879 continue
1880
1881 
1882 if (!entry.is_directory()) 
1883 continue
1884 
1885 tString foundDir(entry.path().u8string().c_str()); 
1886  
1887 // All directories end in a slash in tacent. 
1888 if (foundDir[foundDir.Length()-1] != '/'
1889 foundDir += "/"
1890 if (includeHidden || !tIsHidden(foundDir)) 
1891 foundDirs.Append(new tStringItem(foundDir)); 
1892
1893 
1894 #endif 
1895 return true
1896
1897 
1898 
1899bool tSystem::tFindFiles(tList<tStringItem>& foundFiles, const tString& dir, const tString& ext, bool includeHidden
1900
1901 #ifdef PLATFORM_WINDOWS 
1902 
1903 // FindFirstFile etc seem to like backslashes better. 
1904 tString dirStr(dir); 
1905 dirStr.Replace('/', '\\'); 
1906 
1907 if (dirStr[dirStr.Length() - 1] != '\\'
1908 dirStr += "\\"
1909 
1910 tString path = dirStr + "*."
1911 if (ext.IsEmpty()) 
1912 path += "*"
1913 else 
1914 path += ext; 
1915 
1916 Win32FindData fd; 
1917 WinHandle h = FindFirstFile(path, &fd); 
1918 if (h == INVALID_HANDLE_VALUE) 
1919 return false
1920 
1921 do 
1922
1923 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) 
1924
1925 // It's not a directory... so it's actually a real file. 
1926 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) || includeHidden) 
1927
1928 tStringItem* newItem = new tStringItem(dirStr + fd.cFileName); 
1929 newItem->Replace('\\', '/'); 
1930 
1931 // Holy obscure and annoying FindFirstFile bug! FindFirstFile("*.abc", ...) will also find 
1932 // files like file.abcd. This isn't correct I guess we have to check the extension here. 
1933 // FileMask is required to specify an extension, even if it is ".*" 
1934 if (path[path.Length() - 1] != '*'
1935
1936 tString foundExtension = tGetFileExtension(fd.cFileName); 
1937 if (ext.IsEqualCI(foundExtension)) 
1938 foundFiles.Append(newItem); 
1939
1940 else 
1941
1942 foundFiles.Append(newItem); 
1943
1944
1945
1946 } while (FindNextFile(h, &fd)); 
1947 
1948 FindClose(h); 
1949 if (GetLastError() != ERROR_NO_MORE_FILES) 
1950 return false
1951 
1952 #else 
1953 tString dirPath(dir); 
1954 
1955 // Use current directory if no dirPath supplied. 
1956 if (dirPath.IsEmpty()) 
1957 dirPath = std::filesystem::current_path().u8string().c_str(); 
1958 
1959 // Even root should look like "/". 
1960 if (dirPath.IsEmpty()) 
1961 return false
1962 
1963 // if ((dirPath[dirPath.Length() - 1] == '/') || (dirPath[dirPath.Length() - 1] == '\\')) 
1964 // dirPath[dirPath.Length() - 1] = '\0'; 
1965 if (dirPath[dirPath.Length() - 1] == '\\'
1966 dirPath[dirPath.Length() - 1] = '/'
1967 
1968 std::error_code errorCode
1969 for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(dirPath.Text(), errorCode)) 
1970
1971 if (errorCode || (entry == std::filesystem::directory_entry())) 
1972
1973 errorCode.clear(); 
1974 continue
1975
1976 
1977 if (!entry.is_regular_file()) 
1978 continue
1979 
1980 tString foundFile(entry.path().u8string().c_str()); 
1981 if (!ext.IsEmpty() && (!ext.IsEqualCI(tGetFileExtension(foundFile)))) 
1982 continue
1983 
1984 if (includeHidden || !tIsHidden(foundFile)) 
1985 foundFiles.Append(new tStringItem(foundFile)); 
1986
1987 #endif 
1988 
1989 return true
1990
1991 
1992 
1993bool tSystem::tFindFilesRecursive(tList<tStringItem>& foundFiles, const tString& dir, const tString& ext, bool includeHidden
1994
1995 #ifdef PLATFORM_WINDOWS 
1996 // The windows functions seem to like backslashes better. 
1997 tString pathStr(dir); 
1998 pathStr.Replace('/', '\\'); 
1999 
2000 if (pathStr[pathStr.Length() - 1] != '\\'
2001 pathStr += "\\"
2002 
2003 tFindFiles(foundFiles, dir, ext, includeHidden); 
2004 Win32FindData fd; 
2005 
2006 // Look for all directories. 
2007 WinHandle h = FindFirstFile(pathStr + "*.*", &fd); 
2008 if (h == INVALID_HANDLE_VALUE) 
2009 return false
2010 
2011 do 
2012
2013 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 
2014
2015 // Don't recurse into hidden subdirectories if includeHidden is false. 
2016 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) || includeHidden) 
2017
2018 // If the directory name is not "." or ".." then it's a real directory. 
2019 // Note that you cannot just check for the first character not being "." Some directories (and files) 
2020 // may have a name that starts with a dot, especially if they were copied from a unix machine. 
2021 tString fn(fd.cFileName); 
2022 if ((fn != ".") && (fn != "..")) 
2023 tFindFilesRecursive(foundFiles, pathStr + fd.cFileName + "\\", ext, includeHidden); 
2024
2025
2026 } while (FindNextFile(h, &fd)); 
2027 
2028 FindClose(h); 
2029 if (GetLastError() != ERROR_NO_MORE_FILES) 
2030 return false
2031 
2032 #else 
2033 for (const std::filesystem::directory_entry& entry: std::filesystem::recursive_directory_iterator(dir.Chars())) 
2034
2035 if (!entry.is_regular_file()) 
2036 continue
2037 
2038 tString foundFile(entry.path().u8string().c_str()); 
2039 if (!ext.IsEmpty() && (!ext.IsEqualCI(tGetFileExtension(foundFile)))) 
2040 continue
2041 
2042 if (includeHidden || !tIsHidden(foundFile)) 
2043 foundFiles.Append(new tStringItem(foundFile)); 
2044
2045 #endif 
2046 
2047 return true
2048
2049 
2050 
2051bool tSystem::tFindDirsRecursive(tList<tStringItem>& foundDirs, const tString& dir, bool includeHidden
2052
2053 #ifdef PLATFORM_WINDOWS 
2054 tString pathStr(dir); 
2055 pathStr.Replace('/', '\\'); 
2056 if (pathStr[pathStr.Length() - 1] != '\\'
2057 pathStr += "\\"
2058 
2059 tFindDirs(foundDirs, pathStr, includeHidden); 
2060 Win32FindData fd; 
2061 WinHandle h = FindFirstFile(pathStr + "*.*", &fd); 
2062 if (h == INVALID_HANDLE_VALUE) 
2063 return false
2064 
2065 do 
2066
2067 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 
2068
2069 // Don't recurse into hidden subdirectories if includeHidden is false. 
2070 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) || includeHidden) 
2071
2072 // If the directory name is not "." or ".." then it's a real directory. 
2073 // Note that you cannot just check for the first character not being "." Some directories (and files) 
2074 // may have a name that starts with a dot, especially if they were copied from a unix machine. 
2075 tString fn(fd.cFileName); 
2076 if ((fn != ".") && (fn != "..")) 
2077 tFindDirsRecursive(foundDirs, pathStr + fd.cFileName + "\\", includeHidden); 
2078
2079
2080 } while (FindNextFile(h, &fd)); 
2081 
2082 FindClose(h); 
2083 if (GetLastError() != ERROR_NO_MORE_FILES) 
2084 return false
2085 
2086 #else 
2087 for (const std::filesystem::directory_entry& entry : std::filesystem::recursive_directory_iterator(dir.Chars())) 
2088
2089 if (!entry.is_directory()) 
2090 continue
2091 
2092 tString foundDir(entry.path().u8string().c_str()); 
2093 
2094 if (includeHidden || !tIsHidden(foundDir)) 
2095 foundDirs.Append(new tStringItem(foundDir)); 
2096
2097 
2098 #endif 
2099 return true
2100
2101 
2102 
2103bool tSystem::tDeleteFile(const tString& filename, bool deleteReadOnly, bool useRecycleBin
2104
2105 #ifdef PLATFORM_WINDOWS 
2106 tString file(filename); 
2107 file.Replace('/', '\\'); 
2108 if (deleteReadOnly) 
2109 SetFileAttributes(file, FILE_ATTRIBUTE_NORMAL); 
2110 
2111 if (!useRecycleBin) 
2112
2113 if (DeleteFile(file)) 
2114 return true
2115 else 
2116 return false
2117
2118 else 
2119
2120 tString filenameDoubleNull = filename + "Z"
2121 filenameDoubleNull[filenameDoubleNull.Length()-1] = '\0'
2122 SHFILEOPSTRUCT operation; 
2123 tStd::tMemset(&operation, 0, sizeof(operation)); 
2124 operation.wFunc = FO_DELETE; 
2125 operation.pFrom = filenameDoubleNull.ConstText(); 
2126 operation.fFlags = FOF_ALLOWUNDO | FOF_NO_UI | FOF_NORECURSION; 
2127 int errCode = SHFileOperation(&operation); 
2128 return errCode ? false : true
2129
2130 
2131 return true
2132  
2133 #else 
2134 if (!deleteReadOnly && tIsReadOnly(filename)) 
2135 return true
2136  
2137 std::filesystem::path p(filename.Chars()); 
2138  
2139 if (useRecycleBin
2140
2141 tString homeDir = tGetHomeDir(); 
2142 tString recycleDir = homeDir + ".local/share/Trash/files/"
2143 if (tDirExists(recycleDir)) 
2144
2145 tString toFile = recycleDir + tGetFileName(filename); 
2146 std::filesystem::path toPath(toFile.Chars()); 
2147 std::error_code ec
2148 std::filesystem::rename(p, toPath, ec); 
2149 return ec ? false : true
2150
2151  
2152 return false
2153
2154 
2155 return std::filesystem::remove(p); 
2156 #endif 
2157
2158 
2159 
2160bool tSystem::tDeleteDir(const tString& dir, bool deleteReadOnly
2161
2162 #ifdef PLATFORM_WINDOWS 
2163 // Are we done before we even begin? 
2164 if (!tDirExists(dir)) 
2165 return false
2166 
2167 tList<tStringItem> fileList; 
2168 tFindFiles(fileList, dir); 
2169 tStringItem* file = fileList.First(); 
2170 while (file) 
2171
2172 tDeleteFile(*file, deleteReadOnly); // We don't really care whether it succeeded or not. 
2173 file = file->Next(); 
2174
2175 
2176 fileList.Empty(); // Clean up the file list. 
2177 tString directory(dir); 
2178 directory.Replace('/', '\\'); 
2179 
2180 Win32FindData fd; 
2181 WinHandle h = FindFirstFile(directory + "*.*", &fd); 
2182 if (h == INVALID_HANDLE_VALUE) 
2183 return true
2184 
2185 do 
2186
2187 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 
2188
2189 // If the directory name is not "." or ".." then it's a real directory. 
2190 // Note that you cannot just check for the first character not being "." Some directories (and files) 
2191 // may have a name that starts with a dot, especially if they were copied from a unix machine. 
2192 tString fn(fd.cFileName); 
2193 if ((fn != ".") && (fn != "..")) 
2194 tDeleteDir(dir + fd.cFileName + "/", deleteReadOnly); 
2195
2196 } while (FindNextFile(h, &fd)); 
2197 
2198 bool deleteFilesOK = (GetLastError() == ERROR_NO_MORE_FILES) ? true : false
2199 FindClose(h); 
2200 
2201 if (deleteReadOnly) 
2202 SetFileAttributes(directory, FILE_ATTRIBUTE_NORMAL); // Directories can be read-only too. 
2203 
2204 bool success = false
2205 for (int delTry = 0; delTry < 32; delTry++) 
2206
2207 if (RemoveDirectory(dir)) 
2208
2209 success = true
2210 break
2211
2212 
2213 // In some cases we might need to wait just a little and try again. This can even take up to 10 seconds or so. 
2214 // This seems to happen a lot when the target manager is streaming music, say, from the folder. 
2215 else if (GetLastError() == ERROR_DIR_NOT_EMPTY) 
2216
2217 tSystem::tSleep(500); 
2218
2219 else 
2220
2221 tSystem::tSleep(10); 
2222
2223
2224 
2225 if (!success || !deleteFilesOK) 
2226 return false
2227 
2228 #else 
2229 // Are we done before we even begin? 
2230 if (!tDirExists(dir)) 
2231 return false
2232 
2233 if (tIsReadOnly(dir) && !deleteReadOnly
2234 return true
2235 
2236 std::filesystem::path p(dir.Chars()); 
2237 std::error_code ec
2238 uintmax_t numRemoved = std::filesystem::remove_all(p, ec); 
2239 if (ec
2240 return false
2241 
2242 #endif 
2243 
2244 return true
2245
2246