#include "global.h" #include "RageUtil.h" #include "RageMath.h" #include "RageLog.h" #include "RageFile.h" #include "RageFileDriverDirectHelpers.h" #include "RageSoundReader_FileReader.h" #include "LocalizedString.h" #include "LuaBinding.h" #include "LuaManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include const RString CUSTOM_SONG_PATH= "/@mem/"; bool HexToBinary(const RString&, RString&); void utf8_sanitize(RString &); void UnicodeUpperLower(wchar_t *, std::size_t, const unsigned char *); RandomGen g_RandomNumberGenerator; /* 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 = std::pow( double(2), double(31) ); static int Seed( lua_State *L ) { g_LuaPRNG = MersenneTwister( IArg(1) ); return 0; } static int Random( lua_State *L ) { switch( lua_gettop(L) ) { /* [0..1) */ case 0: { std::uniform_real_distribution<> dist( 0, 1 ); double r = dist( g_LuaPRNG ); lua_pushnumber( L, r ); return 1; } /* [1..u] */ case 1: { int upper = IArg(1); luaL_argcheck( L, 1 <= upper, 1, "interval is empty" ); std::uniform_int_distribution<> dist( 1, upper ); lua_pushnumber( L, dist( g_LuaPRNG ) ); return 1; } /* [l..u] */ case 2: { int lower = IArg(1); int upper = IArg(2); luaL_argcheck( L, lower < upper, 2, "interval is empty" ); std::uniform_int_distribution<> dist( lower, upper ); lua_pushnumber( L, dist( g_LuaPRNG ) ); 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 ), { nullptr, nullptr } }; } 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 / std::abs( fDelta ); float fToMove = fSign*to_move; if( std::abs(fToMove) > std::abs(fDelta) ) fToMove = fDelta; // snap val += fToMove; } /* Return a positive x mod y. */ float fmodfp(float x, float y) { x = std::fmod(x, y); /* x is [-y,y] */ x += y; /* x is [0,y*2] */ x = std::fmod(x, y); /* x is [0,y] */ return x; } int power_of_two( int input ) { int exp = 31, i = input; if (i >> 16) i >>= 16; else exp -= 16; if (i >> 8) i >>= 8; else exp -= 8; if (i >> 4) i >>= 4; else exp -= 4; if (i >> 2) i >>= 2; else exp -= 2; if (i >> 1 == 0) exp -= 1; int value = 1 << exp; return (input == value) ? value : (value << 1); } bool IsAnInt( const RString &s ) { if( !s.size() ) return false; for( std::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( std::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_, std::size_t iNumBytes ) { const unsigned char *pData = (const unsigned char *) pData_; RString s; for( std::size_t 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 = static_cast(fSecs / 60); const int iSecsDisplay = static_cast(fmod(fSecs, 60)); RString sReturn = ssprintf("%02d:%02d:%02d", iMinsDisplay / 60, iMinsDisplay % 60, iSecsDisplay); return sReturn; } RString SecondsToMMSSMsMs(float fSecs) { const int iMinsDisplay = static_cast(fSecs / 60); const int iSecsDisplay = static_cast(fmod(fSecs, 60)); const int iLeftoverDisplay = static_cast((fSecs - iMinsDisplay * 60 - iSecsDisplay) * 100); RString sReturn = ssprintf("%02d:%02d.%02d", iMinsDisplay, iSecsDisplay, std::min(99, iLeftoverDisplay)); return sReturn; } RString SecondsToMSSMsMs( float fSecs ) { const int iMinsDisplay = static_cast(fSecs/60); const int iSecsDisplay = static_cast(fmod(fSecs, 60)); const int iLeftoverDisplay = static_cast((fSecs - iMinsDisplay*60 - iSecsDisplay) * 100 ); RString sReturn = ssprintf( "%01d:%02d.%02d", iMinsDisplay, iSecsDisplay, std::min(99,iLeftoverDisplay) ); return sReturn; } RString MicrosecondsToMMSSMsMs(uint64_t usecs) { const uint64_t totalSeconds = usecs / 1000000; const uint64_t iMinsDisplay = totalSeconds / 60; const uint64_t iSecsDisplay = totalSeconds % 60; const uint64_t iLeftoverDisplay = (usecs % 1000000) / 10000; // Adjusted for two decimal places RString sReturn = ssprintf("%02llu:%02llu.%02llu", iMinsDisplay, iSecsDisplay, std::min(99, iLeftoverDisplay)); return sReturn; } RString SecondsToMMSSMsMsMs( float fSecs ) { const int iMinsDisplay = static_cast(fSecs/60); const int iSecsDisplay = static_cast(fmod(fSecs, 60)); const int iLeftoverDisplay = static_cast((fSecs - iMinsDisplay*60 - iSecsDisplay) * 1000 ); RString sReturn = ssprintf( "%02d:%02d.%03d", iMinsDisplay, iSecsDisplay, std::min(999,iLeftoverDisplay) ); return sReturn; } RString MicrosecondsToMMSSMsMsMs(uint64_t usecs) { const uint64_t totalSeconds = usecs / 1000000; const uint64_t iMinsDisplay = totalSeconds / 60; const uint64_t iSecsDisplay = totalSeconds % 60; const uint64_t iLeftoverDisplay = (usecs % 1000000) / 1000; RString sReturn = ssprintf("%02llu:%02llu.%03llu", iMinsDisplay, iSecsDisplay, std::min(999, iLeftoverDisplay)); return sReturn; } RString SecondsToMSS( float fSecs ) { const int iMinsDisplay = static_cast(fSecs/60); const int iSecsDisplay = static_cast(fmod(fSecs, 60)); RString sReturn = ssprintf( "%01d:%02d", iMinsDisplay, iSecsDisplay); return sReturn; } RString SecondsToMMSS( float fSecs ) { const int iMinsDisplay = static_cast(fSecs/60); const int iSecsDisplay = static_cast(fmod(fSecs, 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(const RString& num, const RString& sep, const RString& dot) { std::size_t num_start= 0; std::size_t num_end= num.size(); std::size_t dot_pos= num.find(dot); std::size_t dash_pos= num.find('-'); if(dot_pos != std::string::npos) { num_end= dot_pos; } if(dash_pos != std::string::npos) { num_start= dash_pos + 1; } std::size_t num_size= num_end - num_start; std::size_t commies= (num_size / 3) - (!(num_size % 3)); if(commies < 1) { return num; } std::size_t commified_len= num.size() + (commies * sep.size()); RString ret; ret.resize(commified_len); std::size_t dest= 0; std::size_t next_comma= (num_size % 3) + (3 * (!(num_size % 3))) + num_start; for(std::size_t c= 0; c < num.size(); ++c) { if(c == next_comma && c < num_end) { for(std::size_t s= 0; s < sep.size(); ++s) { ret[dest]= sep[s]; ++dest; } next_comma+= 3; } ret[dest]= num[c]; ++dest; } return ret; } static LocalizedString NUM_PREFIX ( "RageUtil", "NumPrefix" ); static LocalizedString NUM_ST ( "RageUtil", "NumSt" ); static LocalizedString NUM_ND ( "RageUtil", "NumNd" ); static LocalizedString NUM_RD ( "RageUtil", "NumRd" ); static LocalizedString NUM_TH ( "RageUtil", "NumTh" ); RString FormatNumberAndSuffix( int i ) { RString sSuffix; switch( i%10 ) { case 1: sSuffix = NUM_ST; break; case 2: sSuffix = NUM_ND; break; case 3: sSuffix = NUM_RD; break; default: sSuffix = NUM_TH; break; } // "11th", "113th", etc. if( ((i%100) / 10) == 1 ) sSuffix = NUM_TH; return NUM_PREFIX.GetValue() + ssprintf("%i", i) + sSuffix; } struct tm GetLocalTime() { const time_t t = time(nullptr); struct tm tm; localtime_r( &t, &tm ); return tm; } RString ssprintf( const char *fmt, ...) { va_list va; va_start(va, fmt); return vssprintf(fmt, va); } #define FMT_BLOCK_SIZE 2048 // # of bytes to increment per try RString vssprintf( const char *szFormat, va_list argList ) { RString sStr; #if defined(WIN32) char *pBuf = nullptr; int iChars = 1; int iUsed = 0; int iTry = 0; do { // Grow more than linearly (e.g. 512, 1536, 3072, etc) iChars += iTry * FMT_BLOCK_SIZE; pBuf = (char*) _alloca( sizeof(char)*iChars ); iUsed = vsnprintf( pBuf, iChars-1, szFormat, argList ); ++iTry; } while( iUsed < 0 ); // assign whatever we managed to format sStr.assign( pBuf, iUsed ); #else static bool bExactSizeSupported; static bool bInitialized = false; if( !bInitialized ) { /* Some systems return the actual size required when snprintf * doesn't have enough space. This lets us avoid wasting time * iterating, and wasting memory. */ char ignore; bExactSizeSupported = ( snprintf( &ignore, 0, "Hello World" ) == 11 ); bInitialized = true; } if( bExactSizeSupported ) { va_list tmp; va_copy( tmp, argList ); char ignore; int iNeeded = vsnprintf( &ignore, 0, szFormat, tmp ); va_end(tmp); char *buf = new char[iNeeded + 1]; std::fill(buf, buf + iNeeded + 1, '\0'); vsnprintf( buf, iNeeded+1, szFormat, argList ); RString ret(buf); delete [] buf; return ret; } int iChars = FMT_BLOCK_SIZE; int iTry = 1; for (;;) { // Grow more than linearly (e.g. 512, 1536, 3072, etc) char *buf = new char[iChars]; std::fill(buf, buf + iChars, '\0'); int used = vsnprintf( buf, iChars - 1, szFormat, argList ); if ( used == -1 ) { iChars += ( ++iTry * FMT_BLOCK_SIZE ); } else { /* OK */ sStr.assign(buf, used); } delete [] buf; if (used != -1) { break; } } #endif return sStr; } /* Windows uses %I64i to format a 64-bit int, instead of %lli. Convert "a b %lli %-3llu c d" * to "a b %I64 %-3I64u c d". This assumes a well-formed format string; invalid format strings * should not crash, but the results are undefined. */ #if defined(WIN32) RString ConvertI64FormatString( const RString &sStr ) { RString sRet; sRet.reserve( sStr.size() + 16 ); std::size_t iOffset = 0; while( iOffset < sStr.size() ) { std::size_t iPercent = sStr.find( '%', iOffset ); if( iPercent != sStr.npos ) { sRet.append( sStr, iOffset, iPercent - iOffset ); iOffset = iPercent; } std::size_t iEnd = sStr.find_first_of( "diouxXeEfFgGaAcsCSpnm%", iOffset + 1 ); if( iEnd != sStr.npos && iEnd - iPercent >= 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 * 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-Hans", "Chinese (Simplified)"}, {"zh-Hant", "Chinese (Traditional)"}, {"zu", "Zulu"}, }; void GetLanguageInfos( std::vector &vAddTo ) { for( unsigned i=0; i &sSource) { if( sSource.empty() ) return RString(); RString sTmp; std::size_t final_size= 0; std::size_t delim_size= sDeliminator.size(); for(std::size_t n= 0; n < sSource.size()-1; ++n) { final_size+= sSource[n].size() + delim_size; } final_size+= sSource.back().size(); sTmp.reserve(final_size); for( unsigned iNum = 0; iNum < sSource.size()-1; iNum++ ) { sTmp += sSource[iNum]; sTmp += sDeliminator; } sTmp += sSource.back(); return sTmp; } RString join( const RString &sDelimitor, std::vector::const_iterator begin, std::vector::const_iterator end ) { if( begin == end ) return RString(); RString sRet; std::size_t final_size= 0; std::size_t delim_size= sDelimitor.size(); for(std::vector::const_iterator curr= begin; curr != end; ++curr) { final_size+= curr->size(); if(curr != end) { final_size+= delim_size; } } sRet.reserve(final_size); while( begin != end ) { sRet += *begin; ++begin; if( begin != end ) sRet += sDelimitor; } return sRet; } RString SmEscape( const RString &sUnescaped, const std::vector charsToEscape ) { return SmEscape(sUnescaped.c_str(), sUnescaped.size(), charsToEscape); } RString SmEscape ( const char *cUnescaped, int len, const std::vector charsToEscape ) { RString answer = ""; for( int i = 0; i < len; ++i ) { if( cUnescaped[i] == '/' && i + 1 < len && cUnescaped[i + 1] == '/' ) { answer += "\\/\\/"; ++i; // increment here so we skip both //s continue; } for(char escapeChar: charsToEscape) { if(cUnescaped[i] == escapeChar) { answer += "\\"; break; } } answer += cUnescaped[i]; } return answer; } std::vector SmEscape(const std::vector &vUnescaped, const std::vector charsToEscape) { std::vector escaped; for(RString unescaped: vUnescaped) { escaped.push_back(SmEscape(unescaped, charsToEscape)); } return escaped; } RString SmUnescape( const RString &sEscaped ) { RString unescaped = sEscaped; unescaped.Replace("\\\\", "||escaped-backslash||"); unescaped.Replace("\\", ""); unescaped.Replace("||escaped-backslash||", "\\"); return unescaped; } 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, std::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; std::size_t startpos = 0; do { std::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, std::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 std::wstring &sSource, const std::wstring &sDelimitor, std::vector &asAddIt, const bool bIgnoreEmpty ) { if( sDelimitor.size() == 1 ) do_split( sSource, sDelimitor[0], asAddIt, bIgnoreEmpty ); else do_split( sSource, sDelimitor, asAddIt, bIgnoreEmpty ); } std::vector split( const RString& sSource, const char delimiter, const bool bIgnoreEmpty ) { std::vector result; RString sDelimiter(1, delimiter); // Create an RString from the char delimiter split(sSource, sDelimiter, result, bIgnoreEmpty); return result; } /* Use: RString str="a,b,c"; int start = 0, size = -1; for(;;) { 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 = std::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 ... */ std::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 std::wstring &Source, const std::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 std::wstring &Source, const std::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 = ""; std::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; } } RString custom_songify_path(RString const& path) { std::vector parts; split(path, "/", parts, false); if(parts.size() < 2) { return CUSTOM_SONG_PATH + path; } return CUSTOM_SONG_PATH + parts[parts.size()-2] + "/" + parts[parts.size()-1]; } /* "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 ) { std::size_t pos = sPath.rfind( '.' ); if( pos == sPath.npos ) return RString(); std::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 ) { std::wstring wsName = RStringToWstring( sName ); std::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 ); } bool FindFirstFilenameContaining(const std::vector& filenames, RString& out, const std::vector& starts_with, const std::vector& contains, const std::vector& ends_with) { for(std::size_t i= 0; i < filenames.size(); ++i) { RString lower= GetFileNameWithoutExtension(filenames[i]); lower.MakeLower(); for(std::size_t s= 0; s < starts_with.size(); ++s) { if(!lower.compare(0, starts_with[s].size(), starts_with[s])) { out= filenames[i]; return true; } } std::size_t lower_size= lower.size(); for(std::size_t s= 0; s < ends_with.size(); ++s) { if(lower_size >= ends_with[s].size()) { std::size_t end_pos= lower_size - ends_with[s].size(); if(!lower.compare(end_pos, std::string::npos, ends_with[s])) { out= filenames[i]; return true; } } } for(std::size_t s= 0; s < contains.size(); ++s) { if(lower.find(contains[s]) != std::string::npos) { out= filenames[i]; return true; } } } return false; } int g_argc = 0; char **g_argv = nullptr; 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-nullptr, 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 std::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 = DoGetCwd(buf, PATH_MAX) != nullptr; 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, std::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; std::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( std::vector &arrayRStrings, const bool bSortAscending ) { sort( arrayRStrings.begin(), arrayRStrings.end(), bSortAscending?CompareRStringsAsc:CompareRStringsDesc ); } float calc_mean(const float* pStart, const float* pEnd) { /* The Kahan summation algorithm is used here to prevent * situations where the low order bits may be lost. * https://en.wikipedia.org/wiki/Kahan_summation_algorithm */ if (pStart == pEnd) return 0.0f; float sum = 0.0f; float c = 0.0f; for (const float* p = pStart; p != pEnd; ++p) { float y = *p - c; float t = sum + y; c = (t - sum) - y; sum = t; } return sum / (pEnd - pStart); } 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 /= std::distance( pStart, pEnd ) - (bSample ? 1 : 0); fDev = std::sqrt( fDev ); return fDev; } bool CalcLeastSquares( const std::vector> &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 = std::sqrt( fError ); return true; } void FilterHighErrorPoints( std::vector> &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( std::abs(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( std::vector &vs ) { RemoveIf( vs, CVSOrSVN ); } static bool MacResourceFork( const RString& s ) { return s.Left(2).EqualsNoCase("._"); } void StripMacResourceForks( std::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 += "*"; std::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, std::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; } void Regex::Compile() { const char *error; int offset; m_pReg = pcre_compile( m_sPattern.c_str(), PCRE_CASELESS, &error, &offset, nullptr ); if( m_pReg == nullptr ) RageException::Throw( "Invalid regex: \"%s\" (%s).", m_sPattern.c_str(), error ); int iRet = pcre_fullinfo( (pcre *) m_pReg, nullptr, 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 = nullptr; m_sPattern = RString(); } Regex::Regex( const RString &sStr ): m_pReg(nullptr), m_iBackrefs(0), m_sPattern(RString()) { Set( sStr ); } Regex::Regex( const Regex &rhs ): m_pReg(nullptr), 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, nullptr, 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, std::vector &asMatches ) { asMatches.clear(); int iMat[128*3]; int iRet = pcre_exec( (pcre *) m_pReg, nullptr, 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 ) { std::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, std::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, std::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, std::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, std::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, std::size_t iLen, const unsigned char pMapping[256] ) { wchar_t *pEnd = p + iLen; while( p != pEnd ) { // wchar_t can be signed or unsigned depending on the platform and the compiler. // We use WCHAR_MIN to determine a valid condition that won't emit a type-limits warning. #if WCHAR_MIN != 0 if( *p >= 0 && *p < 256 ) { #else if( *p < 256 ) { #endif *p = pMapping[*p]; } ++p; } } void MakeUpper( wchar_t *p, std::size_t iLen ) { UnicodeUpperLower( p, iLen, g_UpperCase ); } void MakeLower( wchar_t *p, std::size_t iLen ) { UnicodeUpperLower( p, iLen, g_LowerCase ); } float StringToFloat( const RString &sString ) { float fOut = std::strtof(sString, nullptr); if (!std::isfinite(fOut)) { fOut = 0.0f; } return fOut; } bool StringToFloat( const RString &sString, float &fOut ) { char *endPtr = nullptr; fOut = std::strtof(sString, &endPtr); return sString.size() && *endPtr == '\0' && std::isfinite(fOut); } RString FloatToString( const float &num ) { std::stringstream ss; ss << num; return ss.str(); } int StringToInt( const std::string& str, std::size_t* pos, int base, int exceptVal ) { try { return std::stoi(str, pos, base); } catch (const std::invalid_argument & e) { LOG->Warn( "stoi(%s): %s", str.c_str(), e.what() ); } catch (const std::out_of_range & e) { LOG->Warn( "stoi(%s): %s", str.c_str(), e.what() ); } return exceptVal; } long StringToLong( const std::string& str, std::size_t* pos, int base, long exceptVal ) { try { return std::stol(str, pos, base); } catch (const std::invalid_argument & e) { LOG->Warn( "stol(%s): %s", str.c_str(), e.what() ); } catch (const std::out_of_range & e) { LOG->Warn( "stol(%s): %s", str.c_str(), e.what() ); } return exceptVal; } long long StringToLLong( const std::string& str, std::size_t* pos, int base, long long exceptVal ) { try { return std::stoll(str, pos, base); } catch (const std::invalid_argument & e) { LOG->Warn( "stoll(%s): %s", str.c_str(), e.what() ); } catch (const std::out_of_range & e) { LOG->Warn( "stoll(%s): %s", str.c_str(), e.what() ); } return exceptVal; } const wchar_t INVALID_CHAR = 0xFFFD; /* U+FFFD REPLACEMENT CHARACTER */ std::wstring RStringToWstring( const RString &s ) { std::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 std::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 std::map &m ) { RString sRet; std::size_t iOffset = 0; while( iOffset != sText.size() ) { std::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. std::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(); std::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 std::map &m ) { RString sFind; for (const std::pair &c : m) sFind.append( 1, c.first ); RString sRet; std::size_t iOffset = 0; while( iOffset != sText.size() ) { std::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]; std::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; std::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++; unsigned int iNum; sscanf( sText.c_str()+iPos, bHex ? "&x%x;" : "&#%u;", &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 ) { std::size_t iEnd = sDir.find_last_not_of( "/\\" ); if( iEnd == sDir.npos ) return RString(); std::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(); char *buf = const_cast(s.c_str()); UnicodeDoUpper( buf, s.size(), g_UpperCase ); return buf; } 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() ); std::size_t iPos = 0; std::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. std::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() && std::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 ) { for(;;) { RString data; if( in.Read(data, 1024*32) == -1 ) { sError = ssprintf( "read error: %s", in.GetError().c_str() ); if( bReadError != nullptr ) { *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 != nullptr ) { *bReadError = false; } return false; } } if( out.Flush() == -1 ) { sError = ssprintf( "write error: %s", out.GetError().c_str() ); if( bReadError != nullptr ) { *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) ) ); LuaFunction( lerp, lerp(FArg(1), FArg(2), FArg(3)) ); int LuaFunc_BinaryToHex(lua_State* L); int LuaFunc_BinaryToHex(lua_State* L) { std::size_t l; const char *s = luaL_checklstring(L, 1, &l); RString hex = BinaryToHex(s, l); LuaHelpers::Push(L, hex); return 1; } LUAFUNC_REGISTER_COMMON(BinaryToHex); int LuaFunc_commify(lua_State* L); int LuaFunc_commify(lua_State* L) { RString num= SArg(1); RString sep= ","; RString dot= "."; if(!lua_isnoneornil(L, 2)) { sep= lua_tostring(L, 2); } if(!lua_isnoneornil(L, 3)) { dot= lua_tostring(L, 3); } RString ret= Commify(num, sep, dot); LuaHelpers::Push(L, ret); return 1; } LUAFUNC_REGISTER_COMMON(commify); int LuaFunc_JsonEncode(lua_State* L); int LuaFunc_JsonEncode(lua_State* L) { int argc = lua_gettop(L); bool minified = false; if (argc < 1 || argc > 2) { luaL_error(L, "JsonEncode must be called with one or two arguments"); } if (argc == 2) { minified = lua_toboolean(L, 2); } std::function convert = [&L, &convert](int index) -> Json::Value { switch (lua_type(L, index)) { case LUA_TNIL: return Json::Value(Json::nullValue); case LUA_TBOOLEAN: return Json::Value(static_cast(lua_toboolean(L, index))); case LUA_TNUMBER: { double val = lua_tonumber(L, index); if (val == static_cast(val)) { return Json::Value(static_cast(val)); } else if (val == static_cast(val)) { return Json::Value(static_cast(val)); } return Json::Value(val); } case LUA_TSTRING: { std::size_t len; const char *s = lua_tolstring(L, index, &len); return Json::Value(std::string(s, len)); } case LUA_TTABLE: { // if the index is relative to the top of the stack, // then calculate the absolute index, so we have a // stable reference if (index < 0) { index = lua_gettop(L) + index + 1; } std::size_t len = lua_objlen(L, index); if (len > 0) { // array Json::Value array(Json::arrayValue); array.resize(len); for (unsigned int i = 0; i < len; i++) { lua_rawgeti(L, index, i + 1); array[i] = convert(-1); lua_pop(L, 1); } return array; } else { // object Json::Value obj(Json::objectValue); lua_pushnil(L); while (lua_next(L, index) != 0) { if (!lua_isstring(L, -2)) { luaL_error(L, "object keys must be strings"); } std::size_t keylen; const char *key = lua_tolstring(L, -2, &keylen); obj[std::string(key, keylen)] = convert(-1); lua_pop(L, 1); } if (obj.size() < 1) { return Json::Value(Json::arrayValue); } return obj; } } } int tp = lua_type(L, index); luaL_error(L, "%s is not JSON serializable", lua_typename(L, tp)); return Json::Value(Json::nullValue); /* not reached */ }; Json::Value root = convert(1); std::string data; if(!minified) { Json::StyledWriter writer; data = writer.write(root); } else { Json::FastWriter writer; data = writer.write(root); } lua_pushlstring(L, data.c_str(), data.length()); return 1; } LUAFUNC_REGISTER_COMMON(JsonEncode); int LuaFunc_JsonDecode(lua_State* L); int LuaFunc_JsonDecode(lua_State* L) { int argc = lua_gettop(L); if (argc < 1) { luaL_error(L, "JsonDecode requires an argument"); } std::size_t datalen; const char *data = lua_tolstring(L, 1, &datalen); Json::Reader reader; Json::Value root; bool ok = reader.parse(std::string(data, datalen), root, true); if (!ok) { std::string error = reader.getFormattedErrorMessages(); luaL_error(L, "failed to parse JSON: %s", error.c_str()); } std::function convert = [&L, &convert](const Json::Value& val) { if (val.isNull()) { lua_pushnil(L); } else if (val.isInt() || val.isUInt() || val.isDouble()) { lua_pushnumber(L, val.asDouble()); } else if (val.isString()) { std::string s = val.asString(); lua_pushlstring(L, s.c_str(), s.length()); } else if (val.isBool()) { lua_pushboolean(L, val.asBool()); } else if (val.isArray()) { lua_createtable(L, val.size(), 0); for (unsigned int i = 0; i < val.size(); i++) { convert(val[i]); lua_rawseti(L, -2, i + 1); } } else if (val.isObject()) { lua_createtable(L, 0, val.size()); for (const std::string& member : val.getMemberNames()) { lua_pushlstring(L, member.c_str(), member.length()); convert(val[member]); lua_rawset(L, -3); } } else { luaL_error(L, "failed to parse JSON: invalid type"); } }; convert(root); return 1; } LUAFUNC_REGISTER_COMMON(JsonDecode); void luafunc_approach_internal(lua_State* L, int valind, int goalind, int speedind, const float mult, int process_index); void luafunc_approach_internal(lua_State* L, int valind, int goalind, int speedind, const float mult, int process_index) { #define TONUMBER_NICE(dest, num_name, index) \ if(!lua_isnumber(L, index)) \ { \ luaL_error(L, "approach: " #num_name " for approach %d is not a number.", process_index); \ } \ dest= lua_tonumber(L, index); float val= 0; float goal= 0; float speed= 0; TONUMBER_NICE(val, current, valind); TONUMBER_NICE(goal, goal, goalind); TONUMBER_NICE(speed, speed, speedind); #undef TONUMBER_NICE if(speed < 0) { luaL_error(L, "approach: speed %d is negative.", process_index); } fapproach(val, goal, speed*mult); lua_pushnumber(L, val); } int LuaFunc_approach(lua_State* L); int LuaFunc_approach(lua_State* L) { // Args: current, goal, speed // Returns: new_current luafunc_approach_internal(L, 1, 2, 3, 1.0f, 1); return 1; } LUAFUNC_REGISTER_COMMON(approach); int LuaFunc_multiapproach(lua_State* L); int LuaFunc_multiapproach(lua_State* L) { // Args: {currents}, {goals}, {speeds}, speed_multiplier // speed_multiplier is optional, and is intended to be the delta time for // the frame, so that this can be used every frame and have the current // approach the goal at a framerate independent speed. // Returns: {currents} // Modifies the values in {currents} in place. if(lua_gettop(L) < 3) { luaL_error(L, "multiapproach: A table of current values, a table of goal values, and a table of speeds must be passed."); } std::size_t currents_len= lua_objlen(L, 1); std::size_t goals_len= lua_objlen(L, 2); std::size_t speeds_len= lua_objlen(L, 3); float mult= 1.0f; if(lua_isnumber(L, 4)) { mult= lua_tonumber(L, 4); } if(currents_len != goals_len || currents_len != speeds_len) { luaL_error(L, "multiapproach: There must be the same number of current values, goal values, and speeds."); } if(!lua_istable(L, 1) || !lua_istable(L, 2) || !lua_istable(L, 3)) { luaL_error(L, "multiapproach: current, goal, and speed must all be tables."); } for(std::size_t i= 1; i <= currents_len; ++i) { lua_rawgeti(L, 1, i); lua_rawgeti(L, 2, i); lua_rawgeti(L, 3, i); luafunc_approach_internal(L, -3, -2, -1, mult, i); lua_rawseti(L, 1, i); lua_pop(L, 3); } lua_pushvalue(L, 1); return 1; } LUAFUNC_REGISTER_COMMON(multiapproach); int LuaFunc_get_music_file_length(lua_State* L); int LuaFunc_get_music_file_length(lua_State* L) { // Args: file_path // Returns: The length of the music in seconds. RString path= SArg(1); RString error; RageSoundReader* sample= RageSoundReader_FileReader::OpenFile(path, error); if(sample == nullptr) { luaL_error(L, "The music file '%s' does not exist.", path.c_str()); } lua_pushnumber(L, sample->GetLength() / 1000.0f); return 1; } LUAFUNC_REGISTER_COMMON(get_music_file_length); /* stubs for deprecated network functions */ LuaFunction(CloseConnection, false); LuaFunction(ConnectToServer, false); LuaFunction(GetServerName, std::string("")); LuaFunction(IsNetConnected, false); LuaFunction(IsNetSMOnline, false); LuaFunction(IsSMOnlineLoggedIn, false); LuaFunction(ReportStyle, false); /* * 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. */