/* * Copyright © 2006-2016 SplinterGU (Fenix/Bennugd) * Copyright © 2002-2006 Fenix Team (Fenix) * Copyright © 1999-2002 José Luis Cebrián Pagüe (Fenix) * * This file is part of Bennu - Game Development * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source * distribution. * */ /****************************************************************************/ /* FILE : strings.c */ /* DESCRIPTION : Strings management. Includes any function related to */ /* variable-length strings. Those strings are allocated */ /* in dynamic memory with reference counting. */ /****************************************************************************/ #include #include #include #include #ifdef TARGET_BEOS #include #else #include #endif #include "files.h" #include "xctype.h" /****************************************************************************/ #define BLOCK_INCR 1024 #define bit_set(m,b) (((uint32_t *)(m))[(b)>>5] |= 1<<((b)&0x1F)) #define bit_clr(m,b) (((uint32_t *)(m))[(b)>>5] &= ~(1<<((b)&0x1F))) #define bit_tst(m,b) (((uint32_t *)(m))[(b)>>5] & (1<<((b)&0x1F))) /****************************************************************************/ /* STATIC VARIABLES : */ /****************************************************************************/ /* Fixed string memory. The DCB fixed strings are stored here */ static char * string_mem = NULL ; static int string_reserved = 0; /* Last fixed string */ static char ** string_ptr = NULL ; /* Pointers to each string's text. Every string is allocated using strdup() or malloc(). A pointer of a unused slot is 0. Exception: "fixed" strings are stored in a separate memory block and should not be freed */ static uint32_t * string_uct = NULL ; /* Usage count for each string. An unused slot has a count of 0 */ static uint32_t * string_bmp = NULL ; /* Bitmap for speed up string creation, and reused freed slots */ static int string_allocated = 0 ; /* How many string slots are available in the ptr, uct and dontfree arrays */ static int string_bmp_start = 0 ; /* Offset of assignable string for reused (32bits each one) */ static int string_last_id = 1 ; /* How many strings slots are used. This is only the bigger id in use + 1. There may be unused slots in this many positions */ /* --------------------------------------------------------------------------- */ void _string_ptoa( char *t, void * ptr ) { unsigned char c ; int p = ( int ) ptr; c = ((( p ) & 0xf0000000 ) >> 28 ); *t++ = ( c > 9 ? '7' : '0' ) + c; /* '7' + 10 = 'A' */ c = ((( p ) & 0x0f000000 ) >> 24 ); *t++ = ( c > 9 ? '7' : '0' ) + c; /* '7' + 10 = 'A' */ c = ((( p ) & 0x00f00000 ) >> 20 ); *t++ = ( c > 9 ? '7' : '0' ) + c; /* '7' + 10 = 'A' */ c = ((( p ) & 0x000f0000 ) >> 16 ); *t++ = ( c > 9 ? '7' : '0' ) + c; /* '7' + 10 = 'A' */ c = ((( p ) & 0x0000f000 ) >> 12 ); *t++ = ( c > 9 ? '7' : '0' ) + c; /* '7' + 10 = 'A' */ c = ((( p ) & 0x00000f00 ) >> 8 ); *t++ = ( c > 9 ? '7' : '0' ) + c; /* '7' + 10 = 'A' */ c = ((( p ) & 0x000000f0 ) >> 4 ); *t++ = ( c > 9 ? '7' : '0' ) + c; /* '7' + 10 = 'A' */ c = ( p ) & 0x0000000f; *t++ = ( c > 9 ? '7' : '0' ) + c; /* '7' + 10 = 'A' */ *t = '\0'; } /* --------------------------------------------------------------------------- */ void _string_ntoa( char *p, unsigned long n ) { char * i = p ; p += 10; if (( long ) n < 0 ) { * i++ = '-'; p++ ; n = ( unsigned long )( -( long )n ) ; } * p = '\0'; do { * --p = '0' + ( n % 10 ); } while ( n /= 10 ) ; if ( p > i ) while (( *i++ = *p++ ) ) ; } /* --------------------------------------------------------------------------- */ void _string_utoa( char *p, unsigned long n ) { char * i = p ; p += 10; * p = '\0'; do { * --p = '0' + ( n % 10 ); } while ( n /= 10 ) ; if ( p > i ) while (( *i++ = *p++ ) ) ; } /* --------------------------------------------------------------------------- */ /****************************************************************************/ /* FUNCTION : string_alloc */ /****************************************************************************/ /* int bytes: how many new strings we could need */ /****************************************************************************/ /* Increase the size of the internal string arrays. This limits how many */ /* strings you can have in memory at the same time, and this should be */ /* called when every identifier slot available is already used. */ /****************************************************************************/ static void string_alloc( int count ) { int lim = ( string_allocated >> 5 ) ; count = (( count >> 5 ) + 1 ) << 5 ; string_allocated += count ; string_ptr = ( char ** ) realloc( string_ptr, string_allocated * sizeof( char * ) ) ; string_uct = ( uint32_t * ) realloc( string_uct, string_allocated * sizeof( uint32_t ) ) ; string_bmp = ( uint32_t * ) realloc( string_bmp, ( string_allocated >> 5 ) * sizeof( uint32_t ) ); if ( !string_ptr || !string_uct || !string_bmp ) { fprintf( stderr, "ERROR: Runtime error - string_alloc: out of memory\n" ) ; exit( 0 ); } memset( &string_bmp[ lim ], '\0', ( count >> 5 ) * sizeof ( uint32_t ) ); } /****************************************************************************/ /* FUNCTION : string_init */ /****************************************************************************/ /* Allocate memory for the dynamic arrays. You should call this function */ /* before anything else in this file. There is enough space for about */ /* BLOCK_INCR short strings, that should be enough for simple programs. */ /* More space is allocated as needed. */ /****************************************************************************/ void string_init() { string_alloc( BLOCK_INCR ); /* Create an empty string with ID 0 */ string_last_id = 0; string_reserved = 0; string_bmp_start = 0; } /****************************************************************************/ /* FUNCTION : string_dump */ /****************************************************************************/ /* Shows all the strings in memory in the console, including the reference */ /* count (usage count) of each string. */ /****************************************************************************/ void string_dump( void ( *wlog )( const char *fmt, ... ) ) { int i ; int used = 0; if ( wlog ) wlog( "[STRING] ---- Dumping MaxID=%d strings ----\n", string_allocated ) ; else printf( "[STRING] ---- Dumping MaxID=%d strings ----\n", string_allocated ) ; for ( i = 0; i < string_allocated; i++ ) { if ( string_ptr[i] ) { if ( !string_uct[i] ) { if ( i >= string_reserved ) { free( string_ptr[i] ) ; string_ptr[i] = NULL ; bit_clr( string_bmp, i ); } continue ; } used++; if ( wlog ) wlog( "[STRING] %4d [%4d]%s: {%s}\n", i, string_uct[i], ( i >= string_reserved ) ? "" : " STATIC", string_ptr[i] ) ; else printf( "[STRING] %4d [%4d]%s: {%s}\n", i, string_uct[i], ( i >= string_reserved ) ? "" : " STATIC", string_ptr[i] ) ; } else { continue ; } } if ( wlog ) wlog( "[STRING] ---- Dumping Used=%d End ----\n", used ) ; else printf( "[STRING] ---- Dumping Used=%d End ----\n", used ) ; } /****************************************************************************/ /* FUNCTION : string_get */ /****************************************************************************/ /* int code: identifier of the string you want */ /****************************************************************************/ /* Returns the contens of an string. Beware: this pointer with only be */ /* valid while no other string function is called. */ /****************************************************************************/ const char * string_get( int code ) { assert( code < string_allocated && code >= 0 ) ; return string_ptr[code] ; } /****************************************************************************/ /* FUNCTION : string_load */ /****************************************************************************/ /* file * fp: the DCB file (must be opened) */ /* */ /* This function uses the global "dcb" struct. It should be already filled. */ /****************************************************************************/ /* Loads the string portion of a DCB file. This includes an area with all */ /* the text (that will be stored in the string_mem pointer) and an array of */ /* the offsets of every string. This function fills the internal arrayswith */ /* all this data and allocates memory if needed. */ /****************************************************************************/ void string_load( void * fp, int ostroffs, int ostrdata, int nstrings, int totalsize ) { uint32_t * string_offset; int n; string_mem = malloc( totalsize ); assert( string_mem ); string_offset = ( uint32_t * ) malloc( sizeof( uint32_t ) * nstrings ) ; assert( string_offset ); file_seek(( file * )fp, ostroffs, SEEK_SET ) ; file_readUint32A(( file * )fp, string_offset, nstrings ) ; if ( string_last_id + nstrings > string_allocated ) string_alloc((( string_last_id + nstrings - string_allocated ) / BLOCK_INCR + 1 ) * BLOCK_INCR ) ; file_seek(( file * )fp, ostrdata, SEEK_SET ) ; file_read(( file * )fp, string_mem, totalsize ) ; for ( n = 0 ; n < nstrings ; n++ ) { string_ptr[string_last_id + n] = string_mem + string_offset[n] ; string_uct[string_last_id + n] = 0 ; bit_set( string_bmp, string_last_id + n ); } string_last_id += nstrings ; string_last_id = ( string_last_id + 32 ) & ~0x1F; string_reserved = string_last_id ; string_bmp_start = string_last_id >> 5; free( string_offset ) ; } /****************************************************************************/ /* FUNCTION : string_use */ /****************************************************************************/ /* int code: identifier of the string you are using */ /****************************************************************************/ /* Increase the usage counter of an string. Use this when you store the */ /* identifier of the string somewhere. */ /****************************************************************************/ void string_use( int code ) { string_uct[code]++ ; } /****************************************************************************/ /* FUNCTION : string_discard */ /****************************************************************************/ /* int code: identifier of the string you don't need anymore */ /****************************************************************************/ /* Decrease the usage counter of an string. Use this when you retrieve the */ /* identifier of the string and discard it, or some memory (like private */ /* variables) containing the string identifier is destroyed. If the usage */ /* count is decreased to zero, the string will be discarted, and the */ /* identifier may be used in the future by other string. */ /****************************************************************************/ void string_discard( int code ) { if ( code < 0 || code > string_allocated || !string_ptr[code] ) return; if ( !string_uct[code] ) return ; string_uct[code]-- ; if ( !string_uct[code] ) { if ( code >= string_reserved ) { free( string_ptr[code] ) ; string_ptr[code] = NULL ; bit_clr( string_bmp, code ); } } } /****************************************************************************/ /* FUNCTION : string_getid */ /****************************************************************************/ /* Searchs for an available ID and returns it. If none available, more space*/ /* is allocated for the new string. This is used for new strings only. */ /****************************************************************************/ static int string_getid() { int n, nb, lim, ini ; /* Si tengo suficientes alocados, retorno el siguiente segun string_last_id */ if ( string_last_id < string_allocated ) { if ( !bit_tst( string_bmp, string_last_id ) ) { bit_set( string_bmp, string_last_id ); return string_last_id++ ; } } /* Ya no tengo mas espacio, entonces busco alguno libre entre ~+32 desde el ultimo fijo y ~-32 del ultimo asignado */ ini = ( string_last_id < string_allocated ) ? ( string_last_id >> 5 ) : string_reserved ; lim = ( string_allocated >> 5 ) ; while ( 1 ) { for ( n = ini; n < lim ; n++ ) { if ( string_bmp[n] != ( uint32_t ) 0xFFFFFFFF ) /* Aca hay 1 libre, busco cual es */ { for ( nb = 0; nb < 32; nb++ ) { if ( !bit_tst( string_bmp + n, nb ) ) { string_last_id = ( n << 5 ) + nb ; bit_set( string_bmp, string_last_id ); return string_last_id++ ; } } } } if ( ini == string_reserved ) break; lim = ini; ini = string_reserved; } string_last_id = string_allocated ; /* Incremento espacio, no habia libres */ string_alloc( BLOCK_INCR ) ; assert( !bit_tst( string_bmp, string_last_id ) ); /* Devuelvo string_last_id e incremento en 1, ya que ahora tengo BLOCK_INCR mas que antes */ bit_set( string_bmp, string_last_id ); return string_last_id++ ; } /****************************************************************************/ /* FUNCTION : string_new */ /****************************************************************************/ /* Create a new string. It returns its ID. Note that it uses strdup() */ /* TODO: do something if no memory available */ /****************************************************************************/ int string_new( const char * ptr ) { char * str = strdup( ptr ) ; int id ; assert( str ) ; id = string_getid() ; string_ptr[id] = str ; string_uct[id] = 0 ; return id ; } /* * FUNCTION : string_newa * * Create a new string from a text buffer section * * PARAMS: * ptr Pointer to the text buffer at start position * count Number of characters * * RETURN VALUE: * ID of the new string */ int string_newa( const char * ptr, unsigned count ) { char * str = malloc( count + 1 ); int id ; assert( str ) ; id = string_getid() ; strncpy( str, ptr, count ); str[count] = '\0'; string_ptr[id] = str ; string_uct[id] = 0 ; return id ; } /****************************************************************************/ /* FUNCTION : string_concat */ /****************************************************************************/ /* Add some text to an string and return the resulting string. This does not*/ /* modify the original string, but creates a new one. */ /****************************************************************************/ int string_concat( int code1, char * str2 ) { char * str1 ; int len1, len2; assert( code1 < string_allocated && code1 >= 0 ) ; str1 = string_ptr[code1] ; assert( str1 ) ; len1 = strlen( str1 ) ; len2 = strlen( str2 ) + 1 ; str1 = ( char * ) realloc( str1, len1 + len2 ) ; assert( str1 ) ; memmove( str1 + len1, str2, len2 ) ; string_ptr[code1] = str1 ; return code1 ; } /****************************************************************************/ /* FUNCTION : string_add */ /****************************************************************************/ /* Add an string to another one and return the resulting string. This does */ /* not modify the original string, but creates a new one. */ /****************************************************************************/ int string_add( int code1, int code2 ) { const char * str1 = string_get( code1 ) ; const char * str2 = string_get( code2 ) ; char * str3 ; int id ; int len1, len2; assert( str1 ) ; assert( str2 ) ; len1 = strlen( str1 ) ; len2 = strlen( str2 ) + 1; str3 = ( char * ) malloc( len1 + len2 ) ; assert( str3 ) ; memmove( str3, str1, len1 ) ; memmove( str3 + len1, str2, len2 ) ; id = string_getid() ; string_ptr[id] = str3 ; string_uct[id] = 0 ; return id ; } /****************************************************************************/ /* FUNCTION : string_ptoa */ /****************************************************************************/ /* Convert a pointer to a new created string and return its ID. */ /****************************************************************************/ int string_ptoa( void * n ) { char * str ; int id ; str = ( char * ) malloc( 10 ) ; assert( str ) ; _string_ptoa( str, n ) ; id = string_getid() ; string_ptr[id] = str ; string_uct[id] = 0 ; return id ; } /****************************************************************************/ /* FUNCTION : string_ftoa */ /****************************************************************************/ /* Convert a float to a new created string and return its ID. */ /****************************************************************************/ int string_ftoa( float n ) { char * str = ( char * ) malloc( 32 ), * ptr = str; int id ; assert( str ) ; ptr += sprintf( str, "%f", n ) - 1; while ( ptr >= str ) { if ( *ptr != '0' ) break ; *ptr-- = 0 ; } if ( ptr >= str && *ptr == '.' ) *ptr = 0 ; if ( *str == 0 ) { *str = '0'; *( str + 1 ) = '\0'; } id = string_getid() ; string_ptr[id] = str ; string_uct[id] = 0 ; return id ; } /****************************************************************************/ /* FUNCTION : string_itoa */ /****************************************************************************/ /* Convert an integer to a new created string and return its ID. */ /****************************************************************************/ int string_itoa( int n ) { char * str ; int id ; str = ( char * ) malloc( 16 ) ; assert( str ) ; _string_ntoa( str, n ) ; id = string_getid() ; string_ptr[id] = str ; string_uct[id] = 0 ; return id ; } /****************************************************************************/ /* FUNCTION : string_uitoa */ /****************************************************************************/ /* Convert an unsigned integer to a new created string and return its ID. */ /****************************************************************************/ int string_uitoa( unsigned int n ) { char * str ; int id ; str = ( char * ) malloc( 16 ) ; assert( str ) ; _string_utoa( str, n ) ; id = string_getid() ; string_ptr[id] = str ; string_uct[id] = 0 ; return id ; } /****************************************************************************/ /* FUNCTION : string_comp */ /****************************************************************************/ /* Compare two strings using strcmp and return the result */ /****************************************************************************/ int string_comp( int code1, int code2 ) { const char * str1 = string_get( code1 ) ; const char * str2 = string_get( code2 ) ; return strcmp( str1, str2 ) ; } /****************************************************************************/ /* FUNCTION : string_char */ /****************************************************************************/ /* Extract a character from a string. The parameter nchar can be: */ /* - 0 or positive: return this character from the left (0 = leftmost) */ /* - negative: return this character from the right (-1 = rightmost) */ /* The result is not the ASCII value, but the identifier of a new string */ /* that is create in the process and contains only the extracted character */ /****************************************************************************/ int string_char( int n, int nchar ) { const char * str = string_get( n ) ; assert( str ) ; if ( nchar < 0 ) { nchar = strlen( str ) + nchar ; if ( nchar < 0 ) return 0 ; } return str[nchar] ; } /****************************************************************************/ /* FUNCTION : string_substr */ /****************************************************************************/ /* Extract a substring from a string. The parameters can be: */ /* - 0 or positive: count this character from the left (0 = leftmost) */ /* - negative: count this character from the right (-1 = rightmost) */ /* */ /* NO MORE: If first > last, the two values are swapped before returning the result */ /****************************************************************************/ int string_substr( int code, int first, int len ) { const char * str = string_get( code ) ; char * ptr ; int rlen, n ; assert( str ) ; rlen = strlen( str ) ; if ( first < 0 ) { first = rlen + first ; if ( first < 0 ) return string_new( "" ) ; } else if ( first > ( rlen - 1 ) ) return string_new( "" ) ; if ( len < 0 ) { len = rlen + ( len + 2 ) - first - 1 ; if ( len < 1 ) return string_new( "" ) ; } if (( first + len ) > rlen ) len = ( rlen - first ) ; ptr = ( char * )malloc( len + 1 ) ; memcpy( ptr, str + first, len ) ; ptr[len] = '\0' ; n = string_getid() ; string_ptr[n] = ptr ; string_uct[n] = 0 ; return n ; } /* * FUNCTION : string_find * * Find a substring. Returns the position of the leftmost character (0 * for the leftmost position) or -1 if the string was not found. * * PARAMS: * code1 Code of the string * code2 Code of the substring * first Character to start the search * (negative to search backwards) * * RETURN VALUE: * Result of the comparison */ int string_find( int code1, int code2, int first ) { char * str1 = ( char * ) string_get( code1 ) ; char * str2 = ( char * ) string_get( code2 ) ; char * p = str1, * p1, * p2 ; assert( str1 && str2 ) ; if ( first < 0 ) { first += strlen( str1 ) ; if ( first < 0 ) return -1; str1 += first; } else { /* Avoid use strlen */ while ( first-- && *str1 ) str1++; if ( !*str1 ) return -1; } while ( *str1 ) { if ( *str1 == *str2 ) { p1 = str1 + 1; p2 = str2 + 1; while ( *p1 && *p2 && *p1 == *p2 ) { p1++; p2++; } if ( !*p2 ) return str1 - p; } str1++; } return -1 ; } /* * FUNCTION : string_ucase * * Convert an string to upper case. It does not alter the given string, but * creates a new string in the correct case and returns its id. * * PARAMS: * code Internal code of original string * * RETURN VALUE: * Code of the resulting string */ int string_ucase( int code ) { const char * str = string_get( code ) ; char * base, * ptr ; int id ; assert( str ) ; base = ( char * )malloc( strlen( str ) + 1 ) ; assert( base ) ; for ( ptr = base; *str ; ptr++, str++ ) *ptr = TOUPPER( *str ) ; ptr[0] = '\0' ; id = string_getid() ; string_ptr[id] = base ; string_uct[id] = 0 ; return id ; } /* * FUNCTION : string_lcase * * Convert an string to lower case. It does not alter the given string, but * creates a new string in the correct case and returns its id. * * PARAMS: * code Internal code of original string * * RETURN VALUE: * Code of the resulting string */ int string_lcase( int code ) { const char * str = string_get( code ) ; char * base, * ptr ; int id ; assert( str ) ; base = ( char * )malloc( strlen( str ) + 1 ) ; assert( base ) ; for ( ptr = base; *str ; ptr++, str++ ) *ptr = TOLOWER( *str ) ; ptr[0] = '\0' ; id = string_getid() ; string_ptr[id] = base ; string_uct[id] = 0 ; return id ; } /* * FUNCTION : string_strip * * Create a copy of a string, without any leading or ending blanks * * PARAMS: * code Internal code of original string * * RETURN VALUE: * Code of the resulting string */ int string_strip( int code ) { const char * str = string_get( code ) ; char * base, * ptr; int id = string_new( str ); ptr = base = ( char * )string_get( id ) ; assert( ptr ); while ( *str == ' ' || *str == '\n' || *str == '\r' || *str == '\t' ) str++; while ( *str ) *ptr++ = *str++; while ( ptr > base && ( ptr[-1] == ' ' || ptr[-1] == '\n' || ptr[-1] == '\r' || ptr[-1] == '\t' ) ) ptr--; *ptr = '\0'; return id ; } /* * FUNCTION : string_format * * Format a number using the given characters * * PARAMS: * number Number to format * spec Format specification * * RETURN VALUE: * Code of the resulting string */ int string_format( double number, int dec, char point, char thousands ) { char * str = malloc( 128 ); char * s = str, * t, * p = NULL; int c, id, neg ; assert( str ); if ( dec == -1 ) s += sprintf( str, "%f", number ); else s += sprintf( str, "%.*f", dec, number ); neg = (*str == '-') ? 1 : 0; if ( dec ) { p = str; while ( *p && *p != '.' ) p++; if ( *p ) *p = point; } else { p = s; } /* p = where decimal point is */ /* s = where '\0' is */ if ( thousands ) t = s + (p - (str + neg) - 1 ) / 3; else t = s; c = 0; while ( s >= str ) { if ( isdigit( *s ) && s < p ) { if ( c == 3 ) { *t-- = thousands ; c = 0; continue; } else c++; } *t-- = *s-- ; } id = string_getid() ; string_ptr[id] = str ; string_uct[id] = 0 ; return id; } /* * FUNCTION : string_casecmp * * Compare two strings (case-insensitive version) * * PARAMS: * code1 Code of the first string * code2 Code of the second string * * RETURN VALUE: * Result of the comparison */ int string_casecmp( int code1, int code2 ) { unsigned char * str1 = ( unsigned char * ) string_get( code1 ) ; unsigned char * str2 = ( unsigned char * ) string_get( code2 ) ; while ( *str1 || *str2 ) { if ( TOUPPER( *str1 ) != TOUPPER( *str2 ) ) return TOUPPER( *str1 ) - TOUPPER( *str2 ); str1++; str2++; } return 0 ; } /* * FUNCTION : string_pad * * Add spaces to the left or right of a string * * PARAMS: * code Code of the string * total Total length of the final string * align 0 = align to the right; 1 = align to the left * * RETURN VALUE: * Result of the comparison */ int string_pad( int code, int total, int align ) { const char * ptr = string_get( code ); int len; int spaces = 0; int id; char * str; assert( ptr ); len = strlen( ptr ); if ( len < total ) spaces = total - len; if ( !spaces ) return string_new( ptr ) ; str = malloc( total + 1 ); assert( str ); if ( !align ) { memset( str, ' ', spaces ); strcpy( str + spaces, ptr ) ; } else { strcpy( str, ptr ) ; memset( str + len, ' ', spaces ) ; str[total] = '\0'; } id = string_getid() ; string_ptr[id] = str ; string_uct[id] = 0 ; return id ; }