#include "global.h" #include "RageUtil.h" #include "RageMath.h" #include "RageLog.h" #include "RageFile.h" #include "Foreach.h" #include "LocalizedString.h" #include "LuaBinding.h" #include "LuaManager.h" #include #include #include #include #include #include #include #include bool HexToBinary(const RString&, RString&); void utf8_sanitize(RString &); void UnicodeUpperLower(wchar_t *, size_t, const unsigned char *); RandomGen g_RandomNumberGenerator; MersenneTwister::MersenneTwister( int iSeed ) : m_iNext(0) { Reset( iSeed ); } void MersenneTwister::Reset( int iSeed ) { if( iSeed == 0 ) iSeed = time(NULL); m_Values[0] = iSeed; m_iNext = 0; for( int i = 1; i < 624; ++i ) m_Values[i] = ((69069 * m_Values[i-1]) + 1) & 0xFFFFFFFF; GenerateValues(); } void MersenneTwister::GenerateValues() { static const unsigned mask[] = { 0, 0x9908B0DF }; for( int i = 0; i < 227; ++i ) { int iVal = (m_Values[i] & 0x80000000) | (m_Values[i+1] & 0x7FFFFFFF); int iNext = (i + 397); m_Values[i] = m_Values[iNext]; m_Values[i] ^= (iVal >> 1); m_Values[i] ^= mask[iVal&1]; } for( int i = 227; i < 623; ++i ) { int iVal = (m_Values[i] & 0x80000000) | (m_Values[i+1] & 0x7FFFFFFF); int iNext = (i + 397) - 624; m_Values[i] = m_Values[iNext]; m_Values[i] ^= (iVal >> 1); m_Values[i] ^= mask[iVal&1]; } int iVal = (m_Values[623] & 0x80000000) + (m_Values[0] & 0x7FFFFFFF); int iNext = (623 + 397) - 624; m_Values[623] = m_Values[iNext] ^ (iVal>>1); m_Values[623] ^= mask[iVal&1]; } int MersenneTwister::Temper( int iVal ) { iVal ^= (iVal >> 11); iVal ^= (iVal << 7) & 0x9D2C5680; iVal ^= (iVal << 15) & 0xEFC60000; iVal ^= (iVal >> 18); return iVal; } int MersenneTwister::operator()() { if( m_iNext == 624 ) { m_iNext = 0; GenerateValues(); } return Temper( m_Values[m_iNext++] ); } /* Extend MersenneTwister into Lua space. This is intended to replace * math.randomseed and math.random, so we conform to their behavior. */ namespace { MersenneTwister g_LuaPRNG; /* To map from [0..2^31-1] to [0..1), we divide by 2^31. */ const double DIVISOR = pow( double(2), double(31) ); static int Seed( lua_State *L ) { g_LuaPRNG.Reset( IArg(1) ); return 0; } static int Random( lua_State *L ) { switch( lua_gettop(L) ) { /* [0..1) */ case 0: { double r = double(g_LuaPRNG()) / DIVISOR; lua_pushnumber( L, r ); return 1; } /* [1..u] */ case 1: { int upper = IArg(1); luaL_argcheck( L, 1 <= upper, 1, "interval is empty" ); lua_pushnumber( L, g_LuaPRNG(upper) + 1 ); return 1; } /* [l..u] */ case 2: { int lower = IArg(1); int upper = IArg(2); luaL_argcheck( L, lower < upper, 2, "interval is empty" ); lua_pushnumber( L, (int(g_LuaPRNG()) % (upper-lower+1)) + lower ); return 1; } /* wrong amount of arguments */ default: { return luaL_error( L, "wrong number of arguments" ); } } } const luaL_Reg MersenneTwisterTable[] = { LIST_METHOD( Seed ), LIST_METHOD( Random ), { NULL, NULL } }; } LUA_REGISTER_NAMESPACE( MersenneTwister ); void fapproach( float& val, float other_val, float to_move ) { ASSERT_M( to_move >= 0, ssprintf("to_move: %f < 0", to_move) ); if( val == other_val ) return; float fDelta = other_val - val; float fSign = fDelta / fabsf( fDelta ); float fToMove = fSign*to_move; if( fabsf(fToMove) > fabsf(fDelta) ) fToMove = fDelta; // snap val += fToMove; } /* Return a positive x mod y. */ float fmodfp(float x, float y) { x = fmodf(x, y); /* x is [-y,y] */ x += y; /* x is [0,y*2] */ x = fmodf(x, y); /* x is [0,y] */ return x; } int power_of_two( int iInput ) { int iValue = 1; while( iValue < iInput ) iValue <<= 1; return iValue; } bool IsAnInt( const RString &s ) { if( !s.size() ) return false; for( size_t i=0; i < s.size(); ++i ) if( s[i] < '0' || s[i] > '9' ) return false; return true; } bool IsHexVal( const RString &s ) { if( !s.size() ) return false; for( size_t i=0; i < s.size(); ++i ) if( !(s[i] >= '0' && s[i] <= '9') && !(toupper(s[i]) >= 'A' && toupper(s[i]) <= 'F')) return false; return true; } RString BinaryToHex( const void *pData_, int iNumBytes ) { const unsigned char *pData = (const unsigned char *) pData_; RString s; for( int i=0; i arrayBits; split( sHHMMSS, ":", arrayBits, false ); while( arrayBits.size() < 3 ) arrayBits.insert(arrayBits.begin(), "0" ); // pad missing bits float fSeconds = 0; fSeconds += StringToInt( arrayBits[0] ) * 60 * 60; fSeconds += StringToInt( arrayBits[1] ) * 60; fSeconds += StringToFloat( arrayBits[2] ); return fSeconds; } RString SecondsToHHMMSS( float fSecs ) { const int iMinsDisplay = (int)fSecs/60; const int iSecsDisplay = (int)fSecs - iMinsDisplay*60; RString sReturn = ssprintf( "%02d:%02d:%02d", iMinsDisplay/60, iMinsDisplay%60, iSecsDisplay ); return sReturn; } RString SecondsToMMSSMsMs( float fSecs ) { const int iMinsDisplay = (int)fSecs/60; const int iSecsDisplay = (int)fSecs - iMinsDisplay*60; const int iLeftoverDisplay = (int) ( (fSecs - iMinsDisplay*60 - iSecsDisplay) * 100 ); RString sReturn = ssprintf( "%02d:%02d.%02d", iMinsDisplay, iSecsDisplay, min(99,iLeftoverDisplay) ); return sReturn; } RString SecondsToMSSMsMs( float fSecs ) { const int iMinsDisplay = (int)fSecs/60; const int iSecsDisplay = (int)fSecs - iMinsDisplay*60; const int iLeftoverDisplay = (int) ( (fSecs - iMinsDisplay*60 - iSecsDisplay) * 100 ); RString sReturn = ssprintf( "%01d:%02d.%02d", iMinsDisplay, iSecsDisplay, min(99,iLeftoverDisplay) ); return sReturn; } RString SecondsToMMSSMsMsMs( float fSecs ) { const int iMinsDisplay = (int)fSecs/60; const int iSecsDisplay = (int)fSecs - iMinsDisplay*60; const int iLeftoverDisplay = (int) ( (fSecs - iMinsDisplay*60 - iSecsDisplay) * 1000 ); RString sReturn = ssprintf( "%02d:%02d.%03d", iMinsDisplay, iSecsDisplay, min(999,iLeftoverDisplay) ); return sReturn; } RString SecondsToMSS( float fSecs ) { const int iMinsDisplay = (int)fSecs/60; const int iSecsDisplay = (int)fSecs - iMinsDisplay*60; RString sReturn = ssprintf( "%01d:%02d", iMinsDisplay, iSecsDisplay); return sReturn; } RString SecondsToMMSS( float fSecs ) { const int iMinsDisplay = (int)fSecs/60; const int iSecsDisplay = (int)fSecs - iMinsDisplay*60; RString sReturn = ssprintf( "%02d:%02d", iMinsDisplay, iSecsDisplay); return sReturn; } RString PrettyPercent( float fNumerator, float fDenominator) { return ssprintf("%0.2f%%",fNumerator/fDenominator*100); } RString Commify( int iNum ) { RString sNum = ssprintf("%d",iNum); return Commify( sNum ); } RString Commify( RString sNum, RString sSeperator ) { RString sReturn; for( unsigned i=0; i= 3 && iPercent > 2 && sStr[iEnd-2] == 'l' && sStr[iEnd-1] == 'l' ) { sRet.append( sStr, iPercent, iEnd - iPercent - 2 ); // % sRet.append( "I64" ); // %I64 sRet.append( sStr, iEnd, 1 ); // %I64i iOffset = iEnd + 1; } else { if( iEnd == sStr.npos ) iEnd = sStr.size() - 1; sRet.append( sStr, iOffset, iEnd - iOffset + 1 ); iOffset = iEnd + 1; } } return sRet; } #else RString ConvertI64FormatString( const RString &sStr ) { return sStr; } #endif /* ISO-639-1 codes: http://www.loc.gov/standards/iso639-2/php/code_list.php * native forms: http://people.w3.org/rishida/names/languages.html * We don't use 3-letter codes, so we don't bother supporting them. */ static const LanguageInfo g_langs[] = { {"aa", "Afar"}, {"ab", "Abkhazian"}, {"af", "Afrikaans"}, {"am", "Amharic"}, {"ar", "Arabic"}, {"as", "Assamese"}, {"ay", "Aymara"}, {"az", "Azerbaijani"}, {"ba", "Bashkir"}, {"be", "Byelorussian"}, {"bg", "Bulgarian"}, {"bh", "Bihari"}, {"bi", "Bislama"}, {"bn", "Bengali"}, {"bo", "Tibetan"}, {"br", "Breton"}, {"ca", "Catalan"}, {"co", "Corsican"}, {"cs", "Czech"}, {"cy", "Welsh"}, {"da", "Danish"}, {"de", "German"}, {"dz", "Bhutani"}, {"el", "Greek"}, {"en", "English"}, {"eo", "Esperanto"}, {"es", "Spanish"}, {"et", "Estonian"}, {"eu", "Basque"}, {"fa", "Persian"}, {"fi", "Finnish"}, {"fj", "Fiji"}, {"fo", "Faeroese"}, {"fr", "French"}, {"fy", "Frisian"}, {"ga", "Irish"}, {"gd", "Gaelic"}, {"gl", "Galician"}, {"gn", "Guarani"}, {"gu", "Gujarati"}, {"ha", "Hausa"}, {"he", "Hebrew"}, {"hi", "Hindi"}, {"hr", "Croatian"}, {"hu", "Hungarian"}, {"hy", "Armenian"}, {"ia", "Interlingua"}, {"id", "Indonesian"}, {"ie", "Interlingue"}, {"ik", "Inupiak"}, {"in", "Indonesian"}, // compatibility {"is", "Icelandic"}, {"it", "Italian"}, {"iw", "Hebrew"}, // compatibility {"ja", "Japanese"}, {"ji", "Yiddish"}, // compatibility {"jw", "Javanese"}, {"ka", "Georgian"}, {"kk", "Kazakh"}, {"kl", "Greenlandic"}, {"km", "Cambodian"}, {"kn", "Kannada"}, {"ko", "Korean"}, {"ks", "Kashmiri"}, {"ku", "Kurdish"}, {"ky", "Kirghiz"}, {"la", "Latin"}, {"ln", "Lingala"}, {"lo", "Laothian"}, {"lt", "Lithuanian"}, {"lv", "Latvian"}, {"mg", "Malagasy"}, {"mi", "Maori"}, {"mk", "Macedonian"}, {"ml", "Malayalam"}, {"mn", "Mongolian"}, {"mo", "Moldavian"}, {"mr", "Marathi"}, {"ms", "Malay"}, {"mt", "Maltese"}, {"my", "Burmese"}, {"na", "Nauru"}, {"ne", "Nepali"}, {"nl", "Dutch"}, {"no", "Norwegian"}, {"oc", "Occitan"}, {"om", "Oromo"}, {"or", "Oriya"}, {"pa", "Punjabi"}, {"pl", "Polish"}, {"ps", "Pashto"}, {"pt", "Portuguese"}, {"qu", "Quechua"}, {"rm", "Rhaeto-Romance"}, {"rn", "Kirundi"}, {"ro", "Romanian"}, {"ru", "Russian"}, {"rw", "Kinyarwanda"}, {"sa", "Sanskrit"}, {"sd", "Sindhi"}, {"sg", "Sangro"}, {"sh", "Serbo-Croatian"}, {"si", "Singhalese"}, {"sk", "Slovak"}, {"sl", "Slovenian"}, {"sm", "Samoan"}, {"sn", "Shona"}, {"so", "Somali"}, {"sq", "Albanian"}, {"sr", "Serbian"}, {"ss", "Siswati"}, {"st", "Sesotho"}, {"su", "Sudanese"}, {"sv", "Swedish"}, {"sw", "Swahili"}, {"ta", "Tamil"}, {"te", "Tegulu"}, {"tg", "Tajik"}, {"th", "Thai"}, {"ti", "Tigrinya"}, {"tk", "Turkmen"}, {"tl", "Tagalog"}, {"tn", "Setswana"}, {"to", "Tonga"}, {"tr", "Turkish"}, {"ts", "Tsonga"}, {"tt", "Tatar"}, {"tw", "Twi"}, {"uk", "Ukrainian"}, {"ur", "Urdu"}, {"uz", "Uzbek"}, {"vi", "Vietnamese"}, {"vo", "Volapuk"}, {"wo", "Wolof"}, {"xh", "Xhosa"}, {"yi", "Yiddish"}, {"yo", "Yoruba"}, {"zh", "Chinese"}, {"zu", "Zulu"}, }; void GetLanguageInfos( vector &vAddTo ) { for( unsigned i=0; i &sSource) { if( sSource.empty() ) return RString(); RString sTmp; for( unsigned iNum = 0; iNum < sSource.size()-1; iNum++ ) { sTmp += sSource[iNum]; sTmp += sDeliminator; } sTmp += sSource.back(); return sTmp; } RString join( const RString &sDelimitor, vector::const_iterator begin, vector::const_iterator end ) { if( begin == end ) return RString(); RString sRet; while( begin != end ) { sRet += *begin; ++begin; if( begin != end ) sRet += sDelimitor; } return sRet; } RString SmEscape( const RString &sUnescaped ) { return SmEscape( sUnescaped.c_str(), sUnescaped.size() ); } RString SmEscape( const char *cUnescaped, int len ) { RString answer = ""; for( int i = 0; i < len; ++i ) { // Other characters we could theoretically escape: // NotesWriterSM.cpp used to claim ',' should be escaped, but there was no explanation why // '#' is both a control character and a valid part of a parameter. The only way for there to be // any confusion is in a misformatted .sm file, though, so it is unnecessary to escape it. if( cUnescaped[i] == '/' && i + 1 < len && cUnescaped[i + 1] == '/' ) { answer += "\\/\\/"; ++i; // increment here so we skip both //s continue; } if( cUnescaped[i] == '\\' || cUnescaped[i] == ':' || cUnescaped[i] == ';' ) answer += "\\"; answer += cUnescaped[i]; } return answer; } RString DwiEscape( const RString &sUnescaped ) { return DwiEscape( sUnescaped.c_str(), sUnescaped.size() ); } RString DwiEscape( const char *cUnescaped, int len ) { RString answer = ""; for( int i = 0; i < len; ++i ) { switch( cUnescaped[i] ) { // TODO: Which of these characters actually affect DWI? case '\\': case ':': case ';': answer += '|'; break; case '[': answer += '('; break; case ']': answer += ')'; break; default: answer += cUnescaped[i]; } } return answer; } template static int DelimitorLength( const S &Delimitor ) { return Delimitor.size(); } static int DelimitorLength( char Delimitor ) { return 1; } static int DelimitorLength( wchar_t Delimitor ) { return 1; } template void do_split( const S &Source, const C Delimitor, vector &AddIt, const bool bIgnoreEmpty ) { /* Short-circuit if the source is empty; we want to return an empty vector if * the string is empty, even if bIgnoreEmpty is true. */ if( Source.empty() ) return; size_t startpos = 0; do { size_t pos; pos = Source.find( Delimitor, startpos ); if( pos == Source.npos ) pos = Source.size(); if( pos-startpos > 0 || !bIgnoreEmpty ) { /* Optimization: if we're copying the whole string, avoid substr; this * allows this copy to be refcounted, which is much faster. */ if( startpos == 0 && pos-startpos == Source.size() ) AddIt.push_back(Source); else { const S AddRString = Source.substr(startpos, pos-startpos); AddIt.push_back(AddRString); } } startpos = pos+DelimitorLength(Delimitor); } while ( startpos <= Source.size() ); } void split( const RString &sSource, const RString &sDelimitor, vector &asAddIt, const bool bIgnoreEmpty ) { if( sDelimitor.size() == 1 ) do_split( sSource, sDelimitor[0], asAddIt, bIgnoreEmpty ); else do_split( sSource, sDelimitor, asAddIt, bIgnoreEmpty ); } void split( const wstring &sSource, const wstring &sDelimitor, vector &asAddIt, const bool bIgnoreEmpty ) { if( sDelimitor.size() == 1 ) do_split( sSource, sDelimitor[0], asAddIt, bIgnoreEmpty ); else do_split( sSource, sDelimitor, asAddIt, bIgnoreEmpty ); } /* Use: RString str="a,b,c"; int start = 0, size = -1; while( 1 ) { do_split( str, ",", start, size ); if( start == str.size() ) break; str[start] = 'Q'; } */ template void do_split( const S &Source, const S &Delimitor, int &begin, int &size, int len, const bool bIgnoreEmpty ) { if( size != -1 ) { // Start points to the beginning of the last delimiter. Move it up. begin += size+Delimitor.size(); begin = min( begin, len ); } size = 0; if( bIgnoreEmpty ) { // Skip delims. while( begin + Delimitor.size() < Source.size() && !Source.compare( begin, Delimitor.size(), Delimitor ) ) ++begin; } /* Where's the string function to find within a substring? * C++ strings apparently are missing that ... */ size_t pos; if( Delimitor.size() == 1 ) pos = Source.find( Delimitor[0], begin ); else pos = Source.find( Delimitor, begin ); if( pos == Source.npos || (int) pos > len ) pos = len; size = pos - begin; } void split( const RString &Source, const RString &Delimitor, int &begin, int &size, int len, const bool bIgnoreEmpty ) { do_split( Source, Delimitor, begin, size, len, bIgnoreEmpty ); } void split( const wstring &Source, const wstring &Delimitor, int &begin, int &size, int len, const bool bIgnoreEmpty ) { do_split( Source, Delimitor, begin, size, len, bIgnoreEmpty ); } void split( const RString &Source, const RString &Delimitor, int &begin, int &size, const bool bIgnoreEmpty ) { do_split( Source, Delimitor, begin, size, Source.size(), bIgnoreEmpty ); } void split( const wstring &Source, const wstring &Delimitor, int &begin, int &size, const bool bIgnoreEmpty ) { do_split( Source, Delimitor, begin, size, Source.size(), bIgnoreEmpty ); } /* * foo\fum\ -> "foo\fum\", "", "" * c:\foo\bar.txt -> "c:\foo\", "bar", ".txt" * \\foo\fum -> "\\foo\", "fum", "" */ void splitpath( const RString &sPath, RString &sDir, RString &sFilename, RString &sExt ) { sDir = sFilename = sExt = ""; vector asMatches; /* * One level of escapes for the regex, one for C. Ew. * This is really: * ^(.*[\\/])?(.*)$ */ static Regex sep("^(.*[\\\\/])?(.*)$"); bool bCheck = sep.Compare( sPath, asMatches ); ASSERT( bCheck ); sDir = asMatches[0]; const RString sBase = asMatches[1]; /* ^(.*)(\.[^\.]+)$ */ static Regex SplitExt("^(.*)(\\.[^\\.]+)$"); if( SplitExt.Compare(sBase, asMatches) ) { sFilename = asMatches[0]; sExt = asMatches[1]; } else { sFilename = sBase; } } /* "foo.bar", "baz" -> "foo.baz" * "foo", "baz" -> "foo.baz" * "foo.bar", "" -> "foo" */ RString SetExtension( const RString &sPath, const RString &sExt ) { RString sDir, sFileName, sOldExt; splitpath( sPath, sDir, sFileName, sOldExt ); return sDir + sFileName + (sExt.size()? ".":"") + sExt; } RString GetExtension( const RString &sPath ) { size_t pos = sPath.rfind( '.' ); if( pos == sPath.npos ) return RString(); size_t slash = sPath.find( '/', pos ); if( slash != sPath.npos ) return RString(); /* rare: path/dir.ext/fn */ return sPath.substr( pos+1, sPath.size()-pos+1 ); } RString GetFileNameWithoutExtension( const RString &sPath ) { RString sThrowAway, sFName; splitpath( sPath, sThrowAway, sFName, sThrowAway ); return sFName; } void MakeValidFilename( RString &sName ) { wstring wsName = RStringToWstring( sName ); wstring wsInvalid = L"/\\:*?\"<>|"; for( unsigned i = 0; i < wsName.size(); ++i ) { wchar_t w = wsName[i]; if( w >= 32 && w < 126 && wsInvalid.find_first_of(w) == wsInvalid.npos ) continue; if( w == L'"' ) { wsName[i] = L'\''; continue; } /* We could replace with closest matches in ASCII: convert the character * to UTF-8 NFD (decomposed) (maybe NFKD?), and see if the first * character is ASCII. This is useless for non-Western languages, * since we'll replace the whole filename. */ wsName[i] = '_'; } sName = WStringToRString( wsName ); } int g_argc = 0; char **g_argv = NULL; void SetCommandlineArguments( int argc, char **argv ) { g_argc = argc; g_argv = argv; } void GetCommandLineArguments( int &argc, char **&argv ) { argc = g_argc; argv = g_argv; } /* Search for the commandline argument given; eg. "test" searches for the * option "--test". All commandline arguments are getopt_long style: --foo; * short arguments (-x) are not supported. (These are not intended for * common, general use, so having short options isn't currently needed.) * If argument is non-NULL, accept an argument. */ bool GetCommandlineArgument( const RString &option, RString *argument, int iIndex ) { const RString optstr = "--" + option; for( int arg = 1; arg < g_argc; ++arg ) { const RString CurArgument = g_argv[arg]; const size_t i = CurArgument.find( "=" ); RString CurOption = CurArgument.substr(0,i); if( CurOption.CompareNoCase(optstr) ) continue; // no match // Found it. if( iIndex ) { --iIndex; continue; } if( argument ) { if( i != RString::npos ) *argument = CurArgument.substr( i+1 ); else *argument = ""; } return true; } return false; } RString GetCwd() { char buf[PATH_MAX]; bool ret = getcwd(buf, PATH_MAX) != NULL; ASSERT(ret); return buf; } /* * Calculate a standard CRC32. iCRC should be initialized to 0. * References: * http://www.theorem.com/java/CRC32.java, * http://www.faqs.org/rfcs/rfc1952.html */ void CRC32( unsigned int &iCRC, const void *pVoidBuffer, size_t iSize ) { static unsigned tab[256]; static bool initted = false; if( !initted ) { initted = true; const unsigned POLY = 0xEDB88320; for( int i = 0; i < 256; ++i ) { tab[i] = i; for( int j = 0; j < 8; ++j ) { if( tab[i] & 1 ) tab[i] = (tab[i] >> 1) ^ POLY; else tab[i] >>= 1; } } } iCRC ^= 0xFFFFFFFF; const char *pBuffer = (const char *) pVoidBuffer; for( unsigned i = 0; i < iSize; ++i ) iCRC = (iCRC >> 8) ^ tab[(iCRC ^ pBuffer[i]) & 0xFF]; iCRC ^= 0xFFFFFFFF; } unsigned int GetHashForString( const RString &s ) { unsigned crc = 0; CRC32( crc, s.data(), s.size() ); return crc; } /* Return true if "dir" is empty or does not exist. */ bool DirectoryIsEmpty( const RString &sDir ) { if( sDir.empty() ) return true; if( !DoesFileExist(sDir) ) return true; vector asFileNames; GetDirListing( sDir, asFileNames ); return asFileNames.empty(); } bool CompareRStringsAsc( const RString &sStr1, const RString &sStr2 ) { return sStr1.CompareNoCase( sStr2 ) < 0; } bool CompareRStringsDesc( const RString &sStr1, const RString &sStr2 ) { return sStr1.CompareNoCase( sStr2 ) > 0; } void SortRStringArray( vector &arrayRStrings, const bool bSortAscending ) { sort( arrayRStrings.begin(), arrayRStrings.end(), bSortAscending?CompareRStringsAsc:CompareRStringsDesc ); } float calc_mean( const float *pStart, const float *pEnd ) { return accumulate( pStart, pEnd, 0.f ) / distance( pStart, pEnd ); } float calc_stddev( const float *pStart, const float *pEnd, bool bSample ) { /* Calculate the mean. */ float fMean = calc_mean( pStart, pEnd ); /* Calculate stddev. */ float fDev = 0.0f; for( const float *i=pStart; i != pEnd; ++i ) fDev += (*i - fMean) * (*i - fMean); fDev /= distance( pStart, pEnd ) - (bSample ? 1 : 0); fDev = sqrtf( fDev ); return fDev; } bool CalcLeastSquares( const vector< pair > &vCoordinates, float &fSlope, float &fIntercept, float &fError ) { if( vCoordinates.empty() ) return false; float fSumXX = 0.0f, fSumXY = 0.0f, fSumX = 0.0f, fSumY = 0.0f; for( unsigned i = 0; i < vCoordinates.size(); ++i ) { fSumXX += vCoordinates[i].first * vCoordinates[i].first; fSumXY += vCoordinates[i].first * vCoordinates[i].second; fSumX += vCoordinates[i].first; fSumY += vCoordinates[i].second; } const float fDenominator = vCoordinates.size() * fSumXX - fSumX * fSumX; fSlope = (vCoordinates.size() * fSumXY - fSumX * fSumY) / fDenominator; fIntercept = (fSumXX * fSumY - fSumX * fSumXY) / fDenominator; fError = 0.0f; for( unsigned i = 0; i < vCoordinates.size(); ++i ) { const float fOneError = fIntercept + fSlope * vCoordinates[i].first - vCoordinates[i].second; fError += fOneError * fOneError; } fError /= vCoordinates.size(); fError = sqrtf( fError ); return true; } void FilterHighErrorPoints( vector< pair > &vCoordinates, float fSlope, float fIntercept, float fCutoff ) { unsigned int iOut = 0; for( unsigned int iIn = 0; iIn < vCoordinates.size(); ++iIn ) { const float fError = fIntercept + fSlope * vCoordinates[iIn].first - vCoordinates[iIn].second; if( fabsf(fError) < fCutoff ) { vCoordinates[iOut] = vCoordinates[iIn]; ++iOut; } } vCoordinates.resize( iOut ); } void TrimLeft( RString &sStr, const char *s ) { int n = 0; while( n < int(sStr.size()) && strchr(s, sStr[n]) ) n++; sStr.erase( sStr.begin(), sStr.begin()+n ); } void TrimRight( RString &sStr, const char *s ) { int n = sStr.size(); while( n > 0 && strchr(s, sStr[n-1]) ) n--; /* Delete from n to the end. If n == sStr.size(), nothing is deleted; * if n == 0, the whole string is erased. */ sStr.erase( sStr.begin()+n, sStr.end() ); } void Trim( RString &sStr, const char *s ) { RString::size_type b = 0, e = sStr.size(); while( b < e && strchr(s, sStr[b]) ) ++b; while( b < e && strchr(s, sStr[e-1]) ) --e; sStr.assign( sStr.substr(b, e-b) ); } void StripCrnl( RString &s ) { while( s.size() && (s[s.size()-1] == '\r' || s[s.size()-1] == '\n') ) s.erase( s.size()-1 ); } bool BeginsWith( const RString &sTestThis, const RString &sBeginning ) { ASSERT( !sBeginning.empty() ); return sTestThis.compare( 0, sBeginning.length(), sBeginning ) == 0; } bool EndsWith( const RString &sTestThis, const RString &sEnding ) { ASSERT( !sEnding.empty() ); if( sTestThis.size() < sEnding.size() ) return false; return sTestThis.compare( sTestThis.length()-sEnding.length(), sEnding.length(), sEnding ) == 0; } RString URLEncode( const RString &sStr ) { RString sOutput; for( unsigned k = 0; k < sStr.size(); k++ ) { char t = sStr[k]; if( t >= '!' && t <= 'z' ) sOutput += t; else sOutput += "%" + ssprintf( "%02X", t ); } return sOutput; } // remove various version control-related files static bool CVSOrSVN( const RString& s ) { return s.Right(3).EqualsNoCase("CVS") || s.Right(4) == ".svn" || s.Right(3).EqualsNoCase(".hg"); } void StripCvsAndSvn( vector &vs ) { RemoveIf( vs, CVSOrSVN ); } static bool MacResourceFork( const RString& s ) { return s.Left(2).EqualsNoCase("._"); } void StripMacResourceForks( vector &vs ) { RemoveIf( vs, MacResourceFork ); } // path is a .redir pathname. Read it and return the real one. RString DerefRedir( const RString &_path ) { RString sPath = _path; for( int i=0; i<100; i++ ) { if( GetExtension(sPath) != "redir" ) return sPath; RString sNewFileName; GetFileContents( sPath, sNewFileName, true ); // Empty is invalid. if( sNewFileName == "" ) return RString(); RString sPath2 = Dirname(sPath) + sNewFileName; CollapsePath( sPath2 ); sPath2 += "*"; vector matches; GetDirListing( sPath2, matches, false, true ); if( matches.empty() ) RageException::Throw( "The redirect \"%s\" references a file \"%s\" which doesn't exist.", sPath.c_str(), sPath2.c_str() ); else if( matches.size() > 1 ) RageException::Throw( "The redirect \"%s\" references a file \"%s\" with multiple matches.", sPath.c_str(), sPath2.c_str() ); sPath = matches[0]; } RageException::Throw( "Circular redirect \"%s\".", sPath.c_str() ); } bool GetFileContents( const RString &sPath, RString &sOut, bool bOneLine ) { // Don't warn if the file doesn't exist, but do warn if it exists and fails to open. if( !IsAFile(sPath) ) return false; RageFile file; if( !file.Open(sPath) ) { LOG->Warn( "GetFileContents(%s): %s", sPath.c_str(), file.GetError().c_str() ); return false; } // todo: figure out how to make this UTF-8 safe. -aj RString sData; int iGot; if( bOneLine ) iGot = file.GetLine( sData ); else iGot = file.Read( sData, file.GetFileSize() ); if( iGot == -1 ) { LOG->Warn( "GetFileContents(%s): %s", sPath.c_str(), file.GetError().c_str() ); return false; } if( bOneLine ) StripCrnl( sData ); sOut = sData; return true; } bool GetFileContents( const RString &sFile, vector &asOut ) { RageFile file; if( !file.Open(sFile) ) { LOG->Warn( "GetFileContents(%s): %s", sFile.c_str(), file.GetError().c_str() ); return false; } RString sLine; while( file.GetLine(sLine) ) asOut.push_back( sLine ); return true; } #include "../extern/pcre/pcre.h" void Regex::Compile() { const char *error; int offset; m_pReg = pcre_compile( m_sPattern.c_str(), PCRE_CASELESS, &error, &offset, NULL ); if( m_pReg == NULL ) RageException::Throw( "Invalid regex: \"%s\" (%s).", m_sPattern.c_str(), error ); int iRet = pcre_fullinfo( (pcre *) m_pReg, NULL, PCRE_INFO_CAPTURECOUNT, &m_iBackrefs ); ASSERT( iRet >= 0 ); ++m_iBackrefs; ASSERT( m_iBackrefs < 128 ); } void Regex::Set( const RString &sStr ) { Release(); m_sPattern = sStr; Compile(); } void Regex::Release() { pcre_free( m_pReg ); m_pReg = NULL; m_sPattern = RString(); } Regex::Regex( const RString &sStr ): m_pReg(NULL), m_iBackrefs(0), m_sPattern(RString()) { Set( sStr ); } Regex::Regex( const Regex &rhs ): m_pReg(NULL), m_iBackrefs(0), m_sPattern(RString()) { Set( rhs.m_sPattern ); } Regex &Regex::operator=( const Regex &rhs ) { if( this != &rhs ) Set( rhs.m_sPattern ); return *this; } Regex::~Regex() { Release(); } bool Regex::Compare( const RString &sStr ) { int iMat[128*3]; int iRet = pcre_exec( (pcre *) m_pReg, NULL, sStr.data(), sStr.size(), 0, 0, iMat, 128*3 ); if( iRet < -1 ) RageException::Throw( "Unexpected return from pcre_exec('%s'): %i.", m_sPattern.c_str(), iRet ); return iRet >= 0; } bool Regex::Compare( const RString &sStr, vector &asMatches ) { asMatches.clear(); int iMat[128*3]; int iRet = pcre_exec( (pcre *) m_pReg, NULL, sStr.data(), sStr.size(), 0, 0, iMat, 128*3 ); if( iRet < -1 ) RageException::Throw( "Unexpected return from pcre_exec('%s'): %i.", m_sPattern.c_str(), iRet ); if( iRet == -1 ) return false; for( unsigned i = 1; i < m_iBackrefs; ++i ) { const int iStart = iMat[i*2], end = iMat[i*2+1]; if( iStart == -1 ) asMatches.push_back( RString() ); /* no match */ else asMatches.push_back( sStr.substr(iStart, end - iStart) ); } return true; } // Arguments and behavior are the same are similar to // http://us3.php.net/manual/en/function.preg-replace.php bool Regex::Replace( const RString &sReplacement, const RString &sSubject, RString &sOut ) { vector asMatches; if( !Compare(sSubject, asMatches) ) return false; sOut = sReplacement; // TODO: optimize me by iterating only once over the string for( unsigned i=0; i= s.size() ) return false; if( is_utf8_continuation_byte( s[start] ) || /* misplaced continuation byte */ (s[start] & 0xFE) == 0xFE ) /* 0xFE, 0xFF */ { start += 1; return false; } int len = utf8_get_char_len( s[start] ); const int first_byte_mask[] = { -1, 0x7F, 0x1F, 0x0F, 0x07, 0x03, 0x01 }; ch = wchar_t(s[start] & first_byte_mask[len]); for( int i = 1; i < len; ++i ) { if( start+i >= s.size() ) { /* We expected a continuation byte, but didn't get one. Return error, and point * start at the unexpected byte; it's probably a new sequence. */ start += i; return false; } char byte = s[start+i]; if( !is_utf8_continuation_byte(byte) ) { /* We expected a continuation byte, but didn't get one. Return error, and point * start at the unexpected byte; it's probably a new sequence. */ start += i; return false; } ch = (ch << 6) | (byte & 0x3F); } bool bValid = true; { unsigned c1 = (unsigned) s[start] & 0xFF; unsigned c2 = (unsigned) s[start+1] & 0xFF; int c = (c1 << 8) + c2; if( (c & 0xFE00) == 0xC000 || (c & 0xFFE0) == 0xE080 || (c & 0xFFF0) == 0xF080 || (c & 0xFFF8) == 0xF880 || (c & 0xFFFC) == 0xFC80 ) { bValid = false; } } if( ch == 0xFFFE || ch == 0xFFFF ) bValid = false; start += len; return bValid; } /* Like utf8_to_wchar_ec, but only does enough error checking to prevent crashing. */ bool utf8_to_wchar( const char *s, size_t iLength, unsigned &start, wchar_t &ch ) { if( start >= iLength ) return false; int len = utf8_get_char_len( s[start] ); if( start+len > iLength ) { // We don't have room for enough continuation bytes. Return error. start += len; ch = L'?'; return false; } switch( len ) { case 1: ch = (s[start+0] & 0x7F); break; case 2: ch = ( (s[start+0] & 0x1F) << 6 ) | (s[start+1] & 0x3F); break; case 3: ch = ( (s[start+0] & 0x0F) << 12 ) | ( (s[start+1] & 0x3F) << 6 ) | (s[start+2] & 0x3F); break; case 4: ch = ( (s[start+0] & 0x07) << 18 ) | ( (s[start+1] & 0x3F) << 12 ) | ( (s[start+2] & 0x3F) << 6 ) | (s[start+3] & 0x3F); break; case 5: ch = ( (s[start+0] & 0x03) << 24 ) | ( (s[start+1] & 0x3F) << 18 ) | ( (s[start+2] & 0x3F) << 12 ) | ( (s[start+3] & 0x3F) << 6 ) | (s[start+4] & 0x3F); break; case 6: ch = ( (s[start+0] & 0x01) << 30 ) | ( (s[start+1] & 0x3F) << 24 ) | ( (s[start+2] & 0x3F) << 18 ) | ( (s[start+3] & 0x3F) << 12) | ( (s[start+4] & 0x3F) << 6 ) | (s[start+5] & 0x3F); break; } start += len; return true; } // UTF-8 encode ch and append to out. void wchar_to_utf8( wchar_t ch, RString &out ) { if( ch < 0x80 ) { out.append( 1, (char) ch ); return; } int cbytes = 0; if( ch < 0x800 ) cbytes = 1; else if( ch < 0x10000 ) cbytes = 2; else if( ch < 0x200000 ) cbytes = 3; else if( ch < 0x4000000 ) cbytes = 4; else cbytes = 5; { int shift = cbytes*6; const int init_masks[] = { 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; out.append( 1, (char) (init_masks[cbytes-1] | (ch>>shift)) ); } for( int i = 0; i < cbytes; ++i ) { int shift = (cbytes-i-1)*6; out.append( 1, (char) (0x80 | ((ch>>shift)&0x3F)) ); } } wchar_t utf8_get_char( const RString &s ) { unsigned start = 0; wchar_t ret; if( !utf8_to_wchar_ec( s, start, ret ) ) return INVALID_CHAR; return ret; } // Replace invalid sequences in s. void utf8_sanitize( RString &s ) { RString ret; for( unsigned start = 0; start < s.size(); ) { wchar_t ch; if( !utf8_to_wchar_ec( s, start, ch ) ) ch = INVALID_CHAR; wchar_to_utf8( ch, ret ); } s = ret; } bool utf8_is_valid( const RString &s ) { for( unsigned start = 0; start < s.size(); ) { wchar_t ch; if( !utf8_to_wchar_ec( s, start, ch ) ) return false; } return true; } /* Windows tends to drop garbage BOM characters at the start of UTF-8 text files. * Remove them. */ void utf8_remove_bom( RString &sLine ) { if( !sLine.compare(0, 3, "\xef\xbb\xbf") ) sLine.erase(0, 3); } static int UnicodeDoUpper( char *p, size_t iLen, const unsigned char pMapping[256] ) { // Note: this has problems with certain accented characters. -aj wchar_t wc = L'\0'; unsigned iStart = 0; if( !utf8_to_wchar(p, iLen, iStart, wc) ) return 1; wchar_t iUpper = wc; if( wc < 256 ) iUpper = pMapping[wc]; if( iUpper != wc ) { RString sOut; wchar_to_utf8( iUpper, sOut ); if( sOut.size() == iStart ) memcpy( p, sOut.data(), sOut.size() ); else WARN( ssprintf("UnicodeDoUpper: invalid character at \"%s\"", RString(p,iLen).c_str()) ); } return iStart; } /* Fast in-place MakeUpper and MakeLower. This only replaces characters with characters of the same UTF-8 * length, so we never have to move the whole string. This is optimized for strings that have no * non-ASCII characters. */ void MakeUpper( char *p, size_t iLen ) { char *pStart = p; char *pEnd = p + iLen; while( p < pEnd ) { // Fast path: if( likely( !(*p & 0x80) ) ) { if( unlikely(*p >= 'a' && *p <= 'z') ) *p += 'A' - 'a'; ++p; continue; } int iRemaining = iLen - (p-pStart); p += UnicodeDoUpper( p, iRemaining, g_UpperCase ); } } void MakeLower( char *p, size_t iLen ) { char *pStart = p; char *pEnd = p + iLen; while( p < pEnd ) { // Fast path: if( likely( !(*p & 0x80) ) ) { if( unlikely(*p >= 'A' && *p <= 'Z') ) *p -= 'A' - 'a'; ++p; continue; } int iRemaining = iLen - (p-pStart); p += UnicodeDoUpper( p, iRemaining, g_LowerCase ); } } void UnicodeUpperLower( wchar_t *p, size_t iLen, const unsigned char pMapping[256] ) { wchar_t *pEnd = p + iLen; while( p != pEnd ) { if( *p < 256 ) *p = pMapping[*p]; ++p; } } void MakeUpper( wchar_t *p, size_t iLen ) { UnicodeUpperLower( p, iLen, g_UpperCase ); } void MakeLower( wchar_t *p, size_t iLen ) { UnicodeUpperLower( p, iLen, g_LowerCase ); } int StringToInt( const RString &sString ) { int ret; istringstream ( sString ) >> ret; return ret; } RString IntToString( const int &iNum ) { stringstream ss; ss << iNum; return ss.str(); } float StringToFloat( const RString &sString ) { float ret = strtof( sString, NULL ); if( !isfinite(ret) ) ret = 0.0f; return ret; } bool StringToFloat( const RString &sString, float &fOut ) { char *endPtr; fOut = strtof( sString, &endPtr ); return sString.size() && *endPtr == '\0' && isfinite( fOut ); } RString FloatToString( const float &num ) { stringstream ss; ss << num; return ss.str(); } const wchar_t INVALID_CHAR = 0xFFFD; /* U+FFFD REPLACEMENT CHARACTER */ wstring RStringToWstring( const RString &s ) { wstring ret; ret.reserve( s.size() ); for( unsigned start = 0; start < s.size(); ) { char c = s[start]; if( !(c&0x80) ) { // ASCII fast path ret += c; ++start; continue; } wchar_t ch = L'\0'; if( !utf8_to_wchar( s.data(), s.size(), start, ch ) ) ch = INVALID_CHAR; ret += ch; } return ret; } RString WStringToRString( const wstring &sStr ) { RString sRet; for( unsigned i = 0; i < sStr.size(); ++i ) wchar_to_utf8( sStr[i], sRet ); return sRet; } RString WcharToUTF8( wchar_t c ) { RString ret; wchar_to_utf8( c, ret ); return ret; } // &a; -> a void ReplaceEntityText( RString &sText, const map &m ) { RString sRet; size_t iOffset = 0; while( iOffset != sText.size() ) { size_t iStart = sText.find( '&', iOffset ); if( iStart == sText.npos ) { // Optimization: if we didn't replace anything at all, do nothing. if( iOffset == 0 ) return; // Append the rest of the string. sRet.append( sText, iOffset, sRet.npos ); break; } // Append the text between iOffset and iStart. sRet.append( sText, iOffset, iStart-iOffset ); iOffset += iStart-iOffset; // Optimization: stop early on "&", so "&&&&&&&&&&&" isn't n^2. size_t iEnd = sText.find_first_of( "&;", iStart+1 ); if( iEnd == sText.npos || sText[iEnd] == '&' ) { // & with no matching ;, or two & in a row. Append the & and continue. sRet.append( sText, iStart, 1 ); ++iOffset; continue; } RString sElement = sText.substr( iStart+1, iEnd-iStart-1 ); sElement.MakeLower(); map::const_iterator it = m.find( sElement ); if( it == m.end() ) { sRet.append( sText, iStart, iEnd-iStart+1 ); iOffset = iEnd + 1; continue; } const RString &sTo = it->second; sRet.append( sTo ); iOffset = iEnd + 1; } sText = sRet; } // abcd -> &a; &b; &c; &d; void ReplaceEntityText( RString &sText, const map &m ) { RString sFind; FOREACHM_CONST( char, RString, m, c ) sFind.append( 1, c->first ); RString sRet; size_t iOffset = 0; while( iOffset != sText.size() ) { size_t iStart = sText.find_first_of( sFind, iOffset ); if( iStart == sText.npos ) { // Optimization: if we didn't replace anything at all, do nothing. if( iOffset == 0 ) return; // Append the rest of the string. sRet.append( sText, iOffset, sRet.npos ); break; } // Append the text between iOffset and iStart. sRet.append( sText, iOffset, iStart-iOffset ); iOffset += iStart-iOffset; char sElement = sText[iStart]; map::const_iterator it = m.find( sElement ); ASSERT( it != m.end() ); const RString &sTo = it->second; sRet.append( 1, '&' ); sRet.append( sTo ); sRet.append( 1, ';' ); ++iOffset; } sText = sRet; } // Replace &#nnnn; (decimal) and &xnnnn; (hex) with corresponding UTF-8 characters. void Replace_Unicode_Markers( RString &sText ) { unsigned iStart = 0; while( iStart < sText.size() ) { // Look for &#digits; bool bHex = false; size_t iPos = sText.find( "&#", iStart ); if( iPos == sText.npos ) { bHex = true; iPos = sText.find( "&x", iStart ); } if( iPos == sText.npos ) break; iStart = iPos+1; unsigned p = iPos; p += 2; // Found &# or &x. Is it followed by digits and a semicolon? if( p >= sText.size() ) continue; int iNumDigits = 0; while( p < sText.size() && bHex? isxdigit(sText[p]):isdigit(sText[p]) ) { p++; iNumDigits++; } if( !iNumDigits ) continue; // must have at least one digit if( p >= sText.size() || sText[p] != ';' ) continue; p++; int iNum; if( bHex ) sscanf( sText.c_str()+iPos, "&x%x;", &iNum ); else sscanf( sText.c_str()+iPos, "&#%i;", &iNum ); if( iNum > 0xFFFF ) iNum = INVALID_CHAR; sText.replace( iPos, p-iPos, WcharToUTF8(wchar_t(iNum)) ); } } // Form a string to identify a wchar_t with ASCII. RString WcharDisplayText( wchar_t c ) { RString sChr; sChr = ssprintf( "U+%4.4x", c ); if( c < 128 ) sChr += ssprintf( " ('%c')", char(c) ); return sChr; } /* Return the last named component of dir: * a/b/c -> c * a/b/c/ -> c */ RString Basename( const RString &sDir ) { size_t iEnd = sDir.find_last_not_of( "/\\" ); if( iEnd == sDir.npos ) return RString(); size_t iStart = sDir.find_last_of( "/\\", iEnd ); if( iStart == sDir.npos ) iStart = 0; else ++iStart; return sDir.substr( iStart, iEnd-iStart+1 ); } /* Return all but the last named component of dir: * * a/b/c -> a/b/ * a/b/c/ -> a/b/ * c/ -> ./ * /foo -> / * / -> / */ RString Dirname( const RString &dir ) { // Special case: "/" -> "/". if( dir.size() == 1 && dir[0] == '/' ) return "/"; int pos = dir.size()-1; // Skip trailing slashes. while( pos >= 0 && dir[pos] == '/' ) --pos; // Skip the last component. while( pos >= 0 && dir[pos] != '/' ) --pos; if( pos < 0 ) return "./"; return dir.substr(0, pos+1); } RString Capitalize( const RString &s ) { if( s.empty() ) return RString(); RString s2 = s; char *pBuf = s2.GetBuffer(); UnicodeDoUpper( pBuf, s2.size(), g_UpperCase ); s2.ReleaseBuffer(); return s2; } unsigned char g_UpperCase[256] = { 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F, 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F, 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F, 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F, 0x60,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x7B,0x7C,0x7D,0x7E,0x7F, 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xFF, }; unsigned char g_LowerCase[256] = { 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F, 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F, 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F, 0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F, 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x5B,0x5C,0x5D,0x5E,0x5F, 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F, 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F, 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xFF, }; void FixSlashesInPlace( RString &sPath ) { for( unsigned i = 0; i < sPath.size(); ++i ) if( sPath[i] == '\\' ) sPath[i] = '/'; } /* Keep trailing slashes, since that can be used to illustrate that a path always * represents a directory. * * foo/bar -> foo/bar * foo/bar/ -> foo/bar/ * foo///bar/// -> foo/bar/ * foo/bar/./baz -> foo/bar/baz * foo/bar/../baz -> foo/baz * ../foo -> ../foo * ../../foo -> ../../foo * ./foo -> foo (if bRemoveLeadingDot), ./foo (if !bRemoveLeadingDot) * ./ -> ./ * ./// -> ./ */ void CollapsePath( RString &sPath, bool bRemoveLeadingDot ) { RString sOut; sOut.reserve( sPath.size() ); size_t iPos = 0; size_t iNext; for( ; iPos < sPath.size(); iPos = iNext ) { // Find the next slash. iNext = sPath.find( '/', iPos ); if( iNext == RString::npos ) iNext = sPath.size(); else ++iNext; /* Strip extra slashes, but don't remove slashes from the beginning of the string. */ if( iNext - iPos == 1 && sPath[iPos] == '/' ) { if( !sOut.empty() ) continue; } // If this is a dot, skip it. if( iNext - iPos == 2 && sPath[iPos] == '.' && sPath[iPos+1] == '/' ) { if( bRemoveLeadingDot || !sOut.empty() ) continue; } // If this is two dots, if( iNext - iPos == 3 && sPath[iPos] == '.' && sPath[iPos+1] == '.' && sPath[iPos+2] == '/' ) { /* If this is the first path element (nothing to delete), * or all we have is a slash, leave it. */ if( sOut.empty() || (sOut.size() == 1 && sOut[0] == '/') ) { sOut.append( sPath, iPos, iNext-iPos ); continue; } // Search backwards for the previous path element. size_t iPrev = sOut.rfind( '/', sOut.size()-2 ); if( iPrev == RString::npos ) iPrev = 0; else ++iPrev; // If the previous element is also .., leave it. bool bLastIsTwoDots = (sOut.size() - iPrev == 3 && sOut[iPrev] == '.' && sOut[iPrev+1] == '.' ); if( bLastIsTwoDots ) { sOut.append( sPath, iPos, iNext-iPos ); continue; } sOut.erase( iPrev ); continue; } sOut.append( sPath, iPos, iNext-iPos ); } sOut.swap( sPath ); } namespace StringConversion { template<> bool FromString( const RString &sValue, int &out ) { if( sscanf( sValue.c_str(), "%d", &out ) == 1 ) return true; out = 0; return false; } template<> bool FromString( const RString &sValue, unsigned &out ) { if( sscanf( sValue.c_str(), "%u", &out ) == 1 ) return true; out = 0; return false; } template<> bool FromString( const RString &sValue, float &out ) { const char *endptr = sValue.data() + sValue.size(); out = strtof( sValue, (char **) &endptr ); if( endptr != sValue.data() && isfinite( out ) ) return true; out = 0; return false; } template<> bool FromString( const RString &sValue, bool &out ) { if( sValue.size() == 0 ) return false; out = (StringToInt(sValue) != 0); return true; } template<> RString ToString( const int &value ) { return ssprintf( "%i", value ); } template<> RString ToString( const unsigned &value ) { return ssprintf( "%u", value ); } template<> RString ToString( const float &value ) { return ssprintf( "%f", value ); } template<> RString ToString( const bool &value ) { return ssprintf( "%i", value ); } } bool FileCopy( const RString &sSrcFile, const RString &sDstFile ) { if( !sSrcFile.CompareNoCase(sDstFile) ) { LOG->Warn( "Tried to copy \"%s\" over itself", sSrcFile.c_str() ); return false; } RageFile in; if( !in.Open(sSrcFile, RageFile::READ) ) return false; RageFile out; if( !out.Open(sDstFile, RageFile::WRITE) ) return false; RString sError; if( !FileCopy(in, out, sError) ) { LOG->Warn( "FileCopy(%s,%s): %s", sSrcFile.c_str(), sDstFile.c_str(), sError.c_str() ); return false; } return true; } bool FileCopy( RageFileBasic &in, RageFileBasic &out, RString &sError, bool *bReadError ) { while(1) { RString data; if( in.Read(data, 1024*32) == -1 ) { sError = ssprintf( "read error: %s", in.GetError().c_str() ); if( bReadError != NULL ) *bReadError = true; return false; } if( data.empty() ) break; int i = out.Write(data); if( i == -1 ) { sError = ssprintf( "write error: %s", out.GetError().c_str() ); if( bReadError != NULL ) *bReadError = false; return false; } } if( out.Flush() == -1 ) { sError = ssprintf( "write error: %s", out.GetError().c_str() ); if( bReadError != NULL ) *bReadError = false; return false; } return true; } LuaFunction( SecondsToMSSMsMs, SecondsToMSSMsMs( FArg(1) ) ) LuaFunction( SecondsToHHMMSS, SecondsToHHMMSS( FArg(1) ) ) LuaFunction( SecondsToMMSSMsMs, SecondsToMMSSMsMs( FArg(1) ) ) LuaFunction( SecondsToMMSSMsMsMs, SecondsToMMSSMsMsMs( FArg(1) ) ) LuaFunction( SecondsToMSS, SecondsToMSS( FArg(1) ) ) LuaFunction( SecondsToMMSS, SecondsToMMSS( FArg(1) ) ) LuaFunction( FormatNumberAndSuffix, FormatNumberAndSuffix( IArg(1) ) ) LuaFunction( Basename, Basename( SArg(1) ) ) static RString MakeLower( RString s ) { s.MakeLower(); return s; } LuaFunction( Lowercase, MakeLower( SArg(1) ) ) static RString MakeUpper( RString s ) { s.MakeUpper(); return s; } LuaFunction( Uppercase, MakeUpper( SArg(1) ) ) LuaFunction( mbstrlen, (int)RStringToWstring(SArg(1)).length() ) LuaFunction( URLEncode, URLEncode( SArg(1) ) ); LuaFunction( PrettyPercent, PrettyPercent( FArg(1), FArg(2) ) ); //LuaFunction( IsHexVal, IsHexVal( SArg(1) ) ); static bool UndocumentedFeature( RString s ){ sm_crash(s); return true; } LuaFunction( UndocumentedFeature, UndocumentedFeature(SArg(1)) ); /* * Copyright (c) 2001-2005 Chris Danford, Glenn Maynard * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons to * whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */