r5sdk/r5dev/tier1/strtools.cpp
Kawe Mazidjatari f120354e96 Initial port to CMake
* All libraries have been isolated from each other, and build into separate artifacts.
* Project has been restructured to support isolating libraries.
* CCrashHandler now calls a callback on crash (setup from core/dllmain.cpp, this can be setup in any way for any project. This callback is getting called when the apllication crashes. Useful for flushing buffers before closing handles to logging files for example).
* Tier0 'CoreMsgV' function now calls a callback sink, which could be set by the user (currently setup to the SDK's internal logger in core/dllmain.cpp).

TODO:
* Add a batch file to autogenerate all projects.
* Add support for dedicated server.
* Add support for client dll.

Bugs:
* Game crashes on the title screen after the UI script compiler has finished (root cause unknown).
* Curl error messages are getting logged twice for the dedicated server due to the removal of all "DEDICATED" preprocessor directives to support isolating projects. This has to be fixed properly!
2023-05-10 00:05:38 +02:00

912 lines
20 KiB
C++

#include "tier0_pch.h"
#include "tier1/strtools.h"
FORCEINLINE unsigned char tolower_fast(unsigned char c)
{
if ((c >= 'A') && (c <= 'Z'))
return c + ('a' - 'A');
return c;
}
//-----------------------------------------------------------------------------
// Finds a string in another string with a case insensitive test
//-----------------------------------------------------------------------------
char const* V_stristr(char const* pStr, char const* pSearch)
{
AssertValidStringPtr(reinterpret_cast<const TCHAR*>(pStr));
AssertValidStringPtr(reinterpret_cast<const TCHAR*>(pSearch));
if (!pStr || !pSearch)
return 0;
char const* pLetter = pStr;
// Check the entire string
while (*pLetter != 0)
{
// Skip over non-matches
if (tolower_fast((unsigned char)*pLetter) == tolower_fast((unsigned char)*pSearch))
{
// Check for match
char const* pMatch = pLetter + 1;
char const* pTest = pSearch + 1;
while (*pTest != 0)
{
// We've run off the end; don't bother.
if (*pMatch == 0)
return 0;
if (tolower_fast((unsigned char)*pMatch) != tolower_fast((unsigned char)*pTest))
break;
++pMatch;
++pTest;
}
// Found a match!
if (*pTest == 0)
return pLetter;
}
++pLetter;
}
return 0;
}
char* V_stristr(char* pStr, char const* pSearch)
{
AssertValidStringPtr(reinterpret_cast<const TCHAR*>(pStr));
AssertValidStringPtr(reinterpret_cast<const TCHAR*>(pSearch));
return (char*)V_stristr((char const*)pStr, pSearch);
}
//-----------------------------------------------------------------------------
// Finds a string in another string with a case insensitive test w/ length validation
//-----------------------------------------------------------------------------
const char* V_strnistr(const char* pStr, const char* pSearch, int64_t n)
{
Assert(pStr);
Assert(pSearch);
if (!pStr || !pSearch)
return 0;
const char* pLetter = pStr;
// Check the entire string
while (*pLetter != 0)
{
if (n <= 0)
return 0;
// Skip over non-matches
if (FastASCIIToLower(*pLetter) == FastASCIIToLower(*pSearch))
{
int64_t n1 = n - 1;
// Check for match
const char* pMatch = pLetter + 1;
const char* pTest = pSearch + 1;
while (*pTest != 0)
{
if (n1 <= 0)
return 0;
// We've run off the end; don't bother.
if (*pMatch == 0)
return 0;
if (FastASCIIToLower(*pMatch) != FastASCIIToLower(*pTest))
break;
++pMatch;
++pTest;
--n1;
}
// Found a match!
if (*pTest == 0)
return pLetter;
}
++pLetter;
--n;
}
return 0;
}
const char* V_strnchr(const char* pStr, char c, int64_t n)
{
const char* pLetter = pStr;
const char* pLast = pStr + n;
// Check the entire string
while ((pLetter < pLast) && (*pLetter != 0))
{
if (*pLetter == c)
return pLetter;
++pLetter;
}
return NULL;
}
bool V_isspace(int c)
{
// The standard white-space characters are the following: space, tab, carriage-return, newline, vertical tab, and form-feed. In the C locale, V_isspace() returns true only for the standard white-space characters.
//return c == ' ' || c == 9 /*horizontal tab*/ || c == '\r' || c == '\n' || c == 11 /*vertical tab*/ || c == '\f';
// codes of whitespace symbols: 9 HT, 10 \n, 11 VT, 12 form feed, 13 \r, 32 space
// easy to understand version, validated:
// return ((1 << (c-1)) & 0x80001F00) != 0 && ((c-1)&0xE0) == 0;
// 5% faster on Core i7, 35% faster on Xbox360, no branches, validated:
#ifdef _X360
return ((1 << (c - 1)) & 0x80001F00 & ~(-int((c - 1) & 0xE0))) != 0;
#else
// this is 11% faster on Core i7 than the previous, VC2005 compiler generates a seemingly unbalanced search tree that's faster
switch (c)
{
case ' ':
case 9:
case '\r':
case '\n':
case 11:
case '\f':
return true;
default:
return false;
}
#endif
}
int64_t V_StrTrim(char* pStr)
{
char* pSource = pStr;
char* pDest = pStr;
// skip white space at the beginning
while (*pSource != 0 && V_isspace(*pSource))
{
pSource++;
}
// copy everything else
char* pLastWhiteBlock = NULL;
char* pStart = pDest;
while (*pSource != 0)
{
*pDest = *pSource++;
if (V_isspace(*pDest))
{
if (pLastWhiteBlock == NULL)
pLastWhiteBlock = pDest;
}
else
{
pLastWhiteBlock = NULL;
}
pDest++;
}
*pDest = 0;
// did we end in a whitespace block?
if (pLastWhiteBlock != NULL)
{
// yep; shorten the string
pDest = pLastWhiteBlock;
*pLastWhiteBlock = 0;
}
return pDest - pStart;
}
//-----------------------------------------------------------------------------
// Purpose: Converts a UTF8 string into a unicode string
//-----------------------------------------------------------------------------
int V_UTF8ToUnicode(const char* pUTF8, wchar_t* pwchDest, int cubDestSizeInBytes)
{
Assert(cubDestSizeInBytes >= sizeof(*pwchDest));
pwchDest[0] = 0;
if (!pUTF8)
return 0;
#ifdef _WIN32
int cchResult = MultiByteToWideChar(CP_UTF8, 0, pUTF8, -1, pwchDest, cubDestSizeInBytes / sizeof(wchar_t));
#elif POSIX
int cchResult = mbstowcs(pwchDest, pUTF8, cubDestSizeInBytes / sizeof(wchar_t));
#endif
pwchDest[(cubDestSizeInBytes / sizeof(wchar_t)) - 1] = 0;
return cchResult;
}
//-----------------------------------------------------------------------------
// Purpose: Converts a unicode string into a UTF8 (standard) string
//-----------------------------------------------------------------------------
int V_UnicodeToUTF8(const wchar_t* pUnicode, char* pUTF8, int cubDestSizeInBytes)
{
if (cubDestSizeInBytes > 0)
pUTF8[0] = 0;
#ifdef _WIN32
int cchResult = WideCharToMultiByte(CP_UTF8, 0, pUnicode, -1, pUTF8, cubDestSizeInBytes, NULL, NULL);
#elif POSIX
int cchResult = 0;
if (pUnicode && pUTF8)
cchResult = wcstombs(pUTF8, pUnicode, cubDestSizeInBytes);
#endif
if (cubDestSizeInBytes > 0)
pUTF8[cubDestSizeInBytes - 1] = 0;
return cchResult;
}
bool V_StringMatchesPattern(const char* pszSource, const char* pszPattern, int nFlags /*= 0 */)
{
bool bExact = true;
while (1)
{
if ((*pszPattern) == 0)
{
return ((*pszSource) == 0);
}
if ((*pszPattern) == '*')
{
pszPattern++;
if ((*pszPattern) == 0)
{
return true;
}
bExact = false;
continue;
}
ptrdiff_t nLength = 0;
while ((*pszPattern) != '*' && (*pszPattern) != 0)
{
nLength++;
pszPattern++;
}
while (1)
{
const char* pszStartPattern = pszPattern - nLength;
const char* pszSearch = pszSource;
for (ptrdiff_t i = 0; i < nLength; i++, pszSearch++, pszStartPattern++)
{
if ((*pszSearch) == 0)
{
return false;
}
if ((*pszSearch) != (*pszStartPattern))
{
break;
}
}
if (pszSearch - pszSource == nLength)
{
break;
}
if (bExact == true)
{
return false;
}
if ((nFlags & PATTERN_DIRECTORY) != 0)
{
if ((*pszPattern) != '/' && (*pszSource) == '/')
{
return false;
}
}
pszSource++;
}
pszSource += nLength;
}
}
//-----------------------------------------------------------------------------
// Purpose: Changes all '/' or '\' characters into separator
// Input : *pName -
// cSeparator -
//-----------------------------------------------------------------------------
void V_FixSlashes(char* pName, char cSeperator /* = CORRECT_PATH_SEPARATOR */)
{
while (*pName)
{
if (*pName == INCORRECT_PATH_SEPARATOR || *pName == CORRECT_PATH_SEPARATOR)
{
*pName = cSeperator;
}
pName++;
}
}
void V_AppendSlash(char* pStr, size_t strSize, char separator)
{
size_t len = V_strlen(pStr);
if (len > 0 && !PATHSEPARATOR(pStr[len - 1]))
{
if (len + 1 >= strSize)
Error(eDLL_T::COMMON, EXIT_FAILURE, "V_AppendSlash: ran out of space on %s.", pStr);
pStr[len] = separator;
pStr[len + 1] = 0;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *ppath -
//-----------------------------------------------------------------------------
void V_StripTrailingSlash(char* ppath)
{
Assert(ppath);
size_t len = V_strlen(ppath);
if (len > 0)
{
if (PATHSEPARATOR(ppath[len - 1]))
{
ppath[len - 1] = 0;
}
}
}
bool V_RemoveDotSlashes(char* pFilename, char separator)
{
// Remove '//' or '\\'
char* pIn = pFilename;
char* pOut = pFilename;
// (But skip a leading separator, for leading \\'s in network paths)
if (*pIn && PATHSEPARATOR(*pIn))
{
*pOut = *pIn;
++pIn;
++pOut;
}
bool bPrevPathSep = false;
while (*pIn)
{
bool bIsPathSep = PATHSEPARATOR(*pIn);
if (!bIsPathSep || !bPrevPathSep)
{
*pOut++ = *pIn;
}
bPrevPathSep = bIsPathSep;
++pIn;
}
*pOut = 0;
// Get rid of "./"'s
pIn = pFilename;
pOut = pFilename;
while (*pIn)
{
// The logic on the second line is preventing it from screwing up "../"
if (pIn[0] == '.' && PATHSEPARATOR(pIn[1]) &&
(pIn == pFilename || pIn[-1] != '.'))
{
pIn += 2;
}
else
{
*pOut = *pIn;
++pIn;
++pOut;
}
}
*pOut = 0;
// Get rid of a trailing "/." (needless).
size_t len = V_strlen(pFilename);
if (len > 2 && pFilename[len - 1] == '.' && PATHSEPARATOR(pFilename[len - 2]))
{
pFilename[len - 2] = 0;
}
// Each time we encounter a "..", back up until we've read the previous directory name,
// then get rid of it.
pIn = pFilename;
while (*pIn)
{
if (pIn[0] == '.' &&
pIn[1] == '.' &&
(pIn == pFilename || PATHSEPARATOR(pIn[-1])) && // Preceding character must be a slash.
(pIn[2] == 0 || PATHSEPARATOR(pIn[2]))) // Following character must be a slash or the end of the string.
{
char* pEndOfDots = pIn + 2;
char* pStart = pIn - 2;
// Ok, now scan back for the path separator that starts the preceding directory.
while (1)
{
if (pStart < pFilename)
return false;
if (PATHSEPARATOR(*pStart))
break;
--pStart;
}
// Now slide the string down to get rid of the previous directory and the ".."
memmove(pStart, pEndOfDots, strlen(pEndOfDots) + 1);
// Start over.
pIn = pFilename;
}
else
{
++pIn;
}
}
V_FixSlashes(pFilename, separator);
return true;
}
//-----------------------------------------------------------------------------
// Purpose: normalizes the file path
// Input : *pfilePath -
// separator -
// Output : true on success, false otherwise
//-----------------------------------------------------------------------------
bool V_NormalizePath(char* pfilePath, char separator)
{
char v2; // al
char v3; // r9
char* v5; // rbx
char* i; // r8
char v7; // dl
char* v8; // r8
char v9; // al
char* j; // rcx
char v11; // dl
__int64 v12; // rax
__int64 v13; // rax
char v14; // cl
char v15; // al
char* v16; // rcx
char v17; // al
char v18; // al
_BYTE* v19; // rdx
char* k; // rcx
__int64 v21; // r8
char l; // al
v2 = *pfilePath;
v3 = 0;
v5 = pfilePath;
for (i = pfilePath; v2; v3 = v7)
{
if (v2 == '\\')
{
v2 = '\\';
}
else if (v2 != '/')
{
v7 = 0;
LABEL_7:
*pfilePath++ = v2;
goto LABEL_8;
}
v7 = 1;
if (!v3)
goto LABEL_7;
LABEL_8:
v2 = *++i;
}
*pfilePath = 0;
v8 = v5;
v9 = *v5;
for (j = v5; *j; v9 = *j)
{
if (v9 == '.' && ((v11 = j[1], v11 == '\\') || v11 == '/') && (j == v5 || (v9 = '.', *(j - 1) != '.')))
{
v12 = 2i64;
}
else
{
*v8++ = v9;
v12 = 1i64;
}
j += v12;
}
*v8 = 0;
v13 = -1i64;
do
++v13;
while (v5[v13]);
if (v13 > 2 && v5[v13 - 1] == '.')
{
v14 = v5[v13 - 2];
if (v14 == '\\' || v14 == '/')
v5[v13 - 2] = 0;
}
v15 = *v5;
v16 = v5;
if (*v5)
{
do
{
if (v15 == '.' && v16[1] == '.' &&
(v16 == v5 || (v17 = *(v16 - 1), v17 == '\\') || v17 == '/') &&
((v18 = v16[2], v19 = (uint8_t*)v16 + 2, !v18) || v18 == '\\' || v18 == '/'))
{
for (k = v16 - 2; ; --k)
{
if (k < v5)
return false;
if (*k == '\\' || *k == '/')
break;
}
v21 = -1i64;
do
++v21;
while (v19[v21]);
memmove(k, v19, v21 + 1);
v16 = v5;
}
else
{
++v16;
}
v15 = *v16;
} while (*v16);
for (l = *v5; l; ++v5)
{
if (l == '/' || l == '\\')
*v5 = separator;
l = v5[1];
}
}
return true;
}
//-----------------------------------------------------------------------------
// small helper function shared by lots of modules
//-----------------------------------------------------------------------------
bool V_IsAbsolutePath(const char* pStr)
{
if (!(pStr[0] && pStr[1]))
return false;
#if defined( PLATFORM_WINDOWS )
bool bIsAbsolute = (pStr[0] && pStr[1] == ':') ||
((pStr[0] == '/' || pStr[0] == '\\') && (pStr[1] == '/' || pStr[1] == '\\'));
#else
bool bIsAbsolute = (pStr[0] && pStr[1] == ':') || pStr[0] == '/' || pStr[0] == '\\';
#endif
if V_CONSTEXPR(IsX360() && !bIsAbsolute)
{
bIsAbsolute = (V_stristr(pStr, ":") != NULL);
}
return bIsAbsolute;
}
#if defined(_MSC_VER) && _MSC_VER >= 1900
bool
#else
void
#endif
V_MakeAbsolutePath(char* pOut, size_t outLen, const char* pPath, const char* pStartingDir)
{
if (V_IsAbsolutePath(pPath))
{
// pPath is not relative.. just copy it.
V_strncpy(pOut, pPath, outLen);
pOut[outLen - 1] = '\0';
}
else
{
// Make sure the starting directory is absolute..
if (pStartingDir && V_IsAbsolutePath(pStartingDir))
{
V_strncpy(pOut, pStartingDir, outLen);
pOut[outLen - 1] = '\0';
}
else
{
#ifdef _PS3
{
V_strncpy(pOut, g_pPS3PathInfo->GameImagePath(), outLen);
}
#else
{
#pragma warning(push) // Disabled type conversion warning, as some implementations of '_getcwd' take a size_t.
#pragma warning(disable : 4267)
if (!_getcwd(pOut, outLen))
Error(eDLL_T::COMMON, EXIT_FAILURE, "V_MakeAbsolutePath: _getcwd failed.");
#pragma warning(pop)
}
#endif
if (pStartingDir)
{
V_AppendSlash(pOut, outLen);
V_strncat(pOut, pStartingDir, outLen/*, COPY_ALL_CHARACTERS*/);
}
}
// Concatenate the paths.
V_AppendSlash(pOut, outLen);
V_strncat(pOut, pPath, outLen/*, COPY_ALL_CHARACTERS*/);
}
if (!V_NormalizePath(pOut, CORRECT_PATH_SEPARATOR))
Error(eDLL_T::COMMON, EXIT_FAILURE, "V_MakeAbsolutePath: tried to \"..\" past the root.");
V_FixSlashes(pOut);
bool bRet = true;
if (!V_RemoveDotSlashes(pOut))
{
V_strncpy(pOut, pPath, outLen);
V_FixSlashes(pOut);
bRet = false;
}
#if defined(_MSC_VER) && _MSC_VER >= 1900
return bRet;
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Strip off the last directory from dirName
// Input : *dirName -
// maxlen -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool V_StripLastDir(char* dirName, size_t maxlen)
{
if (dirName[0] == 0 ||
!V_stricmp(dirName, "./") ||
!V_stricmp(dirName, ".\\"))
return false;
size_t len = V_strlen(dirName);
Assert(len < maxlen);
// skip trailing slash
if (PATHSEPARATOR(dirName[len - 1]))
{
len--;
}
bool bHitColon = false;
while (len > 0)
{
if (PATHSEPARATOR(dirName[len - 1]))
{
dirName[len] = 0;
V_FixSlashes(dirName, CORRECT_PATH_SEPARATOR);
return true;
}
else if (dirName[len - 1] == ':')
{
bHitColon = true;
}
len--;
}
// If we hit a drive letter, then we're done.
// Ex: If they passed in c:\, then V_StripLastDir should return "" and false.
if (bHitColon)
{
dirName[0] = 0;
return false;
}
// Allow it to return an empty string and true. This can happen if something like "tf2/" is passed in.
// The correct behavior is to strip off the last directory ("tf2") and return true.
if (len == 0 && !bHitColon)
{
V_snprintf(dirName, maxlen, ".%c", CORRECT_PATH_SEPARATOR);
return true;
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Returns a pointer to the beginning of the unqualified file name
// (no path information)
// Input: in - file name (may be unqualified, relative or absolute path)
// Output: pointer to unqualified file name
//-----------------------------------------------------------------------------
const char* V_UnqualifiedFileName(const char* in)
{
if (!in || !in[0])
return in;
// back up until the character after the first path separator we find,
// or the beginning of the string
const char* out = in + strlen(in) - 1;
while ((out > in) && (!PATHSEPARATOR(*(out - 1))))
out--;
return out;
}
//-----------------------------------------------------------------------------
// Purpose: Composes a path and filename together, inserting a path separator
// if need be
// Input: path - path to use
// filename - filename to use
// dest - buffer to compose result in
// destSize - size of destination buffer
//-----------------------------------------------------------------------------
void V_ComposeFileName(const char* path, const char* filename, char* dest, size_t destSize)
{
V_strncpy(dest, path, destSize);
V_FixSlashes(dest);
V_AppendSlash(dest, destSize);
V_strncat(dest, filename, destSize/*, COPY_ALL_CHARACTERS*/);
V_FixSlashes(dest);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *in -
// *out -
// outSize -
//-----------------------------------------------------------------------------
void V_StripExtension(const char* in, char* out, size_t outSize)
{
// Find the last dot. If it's followed by a dot or a slash, then it's part of a
// directory specifier like ../../somedir/./blah.
if (!in || !in[0] || !outSize)
return;
// scan backward for '.'
size_t end = V_strlen(in) - 1;
while (end > 0 && in[end] != '.' && !PATHSEPARATOR(in[end]))
{
--end;
}
if (end > 0 && !PATHSEPARATOR(in[end]) && end < outSize)
{
size_t nChars = MIN(end, outSize - 1);
if (out != in)
{
memcpy(out, in, nChars);
}
out[nChars] = 0;
}
else
{
// nothing found
if (out != in)
{
V_strncpy(out, in, outSize);
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *path -
// *dest -
// destSize -
// Output : void V_ExtractFileExtension
//-----------------------------------------------------------------------------
void V_ExtractFileExtension(const char* path, char* dest, size_t destSize)
{
*dest = 0;
const char* extension = V_GetFileExtension(path);
if (NULL != extension)
V_strncpy(dest, extension, destSize);
}
//-----------------------------------------------------------------------------
// Purpose: Returns a pointer to the file extension within a file name string
// Input: in - file name
// Output: pointer to beginning of extension (after the "."), or NULL
// if there is no extension
//-----------------------------------------------------------------------------
const char* V_GetFileExtension(const char* path)
{
size_t len = V_strlen(path);
if (len <= 1)
return NULL;
const char* src = path + len - 1;
//
// back up until a . or the start
//
while (src != path && *(src - 1) != '.')
src--;
// check to see if the '.' is part of a pathname
if (src == path || PATHSEPARATOR(*src))
{
return NULL; // no extension
}
return src;
}
//-----------------------------------------------------------------------------
// Purpose: Extracts the base name of a file (no path, no extension, assumes '/' or '\' as path separator)
// Input : *in -
// *out -
// maxlen -
//-----------------------------------------------------------------------------
void V_FileBase(const char* in, char* out, size_t maxlen)
{
Assert(maxlen >= 1);
Assert(in);
Assert(out);
if (!in || !in[0])
{
*out = 0;
return;
}
size_t len, start, end;
len = V_strlen(in);
// scan backward for '.'
end = len - 1;
while (end && in[end] != '.' && !PATHSEPARATOR(in[end]))
{
end--;
}
if (in[end] != '.') // no '.', copy to end
{
end = len - 1;
}
else
{
end--; // Found ',', copy to left of '.'
}
// Scan backward for '/'
start = len - 1;
while (start >= 0 && !PATHSEPARATOR(in[start]))
{
start--;
}
if (start < 0 || !PATHSEPARATOR(in[start]))
{
start = 0;
}
else
{
start++;
}
// Length of new sting
len = end - start + 1;
size_t maxcopy = MIN(len + 1, maxlen);
// Copy partial string
V_strncpy(out, &in[start], maxcopy);
}