1 // Copyright 2001-2018 Crytek GmbH / Crytek Group. All rights reserved.
5 #include <CryCore/Platform/CryWindows.h>
6 #include <CryString/CryString.h>
7 #include <CryString/CryFixedString.h>
11 #if CRY_PLATFORM_POSIX
12 #define CRY_NATIVE_PATH_SEPSTR "/"
14 #define CRY_NATIVE_PATH_SEPSTR "\\"
22 struct IsValidStringType
: std::false_type
{};
25 struct IsValidStringType
<string
> : std::true_type
{};
28 struct IsValidStringType
<wstring
> : std::true_type
{};
31 struct IsValidStringType
<CryStackStringT
<char, Size
>> : std::true_type
{};
34 struct IsValidStringType
<CryStackStringT
<wchar_t, Size
>> : std::true_type
{};
37 struct IsValidStringType
<CryFixedStringT
<Size
>> : std::true_type
{};
40 struct IsValidStringType
<CryFixedWStringT
<Size
>> : std::true_type
{};
43 struct SStringConstants
45 static constexpr const char* ForwardSlash
= "/";
46 static constexpr const char* BackSlash
= "\\";
50 struct SStringConstants
<wstring
>
52 static constexpr const wchar_t* ForwardSlash
= L
"/";
53 static constexpr const wchar_t* BackSlash
= L
"\\";
57 //! Convert a path to the uniform form.
58 template<typename TString
>
59 typename
std::enable_if
<detail::IsValidStringType
<TString
>::value
, TString
>::type
60 inline /*TString*/ ToUnixPath(const TString
& strPath
)
62 if (strPath
.find('\\') != TString::npos
)
66 path
.replace('\\', '/');
68 std::replace(path
.begin(), path
.end(), '\\', '/');
75 inline string
ToUnixPath(const char* szPath
)
77 return ToUnixPath(string(szPath
));
80 //! Convert a path to the DOS form.
81 template<typename TString
>
82 typename
std::enable_if
<detail::IsValidStringType
<TString
>::value
, TString
>::type
83 inline /*TString*/ ToDosPath(const TString
& strPath
)
85 if (strPath
.find('/') != TString::npos
)
89 path
.replace('/', '\\');
91 std::replace(path
.begin(), path
.end(), '/', '\\');
98 inline string
ToDosPath(const char* szPath
)
100 return ToDosPath(string(szPath
));
103 inline bool IsStrValid(const char* str
)
108 inline bool IsRelativePath(const char* p
)
114 return p
[0] != '/' && p
[0] != '\\' && !strchr(p
, ':');
117 //! Split full file name to path and filename.
118 //! \param[in] filepath Full file name including path.
119 //! \param[out] path Extracted file path.
120 //! \param[out] filename Extracted file (without extension).
121 //! \param[out] extension Extracted file's extension (without .).
122 template<typename TString
>
123 typename
std::enable_if
<detail::IsValidStringType
<TString
>::value
>::type
124 inline /*void*/ Split(const TString
& filepath
, TString
& path
, TString
& filename
, TString
& extension
)
126 path
= filename
= extension
= TString();
127 if (filepath
.empty())
129 const char* szFilepath
= filepath
.c_str();
130 const char* szExtension
= szFilepath
+ filepath
.length() - 1;
132 for (p
= szExtension
; p
>= szFilepath
; --p
)
139 path
= TString(szFilepath
, p
+ 1);
140 filename
= TString(p
+ 1, szExtension
);
143 // there's an extension in this file name
144 extension
= filepath
.substr(p
- szFilepath
+ 1);
149 filename
= filepath
.substr(p
- szFilepath
+ 1, szExtension
- p
);
152 template<typename TString
>
153 typename
std::enable_if
<detail::IsValidStringType
<TString
>::value
>::type
154 inline /*void*/ Split(const char* szFilepath
, TString
& path
, TString
& filename
, TString
& extension
)
156 return Split(TString(szFilepath
), path
, filename
, extension
);
159 //! Split full file name to path and filename.
160 //! \param[in] filepath Full file name including path.
161 //! \param[out] path Extracted file path.
162 //! \param[out] file Extracted file (with extension).
163 template<typename TString
>
164 typename
std::enable_if
<detail::IsValidStringType
<TString
>::value
>::type
165 inline /*void*/ Split(const TString
& filepath
, TString
& path
, TString
& file
)
168 Split(filepath
, path
, file
, extension
);
169 if (!extension
.empty())
171 file
+= '.' + extension
;
175 template<typename TString
>
176 typename
std::enable_if
<detail::IsValidStringType
<TString
>::value
>::type
177 inline /*void*/ Split(const char* szFilepath
, TString
& path
, TString
& file
)
179 return Split(TString(szFilepath
), path
, file
);
182 //! Extract extension from full specified file path.
183 //! \return Pointer to the extension (without .) or pointer to an empty 0-terminated string.
184 inline const char* GetExt(const char* filepath
)
186 const char* szFilepath
= filepath
;
187 const size_t length
= strlen(filepath
);
188 for (const char* p
= szFilepath
+ length
- 1; p
>= szFilepath
; --p
)
195 // we've reached a path separator - it means there's no extension in this name
198 // there's an extension in this file name
205 //! Removes filename from path. Example: "some/directory/file.ext" => "some/directory/"
206 template<typename TString
>
207 typename
std::enable_if
<detail::IsValidStringType
<TString
>::value
, TString
>::type
208 inline /*TString*/ GetPathWithoutFilename(const TString
& filepath
)
210 const char* szFilepath
= filepath
.c_str();
211 for (const char* p
= szFilepath
+ filepath
.length() - 1; p
>= szFilepath
; --p
)
218 return filepath
.substr(0, p
- szFilepath
+ 1);
224 //! Removes filename from path. Example: "some/directory/file.ext" => "some/directory/"
225 inline string
GetPathWithoutFilename(const char* filepath
)
227 return GetPathWithoutFilename(string(filepath
));
230 //! Extract file name with extension from full specified file path.
231 template<typename TString
>
232 typename
std::enable_if
<detail::IsValidStringType
<TString
>::value
, TString
>::type
233 inline /*TString*/ GetFile(const TString
& filepath
)
235 const char* szFilepath
= filepath
.c_str();
236 for (const char* p
= szFilepath
+ filepath
.length() - 1; p
>= szFilepath
; --p
)
243 return filepath
.substr(p
- szFilepath
+ 1);
249 inline const char* GetFile(const char* szFilepath
)
251 const size_t len
= strlen(szFilepath
);
252 for (const char* p
= szFilepath
+ len
- 1; p
>= szFilepath
; --p
)
265 //! Remove extension for given file.
266 template<typename TString
>
267 typename
std::enable_if
<detail::IsValidStringType
<TString
>::value
, void>::type
268 inline /*void*/ RemoveExtension(TString
& filepath
)
270 const char* szFilepath
= filepath
.c_str();
271 for (const char* p
= szFilepath
+ filepath
.length() - 1; p
>= szFilepath
; --p
)
278 // we've reached a path separator - it means there's no extension in this name
281 // there's an extension in this file name
282 filepath
.erase(p
- szFilepath
);
286 // it seems the file name is a pure name, without extension
289 //! Remove extension for given file.
290 inline void RemoveExtension(char* szFilePath
)
292 for (char* p
= szFilePath
+ strlen(szFilePath
) - 1; p
>= szFilePath
; --p
)
299 // we've reached a path separator - it means there's no extension in this name
302 // there's an extension in this file name
307 // it seems the file name is a pure name, without extension
310 template<typename TString
>
311 typename
std::enable_if
<detail::IsValidStringType
<TString
>::value
, TString
>::type
312 inline /*TString*/ RemoveExtension(const TString
& filepath
)
314 auto result
= filepath
;
315 RemoveExtension(result
);
319 inline string
RemoveExtension(const char* szFilepath
)
321 return RemoveExtension(string(szFilepath
));
324 //! Extract file name without extension from full specified file path.
325 template<typename TString
>
326 typename
std::enable_if
<detail::IsValidStringType
<TString
>::value
, TString
>::type
327 inline /*TString*/ GetFileName(const TString
& filepath
)
329 return GetFile(RemoveExtension(filepath
));
332 inline string
GetFileName(const char* szFilepath
)
334 return GetFileName(string(szFilepath
));
337 //! Removes the trailing slash or backslash from a given path.
338 template<typename TString
>
339 typename
std::enable_if
<detail::IsValidStringType
<TString
>::value
, TString
>::type
340 inline /*TString*/ RemoveSlash(const TString
& path
)
342 if (path
.empty() || (path
[path
.length() - 1] != '/' && path
[path
.length() - 1] != '\\'))
344 return path
.substr(0, path
.length() - 1);
347 inline string
RemoveSlash(const char* szPath
)
349 return RemoveSlash(string(szPath
));
352 inline string
GetEnginePath()
354 char szEngineRootDir
[_MAX_PATH
];
355 CryFindEngineRootFolder(CRY_ARRAY_COUNT(szEngineRootDir
), szEngineRootDir
);
356 return RemoveSlash(szEngineRootDir
);
359 //! Add a slash if necessary.
360 template<typename TString
>
361 typename
std::enable_if
<detail::IsValidStringType
<TString
>::value
, TString
>::type
362 inline /*TString*/ AddSlash(const TString
& path
)
364 if (path
.empty() || path
[path
.length() - 1] == '/')
366 if (path
[path
.length() - 1] == '\\')
367 return path
.substr(0, path
.length() - 1) + detail::SStringConstants
<TString
>::ForwardSlash
;
368 return path
+ detail::SStringConstants
<TString
>::ForwardSlash
;
371 inline string
AddSlash(const char* szPath
)
373 return AddSlash(string(szPath
));
376 //! Add a backslash if necessary.
377 template<typename TString
>
378 typename
std::enable_if
<detail::IsValidStringType
<TString
>::value
, TString
>::type
379 inline /*TString*/ AddBackslash(const TString
& path
)
381 if (path
.empty() || path
[path
.length() - 1] == '\\')
383 if (path
[path
.length() - 1] == '/')
384 return path
.substr(0, path
.length() - 1) + detail::SStringConstants
<TString
>::BackSlash
;
385 return path
+ detail::SStringConstants
<TString
>::BackSlash
;
388 inline string
AddBackslash(const char* szPath
)
390 return AddBackslash(string(szPath
));
393 //! Replace extension for given file.
394 template<typename TString
>
395 typename
std::enable_if
<detail::IsValidStringType
<TString
>::value
, TString
>::type
396 inline /*TString*/ ReplaceExtension(const TString
& filepath
, const TString
& extension
)
399 RemoveExtension(str
);
400 if (extension
[0] != '\0' && extension
[0] != '.')
408 inline string
ReplaceExtension(const char* szFilepath
, const char* szExtension
)
410 return ReplaceExtension(string(szFilepath
), string(szExtension
));
413 //! Makes a fully specified file path from path and file name.
414 template<typename TString
>
415 typename
std::enable_if
<detail::IsValidStringType
<TString
>::value
, TString
>::type
416 inline /*TString*/ Make(const TString
& directory
, const TString
& filename
)
418 return AddSlash(directory
) + filename
;
421 inline string
Make(const char* szPath
, const char* szFilename
)
423 return Make(string(szPath
), string(szFilename
));
426 //! Makes a fully specified file path from path and file name.
427 template<typename TString
>
428 typename
std::enable_if
<detail::IsValidStringType
<TString
>::value
, TString
>::type
429 inline /*TString*/ Make(const TString
& directory
, const TString
& filename
, const TString
& extension
)
431 return AddSlash(directory
) + ReplaceExtension(filename
, extension
);
434 inline string
Make(const char* szPath
, const char* szFilename
, const char* szExtension
)
436 return Make(string(szPath
), string(szFilename
), string(szExtension
));
439 template<typename TString
>
440 typename
std::enable_if
<detail::IsValidStringType
<TString
>::value
, TString
>::type
441 inline /*TString*/ GetParentDirectory(const TString
& filePath
, int generation
= 1)
443 for (const char* p
= filePath
.c_str() + filePath
.length() - 2; // -2 is for the possible trailing slash: there always must be some trailing symbol which is the file/directory name for which we should get the parent
444 p
>= filePath
.c_str();
450 return TString(filePath
.c_str(), p
);
453 // we've reached a path separator - return everything before it.
455 return TString(filePath
.c_str(), p
);
460 // it seems the file name is a pure name, without path or extension
464 inline string
GetParentDirectory(const char* szFilePath
, int generation
= 1)
466 return GetParentDirectory(string(szFilePath
), generation
);
469 //! \return True if the string matches the wildcard.
470 inline bool MatchWildcard(const char* szString
, const char* szWildcard
)
472 const char* pString
= szString
, * pWildcard
= szWildcard
;
473 // skip the obviously the same starting substring
474 while (*pWildcard
&& *pWildcard
!= '*' && *pWildcard
!= '?')
476 if (*pString
!= *pWildcard
)
477 return false; // must be exact match unless there's a wildcard character in the wildcard string
479 ++pString
, ++pWildcard
;
484 // this will only match if there are no non-wild characters in the wildcard
485 for (; *pWildcard
; ++pWildcard
)
487 if (*pWildcard
!= '*' && *pWildcard
!= '?')
496 return false; // the only way to match them after the leading non-wildcard characters is !*pString, which was already checked
498 // we have a wildcard with wild character at the start.
501 // merge consecutive ? and *, since they are equivalent to a single *
502 while (*pWildcard
== '*' || *pWildcard
== '?')
506 return true; // the rest of the string doesn't matter: the wildcard ends with *
508 for (; *pString
; ++pString
)
510 if (MatchWildcard(pString
, pWildcard
))
518 return MatchWildcard(pString
+ 1, pWildcard
+ 1) || MatchWildcard(pString
, pWildcard
+ 1);
526 inline std::vector
<string
> GetDirectoryStructure(const string
& path
)
530 return std::vector
<string
>();
533 string currentDirectoryName
;
534 const char* pchCurrentPosition
= path
.c_str();
535 const char* pchLastPosition
= path
.c_str();
537 // It removes as many slashes the path has in its start...
538 // MAYBE and just maybe we should consider paths starting with
539 // more than 2 slashes invalid paths...
540 while ((*pchLastPosition
== '\\') || (*pchLastPosition
== '/'))
543 ++pchCurrentPosition
;
546 std::vector
<string
> result
;
549 pchCurrentPosition
= strpbrk(pchLastPosition
, "\\/");
550 if (pchCurrentPosition
== nullptr)
554 currentDirectoryName
.assign(pchLastPosition
, pchCurrentPosition
);
555 pchLastPosition
= pchCurrentPosition
+ 1;
556 // Again, here we are skipping as many consecutive slashes.
557 while ((*pchLastPosition
== '\\') || (*pchLastPosition
== '/'))
561 result
.push_back(currentDirectoryName
);
570 //! Allows Posix paths:
571 //! - absolute path (/foo/bar)
572 //! - relative path (foo, ../bar) paths
573 //! Output slashes are '/'
576 //! Allows Posix paths as well as these Windows specific paths:
577 //! - UNC path (\\server\foo\bar)
578 //! - drive-absolute path (c:\foo\bar)
579 //! - drive-relative path (c:foo, c:..\bar)
580 //! Output slashes are '\'
583 #if CRY_PLATFORM_WINAPI
584 ePathStyle_Native
= ePathStyle_Windows
,
585 #elif CRY_PLATFORM_POSIX
586 ePathStyle_Native
= ePathStyle_Posix
,
588 #error Native path style is not supported
592 //! Simplifies a given file path, such that . and .. are removed (if possible).
593 //! Slashes are unified according to the path style and redundant and trailing slashes are removed.
594 //! The path-style also determines what inputs are accepted by the function. See EPathStyle enumeration values for more information.
595 //! If an error occurs or if the buffer is too small, the buffer will contain an empty string and the function returns false.
596 //! \note The output will always be shorter or equal length to the input, so a buffer of the same size as the input will always work.
597 inline bool SimplifyFilePath(const char* const szInput
, char* const szBuf
, const size_t bufLength
, const EPathStyle pathStyle
)
599 #define CRY_SIMPLIFY_REJECT { *szBuf = 0; return false; }
600 #define CRY_SIMPLIFY_EMIT(expr) { if (--pOut < szBuf) { *szBuf = 0; return false; } *pOut = expr; }
602 if (szBuf
== nullptr || bufLength
== 0)
606 if (szInput
== nullptr || (*szInput
== 0))
611 const bool bWinApi
= pathStyle
== ePathStyle_Windows
;
612 const char kSlash
= bWinApi
? '\\' : '/';
613 const char* pIn
= szInput
+ strlen(szInput
);
614 const char* pLast
= pIn
- 1;
615 const char* pLastSlash
= pIn
;
616 char* pOut
= szBuf
+ bufLength
;
617 size_t skipElements
= 0;
619 char driveRelative
= 0;
621 CRY_SIMPLIFY_EMIT(0); // null-terminator
622 while (pIn
!= szInput
)
624 assert(pIn
>= szInput
);
625 const char c
= *--pIn
;
630 if ((pIn
== szInput
+ 1) && ((szInput
[0] == '\\') || (szInput
[0] == '/'))) // UNC path
632 if (!bWinApi
|| bDots
|| skipElements
!= 0)
636 CRY_SIMPLIFY_EMIT(kSlash
);
637 CRY_SIMPLIFY_EMIT(kSlash
);
641 else if (bDots
) // handle redundant slashes and . and .. elements
643 const size_t numDots
= pLastSlash
- pIn
- 1;
648 else if ((numDots
!= 0) && (numDots
!= 1) && (pIn
!= pLast
))
653 else if (skipElements
!= 0) // mark eaten element
657 if ((*pOut
!= kSlash
) && (skipElements
== 0))
659 if (*pOut
!= '\0') // don't emit trailing slashes
661 CRY_SIMPLIFY_EMIT(kSlash
);
663 else if (pIn
== szInput
|| (bWinApi
&& pIn
[-1] == ':')) // exception for single slash input '/' and 'c:\'
665 CRY_SIMPLIFY_EMIT(kSlash
);
673 if (bDots
) // count dots
680 if (bWinApi
) // ':' should belong to a drive, otherwise it is not an allowed char in win
682 if ((pIn
!= szInput
+ 1) || ((pLastSlash
== szInput
+ 2) && (skipElements
!= 0)))
686 else // handle drive identifier
688 const char driveLetter
= pIn
[-1];
689 if (!(driveLetter
>= 'a' && driveLetter
<= 'z') && !(driveLetter
>= 'A' && driveLetter
<= 'Z'))
693 if (pLastSlash
== szInput
+ 2)
695 if (skipElements
!= 0)
702 const size_t numDots
= pLastSlash
- pIn
- 1;
705 CRY_SIMPLIFY_EMIT('.');
706 CRY_SIMPLIFY_EMIT('.');
708 else if (numDots
!= 1)
713 else if (skipElements
!= 0)
717 driveRelative
= driveLetter
;
727 if (skipElements
== 0)
729 const size_t numDots
= pLastSlash
- pIn
- 1;
730 for (size_t i
= 0; i
< numDots
; ++i
)
732 CRY_SIMPLIFY_EMIT('.');
741 CRY_SIMPLIFY_EMIT(c
);
745 if (bDots
) // record remaining dots
747 const size_t numDots
= pLastSlash
- szInput
;
752 else if (numDots
== 1)
754 if ((*pOut
== kSlash
) && (skipElements
== 0))
756 ++pOut
; // leading dot should eat a slash
760 CRY_SIMPLIFY_EMIT('.'); // special case, the input is only a dot, keep it
763 else if (skipElements
!= 0)
768 else if (skipElements
&& !driveRelative
) // if not bDots, then we read a relative element that needs to be discounted, e.g. a/..
773 for (size_t i
= 0; i
< skipElements
; ++i
) // flush all pending dots
775 CRY_SIMPLIFY_EMIT('.');
776 CRY_SIMPLIFY_EMIT('.');
777 if (i
!= skipElements
- 1)
779 CRY_SIMPLIFY_EMIT(kSlash
);
783 if (driveRelative
!= 0) // Fix up non-absolute but drive-relative paths.
785 CRY_SIMPLIFY_EMIT(':');
786 CRY_SIMPLIFY_EMIT(driveRelative
);
789 if (pOut
!= szBuf
) // left-align in the buffer
791 const size_t resultLength
= szBuf
+ bufLength
- pOut
;
792 assert(resultLength
> 0);
793 memmove(szBuf
, pOut
, resultLength
);
797 #undef CRY_SIMPLIFY_REJECT
798 #undef CRY_SIMPLIFY_EMIT
801 //! Converts \ to / and replaces ASCII characters to lower-case (A-Z only).
802 //! This function is ASCII-only and Unicode agnostic.
803 template<typename TString
>
804 typename
std::enable_if
<detail::IsValidStringType
<TString
>::value
>::type
805 inline /*void*/ UnifyFilePath(TString
& path
)
807 path
.replace('\\', '/');
812 #ifndef CRY_COMMON_HELPERS_ONLY
813 #include <CrySystem/File/ICryPak.h>
814 #include <CrySystem/ICmdLine.h>
818 inline string
GetGameFolder()
820 CRY_ASSERT(gEnv
&& gEnv
->pCryPak
);
821 return gEnv
->pCryPak
->GetGameFolder();
824 inline string
GetProjectFolder()
826 static bool checkedForCmdLineProjectArg
= false;
827 static string cmdLineProjectPath
;
828 if (cmdLineProjectPath
.IsEmpty() && !checkedForCmdLineProjectArg
)
830 CRY_ASSERT_MESSAGE(gEnv
&& gEnv
->pSystem
&& gEnv
->pSystem
->GetICmdLine(), "PathUtil::GetProjectFolder() was called before system was initialized");
831 const ICmdLineArg
* project
= gEnv
->pSystem
->GetICmdLine()->FindArg(eCLAT_Pre
, "project");
834 cmdLineProjectPath
= PathUtil::GetParentDirectory(project
->GetValue());
836 checkedForCmdLineProjectArg
= true;
838 return cmdLineProjectPath
;
841 inline string
GetLocalizationFolder()
843 CRY_ASSERT(gEnv
&& gEnv
->pCryPak
);
844 return gEnv
->pCryPak
->GetLocalizationFolder();
847 //! Make a game correct path out of any input path.
848 template<typename TString
>
849 typename
std::enable_if
<detail::IsValidStringType
<TString
>::value
, TString
>::type
850 inline /*TString*/ MakeGamePath(const TString
& path
)
852 const auto fullpath
= ToUnixPath(path
);
853 const auto rootDataFolder
= ToUnixPath(AddSlash(PathUtil::GetGameFolder()));
854 if (fullpath
.length() > rootDataFolder
.length() && strnicmp(fullpath
.c_str(), rootDataFolder
.c_str(), rootDataFolder
.length()) == 0)
856 return fullpath
.substr(rootDataFolder
.length(), fullpath
.length() - rootDataFolder
.length());
861 inline string
MakeGamePath(const char* szPath
)
863 return MakeGamePath(string(szPath
));