mirror of
https://github.com/alliedmodders/hl2sdk.git
synced 2024-12-23 01:59:43 +08:00
2983 lines
77 KiB
C++
2983 lines
77 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: String Tools
|
|
//
|
|
//===========================================================================//
|
|
|
|
// These are redefined in the project settings to prevent anyone from using them.
|
|
// We in this module are of a higher caste and thus are privileged in their use.
|
|
#ifdef strncpy
|
|
#undef strncpy
|
|
#endif
|
|
|
|
#ifdef _snprintf
|
|
#undef _snprintf
|
|
#endif
|
|
|
|
#if defined( sprintf )
|
|
#undef sprintf
|
|
#endif
|
|
|
|
#if defined( vsprintf )
|
|
#undef vsprintf
|
|
#endif
|
|
|
|
#ifdef _vsnprintf
|
|
#ifdef _WIN32
|
|
#undef _vsnprintf
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef vsnprintf
|
|
#ifndef _WIN32
|
|
#undef vsnprintf
|
|
#endif
|
|
#endif
|
|
|
|
#if defined( strcat )
|
|
#undef strcat
|
|
#endif
|
|
|
|
#ifdef strncat
|
|
#undef strncat
|
|
#endif
|
|
|
|
// NOTE: I have to include stdio + stdarg first so vsnprintf gets compiled in
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
|
|
#ifdef POSIX
|
|
#include <iconv.h>
|
|
#include <ctype.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#define _getcwd getcwd
|
|
#elif _WIN32
|
|
#include <direct.h>
|
|
#if !defined( _X360 )
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
#ifndef CP_UTF8
|
|
#define CP_UTF8 65001
|
|
#endif
|
|
#endif
|
|
#include "tier0/dbg.h"
|
|
#include "tier1/strtools.h"
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include "tier0/basetypes.h"
|
|
#include "tier1/utldict.h"
|
|
#if defined( _X360 )
|
|
#include "xbox/xbox_win32stubs.h"
|
|
#endif
|
|
#include "tier0/memdbgon.h"
|
|
|
|
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;
|
|
}
|
|
|
|
void _V_memset (const char* file, int line, void *dest, int fill, int count)
|
|
{
|
|
Assert( count >= 0 );
|
|
AssertValidWritePtr( dest, count );
|
|
|
|
memset(dest,fill,count);
|
|
}
|
|
|
|
void _V_memcpy (const char* file, int line, void *dest, const void *src, int count)
|
|
{
|
|
Assert( count >= 0 );
|
|
AssertValidReadPtr( src, count );
|
|
AssertValidWritePtr( dest, count );
|
|
|
|
memcpy( dest, src, count );
|
|
}
|
|
|
|
void _V_memmove(const char* file, int line, void *dest, const void *src, int count)
|
|
{
|
|
Assert( count >= 0 );
|
|
AssertValidReadPtr( src, count );
|
|
AssertValidWritePtr( dest, count );
|
|
|
|
memmove( dest, src, count );
|
|
}
|
|
|
|
int _V_memcmp (const char* file, int line, const void *m1, const void *m2, int count)
|
|
{
|
|
Assert( count >= 0 );
|
|
AssertValidReadPtr( m1, count );
|
|
AssertValidReadPtr( m2, count );
|
|
|
|
return memcmp( m1, m2, count );
|
|
}
|
|
|
|
int _V_strlen(const char* file, int line, const char *str)
|
|
{
|
|
AssertValidStringPtr(str);
|
|
return strlen( str );
|
|
}
|
|
|
|
void _V_strcpy (const char* file, int line, char *dest, const char *src)
|
|
{
|
|
AssertValidWritePtr(dest);
|
|
AssertValidStringPtr(src);
|
|
|
|
strcpy( dest, src );
|
|
}
|
|
|
|
int _V_wcslen(const char* file, int line, const wchar_t *pwch)
|
|
{
|
|
return wcslen( pwch );
|
|
}
|
|
|
|
char *_V_strrchr(const char* file, int line, const char *s, char c)
|
|
{
|
|
AssertValidStringPtr( s );
|
|
int len = V_strlen(s);
|
|
s += len;
|
|
while (len--)
|
|
if (*--s == c) return (char *)s;
|
|
return 0;
|
|
}
|
|
|
|
int _V_strcmp (const char* file, int line, const char *s1, const char *s2)
|
|
{
|
|
AssertValidStringPtr( s1 );
|
|
AssertValidStringPtr( s2 );
|
|
|
|
return strcmp( s1, s2 );
|
|
}
|
|
|
|
int _V_wcscmp (const char* file, int line, const wchar_t *s1, const wchar_t *s2)
|
|
{
|
|
AssertValidReadPtr( s1 );
|
|
AssertValidReadPtr( s2 );
|
|
|
|
while ( *s1 == *s2 )
|
|
{
|
|
if ( !*s1 )
|
|
return 0; // strings are equal
|
|
|
|
s1++;
|
|
s2++;
|
|
}
|
|
|
|
return *s1 > *s2 ? 1 : -1; // strings not equal
|
|
}
|
|
|
|
|
|
char *_V_strstr(const char* file, int line, const char *s1, const char *search )
|
|
{
|
|
AssertValidStringPtr( s1 );
|
|
AssertValidStringPtr( search );
|
|
|
|
#if defined( _X360 )
|
|
return (char *)strstr( (char *)s1, search );
|
|
#else
|
|
return (char *)strstr( s1, search );
|
|
#endif
|
|
}
|
|
|
|
wchar_t *_V_wcsupr (const char* file, int line, wchar_t *start)
|
|
{
|
|
return _wcsupr( start );
|
|
}
|
|
|
|
|
|
wchar_t *_V_wcslower (const char* file, int line, wchar_t *start)
|
|
{
|
|
return _wcslwr(start);
|
|
}
|
|
|
|
|
|
|
|
char *V_strupr( char *start )
|
|
{
|
|
unsigned char *str = (unsigned char*)start;
|
|
while( *str )
|
|
{
|
|
if ( (unsigned char)(*str - 'a') <= ('z' - 'a') )
|
|
*str -= 'a' - 'A';
|
|
else if ( (unsigned char)*str >= 0x80 ) // non-ascii, fall back to CRT
|
|
*str = toupper( *str );
|
|
str++;
|
|
}
|
|
return start;
|
|
}
|
|
|
|
char *V_strlower( char *start )
|
|
{
|
|
unsigned char *str = (unsigned char*)start;
|
|
while( *str )
|
|
{
|
|
if ( (unsigned char)(*str - 'A') <= ('Z' - 'A') )
|
|
*str += 'a' - 'A';
|
|
else if ( (unsigned char)*str >= 0x80 ) // non-ascii, fall back to CRT
|
|
*str = tolower( *str );
|
|
str++;
|
|
}
|
|
return start;
|
|
}
|
|
|
|
char *V_strnlwr(char *s, size_t count)
|
|
{
|
|
// Assert( count >= 0 ); tautology since size_t is unsigned
|
|
AssertValidStringPtr( s, count );
|
|
|
|
char* pRet = s;
|
|
if ( !s || !count )
|
|
return s;
|
|
|
|
while ( -- count > 0 )
|
|
{
|
|
if ( !*s )
|
|
return pRet; // reached end of string
|
|
|
|
*s = tolower( *s );
|
|
++s;
|
|
}
|
|
|
|
*s = 0; // null-terminate original string at "count-1"
|
|
return pRet;
|
|
}
|
|
|
|
int V_stricmp( const char *str1, const char *str2 )
|
|
{
|
|
// It is not uncommon to compare a string to itself. See
|
|
// VPanelWrapper::GetPanel which does this a lot. Since stricmp
|
|
// is expensive and pointer comparison is cheap, this simple test
|
|
// can save a lot of cycles, and cache pollution.
|
|
if ( str1 == str2 )
|
|
{
|
|
return 0;
|
|
}
|
|
const unsigned char *s1 = (const unsigned char*)str1;
|
|
const unsigned char *s2 = (const unsigned char*)str2;
|
|
for ( ; *s1; ++s1, ++s2 )
|
|
{
|
|
if ( *s1 != *s2 )
|
|
{
|
|
// in ascii char set, lowercase = uppercase | 0x20
|
|
unsigned char c1 = *s1 | 0x20;
|
|
unsigned char c2 = *s2 | 0x20;
|
|
if ( c1 != c2 || (unsigned char)(c1 - 'a') > ('z' - 'a') )
|
|
{
|
|
// if non-ascii mismatch, fall back to CRT for locale
|
|
if ( (c1 | c2) >= 0x80 ) return stricmp( (const char*)s1, (const char*)s2 );
|
|
// ascii mismatch. only use the | 0x20 value if alphabetic.
|
|
if ((unsigned char)(c1 - 'a') > ('z' - 'a')) c1 = *s1;
|
|
if ((unsigned char)(c2 - 'a') > ('z' - 'a')) c2 = *s2;
|
|
return c1 > c2 ? 1 : -1;
|
|
}
|
|
}
|
|
}
|
|
return *s2 ? -1 : 0;
|
|
}
|
|
|
|
int V_strnicmp( const char *str1, const char *str2, int n )
|
|
{
|
|
const unsigned char *s1 = (const unsigned char*)str1;
|
|
const unsigned char *s2 = (const unsigned char*)str2;
|
|
for ( ; n > 0 && *s1; --n, ++s1, ++s2 )
|
|
{
|
|
if ( *s1 != *s2 )
|
|
{
|
|
// in ascii char set, lowercase = uppercase | 0x20
|
|
unsigned char c1 = *s1 | 0x20;
|
|
unsigned char c2 = *s2 | 0x20;
|
|
if ( c1 != c2 || (unsigned char)(c1 - 'a') > ('z' - 'a') )
|
|
{
|
|
// if non-ascii mismatch, fall back to CRT for locale
|
|
if ( (c1 | c2) >= 0x80 ) return strnicmp( (const char*)s1, (const char*)s2, n );
|
|
// ascii mismatch. only use the | 0x20 value if alphabetic.
|
|
if ((unsigned char)(c1 - 'a') > ('z' - 'a')) c1 = *s1;
|
|
if ((unsigned char)(c2 - 'a') > ('z' - 'a')) c2 = *s2;
|
|
return c1 > c2 ? 1 : -1;
|
|
}
|
|
}
|
|
}
|
|
return (n > 0 && *s2) ? -1 : 0;
|
|
}
|
|
|
|
int V_strncmp( const char *s1, const char *s2, int count )
|
|
{
|
|
Assert( count >= 0 );
|
|
AssertValidStringPtr( s1, count );
|
|
AssertValidStringPtr( s2, count );
|
|
|
|
while ( count > 0 )
|
|
{
|
|
if ( *s1 != *s2 )
|
|
return (unsigned char)*s1 < (unsigned char)*s2 ? -1 : 1; // string different
|
|
if ( *s1 == '\0' )
|
|
return 0; // null terminator hit - strings the same
|
|
s1++;
|
|
s2++;
|
|
count--;
|
|
}
|
|
|
|
return 0; // count characters compared the same
|
|
}
|
|
|
|
|
|
const char *StringAfterPrefix( const char *str, const char *prefix )
|
|
{
|
|
AssertValidStringPtr( str );
|
|
AssertValidStringPtr( prefix );
|
|
do
|
|
{
|
|
if ( !*prefix )
|
|
return str;
|
|
}
|
|
while ( FastToLower( *str++ ) == FastToLower( *prefix++ ) );
|
|
return NULL;
|
|
}
|
|
|
|
const char *StringAfterPrefixCaseSensitive( const char *str, const char *prefix )
|
|
{
|
|
AssertValidStringPtr( str );
|
|
AssertValidStringPtr( prefix );
|
|
do
|
|
{
|
|
if ( !*prefix )
|
|
return str;
|
|
}
|
|
while ( *str++ == *prefix++ );
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int64 V_atoi64( const char *str )
|
|
{
|
|
AssertValidStringPtr( str );
|
|
|
|
int64 val;
|
|
int64 sign;
|
|
int64 c;
|
|
|
|
Assert( str );
|
|
if (*str == '-')
|
|
{
|
|
sign = -1;
|
|
str++;
|
|
}
|
|
else
|
|
sign = 1;
|
|
|
|
val = 0;
|
|
|
|
//
|
|
// check for hex
|
|
//
|
|
if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') )
|
|
{
|
|
str += 2;
|
|
while (1)
|
|
{
|
|
c = *str++;
|
|
if (c >= '0' && c <= '9')
|
|
val = (val<<4) + c - '0';
|
|
else if (c >= 'a' && c <= 'f')
|
|
val = (val<<4) + c - 'a' + 10;
|
|
else if (c >= 'A' && c <= 'F')
|
|
val = (val<<4) + c - 'A' + 10;
|
|
else
|
|
return val*sign;
|
|
}
|
|
}
|
|
|
|
//
|
|
// check for character
|
|
//
|
|
if (str[0] == '\'')
|
|
{
|
|
return sign * str[1];
|
|
}
|
|
|
|
//
|
|
// assume decimal
|
|
//
|
|
while (1)
|
|
{
|
|
c = *str++;
|
|
if (c <'0' || c > '9')
|
|
return val*sign;
|
|
val = val*10 + c - '0';
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint64 V_atoui64( const char *str )
|
|
{
|
|
AssertValidStringPtr( str );
|
|
|
|
uint64 val;
|
|
uint64 c;
|
|
|
|
Assert( str );
|
|
|
|
val = 0;
|
|
|
|
//
|
|
// check for hex
|
|
//
|
|
if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') )
|
|
{
|
|
str += 2;
|
|
while (1)
|
|
{
|
|
c = *str++;
|
|
if (c >= '0' && c <= '9')
|
|
val = (val<<4) + c - '0';
|
|
else if (c >= 'a' && c <= 'f')
|
|
val = (val<<4) + c - 'a' + 10;
|
|
else if (c >= 'A' && c <= 'F')
|
|
val = (val<<4) + c - 'A' + 10;
|
|
else
|
|
return val;
|
|
}
|
|
}
|
|
|
|
//
|
|
// check for character
|
|
//
|
|
if (str[0] == '\'')
|
|
{
|
|
return str[1];
|
|
}
|
|
|
|
//
|
|
// assume decimal
|
|
//
|
|
while (1)
|
|
{
|
|
c = *str++;
|
|
if (c <'0' || c > '9')
|
|
return val;
|
|
val = val*10 + c - '0';
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int V_atoi( const char *str )
|
|
{
|
|
return (int)V_atoi64( str );
|
|
}
|
|
|
|
float V_atof (const char *str)
|
|
{
|
|
AssertValidStringPtr( str );
|
|
double val;
|
|
int sign;
|
|
int c;
|
|
int decimal, total;
|
|
|
|
if (*str == '-')
|
|
{
|
|
sign = -1;
|
|
str++;
|
|
}
|
|
else if (*str == '+')
|
|
{
|
|
sign = 1;
|
|
str++;
|
|
}
|
|
else
|
|
{
|
|
sign = 1;
|
|
}
|
|
|
|
val = 0;
|
|
|
|
//
|
|
// check for hex
|
|
//
|
|
if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') )
|
|
{
|
|
str += 2;
|
|
while (1)
|
|
{
|
|
c = *str++;
|
|
if (c >= '0' && c <= '9')
|
|
val = (val*16) + c - '0';
|
|
else if (c >= 'a' && c <= 'f')
|
|
val = (val*16) + c - 'a' + 10;
|
|
else if (c >= 'A' && c <= 'F')
|
|
val = (val*16) + c - 'A' + 10;
|
|
else
|
|
return val*sign;
|
|
}
|
|
}
|
|
|
|
//
|
|
// check for character
|
|
//
|
|
if (str[0] == '\'')
|
|
{
|
|
return sign * str[1];
|
|
}
|
|
|
|
//
|
|
// assume decimal
|
|
//
|
|
decimal = -1;
|
|
total = 0;
|
|
int exponent = 0;
|
|
while (1)
|
|
{
|
|
c = *str++;
|
|
if (c == '.')
|
|
{
|
|
if ( decimal != -1 )
|
|
{
|
|
break;
|
|
}
|
|
|
|
decimal = total;
|
|
continue;
|
|
}
|
|
if (c <'0' || c > '9')
|
|
{
|
|
if ( c == 'e' || c == 'E' )
|
|
{
|
|
exponent = V_atoi(str);
|
|
}
|
|
break;
|
|
}
|
|
val = val*10 + c - '0';
|
|
total++;
|
|
}
|
|
|
|
if ( exponent != 0 )
|
|
{
|
|
val *= pow( 10.0, exponent );
|
|
}
|
|
if (decimal == -1)
|
|
return val*sign;
|
|
while (total > decimal)
|
|
{
|
|
val /= 10;
|
|
total--;
|
|
}
|
|
|
|
return val*sign;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Normalizes a float string in place.
|
|
//
|
|
// (removes leading zeros, trailing zeros after the decimal point, and the decimal point itself where possible)
|
|
//-----------------------------------------------------------------------------
|
|
void V_normalizeFloatString( char* pFloat )
|
|
{
|
|
// If we have a decimal point, remove trailing zeroes:
|
|
if( strchr( pFloat,'.' ) )
|
|
{
|
|
int len = V_strlen(pFloat);
|
|
|
|
while( len > 1 && pFloat[len - 1] == '0' )
|
|
{
|
|
pFloat[len - 1] = '\0';
|
|
len--;
|
|
}
|
|
|
|
if( len > 1 && pFloat[ len - 1 ] == '.' )
|
|
{
|
|
pFloat[len - 1] = '\0';
|
|
len--;
|
|
}
|
|
}
|
|
|
|
// TODO: Strip leading zeros
|
|
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Finds a string in another string with a case insensitive test
|
|
//-----------------------------------------------------------------------------
|
|
char const* V_stristr( char const* pStr, char const* pSearch )
|
|
{
|
|
AssertValidStringPtr(pStr);
|
|
AssertValidStringPtr(pSearch);
|
|
|
|
if (!pStr || !pSearch)
|
|
return 0;
|
|
|
|
char const* pLetter = pStr;
|
|
|
|
// Check the entire string
|
|
while (*pLetter != 0)
|
|
{
|
|
// Skip over non-matches
|
|
if (FastToLower((unsigned char)*pLetter) == FastToLower((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 (FastToLower((unsigned char)*pMatch) != FastToLower((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( pStr );
|
|
AssertValidStringPtr( pSearch );
|
|
|
|
return (char*)V_stristr( (char const*)pStr, pSearch );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Finds a string in another string with a case insensitive test w/ length validation
|
|
//-----------------------------------------------------------------------------
|
|
|
|
char const* V_strnistr( char const* pStr, char const* pSearch, int n )
|
|
{
|
|
AssertValidStringPtr(pStr);
|
|
AssertValidStringPtr(pSearch);
|
|
|
|
if (!pStr || !pSearch)
|
|
return 0;
|
|
|
|
char const* pLetter = pStr;
|
|
|
|
// Check the entire string
|
|
while (*pLetter != 0)
|
|
{
|
|
if ( n <= 0 )
|
|
return 0;
|
|
|
|
// Skip over non-matches
|
|
if (FastToLower(*pLetter) == FastToLower(*pSearch))
|
|
{
|
|
int n1 = n - 1;
|
|
|
|
// Check for match
|
|
char const* pMatch = pLetter + 1;
|
|
char const* 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 (FastToLower(*pMatch) != FastToLower(*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, int n )
|
|
{
|
|
char const* pLetter = pStr;
|
|
char const* pLast = pStr + n;
|
|
|
|
// Check the entire string
|
|
while ( (pLetter < pLast) && (*pLetter != 0) )
|
|
{
|
|
if (*pLetter == c)
|
|
return pLetter;
|
|
++pLetter;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void V_strncpy( char *pDest, char const *pSrc, int maxLen )
|
|
{
|
|
Assert( maxLen >= sizeof( *pDest ) );
|
|
AssertValidWritePtr( pDest, maxLen );
|
|
AssertValidStringPtr( pSrc );
|
|
|
|
strncpy( pDest, pSrc, maxLen );
|
|
if ( maxLen > 0 )
|
|
{
|
|
pDest[maxLen-1] = 0;
|
|
}
|
|
}
|
|
|
|
// warning C6053: Call to 'wcsncpy' might not zero-terminate string 'pDest'
|
|
// warning C6059: Incorrect length parameter in call to 'strncat'. Pass the number of remaining characters, not the buffer size of 'argument 1'
|
|
// warning C6386: Buffer overrun: accessing 'argument 1', the writable size is 'destBufferSize' bytes, but '1000' bytes might be written
|
|
// These warnings were investigated through code inspection and writing of tests and they are
|
|
// believed to all be spurious.
|
|
#ifdef _PREFAST_
|
|
#pragma warning( push )
|
|
#pragma warning( disable : 6053 6059 6386 )
|
|
#endif
|
|
|
|
void V_wcsncpy( wchar_t *pDest, wchar_t const *pSrc, int maxLenInBytes )
|
|
{
|
|
Assert( maxLenInBytes >= sizeof( *pDest ) );
|
|
AssertValidWritePtr( pDest, maxLenInBytes );
|
|
AssertValidReadPtr( pSrc );
|
|
|
|
int maxLen = maxLenInBytes / sizeof(wchar_t);
|
|
|
|
wcsncpy( pDest, pSrc, maxLen );
|
|
if( maxLen )
|
|
{
|
|
pDest[maxLen-1] = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
int V_snwprintf( wchar_t *pDest, int maxLen, const wchar_t *pFormat, ... )
|
|
{
|
|
Assert( maxLen > 0 );
|
|
AssertValidWritePtr( pDest, maxLen );
|
|
AssertValidReadPtr( pFormat );
|
|
|
|
va_list marker;
|
|
|
|
va_start( marker, pFormat );
|
|
#ifdef _WIN32
|
|
int len = _vsnwprintf( pDest, maxLen, pFormat, marker );
|
|
#elif POSIX
|
|
int len = vswprintf( pDest, maxLen, pFormat, marker );
|
|
#else
|
|
#error "define vsnwprintf type."
|
|
#endif
|
|
va_end( marker );
|
|
|
|
// Len > maxLen represents an overflow on POSIX, < 0 is an overflow on windows
|
|
if( len < 0 || len >= maxLen )
|
|
{
|
|
len = maxLen;
|
|
pDest[maxLen-1] = 0;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
int V_vsnwprintf( wchar_t *pDest, int maxLen, const wchar_t *pFormat, va_list params )
|
|
{
|
|
Assert( maxLen > 0 );
|
|
|
|
#ifdef _WIN32
|
|
int len = _vsnwprintf( pDest, maxLen, pFormat, params );
|
|
#elif POSIX
|
|
int len = vswprintf( pDest, maxLen, pFormat, params );
|
|
#else
|
|
#error "define vsnwprintf type."
|
|
#endif
|
|
|
|
// Len < 0 represents an overflow
|
|
// Len == maxLen represents exactly fitting with no NULL termination
|
|
// Len >= maxLen represents overflow on POSIX
|
|
if ( len < 0 || len >= maxLen )
|
|
{
|
|
len = maxLen;
|
|
pDest[maxLen-1] = 0;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
int V_snprintf( char *pDest, int maxLen, char const *pFormat, ... )
|
|
{
|
|
Assert( maxLen > 0 );
|
|
AssertValidWritePtr( pDest, maxLen );
|
|
AssertValidStringPtr( pFormat );
|
|
|
|
va_list marker;
|
|
|
|
va_start( marker, pFormat );
|
|
#ifdef _WIN32
|
|
int len = _vsnprintf( pDest, maxLen, pFormat, marker );
|
|
#elif POSIX
|
|
int len = vsnprintf( pDest, maxLen, pFormat, marker );
|
|
#else
|
|
#error "define vsnprintf type."
|
|
#endif
|
|
va_end( marker );
|
|
|
|
// Len > maxLen represents an overflow on POSIX, < 0 is an overflow on windows
|
|
if( len < 0 || len >= maxLen )
|
|
{
|
|
len = maxLen;
|
|
pDest[maxLen-1] = 0;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
int V_vsnprintf( char *pDest, int maxLen, char const *pFormat, va_list params )
|
|
{
|
|
Assert( maxLen > 0 );
|
|
AssertValidWritePtr( pDest, maxLen );
|
|
AssertValidStringPtr( pFormat );
|
|
|
|
int len = _vsnprintf( pDest, maxLen, pFormat, params );
|
|
|
|
// Len > maxLen represents an overflow on POSIX, < 0 is an overflow on windows
|
|
if( len < 0 || len >= maxLen )
|
|
{
|
|
len = maxLen;
|
|
pDest[maxLen-1] = 0;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
int V_vsnprintfRet( char *pDest, int maxLen, const char *pFormat, va_list params, bool *pbTruncated )
|
|
{
|
|
Assert( maxLen > 0 );
|
|
AssertValidWritePtr( pDest, maxLen );
|
|
AssertValidStringPtr( pFormat );
|
|
|
|
int len = _vsnprintf( pDest, maxLen, pFormat, params );
|
|
|
|
if ( pbTruncated )
|
|
{
|
|
*pbTruncated = ( len < 0 || len >= maxLen );
|
|
}
|
|
|
|
if ( len < 0 || len >= maxLen )
|
|
{
|
|
len = maxLen;
|
|
pDest[maxLen-1] = 0;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: If COPY_ALL_CHARACTERS == max_chars_to_copy then we try to add the whole pSrc to the end of pDest, otherwise
|
|
// we copy only as many characters as are specified in max_chars_to_copy (or the # of characters in pSrc if thats's less).
|
|
// Input : *pDest - destination buffer
|
|
// *pSrc - string to append
|
|
// destBufferSize - sizeof the buffer pointed to by pDest
|
|
// max_chars_to_copy - COPY_ALL_CHARACTERS in pSrc or max # to copy
|
|
// Output : char * the copied buffer
|
|
//-----------------------------------------------------------------------------
|
|
char *V_strncat(char *pDest, const char *pSrc, size_t destBufferSize, int max_chars_to_copy )
|
|
{
|
|
size_t charstocopy = (size_t)0;
|
|
|
|
Assert( (ptrdiff_t)destBufferSize >= 0 );
|
|
AssertValidStringPtr( pDest);
|
|
AssertValidStringPtr( pSrc );
|
|
|
|
size_t len = strlen(pDest);
|
|
size_t srclen = strlen( pSrc );
|
|
if ( max_chars_to_copy <= COPY_ALL_CHARACTERS )
|
|
{
|
|
charstocopy = srclen;
|
|
}
|
|
else
|
|
{
|
|
charstocopy = (size_t)V_min( max_chars_to_copy, (int)srclen );
|
|
}
|
|
|
|
if ( len + charstocopy >= destBufferSize )
|
|
{
|
|
charstocopy = destBufferSize - len - 1;
|
|
}
|
|
|
|
if ( (int)charstocopy <= 0 )
|
|
{
|
|
return pDest;
|
|
}
|
|
|
|
ANALYZE_SUPPRESS( 6059 ); // warning C6059: : Incorrect length parameter in call to 'strncat'. Pass the number of remaining characters, not the buffer size of 'argument 1'
|
|
char *pOut = strncat( pDest, pSrc, charstocopy );
|
|
return pOut;
|
|
}
|
|
|
|
wchar_t *V_wcsncat( INOUT_Z_CAP(cchDest) wchar_t *pDest, const wchar_t *pSrc, size_t cchDest, int max_chars_to_copy )
|
|
{
|
|
size_t charstocopy = (size_t)0;
|
|
|
|
Assert( (ptrdiff_t)cchDest >= 0 );
|
|
|
|
size_t len = wcslen(pDest);
|
|
size_t srclen = wcslen( pSrc );
|
|
if ( max_chars_to_copy <= COPY_ALL_CHARACTERS )
|
|
{
|
|
charstocopy = srclen;
|
|
}
|
|
else
|
|
{
|
|
charstocopy = (size_t)V_min( max_chars_to_copy, (int)srclen );
|
|
}
|
|
|
|
if ( len + charstocopy >= cchDest )
|
|
{
|
|
charstocopy = cchDest - len - 1;
|
|
}
|
|
|
|
if ( (int)charstocopy <= 0 )
|
|
{
|
|
return pDest;
|
|
}
|
|
|
|
ANALYZE_SUPPRESS( 6059 ); // warning C6059: : Incorrect length parameter in call to 'strncat'. Pass the number of remaining characters, not the buffer size of 'argument 1'
|
|
wchar_t *pOut = wcsncat( pDest, pSrc, charstocopy );
|
|
return pOut;
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Converts value into x.xx MB/ x.xx KB, x.xx bytes format, including commas
|
|
// Input : value -
|
|
// 2 -
|
|
// false -
|
|
// Output : char
|
|
//-----------------------------------------------------------------------------
|
|
#define NUM_PRETIFYMEM_BUFFERS 8
|
|
char *V_pretifymem( float value, int digitsafterdecimal /*= 2*/, bool usebinaryonek /*= false*/ )
|
|
{
|
|
static char output[ NUM_PRETIFYMEM_BUFFERS ][ 32 ];
|
|
static int current;
|
|
|
|
float onekb = usebinaryonek ? 1024.0f : 1000.0f;
|
|
float onemb = onekb * onekb;
|
|
|
|
char *out = output[ current ];
|
|
current = ( current + 1 ) & ( NUM_PRETIFYMEM_BUFFERS -1 );
|
|
|
|
char suffix[ 8 ];
|
|
|
|
// First figure out which bin to use
|
|
if ( value > onemb )
|
|
{
|
|
value /= onemb;
|
|
V_snprintf( suffix, sizeof( suffix ), " MB" );
|
|
}
|
|
else if ( value > onekb )
|
|
{
|
|
value /= onekb;
|
|
V_snprintf( suffix, sizeof( suffix ), " KB" );
|
|
}
|
|
else
|
|
{
|
|
V_snprintf( suffix, sizeof( suffix ), " bytes" );
|
|
}
|
|
|
|
char val[ 32 ];
|
|
|
|
// Clamp to >= 0
|
|
digitsafterdecimal = V_max( digitsafterdecimal, 0 );
|
|
|
|
// If it's basically integral, don't do any decimals
|
|
if ( FloatMakePositive( value - (int)value ) < 0.00001 )
|
|
{
|
|
V_snprintf( val, sizeof( val ), "%i%s", (int)value, suffix );
|
|
}
|
|
else
|
|
{
|
|
char fmt[ 32 ];
|
|
|
|
// Otherwise, create a format string for the decimals
|
|
V_snprintf( fmt, sizeof( fmt ), "%%.%if%s", digitsafterdecimal, suffix );
|
|
V_snprintf( val, sizeof( val ), fmt, value );
|
|
}
|
|
|
|
// Copy from in to out
|
|
char *i = val;
|
|
char *o = out;
|
|
|
|
// Search for decimal or if it was integral, find the space after the raw number
|
|
char *dot = strstr( i, "." );
|
|
if ( !dot )
|
|
{
|
|
dot = strstr( i, " " );
|
|
}
|
|
|
|
// Compute position of dot
|
|
int pos = dot - i;
|
|
// Don't put a comma if it's <= 3 long
|
|
pos -= 3;
|
|
|
|
while ( *i )
|
|
{
|
|
// If pos is still valid then insert a comma every third digit, except if we would be
|
|
// putting one in the first spot
|
|
if ( pos >= 0 && !( pos % 3 ) )
|
|
{
|
|
// Never in first spot
|
|
if ( o != out )
|
|
{
|
|
*o++ = ',';
|
|
}
|
|
}
|
|
|
|
// Count down comma position
|
|
pos--;
|
|
|
|
// Copy rest of data as normal
|
|
*o++ = *i++;
|
|
}
|
|
|
|
// Terminate
|
|
*o = 0;
|
|
|
|
return out;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns a string representation of an integer with commas
|
|
// separating the 1000s (ie, 37,426,421)
|
|
// Input : value - Value to convert
|
|
// Output : Pointer to a static buffer containing the output
|
|
//-----------------------------------------------------------------------------
|
|
#define NUM_PRETIFYNUM_BUFFERS 8 // Must be a power of two
|
|
char *V_pretifynum( int64 inputValue )
|
|
{
|
|
static char output[ NUM_PRETIFYMEM_BUFFERS ][ 32 ];
|
|
static int current;
|
|
|
|
// Point to the output buffer.
|
|
char * const out = output[ current ];
|
|
// Track the output buffer end for easy calculation of bytes-remaining.
|
|
const char* const outEnd = out + sizeof( output[ current ] );
|
|
|
|
// Point to the current output location in the output buffer.
|
|
char *pchRender = out;
|
|
// Move to the next output pointer.
|
|
current = ( current + 1 ) & ( NUM_PRETIFYMEM_BUFFERS -1 );
|
|
|
|
*out = 0;
|
|
|
|
// In order to handle the most-negative int64 we need to negate it
|
|
// into a uint64.
|
|
uint64 value;
|
|
// Render the leading minus sign, if necessary
|
|
if ( inputValue < 0 )
|
|
{
|
|
V_snprintf( pchRender, 32, "-" );
|
|
value = (uint64)-inputValue;
|
|
// Advance our output pointer.
|
|
pchRender += V_strlen( pchRender );
|
|
}
|
|
else
|
|
{
|
|
value = (uint64)inputValue;
|
|
}
|
|
|
|
// Now let's find out how big our number is. The largest number we can fit
|
|
// into 63 bits is about 9.2e18. So, there could potentially be six
|
|
// three-digit groups.
|
|
|
|
// We need the initial value of 'divisor' to be big enough to divide our
|
|
// number down to 1-999 range.
|
|
uint64 divisor = 1;
|
|
// Loop more than six times to avoid integer overflow.
|
|
for ( int i = 0; i < 6; ++i )
|
|
{
|
|
// If our divisor is already big enough then stop.
|
|
if ( value < divisor * 1000 )
|
|
break;
|
|
|
|
divisor *= 1000;
|
|
}
|
|
|
|
// Print the leading batch of one to three digits.
|
|
int toPrint = value / divisor;
|
|
V_snprintf( pchRender, outEnd - pchRender, "%d", toPrint );
|
|
|
|
for (;;)
|
|
{
|
|
// Advance our output pointer.
|
|
pchRender += V_strlen( pchRender );
|
|
// Adjust our value to be printed and our divisor.
|
|
value -= toPrint * divisor;
|
|
divisor /= 1000;
|
|
if ( !divisor )
|
|
break;
|
|
|
|
// The remaining blocks of digits always include a comma and three digits.
|
|
toPrint = value / divisor;
|
|
V_snprintf( pchRender, outEnd - pchRender, ",%03d", toPrint );
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: returns true if a wide character is a "mean" space; that is,
|
|
// if it is technically a space or punctuation, but causes disruptive
|
|
// behavior when used in names, web pages, chat windows, etc.
|
|
//
|
|
// characters in this set are removed from the beginning and/or end of strings
|
|
// by Q_AggressiveStripPrecedingAndTrailingWhitespaceW()
|
|
//-----------------------------------------------------------------------------
|
|
bool Q_IsMeanSpaceW( wchar_t wch )
|
|
{
|
|
bool bIsMean = false;
|
|
|
|
switch ( wch )
|
|
{
|
|
case L'\x0082': // BREAK PERMITTED HERE
|
|
case L'\x0083': // NO BREAK PERMITTED HERE
|
|
case L'\x00A0': // NO-BREAK SPACE
|
|
case L'\x034F': // COMBINING GRAPHEME JOINER
|
|
case L'\x2000': // EN QUAD
|
|
case L'\x2001': // EM QUAD
|
|
case L'\x2002': // EN SPACE
|
|
case L'\x2003': // EM SPACE
|
|
case L'\x2004': // THICK SPACE
|
|
case L'\x2005': // MID SPACE
|
|
case L'\x2006': // SIX SPACE
|
|
case L'\x2007': // figure space
|
|
case L'\x2008': // PUNCTUATION SPACE
|
|
case L'\x2009': // THIN SPACE
|
|
case L'\x200A': // HAIR SPACE
|
|
case L'\x200B': // ZERO-WIDTH SPACE
|
|
case L'\x200C': // ZERO-WIDTH NON-JOINER
|
|
case L'\x200D': // ZERO WIDTH JOINER
|
|
case L'\x200E': // LEFT-TO-RIGHT MARK
|
|
case L'\x2028': // LINE SEPARATOR
|
|
case L'\x2029': // PARAGRAPH SEPARATOR
|
|
case L'\x202F': // NARROW NO-BREAK SPACE
|
|
case L'\x2060': // word joiner
|
|
case L'\xFEFF': // ZERO-WIDTH NO BREAK SPACE
|
|
case L'\xFFFC': // OBJECT REPLACEMENT CHARACTER
|
|
bIsMean = true;
|
|
break;
|
|
}
|
|
|
|
return bIsMean;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: strips trailing whitespace; returns pointer inside string just past
|
|
// any leading whitespace.
|
|
//
|
|
// bAggresive = true causes this function to also check for "mean" spaces,
|
|
// which we don't want in persona names or chat strings as they're disruptive
|
|
// to the user experience.
|
|
//-----------------------------------------------------------------------------
|
|
static wchar_t *StripWhitespaceWorker( int cchLength, wchar_t *pwch, bool *pbStrippedWhitespace, bool bAggressive )
|
|
{
|
|
// walk backwards from the end of the string, killing any whitespace
|
|
*pbStrippedWhitespace = false;
|
|
|
|
wchar_t *pwchEnd = pwch + cchLength;
|
|
while ( --pwchEnd >= pwch )
|
|
{
|
|
if ( !iswspace( *pwchEnd ) && ( !bAggressive || !Q_IsMeanSpaceW( *pwchEnd ) ) )
|
|
break;
|
|
|
|
*pwchEnd = 0;
|
|
*pbStrippedWhitespace = true;
|
|
}
|
|
|
|
// walk forward in the string
|
|
while ( pwch < pwchEnd )
|
|
{
|
|
if ( !iswspace( *pwch ) )
|
|
break;
|
|
|
|
*pbStrippedWhitespace = true;
|
|
pwch++;
|
|
}
|
|
|
|
return pwch;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Strips all evil characters (ie. zero-width no-break space)
|
|
// from a string.
|
|
//-----------------------------------------------------------------------------
|
|
bool Q_RemoveAllEvilCharacters( char *pch )
|
|
{
|
|
// convert to unicode
|
|
int cch = Q_strlen( pch );
|
|
int cubDest = (cch + 1 ) * sizeof( wchar_t );
|
|
wchar_t *pwch = (wchar_t *)stackalloc( cubDest );
|
|
int cwch = Q_UTF8ToUnicode( pch, pwch, cubDest ) / sizeof( wchar_t );
|
|
|
|
bool bStrippedWhitespace = false;
|
|
|
|
// Walk through and skip over evil characters
|
|
int nWalk = 0;
|
|
for( int i=0; i<cwch; ++i )
|
|
{
|
|
if( !Q_IsMeanSpaceW( pwch[i] ) )
|
|
{
|
|
pwch[nWalk] = pwch[i];
|
|
++nWalk;
|
|
}
|
|
else
|
|
{
|
|
bStrippedWhitespace = true;
|
|
}
|
|
}
|
|
|
|
// Null terminate
|
|
pwch[nWalk-1] = L'\0';
|
|
|
|
|
|
// copy back, if necessary
|
|
if ( bStrippedWhitespace )
|
|
{
|
|
Q_UnicodeToUTF8( pwch, pch, cch );
|
|
}
|
|
|
|
return bStrippedWhitespace;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: strips leading and trailing whitespace
|
|
//-----------------------------------------------------------------------------
|
|
bool Q_StripPrecedingAndTrailingWhitespaceW( wchar_t *pwch )
|
|
{
|
|
int cch = Q_wcslen( pwch );
|
|
|
|
// Early out and don't convert if we don't have any chars or leading/trailing ws.
|
|
if ( ( cch < 1 ) || ( !iswspace( pwch[ 0 ] ) && !iswspace( pwch[ cch - 1 ] ) ) )
|
|
return false;
|
|
|
|
// duplicate on stack
|
|
int cubDest = ( cch + 1 ) * sizeof( wchar_t );
|
|
wchar_t *pwchT = (wchar_t *)stackalloc( cubDest );
|
|
Q_wcsncpy( pwchT, pwch, cubDest );
|
|
|
|
bool bStrippedWhitespace = false;
|
|
pwchT = StripWhitespaceWorker( cch, pwch, &bStrippedWhitespace, false /* not aggressive */ );
|
|
|
|
// copy back, if necessary
|
|
if ( bStrippedWhitespace )
|
|
{
|
|
Q_wcsncpy( pwch, pwchT, cubDest );
|
|
}
|
|
|
|
return bStrippedWhitespace;
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: strips leading and trailing whitespace,
|
|
// and also strips punctuation and formatting characters with "clear"
|
|
// representations.
|
|
//-----------------------------------------------------------------------------
|
|
bool Q_AggressiveStripPrecedingAndTrailingWhitespaceW( wchar_t *pwch )
|
|
{
|
|
// duplicate on stack
|
|
int cch = Q_wcslen( pwch );
|
|
int cubDest = ( cch + 1 ) * sizeof( wchar_t );
|
|
wchar_t *pwchT = (wchar_t *)stackalloc( cubDest );
|
|
Q_wcsncpy( pwchT, pwch, cubDest );
|
|
|
|
bool bStrippedWhitespace = false;
|
|
pwchT = StripWhitespaceWorker( cch, pwch, &bStrippedWhitespace, true /* is aggressive */ );
|
|
|
|
// copy back, if necessary
|
|
if ( bStrippedWhitespace )
|
|
{
|
|
Q_wcsncpy( pwch, pwchT, cubDest );
|
|
}
|
|
|
|
return bStrippedWhitespace;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: strips leading and trailing whitespace
|
|
//-----------------------------------------------------------------------------
|
|
bool Q_StripPrecedingAndTrailingWhitespace( char *pch )
|
|
{
|
|
int cch = Q_strlen( pch );
|
|
|
|
// Early out and don't convert if we don't have any chars or leading/trailing ws.
|
|
if ( ( cch < 1 ) || ( !isspace( (unsigned char)pch[ 0 ] ) && !isspace( (unsigned char)pch[ cch - 1 ] ) ) )
|
|
return false;
|
|
|
|
// convert to unicode
|
|
int cubDest = (cch + 1 ) * sizeof( wchar_t );
|
|
wchar_t *pwch = (wchar_t *)stackalloc( cubDest );
|
|
int cwch = Q_UTF8ToUnicode( pch, pwch, cubDest ) / sizeof( wchar_t );
|
|
|
|
bool bStrippedWhitespace = false;
|
|
pwch = StripWhitespaceWorker( cwch-1, pwch, &bStrippedWhitespace, false /* not aggressive */ );
|
|
|
|
// copy back, if necessary
|
|
if ( bStrippedWhitespace )
|
|
{
|
|
Q_UnicodeToUTF8( pwch, pch, cch );
|
|
}
|
|
|
|
return bStrippedWhitespace;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: strips leading and trailing whitespace
|
|
//-----------------------------------------------------------------------------
|
|
bool Q_AggressiveStripPrecedingAndTrailingWhitespace( char *pch )
|
|
{
|
|
// convert to unicode
|
|
int cch = Q_strlen( pch );
|
|
int cubDest = (cch + 1 ) * sizeof( wchar_t );
|
|
wchar_t *pwch = (wchar_t *)stackalloc( cubDest );
|
|
int cwch = Q_UTF8ToUnicode( pch, pwch, cubDest ) / sizeof( wchar_t );
|
|
|
|
bool bStrippedWhitespace = false;
|
|
pwch = StripWhitespaceWorker( cwch-1, pwch, &bStrippedWhitespace, true /* is aggressive */ );
|
|
|
|
// copy back, if necessary
|
|
if ( bStrippedWhitespace )
|
|
{
|
|
Q_UnicodeToUTF8( pwch, pch, cch );
|
|
}
|
|
|
|
return bStrippedWhitespace;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Converts a ucs2 string to a unicode (wchar_t) one, no-op on win32
|
|
//-----------------------------------------------------------------------------
|
|
int _V_UCS2ToUnicode( const ucs2 *pUCS2, wchar_t *pUnicode, int cubDestSizeInBytes )
|
|
{
|
|
Assert( cubDestSizeInBytes >= sizeof( *pUnicode ) );
|
|
AssertValidWritePtr(pUnicode);
|
|
AssertValidReadPtr(pUCS2);
|
|
|
|
pUnicode[0] = 0;
|
|
#ifdef _WIN32
|
|
int cchResult = V_wcslen( pUCS2 );
|
|
V_memcpy( pUnicode, pUCS2, cubDestSizeInBytes );
|
|
#else
|
|
iconv_t conv_t = iconv_open( "UCS-4LE", "UCS-2LE" );
|
|
int cchResult = -1;
|
|
size_t nLenUnicde = cubDestSizeInBytes;
|
|
size_t nMaxUTF8 = cubDestSizeInBytes;
|
|
char *pIn = (char *)pUCS2;
|
|
char *pOut = (char *)pUnicode;
|
|
if ( reinterpret_cast<int>(conv_t) > 0 )
|
|
{
|
|
cchResult = iconv( conv_t, &pIn, &nLenUnicde, &pOut, &nMaxUTF8 );
|
|
iconv_close( conv_t );
|
|
if ( (int)cchResult < 0 )
|
|
cchResult = 0;
|
|
else
|
|
cchResult = nMaxUTF8;
|
|
}
|
|
#endif
|
|
pUnicode[(cubDestSizeInBytes / sizeof(wchar_t)) - 1] = 0;
|
|
return cchResult;
|
|
|
|
}
|
|
|
|
#ifdef _PREFAST_
|
|
#pragma warning( pop ) // Restore the /analyze warnings
|
|
#endif
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Converts a wchar_t string into a UCS2 string -noop on windows
|
|
//-----------------------------------------------------------------------------
|
|
int _V_UnicodeToUCS2( const wchar_t *pUnicode, int cubSrcInBytes, char *pUCS2, int cubDestSizeInBytes )
|
|
{
|
|
#ifdef _WIN32
|
|
// Figure out which buffer is smaller and convert from bytes to character
|
|
// counts.
|
|
int cchResult = V_min( (size_t)cubSrcInBytes/sizeof(wchar_t), cubDestSizeInBytes/sizeof(wchar_t) );
|
|
wchar_t *pDest = (wchar_t*)pUCS2;
|
|
wcsncpy( pDest, pUnicode, cchResult );
|
|
// Make sure we NULL-terminate.
|
|
pDest[ cchResult - 1 ] = 0;
|
|
#elif defined (POSIX)
|
|
iconv_t conv_t = iconv_open( "UCS-2LE", "UTF-32LE" );
|
|
size_t cchResult = -1;
|
|
size_t nLenUnicde = cubSrcInBytes;
|
|
size_t nMaxUCS2 = cubDestSizeInBytes;
|
|
char *pIn = (char*)pUnicode;
|
|
char *pOut = pUCS2;
|
|
if ( reinterpret_cast<int>(conv_t) > 0 )
|
|
{
|
|
cchResult = iconv( conv_t, &pIn, &nLenUnicde, &pOut, &nMaxUCS2 );
|
|
iconv_close( conv_t );
|
|
if ( (int)cchResult < 0 )
|
|
cchResult = 0;
|
|
else
|
|
cchResult = cubSrcInBytes / sizeof( wchar_t );
|
|
}
|
|
#else
|
|
#error Must be implemented for this platform
|
|
#endif
|
|
return cchResult;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Converts a ucs-2 (windows wchar_t) string into a UTF8 (standard) string
|
|
//-----------------------------------------------------------------------------
|
|
int _V_UCS2ToUTF8( const ucs2 *pUCS2, char *pUTF8, int cubDestSizeInBytes )
|
|
{
|
|
AssertValidStringPtr(pUTF8, cubDestSizeInBytes);
|
|
AssertValidReadPtr(pUCS2);
|
|
|
|
pUTF8[0] = 0;
|
|
#ifdef _WIN32
|
|
// under win32 wchar_t == ucs2, sigh
|
|
int cchResult = WideCharToMultiByte( CP_UTF8, 0, pUCS2, -1, pUTF8, cubDestSizeInBytes, NULL, NULL );
|
|
#elif defined(POSIX)
|
|
iconv_t conv_t = iconv_open( "UTF-8", "UCS-2LE" );
|
|
size_t cchResult = -1;
|
|
|
|
// pUCS2 will be null-terminated so use that to work out the input
|
|
// buffer size. Note that we shouldn't assume iconv will stop when it
|
|
// finds a zero, and nLenUnicde should be given in bytes, so we multiply
|
|
// it by sizeof( ucs2 ) at the end.
|
|
size_t nLenUnicde = 0;
|
|
while ( pUCS2[nLenUnicde] )
|
|
{
|
|
++nLenUnicde;
|
|
}
|
|
nLenUnicde *= sizeof( ucs2 );
|
|
|
|
// Calculate number of bytes we want iconv to write, leaving space
|
|
// for the null-terminator
|
|
size_t nMaxUTF8 = cubDestSizeInBytes - 1;
|
|
char *pIn = (char *)pUCS2;
|
|
char *pOut = (char *)pUTF8;
|
|
if ( reinterpret_cast<int>(conv_t) > 0 )
|
|
{
|
|
const size_t nBytesToWrite = nMaxUTF8;
|
|
cchResult = iconv( conv_t, &pIn, &nLenUnicde, &pOut, &nMaxUTF8 );
|
|
|
|
// Calculate how many bytes were actually written and use that to
|
|
// null-terminate our output string.
|
|
const size_t nBytesWritten = nBytesToWrite - nMaxUTF8;
|
|
pUTF8[nBytesWritten] = 0;
|
|
|
|
iconv_close( conv_t );
|
|
if ( (int)cchResult < 0 )
|
|
cchResult = 0;
|
|
else
|
|
cchResult = nMaxUTF8;
|
|
}
|
|
#endif
|
|
pUTF8[cubDestSizeInBytes - 1] = 0;
|
|
return cchResult;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Converts a UTF8 to ucs-2 (windows wchar_t)
|
|
//-----------------------------------------------------------------------------
|
|
int _V_UTF8ToUCS2( const char *pUTF8, int cubSrcInBytes, ucs2 *pUCS2, int cubDestSizeInBytes )
|
|
{
|
|
Assert( cubDestSizeInBytes >= sizeof(pUCS2[0]) );
|
|
AssertValidStringPtr(pUTF8, cubDestSizeInBytes);
|
|
AssertValidReadPtr(pUCS2);
|
|
|
|
pUCS2[0] = 0;
|
|
#ifdef _WIN32
|
|
// under win32 wchar_t == ucs2, sigh
|
|
int cchResult = MultiByteToWideChar( CP_UTF8, 0, pUTF8, -1, pUCS2, cubDestSizeInBytes / sizeof(wchar_t) );
|
|
#elif defined( _PS3 ) // bugbug JLB
|
|
int cchResult = 0;
|
|
Assert( 0 );
|
|
#elif defined(POSIX)
|
|
iconv_t conv_t = iconv_open( "UCS-2LE", "UTF-8" );
|
|
size_t cchResult = -1;
|
|
size_t nLenUnicde = cubSrcInBytes;
|
|
size_t nMaxUTF8 = cubDestSizeInBytes;
|
|
char *pIn = (char *)pUTF8;
|
|
char *pOut = (char *)pUCS2;
|
|
if ( reinterpret_cast<int>(conv_t) > 0 )
|
|
{
|
|
cchResult = iconv( conv_t, &pIn, &nLenUnicde, &pOut, &nMaxUTF8 );
|
|
iconv_close( conv_t );
|
|
if ( (int)cchResult < 0 )
|
|
cchResult = 0;
|
|
else
|
|
cchResult = cubSrcInBytes;
|
|
|
|
}
|
|
#endif
|
|
pUCS2[ (cubDestSizeInBytes/sizeof(ucs2)) - 1] = 0;
|
|
return cchResult;
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the 4 bit nibble for a hex character
|
|
// Input : c -
|
|
// Output : unsigned char
|
|
//-----------------------------------------------------------------------------
|
|
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, int numchars, byte *out, int maxoutputbytes )
|
|
{
|
|
int len = V_strlen( in );
|
|
numchars = V_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;
|
|
int i;
|
|
|
|
p = out;
|
|
for ( i = 0;
|
|
( i < numchars ) && ( ( 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, int inputbytes, char *out, int 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, COPY_ALL_CHARACTERS );
|
|
}
|
|
}
|
|
|
|
// Even though \ on Posix (Linux&Mac) isn't techincally a path separator we are
|
|
// now counting it as one even Posix since so many times our filepaths aren't actual
|
|
// paths but rather text strings passed in from data files, treating \ as a pathseparator
|
|
// covers the full range of cases
|
|
bool PATHSEPARATOR( char c )
|
|
{
|
|
return c == '\\' || c == '/';
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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, int maxlen )
|
|
{
|
|
Assert( maxlen >= 1 );
|
|
Assert( in );
|
|
Assert( out );
|
|
|
|
if ( !in || !in[ 0 ] )
|
|
{
|
|
*out = 0;
|
|
return;
|
|
}
|
|
|
|
int 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;
|
|
|
|
int maxcopy = V_min( len + 1, maxlen );
|
|
|
|
// Copy partial string
|
|
V_strncpy( out, &in[start], maxcopy );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *ppath -
|
|
//-----------------------------------------------------------------------------
|
|
void V_StripTrailingSlash( char *ppath )
|
|
{
|
|
Assert( ppath );
|
|
|
|
int len = V_strlen( ppath );
|
|
if ( len > 0 )
|
|
{
|
|
if ( PATHSEPARATOR( ppath[ len - 1 ] ) )
|
|
{
|
|
ppath[ len - 1 ] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *in -
|
|
// *out -
|
|
// outSize -
|
|
//-----------------------------------------------------------------------------
|
|
void V_StripExtension( const char *in, char *out, int 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.
|
|
|
|
// scan backward for '.'
|
|
int end = V_strlen( in ) - 1;
|
|
while ( end > 0 && in[end] != '.' && !PATHSEPARATOR( in[end] ) )
|
|
{
|
|
--end;
|
|
}
|
|
|
|
if (end > 0 && !PATHSEPARATOR( in[end] ) && end < outSize)
|
|
{
|
|
int nChars = V_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 -
|
|
// *extension -
|
|
// pathStringLength -
|
|
//-----------------------------------------------------------------------------
|
|
void V_DefaultExtension( char *path, const char *extension, int pathStringLength )
|
|
{
|
|
Assert( path );
|
|
Assert( pathStringLength >= 1 );
|
|
Assert( extension );
|
|
Assert( extension[0] == '.' );
|
|
|
|
char *src;
|
|
|
|
// if path doesn't have a .EXT, append extension
|
|
// (extension should include the .)
|
|
src = path + V_strlen(path) - 1;
|
|
|
|
while ( !PATHSEPARATOR( *src ) && ( src > path ) )
|
|
{
|
|
if (*src == '.')
|
|
{
|
|
// it has an extension
|
|
return;
|
|
}
|
|
src--;
|
|
}
|
|
|
|
// Concatenate the desired extension
|
|
V_strncat( path, extension, pathStringLength, COPY_ALL_CHARACTERS );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Force extension...
|
|
// Input : *path -
|
|
// *extension -
|
|
// pathStringLength -
|
|
//-----------------------------------------------------------------------------
|
|
void V_SetExtension( char *path, const char *extension, int pathStringLength )
|
|
{
|
|
V_StripExtension( path, path, pathStringLength );
|
|
|
|
// We either had an extension and stripped it, or didn't have an extension
|
|
// at all. Either way, we need to concatenate our extension now.
|
|
|
|
// extension is not required to start with '.', so if it's not there,
|
|
// then append that first.
|
|
if ( extension[0] != '.' )
|
|
{
|
|
V_strncat( path, ".", pathStringLength, COPY_ALL_CHARACTERS );
|
|
}
|
|
|
|
V_strncat( path, extension, pathStringLength, COPY_ALL_CHARACTERS );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Remove final filename from string
|
|
// Input : *path -
|
|
// Output : void V_StripFilename
|
|
//-----------------------------------------------------------------------------
|
|
void V_StripFilename (char *path)
|
|
{
|
|
int length;
|
|
|
|
length = V_strlen( path )-1;
|
|
if ( length <= 0 )
|
|
return;
|
|
|
|
while ( length > 0 &&
|
|
!PATHSEPARATOR( path[length] ) )
|
|
{
|
|
length--;
|
|
}
|
|
|
|
path[ length ] = 0;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
#define CORRECT_PATH_SEPARATOR '\\'
|
|
#define INCORRECT_PATH_SEPARATOR '/'
|
|
#elif POSIX
|
|
#define CORRECT_PATH_SEPARATOR '/'
|
|
#define INCORRECT_PATH_SEPARATOR '\\'
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Changes all '/' or '\' characters into separator
|
|
// Input : *pname -
|
|
// separator -
|
|
//-----------------------------------------------------------------------------
|
|
void V_FixSlashes( char *pname, char separator /* = CORRECT_PATH_SEPARATOR */ )
|
|
{
|
|
while ( *pname )
|
|
{
|
|
if ( *pname == INCORRECT_PATH_SEPARATOR || *pname == CORRECT_PATH_SEPARATOR )
|
|
{
|
|
*pname = separator;
|
|
}
|
|
pname++;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: This function fixes cases of filenames like materials\\blah.vmt or somepath\otherpath\\ and removes the extra double slash.
|
|
//-----------------------------------------------------------------------------
|
|
void V_FixDoubleSlashes( char *pStr )
|
|
{
|
|
int len = V_strlen( pStr );
|
|
|
|
for ( int i=1; i < len-1; i++ )
|
|
{
|
|
if ( (pStr[i] == '/' || pStr[i] == '\\') && (pStr[i+1] == '/' || pStr[i+1] == '\\') )
|
|
{
|
|
// This means there's a double slash somewhere past the start of the filename. That
|
|
// can happen in Hammer if they use a material in the root directory. You'll get a filename
|
|
// that looks like 'materials\\blah.vmt'
|
|
V_memmove( &pStr[i], &pStr[i+1], len - i );
|
|
--len;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Strip off the last directory from dirName
|
|
// Input : *dirName -
|
|
// maxlen -
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool V_StripLastDir( char *dirName, int maxlen )
|
|
{
|
|
if( dirName[0] == 0 ||
|
|
!V_stricmp( dirName, "./" ) ||
|
|
!V_stricmp( dirName, ".\\" ) )
|
|
return false;
|
|
|
|
int len = V_strlen( dirName );
|
|
|
|
Assert( len < maxlen );
|
|
|
|
// skip trailing slash
|
|
if ( PATHSEPARATOR( dirName[len-1] ) )
|
|
{
|
|
len--;
|
|
}
|
|
|
|
while ( len > 0 )
|
|
{
|
|
if ( PATHSEPARATOR( dirName[len-1] ) )
|
|
{
|
|
dirName[len] = 0;
|
|
V_FixSlashes( dirName, CORRECT_PATH_SEPARATOR );
|
|
return true;
|
|
}
|
|
len--;
|
|
}
|
|
|
|
// 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 )
|
|
{
|
|
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 )
|
|
{
|
|
// 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, int 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 : *path -
|
|
// *dest -
|
|
// destSize -
|
|
// Output : void V_ExtractFilePath
|
|
//-----------------------------------------------------------------------------
|
|
bool V_ExtractFilePath (const char *path, char *dest, int destSize )
|
|
{
|
|
Assert( destSize >= 1 );
|
|
if ( destSize < 1 )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Last char
|
|
int 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--;
|
|
}
|
|
|
|
int copysize = V_min( src - path, destSize - 1 );
|
|
memcpy( dest, path, copysize );
|
|
dest[copysize] = 0;
|
|
|
|
return copysize != 0 ? true : false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *path -
|
|
// *dest -
|
|
// destSize -
|
|
// Output : void V_ExtractFileExtension
|
|
//-----------------------------------------------------------------------------
|
|
void V_ExtractFileExtension( const char *path, char *dest, int destSize )
|
|
{
|
|
*dest = NULL;
|
|
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 )
|
|
{
|
|
const char *src;
|
|
|
|
src = path + strlen(path) - 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: Returns a pointer to the filename part of a path string
|
|
// Input: in - file name
|
|
// Output: pointer to beginning of filename (after the "/"). If there were no /,
|
|
// output is identical to input
|
|
//-----------------------------------------------------------------------------
|
|
const char * V_GetFileName( const char * path )
|
|
{
|
|
return V_UnqualifiedFileName( path );
|
|
}
|
|
|
|
|
|
bool V_RemoveDotSlashes( char *pFilename, char separator, bool bRemoveDoubleSlashes /* = true */ )
|
|
{
|
|
char *pIn = pFilename;
|
|
char *pOut = pFilename;
|
|
bool bRetVal = true;
|
|
|
|
bool bBoundary = true;
|
|
while ( *pIn )
|
|
{
|
|
if ( bBoundary && pIn[0] == '.' && pIn[1] == '.' && ( PATHSEPARATOR( pIn[2] ) || !pIn[2] ) )
|
|
{
|
|
// Get rid of /../ or trailing /.. by backing pOut up to previous separator
|
|
|
|
// Eat the last separator (or repeated separators) we wrote out
|
|
while ( pOut != pFilename && pOut[-1] == separator )
|
|
{
|
|
--pOut;
|
|
}
|
|
|
|
while ( true )
|
|
{
|
|
if ( pOut == pFilename )
|
|
{
|
|
bRetVal = false; // backwards compat. return value, even though we continue handling
|
|
break;
|
|
}
|
|
--pOut;
|
|
if ( *pOut == separator )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Skip the '..' but not the slash, next loop iteration will handle separator
|
|
pIn += 2;
|
|
bBoundary = ( pOut == pFilename );
|
|
}
|
|
else if ( bBoundary && pIn[0] == '.' && ( PATHSEPARATOR( pIn[1] ) || !pIn[1] ) )
|
|
{
|
|
// Handle "./" by simply skipping this sequence. bBoundary is unchanged.
|
|
if ( PATHSEPARATOR( pIn[1] ) )
|
|
{
|
|
pIn += 2;
|
|
}
|
|
else
|
|
{
|
|
// Special case: if trailing "." is preceded by separator, eg "path/.",
|
|
// then the final separator should also be stripped. bBoundary may then
|
|
// be in an incorrect state, but we are at the end of processing anyway
|
|
// so we don't really care (the processing loop is about to terminate).
|
|
if ( pOut != pFilename && pOut[-1] == separator )
|
|
{
|
|
--pOut;
|
|
}
|
|
pIn += 1;
|
|
}
|
|
}
|
|
else if ( PATHSEPARATOR( pIn[0] ) )
|
|
{
|
|
*pOut = separator;
|
|
pOut += 1 - (bBoundary & bRemoveDoubleSlashes & (pOut != pFilename));
|
|
pIn += 1;
|
|
bBoundary = true;
|
|
}
|
|
else
|
|
{
|
|
if ( pOut != pIn )
|
|
{
|
|
*pOut = *pIn;
|
|
}
|
|
pOut += 1;
|
|
pIn += 1;
|
|
bBoundary = false;
|
|
}
|
|
}
|
|
*pOut = 0;
|
|
|
|
return bRetVal;
|
|
}
|
|
|
|
|
|
void V_AppendSlash( char *pStr, int strSize )
|
|
{
|
|
int len = V_strlen( pStr );
|
|
if ( len > 0 && !PATHSEPARATOR(pStr[len-1]) )
|
|
{
|
|
if ( len+1 >= strSize )
|
|
Error( "V_AppendSlash: ran out of space on %s.", pStr );
|
|
|
|
pStr[len] = CORRECT_PATH_SEPARATOR;
|
|
pStr[len+1] = 0;
|
|
}
|
|
}
|
|
|
|
|
|
void V_MakeAbsolutePath( char *pOut, int outLen, const char *pPath, const char *pStartingDir )
|
|
{
|
|
if ( V_IsAbsolutePath( pPath ) )
|
|
{
|
|
// pPath is not relative.. just copy it.
|
|
V_strncpy( pOut, pPath, outLen );
|
|
}
|
|
else
|
|
{
|
|
// Make sure the starting directory is absolute..
|
|
if ( pStartingDir && V_IsAbsolutePath( pStartingDir ) )
|
|
{
|
|
V_strncpy( pOut, pStartingDir, outLen );
|
|
}
|
|
else
|
|
{
|
|
if ( !_getcwd( pOut, outLen ) )
|
|
Error( "V_MakeAbsolutePath: _getcwd failed." );
|
|
|
|
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_RemoveDotSlashes( pOut ) )
|
|
Error( "V_MakeAbsolutePath: tried to \"..\" past the root." );
|
|
|
|
//V_FixSlashes( pOut ); - handled by V_RemoveDotSlashes
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Makes a relative path
|
|
//-----------------------------------------------------------------------------
|
|
bool V_MakeRelativePath( const char *pFullPath, const char *pDirectory, char *pRelativePath, int 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 ../
|
|
int 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;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// small helper function shared by lots of modules
|
|
//-----------------------------------------------------------------------------
|
|
bool V_IsAbsolutePath( const char *pStr )
|
|
{
|
|
bool bIsAbsolute = ( pStr[0] && pStr[1] == ':' ) || pStr[0] == '/' || pStr[0] == '\\';
|
|
if ( IsX360() && !bIsAbsolute )
|
|
{
|
|
bIsAbsolute = ( V_stristr( pStr, ":" ) != NULL );
|
|
}
|
|
return bIsAbsolute;
|
|
}
|
|
|
|
|
|
// Copies at most nCharsToCopy bytes from pIn into pOut.
|
|
// Returns false if it would have overflowed pOut's buffer.
|
|
static bool CopyToMaxChars( char *pOut, int outSize, const char *pIn, int nCharsToCopy )
|
|
{
|
|
if ( outSize == 0 )
|
|
return false;
|
|
|
|
int iOut = 0;
|
|
while ( *pIn && nCharsToCopy > 0 )
|
|
{
|
|
if ( iOut == (outSize-1) )
|
|
{
|
|
pOut[iOut] = 0;
|
|
return false;
|
|
}
|
|
pOut[iOut] = *pIn;
|
|
++iOut;
|
|
++pIn;
|
|
--nCharsToCopy;
|
|
}
|
|
|
|
pOut[iOut] = 0;
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Fixes up a file name, removing dot slashes, fixing slashes, converting to lowercase, etc.
|
|
//-----------------------------------------------------------------------------
|
|
void V_FixupPathName( char *pOut, size_t nOutLen, const char *pPath )
|
|
{
|
|
V_strncpy( pOut, pPath, nOutLen );
|
|
V_RemoveDotSlashes( pOut, CORRECT_PATH_SEPARATOR, true );
|
|
#ifdef WIN32
|
|
V_strlower( pOut );
|
|
#endif
|
|
}
|
|
|
|
|
|
// Returns true if it completed successfully.
|
|
// If it would overflow pOut, it fills as much as it can and returns false.
|
|
bool V_StrSubst(
|
|
const char *pIn,
|
|
const char *pMatch,
|
|
const char *pReplaceWith,
|
|
char *pOut,
|
|
int outLen,
|
|
bool bCaseSensitive
|
|
)
|
|
{
|
|
int replaceFromLen = strlen( pMatch );
|
|
int replaceToLen = strlen( pReplaceWith );
|
|
|
|
const char *pInStart = pIn;
|
|
char *pOutPos = pOut;
|
|
pOutPos[0] = 0;
|
|
|
|
while ( 1 )
|
|
{
|
|
int nRemainingOut = outLen - (pOutPos - pOut);
|
|
|
|
const char *pTestPos = ( bCaseSensitive ? strstr( pInStart, pMatch ) : V_stristr( pInStart, pMatch ) );
|
|
if ( pTestPos )
|
|
{
|
|
// Found an occurence of pMatch. First, copy whatever leads up to the string.
|
|
int copyLen = pTestPos - pInStart;
|
|
if ( !CopyToMaxChars( pOutPos, nRemainingOut, pInStart, copyLen ) )
|
|
return false;
|
|
|
|
// Did we hit the end of the output string?
|
|
if ( copyLen > nRemainingOut-1 )
|
|
return false;
|
|
|
|
pOutPos += strlen( pOutPos );
|
|
nRemainingOut = outLen - (pOutPos - pOut);
|
|
|
|
// Now add the replacement string.
|
|
if ( !CopyToMaxChars( pOutPos, nRemainingOut, pReplaceWith, replaceToLen ) )
|
|
return false;
|
|
|
|
pInStart += copyLen + replaceFromLen;
|
|
pOutPos += replaceToLen;
|
|
}
|
|
else
|
|
{
|
|
// We're at the end of pIn. Copy whatever remains and get out.
|
|
int copyLen = strlen( pInStart );
|
|
V_strncpy( pOutPos, pInStart, nRemainingOut );
|
|
return ( copyLen <= nRemainingOut-1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
char* AllocString( const char *pStr, int nMaxChars )
|
|
{
|
|
int allocLen;
|
|
if ( nMaxChars == -1 )
|
|
allocLen = strlen( pStr ) + 1;
|
|
else
|
|
allocLen = V_min( (int)strlen(pStr), nMaxChars ) + 1;
|
|
|
|
char *pOut = new char[allocLen];
|
|
V_strncpy( pOut, pStr, allocLen );
|
|
return pOut;
|
|
}
|
|
|
|
|
|
void V_SplitString2( const char *pString, const char **pSeparators, int nSeparators, CUtlVector<char*> &outStrings )
|
|
{
|
|
outStrings.Purge();
|
|
const char *pCurPos = pString;
|
|
while ( 1 )
|
|
{
|
|
int iFirstSeparator = -1;
|
|
const char *pFirstSeparator = 0;
|
|
for ( int i=0; i < nSeparators; i++ )
|
|
{
|
|
const char *pTest = V_stristr( pCurPos, pSeparators[i] );
|
|
if ( pTest && (!pFirstSeparator || pTest < pFirstSeparator) )
|
|
{
|
|
iFirstSeparator = i;
|
|
pFirstSeparator = pTest;
|
|
}
|
|
}
|
|
|
|
if ( pFirstSeparator )
|
|
{
|
|
// Split on this separator and continue on.
|
|
int 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, CUtlVector<char*> &outStrings )
|
|
{
|
|
V_SplitString2( pString, &pSeparator, 1, outStrings );
|
|
}
|
|
|
|
|
|
bool V_GetCurrentDirectory( char *pOut, int maxLen )
|
|
{
|
|
return _getcwd( pOut, maxLen ) == pOut;
|
|
}
|
|
|
|
|
|
bool V_SetCurrentDirectory( const char *pDirName )
|
|
{
|
|
return _chdir( pDirName ) == 0;
|
|
}
|
|
|
|
|
|
// This function takes a slice out of pStr and stores it in pOut.
|
|
// It follows the Python slice convention:
|
|
// Negative numbers wrap around the string (-1 references the last character).
|
|
// Numbers are clamped to the end of the string.
|
|
void V_StrSlice( const char *pStr, int firstChar, int lastCharNonInclusive, char *pOut, int outSize )
|
|
{
|
|
if ( outSize == 0 )
|
|
return;
|
|
|
|
int length = strlen( pStr );
|
|
|
|
// Fixup the string indices.
|
|
if ( firstChar < 0 )
|
|
{
|
|
firstChar = length - (-firstChar % length);
|
|
}
|
|
else if ( firstChar >= length )
|
|
{
|
|
pOut[0] = 0;
|
|
return;
|
|
}
|
|
|
|
if ( lastCharNonInclusive < 0 )
|
|
{
|
|
lastCharNonInclusive = length - (-lastCharNonInclusive % length);
|
|
}
|
|
else if ( lastCharNonInclusive > length )
|
|
{
|
|
lastCharNonInclusive %= length;
|
|
}
|
|
|
|
if ( lastCharNonInclusive <= firstChar )
|
|
{
|
|
pOut[0] = 0;
|
|
return;
|
|
}
|
|
|
|
int copyLen = lastCharNonInclusive - firstChar;
|
|
if ( copyLen <= (outSize-1) )
|
|
{
|
|
memcpy( pOut, &pStr[firstChar], copyLen );
|
|
pOut[copyLen] = 0;
|
|
}
|
|
else
|
|
{
|
|
memcpy( pOut, &pStr[firstChar], outSize-1 );
|
|
pOut[outSize-1] = 0;
|
|
}
|
|
}
|
|
|
|
|
|
void V_StrLeft( const char *pStr, int nChars, char *pOut, int outSize )
|
|
{
|
|
if ( nChars == 0 )
|
|
{
|
|
if ( outSize != 0 )
|
|
pOut[0] = 0;
|
|
|
|
return;
|
|
}
|
|
|
|
V_StrSlice( pStr, 0, nChars, pOut, outSize );
|
|
}
|
|
|
|
|
|
void V_StrRight( const char *pStr, int nChars, char *pOut, int outSize )
|
|
{
|
|
int len = strlen( pStr );
|
|
if ( nChars >= len )
|
|
{
|
|
V_strncpy( pOut, pStr, outSize );
|
|
}
|
|
else
|
|
{
|
|
V_StrSlice( pStr, -nChars, strlen( pStr ), pOut, outSize );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Convert multibyte to wchar + back
|
|
//-----------------------------------------------------------------------------
|
|
void V_strtowcs( const char *pString, int nInSize, wchar_t *pWString, int nOutSizeInBytes )
|
|
{
|
|
Assert( nOutSizeInBytes >= sizeof(pWString[0]) );
|
|
#ifdef _WIN32
|
|
int nOutSizeInChars = nOutSizeInBytes / sizeof(pWString[0]);
|
|
int result = MultiByteToWideChar( CP_UTF8, 0, pString, nInSize, pWString, nOutSizeInChars );
|
|
// If the string completely fails to fit then MultiByteToWideChar will return 0.
|
|
// If the string exactly fits but with no room for a null-terminator then MultiByteToWideChar
|
|
// will happily fill the buffer and omit the null-terminator, returning nOutSizeInChars.
|
|
// Either way we need to return an empty string rather than a bogus and possibly not
|
|
// null-terminated result.
|
|
if ( result <= 0 || result >= nOutSizeInChars )
|
|
{
|
|
// If nInSize includes the null-terminator then a result of nOutSizeInChars is
|
|
// legal. We check this by seeing if the last character in the output buffer is
|
|
// a zero.
|
|
if ( result == nOutSizeInChars && pWString[ nOutSizeInChars - 1 ] == 0)
|
|
{
|
|
// We're okay! Do nothing.
|
|
}
|
|
else
|
|
{
|
|
// The string completely to fit. Null-terminate the buffer.
|
|
*pWString = L'\0';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We have successfully converted our string. Now we need to null-terminate it, because
|
|
// MultiByteToWideChar will only do that if nInSize includes the source null-terminator!
|
|
pWString[ result ] = 0;
|
|
}
|
|
#elif POSIX
|
|
if ( mbstowcs( pWString, pString, nOutSizeInBytes / sizeof(pWString[0]) ) <= 0 )
|
|
{
|
|
*pWString = 0;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void V_wcstostr( const wchar_t *pWString, int nInSize, char *pString, int nOutSizeInChars )
|
|
{
|
|
#ifdef _WIN32
|
|
int result = WideCharToMultiByte( CP_UTF8, 0, pWString, nInSize, pString, nOutSizeInChars, NULL, NULL );
|
|
// If the string completely fails to fit then MultiByteToWideChar will return 0.
|
|
// If the string exactly fits but with no room for a null-terminator then MultiByteToWideChar
|
|
// will happily fill the buffer and omit the null-terminator, returning nOutSizeInChars.
|
|
// Either way we need to return an empty string rather than a bogus and possibly not
|
|
// null-terminated result.
|
|
if ( result <= 0 || result >= nOutSizeInChars )
|
|
{
|
|
// If nInSize includes the null-terminator then a result of nOutSizeInChars is
|
|
// legal. We check this by seeing if the last character in the output buffer is
|
|
// a zero.
|
|
if ( result == nOutSizeInChars && pWString[ nOutSizeInChars - 1 ] == 0)
|
|
{
|
|
// We're okay! Do nothing.
|
|
}
|
|
else
|
|
{
|
|
*pString = '\0';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We have successfully converted our string. Now we need to null-terminate it, because
|
|
// MultiByteToWideChar will only do that if nInSize includes the source null-terminator!
|
|
pString[ result ] = '\0';
|
|
}
|
|
#elif POSIX
|
|
if ( wcstombs( pString, pWString, nOutSizeInChars ) <= 0 )
|
|
{
|
|
*pString = '\0';
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------------
|
|
// backslashification
|
|
//--------------------------------------------------------------------------------
|
|
|
|
static char s_BackSlashMap[]="\tt\nn\rr\"\"\\\\";
|
|
|
|
char *V_AddBackSlashesToSpecialChars( char const *pSrc )
|
|
{
|
|
// first, count how much space we are going to need
|
|
int nSpaceNeeded = 0;
|
|
for( char const *pScan = pSrc; *pScan; pScan++ )
|
|
{
|
|
nSpaceNeeded++;
|
|
for(char const *pCharSet=s_BackSlashMap; *pCharSet; pCharSet += 2 )
|
|
{
|
|
if ( *pCharSet == *pScan )
|
|
nSpaceNeeded++; // we need to store a bakslash
|
|
}
|
|
}
|
|
char *pRet = new char[ nSpaceNeeded + 1 ]; // +1 for null
|
|
char *pOut = pRet;
|
|
|
|
for( char const *pScan = pSrc; *pScan; pScan++ )
|
|
{
|
|
bool bIsSpecial = false;
|
|
for(char const *pCharSet=s_BackSlashMap; *pCharSet; pCharSet += 2 )
|
|
{
|
|
if ( *pCharSet == *pScan )
|
|
{
|
|
*( pOut++ ) = '\\';
|
|
*( pOut++ ) = pCharSet[1];
|
|
bIsSpecial = true;
|
|
break;
|
|
}
|
|
}
|
|
if (! bIsSpecial )
|
|
{
|
|
*( pOut++ ) = *pScan;
|
|
}
|
|
}
|
|
*( pOut++ ) = 0;
|
|
return pRet;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Helper for converting a numeric value to a hex digit, value should be 0-15.
|
|
//-----------------------------------------------------------------------------
|
|
char cIntToHexDigit( int nValue )
|
|
{
|
|
Assert( nValue >= 0 && nValue <= 15 );
|
|
return "0123456789ABCDEF"[ nValue & 15 ];
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Helper for converting a hex char value to numeric, return -1 if the char
|
|
// is not a valid hex digit.
|
|
//-----------------------------------------------------------------------------
|
|
int iHexCharToInt( char cValue )
|
|
{
|
|
int32 iValue = cValue;
|
|
if ( (uint32)( iValue - '0' ) < 10 )
|
|
return iValue - '0';
|
|
|
|
iValue |= 0x20;
|
|
if ( (uint32)( iValue - 'a' ) < 6 )
|
|
return iValue - 'a' + 10;
|
|
|
|
return -1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Internal implementation of encode, works in the strict RFC manner, or
|
|
// with spaces turned to + like HTML form encoding.
|
|
//-----------------------------------------------------------------------------
|
|
void Q_URLEncodeInternal( char *pchDest, int nDestLen, const char *pchSource, int nSourceLen, bool bUsePlusForSpace )
|
|
{
|
|
if ( nDestLen < 3*nSourceLen )
|
|
{
|
|
pchDest[0] = '\0';
|
|
AssertMsg( false, "Target buffer for Q_URLEncode needs to be 3 times larger than source to guarantee enough space\n" );
|
|
return;
|
|
}
|
|
|
|
int iDestPos = 0;
|
|
for ( int i=0; i < nSourceLen; ++i )
|
|
{
|
|
// We allow only a-z, A-Z, 0-9, period, underscore, and hyphen to pass through unescaped.
|
|
// These are the characters allowed by both the original RFC 1738 and the latest RFC 3986.
|
|
// Current specs also allow '~', but that is forbidden under original RFC 1738.
|
|
if ( !( pchSource[i] >= 'a' && pchSource[i] <= 'z' ) && !( pchSource[i] >= 'A' && pchSource[i] <= 'Z' ) && !(pchSource[i] >= '0' && pchSource[i] <= '9' )
|
|
&& pchSource[i] != '-' && pchSource[i] != '_' && pchSource[i] != '.'
|
|
)
|
|
{
|
|
if ( bUsePlusForSpace && pchSource[i] == ' ' )
|
|
{
|
|
pchDest[iDestPos++] = '+';
|
|
}
|
|
else
|
|
{
|
|
pchDest[iDestPos++] = '%';
|
|
uint8 iValue = pchSource[i];
|
|
if ( iValue == 0 )
|
|
{
|
|
pchDest[iDestPos++] = '0';
|
|
pchDest[iDestPos++] = '0';
|
|
}
|
|
else
|
|
{
|
|
char cHexDigit1 = cIntToHexDigit( iValue % 16 );
|
|
iValue /= 16;
|
|
char cHexDigit2 = cIntToHexDigit( iValue );
|
|
pchDest[iDestPos++] = cHexDigit2;
|
|
pchDest[iDestPos++] = cHexDigit1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pchDest[iDestPos++] = pchSource[i];
|
|
}
|
|
}
|
|
|
|
// Null terminate
|
|
pchDest[iDestPos++] = 0;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Internal implementation of decode, works in the strict RFC manner, or
|
|
// with spaces turned to + like HTML form encoding.
|
|
//
|
|
// Returns the amount of space used in the output buffer.
|
|
//-----------------------------------------------------------------------------
|
|
size_t Q_URLDecodeInternal( char *pchDecodeDest, int nDecodeDestLen, const char *pchEncodedSource, int nEncodedSourceLen, bool bUsePlusForSpace )
|
|
{
|
|
if ( nDecodeDestLen < nEncodedSourceLen )
|
|
{
|
|
AssertMsg( false, "Q_URLDecode needs a dest buffer at least as large as the source" );
|
|
return 0;
|
|
}
|
|
|
|
int iDestPos = 0;
|
|
for( int i=0; i < nEncodedSourceLen; ++i )
|
|
{
|
|
if ( bUsePlusForSpace && pchEncodedSource[i] == '+' )
|
|
{
|
|
pchDecodeDest[ iDestPos++ ] = ' ';
|
|
}
|
|
else if ( pchEncodedSource[i] == '%' )
|
|
{
|
|
// Percent signifies an encoded value, look ahead for the hex code, convert to numeric, and use that
|
|
|
|
// First make sure we have 2 more chars
|
|
if ( i < nEncodedSourceLen - 2 )
|
|
{
|
|
char cHexDigit1 = pchEncodedSource[i+1];
|
|
char cHexDigit2 = pchEncodedSource[i+2];
|
|
|
|
// Turn the chars into a hex value, if they are not valid, then we'll
|
|
// just place the % and the following two chars direct into the string,
|
|
// even though this really shouldn't happen, who knows what bad clients
|
|
// may do with encoding.
|
|
bool bValid = false;
|
|
int iValue = iHexCharToInt( cHexDigit1 );
|
|
if ( iValue != -1 )
|
|
{
|
|
iValue *= 16;
|
|
int iValue2 = iHexCharToInt( cHexDigit2 );
|
|
if ( iValue2 != -1 )
|
|
{
|
|
iValue += iValue2;
|
|
pchDecodeDest[ iDestPos++ ] = iValue;
|
|
bValid = true;
|
|
}
|
|
}
|
|
|
|
if ( !bValid )
|
|
{
|
|
pchDecodeDest[ iDestPos++ ] = '%';
|
|
pchDecodeDest[ iDestPos++ ] = cHexDigit1;
|
|
pchDecodeDest[ iDestPos++ ] = cHexDigit2;
|
|
}
|
|
}
|
|
|
|
// Skip ahead
|
|
i += 2;
|
|
}
|
|
else
|
|
{
|
|
pchDecodeDest[ iDestPos++ ] = pchEncodedSource[i];
|
|
}
|
|
}
|
|
|
|
// We may not have extra room to NULL terminate, since this can be used on raw data, but if we do
|
|
// go ahead and do it as this can avoid bugs.
|
|
if ( iDestPos < nDecodeDestLen )
|
|
{
|
|
pchDecodeDest[iDestPos] = 0;
|
|
}
|
|
|
|
return (size_t)iDestPos;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Encodes a string (or binary data) from URL encoding format, see rfc1738 section 2.2.
|
|
// This version of the call isn't a strict RFC implementation, but uses + for space as is
|
|
// the standard in HTML form encoding, despite it not being part of the RFC.
|
|
//
|
|
// Dest buffer should be at least as large as source buffer to guarantee room for decode.
|
|
//-----------------------------------------------------------------------------
|
|
void Q_URLEncode( char *pchDest, int nDestLen, const char *pchSource, int nSourceLen )
|
|
{
|
|
return Q_URLEncodeInternal( pchDest, nDestLen, pchSource, nSourceLen, true );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Decodes a string (or binary data) from URL encoding format, see rfc1738 section 2.2.
|
|
// This version of the call isn't a strict RFC implementation, but uses + for space as is
|
|
// the standard in HTML form encoding, despite it not being part of the RFC.
|
|
//
|
|
// Dest buffer should be at least as large as source buffer to guarantee room for decode.
|
|
// Dest buffer being the same as the source buffer (decode in-place) is explicitly allowed.
|
|
//-----------------------------------------------------------------------------
|
|
size_t Q_URLDecode( char *pchDecodeDest, int nDecodeDestLen, const char *pchEncodedSource, int nEncodedSourceLen )
|
|
{
|
|
return Q_URLDecodeInternal( pchDecodeDest, nDecodeDestLen, pchEncodedSource, nEncodedSourceLen, true );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Encodes a string (or binary data) from URL encoding format, see rfc1738 section 2.2.
|
|
// This version will not encode space as + (which HTML form encoding uses despite not being part of the RFC)
|
|
//
|
|
// Dest buffer should be at least as large as source buffer to guarantee room for decode.
|
|
//-----------------------------------------------------------------------------
|
|
void Q_URLEncodeRaw( char *pchDest, int nDestLen, const char *pchSource, int nSourceLen )
|
|
{
|
|
return Q_URLEncodeInternal( pchDest, nDestLen, pchSource, nSourceLen, false );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Decodes a string (or binary data) from URL encoding format, see rfc1738 section 2.2.
|
|
// This version will not recognize + as a space (which HTML form encoding uses despite not being part of the RFC)
|
|
//
|
|
// Dest buffer should be at least as large as source buffer to guarantee room for decode.
|
|
// Dest buffer being the same as the source buffer (decode in-place) is explicitly allowed.
|
|
//-----------------------------------------------------------------------------
|
|
size_t Q_URLDecodeRaw( char *pchDecodeDest, int nDecodeDestLen, const char *pchEncodedSource, int nEncodedSourceLen )
|
|
{
|
|
return Q_URLDecodeInternal( pchDecodeDest, nDecodeDestLen, pchEncodedSource, nEncodedSourceLen, false );
|
|
}
|
|
|
|
#if defined( LINUX ) || defined( _PS3 )
|
|
extern "C" void qsort_s( void *base, size_t num, size_t width, int (*compare )(void *, const void *, const void *), void * context );
|
|
#endif
|
|
|
|
void V_qsort_s( void *base, size_t num, size_t width, int ( __cdecl *compare )(void *, const void *, const void *), void * context )
|
|
{
|
|
#if defined OSX
|
|
// the arguments are swapped 'round on the mac - awesome, huh?
|
|
return qsort_r( base, num, width, context, compare );
|
|
#else
|
|
return qsort_s( base, num, width, compare, context );
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: format the time and/or date with the user's current locale
|
|
// If timeVal is 0, gets the current time
|
|
//
|
|
// This is generally for use with chatroom dialogs, etc. which need to be
|
|
// able to say "Last message received: %date% at %time%"
|
|
//
|
|
// Note that this uses time_t because RTime32 is not hooked-up on the client
|
|
//-----------------------------------------------------------------------------
|
|
bool BGetLocalFormattedDateAndTime( time_t timeVal, char *pchDate, int cubDate, char *pchTime, int cubTime )
|
|
{
|
|
if ( 0 == timeVal || timeVal < 0 )
|
|
{
|
|
// get the current time
|
|
time( &timeVal );
|
|
}
|
|
|
|
if ( timeVal )
|
|
{
|
|
// Convert it to our local time
|
|
struct tm tmStruct;
|
|
struct tm tmToDisplay = *( Plat_localtime( ( const time_t* )&timeVal, &tmStruct ) );
|
|
#ifdef POSIX
|
|
if ( pchDate != NULL )
|
|
{
|
|
pchDate[ 0 ] = 0;
|
|
if ( 0 == strftime( pchDate, cubDate, "%A %b %d", &tmToDisplay ) )
|
|
return false;
|
|
}
|
|
|
|
if ( pchTime != NULL )
|
|
{
|
|
pchTime[ 0 ] = 0;
|
|
if ( 0 == strftime( pchTime, cubTime - 6, "%I:%M ", &tmToDisplay ) )
|
|
return false;
|
|
|
|
// append am/pm in lower case (since strftime doesn't have a lowercase formatting option)
|
|
if (tmToDisplay.tm_hour >= 12)
|
|
{
|
|
Q_strcat( pchTime, "p.m.", cubTime );
|
|
}
|
|
else
|
|
{
|
|
Q_strcat( pchTime, "a.m.", cubTime );
|
|
}
|
|
}
|
|
#else // WINDOWS
|
|
// convert time_t to a SYSTEMTIME
|
|
SYSTEMTIME st;
|
|
st.wHour = tmToDisplay.tm_hour;
|
|
st.wMinute = tmToDisplay.tm_min;
|
|
st.wSecond = tmToDisplay.tm_sec;
|
|
st.wDay = tmToDisplay.tm_mday;
|
|
st.wMonth = tmToDisplay.tm_mon + 1;
|
|
st.wYear = tmToDisplay.tm_year + 1900;
|
|
st.wDayOfWeek = tmToDisplay.tm_wday;
|
|
st.wMilliseconds = 0;
|
|
|
|
WCHAR rgwch[ MAX_PATH ];
|
|
|
|
if ( pchDate != NULL )
|
|
{
|
|
pchDate[ 0 ] = 0;
|
|
if ( !GetDateFormatW( LOCALE_USER_DEFAULT, DATE_LONGDATE, &st, NULL, rgwch, MAX_PATH ) )
|
|
return false;
|
|
Q_strncpy( pchDate, CStrAutoEncode( rgwch ).ToString(), cubDate );
|
|
}
|
|
|
|
if ( pchTime != NULL )
|
|
{
|
|
pchTime[ 0 ] = 0;
|
|
if ( !GetTimeFormatW( LOCALE_USER_DEFAULT, TIME_NOSECONDS, &st, NULL, rgwch, MAX_PATH ) )
|
|
return false;
|
|
Q_strncpy( pchTime, CStrAutoEncode( rgwch ).ToString(), cubTime );
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
// And a couple of helpers so people don't have to remember the order of the parameters in the above function
|
|
bool BGetLocalFormattedDate( time_t timeVal, char *pchDate, int cubDate )
|
|
{
|
|
return BGetLocalFormattedDateAndTime( timeVal, pchDate, cubDate, NULL, 0 );
|
|
}
|
|
bool BGetLocalFormattedTime( time_t timeVal, char *pchTime, int cubTime )
|
|
{
|
|
return BGetLocalFormattedDateAndTime( timeVal, NULL, 0, pchTime, cubTime );
|
|
}
|