DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

Str.cpp (39829B)


      1 /*
      2 ===========================================================================
      3 
      4 Doom 3 BFG Edition GPL Source Code
      5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 
      6 
      7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").  
      8 
      9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
     10 it under the terms of the GNU General Public License as published by
     11 the Free Software Foundation, either version 3 of the License, or
     12 (at your option) any later version.
     13 
     14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
     15 but WITHOUT ANY WARRANTY; without even the implied warranty of
     16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17 GNU General Public License for more details.
     18 
     19 You should have received a copy of the GNU General Public License
     20 along with Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.
     21 
     22 In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code.  If not, please request a copy in writing from id Software at the address below.
     23 
     24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
     25 
     26 ===========================================================================
     27 */
     28 
     29 #include "precompiled.h"
     30 #pragma hdrstop
     31 
     32 #ifdef USE_STRING_DATA_ALLOCATOR
     33 static idDynamicBlockAlloc<char, 1<<18, 128, TAG_STRING>	stringDataAllocator;
     34 #endif
     35 
     36 idVec4	g_color_table[16] =
     37 {
     38 	idVec4(0.0f, 0.0f, 0.0f, 1.0f),
     39 	idVec4(1.0f, 0.0f, 0.0f, 1.0f), // S_COLOR_RED
     40 	idVec4(0.0f, 1.0f, 0.0f, 1.0f), // S_COLOR_GREEN
     41 	idVec4(1.0f, 1.0f, 0.0f, 1.0f), // S_COLOR_YELLOW
     42 	idVec4(0.0f, 0.0f, 1.0f, 1.0f), // S_COLOR_BLUE
     43 	idVec4(0.0f, 1.0f, 1.0f, 1.0f), // S_COLOR_CYAN
     44 	idVec4(1.0f, 0.5f, 0.0f, 1.0f), // S_COLOR_ORANGE
     45 	idVec4(1.0f, 1.0f, 1.0f, 1.0f), // S_COLOR_WHITE
     46 	idVec4(0.5f, 0.5f, 0.5f, 1.0f), // S_COLOR_GRAY
     47 	idVec4(0.0f, 0.0f, 0.0f, 1.0f), // S_COLOR_BLACK
     48 	idVec4(0.0f, 0.0f, 0.0f, 1.0f),
     49 	idVec4(0.0f, 0.0f, 0.0f, 1.0f),
     50 	idVec4(0.0f, 0.0f, 0.0f, 1.0f),
     51 	idVec4(0.0f, 0.0f, 0.0f, 1.0f),
     52 	idVec4(0.0f, 0.0f, 0.0f, 1.0f),
     53 	idVec4(0.0f, 0.0f, 0.0f, 1.0f),
     54 };
     55 
     56 const char *units[2][4] =
     57 {
     58 	{ "B", "KB", "MB", "GB" },
     59 	{ "B/s", "KB/s", "MB/s", "GB/s" }
     60 };
     61 
     62 /*
     63 ============
     64 idStr::ColorForIndex
     65 ============
     66 */
     67 idVec4 & idStr::ColorForIndex( int i ) {
     68 	return g_color_table[ i & 15 ];
     69 }
     70 
     71 /*
     72 ============
     73 idStr::ReAllocate
     74 ============
     75 */
     76 void idStr::ReAllocate( int amount, bool keepold ) {
     77 	char	*newbuffer;
     78 	int		newsize;
     79 	int		mod;
     80 
     81 	//assert( data );
     82 	assert( amount > 0 );
     83 
     84 	mod = amount % STR_ALLOC_GRAN;
     85 	if ( !mod ) {
     86 		newsize = amount;
     87 	}
     88 	else {
     89 		newsize = amount + STR_ALLOC_GRAN - mod;
     90 	}
     91 	SetAlloced( newsize );
     92 
     93 #ifdef USE_STRING_DATA_ALLOCATOR
     94 	newbuffer = stringDataAllocator.Alloc( GetAlloced() );
     95 #else
     96 	newbuffer = new (TAG_STRING) char[ GetAlloced() ];
     97 #endif
     98 	if ( keepold && data ) {
     99 		data[ len ] = '\0';
    100 		strcpy( newbuffer, data );
    101 	}
    102 
    103 	if ( data && data != baseBuffer ) {
    104 #ifdef USE_STRING_DATA_ALLOCATOR
    105 		stringDataAllocator.Free( data );
    106 #else
    107 		delete [] data;
    108 #endif
    109 	}
    110 
    111 	data = newbuffer;
    112 }
    113 
    114 /*
    115 ============
    116 idStr::FreeData
    117 ============
    118 */
    119 void idStr::FreeData() {
    120 	if ( IsStatic() ) {
    121 		return;
    122 	}
    123 
    124 	if ( data && data != baseBuffer ) {
    125 #ifdef USE_STRING_DATA_ALLOCATOR
    126 		stringDataAllocator.Free( data );
    127 #else
    128 		delete[] data;
    129 #endif
    130 		data = baseBuffer;
    131 	}
    132 }
    133 
    134 /*
    135 ============
    136 idStr::operator=
    137 ============
    138 */
    139 void idStr::operator=( const char *text ) {
    140 	int l;
    141 	int diff;
    142 	int i;
    143 
    144 	if ( !text ) {
    145 		// safe behavior if NULL
    146 		EnsureAlloced( 1, false );
    147 		data[ 0 ] = '\0';
    148 		len = 0;
    149 		return;
    150 	}
    151 
    152 	if ( text == data ) {
    153 		return; // copying same thing
    154 	}
    155 
    156 	// check if we're aliasing
    157 	if ( text >= data && text <= data + len ) {
    158 		diff = text - data;
    159 
    160 		assert( strlen( text ) < (unsigned)len );
    161 
    162 		for ( i = 0; text[ i ]; i++ ) {
    163 			data[ i ] = text[ i ];
    164 		}
    165 
    166 		data[ i ] = '\0';
    167 
    168 		len -= diff;
    169 
    170 		return;
    171 	}
    172 
    173 	l = strlen( text );
    174 	EnsureAlloced( l + 1, false );
    175 	strcpy( data, text );
    176 	len = l;
    177 }
    178 
    179 /*
    180 ============
    181 idStr::FindChar
    182 
    183 returns -1 if not found otherwise the index of the char
    184 ============
    185 */
    186 int idStr::FindChar( const char *str, const char c, int start, int end ) {
    187 	int i;
    188 
    189 	if ( end == -1 ) {
    190 		end = strlen( str ) - 1;
    191 	}
    192 	for ( i = start; i <= end; i++ ) {
    193 		if ( str[i] == c ) {
    194 			return i;
    195 		}
    196 	}
    197 	return -1;
    198 }
    199 
    200 /*
    201 ============
    202 idStr::FindText
    203 
    204 returns -1 if not found otherwise the index of the text
    205 ============
    206 */
    207 int idStr::FindText( const char *str, const char *text, bool casesensitive, int start, int end ) {
    208 	int l, i, j;
    209 
    210 	if ( end == -1 ) {
    211 		end = strlen( str );
    212 	}
    213 	l = end - strlen( text );
    214 	for ( i = start; i <= l; i++ ) {
    215 		if ( casesensitive ) {
    216 			for ( j = 0; text[j]; j++ ) {
    217 				if ( str[i+j] != text[j] ) {
    218 					break;
    219 				}
    220 			}
    221 		} else {
    222 			for ( j = 0; text[j]; j++ ) {
    223 				if ( ::toupper( str[i+j] ) != ::toupper( text[j] ) ) {
    224 					break;
    225 				}
    226 			}
    227 		}
    228 		if ( !text[j] ) {
    229 			return i;
    230 		}
    231 	}
    232 	return -1;
    233 }
    234 
    235 /*
    236 ============
    237 idStr::Filter
    238 
    239 Returns true if the string conforms the given filter.
    240 Several metacharacter may be used in the filter.
    241 
    242 *          match any string of zero or more characters
    243 ?          match any single character
    244 [abc...]   match any of the enclosed characters; a hyphen can
    245            be used to specify a range (e.g. a-z, A-Z, 0-9)
    246 
    247 ============
    248 */
    249 bool idStr::Filter( const char *filter, const char *name, bool casesensitive ) {
    250 	idStr buf;
    251 	int i, found, index;
    252 
    253 	while(*filter) {
    254 		if (*filter == '*') {
    255 			filter++;
    256 			buf.Empty();
    257 			for (i = 0; *filter; i++) {
    258 				if ( *filter == '*' || *filter == '?' || (*filter == '[' && *(filter+1) != '[') ) {
    259 					break;
    260 				}
    261 				buf += *filter;
    262 				if ( *filter == '[' ) {
    263 					filter++;
    264 				}
    265 				filter++;
    266 			}
    267 			if ( buf.Length() ) {
    268 				index = idStr(name).Find( buf.c_str(), casesensitive );
    269 				if ( index == -1 ) {
    270 					return false;
    271 				}
    272 				name += index + strlen(buf);
    273 			}
    274 		}
    275 		else if (*filter == '?') {
    276 			filter++;
    277 			name++;
    278 		}
    279 		else if (*filter == '[') {
    280 			if ( *(filter+1) == '[' ) {
    281 				if ( *name != '[' ) {
    282 					return false;
    283 				}
    284 				filter += 2;
    285 				name++;
    286 			}
    287 			else {
    288 				filter++;
    289 				found = false;
    290 				while(*filter && !found) {
    291 					if (*filter == ']' && *(filter+1) != ']') {
    292 						break;
    293 					}
    294 					if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']')) {
    295 						if (casesensitive) {
    296 							if (*name >= *filter && *name <= *(filter+2)) {
    297 								found = true;
    298 							}
    299 						}
    300 						else {
    301 							if ( ::toupper(*name) >= ::toupper(*filter) && ::toupper(*name) <= ::toupper(*(filter+2)) ) {
    302 								found = true;
    303 							}
    304 						}
    305 						filter += 3;
    306 					}
    307 					else {
    308 						if (casesensitive) {
    309 							if (*filter == *name) {
    310 								found = true;
    311 							}
    312 						}
    313 						else {
    314 							if ( ::toupper(*filter) == ::toupper(*name) ) {
    315 								found = true;
    316 							}
    317 						}
    318 						filter++;
    319 					}
    320 				}
    321 				if (!found) {
    322 					return false;
    323 				}
    324 				while(*filter) {
    325 					if ( *filter == ']' && *(filter+1) != ']' ) {
    326 						break;
    327 					}
    328 					filter++;
    329 				}
    330 				filter++;
    331 				name++;
    332 			}
    333 		}
    334 		else {
    335 			if (casesensitive) {
    336 				if (*filter != *name) {
    337 					return false;
    338 				}
    339 			}
    340 			else {
    341 				if ( ::toupper(*filter) != ::toupper(*name) ) {
    342 					return false;
    343 				}
    344 			}
    345 			filter++;
    346 			name++;
    347 		}
    348 	}
    349 	return true;
    350 }
    351 
    352 /*
    353 =============
    354 idStr::StripMediaName
    355 
    356   makes the string lower case, replaces backslashes with forward slashes, and removes extension
    357 =============
    358 */
    359 void idStr::StripMediaName( const char *name, idStr &mediaName ) {
    360 	char c;
    361 
    362 	mediaName.Empty();
    363 
    364 	for ( c = *name; c; c = *(++name) ) {
    365 		// truncate at an extension
    366 		if ( c == '.' ) {
    367 			break;
    368 		}
    369 		// convert backslashes to forward slashes
    370 		if ( c == '\\' ) {
    371 			mediaName.Append( '/' );
    372 		} else {
    373 			mediaName.Append( idStr::ToLower( c ) );
    374 		}
    375 	}
    376 }
    377 
    378 /*
    379 =============
    380 idStr::CheckExtension
    381 =============
    382 */
    383 bool idStr::CheckExtension( const char *name, const char *ext ) {
    384 	const char *s1 = name + Length( name ) - 1;
    385 	const char *s2 = ext + Length( ext ) - 1;
    386 	int c1, c2, d;
    387 
    388 	do {
    389 		c1 = *s1--;
    390 		c2 = *s2--;
    391 
    392 		d = c1 - c2;
    393 		while( d ) {
    394 			if ( c1 <= 'Z' && c1 >= 'A' ) {
    395 				d += ('a' - 'A');
    396 				if ( !d ) {
    397 					break;
    398 				}
    399 			}
    400 			if ( c2 <= 'Z' && c2 >= 'A' ) {
    401 				d -= ('a' - 'A');
    402 				if ( !d ) {
    403 					break;
    404 				}
    405 			}
    406 			return false;
    407 		}
    408 	} while( s1 > name && s2 > ext );
    409 
    410 	return ( s1 >= name );
    411 }
    412 
    413 /*
    414 =============
    415 idStr::FloatArrayToString
    416 =============
    417 */
    418 const char *idStr::FloatArrayToString( const float *array, const int length, const int precision ) {
    419 	static int index = 0;
    420 	static char str[4][16384];	// in case called by nested functions
    421 	int i, n;
    422 	char format[16], *s;
    423 
    424 	// use an array of string so that multiple calls won't collide
    425 	s = str[ index ];
    426 	index = (index + 1) & 3;
    427 
    428 	idStr::snPrintf( format, sizeof( format ), "%%.%df", precision );
    429 	n = idStr::snPrintf( s, sizeof( str[0] ), format, array[0] );
    430 	if ( precision > 0 ) {
    431 		while( n > 0 && s[n-1] == '0' ) s[--n] = '\0';
    432 		while( n > 0 && s[n-1] == '.' ) s[--n] = '\0';
    433 	}
    434 	idStr::snPrintf( format, sizeof( format ), " %%.%df", precision );
    435 	for ( i = 1; i < length; i++ ) {
    436 		n += idStr::snPrintf( s + n, sizeof( str[0] ) - n, format, array[i] );
    437 		if ( precision > 0 ) {
    438 			while( n > 0 && s[n-1] == '0' ) s[--n] = '\0';
    439 			while( n > 0 && s[n-1] == '.' ) s[--n] = '\0';
    440 		}
    441 	}
    442 	return s;
    443 }
    444 
    445 /*
    446 ========================
    447 idStr::CStyleQuote
    448 ========================
    449 */
    450 const char *idStr::CStyleQuote( const char *str ) {
    451 	static int index = 0;
    452 	static char buffers[4][16384];	// in case called by nested functions
    453 	unsigned int i;
    454 	char *buf;
    455 
    456 	buf = buffers[index];
    457 	index = ( index + 1 ) & 3;
    458 
    459 	buf[0] = '\"';
    460 	for ( i = 1; i < sizeof( buffers[0] ) - 2; i++ ) {
    461 		int c = *str++;
    462 		switch( c ) {
    463 			case '\0': buf[i++] = '\"'; buf[i] = '\0'; return buf;
    464 			case '\\': buf[i++] = '\\'; buf[i] = '\\'; break;
    465 			case '\n': buf[i++] = '\\'; buf[i] = 'n'; break;
    466 			case '\r': buf[i++] = '\\'; buf[i] = 'r'; break;
    467 			case '\t': buf[i++] = '\\'; buf[i] = 't'; break;
    468 			case '\v': buf[i++] = '\\'; buf[i] = 'v'; break;
    469 			case '\b': buf[i++] = '\\'; buf[i] = 'b'; break;
    470 			case '\f': buf[i++] = '\\'; buf[i] = 'f'; break;
    471 			case '\a': buf[i++] = '\\'; buf[i] = 'a'; break;
    472 			case '\'': buf[i++] = '\\'; buf[i] = '\''; break;
    473 			case '\"': buf[i++] = '\\'; buf[i] = '\"'; break;
    474 			case '\?': buf[i++] = '\\'; buf[i] = '\?'; break;
    475 			default: buf[i] = c; break;
    476 		}
    477 	}
    478 	buf[i++] = '\"';
    479 	buf[i] = '\0';
    480 	return buf;
    481 }
    482 
    483 /*
    484 ========================
    485 idStr::CStyleUnQuote
    486 ========================
    487 */
    488 const char *idStr::CStyleUnQuote( const char *str ) {
    489 	if ( str[0] != '\"' ) {
    490 		return str;
    491 	}
    492 
    493 	static int index = 0;
    494 	static char buffers[4][16384];	// in case called by nested functions
    495 	unsigned int i;
    496 	char *buf;
    497 
    498 	buf = buffers[index];
    499 	index = ( index + 1 ) & 3;
    500 
    501 	str++;
    502 	for ( i = 0; i < sizeof( buffers[0] ) - 1; i++ ) {
    503 		int c = *str++;
    504 		if ( c == '\0' ) {
    505 			break;
    506 		} else if ( c == '\\' ) {
    507 			c = *str++;
    508 			switch( c ) {
    509 				case '\\': buf[i] = '\\'; break;
    510 				case 'n': buf[i] = '\n'; break;
    511 				case 'r': buf[i] = '\r'; break;
    512 				case 't': buf[i] = '\t'; break;
    513 				case 'v': buf[i] = '\v'; break;
    514 				case 'b': buf[i] = '\b'; break;
    515 				case 'f': buf[i] = '\f'; break;
    516 				case 'a': buf[i] = '\a'; break;
    517 				case '\'': buf[i] = '\''; break;
    518 				case '\"': buf[i] = '\"'; break;
    519 				case '\?': buf[i] = '\?'; break;
    520 			}
    521 		} else {
    522 			buf[i] = c;
    523 		}
    524 	}
    525 	assert( buf[i-1] == '\"' );
    526 	buf[i-1] = '\0';
    527 	return buf;
    528 }
    529 
    530 /*
    531 ============
    532 idStr::Last
    533 
    534 returns -1 if not found otherwise the index of the char
    535 ============
    536 */
    537 int idStr::Last( const char c ) const {
    538 	int i;
    539 	
    540 	for( i = Length(); i > 0; i-- ) {
    541 		if ( data[ i - 1 ] == c ) {
    542 			return i - 1;
    543 		}
    544 	}
    545 
    546 	return -1;
    547 }
    548 
    549 /*
    550 ========================
    551 idStr::Format
    552 
    553 perform a threadsafe sprintf to the string
    554 ========================
    555 */
    556 void idStr::Format( const char *fmt, ... ) {
    557 	va_list argptr;
    558 	char text[MAX_PRINT_MSG];
    559 
    560 	va_start( argptr, fmt );
    561 	int len = idStr::vsnPrintf( text, sizeof( text ) - 1, fmt, argptr );
    562 	va_end( argptr );
    563 	text[ sizeof( text ) - 1 ] = '\0';
    564 
    565 	if ( (size_t)len >= sizeof( text ) - 1 ) {
    566 		idLib::common->FatalError( "Tried to set a large buffer using %s", fmt );
    567 	}
    568 	*this = text;
    569 }
    570 
    571 /*
    572 ========================
    573 idStr::FormatInt
    574 
    575 Formats integers with commas for readability.
    576 ========================
    577 */
    578 idStr idStr::FormatInt( const int num, bool isCash ) {
    579 	idStr val = va( "%d", num );
    580 	int len = val.Length();
    581 	for ( int i = 0 ; i < ( ( len - 1 ) / 3 ); i++ ) {
    582 		int pos = val.Length() - ( ( i + 1 ) * 3 + i );
    583 		if ( pos > 1 || val[0] != '-' ) {
    584 			val.Insert( ',', pos );
    585 		}
    586 	}
    587 
    588 	if ( isCash ) {
    589 		val.Insert( '$', val[0] == '-' ? 1 : 0 );
    590 	}
    591 
    592 	return val;
    593 }
    594 
    595 /*
    596 ============
    597 idStr::StripLeading
    598 ============
    599 */
    600 void idStr::StripLeading( const char c ) {
    601 	while( data[ 0 ] == c ) {
    602 		memmove( &data[ 0 ], &data[ 1 ], len );
    603 		len--;
    604 	}
    605 }
    606 
    607 /*
    608 ============
    609 idStr::StripLeading
    610 ============
    611 */
    612 void idStr::StripLeading( const char *string ) {
    613 	int l;
    614 
    615 	l = strlen( string );
    616 	if ( l > 0 ) {
    617 		while ( !Cmpn( string, l ) ) {
    618 			memmove( data, data + l, len - l + 1 );
    619 			len -= l;
    620 		}
    621 	}
    622 }
    623 
    624 /*
    625 ============
    626 idStr::StripLeadingOnce
    627 ============
    628 */
    629 bool idStr::StripLeadingOnce( const char *string ) {
    630 	int l;
    631 
    632 	l = strlen( string );
    633 	if ( ( l > 0 ) && !Cmpn( string, l ) ) {
    634 		memmove( data, data + l, len - l + 1 );
    635 		len -= l;
    636 		return true;
    637 	}
    638 	return false;
    639 }
    640 
    641 /*
    642 ============
    643 idStr::StripTrailing
    644 ============
    645 */
    646 void idStr::StripTrailing( const char c ) {
    647 	int i;
    648 	
    649 	for( i = Length(); i > 0 && data[ i - 1 ] == c; i-- ) {
    650 		data[ i - 1 ] = '\0';
    651 		len--;
    652 	}
    653 }
    654 
    655 /*
    656 ============
    657 idStr::StripLeading
    658 ============
    659 */
    660 void idStr::StripTrailing( const char *string ) {
    661 	int l;
    662 
    663 	l = strlen( string );
    664 	if ( l > 0 ) {
    665 		while ( ( len >= l ) && !Cmpn( string, data + len - l, l ) ) {
    666 			len -= l;
    667 			data[len] = '\0';
    668 		}
    669 	}
    670 }
    671 
    672 /*
    673 ============
    674 idStr::StripTrailingOnce
    675 ============
    676 */
    677 bool idStr::StripTrailingOnce( const char *string ) {
    678 	int l;
    679 
    680 	l = strlen( string );
    681 	if ( ( l > 0 ) && ( len >= l ) && !Cmpn( string, data + len - l, l ) ) {
    682 		len -= l;
    683 		data[len] = '\0';
    684 		return true;
    685 	}
    686 	return false;
    687 }
    688 
    689 /*
    690 ============
    691 idStr::Replace
    692 ============
    693 */
    694 bool  idStr::ReplaceChar( const char old, const char nw ) {
    695 	bool replaced = false;
    696 	for ( int i = 0; i < Length(); i++ ) {
    697 		if ( data[i] == old ) {
    698 			data[i] = nw;
    699 			replaced = true;
    700 		}
    701 	}
    702 	return replaced;
    703 }
    704 
    705 /*
    706 ============
    707 idStr::Replace
    708 ============
    709 */
    710 bool idStr::Replace( const char *old, const char *nw ) {
    711 	int oldLen = strlen( old );
    712 	int newLen = strlen( nw );
    713 
    714 	// Work out how big the new string will be
    715 	int count = 0;
    716 	for ( int i = 0; i < Length(); i++ ) {
    717 		if ( idStr::Cmpn( &data[i], old, oldLen ) == 0 ) {
    718 			count++;
    719 			i += oldLen - 1;
    720 		}
    721 	}
    722 
    723 	if ( count ) {
    724 		idStr oldString( data );
    725 
    726 		EnsureAlloced( len + ( ( newLen - oldLen ) * count ) + 2, false );
    727 
    728 		// Replace the old data with the new data
    729 		int j = 0;
    730 		for ( int i = 0; i < oldString.Length(); i++ ) {
    731 			if ( idStr::Cmpn( &oldString[i], old, oldLen ) == 0 ) {
    732 				memcpy( data + j, nw, newLen );
    733 				i += oldLen - 1;
    734 				j += newLen;
    735 			} else {
    736 				data[j] = oldString[i];
    737 				j++;
    738 			}
    739 		}
    740 		data[j] = 0;
    741 		len = strlen( data );
    742 		return true;
    743 	}
    744 	return false;
    745 }
    746 
    747 /*
    748 ============
    749 idStr::Mid
    750 ============
    751 */
    752 const char *idStr::Mid( int start, int len, idStr &result ) const {
    753 	int i;
    754 
    755 	result.Empty();
    756 
    757 	i = Length();
    758 	if ( i == 0 || len <= 0 || start >= i ) {
    759 		return NULL;
    760 	}
    761 
    762 	if ( start + len >= i ) {
    763 		len = i - start;
    764 	}
    765 
    766 	result.Append( &data[ start ], len );
    767 	return result;
    768 }
    769 
    770 /*
    771 ============
    772 idStr::Mid
    773 ============
    774 */
    775 idStr idStr::Mid( int start, int len ) const {
    776 	int i;
    777 	idStr result;
    778 
    779 	i = Length();
    780 	if ( i == 0 || len <= 0 || start >= i ) {
    781 		return result;
    782 	}
    783 
    784 	if ( start + len >= i ) {
    785 		len = i - start;
    786 	}
    787 
    788 	result.Append( &data[ start ], len );
    789 	return result;
    790 }
    791 
    792 /*
    793 ============
    794 idStr::StripTrailingWhitespace
    795 ============
    796 */
    797 void idStr::StripTrailingWhitespace() {
    798 	int i;
    799 	
    800 	// cast to unsigned char to prevent stripping off high-ASCII characters
    801 	for( i = Length(); i > 0 && (unsigned char)(data[ i - 1 ]) <= ' '; i-- ) {
    802 		data[ i - 1 ] = '\0';
    803 		len--;
    804 	}
    805 }
    806 
    807 /*
    808 ============
    809 idStr::StripQuotes
    810 
    811 Removes the quotes from the beginning and end of the string
    812 ============
    813 */
    814 idStr& idStr::StripQuotes ()
    815 {
    816 	if ( data[0] != '\"' )
    817 	{
    818 		return *this;
    819 	}
    820 	
    821 	// Remove the trailing quote first
    822 	if ( data[len-1] == '\"' )
    823 	{
    824 		data[len-1] = '\0';
    825 		len--;
    826 	}
    827 
    828 	// Strip the leading quote now
    829 	len--;	
    830 	memmove( &data[ 0 ], &data[ 1 ], len );
    831 	data[len] = '\0';
    832 	
    833 	return *this;
    834 }
    835 
    836 /*
    837 =====================================================================
    838 
    839   filename methods
    840 
    841 =====================================================================
    842 */
    843 
    844 /*
    845 ============
    846 idStr::FileNameHash
    847 ============
    848 */
    849 int idStr::FileNameHash() const {
    850 	int		i;
    851 	long	hash;
    852 	char	letter;
    853 
    854 	hash = 0;
    855 	i = 0;
    856 	while( data[i] != '\0' ) {
    857 		letter = idStr::ToLower( data[i] );
    858 		if ( letter == '.' ) {
    859 			break;				// don't include extension
    860 		}
    861 		if ( letter =='\\' ) {
    862 			letter = '/';
    863 		}
    864 		hash += (long)(letter)*(i+119);
    865 		i++;
    866 	}
    867 	hash &= (FILE_HASH_SIZE-1);
    868 	return hash;
    869 }
    870 
    871 /*
    872 ============
    873 idStr::BackSlashesToSlashes
    874 ============
    875 */
    876 idStr &idStr::BackSlashesToSlashes() {
    877 	int i;
    878 
    879 	for ( i = 0; i < len; i++ ) {
    880 		if ( data[ i ] == '\\' ) {
    881 			data[ i ] = '/';
    882 		}
    883 	}
    884 	return *this;
    885 }
    886 
    887 /*
    888 ============
    889 idStr::SlashesToBackSlashes
    890 ============
    891 */
    892 idStr &idStr::SlashesToBackSlashes() {
    893 	int i;
    894 
    895 	for ( i = 0; i < len; i++ ) {
    896 		if ( data[ i ] == '/' ) {
    897 			data[ i ] = '\\';
    898 		}
    899 	}
    900 	return *this;
    901 }
    902 
    903 /*
    904 ============
    905 idStr::SetFileExtension
    906 ============
    907 */
    908 idStr &idStr::SetFileExtension( const char *extension ) {
    909 	StripFileExtension();
    910 	if ( *extension != '.' ) {
    911 		Append( '.' );
    912 	}
    913 	Append( extension );
    914 	return *this;
    915 }
    916 
    917 /*
    918 ============
    919 idStr::StripFileExtension
    920 ============
    921 */
    922 idStr &idStr::StripFileExtension() {
    923 	int i;
    924 
    925 	for ( i = len-1; i >= 0; i-- ) {
    926 		if ( data[i] == '.' ) {
    927 			data[i] = '\0';
    928 			len = i;
    929 			break;
    930 		}
    931 	}
    932 	return *this;
    933 }
    934 
    935 /*
    936 ============
    937 idStr::StripAbsoluteFileExtension
    938 ============
    939 */
    940 idStr &idStr::StripAbsoluteFileExtension() {
    941 	int i;
    942 
    943 	for ( i = 0; i < len; i++ ) {
    944 		if ( data[i] == '.' ) {
    945 			data[i] = '\0';
    946 			len = i;
    947 			break;
    948 		}
    949 	}
    950 
    951 	return *this;
    952 }
    953 
    954 /*
    955 ==================
    956 idStr::DefaultFileExtension
    957 ==================
    958 */
    959 idStr &idStr::DefaultFileExtension( const char *extension ) {
    960 	int i;
    961 
    962 	// do nothing if the string already has an extension
    963 	for ( i = len-1; i >= 0; i-- ) {
    964 		if ( data[i] == '.' ) {
    965 			return *this;
    966 		}
    967 	}
    968 	if ( *extension != '.' ) {
    969 		Append( '.' );
    970 	}
    971 	Append( extension );
    972 	return *this;
    973 }
    974 
    975 /*
    976 ==================
    977 idStr::DefaultPath
    978 ==================
    979 */
    980 idStr &idStr::DefaultPath( const char *basepath ) {
    981 	if ( ( ( *this )[ 0 ] == '/' ) || ( ( *this )[ 0 ] == '\\' ) ) {
    982 		// absolute path location
    983 		return *this;
    984 	}
    985 
    986 	*this = basepath + *this;
    987 	return *this;
    988 }
    989 
    990 /*
    991 ====================
    992 idStr::AppendPath
    993 ====================
    994 */
    995 void idStr::AppendPath( const char *text ) {
    996 	int pos;
    997 	int i = 0;
    998 
    999 	if ( text && text[i] ) {
   1000 		pos = len;
   1001 		EnsureAlloced( len + strlen( text ) + 2 );
   1002 
   1003 		if ( pos ) {
   1004 			if ( data[ pos-1 ] != '/' ) {
   1005 				data[ pos++ ] = '/';
   1006 			}
   1007 		}
   1008 		if ( text[i] == '/' ) {
   1009 			i++;
   1010 		}
   1011 
   1012 		for ( ; text[ i ]; i++ ) {
   1013 			if ( text[ i ] == '\\' ) {
   1014 				data[ pos++ ] = '/';
   1015 			} else {
   1016 				data[ pos++ ] = text[ i ];
   1017 			}
   1018 		}
   1019 		len = pos;
   1020 		data[ pos ] = '\0';
   1021 	}
   1022 }
   1023 
   1024 /*
   1025 ==================
   1026 idStr::StripFilename
   1027 ==================
   1028 */
   1029 idStr &idStr::StripFilename() {
   1030 	int pos;
   1031 
   1032 	pos = Length() - 1;
   1033 	while( ( pos > 0 ) && ( ( *this )[ pos ] != '/' ) && ( ( *this )[ pos ] != '\\' ) ) {
   1034 		pos--;
   1035 	}
   1036 
   1037 	if ( pos < 0 ) {
   1038 		pos = 0;
   1039 	}
   1040 
   1041 	CapLength( pos );
   1042 	return *this;
   1043 }
   1044 
   1045 /*
   1046 ==================
   1047 idStr::StripPath
   1048 ==================
   1049 */
   1050 idStr &idStr::StripPath() {
   1051 	int pos;
   1052 
   1053 	pos = Length();
   1054 	while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '/' ) && ( ( *this )[ pos - 1 ] != '\\' ) ) {
   1055 		pos--;
   1056 	}
   1057 
   1058 	*this = Right( Length() - pos );
   1059 	return *this;
   1060 }
   1061 
   1062 /*
   1063 ====================
   1064 idStr::ExtractFilePath
   1065 ====================
   1066 */
   1067 void idStr::ExtractFilePath( idStr &dest ) const {
   1068 	int pos;
   1069 
   1070 	//
   1071 	// back up until a \ or the start
   1072 	//
   1073 	pos = Length();
   1074 	while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '/' ) && ( ( *this )[ pos - 1 ] != '\\' ) ) {
   1075 		pos--;
   1076 	}
   1077 
   1078 	Left( pos, dest );
   1079 }
   1080 
   1081 /*
   1082 ====================
   1083 idStr::ExtractFileName
   1084 ====================
   1085 */
   1086 void idStr::ExtractFileName( idStr &dest ) const {
   1087 	int pos;
   1088 
   1089 	//
   1090 	// back up until a \ or the start
   1091 	//
   1092 	pos = Length() - 1;
   1093 	while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '/' ) && ( ( *this )[ pos - 1 ] != '\\' ) ) {
   1094 		pos--;
   1095 	}
   1096 
   1097 	Right( Length() - pos, dest );
   1098 }
   1099 
   1100 /*
   1101 ====================
   1102 idStr::ExtractFileBase
   1103 ====================
   1104 */
   1105 void idStr::ExtractFileBase( idStr &dest ) const {
   1106 	int pos;
   1107 	int start;
   1108 
   1109 	//
   1110 	// back up until a \ or the start
   1111 	//
   1112 	pos = Length() - 1;
   1113 	while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '/' ) && ( ( *this )[ pos - 1 ] != '\\' ) ) {
   1114 		pos--;
   1115 	}
   1116 
   1117 	start = pos;
   1118 	while( ( pos < Length() ) && ( ( *this )[ pos ] != '.' ) ) {
   1119 		pos++;
   1120 	}
   1121 
   1122 	Mid( start, pos - start, dest );
   1123 }
   1124 
   1125 /*
   1126 ====================
   1127 idStr::ExtractFileExtension
   1128 ====================
   1129 */
   1130 void idStr::ExtractFileExtension( idStr &dest ) const {
   1131 	int pos;
   1132 
   1133 	//
   1134 	// back up until a . or the start
   1135 	//
   1136 	pos = Length() - 1;
   1137 	while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '.' ) ) {
   1138 		pos--;
   1139 	}
   1140 
   1141 	if ( !pos ) {
   1142 		// no extension
   1143 		dest.Empty();
   1144 	} else {
   1145 		Right( Length() - pos, dest );
   1146 	}
   1147 }
   1148 
   1149 
   1150 /*
   1151 =====================================================================
   1152 
   1153   char * methods to replace library functions
   1154 
   1155 =====================================================================
   1156 */
   1157 
   1158 /*
   1159 ============
   1160 idStr::IsNumeric
   1161 
   1162 Checks a string to see if it contains only numerical values.
   1163 ============
   1164 */
   1165 bool idStr::IsNumeric( const char *s ) {
   1166 	int		i;
   1167 	bool	dot;
   1168 
   1169 	if ( *s == '-' ) {
   1170 		s++;
   1171 	}
   1172 
   1173 	dot = false;
   1174 	for ( i = 0; s[i]; i++ ) {
   1175 		if ( !isdigit( (const unsigned char)s[i] ) ) {
   1176 			if ( ( s[ i ] == '.' ) && !dot ) {
   1177 				dot = true;
   1178 				continue;
   1179 			}
   1180 			return false;
   1181 		}
   1182 	}
   1183 
   1184 	return true;
   1185 }
   1186 
   1187 /*
   1188 ============
   1189 idStr::HasLower
   1190 
   1191 Checks if a string has any lowercase chars
   1192 ============
   1193 */
   1194 bool idStr::HasLower( const char *s ) {
   1195 	if ( !s ) {
   1196 		return false;
   1197 	}
   1198 	
   1199 	while ( *s ) {
   1200 		if ( CharIsLower( *s ) ) {
   1201 			return true;
   1202 		}
   1203 		s++;
   1204 	}
   1205 	
   1206 	return false;
   1207 }
   1208 
   1209 /*
   1210 ============
   1211 idStr::HasUpper
   1212 	
   1213 Checks if a string has any uppercase chars
   1214 ============
   1215 */
   1216 bool idStr::HasUpper( const char *s ) {
   1217 	if ( !s ) {
   1218 		return false;
   1219 	}
   1220 	
   1221 	while ( *s ) {
   1222 		if ( CharIsUpper( *s ) ) {
   1223 			return true;
   1224 		}
   1225 		s++;
   1226 	}
   1227 	
   1228 	return false;
   1229 }
   1230 
   1231 /*
   1232 ================
   1233 idStr::Cmp
   1234 ================
   1235 */
   1236 int idStr::Cmp( const char *s1, const char *s2 ) {
   1237 	int c1, c2, d;
   1238 
   1239 	do {
   1240 		c1 = *s1++;
   1241 		c2 = *s2++;
   1242 
   1243 		d = c1 - c2;
   1244 		if ( d ) {
   1245 			return ( INT32_SIGNBITNOTSET( d ) << 1 ) - 1;
   1246 		}
   1247 	} while( c1 );
   1248 
   1249 	return 0;		// strings are equal
   1250 }
   1251 
   1252 /*
   1253 ================
   1254 idStr::Cmpn
   1255 ================
   1256 */
   1257 int idStr::Cmpn( const char *s1, const char *s2, int n ) {
   1258 	int c1, c2, d;
   1259 
   1260 	assert( n >= 0 );
   1261 
   1262 	do {
   1263 		c1 = *s1++;
   1264 		c2 = *s2++;
   1265 
   1266 		if ( !n-- ) {
   1267 			return 0;		// strings are equal until end point
   1268 		}
   1269 
   1270 		d = c1 - c2;
   1271 		if ( d ) {
   1272 			return ( INT32_SIGNBITNOTSET( d ) << 1 ) - 1;
   1273 		}
   1274 	} while( c1 );
   1275 
   1276 	return 0;		// strings are equal
   1277 }
   1278 
   1279 /*
   1280 ================
   1281 idStr::Icmp
   1282 ================
   1283 */
   1284 int idStr::Icmp( const char *s1, const char *s2 ) {
   1285 	int c1, c2, d;
   1286 
   1287 	do {
   1288 		c1 = *s1++;
   1289 		c2 = *s2++;
   1290 
   1291 		d = c1 - c2;
   1292 		while( d ) {
   1293 			if ( c1 <= 'Z' && c1 >= 'A' ) {
   1294 				d += ('a' - 'A');
   1295 				if ( !d ) {
   1296 					break;
   1297 				}
   1298 			}
   1299 			if ( c2 <= 'Z' && c2 >= 'A' ) {
   1300 				d -= ('a' - 'A');
   1301 				if ( !d ) {
   1302 					break;
   1303 				}
   1304 			}
   1305 			return ( INT32_SIGNBITNOTSET( d ) << 1 ) - 1;
   1306 		}
   1307 	} while( c1 );
   1308 
   1309 	return 0;		// strings are equal
   1310 }
   1311 
   1312 /*
   1313 ================
   1314 idStr::Icmpn
   1315 ================
   1316 */
   1317 int idStr::Icmpn( const char *s1, const char *s2, int n ) {
   1318 	int c1, c2, d;
   1319 
   1320 	assert( n >= 0 );
   1321 
   1322 	do {
   1323 		c1 = *s1++;
   1324 		c2 = *s2++;
   1325 
   1326 		if ( !n-- ) {
   1327 			return 0;		// strings are equal until end point
   1328 		}
   1329 
   1330 		d = c1 - c2;
   1331 		while( d ) {
   1332 			if ( c1 <= 'Z' && c1 >= 'A' ) {
   1333 				d += ('a' - 'A');
   1334 				if ( !d ) {
   1335 					break;
   1336 				}
   1337 			}
   1338 			if ( c2 <= 'Z' && c2 >= 'A' ) {
   1339 				d -= ('a' - 'A');
   1340 				if ( !d ) {
   1341 					break;
   1342 				}
   1343 			}
   1344 			return ( INT32_SIGNBITNOTSET( d ) << 1 ) - 1;
   1345 		}
   1346 	} while( c1 );
   1347 
   1348 	return 0;		// strings are equal
   1349 }
   1350 
   1351 /*
   1352 ================
   1353 idStr::Icmp
   1354 ================
   1355 */
   1356 int idStr::IcmpNoColor( const char *s1, const char *s2 ) {
   1357 	int c1, c2, d;
   1358 
   1359 	do {
   1360 		while ( idStr::IsColor( s1 ) ) {
   1361 			s1 += 2;
   1362 		}
   1363 		while ( idStr::IsColor( s2 ) ) {
   1364 			s2 += 2;
   1365 		}
   1366 		c1 = *s1++;
   1367 		c2 = *s2++;
   1368 
   1369 		d = c1 - c2;
   1370 		while( d ) {
   1371 			if ( c1 <= 'Z' && c1 >= 'A' ) {
   1372 				d += ('a' - 'A');
   1373 				if ( !d ) {
   1374 					break;
   1375 				}
   1376 			}
   1377 			if ( c2 <= 'Z' && c2 >= 'A' ) {
   1378 				d -= ('a' - 'A');
   1379 				if ( !d ) {
   1380 					break;
   1381 				}
   1382 			}
   1383 			return ( INT32_SIGNBITNOTSET( d ) << 1 ) - 1;
   1384 		}
   1385 	} while( c1 );
   1386 
   1387 	return 0;		// strings are equal
   1388 }
   1389 
   1390 /*
   1391 ================
   1392 idStr::IcmpPath
   1393 ================
   1394 */
   1395 int idStr::IcmpPath( const char *s1, const char *s2 ) {
   1396 	int c1, c2, d;
   1397 
   1398 #if 0
   1399 //#if !defined( ID_PC_WIN )
   1400 	idLib::common->Printf( "WARNING: IcmpPath used on a case-sensitive filesystem?\n" );
   1401 #endif
   1402 
   1403 	do {
   1404 		c1 = *s1++;
   1405 		c2 = *s2++;
   1406 
   1407 		d = c1 - c2;
   1408 		while( d ) {
   1409 			if ( c1 <= 'Z' && c1 >= 'A' ) {
   1410 				d += ('a' - 'A');
   1411 				if ( !d ) {
   1412 					break;
   1413 				}
   1414 			}
   1415 			if ( c1 == '\\' ) {
   1416 				d += ('/' - '\\');
   1417 				if ( !d ) {
   1418 					break;
   1419 				}
   1420 			}
   1421 			if ( c2 <= 'Z' && c2 >= 'A' ) {
   1422 				d -= ('a' - 'A');
   1423 				if ( !d ) {
   1424 					break;
   1425 				}
   1426 			}
   1427 			if ( c2 == '\\' ) {
   1428 				d -= ('/' - '\\');
   1429 				if ( !d ) {
   1430 					break;
   1431 				}
   1432 			}
   1433 			// make sure folders come first
   1434 			while( c1 ) {
   1435 				if ( c1 == '/' || c1 == '\\' ) {
   1436 					break;
   1437 				}
   1438 				c1 = *s1++;
   1439 			}
   1440 			while( c2 ) {
   1441 				if ( c2 == '/' || c2 == '\\' ) {
   1442 					break;
   1443 				}
   1444 				c2 = *s2++;
   1445 			}
   1446 			if ( c1 && !c2 ) {
   1447 				return -1;
   1448 			} else if ( !c1 && c2 ) {
   1449 				return 1;
   1450 			}
   1451 			// same folder depth so use the regular compare
   1452 			return ( INT32_SIGNBITNOTSET( d ) << 1 ) - 1;
   1453 		}
   1454 	} while( c1 );
   1455 
   1456 	return 0;
   1457 }
   1458 
   1459 /*
   1460 ================
   1461 idStr::IcmpnPath
   1462 ================
   1463 */
   1464 int idStr::IcmpnPath( const char *s1, const char *s2, int n ) {
   1465 	int c1, c2, d;
   1466 
   1467 #if 0
   1468 //#if !defined( ID_PC_WIN )
   1469 	idLib::common->Printf( "WARNING: IcmpPath used on a case-sensitive filesystem?\n" );
   1470 #endif
   1471 
   1472 	assert( n >= 0 );
   1473 
   1474 	do {
   1475 		c1 = *s1++;
   1476 		c2 = *s2++;
   1477 
   1478 		if ( !n-- ) {
   1479 			return 0;		// strings are equal until end point
   1480 		}
   1481 
   1482 		d = c1 - c2;
   1483 		while( d ) {
   1484 			if ( c1 <= 'Z' && c1 >= 'A' ) {
   1485 				d += ('a' - 'A');
   1486 				if ( !d ) {
   1487 					break;
   1488 				}
   1489 			}
   1490 			if ( c1 == '\\' ) {
   1491 				d += ('/' - '\\');
   1492 				if ( !d ) {
   1493 					break;
   1494 				}
   1495 			}
   1496 			if ( c2 <= 'Z' && c2 >= 'A' ) {
   1497 				d -= ('a' - 'A');
   1498 				if ( !d ) {
   1499 					break;
   1500 				}
   1501 			}
   1502 			if ( c2 == '\\' ) {
   1503 				d -= ('/' - '\\');
   1504 				if ( !d ) {
   1505 					break;
   1506 				}
   1507 			}
   1508 			// make sure folders come first
   1509 			while( c1 ) {
   1510 				if ( c1 == '/' || c1 == '\\' ) {
   1511 					break;
   1512 				}
   1513 				c1 = *s1++;
   1514 			}
   1515 			while( c2 ) {
   1516 				if ( c2 == '/' || c2 == '\\' ) {
   1517 					break;
   1518 				}
   1519 				c2 = *s2++;
   1520 			}
   1521 			if ( c1 && !c2 ) {
   1522 				return -1;
   1523 			} else if ( !c1 && c2 ) {
   1524 				return 1;
   1525 			}
   1526 			// same folder depth so use the regular compare
   1527 			return ( INT32_SIGNBITNOTSET( d ) << 1 ) - 1;
   1528 		}
   1529 	} while( c1 );
   1530 
   1531 	return 0;
   1532 }
   1533 
   1534 /*
   1535 =============
   1536 idStr::Copynz
   1537  
   1538 Safe strncpy that ensures a trailing zero
   1539 =============
   1540 */
   1541 void idStr::Copynz( char *dest, const char *src, int destsize ) {
   1542 	if ( !src ) {
   1543 		idLib::common->Warning( "idStr::Copynz: NULL src" );
   1544 		return;
   1545 	}
   1546 	if ( destsize < 1 ) {
   1547 		idLib::common->Warning( "idStr::Copynz: destsize < 1" ); 
   1548 		return;
   1549 	}
   1550 
   1551 	strncpy( dest, src, destsize-1 );
   1552     dest[destsize-1] = 0;
   1553 }
   1554 
   1555 /*
   1556 ================
   1557 idStr::Append
   1558 
   1559   never goes past bounds or leaves without a terminating 0
   1560 ================
   1561 */
   1562 void idStr::Append( char *dest, int size, const char *src ) {
   1563 	int		l1;
   1564 
   1565 	l1 = strlen( dest );
   1566 	if ( l1 >= size ) {
   1567 		idLib::common->Error( "idStr::Append: already overflowed" );
   1568 	}
   1569 	idStr::Copynz( dest + l1, src, size - l1 );
   1570 }
   1571 
   1572 /*
   1573 ========================
   1574 idStr::IsValidUTF8
   1575 ========================
   1576 */
   1577 bool idStr::IsValidUTF8( const uint8 * s, const int maxLen, utf8Encoding_t & encoding ) {
   1578 	struct local_t {
   1579 		static int GetNumEncodedUTF8Bytes( const uint8 c ) {
   1580 			if ( c < 0x80 ) {
   1581 				return 1;
   1582 			} else if ( ( c >> 5 ) == 0x06 ) {
   1583 				// 2 byte encoding - the next byte must begin with
   1584 				return 2;
   1585 			} else if ( ( c >> 4 ) == 0x0E ) {
   1586 				// 3 byte encoding
   1587 				return 3;
   1588 			} else if ( ( c >> 5 ) == 0x1E ) {
   1589 				// 4 byte encoding
   1590 				return 4;
   1591 			} 
   1592 			// this isnt' a valid UTF-8 precursor character
   1593 			return 0;
   1594 		}
   1595 		static bool RemainingCharsAreUTF8FollowingBytes( const uint8 * s, const int curChar, const int maxLen, const int num ) {
   1596 			if ( maxLen - curChar < num ) {
   1597 				return false;
   1598 			}
   1599 			for ( int i = curChar + 1; i <= curChar + num; i++ ) {
   1600 				if ( s[ i ] == '\0' ) {
   1601 					return false;
   1602 				}
   1603 				if ( ( s[ i ] >> 6 ) != 0x02 ) {
   1604 					return false;
   1605 				}
   1606 			}
   1607 			return true;
   1608 		}
   1609 	};
   1610 
   1611 	// check for byte-order-marker
   1612 	encoding = UTF8_PURE_ASCII;
   1613 	utf8Encoding_t utf8Type = UTF8_ENCODED_NO_BOM;
   1614 	if ( maxLen > 3 && s[ 0 ] == 0xEF && s[ 1 ] == 0xBB && s[ 2 ] == 0xBF ) {
   1615 		utf8Type = UTF8_ENCODED_BOM;
   1616 	}
   1617 
   1618 	for ( int i = 0; s[ i ] != '\0' && i < maxLen; i++ ) {
   1619 		int numBytes = local_t::GetNumEncodedUTF8Bytes( s[ i ] );
   1620 		if ( numBytes == 1 ) {
   1621 			continue;	// just low ASCII
   1622 		} else if ( numBytes == 2 ) {
   1623 			// 2 byte encoding - the next byte must begin with bit pattern 10
   1624 			if ( !local_t::RemainingCharsAreUTF8FollowingBytes( s, i, maxLen, 1 ) ) {
   1625 				return false;
   1626 			}
   1627 			// skip over UTF-8 character
   1628 			i += 1;
   1629 			encoding = utf8Type;
   1630 		} else if ( numBytes == 3 ) {
   1631 			// 3 byte encoding - the next 2 bytes must begin with bit pattern 10
   1632 			if ( !local_t::RemainingCharsAreUTF8FollowingBytes( s, i, maxLen, 2 ) ) {
   1633 				return false;
   1634 			}
   1635 			// skip over UTF-8 character
   1636 			i += 2;
   1637 			encoding = utf8Type;
   1638 		} else if ( numBytes == 4 ) {
   1639 			// 4 byte encoding - the next 3 bytes must begin with bit pattern 10
   1640 			if ( !local_t::RemainingCharsAreUTF8FollowingBytes( s, i, maxLen, 3 ) ) {
   1641 				return false;
   1642 			}
   1643 			// skip over UTF-8 character
   1644 			i += 3;
   1645 			encoding = utf8Type;
   1646 		} else {
   1647 			// this isnt' a valid UTF-8 character
   1648 			if ( utf8Type == UTF8_ENCODED_BOM ) {
   1649 				encoding = UTF8_INVALID_BOM;
   1650 			} else {
   1651 				encoding = UTF8_INVALID;
   1652 			}
   1653 			return false;
   1654 		}
   1655 	}
   1656 	return true;
   1657 }
   1658 
   1659 /*
   1660 ========================
   1661 idStr::UTF8Length
   1662 ========================
   1663 */
   1664 int idStr::UTF8Length( const byte * s ) {
   1665 	int mbLen = 0;
   1666 	int charLen = 0;
   1667 	while ( s[ mbLen ] != '\0' ) {
   1668 		uint32 cindex;
   1669 		cindex = s[ mbLen ];
   1670 		if ( cindex < 0x80 ) {
   1671 			mbLen++;
   1672 		} else {
   1673 			int trailing = 0;
   1674 			if ( cindex >= 0xc0 ) {
   1675 				static const byte trailingBytes[ 64 ] = { 
   1676 					1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
   1677 					2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
   1678 				};
   1679 				trailing = trailingBytes[ cindex - 0xc0 ];
   1680 			}
   1681 			mbLen += trailing + 1; 
   1682 		}
   1683 		charLen++;
   1684 	}
   1685 	return charLen;
   1686 }
   1687 
   1688 
   1689 /*
   1690 ========================
   1691 idStr::AppendUTF8Char
   1692 ========================
   1693 */
   1694 void idStr::AppendUTF8Char( uint32 c ) {
   1695 	if ( c < 0x80 ) {
   1696 		Append( ( char)c );
   1697 	} else if ( c < 0x800 ) { // 11 bits
   1698 		Append( (char)( 0xC0 | ( c >> 6 ) ) );
   1699 		Append( (char)( 0x80 | ( c & 0x3F ) ) );
   1700 	} else if ( c < 0x10000 ) { // 16 bits
   1701 		Append( (char)( 0xE0 | ( c >> 12 ) ) );
   1702 		Append( (char)( 0x80 | ( ( c >> 6 ) & 0x3F ) ) );
   1703 		Append( (char)( 0x80 | ( c & 0x3F ) ) );
   1704 	} else if ( c < 0x200000 ) {	// 21 bits
   1705 		Append( (char)( 0xF0 | ( c >> 18 ) ) );
   1706 		Append( (char)( 0x80 | ( ( c >> 12 ) & 0x3F ) ) );
   1707 		Append( (char)( 0x80 | ( ( c >> 6 ) & 0x3F ) ) );
   1708 		Append( (char)( 0x80 | ( c & 0x3F ) ) );
   1709 	} else {
   1710 		// UTF-8 can encode up to 6 bytes. Why don't we support that?
   1711 		// This is an invalid Unicode character
   1712 		Append( '?' );
   1713 	}
   1714 }
   1715 
   1716 /*
   1717 ========================
   1718 idStr::UTF8Char
   1719 ========================
   1720 */
   1721 uint32 idStr::UTF8Char( const byte * s, int & idx ) {
   1722 	if ( idx >= 0 ) {
   1723 		while ( s[ idx ] != '\0' ) {
   1724 			uint32 cindex = s[ idx ];
   1725 			if ( cindex < 0x80 ) {
   1726 				idx++;
   1727 				return cindex;
   1728 			}
   1729 			int trailing = 0;
   1730 			if ( cindex >= 0xc0 ) {
   1731 				static const byte trailingBytes[ 64 ] = { 
   1732 					1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
   1733 					2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
   1734 				};
   1735 				trailing = trailingBytes[ cindex - 0xc0 ];
   1736 			}
   1737 			static const uint32 trailingMask[ 6 ] = { 0x0000007f, 0x0000001f, 0x0000000f, 0x00000007, 0x00000003, 0x00000001 };
   1738 			cindex &= trailingMask[ trailing  ];
   1739 			while ( trailing-- > 0 ) {
   1740 				cindex <<= 6;
   1741 				cindex += s[ ++idx ] & 0x0000003f;
   1742 			}
   1743 			idx++;
   1744 			return cindex;
   1745 		}
   1746 	}
   1747 	idx++;
   1748 	return 0;	// return a null terminator if out of range
   1749 }
   1750 
   1751 /*
   1752 ================
   1753 idStr::LengthWithoutColors
   1754 ================
   1755 */
   1756 int idStr::LengthWithoutColors( const char *s ) {
   1757 	int len;
   1758 	const char *p;
   1759 
   1760 	if ( !s ) {
   1761 		return 0;
   1762 	}
   1763 
   1764 	len = 0;
   1765 	p = s;
   1766 	while( *p ) {
   1767 		if ( idStr::IsColor( p ) ) {
   1768 			p += 2;
   1769 			continue;
   1770 		}
   1771 		p++;
   1772 		len++;
   1773 	}
   1774 
   1775 	return len;
   1776 }
   1777 
   1778 /*
   1779 ================
   1780 idStr::RemoveColors
   1781 ================
   1782 */
   1783 char *idStr::RemoveColors( char *string ) {
   1784 	char *d;
   1785 	char *s;
   1786 	int c;
   1787 
   1788 	s = string;
   1789 	d = string;
   1790 	while( (c = *s) != 0 ) {
   1791 		if ( idStr::IsColor( s ) ) {
   1792 			s++;
   1793 		}		
   1794 		else {
   1795 			*d++ = c;
   1796 		}
   1797 		s++;
   1798 	}
   1799 	*d = '\0';
   1800 
   1801 	return string;
   1802 }
   1803 
   1804 /*
   1805 ================
   1806 idStr::snPrintf
   1807 ================
   1808 */
   1809 int idStr::snPrintf( char *dest, int size, const char *fmt, ...) {
   1810 	int len;
   1811 	va_list argptr;
   1812 	char buffer[32000];	// big, but small enough to fit in PPC stack
   1813 
   1814 	va_start( argptr, fmt );
   1815 	len = vsprintf( buffer, fmt, argptr );
   1816 	va_end( argptr );
   1817 	if ( len >= sizeof( buffer ) ) {
   1818 		idLib::common->Error( "idStr::snPrintf: overflowed buffer" );
   1819 	}
   1820 	if ( len >= size ) {
   1821 		idLib::common->Warning( "idStr::snPrintf: overflow of %i in %i\n", len, size );
   1822 		len = size;
   1823 	}
   1824 	idStr::Copynz( dest, buffer, size );
   1825 	return len;
   1826 }
   1827 
   1828 /*
   1829 ============
   1830 idStr::vsnPrintf
   1831 
   1832 vsnprintf portability:
   1833 
   1834 C99 standard: vsnprintf returns the number of characters (excluding the trailing
   1835 '\0') which would have been written to the final string if enough space had been available
   1836 snprintf and vsnprintf do not write more than size bytes (including the trailing '\0')
   1837 
   1838 win32: _vsnprintf returns the number of characters written, not including the terminating null character,
   1839 or a negative value if an output error occurs. If the number of characters to write exceeds count, then count 
   1840 characters are written and -1 is returned and no trailing '\0' is added.
   1841 
   1842 idStr::vsnPrintf: always appends a trailing '\0', returns number of characters written (not including terminal \0)
   1843 or returns -1 on failure or if the buffer would be overflowed.
   1844 ============
   1845 */
   1846 int idStr::vsnPrintf( char *dest, int size, const char *fmt, va_list argptr ) {
   1847 	int ret;
   1848 
   1849 #undef _vsnprintf
   1850 	ret = _vsnprintf( dest, size-1, fmt, argptr );
   1851 #define _vsnprintf	use_idStr_vsnPrintf
   1852 	dest[size-1] = '\0';
   1853 	if ( ret < 0 || ret >= size ) {
   1854 		return -1;
   1855 	}
   1856 	return ret;
   1857 }
   1858 
   1859 /*
   1860 ============
   1861 sprintf
   1862 
   1863 Sets the value of the string using a printf interface.
   1864 ============
   1865 */
   1866 int sprintf( idStr &string, const char *fmt, ... ) {
   1867 	int l;
   1868 	va_list argptr;
   1869 	char buffer[32000];
   1870 	
   1871 	va_start( argptr, fmt );
   1872 	l = idStr::vsnPrintf( buffer, sizeof(buffer)-1, fmt, argptr );
   1873 	va_end( argptr );
   1874 	buffer[sizeof(buffer)-1] = '\0';
   1875 
   1876 	string = buffer;
   1877 	return l;
   1878 }
   1879 
   1880 /*
   1881 ============
   1882 vsprintf
   1883 
   1884 Sets the value of the string using a vprintf interface.
   1885 ============
   1886 */
   1887 int vsprintf( idStr &string, const char *fmt, va_list argptr ) {
   1888 	int l;
   1889 	char buffer[32000];
   1890 	
   1891 	l = idStr::vsnPrintf( buffer, sizeof(buffer)-1, fmt, argptr );
   1892 	buffer[sizeof(buffer)-1] = '\0';
   1893 	
   1894 	string = buffer;
   1895 	return l;
   1896 }
   1897 
   1898 /*
   1899 ============
   1900 va
   1901 
   1902 does a varargs printf into a temp buffer
   1903 NOTE: not thread safe
   1904 ============
   1905 */
   1906 char *va( const char *fmt, ... ) {
   1907 	va_list argptr;
   1908 	static int index = 0;
   1909 	static char string[4][16384];	// in case called by nested functions
   1910 	char *buf;
   1911 
   1912 	buf = string[index];
   1913 	index = (index + 1) & 3;
   1914 
   1915 	va_start( argptr, fmt );
   1916 	vsprintf( buf, fmt, argptr );
   1917 	va_end( argptr );
   1918 
   1919 	return buf;
   1920 }
   1921 
   1922 
   1923 
   1924 /*
   1925 ============
   1926 idStr::BestUnit
   1927 ============
   1928 */
   1929 int idStr::BestUnit( const char *format, float value, Measure_t measure ) {
   1930 	int unit = 1;
   1931 	while ( unit <= 3 && ( 1 << ( unit * 10 ) < value ) ) {
   1932 		unit++;
   1933 	}
   1934 	unit--;
   1935 	value /= 1 << ( unit * 10 );
   1936 	sprintf( *this, format, value );
   1937 	*this += " ";
   1938 	*this += units[ measure ][ unit ];
   1939 	return unit;
   1940 }
   1941 
   1942 /*
   1943 ============
   1944 idStr::SetUnit
   1945 ============
   1946 */
   1947 void idStr::SetUnit( const char *format, float value, int unit, Measure_t measure ) {
   1948 	value /= 1 << ( unit * 10 );
   1949 	sprintf( *this, format, value );
   1950 	*this += " ";
   1951 	*this += units[ measure ][ unit ];	
   1952 }
   1953 
   1954 /*
   1955 ================
   1956 idStr::InitMemory
   1957 ================
   1958 */
   1959 void idStr::InitMemory() {
   1960 #ifdef USE_STRING_DATA_ALLOCATOR
   1961 	stringDataAllocator.Init();
   1962 #endif
   1963 }
   1964 
   1965 /*
   1966 ================
   1967 idStr::ShutdownMemory
   1968 ================
   1969 */
   1970 void idStr::ShutdownMemory() {
   1971 #ifdef USE_STRING_DATA_ALLOCATOR
   1972 	stringDataAllocator.Shutdown();
   1973 #endif
   1974 }
   1975 
   1976 /*
   1977 ================
   1978 idStr::PurgeMemory
   1979 ================
   1980 */
   1981 void idStr::PurgeMemory() {
   1982 #ifdef USE_STRING_DATA_ALLOCATOR
   1983 	stringDataAllocator.FreeEmptyBaseBlocks();
   1984 #endif
   1985 }
   1986 
   1987 /*
   1988 ================
   1989 idStr::ShowMemoryUsage_f
   1990 ================
   1991 */
   1992 void idStr::ShowMemoryUsage_f( const idCmdArgs &args ) {
   1993 #ifdef USE_STRING_DATA_ALLOCATOR
   1994 	idLib::common->Printf( "%6d KB string memory (%d KB free in %d blocks, %d empty base blocks)\n",
   1995 		stringDataAllocator.GetBaseBlockMemory() >> 10, stringDataAllocator.GetFreeBlockMemory() >> 10,
   1996 			stringDataAllocator.GetNumFreeBlocks(), stringDataAllocator.GetNumEmptyBaseBlocks() );
   1997 #endif
   1998 }
   1999 
   2000 /*
   2001 ================
   2002 idStr::FormatNumber
   2003 ================
   2004 */
   2005 struct formatList_t {
   2006 	int			gran;
   2007 	int			count;
   2008 };
   2009 
   2010 // elements of list need to decend in size
   2011 formatList_t formatList[] = {
   2012 	{ 1000000000, 0 },
   2013 	{ 1000000, 0 },
   2014 	{ 1000, 0 }
   2015 };
   2016 
   2017 int numFormatList = sizeof(formatList) / sizeof( formatList[0] );
   2018 
   2019 
   2020 idStr idStr::FormatNumber( int number ) {
   2021 	idStr string;
   2022 	bool hit;
   2023 
   2024 	// reset
   2025 	for ( int i = 0; i < numFormatList; i++ ) {
   2026 		formatList_t *li = formatList + i;
   2027 		li->count = 0;
   2028 	}
   2029 
   2030 	// main loop
   2031 	do {
   2032 		hit = false;
   2033 
   2034 		for ( int i = 0; i < numFormatList; i++ ) {
   2035 			formatList_t *li = formatList + i;
   2036 
   2037 			if ( number >= li->gran ) {
   2038 				li->count++;
   2039 				number -= li->gran;
   2040 				hit = true;
   2041 				break;
   2042 			}
   2043 		}
   2044 	} while ( hit );
   2045 
   2046 	// print out
   2047 	bool found = false;
   2048 
   2049 	for ( int i = 0; i < numFormatList; i++ ) {
   2050 		formatList_t *li = formatList + i;
   2051 
   2052 		if ( li->count ) {
   2053 			if ( !found ) {
   2054 				string += va( "%i,", li->count );
   2055 			} else {
   2056 				string += va( "%3.3i,", li->count );
   2057 			}
   2058 			found = true;
   2059 		}
   2060 		else if ( found ) {
   2061 			string += va( "%3.3i,", li->count );
   2062 		}
   2063 	}
   2064 
   2065 	if ( found ) {
   2066 		string += va( "%3.3i", number );
   2067 	}
   2068 	else {
   2069 		string += va( "%i", number );
   2070 	}
   2071 
   2072 	// pad to proper size
   2073 	int count = 11 - string.Length();
   2074 
   2075 	for ( int i = 0; i < count; i++ ) {
   2076 		string.Insert( " ", 0 );
   2077 	}
   2078 
   2079 	return string;
   2080 }
   2081 
   2082 CONSOLE_COMMAND( testStrId, "prints a localized string", 0 ) {
   2083 	if ( args.Argc() != 2 ) {
   2084 		idLib::Printf( "need a str id like 'STR_SWF_ACCEPT' without the hash, it gets parsed as a separate argument\n" );
   2085 		return;
   2086 	}
   2087 
   2088 	idStrId str( va( "#%s", args.Argv( 1 ) ) );
   2089 	idLib::Printf( "%s = %s\n", args.Argv( 1 ), str.GetLocalizedString() );
   2090 }