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