1 /*
2 ===========================================================================
3 
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
6 
7 This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
8 
9 Doom 3 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 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 Source Code.  If not, see <http://www.gnu.org/licenses/>.
21 
22 In addition, the Doom 3 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 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 "sys/platform.h"
30 #include "idlib/math/Vector.h"
31 #include "idlib/Heap.h"
32 #include "framework/Common.h"
33 
34 #include "idlib/Str.h"
35 
36 #if !defined( ID_REDIRECT_NEWDELETE ) && !defined( MACOS_X )
37 	#define USE_STRING_DATA_ALLOCATOR
38 #endif
39 
40 #ifdef USE_STRING_DATA_ALLOCATOR
41 static idDynamicBlockAlloc<char, 1<<18, 128>	stringDataAllocator;
42 #endif
43 
44 idVec4	g_color_table[16] =
45 {
46 	idVec4(0.0f, 0.0f, 0.0f, 1.0f),
47 	idVec4(1.0f, 0.0f, 0.0f, 1.0f), // S_COLOR_RED
48 	idVec4(0.0f, 1.0f, 0.0f, 1.0f), // S_COLOR_GREEN
49 	idVec4(1.0f, 1.0f, 0.0f, 1.0f), // S_COLOR_YELLOW
50 	idVec4(0.0f, 0.0f, 1.0f, 1.0f), // S_COLOR_BLUE
51 	idVec4(0.0f, 1.0f, 1.0f, 1.0f), // S_COLOR_CYAN
52 	idVec4(1.0f, 0.0f, 1.0f, 1.0f), // S_COLOR_MAGENTA
53 	idVec4(1.0f, 1.0f, 1.0f, 1.0f), // S_COLOR_WHITE
54 	idVec4(0.5f, 0.5f, 0.5f, 1.0f), // S_COLOR_GRAY
55 	idVec4(0.0f, 0.0f, 0.0f, 1.0f), // S_COLOR_BLACK
56 	idVec4(0.0f, 0.0f, 0.0f, 1.0f),
57 	idVec4(0.0f, 0.0f, 0.0f, 1.0f),
58 	idVec4(0.0f, 0.0f, 0.0f, 1.0f),
59 	idVec4(0.0f, 0.0f, 0.0f, 1.0f),
60 	idVec4(0.0f, 0.0f, 0.0f, 1.0f),
61 	idVec4(0.0f, 0.0f, 0.0f, 1.0f),
62 };
63 
64 const char *units[2][4] =
65 {
66 	{ "B", "KB", "MB", "GB" },
67 	{ "B/s", "KB/s", "MB/s", "GB/s" }
68 };
69 
70 /*
71 ============
72 idStr::ColorForIndex
73 ============
74 */
ColorForIndex(int i)75 idVec4 & idStr::ColorForIndex( int i ) {
76 	return g_color_table[ i & 15 ];
77 }
78 
79 /*
80 ============
81 idStr::ReAllocate
82 ============
83 */
ReAllocate(int amount,bool keepold)84 void idStr::ReAllocate( int amount, bool keepold ) {
85 	char	*newbuffer;
86 	int		newsize;
87 	int		mod;
88 
89 	//assert( data );
90 	assert( amount > 0 );
91 
92 	mod = amount % STR_ALLOC_GRAN;
93 	if ( !mod ) {
94 		newsize = amount;
95 	}
96 	else {
97 		newsize = amount + STR_ALLOC_GRAN - mod;
98 	}
99 	alloced = newsize;
100 
101 #ifdef USE_STRING_DATA_ALLOCATOR
102 	newbuffer = stringDataAllocator.Alloc( alloced );
103 #else
104 	newbuffer = new char[ alloced ];
105 #endif
106 	if ( keepold && data ) {
107 		data[ len ] = '\0';
108 		strcpy( newbuffer, data );
109 	}
110 
111 	if ( data && data != baseBuffer ) {
112 #ifdef USE_STRING_DATA_ALLOCATOR
113 		stringDataAllocator.Free( data );
114 #else
115 		delete [] data;
116 #endif
117 	}
118 
119 	data = newbuffer;
120 }
121 
122 /*
123 ============
124 idStr::FreeData
125 ============
126 */
FreeData(void)127 void idStr::FreeData( void ) {
128 	if ( data && data != baseBuffer ) {
129 #ifdef USE_STRING_DATA_ALLOCATOR
130 		stringDataAllocator.Free( data );
131 #else
132 		delete[] data;
133 #endif
134 		data = baseBuffer;
135 	}
136 }
137 
138 /*
139 ============
140 idStr::operator=
141 ============
142 */
operator =(const char * text)143 void idStr::operator=( const char *text ) {
144 	int l;
145 	int diff;
146 	int i;
147 
148 	if ( !text ) {
149 		// safe behaviour if NULL
150 		EnsureAlloced( 1, false );
151 		data[ 0 ] = '\0';
152 		len = 0;
153 		return;
154 	}
155 
156 	if ( text == data ) {
157 		return; // copying same thing
158 	}
159 
160 	// check if we're aliasing
161 	if ( text >= data && text <= data + len ) {
162 		diff = text - data;
163 
164 		assert( strlen( text ) < (unsigned)len );
165 
166 		for ( i = 0; text[ i ]; i++ ) {
167 			data[ i ] = text[ i ];
168 		}
169 
170 		data[ i ] = '\0';
171 
172 		len -= diff;
173 
174 		return;
175 	}
176 
177 	l = strlen( text );
178 	EnsureAlloced( l + 1, false );
179 	strcpy( data, text );
180 	len = l;
181 }
182 
183 /*
184 ============
185 idStr::FindChar
186 
187 returns -1 if not found otherwise the index of the char
188 ============
189 */
FindChar(const char * str,const char c,int start,int end)190 int idStr::FindChar( const char *str, const char c, int start, int end ) {
191 	int i;
192 
193 	if ( end == -1 ) {
194 		end = strlen( str ) - 1;
195 	}
196 	for ( i = start; i <= end; i++ ) {
197 		if ( str[i] == c ) {
198 			return i;
199 		}
200 	}
201 	return -1;
202 }
203 
204 /*
205 ============
206 idStr::FindText
207 
208 returns -1 if not found otherwise the index of the text
209 ============
210 */
FindText(const char * str,const char * text,bool casesensitive,int start,int end)211 int idStr::FindText( const char *str, const char *text, bool casesensitive, int start, int end ) {
212 	int l, i, j;
213 
214 	if ( end == -1 ) {
215 		end = strlen( str );
216 	}
217 	l = end - strlen( text );
218 	for ( i = start; i <= l; i++ ) {
219 		if ( casesensitive ) {
220 			for ( j = 0; text[j]; j++ ) {
221 				if ( str[i+j] != text[j] ) {
222 					break;
223 				}
224 			}
225 		} else {
226 			for ( j = 0; text[j]; j++ ) {
227 				if ( ::toupper( str[i+j] ) != ::toupper( text[j] ) ) {
228 					break;
229 				}
230 			}
231 		}
232 		if ( !text[j] ) {
233 			return i;
234 		}
235 	}
236 	return -1;
237 }
238 
239 /*
240 ============
241 idStr::Filter
242 
243 Returns true if the string conforms the given filter.
244 Several metacharacter may be used in the filter.
245 
246 *          match any string of zero or more characters
247 ?          match any single character
248 [abc...]   match any of the enclosed characters; a hyphen can
249 		   be used to specify a range (e.g. a-z, A-Z, 0-9)
250 
251 ============
252 */
Filter(const char * filter,const char * name,bool casesensitive)253 bool idStr::Filter( const char *filter, const char *name, bool casesensitive ) {
254 	idStr buf;
255 	int i, found, index;
256 
257 	while(*filter) {
258 		if (*filter == '*') {
259 			filter++;
260 			buf.Empty();
261 			for (i = 0; *filter; i++) {
262 				if ( *filter == '*' || *filter == '?' || (*filter == '[' && *(filter+1) != '[') ) {
263 					break;
264 				}
265 				buf += *filter;
266 				if ( *filter == '[' ) {
267 					filter++;
268 				}
269 				filter++;
270 			}
271 			if ( buf.Length() ) {
272 				index = idStr(name).Find( buf.c_str(), casesensitive );
273 				if ( index == -1 ) {
274 					return false;
275 				}
276 				name += index + strlen(buf);
277 			}
278 		}
279 		else if (*filter == '?') {
280 			filter++;
281 			name++;
282 		}
283 		else if (*filter == '[') {
284 			if ( *(filter+1) == '[' ) {
285 				if ( *name != '[' ) {
286 					return false;
287 				}
288 				filter += 2;
289 				name++;
290 			}
291 			else {
292 				filter++;
293 				found = false;
294 				while(*filter && !found) {
295 					if (*filter == ']' && *(filter+1) != ']') {
296 						break;
297 					}
298 					if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']')) {
299 						if (casesensitive) {
300 							if (*name >= *filter && *name <= *(filter+2)) {
301 								found = true;
302 							}
303 						}
304 						else {
305 							if ( ::toupper(*name) >= ::toupper(*filter) && ::toupper(*name) <= ::toupper(*(filter+2)) ) {
306 								found = true;
307 							}
308 						}
309 						filter += 3;
310 					}
311 					else {
312 						if (casesensitive) {
313 							if (*filter == *name) {
314 								found = true;
315 							}
316 						}
317 						else {
318 							if ( ::toupper(*filter) == ::toupper(*name) ) {
319 								found = true;
320 							}
321 						}
322 						filter++;
323 					}
324 				}
325 				if (!found) {
326 					return false;
327 				}
328 				while(*filter) {
329 					if ( *filter == ']' && *(filter+1) != ']' ) {
330 						break;
331 					}
332 					filter++;
333 				}
334 				filter++;
335 				name++;
336 			}
337 		}
338 		else {
339 			if (casesensitive) {
340 				if (*filter != *name) {
341 					return false;
342 				}
343 			}
344 			else {
345 				if ( ::toupper(*filter) != ::toupper(*name) ) {
346 					return false;
347 				}
348 			}
349 			filter++;
350 			name++;
351 		}
352 	}
353 	return true;
354 }
355 
356 /*
357 =============
358 idStr::StripMediaName
359 
360   makes the string lower case, replaces backslashes with forward slashes, and removes extension
361 =============
362 */
StripMediaName(const char * name,idStr & mediaName)363 void idStr::StripMediaName( const char *name, idStr &mediaName ) {
364 	char c;
365 
366 	mediaName.Empty();
367 
368 	for ( c = *name; c; c = *(++name) ) {
369 		// truncate at an extension
370 		if ( c == '.' ) {
371 			break;
372 		}
373 		// convert backslashes to forward slashes
374 		if ( c == '\\' ) {
375 			mediaName.Append( '/' );
376 		} else {
377 			mediaName.Append( idStr::ToLower( c ) );
378 		}
379 	}
380 }
381 
382 /*
383 =============
384 idStr::CheckExtension
385 =============
386 */
CheckExtension(const char * name,const char * ext)387 bool idStr::CheckExtension( const char *name, const char *ext ) {
388 	const char *s1 = name + Length( name ) - 1;
389 	const char *s2 = ext + Length( ext ) - 1;
390 	int c1, c2, d;
391 
392 	do {
393 		c1 = *s1--;
394 		c2 = *s2--;
395 
396 		d = c1 - c2;
397 		while( d ) {
398 			if ( c1 <= 'Z' && c1 >= 'A' ) {
399 				d += ('a' - 'A');
400 				if ( !d ) {
401 					break;
402 				}
403 			}
404 			if ( c2 <= 'Z' && c2 >= 'A' ) {
405 				d -= ('a' - 'A');
406 				if ( !d ) {
407 					break;
408 				}
409 			}
410 			return false;
411 		}
412 	} while( s1 > name && s2 > ext );
413 
414 	return ( s1 >= name );
415 }
416 
417 /*
418 =============
419 idStr::FloatArrayToString
420 =============
421 */
FloatArrayToString(const float * array,const int length,const int precision)422 const char *idStr::FloatArrayToString( const float *array, const int length, const int precision ) {
423 	static int index = 0;
424 	static char str[4][16384];	// in case called by nested functions
425 	int i, n;
426 	char format[16], *s;
427 
428 	// use an array of string so that multiple calls won't collide
429 	s = str[ index ];
430 	index = (index + 1) & 3;
431 
432 	idStr::snPrintf( format, sizeof( format ), "%%.%df", precision );
433 	n = idStr::snPrintf( s, sizeof( str[0] ), format, array[0] );
434 	if ( precision > 0 ) {
435 		while( n > 0 && s[n-1] == '0' ) s[--n] = '\0';
436 		while( n > 0 && s[n-1] == '.' ) s[--n] = '\0';
437 	}
438 	idStr::snPrintf( format, sizeof( format ), " %%.%df", precision );
439 	for ( i = 1; i < length; i++ ) {
440 		n += idStr::snPrintf( s + n, sizeof( str[0] ) - n, format, array[i] );
441 		if ( precision > 0 ) {
442 			while( n > 0 && s[n-1] == '0' ) s[--n] = '\0';
443 			while( n > 0 && s[n-1] == '.' ) s[--n] = '\0';
444 		}
445 	}
446 	return s;
447 }
448 
449 /*
450 ============
451 idStr::Last
452 
453 returns -1 if not found otherwise the index of the char
454 ============
455 */
Last(const char c) const456 int idStr::Last( const char c ) const {
457 	int i;
458 
459 	for( i = Length(); i > 0; i-- ) {
460 		if ( data[ i - 1 ] == c ) {
461 			return i - 1;
462 		}
463 	}
464 
465 	return -1;
466 }
467 
468 /*
469 ============
470 idStr::StripLeading
471 ============
472 */
StripLeading(const char c)473 void idStr::StripLeading( const char c ) {
474 	while( data[ 0 ] == c ) {
475 		memmove( &data[ 0 ], &data[ 1 ], len );
476 		len--;
477 	}
478 }
479 
480 /*
481 ============
482 idStr::StripLeading
483 ============
484 */
StripLeading(const char * string)485 void idStr::StripLeading( const char *string ) {
486 	int l;
487 
488 	l = strlen( string );
489 	if ( l > 0 ) {
490 		while ( !Cmpn( string, l ) ) {
491 			memmove( data, data + l, len - l + 1 );
492 			len -= l;
493 		}
494 	}
495 }
496 
497 /*
498 ============
499 idStr::StripLeadingOnce
500 ============
501 */
StripLeadingOnce(const char * string)502 bool idStr::StripLeadingOnce( const char *string ) {
503 	int l;
504 
505 	l = strlen( string );
506 	if ( ( l > 0 ) && !Cmpn( string, l ) ) {
507 		memmove( data, data + l, len - l + 1 );
508 		len -= l;
509 		return true;
510 	}
511 	return false;
512 }
513 
514 /*
515 ============
516 idStr::StripTrailing
517 ============
518 */
StripTrailing(const char c)519 void idStr::StripTrailing( const char c ) {
520 	int i;
521 
522 	for( i = Length(); i > 0 && data[ i - 1 ] == c; i-- ) {
523 		data[ i - 1 ] = '\0';
524 		len--;
525 	}
526 }
527 
528 /*
529 ============
530 idStr::StripLeading
531 ============
532 */
StripTrailing(const char * string)533 void idStr::StripTrailing( const char *string ) {
534 	int l;
535 
536 	l = strlen( string );
537 	if ( l > 0 ) {
538 		while ( ( len >= l ) && !Cmpn( string, data + len - l, l ) ) {
539 			len -= l;
540 			data[len] = '\0';
541 		}
542 	}
543 }
544 
545 /*
546 ============
547 idStr::StripTrailingOnce
548 ============
549 */
StripTrailingOnce(const char * string)550 bool idStr::StripTrailingOnce( const char *string ) {
551 	int l;
552 
553 	l = strlen( string );
554 	if ( ( l > 0 ) && ( len >= l ) && !Cmpn( string, data + len - l, l ) ) {
555 		len -= l;
556 		data[len] = '\0';
557 		return true;
558 	}
559 	return false;
560 }
561 
562 /*
563 ============
564 idStr::Replace
565 ============
566 */
Replace(const char * old,const char * nw)567 void idStr::Replace( const char *old, const char *nw ) {
568 	int		oldLen, newLen, i, j, count;
569 	idStr	oldString( data );
570 
571 	oldLen = strlen( old );
572 	newLen = strlen( nw );
573 
574 	// Work out how big the new string will be
575 	count = 0;
576 	for( i = 0; i < oldString.Length(); i++ ) {
577 		if( !idStr::Cmpn( &oldString[i], old, oldLen ) ) {
578 			count++;
579 			i += oldLen - 1;
580 		}
581 	}
582 
583 	if( count ) {
584 		EnsureAlloced( len + ( ( newLen - oldLen ) * count ) + 2, false );
585 
586 		// Replace the old data with the new data
587 		for( i = 0, j = 0; i < oldString.Length(); i++ ) {
588 			if( !idStr::Cmpn( &oldString[i], old, oldLen ) ) {
589 				memcpy( data + j, nw, newLen );
590 				i += oldLen - 1;
591 				j += newLen;
592 			} else {
593 				data[j] = oldString[i];
594 				j++;
595 			}
596 		}
597 		data[j] = 0;
598 		len = strlen( data );
599 	}
600 }
601 
602 /*
603 ============
604 idStr::Mid
605 ============
606 */
Mid(int start,int len,idStr & result) const607 const char *idStr::Mid( int start, int len, idStr &result ) const {
608 	int i;
609 
610 	result.Empty();
611 
612 	i = Length();
613 	if ( i == 0 || len <= 0 || start >= i ) {
614 		return NULL;
615 	}
616 
617 	if ( start + len >= i ) {
618 		len = i - start;
619 	}
620 
621 	result.Append( &data[ start ], len );
622 	return result;
623 }
624 
625 /*
626 ============
627 idStr::Mid
628 ============
629 */
Mid(int start,int len) const630 idStr idStr::Mid( int start, int len ) const {
631 	int i;
632 	idStr result;
633 
634 	i = Length();
635 	if ( i == 0 || len <= 0 || start >= i ) {
636 		return result;
637 	}
638 
639 	if ( start + len >= i ) {
640 		len = i - start;
641 	}
642 
643 	result.Append( &data[ start ], len );
644 	return result;
645 }
646 
647 /*
648 ============
649 idStr::StripTrailingWhitespace
650 ============
651 */
StripTrailingWhitespace(void)652 void idStr::StripTrailingWhitespace( void ) {
653 	int i;
654 
655 	// cast to unsigned char to prevent stripping off high-ASCII characters
656 	for( i = Length(); i > 0 && (unsigned char)(data[ i - 1 ]) <= ' '; i-- ) {
657 		data[ i - 1 ] = '\0';
658 		len--;
659 	}
660 }
661 
662 /*
663 ============
664 idStr::StripQuotes
665 
666 Removes the quotes from the beginning and end of the string
667 ============
668 */
StripQuotes(void)669 idStr& idStr::StripQuotes ( void )
670 {
671 	if ( data[0] != '\"' )
672 	{
673 		return *this;
674 	}
675 
676 	// Remove the trailing quote first
677 	if ( data[len-1] == '\"' )
678 	{
679 		data[len-1] = '\0';
680 		len--;
681 	}
682 
683 	// Strip the leading quote now
684 	len--;
685 	memmove( &data[ 0 ], &data[ 1 ], len );
686 	data[len] = '\0';
687 
688 	return *this;
689 }
690 
691 /*
692 =====================================================================
693 
694   filename methods
695 
696 =====================================================================
697 */
698 
699 /*
700 ============
701 idStr::FileNameHash
702 ============
703 */
FileNameHash(void) const704 int idStr::FileNameHash( void ) const {
705 	int		i;
706 	int		hash;
707 	char	letter;
708 
709 	hash = 0;
710 	i = 0;
711 	while( data[i] != '\0' ) {
712 		letter = idStr::ToLower( data[i] );
713 		if ( letter == '.' ) {
714 			break;				// don't include extension
715 		}
716 		if ( letter =='\\' ) {
717 			letter = '/';
718 		}
719 		hash += (int)(letter)*(i+119);
720 		i++;
721 	}
722 	hash &= (FILE_HASH_SIZE-1);
723 	return hash;
724 }
725 
726 /*
727 ============
728 idStr::BackSlashesToSlashes
729 ============
730 */
BackSlashesToSlashes(void)731 idStr &idStr::BackSlashesToSlashes( void ) {
732 	int i;
733 
734 	for ( i = 0; i < len; i++ ) {
735 		if ( data[ i ] == '\\' ) {
736 			data[ i ] = '/';
737 		}
738 	}
739 	return *this;
740 }
741 
742 /*
743 ============
744 idStr::SetFileExtension
745 ============
746 */
SetFileExtension(const char * extension)747 idStr &idStr::SetFileExtension( const char *extension ) {
748 	StripFileExtension();
749 	if ( *extension != '.' ) {
750 		Append( '.' );
751 	}
752 	Append( extension );
753 	return *this;
754 }
755 
756 // DG: helper-function that returns true if the character c is a directory separator
757 //     on the current platform
isDirSeparator(int c)758 static ID_INLINE bool isDirSeparator( int c )
759 {
760 	if ( c == '/' ) {
761 		return true;
762 	}
763 #ifdef _WIN32
764 	if ( c == '\\' ) {
765 		return true;
766 	}
767 #elif defined(__AROS__)
768 	if ( c == ':' ) {
769 		return true;
770 	}
771 #endif
772 	return false;
773 }
774 // DG end
775 
776 /*
777 ============
778 idStr::StripFileExtension
779 ============
780 */
StripFileExtension(void)781 idStr &idStr::StripFileExtension( void ) {
782 	int i;
783 
784 	for ( i = len-1; i >= 0; i-- ) {
785 		// DG: we're at a directory separator, nothing to strip at filename
786 		if ( isDirSeparator( data[i] ) ) {
787 			break;
788 		} // DG end
789 		if ( data[i] == '.' ) {
790 			data[i] = '\0';
791 			len = i;
792 			break;
793 		}
794 	}
795 	return *this;
796 }
797 
798 /*
799 ============
800 idStr::StripAbsoluteFileExtension
801 ============
802 */
StripAbsoluteFileExtension(void)803 idStr &idStr::StripAbsoluteFileExtension( void ) {
804 	int i;
805 	// FIXME DG: seems like this is unused, but it probably doesn't do what's expected
806 	//           (if you wanna strip .tar.gz this will fail with dots in path,
807 	//            if you indeed wanna strip the first dot in *path* (even in some directory) this is right)
808 	for ( i = 0; i < len; i++ ) {
809 		if ( data[i] == '.' ) {
810 			data[i] = '\0';
811 			len = i;
812 			break;
813 		}
814 	}
815 
816 	return *this;
817 }
818 
819 /*
820 ==================
821 idStr::DefaultFileExtension
822 ==================
823 */
DefaultFileExtension(const char * extension)824 idStr &idStr::DefaultFileExtension( const char *extension ) {
825 	int i;
826 
827 	// do nothing if the string already has an extension
828 	for ( i = len-1; i >= 0; i-- ) {
829 		// DG: we're at a directory separator, there was no file extension
830 		if ( isDirSeparator( data[i] ) ) {
831 			break;
832 		} // DG end
833 		if ( data[i] == '.' ) {
834 			return *this;
835 		}
836 	}
837 	if ( *extension != '.' ) {
838 		Append( '.' );
839 	}
840 	Append( extension );
841 	return *this;
842 }
843 
844 /*
845 ==================
846 idStr::DefaultPath
847 ==================
848 */
DefaultPath(const char * basepath)849 idStr &idStr::DefaultPath( const char *basepath ) {
850 	if ( isDirSeparator( ( *this )[ 0 ] ) ) {
851 		// absolute path location
852 		return *this;
853 	}
854 
855 	*this = basepath + *this;
856 	return *this;
857 }
858 
859 /*
860 ====================
861 idStr::AppendPath
862 ====================
863 */
AppendPath(const char * text)864 void idStr::AppendPath( const char *text ) {
865 	int pos;
866 	int i = 0;
867 
868 	if ( text && text[i] ) {
869 		pos = len;
870 		EnsureAlloced( len + strlen( text ) + 2 );
871 
872 		if ( pos ) {
873 			if ( !isDirSeparator( data[ pos-1 ] ) ) {
874 				data[ pos++ ] = '/';
875 			}
876 		}
877 
878 		if ( isDirSeparator( text[ i ] ) ) {
879 			i++;
880 		}
881 
882 		for ( ; text[ i ]; i++ ) {
883 			if ( text[ i ] == '\\' ) {
884 				data[ pos++ ] = '/';
885 			} else {
886 				data[ pos++ ] = text[ i ];
887 			}
888 		}
889 		len = pos;
890 		data[ pos ] = '\0';
891 	}
892 }
893 
894 /*
895 ==================
896 idStr::StripFilename
897 ==================
898 */
StripFilename(void)899 idStr &idStr::StripFilename( void ) {
900 	int pos;
901 
902 	pos = Length() - 1;
903 	while( ( pos > 0 ) && !isDirSeparator( ( *this )[ pos ] ) ) {
904 		pos--;
905 	}
906 
907 	if ( pos < 0 ) {
908 		pos = 0;
909 	}
910 
911 	CapLength( pos );
912 	return *this;
913 }
914 
915 /*
916 ==================
917 idStr::StripPath
918 ==================
919 */
StripPath(void)920 idStr &idStr::StripPath( void ) {
921 	int pos;
922 
923 	pos = Length();
924 	while( ( pos > 0 ) && !isDirSeparator( ( *this )[ pos - 1 ] ) ) {
925 		pos--;
926 	}
927 
928 	*this = Right( Length() - pos );
929 	return *this;
930 }
931 
932 /*
933 ====================
934 idStr::ExtractFilePath
935 ====================
936 */
ExtractFilePath(idStr & dest) const937 void idStr::ExtractFilePath( idStr &dest ) const {
938 	int pos;
939 
940 	//
941 	// back up until a \ or the start
942 	//
943 	pos = Length();
944 	while( ( pos > 0 ) &&  !isDirSeparator( ( *this )[ pos - 1 ] ) ) {
945 		pos--;
946 	}
947 
948 	Left( pos, dest );
949 }
950 
951 /*
952 ====================
953 idStr::ExtractFileName
954 ====================
955 */
ExtractFileName(idStr & dest) const956 void idStr::ExtractFileName( idStr &dest ) const {
957 	int pos;
958 
959 	//
960 	// back up until a \ or the start
961 	//
962 	pos = Length() - 1;
963 	while( ( pos > 0 ) && !isDirSeparator( ( *this )[ pos - 1 ] ) ) {
964 		pos--;
965 	}
966 
967 	Right( Length() - pos, dest );
968 }
969 
970 /*
971 ====================
972 idStr::ExtractFileBase
973 ====================
974 */
ExtractFileBase(idStr & dest) const975 void idStr::ExtractFileBase( idStr &dest ) const {
976 	int pos;
977 	int start;
978 
979 	//
980 	// back up until a \ or the start
981 	//
982 	pos = Length() - 1;
983 	while( ( pos > 0 ) && !isDirSeparator( ( *this )[ pos - 1 ] ) ) {
984 		pos--;
985 	}
986 
987 	start = pos;
988 	while( ( pos < Length() ) && ( ( *this )[ pos ] != '.' ) ) {
989 		pos++;
990 	}
991 
992 	Mid( start, pos - start, dest );
993 }
994 
995 /*
996 ====================
997 idStr::ExtractFileExtension
998 ====================
999 */
ExtractFileExtension(idStr & dest) const1000 void idStr::ExtractFileExtension( idStr &dest ) const {
1001 	int pos;
1002 
1003 	//
1004 	// back up until a . or the start
1005 	//
1006 	pos = Length() - 1;
1007 	while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '.' ) ) {
1008 		pos--;
1009 		if( isDirSeparator( ( *this )[ pos ] ) ) { // DG: check for directory separator
1010 			// no extension in the whole filename
1011 			dest.Empty();
1012 		} // DG end
1013 	}
1014 
1015 	if ( !pos ) {
1016 		// no extension
1017 		dest.Empty();
1018 	} else {
1019 		Right( Length() - pos, dest );
1020 	}
1021 }
1022 
1023 
1024 /*
1025 =====================================================================
1026 
1027   char * methods to replace library functions
1028 
1029 =====================================================================
1030 */
1031 
1032 /*
1033 ============
1034 idStr::IsNumeric
1035 
1036 Checks a string to see if it contains only numerical values.
1037 ============
1038 */
IsNumeric(const char * s)1039 bool idStr::IsNumeric( const char *s ) {
1040 	int		i;
1041 	bool	dot;
1042 
1043 	if ( *s == '-' ) {
1044 		s++;
1045 	}
1046 
1047 	dot = false;
1048 	for ( i = 0; s[i]; i++ ) {
1049 		if ( !isdigit( s[i] ) ) {
1050 			if ( ( s[ i ] == '.' ) && !dot ) {
1051 				dot = true;
1052 				continue;
1053 			}
1054 			return false;
1055 		}
1056 	}
1057 
1058 	return true;
1059 }
1060 
1061 /*
1062 ============
1063 idStr::HasLower
1064 
1065 Checks if a string has any lowercase chars
1066 ============
1067 */
HasLower(const char * s)1068 bool idStr::HasLower( const char *s ) {
1069 	if ( !s ) {
1070 		return false;
1071 	}
1072 
1073 	while ( *s ) {
1074 		if ( CharIsLower( *s ) ) {
1075 			return true;
1076 		}
1077 		s++;
1078 	}
1079 
1080 	return false;
1081 }
1082 
1083 /*
1084 ============
1085 idStr::HasUpper
1086 
1087 Checks if a string has any uppercase chars
1088 ============
1089 */
HasUpper(const char * s)1090 bool idStr::HasUpper( const char *s ) {
1091 	if ( !s ) {
1092 		return false;
1093 	}
1094 
1095 	while ( *s ) {
1096 		if ( CharIsUpper( *s ) ) {
1097 			return true;
1098 		}
1099 		s++;
1100 	}
1101 
1102 	return false;
1103 }
1104 
1105 /*
1106 ================
1107 idStr::Cmp
1108 ================
1109 */
Cmp(const char * s1,const char * s2)1110 int idStr::Cmp( const char *s1, const char *s2 ) {
1111 	int c1, c2, d;
1112 
1113 	do {
1114 		c1 = *s1++;
1115 		c2 = *s2++;
1116 
1117 		d = c1 - c2;
1118 		if ( d ) {
1119 			return ( INTSIGNBITNOTSET( d ) << 1 ) - 1;
1120 		}
1121 	} while( c1 );
1122 
1123 	return 0;		// strings are equal
1124 }
1125 
1126 /*
1127 ================
1128 idStr::Cmpn
1129 ================
1130 */
Cmpn(const char * s1,const char * s2,int n)1131 int idStr::Cmpn( const char *s1, const char *s2, int n ) {
1132 	int c1, c2, d;
1133 
1134 	assert( n >= 0 );
1135 
1136 	do {
1137 		c1 = *s1++;
1138 		c2 = *s2++;
1139 
1140 		if ( !n-- ) {
1141 			return 0;		// strings are equal until end point
1142 		}
1143 
1144 		d = c1 - c2;
1145 		if ( d ) {
1146 			return ( INTSIGNBITNOTSET( d ) << 1 ) - 1;
1147 		}
1148 	} while( c1 );
1149 
1150 	return 0;		// strings are equal
1151 }
1152 
1153 /*
1154 ================
1155 idStr::Icmp
1156 ================
1157 */
Icmp(const char * s1,const char * s2)1158 int idStr::Icmp( const char *s1, const char *s2 ) {
1159 	int c1, c2, d;
1160 
1161 	do {
1162 		c1 = *s1++;
1163 		c2 = *s2++;
1164 
1165 		d = c1 - c2;
1166 		while( d ) {
1167 			if ( c1 <= 'Z' && c1 >= 'A' ) {
1168 				d += ('a' - 'A');
1169 				if ( !d ) {
1170 					break;
1171 				}
1172 			}
1173 			if ( c2 <= 'Z' && c2 >= 'A' ) {
1174 				d -= ('a' - 'A');
1175 				if ( !d ) {
1176 					break;
1177 				}
1178 			}
1179 			return ( INTSIGNBITNOTSET( d ) << 1 ) - 1;
1180 		}
1181 	} while( c1 );
1182 
1183 	return 0;		// strings are equal
1184 }
1185 
1186 /*
1187 ================
1188 idStr::Icmpn
1189 ================
1190 */
Icmpn(const char * s1,const char * s2,int n)1191 int idStr::Icmpn( const char *s1, const char *s2, int n ) {
1192 	int c1, c2, d;
1193 
1194 	assert( n >= 0 );
1195 
1196 	do {
1197 		c1 = *s1++;
1198 		c2 = *s2++;
1199 
1200 		if ( !n-- ) {
1201 			return 0;		// strings are equal until end point
1202 		}
1203 
1204 		d = c1 - c2;
1205 		while( d ) {
1206 			if ( c1 <= 'Z' && c1 >= 'A' ) {
1207 				d += ('a' - 'A');
1208 				if ( !d ) {
1209 					break;
1210 				}
1211 			}
1212 			if ( c2 <= 'Z' && c2 >= 'A' ) {
1213 				d -= ('a' - 'A');
1214 				if ( !d ) {
1215 					break;
1216 				}
1217 			}
1218 			return ( INTSIGNBITNOTSET( d ) << 1 ) - 1;
1219 		}
1220 	} while( c1 );
1221 
1222 	return 0;		// strings are equal
1223 }
1224 
1225 /*
1226 ================
1227 idStr::Icmp
1228 ================
1229 */
IcmpNoColor(const char * s1,const char * s2)1230 int idStr::IcmpNoColor( const char *s1, const char *s2 ) {
1231 	int c1, c2, d;
1232 
1233 	do {
1234 		while ( idStr::IsColor( s1 ) ) {
1235 			s1 += 2;
1236 		}
1237 		while ( idStr::IsColor( s2 ) ) {
1238 			s2 += 2;
1239 		}
1240 		c1 = *s1++;
1241 		c2 = *s2++;
1242 
1243 		d = c1 - c2;
1244 		while( d ) {
1245 			if ( c1 <= 'Z' && c1 >= 'A' ) {
1246 				d += ('a' - 'A');
1247 				if ( !d ) {
1248 					break;
1249 				}
1250 			}
1251 			if ( c2 <= 'Z' && c2 >= 'A' ) {
1252 				d -= ('a' - 'A');
1253 				if ( !d ) {
1254 					break;
1255 				}
1256 			}
1257 			return ( INTSIGNBITNOTSET( d ) << 1 ) - 1;
1258 		}
1259 	} while( c1 );
1260 
1261 	return 0;		// strings are equal
1262 }
1263 
1264 /*
1265 ================
1266 idStr::IcmpPath
1267 ================
1268 */
IcmpPath(const char * s1,const char * s2)1269 int idStr::IcmpPath( const char *s1, const char *s2 ) {
1270 	int c1, c2, d;
1271 
1272 #if 0
1273 //#if !defined( _WIN32 )
1274 	idLib::common->Printf( "WARNING: IcmpPath used on a case-sensitive filesystem?\n" );
1275 #endif
1276 
1277 	do {
1278 		c1 = *s1++;
1279 		c2 = *s2++;
1280 
1281 		d = c1 - c2;
1282 		while( d ) {
1283 			if ( c1 <= 'Z' && c1 >= 'A' ) {
1284 				d += ('a' - 'A');
1285 				if ( !d ) {
1286 					break;
1287 				}
1288 			}
1289 			if ( c1 == '\\' ) {
1290 				d += ('/' - '\\');
1291 				if ( !d ) {
1292 					break;
1293 				}
1294 			}
1295 			if ( c2 <= 'Z' && c2 >= 'A' ) {
1296 				d -= ('a' - 'A');
1297 				if ( !d ) {
1298 					break;
1299 				}
1300 			}
1301 			if ( c2 == '\\' ) {
1302 				d -= ('/' - '\\');
1303 				if ( !d ) {
1304 					break;
1305 				}
1306 			}
1307 			// make sure folders come first
1308 			while( c1 ) {
1309 				if ( c1 == '/' || c1 == '\\' ) {
1310 					break;
1311 				}
1312 				c1 = *s1++;
1313 			}
1314 			while( c2 ) {
1315 				if ( c2 == '/' || c2 == '\\' ) {
1316 					break;
1317 				}
1318 				c2 = *s2++;
1319 			}
1320 			if ( c1 && !c2 ) {
1321 				return -1;
1322 			} else if ( !c1 && c2 ) {
1323 				return 1;
1324 			}
1325 			// same folder depth so use the regular compare
1326 			return ( INTSIGNBITNOTSET( d ) << 1 ) - 1;
1327 		}
1328 	} while( c1 );
1329 
1330 	return 0;
1331 }
1332 
1333 /*
1334 ================
1335 idStr::IcmpnPath
1336 ================
1337 */
IcmpnPath(const char * s1,const char * s2,int n)1338 int idStr::IcmpnPath( const char *s1, const char *s2, int n ) {
1339 	int c1, c2, d;
1340 
1341 #if 0
1342 //#if !defined( _WIN32 )
1343 	idLib::common->Printf( "WARNING: IcmpPath used on a case-sensitive filesystem?\n" );
1344 #endif
1345 
1346 	assert( n >= 0 );
1347 
1348 	do {
1349 		c1 = *s1++;
1350 		c2 = *s2++;
1351 
1352 		if ( !n-- ) {
1353 			return 0;		// strings are equal until end point
1354 		}
1355 
1356 		d = c1 - c2;
1357 		while( d ) {
1358 			if ( c1 <= 'Z' && c1 >= 'A' ) {
1359 				d += ('a' - 'A');
1360 				if ( !d ) {
1361 					break;
1362 				}
1363 			}
1364 			if ( c1 == '\\' ) {
1365 				d += ('/' - '\\');
1366 				if ( !d ) {
1367 					break;
1368 				}
1369 			}
1370 			if ( c2 <= 'Z' && c2 >= 'A' ) {
1371 				d -= ('a' - 'A');
1372 				if ( !d ) {
1373 					break;
1374 				}
1375 			}
1376 			if ( c2 == '\\' ) {
1377 				d -= ('/' - '\\');
1378 				if ( !d ) {
1379 					break;
1380 				}
1381 			}
1382 			// make sure folders come first
1383 			while( c1 ) {
1384 				if ( c1 == '/' || c1 == '\\' ) {
1385 					break;
1386 				}
1387 				c1 = *s1++;
1388 			}
1389 			while( c2 ) {
1390 				if ( c2 == '/' || c2 == '\\' ) {
1391 					break;
1392 				}
1393 				c2 = *s2++;
1394 			}
1395 			if ( c1 && !c2 ) {
1396 				return -1;
1397 			} else if ( !c1 && c2 ) {
1398 				return 1;
1399 			}
1400 			// same folder depth so use the regular compare
1401 			return ( INTSIGNBITNOTSET( d ) << 1 ) - 1;
1402 		}
1403 	} while( c1 );
1404 
1405 	return 0;
1406 }
1407 
1408 /*
1409 =============
1410 idStr::Copynz
1411 
1412 Safe strncpy that ensures a trailing zero
1413 =============
1414 */
Copynz(char * dest,const char * src,int destsize)1415 void idStr::Copynz( char *dest, const char *src, int destsize ) {
1416 	if ( !src ) {
1417 		idLib::common->Warning( "idStr::Copynz: NULL src" );
1418 		return;
1419 	}
1420 	if ( destsize < 1 ) {
1421 		idLib::common->Warning( "idStr::Copynz: destsize < 1" );
1422 		return;
1423 	}
1424 
1425 	strncpy( dest, src, destsize-1 );
1426 	dest[destsize-1] = 0;
1427 }
1428 
1429 /*
1430 ================
1431 idStr::Append
1432 
1433   never goes past bounds or leaves without a terminating 0
1434 ================
1435 */
Append(char * dest,int size,const char * src)1436 void idStr::Append( char *dest, int size, const char *src ) {
1437 	int		l1;
1438 
1439 	l1 = strlen( dest );
1440 	if ( l1 >= size ) {
1441 		idLib::common->Error( "idStr::Append: already overflowed" );
1442 	}
1443 	idStr::Copynz( dest + l1, src, size - l1 );
1444 }
1445 
1446 /*
1447 ================
1448 idStr::LengthWithoutColors
1449 ================
1450 */
LengthWithoutColors(const char * s)1451 int idStr::LengthWithoutColors( const char *s ) {
1452 	int len;
1453 	const char *p;
1454 
1455 	if ( !s ) {
1456 		return 0;
1457 	}
1458 
1459 	len = 0;
1460 	p = s;
1461 	while( *p ) {
1462 		if ( idStr::IsColor( p ) ) {
1463 			p += 2;
1464 			continue;
1465 		}
1466 		p++;
1467 		len++;
1468 	}
1469 
1470 	return len;
1471 }
1472 
1473 /*
1474 ================
1475 idStr::RemoveColors
1476 ================
1477 */
RemoveColors(char * string)1478 char *idStr::RemoveColors( char *string ) {
1479 	char *d;
1480 	char *s;
1481 	int c;
1482 
1483 	s = string;
1484 	d = string;
1485 	while( (c = *s) != 0 ) {
1486 		if ( idStr::IsColor( s ) ) {
1487 			s++;
1488 		}
1489 		else {
1490 			*d++ = c;
1491 		}
1492 		s++;
1493 	}
1494 	*d = '\0';
1495 
1496 	return string;
1497 }
1498 
1499 /*
1500 ================
1501 idStr::snPrintf
1502 ================
1503 */
snPrintf(char * dest,int size,const char * fmt,...)1504 int idStr::snPrintf( char *dest, int size, const char *fmt, ...) {
1505 	int len;
1506 	va_list argptr;
1507 	char buffer[32000];	// big, but small enough to fit in PPC stack
1508 
1509 	va_start( argptr, fmt );
1510 	len = vsprintf( buffer, fmt, argptr );
1511 	va_end( argptr );
1512 	if ( len >= sizeof( buffer ) ) {
1513 		idLib::common->Error( "idStr::snPrintf: overflowed buffer" );
1514 	}
1515 	if ( len >= size ) {
1516 		idLib::common->Warning( "idStr::snPrintf: overflow of %i in %i\n", len, size );
1517 		len = size;
1518 	}
1519 	idStr::Copynz( dest, buffer, size );
1520 	return len;
1521 }
1522 
1523 /*
1524 ============
1525 idStr::vsnPrintf
1526 
1527 vsnprintf portability:
1528 
1529 C99 standard: vsnprintf returns the number of characters (excluding the trailing
1530 '\0') which would have been written to the final string if enough space had been available
1531 snprintf and vsnprintf do not write more than size bytes (including the trailing '\0')
1532 
1533 win32: _vsnprintf returns the number of characters written, not including the terminating null character,
1534 or a negative value if an output error occurs. If the number of characters to write exceeds count, then count
1535 characters are written and -1 is returned and no trailing '\0' is added.
1536 
1537 idStr::vsnPrintf: always appends a trailing '\0', returns number of characters written (not including terminal \0)
1538 or returns -1 on failure or if the buffer would be overflowed.
1539 ============
1540 */
vsnPrintf(char * dest,int size,const char * fmt,va_list argptr)1541 int idStr::vsnPrintf( char *dest, int size, const char *fmt, va_list argptr ) {
1542 	int ret;
1543 
1544 #ifdef _WIN32
1545 #undef _vsnprintf
1546 	ret = _vsnprintf( dest, size-1, fmt, argptr );
1547 #define _vsnprintf	use_idStr_vsnPrintf
1548 #else
1549 #undef vsnprintf
1550 	ret = vsnprintf( dest, size, fmt, argptr );
1551 #define vsnprintf	use_idStr_vsnPrintf
1552 #endif
1553 	dest[size-1] = '\0';
1554 	if ( ret < 0 || ret >= size ) {
1555 		return -1;
1556 	}
1557 	return ret;
1558 }
1559 
1560 /*
1561 ============
1562 sprintf
1563 
1564 Sets the value of the string using a printf interface.
1565 ============
1566 */
sprintf(idStr & string,const char * fmt,...)1567 int sprintf( idStr &string, const char *fmt, ... ) {
1568 	int l;
1569 	va_list argptr;
1570 	char buffer[32000];
1571 
1572 	va_start( argptr, fmt );
1573 	l = idStr::vsnPrintf( buffer, sizeof(buffer)-1, fmt, argptr );
1574 	va_end( argptr );
1575 	buffer[sizeof(buffer)-1] = '\0';
1576 
1577 	string = buffer;
1578 	return l;
1579 }
1580 
1581 /*
1582 ============
1583 vsprintf
1584 
1585 Sets the value of the string using a vprintf interface.
1586 ============
1587 */
vsprintf(idStr & string,const char * fmt,va_list argptr)1588 int vsprintf( idStr &string, const char *fmt, va_list argptr ) {
1589 	int l;
1590 	char buffer[32000];
1591 
1592 	l = idStr::vsnPrintf( buffer, sizeof(buffer)-1, fmt, argptr );
1593 	buffer[sizeof(buffer)-1] = '\0';
1594 
1595 	string = buffer;
1596 	return l;
1597 }
1598 
1599 /*
1600 ============
1601 va
1602 
1603 does a varargs printf into a temp buffer
1604 NOTE: not thread safe
1605 ============
1606 */
va(const char * fmt,...)1607 char *va( const char *fmt, ... ) {
1608 	va_list argptr;
1609 	static int index = 0;
1610 	static char string[4][16384];	// in case called by nested functions
1611 	char *buf;
1612 
1613 	buf = string[index];
1614 	index = (index + 1) & 3;
1615 
1616 	va_start( argptr, fmt );
1617 	vsprintf( buf, fmt, argptr );
1618 	va_end( argptr );
1619 
1620 	return buf;
1621 }
1622 
1623 
1624 
1625 /*
1626 ============
1627 idStr::BestUnit
1628 ============
1629 */
BestUnit(const char * format,float value,Measure_t measure)1630 int idStr::BestUnit( const char *format, float value, Measure_t measure ) {
1631 	int unit = 1;
1632 	while ( unit <= 3 && ( 1 << ( unit * 10 ) < value ) ) {
1633 		unit++;
1634 	}
1635 	unit--;
1636 	value /= 1 << ( unit * 10 );
1637 	sprintf( *this, format, value );
1638 	*this += " ";
1639 	*this += units[ measure ][ unit ];
1640 	return unit;
1641 }
1642 
1643 /*
1644 ============
1645 idStr::SetUnit
1646 ============
1647 */
SetUnit(const char * format,float value,int unit,Measure_t measure)1648 void idStr::SetUnit( const char *format, float value, int unit, Measure_t measure ) {
1649 	value /= 1 << ( unit * 10 );
1650 	sprintf( *this, format, value );
1651 	*this += " ";
1652 	*this += units[ measure ][ unit ];
1653 }
1654 
1655 /*
1656 ================
1657 idStr::InitMemory
1658 ================
1659 */
InitMemory(void)1660 void idStr::InitMemory( void ) {
1661 #ifdef USE_STRING_DATA_ALLOCATOR
1662 	stringDataAllocator.Init();
1663 #endif
1664 }
1665 
1666 /*
1667 ================
1668 idStr::ShutdownMemory
1669 ================
1670 */
ShutdownMemory(void)1671 void idStr::ShutdownMemory( void ) {
1672 #ifdef USE_STRING_DATA_ALLOCATOR
1673 	stringDataAllocator.Shutdown();
1674 #endif
1675 }
1676 
1677 /*
1678 ================
1679 idStr::PurgeMemory
1680 ================
1681 */
PurgeMemory(void)1682 void idStr::PurgeMemory( void ) {
1683 #ifdef USE_STRING_DATA_ALLOCATOR
1684 	stringDataAllocator.FreeEmptyBaseBlocks();
1685 #endif
1686 }
1687 
1688 /*
1689 ================
1690 idStr::ShowMemoryUsage_f
1691 ================
1692 */
ShowMemoryUsage_f(const idCmdArgs & args)1693 void idStr::ShowMemoryUsage_f( const idCmdArgs &args ) {
1694 #ifdef USE_STRING_DATA_ALLOCATOR
1695 	idLib::common->Printf( "%6d KB string memory (%d KB free in %d blocks, %d empty base blocks)\n",
1696 		stringDataAllocator.GetBaseBlockMemory() >> 10, stringDataAllocator.GetFreeBlockMemory() >> 10,
1697 			stringDataAllocator.GetNumFreeBlocks(), stringDataAllocator.GetNumEmptyBaseBlocks() );
1698 #endif
1699 }
1700 
1701 /*
1702 ================
1703 idStr::FormatNumber
1704 ================
1705 */
1706 struct formatList_t {
1707 	int			gran;
1708 	int			count;
1709 };
1710 
1711 // elements of list need to decend in size
1712 formatList_t formatList[] = {
1713 	{ 1000000000, 0 },
1714 	{ 1000000, 0 },
1715 	{ 1000, 0 }
1716 };
1717 
1718 int numFormatList = sizeof(formatList) / sizeof( formatList[0] );
1719 
1720 
FormatNumber(int number)1721 idStr idStr::FormatNumber( int number ) {
1722 	idStr string;
1723 	bool hit;
1724 
1725 	// reset
1726 	for ( int i = 0; i < numFormatList; i++ ) {
1727 		formatList_t *li = formatList + i;
1728 		li->count = 0;
1729 	}
1730 
1731 	// main loop
1732 	do {
1733 		hit = false;
1734 
1735 		for ( int i = 0; i < numFormatList; i++ ) {
1736 			formatList_t *li = formatList + i;
1737 
1738 			if ( number >= li->gran ) {
1739 				li->count++;
1740 				number -= li->gran;
1741 				hit = true;
1742 				break;
1743 			}
1744 		}
1745 	} while ( hit );
1746 
1747 	// print out
1748 	bool found = false;
1749 
1750 	for ( int i = 0; i < numFormatList; i++ ) {
1751 		formatList_t *li = formatList + i;
1752 
1753 		if ( li->count ) {
1754 			if ( !found ) {
1755 				string += va( "%i,", li->count );
1756 			} else {
1757 				string += va( "%3.3i,", li->count );
1758 			}
1759 			found = true;
1760 		}
1761 		else if ( found ) {
1762 			string += va( "%3.3i,", li->count );
1763 		}
1764 	}
1765 
1766 	if ( found ) {
1767 		string += va( "%3.3i", number );
1768 	}
1769 	else {
1770 		string += va( "%i", number );
1771 	}
1772 
1773 	// pad to proper size
1774 	int count = 11 - string.Length();
1775 
1776 	for ( int i = 0; i < count; i++ ) {
1777 		string.Insert( " ", 0 );
1778 	}
1779 
1780 	return string;
1781 }
1782