mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
This code was actually part of the LiveAPI system, but there were many opportunities to make this particular code modular, so it has been decoupled and moved to Tier2. The LiveAPI system will soon use this class instead. The implementation has also been improved by adding dedicated routines for updating socket parameters, disconnecting/reconnecting and destroying sockets. This commit also removes legacy workaround code in UtlVector which was used for before we had early enough access to the game's memalloc singleton. This code was no longer used. This commit also implements the CUtlStringList class, which is now used for the new websocket class to split each socket connection up by a comma delimiter.
1450 lines
31 KiB
C++
1450 lines
31 KiB
C++
#include "tier1/strtools.h"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Convert upper case characters to lower
|
|
//-----------------------------------------------------------------------------
|
|
static int FastToLower(char c)
|
|
{
|
|
int i = (unsigned char)c;
|
|
if (i < 0x80)
|
|
{
|
|
// Brutally fast branchless ASCII tolower():
|
|
i += (((('A' - 1) - i) & (i - ('Z' + 1))) >> 26) & 0x20;
|
|
}
|
|
else
|
|
{
|
|
i += isupper(i) ? 0x20 : 0;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Allocate a string buffer
|
|
//-----------------------------------------------------------------------------
|
|
char* AllocString(const char* pStr, ssize_t nMaxChars)
|
|
{
|
|
const ssize_t allocLen = (nMaxChars == -1)
|
|
? strlen(pStr) + 1
|
|
: Min((ssize_t)strlen(pStr), nMaxChars) + 1;
|
|
|
|
char* const pOut = new char[allocLen];
|
|
V_strncpy(pOut, pStr, allocLen);
|
|
|
|
return pOut;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// A special high-performance case-insensitive compare function
|
|
// returns 0 if strings match exactly
|
|
// returns >0 if strings match in a case-insensitive way, but do not match exactly
|
|
// returns <0 if strings do not match even in a case-insensitive way
|
|
//-----------------------------------------------------------------------------
|
|
int _V_stricmp_NegativeForUnequal(const char* s1, const char* s2)
|
|
{
|
|
// It is not uncommon to compare a string to itself. Since stricmp
|
|
// is expensive and pointer comparison is cheap, this simple test
|
|
// can save a lot of cycles, and cache pollution.
|
|
if (s1 == s2)
|
|
return 0;
|
|
|
|
uint8 const* pS1 = (uint8 const*)s1;
|
|
uint8 const* pS2 = (uint8 const*)s2;
|
|
int iExactMatchResult = 1;
|
|
for (;;)
|
|
{
|
|
int c1 = *(pS1++);
|
|
int c2 = *(pS2++);
|
|
if (c1 == c2)
|
|
{
|
|
// strings are case-insensitive equal, coerce accumulated
|
|
// case-difference to 0/1 and return it
|
|
if (!c1) return !iExactMatchResult;
|
|
}
|
|
else
|
|
{
|
|
if (!c2)
|
|
{
|
|
// c2=0 and != c1 => not equal
|
|
return -1;
|
|
}
|
|
iExactMatchResult = 0;
|
|
c1 = FastASCIIToLower(c1);
|
|
c2 = FastASCIIToLower(c2);
|
|
if (c1 != c2)
|
|
{
|
|
// strings are not equal
|
|
return -1;
|
|
}
|
|
}
|
|
c1 = *(pS1++);
|
|
c2 = *(pS2++);
|
|
if (c1 == c2)
|
|
{
|
|
// strings are case-insensitive equal, coerce accumulated
|
|
// case-difference to 0/1 and return it
|
|
if (!c1) return !iExactMatchResult;
|
|
}
|
|
else
|
|
{
|
|
if (!c2)
|
|
{
|
|
// c2=0 and != c1 => not equal
|
|
return -1;
|
|
}
|
|
iExactMatchResult = 0;
|
|
c1 = FastASCIIToLower(c1);
|
|
c2 = FastASCIIToLower(c2);
|
|
if (c1 != c2)
|
|
{
|
|
// strings are not equal
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 (FastASCIIToLower((unsigned char)*pLetter) == FastASCIIToLower((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 (FastASCIIToLower((unsigned char)*pMatch) != FastASCIIToLower((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, ssize_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))
|
|
{
|
|
ssize_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, ssize_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
|
|
}
|
|
|
|
bool V_IsAllDigit(const char* pString)
|
|
{
|
|
while (*pString)
|
|
{
|
|
if (!V_isdigit(*pString))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
pString++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the 4 bit nibble for a hex character
|
|
// Input : c -
|
|
// Output : unsigned char
|
|
//-----------------------------------------------------------------------------
|
|
static unsigned char V_nibble(char c)
|
|
{
|
|
if ((c >= '0') &&
|
|
(c <= '9'))
|
|
{
|
|
return (unsigned char)(c - '0');
|
|
}
|
|
|
|
if ((c >= 'A') &&
|
|
(c <= 'F'))
|
|
{
|
|
return (unsigned char)(c - 'A' + 0x0a);
|
|
}
|
|
|
|
if ((c >= 'a') &&
|
|
(c <= 'f'))
|
|
{
|
|
return (unsigned char)(c - 'a' + 0x0a);
|
|
}
|
|
|
|
return '0';
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *in -
|
|
// numchars -
|
|
// *out -
|
|
// maxoutputbytes -
|
|
//-----------------------------------------------------------------------------
|
|
void V_hextobinary(char const* in, size_t numchars, byte* out, size_t maxoutputbytes)
|
|
{
|
|
size_t len = V_strlen(in);
|
|
numchars = Min(len, numchars);
|
|
// Make sure it's even
|
|
numchars = (numchars) & ~0x1;
|
|
|
|
// Must be an even # of input characters (two chars per output byte)
|
|
Assert(numchars >= 2);
|
|
|
|
memset(out, 0x00, maxoutputbytes);
|
|
|
|
byte* p;
|
|
size_t i;
|
|
|
|
p = out;
|
|
for (i = 0;
|
|
(i < numchars) &&
|
|
((size_t)(p - out) < maxoutputbytes);
|
|
i += 2, p++)
|
|
{
|
|
*p = (V_nibble(in[i]) << 4) | V_nibble(in[i + 1]);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *in -
|
|
// inputbytes -
|
|
// *out -
|
|
// outsize -
|
|
//-----------------------------------------------------------------------------
|
|
void V_binarytohex(const byte* in, size_t inputbytes, char* out, size_t outsize)
|
|
{
|
|
Assert(outsize >= 1);
|
|
char doublet[10];
|
|
int i;
|
|
|
|
out[0] = 0;
|
|
|
|
for (i = 0; i < inputbytes; i++)
|
|
{
|
|
unsigned char c = in[i];
|
|
V_snprintf(doublet, sizeof(doublet), "%02x", c);
|
|
V_strncat(out, doublet, outsize);
|
|
}
|
|
}
|
|
|
|
|
|
ssize_t V_vsnprintfRet(char* pDest, size_t maxLen, const char* pFormat, va_list params, bool* pbTruncated)
|
|
{
|
|
Assert(maxLen > 0);
|
|
|
|
ssize_t len = _vsnprintf(pDest, maxLen, pFormat, params);
|
|
const bool bTruncated = (len < 0) || (len >= (ssize_t)maxLen);
|
|
|
|
if (pbTruncated)
|
|
{
|
|
*pbTruncated = bTruncated;
|
|
}
|
|
|
|
if (bTruncated && maxLen > 0)
|
|
{
|
|
len = maxLen - 1;
|
|
pDest[maxLen - 1] = 0;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
ssize_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;
|
|
}
|
|
|
|
void V_SplitString2(const char* pString, const char** pSeparators, ssize_t nSeparators, CUtlStringList& outStrings)
|
|
{
|
|
outStrings.Purge();
|
|
const char* pCurPos = pString;
|
|
|
|
while (true)
|
|
{
|
|
ssize_t iFirstSeparator = -1;
|
|
const char* pFirstSeparator = nullptr;
|
|
|
|
for (ssize_t i = 0; i < nSeparators; i++)
|
|
{
|
|
const char* const pTest = V_stristr(pCurPos, pSeparators[i]);
|
|
|
|
if (pTest && (!pFirstSeparator || pTest < pFirstSeparator))
|
|
{
|
|
iFirstSeparator = i;
|
|
pFirstSeparator = pTest;
|
|
}
|
|
}
|
|
|
|
if (pFirstSeparator)
|
|
{
|
|
// Split on this separator and continue on.
|
|
const ssize_t separatorLen = strlen(pSeparators[iFirstSeparator]);
|
|
|
|
if (pFirstSeparator > pCurPos)
|
|
{
|
|
outStrings.AddToTail(AllocString(pCurPos, pFirstSeparator - pCurPos));
|
|
}
|
|
|
|
pCurPos = pFirstSeparator + separatorLen;
|
|
}
|
|
else
|
|
{
|
|
// Copy the rest of the string
|
|
if (strlen(pCurPos))
|
|
{
|
|
outStrings.AddToTail(AllocString(pCurPos, -1));
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void V_SplitString(const char* pString, const char* pSeparator, CUtlStringList& outStrings)
|
|
{
|
|
V_SplitString2(pString, &pSeparator, 1, outStrings);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Converts a UTF-8 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 UTF-8 (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;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the UTF-8 character length
|
|
//-----------------------------------------------------------------------------
|
|
int V_UTF8CharLength(const unsigned char input)
|
|
{
|
|
if ((input & 0xFE) == 0xFC)
|
|
return 6;
|
|
if ((input & 0xFC) == 0xF8)
|
|
return 5;
|
|
if ((input & 0xF8) == 0xF0)
|
|
return 4;
|
|
else if ((input & 0xF0) == 0xE0)
|
|
return 3;
|
|
else if ((input & 0xE0) == 0xC0)
|
|
return 2;
|
|
return 1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Checks if a given string only contains UTF-8 characters
|
|
//-----------------------------------------------------------------------------
|
|
bool V_IsValidUTF8(const char* pszString)
|
|
{
|
|
char c;
|
|
const char* it;
|
|
|
|
while (true)
|
|
{
|
|
while (true)
|
|
{
|
|
c = *pszString;
|
|
it = pszString++;
|
|
if (c < 0)
|
|
{
|
|
break;
|
|
}
|
|
if (!c)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
char s = *pszString;
|
|
if ((*pszString & 0xC0) != 0x80)
|
|
{
|
|
break;
|
|
}
|
|
|
|
pszString = it + 2;
|
|
if (c >= 0xE0u)
|
|
{
|
|
int n = (*pszString & 0x3F) | (((s & 0x3F) | ((c & 0xF) << 6)) << 6);
|
|
if ((*pszString & 0xC0) != 0x80)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
pszString = it + 3;
|
|
if (c >= 0xF0u)
|
|
{
|
|
if ((*pszString & 0xC0) != 0x80 || ((n << 6) | (*pszString & 0x3Fu)) > 0x10FFFF)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
pszString = it + 4;
|
|
}
|
|
else if ((n - 0xD800) <= 0x7FF)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else if (c < 0xC2u)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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: Compares file paths, ignores case and path separators
|
|
// Input : *a -
|
|
// *b -
|
|
// Output : true if equal, false otherwise
|
|
//-----------------------------------------------------------------------------
|
|
bool V_ComparePath(const char* a, const char* b)
|
|
{
|
|
if (strlen(a) != strlen(b))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Case and separator invariant
|
|
for (; *a; a++, b++)
|
|
{
|
|
if (*a == *b)
|
|
{
|
|
continue;
|
|
}
|
|
if (FastASCIIToLower(*a) == FastASCIIToLower(*b))
|
|
{
|
|
continue;
|
|
}
|
|
if ((*a == '/' || *a == '\\') &&
|
|
(*b == '/' || *b == '\\'))
|
|
{
|
|
continue;
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sanity-check to verify that a path is a relative path inside the game dir
|
|
// Taken From: engine/cmd.cpp
|
|
//-----------------------------------------------------------------------------
|
|
bool V_IsValidPath(const char* pStr)
|
|
{
|
|
if (!pStr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Q_strlen(pStr) <= 0 ||
|
|
V_IsAbsolutePath(pStr) || // to protect absolute paths
|
|
Q_strstr(pStr, "..")) // to protect relative paths
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#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
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Makes a relative path
|
|
//-----------------------------------------------------------------------------
|
|
bool V_MakeRelativePath(const char* pFullPath, const char* pDirectory, char* pRelativePath, const size_t nBufLen)
|
|
{
|
|
Assert(nBufLen);
|
|
pRelativePath[0] = 0;
|
|
|
|
const char* pPath = pFullPath;
|
|
const char* pDir = pDirectory;
|
|
|
|
// Strip out common parts of the path
|
|
const char* pLastCommonPath = NULL;
|
|
const char* pLastCommonDir = NULL;
|
|
while (*pPath && (FastToLower(*pPath) == FastToLower(*pDir) ||
|
|
(PATHSEPARATOR(*pPath) && (PATHSEPARATOR(*pDir) || (*pDir == 0)))))
|
|
{
|
|
if (PATHSEPARATOR(*pPath))
|
|
{
|
|
pLastCommonPath = pPath + 1;
|
|
pLastCommonDir = pDir + 1;
|
|
}
|
|
if (*pDir == 0)
|
|
{
|
|
--pLastCommonDir;
|
|
break;
|
|
}
|
|
++pDir; ++pPath;
|
|
}
|
|
|
|
// Nothing in common
|
|
if (!pLastCommonPath)
|
|
return false;
|
|
|
|
// For each path separator remaining in the dir, need a ../
|
|
size_t nOutLen = 0;
|
|
bool bLastCharWasSeparator = true;
|
|
for (; *pLastCommonDir; ++pLastCommonDir)
|
|
{
|
|
if (PATHSEPARATOR(*pLastCommonDir))
|
|
{
|
|
pRelativePath[nOutLen++] = '.';
|
|
pRelativePath[nOutLen++] = '.';
|
|
pRelativePath[nOutLen++] = CORRECT_PATH_SEPARATOR;
|
|
bLastCharWasSeparator = true;
|
|
}
|
|
else
|
|
{
|
|
bLastCharWasSeparator = false;
|
|
}
|
|
}
|
|
|
|
// Deal with relative paths not specified with a trailing slash
|
|
if (!bLastCharWasSeparator)
|
|
{
|
|
pRelativePath[nOutLen++] = '.';
|
|
pRelativePath[nOutLen++] = '.';
|
|
pRelativePath[nOutLen++] = CORRECT_PATH_SEPARATOR;
|
|
}
|
|
|
|
// Copy the remaining part of the relative path over, fixing the path separators
|
|
for (; *pLastCommonPath; ++pLastCommonPath)
|
|
{
|
|
if (PATHSEPARATOR(*pLastCommonPath))
|
|
{
|
|
pRelativePath[nOutLen++] = CORRECT_PATH_SEPARATOR;
|
|
}
|
|
else
|
|
{
|
|
pRelativePath[nOutLen++] = *pLastCommonPath;
|
|
}
|
|
|
|
// Check for overflow
|
|
if (nOutLen == nBufLen - 1)
|
|
break;
|
|
}
|
|
|
|
pRelativePath[nOutLen] = 0;
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Strip off the last directory from dirName
|
|
// Input : *dirName -
|
|
// maxLen -
|
|
// Output : Returns the new length of the string
|
|
//-----------------------------------------------------------------------------
|
|
size_t V_StripLastDir(char* dirName, size_t maxLen)
|
|
{
|
|
Assert(dirName);
|
|
|
|
if (dirName[0] == '\0')
|
|
return 0;
|
|
|
|
size_t len = V_strlen(dirName);
|
|
Assert(len < maxLen);
|
|
|
|
if (!V_stricmp(dirName, "./") ||
|
|
!V_stricmp(dirName, ".\\"))
|
|
return len;
|
|
|
|
// skip trailing slash
|
|
if (PATHSEPARATOR(dirName[len - 1]))
|
|
{
|
|
len--;
|
|
}
|
|
|
|
bool bHitColon = false;
|
|
while (len > 0)
|
|
{
|
|
if (PATHSEPARATOR(dirName[len - 1]))
|
|
{
|
|
dirName[len] = '\0';
|
|
return len;
|
|
}
|
|
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
|
|
// turn the string into "" and return 0.
|
|
if (bHitColon)
|
|
{
|
|
dirName[0] = '\0';
|
|
return 0;
|
|
}
|
|
|
|
// Allow it to return an empty string and 0. This can happen if something like "tf2/" is passed in.
|
|
// The correct behavior is to strip off the last directory ("tf2") and return the new length.
|
|
if (len == 0)
|
|
{
|
|
int ret = V_snprintf(dirName, maxLen, ".%c", CORRECT_PATH_SEPARATOR);
|
|
|
|
// snprintf failed, turn the string into "" and return 0.
|
|
if (ret < 0)
|
|
{
|
|
Assert(0);
|
|
|
|
dirName[0] = '\0';
|
|
return 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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)
|
|
{
|
|
Assert(in);
|
|
|
|
const char* out = in;
|
|
|
|
while (*in)
|
|
{
|
|
if (PATHSEPARATOR(*in))
|
|
{
|
|
// +1 to skip the slash
|
|
out = in + 1;
|
|
}
|
|
|
|
in++;
|
|
}
|
|
|
|
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: 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 the passed
|
|
// in string if there is no extension
|
|
//-----------------------------------------------------------------------------
|
|
const char* V_GetFileExtension(const char* path, const bool keepDot)
|
|
{
|
|
Assert(path);
|
|
|
|
const char* out = nullptr;
|
|
|
|
while (*path)
|
|
{
|
|
if (*path == '.')
|
|
{
|
|
out = path;
|
|
|
|
if (!keepDot)
|
|
out++;
|
|
}
|
|
else if (PATHSEPARATOR(*path))
|
|
{
|
|
// didn't reach the file name yet, reset
|
|
out = nullptr;
|
|
}
|
|
|
|
path++;
|
|
}
|
|
|
|
return out ? out : path;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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:
|
|
// Input : *path -
|
|
// *dest -
|
|
// destSize -
|
|
// Output : void V_ExtractFilePath
|
|
//-----------------------------------------------------------------------------
|
|
bool V_ExtractFilePath(const char* path, char* dest, size_t destSize)
|
|
{
|
|
Assert(destSize >= 1);
|
|
if (destSize < 1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Last char
|
|
const size_t len = V_strlen(path);
|
|
const char* src = path + (len ? len - 1 : 0);
|
|
|
|
// back up until a \ or the start
|
|
while (src != path && !PATHSEPARATOR(*(src - 1)))
|
|
{
|
|
src--;
|
|
}
|
|
|
|
const ssize_t copysize = Min(size_t(src - path), destSize - 1);
|
|
memcpy(dest, path, copysize);
|
|
dest[copysize] = 0;
|
|
|
|
return copysize != 0 ? true : false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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);
|
|
}
|