From f5c877cbb6afa440e42b83cb269e0c9194916b19 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 4 May 2013 11:59:25 -0400 Subject: [PATCH] Replace StringToInt with the std version. Again, RageUtil isn't the right home. --- src/BGAnimationLayer.cpp | 6 +- src/CourseLoaderCRS.cpp | 20 +- src/FileDownload.cpp | 6 +- src/Font.cpp | 4 +- src/GameCommand.cpp | 4 +- src/GameState.cpp | 2 +- src/NoteSkinManager.cpp | 4 +- src/NotesLoaderBMS.cpp | 2 +- src/NotesLoaderDWI.cpp | 1528 +++++++++++++++++----------------- src/NotesLoaderKSF.cpp | 1620 ++++++++++++++++++------------------- src/NotesLoaderSM.cpp | 16 +- src/NotesLoaderSMA.cpp | 10 +- src/NotesLoaderSSC.cpp | 16 +- src/OptionRowHandler.cpp | 6 +- src/PlayerOptions.cpp | 2 +- src/Profile.cpp | 2 +- src/ProfileManager.cpp | 2 +- src/RageBitmapTexture.cpp | 4 +- src/RageTexture.cpp | 236 +++--- src/RageUtil.cpp | 13 +- src/RageUtil.h | 6 +- src/ScreenEdit.cpp | 8 +- src/ScreenGameplay.cpp | 2 +- src/ScreenPackages.cpp | 1482 ++++++++++++++++----------------- src/SongOptions.cpp | 2 +- src/XmlFile.cpp | 4 +- 26 files changed, 2498 insertions(+), 2509 deletions(-) diff --git a/src/BGAnimationLayer.cpp b/src/BGAnimationLayer.cpp index 6c9cbfba2e..9766173eff 100644 --- a/src/BGAnimationLayer.cpp +++ b/src/BGAnimationLayer.cpp @@ -402,16 +402,16 @@ void BGAnimationLayer::LoadFromNode( const XNode* pNode ) { m_Type = TYPE_TILES; } - else if( StringToInt(type) == 1 ) + else if( std::stoi(type) == 1 ) { m_Type = TYPE_SPRITE; bStretch = true; } - else if( StringToInt(type) == 2 ) + else if( std::stoi(type) == 2 ) { m_Type = TYPE_PARTICLES; } - else if( StringToInt(type) == 3 ) + else if( std::stoi(type) == 3 ) { m_Type = TYPE_TILES; } diff --git a/src/CourseLoaderCRS.cpp b/src/CourseLoaderCRS.cpp index a6b97fb4a4..3ac48d88be 100644 --- a/src/CourseLoaderCRS.cpp +++ b/src/CourseLoaderCRS.cpp @@ -87,7 +87,7 @@ bool CourseLoaderCRS::LoadFromMsd( const RString &sPath, const MsdFile &msd, Cou } else if( sValueName.EqualsNoCase("LIVES") ) { - out.m_iLives = max( StringToInt(sParams[1]), 0 ); + out.m_iLives = max( std::stoi(sParams[1]), 0 ); } else if( sValueName.EqualsNoCase("GAINSECONDS") ) { @@ -97,7 +97,7 @@ bool CourseLoaderCRS::LoadFromMsd( const RString &sPath, const MsdFile &msd, Cou { if( sParams.params.size() == 2 ) { - out.m_iCustomMeter[Difficulty_Medium] = max( StringToInt(sParams[1]), 0 ); /* compat */ + out.m_iCustomMeter[Difficulty_Medium] = max( std::stoi(sParams[1]), 0 ); /* compat */ } else if( sParams.params.size() == 3 ) { @@ -107,7 +107,7 @@ bool CourseLoaderCRS::LoadFromMsd( const RString &sPath, const MsdFile &msd, Cou LOG->UserLog( "Course file", sPath, "contains an invalid #METER string: \"%s\"", sParams[1].c_str() ); continue; } - out.m_iCustomMeter[cd] = max( StringToInt(sParams[2]), 0 ); + out.m_iCustomMeter[cd] = max( std::stoi(sParams[2]), 0 ); } } @@ -168,7 +168,7 @@ bool CourseLoaderCRS::LoadFromMsd( const RString &sPath, const MsdFile &msd, Cou // most played if( sParams[1].Left(strlen("BEST")) == "BEST" ) { - int iChooseIndex = StringToInt( sParams[1].Right(sParams[1].size()-strlen("BEST")) ) - 1; + int iChooseIndex = std::stoi( sParams[1].Right(sParams[1].size()-strlen("BEST")) ) - 1; if( iChooseIndex > iNumSongs ) { // looking up a song that doesn't exist. @@ -185,7 +185,7 @@ bool CourseLoaderCRS::LoadFromMsd( const RString &sPath, const MsdFile &msd, Cou // least played else if( sParams[1].Left(strlen("WORST")) == "WORST" ) { - int iChooseIndex = StringToInt( sParams[1].Right(sParams[1].size()-strlen("BEST")) ) - 1; + int iChooseIndex = std::stoi( sParams[1].Right(sParams[1].size()-strlen("BEST")) ) - 1; if( iChooseIndex > iNumSongs ) { // looking up a song that doesn't exist. @@ -202,14 +202,14 @@ bool CourseLoaderCRS::LoadFromMsd( const RString &sPath, const MsdFile &msd, Cou // best grades else if( sParams[1].Left(strlen("GRADEBEST")) == "GRADEBEST" ) { - new_entry.iChooseIndex = StringToInt( sParams[1].Right(sParams[1].size()-strlen("GRADEBEST")) ) - 1; + new_entry.iChooseIndex = std::stoi( sParams[1].Right(sParams[1].size()-strlen("GRADEBEST")) ) - 1; CLAMP( new_entry.iChooseIndex, 0, 500 ); new_entry.songSort = SongSort_TopGrades; } // worst grades else if( sParams[1].Left(strlen("GRADEWORST")) == "GRADEWORST" ) { - new_entry.iChooseIndex = StringToInt( sParams[1].Right(sParams[1].size()-strlen("GRADEWORST")) ) - 1; + new_entry.iChooseIndex = std::stoi( sParams[1].Right(sParams[1].size()-strlen("GRADEWORST")) ) - 1; CLAMP( new_entry.iChooseIndex, 0, 500 ); new_entry.songSort = SongSort_LowestGrades; } @@ -308,7 +308,7 @@ bool CourseLoaderCRS::LoadFromMsd( const RString &sPath, const MsdFile &msd, Cou else if( !sMod.CompareNoCase("nodifficult") ) new_entry.bNoDifficult = true; else if( sMod.length() > 5 && !sMod.Left(5).CompareNoCase("award") ) - new_entry.iGainLives = StringToInt( sMod.substr(5) ); + new_entry.iGainLives = std::stoi( sMod.substr(5) ); else continue; mods.erase( mods.begin() + j ); @@ -330,8 +330,8 @@ bool CourseLoaderCRS::LoadFromMsd( const RString &sPath, const MsdFile &msd, Cou else if( bFromCache && !sValueName.EqualsNoCase("RADAR") ) { - StepsType st = (StepsType) StringToInt(sParams[1]); - CourseDifficulty cd = (CourseDifficulty) StringToInt( sParams[2] ); + StepsType st = (StepsType) std::stoi(sParams[1]); + CourseDifficulty cd = (CourseDifficulty) std::stoi( sParams[2] ); RadarValues rv; rv.FromString( sParams[3] ); diff --git a/src/FileDownload.cpp b/src/FileDownload.cpp index 75249845d0..dc0eea7bc5 100644 --- a/src/FileDownload.cpp +++ b/src/FileDownload.cpp @@ -267,14 +267,14 @@ void FileTransfer::HTTPUpdate() m_sResponseName = "Malformed response."; return; } - m_iResponseCode = StringToInt(m_sBUFFER.substr(i+1,j-i)); + m_iResponseCode = std::stoi(m_sBUFFER.substr(i+1,j-i)); m_sResponseName = m_sBUFFER.substr( j+1, k-j ); i = m_sBUFFER.find("Content-Length:"); j = m_sBUFFER.find("\n", i+1 ); if( i != string::npos ) - m_iTotalBytes = StringToInt(m_sBUFFER.substr(i+16,j-i)); + m_iTotalBytes = std::stoi(m_sBUFFER.substr(i+16,j-i)); else m_iTotalBytes = -1; // We don't know, so go until disconnect @@ -345,7 +345,7 @@ bool FileTransfer::ParseHTTPAddress( const RString &URL, RString &sProto, RStrin sServer = asMatches[1]; if( asMatches[3] != "" ) { - iPort = StringToInt(asMatches[3]); + iPort = std::stoi(asMatches[3]); if( iPort == 0 ) return false; } diff --git a/src/Font.cpp b/src/Font.cpp index 7f2e7c97e7..77ec7e0742 100644 --- a/src/Font.cpp +++ b/src/Font.cpp @@ -428,7 +428,7 @@ void Font::LoadFontPageSettings( FontPageSettings &cfg, IniFile &ini, const RStr // If val is an integer, it's a width, eg. "10=27". if( IsAnInt(sName) ) { - cfg.m_mapGlyphWidths[StringToInt(sName)] = pValue->GetValue(); + cfg.m_mapGlyphWidths[std::stoi(sName)] = pValue->GetValue(); continue; } @@ -530,7 +530,7 @@ void Font::LoadFontPageSettings( FontPageSettings &cfg, IniFile &ini, const RStr TrimLeft( sRowStr ); ASSERT( IsAnInt(sRowStr) ); - const int iRow = StringToInt( sRowStr ); + const int iRow = std::stoi( sRowStr ); const int iFirstFrame = iRow * iNumFramesWide; if( iRow > iNumFramesHigh ) diff --git a/src/GameCommand.cpp b/src/GameCommand.cpp index 90370e47b7..638a838680 100644 --- a/src/GameCommand.cpp +++ b/src/GameCommand.cpp @@ -333,12 +333,12 @@ void GameCommand::LoadOne( const Command& cmd ) else if( sName == "weight" ) { - m_iWeightPounds = StringToInt( sValue ); + m_iWeightPounds = std::stoi( sValue ); } else if( sName == "goalcalories" ) { - m_iGoalCalories = StringToInt( sValue ); + m_iGoalCalories = std::stoi( sValue ); } else if( sName == "goaltype" ) diff --git a/src/GameState.cpp b/src/GameState.cpp index eee5d4df46..9a64ae9fc1 100644 --- a/src/GameState.cpp +++ b/src/GameState.cpp @@ -233,7 +233,7 @@ void GameState::ApplyCmdline() RString sPlayer; for( int i = 0; GetCommandlineArgument( "player", &sPlayer, i ); ++i ) { - int pn = StringToInt( sPlayer )-1; + int pn = std::stoi( sPlayer )-1; if( !IsAnInt( sPlayer ) || pn < 0 || pn >= NUM_PLAYERS ) RageException::Throw( "Invalid argument \"--player=%s\".", sPlayer.c_str() ); diff --git a/src/NoteSkinManager.cpp b/src/NoteSkinManager.cpp index f4bc91e30d..b3f167adce 100644 --- a/src/NoteSkinManager.cpp +++ b/src/NoteSkinManager.cpp @@ -253,7 +253,7 @@ RString NoteSkinManager::GetMetric( const RString &sButtonName, const RString &s int NoteSkinManager::GetMetricI( const RString &sButtonName, const RString &sValueName ) { - return StringToInt( GetMetric(sButtonName,sValueName) ); + return std::stoi( GetMetric(sButtonName,sValueName) ); } float NoteSkinManager::GetMetricF( const RString &sButtonName, const RString &sValueName ) @@ -264,7 +264,7 @@ float NoteSkinManager::GetMetricF( const RString &sButtonName, const RString &sV bool NoteSkinManager::GetMetricB( const RString &sButtonName, const RString &sValueName ) { // Could also call GetMetricI here...hmm. - return StringToInt( GetMetric(sButtonName,sValueName) ) != 0; + return std::stoi( GetMetric(sButtonName,sValueName) ) != 0; } apActorCommands NoteSkinManager::GetMetricA( const RString &sButtonName, const RString &sValueName ) diff --git a/src/NotesLoaderBMS.cpp b/src/NotesLoaderBMS.cpp index 473a7fff39..a26391406a 100644 --- a/src/NotesLoaderBMS.cpp +++ b/src/NotesLoaderBMS.cpp @@ -565,7 +565,7 @@ void BMSChartReader::ReadHeaders() } else if( it->first == "#playlevel" ) { - out->SetMeter( StringToInt(it->second) ); + out->SetMeter( std::stoi(it->second) ); } } } diff --git a/src/NotesLoaderDWI.cpp b/src/NotesLoaderDWI.cpp index 4009744d9a..972e4948b9 100644 --- a/src/NotesLoaderDWI.cpp +++ b/src/NotesLoaderDWI.cpp @@ -1,764 +1,764 @@ -#include "global.h" -#include "NotesLoaderDWI.h" -#include "RageLog.h" -#include "MsdFile.h" -#include "RageUtil.h" -#include "RageUtil_CharConversions.h" -#include "NoteData.h" -#include "Song.h" -#include "Steps.h" -#include "GameInput.h" -#include "NotesLoader.h" -#include "PrefsManager.h" -#include "Difficulty.h" - -#include - -Difficulty DwiCompatibleStringToDifficulty( const RString& sDC ); - -static std::map g_mapDanceNoteToNoteDataColumn; - -/** @brief The different types of core DWI arrows and pads. */ -enum -{ - DANCE_NOTE_NONE = 0, - DANCE_NOTE_PAD1_LEFT, - DANCE_NOTE_PAD1_UPLEFT, - DANCE_NOTE_PAD1_DOWN, - DANCE_NOTE_PAD1_UP, - DANCE_NOTE_PAD1_UPRIGHT, - DANCE_NOTE_PAD1_RIGHT, - DANCE_NOTE_PAD2_LEFT, - DANCE_NOTE_PAD2_UPLEFT, - DANCE_NOTE_PAD2_DOWN, - DANCE_NOTE_PAD2_UP, - DANCE_NOTE_PAD2_UPRIGHT, - DANCE_NOTE_PAD2_RIGHT -}; - -/** - * @brief Turn the individual character to the proper note. - * @param c The character in question. - * @param i The player. - * @param note1Out The first result based on the character. - * @param note2Out The second result based on the character. - * @param sPath the path to the file. - */ -static void DWIcharToNote( char c, GameController i, int ¬e1Out, int ¬e2Out, const RString &sPath ) -{ - switch( c ) - { - case '0': note1Out = DANCE_NOTE_NONE; note2Out = DANCE_NOTE_NONE; break; - case '1': note1Out = DANCE_NOTE_PAD1_DOWN; note2Out = DANCE_NOTE_PAD1_LEFT; break; - case '2': note1Out = DANCE_NOTE_PAD1_DOWN; note2Out = DANCE_NOTE_NONE; break; - case '3': note1Out = DANCE_NOTE_PAD1_DOWN; note2Out = DANCE_NOTE_PAD1_RIGHT; break; - case '4': note1Out = DANCE_NOTE_PAD1_LEFT; note2Out = DANCE_NOTE_NONE; break; - case '5': note1Out = DANCE_NOTE_NONE; note2Out = DANCE_NOTE_NONE; break; - case '6': note1Out = DANCE_NOTE_PAD1_RIGHT; note2Out = DANCE_NOTE_NONE; break; - case '7': note1Out = DANCE_NOTE_PAD1_UP; note2Out = DANCE_NOTE_PAD1_LEFT; break; - case '8': note1Out = DANCE_NOTE_PAD1_UP; note2Out = DANCE_NOTE_NONE; break; - case '9': note1Out = DANCE_NOTE_PAD1_UP; note2Out = DANCE_NOTE_PAD1_RIGHT; break; - case 'A': note1Out = DANCE_NOTE_PAD1_UP; note2Out = DANCE_NOTE_PAD1_DOWN; break; - case 'B': note1Out = DANCE_NOTE_PAD1_LEFT; note2Out = DANCE_NOTE_PAD1_RIGHT; break; - case 'C': note1Out = DANCE_NOTE_PAD1_UPLEFT; note2Out = DANCE_NOTE_NONE; break; - case 'D': note1Out = DANCE_NOTE_PAD1_UPRIGHT; note2Out = DANCE_NOTE_NONE; break; - case 'E': note1Out = DANCE_NOTE_PAD1_LEFT; note2Out = DANCE_NOTE_PAD1_UPLEFT; break; - case 'F': note1Out = DANCE_NOTE_PAD1_UPLEFT; note2Out = DANCE_NOTE_PAD1_DOWN; break; - case 'G': note1Out = DANCE_NOTE_PAD1_UPLEFT; note2Out = DANCE_NOTE_PAD1_UP; break; - case 'H': note1Out = DANCE_NOTE_PAD1_UPLEFT; note2Out = DANCE_NOTE_PAD1_RIGHT; break; - case 'I': note1Out = DANCE_NOTE_PAD1_LEFT; note2Out = DANCE_NOTE_PAD1_UPRIGHT; break; - case 'J': note1Out = DANCE_NOTE_PAD1_DOWN; note2Out = DANCE_NOTE_PAD1_UPRIGHT; break; - case 'K': note1Out = DANCE_NOTE_PAD1_UP; note2Out = DANCE_NOTE_PAD1_UPRIGHT; break; - case 'L': note1Out = DANCE_NOTE_PAD1_UPRIGHT; note2Out = DANCE_NOTE_PAD1_RIGHT; break; - case 'M': note1Out = DANCE_NOTE_PAD1_UPLEFT; note2Out = DANCE_NOTE_PAD1_UPRIGHT; break; - default: - LOG->UserLog( "Song file", sPath, "has an invalid DWI note character '%c'.", c ); - note1Out = DANCE_NOTE_NONE; note2Out = DANCE_NOTE_NONE; break; - } - - switch( i ) - { - case GameController_1: - break; - case GameController_2: - if( note1Out != DANCE_NOTE_NONE ) - note1Out += 6; - if( note2Out != DANCE_NOTE_NONE ) - note2Out += 6; - break; - default: - FAIL_M(ssprintf("Invalid GameController: %i", i)); - } -} - -/** - * @brief Determine the note column[s] to place notes. - * @param c The character in question. - * @param i The player. - * @param col1Out The first result based on the character. - * @param col2Out The second result based on the character. - * @param sPath the path to the file. - */ -static void DWIcharToNoteCol( char c, GameController i, int &col1Out, int &col2Out, const RString &sPath ) -{ - int note1, note2; - DWIcharToNote( c, i, note1, note2, sPath ); - - if( note1 != DANCE_NOTE_NONE ) - col1Out = g_mapDanceNoteToNoteDataColumn[note1]; - else - col1Out = -1; - - if( note2 != DANCE_NOTE_NONE ) - col2Out = g_mapDanceNoteToNoteDataColumn[note2]; - else - col2Out = -1; -} - -/** - * @brief Determine if the note in question is a 192nd note. - * - * DWI used to use <...> to indicate 1/192nd notes; at some - * point, <...> was changed to indicate jumps, and `' was used for - * 1/192nds. So, we have to do a check to figure out what it really - * means. If it contains 0s, it's most likely 192nds; otherwise, - * it's most likely a jump. Search for a 0 before the next >: - * @param sStepData the step data. - * @param pos the position of the step data. - * @return true if it's a 192nd note, false otherwise. - */ -static bool Is192( const RString &sStepData, size_t pos ) -{ - while( pos < sStepData.size() ) - { - if( sStepData[pos] == '>' ) - return false; - if( sStepData[pos] == '0' ) - return true; - ++pos; - } - - return false; -} -/** @brief All DWI files use 4 beats per measure. */ -const int BEATS_PER_MEASURE = 4; - -/* We prefer the normal names; recognize a number of others, too. (They'll get - * normalized when written to SMs, etc.) */ -Difficulty DwiCompatibleStringToDifficulty( const RString& sDC ) -{ - RString s2 = sDC; - s2.MakeLower(); - if( s2 == "beginner" ) return Difficulty_Beginner; - else if( s2 == "easy" ) return Difficulty_Easy; - else if( s2 == "basic" ) return Difficulty_Easy; - else if( s2 == "light" ) return Difficulty_Easy; - else if( s2 == "medium" ) return Difficulty_Medium; - else if( s2 == "another" ) return Difficulty_Medium; - else if( s2 == "trick" ) return Difficulty_Medium; - else if( s2 == "standard" ) return Difficulty_Medium; - else if( s2 == "difficult") return Difficulty_Medium; - else if( s2 == "hard" ) return Difficulty_Hard; - else if( s2 == "ssr" ) return Difficulty_Hard; - else if( s2 == "maniac" ) return Difficulty_Hard; - else if( s2 == "heavy" ) return Difficulty_Hard; - else if( s2 == "smaniac" ) return Difficulty_Challenge; - else if( s2 == "challenge" ) return Difficulty_Challenge; - else if( s2 == "expert" ) return Difficulty_Challenge; - else if( s2 == "oni" ) return Difficulty_Challenge; - else if( s2 == "edit" ) return Difficulty_Edit; - else return Difficulty_Invalid; -} - -static StepsType GetTypeFromMode(const RString &mode) -{ - if( mode == "SINGLE" ) - return StepsType_dance_single; - else if( mode == "DOUBLE" ) - return StepsType_dance_double; - else if( mode == "COUPLE" ) - return StepsType_dance_couple; - else if( mode == "SOLO" ) - return StepsType_dance_solo; - ASSERT_M(0, "Unrecognized DWI notes format " + mode + "!"); - return StepsType_Invalid; // just in case. -} - -static NoteData ParseNoteData(RString &step1, RString &step2, - Steps &out, const RString &path) -{ - g_mapDanceNoteToNoteDataColumn.clear(); - switch( out.m_StepsType ) - { - case StepsType_dance_single: - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3; - break; - case StepsType_dance_double: - case StepsType_dance_couple: - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_LEFT] = 4; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_DOWN] = 5; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_UP] = 6; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_RIGHT] = 7; - break; - case StepsType_dance_solo: - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPLEFT] = 1; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 2; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 3; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPRIGHT] = 4; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 5; - break; - DEFAULT_FAIL( out.m_StepsType ); - } - - NoteData newNoteData; - newNoteData.SetNumTracks( g_mapDanceNoteToNoteDataColumn.size() ); - - for( int pad=0; pad<2; pad++ ) // foreach pad - { - RString sStepData; - switch( pad ) - { - case 0: - sStepData = step1; - break; - case 1: - if( step2 == "" ) // no data - continue; // skip - sStepData = step2; - break; - DEFAULT_FAIL( pad ); - } - - sStepData.Replace("\n", ""); - sStepData.Replace("\r", ""); - sStepData.Replace("\t", ""); - sStepData.Replace(" ", ""); - - double fCurrentBeat = 0; - double fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE; - - for( size_t i=0; i': - fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE; - break; - - default: // this is a note character - { - if( c == '!' ) - { - LOG->UserLog( - "Song file", - path, - "has an unexpected character: '!'." ); - continue; - } - - bool jump = false; - if( c == '<' ) - { - /* Arr. Is this a jump or a 1/192 marker? */ - if( Is192( sStepData, i ) ) - { - fCurrentIncrementer = 1.0/192 * BEATS_PER_MEASURE; - break; - } - - /* It's a jump. - * We need to keep reading notes until we hit a >. */ - jump = true; - i++; - } - - const int iIndex = BeatToNoteRow( (float)fCurrentBeat ); - i--; - do { - c = sStepData[i++]; - - if( jump && c == '>' ) - break; - - int iCol1, iCol2; - DWIcharToNoteCol( - c, - (GameController)pad, - iCol1, - iCol2, - path ); - - if( iCol1 != -1 ) - newNoteData.SetTapNote(iCol1, - iIndex, - TAP_ORIGINAL_TAP); - if( iCol2 != -1 ) - newNoteData.SetTapNote(iCol2, - iIndex, - TAP_ORIGINAL_TAP); - - if(i>=sStepData.length()) - { - break; - //we ran out of data - //while looking for the ending > mark - } - - if( sStepData[i] == '!' ) - { - i++; - const char holdChar = sStepData[i++]; - - DWIcharToNoteCol(holdChar, - (GameController)pad, - iCol1, - iCol2, - path ); - - if( iCol1 != -1 ) - newNoteData.SetTapNote(iCol1, - iIndex, - TAP_ORIGINAL_HOLD_HEAD); - if( iCol2 != -1 ) - newNoteData.SetTapNote(iCol2, - iIndex, - TAP_ORIGINAL_HOLD_HEAD); - } - } - while( jump ); - fCurrentBeat += fCurrentIncrementer; - } - break; - } - } - } - - /* Fill in iDuration. */ - for( int t=0; tUserLog("Song file", - path, - "failed to close a hold note in \"%s\" on track %i", - DifficultyToString(out.GetDifficulty()).c_str(), - t); - - newNoteData.SetTapNote( t, iHeadRow, TAP_EMPTY ); - } - } - } - - ASSERT( newNoteData.GetNumTracks() > 0 ); - return newNoteData; -} - -/** - * @brief Look through the notes tag to extract the data. - * @param sMode the steps type. - * @param sDescription the difficulty. - * @param sNumFeet the meter. - * @param sStepData1 the guaranteed step data. - * @param sStepData2 used if sMode is double or couple. - * @param out the step data. - * @param sPath the path to the file. - * @return the success or failure of the operation. - */ -static bool LoadFromDWITokens( - RString sMode, - RString sDescription, - RString sNumFeet, - RString sStepData1, - RString sStepData2, - Steps &out, - const RString &sPath ) -{ - CHECKPOINT_M( "DWILoader::LoadFromDWITokens()" ); - - out.m_StepsType = GetTypeFromMode(sMode); - - // if the meter is empty, force it to 1. - if( sNumFeet.empty() ) - sNumFeet = "1"; - - out.SetMeter(StringToInt(sNumFeet)); - - out.SetDifficulty( DwiCompatibleStringToDifficulty(sDescription) ); - - out.SetNoteData( ParseNoteData(sStepData1, sStepData2, out, sPath) ); - - out.TidyUpData(); - - out.SetSavedToDisk( true ); // we're loading from disk, so this is by definintion already saved - return true; -} - -/** - * @brief Turn the DWI style timestamp into a compatible time for our system. - * - * This value can be in either "HH:MM:SS.sssss", "MM:SS.sssss", "SSS.sssss" - * or milliseconds. - * @param arg1 Either hours, minutes, or seconds, depending on other args. - * @param arg2 Either minutes or seconds, depending on other args. - * @param arg3 Seconds if not empty. - * @return the proper timestamp. - */ -static float ParseBrokenDWITimestamp( const RString &arg1, const RString &arg2, const RString &arg3 ) -{ - if( arg1.empty() ) - return 0; - - /* 1+ args */ - if( arg2.empty() ) - { - /* If the value contains a period, treat it as seconds; otherwise ms. */ - if( arg1.find_first_of(".") != arg1.npos ) - return StringToFloat( arg1 ); - else - return StringToFloat( arg1 ) / 1000.f; - } - - /* 2+ args */ - if( arg3.empty() ) - return HHMMSSToSeconds( arg1+":"+arg2 ); - - /* 3+ args */ - return HHMMSSToSeconds( arg1+":"+arg2+":"+arg3 ); -} - - -void DWILoader::GetApplicableFiles( const RString &sPath, vector &out ) -{ - GetDirListing( sPath + RString("*.dwi"), out ); -} - -bool DWILoader::LoadNoteDataFromSimfile( const RString &path, Steps &out ) -{ - MsdFile msd; - if( !msd.ReadFile( path, false ) ) // don't unescape - { - LOG->UserLog("Song file", - path, - "couldn't be opened: %s", - msd.GetError().c_str() ); - return false; - } - - for( unsigned i=0; i &BlacklistedImages ) -{ - vector aFileNames; - GetApplicableFiles( sPath_, aFileNames ); - - if( aFileNames.size() > 1 ) - { - LOG->UserLog( "Song", sPath_, "has more than one DWI file. There should be only one!" ); - return false; - } - - /* We should have exactly one; if we had none, we shouldn't have been called to begin with. */ - ASSERT( aFileNames.size() == 1 ); - const RString sPath = sPath_ + aFileNames[0]; - - LOG->Trace( "Song::LoadFromDWIFile(%s)", sPath.c_str() ); - - MsdFile msd; - if( !msd.ReadFile( sPath, false ) ) // don't unescape - { - LOG->UserLog( "Song file", sPath, "couldn't be opened: %s", msd.GetError().c_str() ); - return false; - } - - out.m_sSongFileName = sPath; - - for( unsigned i=0; iUserLog( "Song file", sPath, "has tag \"%s\" with no parameters.", sValueName.c_str() ); - continue; - } - - // handle the data - if( sValueName.EqualsNoCase("FILE") ) - out.m_sMusicFile = sParams[1]; - - else if( sValueName.EqualsNoCase("TITLE") ) - { - NotesLoader::GetMainAndSubTitlesFromFullTitle( sParams[1], out.m_sMainTitle, out.m_sSubTitle ); - - /* As far as I know, there's no spec on the encoding of this text. (I didn't - * look very hard, though.) I've seen at least one file in ISO-8859-1. */ - ConvertString( out.m_sMainTitle, "utf-8,english" ); - ConvertString( out.m_sSubTitle, "utf-8,english" ); - } - - else if( sValueName.EqualsNoCase("ARTIST") ) - { - out.m_sArtist = sParams[1]; - ConvertString( out.m_sArtist, "utf-8,english" ); - } - - else if( sValueName.EqualsNoCase("GENRE") ) - { - out.m_sGenre = sParams[1]; - ConvertString( out.m_sGenre, "utf-8,english" ); - } - - else if( sValueName.EqualsNoCase("CDTITLE") ) - out.m_sCDTitleFile = sParams[1]; - - else if( sValueName.EqualsNoCase("BPM") ) - { - const float fBPM = StringToFloat( sParams[1] ); - - if( unlikely(fBPM <= 0.0f && !PREFSMAN->m_bQuirksMode) ) - { - LOG->UserLog("Song file", sPath, "has an invalid BPM change at beat %f, BPM %f.", - 0.0f, fBPM ); - } - else - { - out.m_SongTiming.AddSegment( BPMSegment(0, fBPM) ); - } - } - else if( sValueName.EqualsNoCase("DISPLAYBPM") ) - { - // #DISPLAYBPM:[xxx..xxx]|[xxx]|[*]; - int iMin, iMax; - /* We can't parse this as a float with sscanf, since '.' is a valid - * character in a float. (We could do it with a regex, but it's not - * worth bothering with since we don't display fractional BPM anyway.) */ - if( sscanf( sParams[1], "%i..%i", &iMin, &iMax ) == 2 ) - { - out.m_DisplayBPMType = DISPLAY_BPM_SPECIFIED; - out.m_fSpecifiedBPMMin = (float) iMin; - out.m_fSpecifiedBPMMax = (float) iMax; - } - else if( sscanf( sParams[1], "%i", &iMin ) == 1 ) - { - out.m_DisplayBPMType = DISPLAY_BPM_SPECIFIED; - out.m_fSpecifiedBPMMin = out.m_fSpecifiedBPMMax = (float) iMin; - } - else - { - out.m_DisplayBPMType = DISPLAY_BPM_RANDOM; - } - } - - else if( sValueName.EqualsNoCase("GAP") ) - // the units of GAP is 1/1000 second - out.m_SongTiming.m_fBeat0OffsetInSeconds = -StringToInt(sParams[1]) / 1000.0f; - - else if( sValueName.EqualsNoCase("SAMPLESTART") ) - out.m_fMusicSampleStartSeconds = ParseBrokenDWITimestamp(sParams[1], sParams[2], sParams[3]); - - else if( sValueName.EqualsNoCase("SAMPLELENGTH") ) - out.m_fMusicSampleLengthSeconds = ParseBrokenDWITimestamp(sParams[1], sParams[2], sParams[3]); - - else if( sValueName.EqualsNoCase("FREEZE") ) - { - vector arrayFreezeExpressions; - split( sParams[1], ",", arrayFreezeExpressions ); - - for( unsigned f=0; f arrayFreezeValues; - split( arrayFreezeExpressions[f], "=", arrayFreezeValues ); - if( arrayFreezeValues.size() != 2 ) - { - LOG->UserLog( "Song file", sPath, "has an invalid FREEZE: '%s'.", arrayFreezeExpressions[f].c_str() ); - continue; - } - int iFreezeRow = BeatToNoteRow( StringToFloat(arrayFreezeValues[0]) / 4.0f ); - float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] ) / 1000.0f; - - out.m_SongTiming.AddSegment( StopSegment(iFreezeRow, fFreezeSeconds) ); -// LOG->Trace( "Adding a freeze segment: beat: %f, seconds = %f", fFreezeBeat, fFreezeSeconds ); - } - } - - else if( sValueName.EqualsNoCase("CHANGEBPM") || sValueName.EqualsNoCase("BPMCHANGE") ) - { - vector arrayBPMChangeExpressions; - split( sParams[1], ",", arrayBPMChangeExpressions ); - - for( unsigned b=0; b arrayBPMChangeValues; - split( arrayBPMChangeExpressions[b], "=", arrayBPMChangeValues ); - if( arrayBPMChangeValues.size() != 2 ) - { - LOG->UserLog( "Song file", sPath, "has an invalid CHANGEBPM: '%s'.", arrayBPMChangeExpressions[b].c_str() ); - continue; - } - - int iStartIndex = BeatToNoteRow( StringToFloat(arrayBPMChangeValues[0]) / 4.0f ); - float fBPM = StringToFloat( arrayBPMChangeValues[1] ); - if( fBPM > 0.0f ) - out.m_SongTiming.AddSegment( BPMSegment(iStartIndex, fBPM) ); - else - LOG->UserLog( "Song file", sPath, "has an invalid BPM change at beat %f, BPM %f.", - NoteRowToBeat(iStartIndex), fBPM ); - } - } - - else if( sValueName.EqualsNoCase("SINGLE") || - sValueName.EqualsNoCase("DOUBLE") || - sValueName.EqualsNoCase("COUPLE") || - sValueName.EqualsNoCase("SOLO") ) - { - Steps* pNewNotes = out.CreateSteps(); - LoadFromDWITokens( - sParams[0], - sParams[1], - sParams[2], - sParams[3], - (iNumParams==5) ? sParams[4] : RString(""), - *pNewNotes, - sPath - ); - if( pNewNotes->m_StepsType != StepsType_Invalid ) - { - pNewNotes->SetFilename( sPath ); - out.AddSteps( pNewNotes ); - } - else - delete pNewNotes; - } - else if( sValueName.EqualsNoCase("DISPLAYTITLE") || - sValueName.EqualsNoCase("DISPLAYARTIST") ) - { - /* We don't want to support these tags. However, we don't want - * to pick up images used here as song images (eg. banners). */ - RString param = sParams[1]; - /* "{foo} ... {foo2}" */ - size_t pos = 0; - while( pos < RString::npos ) - { - - size_t startpos = param.find('{', pos); - if( startpos == RString::npos ) - break; - size_t endpos = param.find('}', startpos); - if( endpos == RString::npos ) - break; - - RString sub = param.substr( startpos+1, endpos-startpos-1 ); - - pos = endpos + 1; - - sub.MakeLower(); - BlacklistedImages.insert( sub ); - } - } - else - { - // do nothing. We don't care about this value name - } - } - return true; -} - -/* - * (c) 2001-2004 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. - */ +#include "global.h" +#include "NotesLoaderDWI.h" +#include "RageLog.h" +#include "MsdFile.h" +#include "RageUtil.h" +#include "RageUtil_CharConversions.h" +#include "NoteData.h" +#include "Song.h" +#include "Steps.h" +#include "GameInput.h" +#include "NotesLoader.h" +#include "PrefsManager.h" +#include "Difficulty.h" + +#include + +Difficulty DwiCompatibleStringToDifficulty( const RString& sDC ); + +static std::map g_mapDanceNoteToNoteDataColumn; + +/** @brief The different types of core DWI arrows and pads. */ +enum +{ + DANCE_NOTE_NONE = 0, + DANCE_NOTE_PAD1_LEFT, + DANCE_NOTE_PAD1_UPLEFT, + DANCE_NOTE_PAD1_DOWN, + DANCE_NOTE_PAD1_UP, + DANCE_NOTE_PAD1_UPRIGHT, + DANCE_NOTE_PAD1_RIGHT, + DANCE_NOTE_PAD2_LEFT, + DANCE_NOTE_PAD2_UPLEFT, + DANCE_NOTE_PAD2_DOWN, + DANCE_NOTE_PAD2_UP, + DANCE_NOTE_PAD2_UPRIGHT, + DANCE_NOTE_PAD2_RIGHT +}; + +/** + * @brief Turn the individual character to the proper note. + * @param c The character in question. + * @param i The player. + * @param note1Out The first result based on the character. + * @param note2Out The second result based on the character. + * @param sPath the path to the file. + */ +static void DWIcharToNote( char c, GameController i, int ¬e1Out, int ¬e2Out, const RString &sPath ) +{ + switch( c ) + { + case '0': note1Out = DANCE_NOTE_NONE; note2Out = DANCE_NOTE_NONE; break; + case '1': note1Out = DANCE_NOTE_PAD1_DOWN; note2Out = DANCE_NOTE_PAD1_LEFT; break; + case '2': note1Out = DANCE_NOTE_PAD1_DOWN; note2Out = DANCE_NOTE_NONE; break; + case '3': note1Out = DANCE_NOTE_PAD1_DOWN; note2Out = DANCE_NOTE_PAD1_RIGHT; break; + case '4': note1Out = DANCE_NOTE_PAD1_LEFT; note2Out = DANCE_NOTE_NONE; break; + case '5': note1Out = DANCE_NOTE_NONE; note2Out = DANCE_NOTE_NONE; break; + case '6': note1Out = DANCE_NOTE_PAD1_RIGHT; note2Out = DANCE_NOTE_NONE; break; + case '7': note1Out = DANCE_NOTE_PAD1_UP; note2Out = DANCE_NOTE_PAD1_LEFT; break; + case '8': note1Out = DANCE_NOTE_PAD1_UP; note2Out = DANCE_NOTE_NONE; break; + case '9': note1Out = DANCE_NOTE_PAD1_UP; note2Out = DANCE_NOTE_PAD1_RIGHT; break; + case 'A': note1Out = DANCE_NOTE_PAD1_UP; note2Out = DANCE_NOTE_PAD1_DOWN; break; + case 'B': note1Out = DANCE_NOTE_PAD1_LEFT; note2Out = DANCE_NOTE_PAD1_RIGHT; break; + case 'C': note1Out = DANCE_NOTE_PAD1_UPLEFT; note2Out = DANCE_NOTE_NONE; break; + case 'D': note1Out = DANCE_NOTE_PAD1_UPRIGHT; note2Out = DANCE_NOTE_NONE; break; + case 'E': note1Out = DANCE_NOTE_PAD1_LEFT; note2Out = DANCE_NOTE_PAD1_UPLEFT; break; + case 'F': note1Out = DANCE_NOTE_PAD1_UPLEFT; note2Out = DANCE_NOTE_PAD1_DOWN; break; + case 'G': note1Out = DANCE_NOTE_PAD1_UPLEFT; note2Out = DANCE_NOTE_PAD1_UP; break; + case 'H': note1Out = DANCE_NOTE_PAD1_UPLEFT; note2Out = DANCE_NOTE_PAD1_RIGHT; break; + case 'I': note1Out = DANCE_NOTE_PAD1_LEFT; note2Out = DANCE_NOTE_PAD1_UPRIGHT; break; + case 'J': note1Out = DANCE_NOTE_PAD1_DOWN; note2Out = DANCE_NOTE_PAD1_UPRIGHT; break; + case 'K': note1Out = DANCE_NOTE_PAD1_UP; note2Out = DANCE_NOTE_PAD1_UPRIGHT; break; + case 'L': note1Out = DANCE_NOTE_PAD1_UPRIGHT; note2Out = DANCE_NOTE_PAD1_RIGHT; break; + case 'M': note1Out = DANCE_NOTE_PAD1_UPLEFT; note2Out = DANCE_NOTE_PAD1_UPRIGHT; break; + default: + LOG->UserLog( "Song file", sPath, "has an invalid DWI note character '%c'.", c ); + note1Out = DANCE_NOTE_NONE; note2Out = DANCE_NOTE_NONE; break; + } + + switch( i ) + { + case GameController_1: + break; + case GameController_2: + if( note1Out != DANCE_NOTE_NONE ) + note1Out += 6; + if( note2Out != DANCE_NOTE_NONE ) + note2Out += 6; + break; + default: + FAIL_M(ssprintf("Invalid GameController: %i", i)); + } +} + +/** + * @brief Determine the note column[s] to place notes. + * @param c The character in question. + * @param i The player. + * @param col1Out The first result based on the character. + * @param col2Out The second result based on the character. + * @param sPath the path to the file. + */ +static void DWIcharToNoteCol( char c, GameController i, int &col1Out, int &col2Out, const RString &sPath ) +{ + int note1, note2; + DWIcharToNote( c, i, note1, note2, sPath ); + + if( note1 != DANCE_NOTE_NONE ) + col1Out = g_mapDanceNoteToNoteDataColumn[note1]; + else + col1Out = -1; + + if( note2 != DANCE_NOTE_NONE ) + col2Out = g_mapDanceNoteToNoteDataColumn[note2]; + else + col2Out = -1; +} + +/** + * @brief Determine if the note in question is a 192nd note. + * + * DWI used to use <...> to indicate 1/192nd notes; at some + * point, <...> was changed to indicate jumps, and `' was used for + * 1/192nds. So, we have to do a check to figure out what it really + * means. If it contains 0s, it's most likely 192nds; otherwise, + * it's most likely a jump. Search for a 0 before the next >: + * @param sStepData the step data. + * @param pos the position of the step data. + * @return true if it's a 192nd note, false otherwise. + */ +static bool Is192( const RString &sStepData, size_t pos ) +{ + while( pos < sStepData.size() ) + { + if( sStepData[pos] == '>' ) + return false; + if( sStepData[pos] == '0' ) + return true; + ++pos; + } + + return false; +} +/** @brief All DWI files use 4 beats per measure. */ +const int BEATS_PER_MEASURE = 4; + +/* We prefer the normal names; recognize a number of others, too. (They'll get + * normalized when written to SMs, etc.) */ +Difficulty DwiCompatibleStringToDifficulty( const RString& sDC ) +{ + RString s2 = sDC; + s2.MakeLower(); + if( s2 == "beginner" ) return Difficulty_Beginner; + else if( s2 == "easy" ) return Difficulty_Easy; + else if( s2 == "basic" ) return Difficulty_Easy; + else if( s2 == "light" ) return Difficulty_Easy; + else if( s2 == "medium" ) return Difficulty_Medium; + else if( s2 == "another" ) return Difficulty_Medium; + else if( s2 == "trick" ) return Difficulty_Medium; + else if( s2 == "standard" ) return Difficulty_Medium; + else if( s2 == "difficult") return Difficulty_Medium; + else if( s2 == "hard" ) return Difficulty_Hard; + else if( s2 == "ssr" ) return Difficulty_Hard; + else if( s2 == "maniac" ) return Difficulty_Hard; + else if( s2 == "heavy" ) return Difficulty_Hard; + else if( s2 == "smaniac" ) return Difficulty_Challenge; + else if( s2 == "challenge" ) return Difficulty_Challenge; + else if( s2 == "expert" ) return Difficulty_Challenge; + else if( s2 == "oni" ) return Difficulty_Challenge; + else if( s2 == "edit" ) return Difficulty_Edit; + else return Difficulty_Invalid; +} + +static StepsType GetTypeFromMode(const RString &mode) +{ + if( mode == "SINGLE" ) + return StepsType_dance_single; + else if( mode == "DOUBLE" ) + return StepsType_dance_double; + else if( mode == "COUPLE" ) + return StepsType_dance_couple; + else if( mode == "SOLO" ) + return StepsType_dance_solo; + ASSERT_M(0, "Unrecognized DWI notes format " + mode + "!"); + return StepsType_Invalid; // just in case. +} + +static NoteData ParseNoteData(RString &step1, RString &step2, + Steps &out, const RString &path) +{ + g_mapDanceNoteToNoteDataColumn.clear(); + switch( out.m_StepsType ) + { + case StepsType_dance_single: + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3; + break; + case StepsType_dance_double: + case StepsType_dance_couple: + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_LEFT] = 4; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_DOWN] = 5; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_UP] = 6; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_RIGHT] = 7; + break; + case StepsType_dance_solo: + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPLEFT] = 1; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 2; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 3; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPRIGHT] = 4; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 5; + break; + DEFAULT_FAIL( out.m_StepsType ); + } + + NoteData newNoteData; + newNoteData.SetNumTracks( g_mapDanceNoteToNoteDataColumn.size() ); + + for( int pad=0; pad<2; pad++ ) // foreach pad + { + RString sStepData; + switch( pad ) + { + case 0: + sStepData = step1; + break; + case 1: + if( step2 == "" ) // no data + continue; // skip + sStepData = step2; + break; + DEFAULT_FAIL( pad ); + } + + sStepData.Replace("\n", ""); + sStepData.Replace("\r", ""); + sStepData.Replace("\t", ""); + sStepData.Replace(" ", ""); + + double fCurrentBeat = 0; + double fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE; + + for( size_t i=0; i': + fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE; + break; + + default: // this is a note character + { + if( c == '!' ) + { + LOG->UserLog( + "Song file", + path, + "has an unexpected character: '!'." ); + continue; + } + + bool jump = false; + if( c == '<' ) + { + /* Arr. Is this a jump or a 1/192 marker? */ + if( Is192( sStepData, i ) ) + { + fCurrentIncrementer = 1.0/192 * BEATS_PER_MEASURE; + break; + } + + /* It's a jump. + * We need to keep reading notes until we hit a >. */ + jump = true; + i++; + } + + const int iIndex = BeatToNoteRow( (float)fCurrentBeat ); + i--; + do { + c = sStepData[i++]; + + if( jump && c == '>' ) + break; + + int iCol1, iCol2; + DWIcharToNoteCol( + c, + (GameController)pad, + iCol1, + iCol2, + path ); + + if( iCol1 != -1 ) + newNoteData.SetTapNote(iCol1, + iIndex, + TAP_ORIGINAL_TAP); + if( iCol2 != -1 ) + newNoteData.SetTapNote(iCol2, + iIndex, + TAP_ORIGINAL_TAP); + + if(i>=sStepData.length()) + { + break; + //we ran out of data + //while looking for the ending > mark + } + + if( sStepData[i] == '!' ) + { + i++; + const char holdChar = sStepData[i++]; + + DWIcharToNoteCol(holdChar, + (GameController)pad, + iCol1, + iCol2, + path ); + + if( iCol1 != -1 ) + newNoteData.SetTapNote(iCol1, + iIndex, + TAP_ORIGINAL_HOLD_HEAD); + if( iCol2 != -1 ) + newNoteData.SetTapNote(iCol2, + iIndex, + TAP_ORIGINAL_HOLD_HEAD); + } + } + while( jump ); + fCurrentBeat += fCurrentIncrementer; + } + break; + } + } + } + + /* Fill in iDuration. */ + for( int t=0; tUserLog("Song file", + path, + "failed to close a hold note in \"%s\" on track %i", + DifficultyToString(out.GetDifficulty()).c_str(), + t); + + newNoteData.SetTapNote( t, iHeadRow, TAP_EMPTY ); + } + } + } + + ASSERT( newNoteData.GetNumTracks() > 0 ); + return newNoteData; +} + +/** + * @brief Look through the notes tag to extract the data. + * @param sMode the steps type. + * @param sDescription the difficulty. + * @param sNumFeet the meter. + * @param sStepData1 the guaranteed step data. + * @param sStepData2 used if sMode is double or couple. + * @param out the step data. + * @param sPath the path to the file. + * @return the success or failure of the operation. + */ +static bool LoadFromDWITokens( + RString sMode, + RString sDescription, + RString sNumFeet, + RString sStepData1, + RString sStepData2, + Steps &out, + const RString &sPath ) +{ + CHECKPOINT_M( "DWILoader::LoadFromDWITokens()" ); + + out.m_StepsType = GetTypeFromMode(sMode); + + // if the meter is empty, force it to 1. + if( sNumFeet.empty() ) + sNumFeet = "1"; + + out.SetMeter(std::stoi(sNumFeet)); + + out.SetDifficulty( DwiCompatibleStringToDifficulty(sDescription) ); + + out.SetNoteData( ParseNoteData(sStepData1, sStepData2, out, sPath) ); + + out.TidyUpData(); + + out.SetSavedToDisk( true ); // we're loading from disk, so this is by definintion already saved + return true; +} + +/** + * @brief Turn the DWI style timestamp into a compatible time for our system. + * + * This value can be in either "HH:MM:SS.sssss", "MM:SS.sssss", "SSS.sssss" + * or milliseconds. + * @param arg1 Either hours, minutes, or seconds, depending on other args. + * @param arg2 Either minutes or seconds, depending on other args. + * @param arg3 Seconds if not empty. + * @return the proper timestamp. + */ +static float ParseBrokenDWITimestamp( const RString &arg1, const RString &arg2, const RString &arg3 ) +{ + if( arg1.empty() ) + return 0; + + /* 1+ args */ + if( arg2.empty() ) + { + /* If the value contains a period, treat it as seconds; otherwise ms. */ + if( arg1.find_first_of(".") != arg1.npos ) + return StringToFloat( arg1 ); + else + return StringToFloat( arg1 ) / 1000.f; + } + + /* 2+ args */ + if( arg3.empty() ) + return HHMMSSToSeconds( arg1+":"+arg2 ); + + /* 3+ args */ + return HHMMSSToSeconds( arg1+":"+arg2+":"+arg3 ); +} + + +void DWILoader::GetApplicableFiles( const RString &sPath, vector &out ) +{ + GetDirListing( sPath + RString("*.dwi"), out ); +} + +bool DWILoader::LoadNoteDataFromSimfile( const RString &path, Steps &out ) +{ + MsdFile msd; + if( !msd.ReadFile( path, false ) ) // don't unescape + { + LOG->UserLog("Song file", + path, + "couldn't be opened: %s", + msd.GetError().c_str() ); + return false; + } + + for( unsigned i=0; i &BlacklistedImages ) +{ + vector aFileNames; + GetApplicableFiles( sPath_, aFileNames ); + + if( aFileNames.size() > 1 ) + { + LOG->UserLog( "Song", sPath_, "has more than one DWI file. There should be only one!" ); + return false; + } + + /* We should have exactly one; if we had none, we shouldn't have been called to begin with. */ + ASSERT( aFileNames.size() == 1 ); + const RString sPath = sPath_ + aFileNames[0]; + + LOG->Trace( "Song::LoadFromDWIFile(%s)", sPath.c_str() ); + + MsdFile msd; + if( !msd.ReadFile( sPath, false ) ) // don't unescape + { + LOG->UserLog( "Song file", sPath, "couldn't be opened: %s", msd.GetError().c_str() ); + return false; + } + + out.m_sSongFileName = sPath; + + for( unsigned i=0; iUserLog( "Song file", sPath, "has tag \"%s\" with no parameters.", sValueName.c_str() ); + continue; + } + + // handle the data + if( sValueName.EqualsNoCase("FILE") ) + out.m_sMusicFile = sParams[1]; + + else if( sValueName.EqualsNoCase("TITLE") ) + { + NotesLoader::GetMainAndSubTitlesFromFullTitle( sParams[1], out.m_sMainTitle, out.m_sSubTitle ); + + /* As far as I know, there's no spec on the encoding of this text. (I didn't + * look very hard, though.) I've seen at least one file in ISO-8859-1. */ + ConvertString( out.m_sMainTitle, "utf-8,english" ); + ConvertString( out.m_sSubTitle, "utf-8,english" ); + } + + else if( sValueName.EqualsNoCase("ARTIST") ) + { + out.m_sArtist = sParams[1]; + ConvertString( out.m_sArtist, "utf-8,english" ); + } + + else if( sValueName.EqualsNoCase("GENRE") ) + { + out.m_sGenre = sParams[1]; + ConvertString( out.m_sGenre, "utf-8,english" ); + } + + else if( sValueName.EqualsNoCase("CDTITLE") ) + out.m_sCDTitleFile = sParams[1]; + + else if( sValueName.EqualsNoCase("BPM") ) + { + const float fBPM = StringToFloat( sParams[1] ); + + if( unlikely(fBPM <= 0.0f && !PREFSMAN->m_bQuirksMode) ) + { + LOG->UserLog("Song file", sPath, "has an invalid BPM change at beat %f, BPM %f.", + 0.0f, fBPM ); + } + else + { + out.m_SongTiming.AddSegment( BPMSegment(0, fBPM) ); + } + } + else if( sValueName.EqualsNoCase("DISPLAYBPM") ) + { + // #DISPLAYBPM:[xxx..xxx]|[xxx]|[*]; + int iMin, iMax; + /* We can't parse this as a float with sscanf, since '.' is a valid + * character in a float. (We could do it with a regex, but it's not + * worth bothering with since we don't display fractional BPM anyway.) */ + if( sscanf( sParams[1], "%i..%i", &iMin, &iMax ) == 2 ) + { + out.m_DisplayBPMType = DISPLAY_BPM_SPECIFIED; + out.m_fSpecifiedBPMMin = (float) iMin; + out.m_fSpecifiedBPMMax = (float) iMax; + } + else if( sscanf( sParams[1], "%i", &iMin ) == 1 ) + { + out.m_DisplayBPMType = DISPLAY_BPM_SPECIFIED; + out.m_fSpecifiedBPMMin = out.m_fSpecifiedBPMMax = (float) iMin; + } + else + { + out.m_DisplayBPMType = DISPLAY_BPM_RANDOM; + } + } + + else if( sValueName.EqualsNoCase("GAP") ) + // the units of GAP is 1/1000 second + out.m_SongTiming.m_fBeat0OffsetInSeconds = -std::stoi(sParams[1]) / 1000.0f; + + else if( sValueName.EqualsNoCase("SAMPLESTART") ) + out.m_fMusicSampleStartSeconds = ParseBrokenDWITimestamp(sParams[1], sParams[2], sParams[3]); + + else if( sValueName.EqualsNoCase("SAMPLELENGTH") ) + out.m_fMusicSampleLengthSeconds = ParseBrokenDWITimestamp(sParams[1], sParams[2], sParams[3]); + + else if( sValueName.EqualsNoCase("FREEZE") ) + { + vector arrayFreezeExpressions; + split( sParams[1], ",", arrayFreezeExpressions ); + + for( unsigned f=0; f arrayFreezeValues; + split( arrayFreezeExpressions[f], "=", arrayFreezeValues ); + if( arrayFreezeValues.size() != 2 ) + { + LOG->UserLog( "Song file", sPath, "has an invalid FREEZE: '%s'.", arrayFreezeExpressions[f].c_str() ); + continue; + } + int iFreezeRow = BeatToNoteRow( StringToFloat(arrayFreezeValues[0]) / 4.0f ); + float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] ) / 1000.0f; + + out.m_SongTiming.AddSegment( StopSegment(iFreezeRow, fFreezeSeconds) ); +// LOG->Trace( "Adding a freeze segment: beat: %f, seconds = %f", fFreezeBeat, fFreezeSeconds ); + } + } + + else if( sValueName.EqualsNoCase("CHANGEBPM") || sValueName.EqualsNoCase("BPMCHANGE") ) + { + vector arrayBPMChangeExpressions; + split( sParams[1], ",", arrayBPMChangeExpressions ); + + for( unsigned b=0; b arrayBPMChangeValues; + split( arrayBPMChangeExpressions[b], "=", arrayBPMChangeValues ); + if( arrayBPMChangeValues.size() != 2 ) + { + LOG->UserLog( "Song file", sPath, "has an invalid CHANGEBPM: '%s'.", arrayBPMChangeExpressions[b].c_str() ); + continue; + } + + int iStartIndex = BeatToNoteRow( StringToFloat(arrayBPMChangeValues[0]) / 4.0f ); + float fBPM = StringToFloat( arrayBPMChangeValues[1] ); + if( fBPM > 0.0f ) + out.m_SongTiming.AddSegment( BPMSegment(iStartIndex, fBPM) ); + else + LOG->UserLog( "Song file", sPath, "has an invalid BPM change at beat %f, BPM %f.", + NoteRowToBeat(iStartIndex), fBPM ); + } + } + + else if( sValueName.EqualsNoCase("SINGLE") || + sValueName.EqualsNoCase("DOUBLE") || + sValueName.EqualsNoCase("COUPLE") || + sValueName.EqualsNoCase("SOLO") ) + { + Steps* pNewNotes = out.CreateSteps(); + LoadFromDWITokens( + sParams[0], + sParams[1], + sParams[2], + sParams[3], + (iNumParams==5) ? sParams[4] : RString(""), + *pNewNotes, + sPath + ); + if( pNewNotes->m_StepsType != StepsType_Invalid ) + { + pNewNotes->SetFilename( sPath ); + out.AddSteps( pNewNotes ); + } + else + delete pNewNotes; + } + else if( sValueName.EqualsNoCase("DISPLAYTITLE") || + sValueName.EqualsNoCase("DISPLAYARTIST") ) + { + /* We don't want to support these tags. However, we don't want + * to pick up images used here as song images (eg. banners). */ + RString param = sParams[1]; + /* "{foo} ... {foo2}" */ + size_t pos = 0; + while( pos < RString::npos ) + { + + size_t startpos = param.find('{', pos); + if( startpos == RString::npos ) + break; + size_t endpos = param.find('}', startpos); + if( endpos == RString::npos ) + break; + + RString sub = param.substr( startpos+1, endpos-startpos-1 ); + + pos = endpos + 1; + + sub.MakeLower(); + BlacklistedImages.insert( sub ); + } + } + else + { + // do nothing. We don't care about this value name + } + } + return true; +} + +/* + * (c) 2001-2004 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. + */ diff --git a/src/NotesLoaderKSF.cpp b/src/NotesLoaderKSF.cpp index 35a9d0d433..e75665a529 100644 --- a/src/NotesLoaderKSF.cpp +++ b/src/NotesLoaderKSF.cpp @@ -1,810 +1,810 @@ -#include "global.h" -#include "NotesLoaderKSF.h" -#include "RageUtil_CharConversions.h" -#include "MsdFile.h" -#include "RageLog.h" -#include "RageUtil.h" -#include "NoteData.h" -#include "NoteTypes.h" -#include "Song.h" -#include "Steps.h" - -static void HandleBunki( TimingData &timing, const float fEarlyBPM, - const float fCurBPM, const float fGap, - const float fPos ) -{ - const float BeatsPerSecond = fEarlyBPM / 60.0f; - const float beat = (fPos + fGap) * BeatsPerSecond; - LOG->Trace( "BPM %f, BPS %f, BPMPos %f, beat %f", - fEarlyBPM, BeatsPerSecond, fPos, beat ); - timing.AddSegment( BPMSegment(BeatToNoteRow(beat), fCurBPM) ); -} - -static bool LoadFromKSFFile( const RString &sPath, Steps &out, Song &song, bool bKIUCompliant ) -{ - LOG->Trace( "Steps::LoadFromKSFFile( '%s' )", sPath.c_str() ); - - MsdFile msd; - if( !msd.ReadFile( sPath, false ) ) // don't unescape - { - LOG->UserLog( "Song file", sPath, "couldn't be opened: %s", msd.GetError().c_str() ); - return false; - } - - // this is the value we read for TICKCOUNT - int iTickCount = -1; - // used to adapt weird tickcounts - //float fScrollRatio = 1.0f; -- uncomment when ready to use. - vector vNoteRows; - - // According to Aldo_MX, there is a default BPM and it's 60. -aj - bool bDoublesChart = false; - - TimingData stepsTiming; - float SMGap1 = 0, SMGap2 = 0, BPM1 = -1, BPMPos2 = -1, BPM2 = -1, BPMPos3 = -1, BPM3 = -1; - - for( unsigned i=0; iUserLog( "Song file", sPath, "has an invalid tick count: %d.", iTickCount ); - return false; - } - stepsTiming.AddSegment( TickcountSegment(0, iTickCount)); - } - - else if( sValueName=="DIFFICULTY" ) - { - out.SetMeter( max(StringToInt(sParams[1]), 1) ); - } - // new cases from Aldo_MX's fork: - else if( sValueName=="PLAYER" ) - { - RString sPlayer = sParams[1]; - sPlayer.MakeLower(); - if( sPlayer.find( "double" ) != string::npos ) - bDoublesChart = true; - } - // This should always be last. - else if( sValueName=="STEP" ) - { - RString theSteps = sParams[1]; - TrimLeft( theSteps ); - split( theSteps, "\n", vNoteRows, true ); - } - } - - if( iTickCount == -1 ) - { - iTickCount = 4; - LOG->UserLog( "Song file", sPath, "doesn't have a TICKCOUNT. Defaulting to %i.", iTickCount ); - } - - // Prepare BPM stuff already if the file uses KSF syntax. - if( bKIUCompliant ) - { - if( BPM2 > 0 && BPMPos2 > 0 ) - { - HandleBunki( stepsTiming, BPM1, BPM2, SMGap1, BPMPos2 ); - } - - if( BPM3 > 0 && BPMPos3 > 0 ) - { - HandleBunki( stepsTiming, BPM2, BPM3, SMGap2, BPMPos3 ); - } - } - - NoteData notedata; // read it into here - - { - RString sDir, sFName, sExt; - splitpath( sPath, sDir, sFName, sExt ); - sFName.MakeLower(); - - out.SetDescription(sFName); - // Check another before anything else... is this okay? -DaisuMaster - if( sFName.find("another") != string::npos ) - { - out.SetDifficulty( Difficulty_Edit ); - if( !out.GetMeter() ) out.SetMeter( 25 ); - } - else if(sFName.find("wild") != string::npos || - sFName.find("wd") != string::npos || - sFName.find("crazy+") != string::npos || - sFName.find("cz+") != string::npos || - sFName.find("hardcore") != string::npos ) - { - out.SetDifficulty( Difficulty_Challenge ); - if( !out.GetMeter() ) out.SetMeter( 20 ); - } - else if(sFName.find("crazy") != string::npos || - sFName.find("cz") != string::npos || - sFName.find("nightmare") != string::npos || - sFName.find("nm") != string::npos || - sFName.find("crazydouble") != string::npos ) - { - out.SetDifficulty( Difficulty_Hard ); - if( !out.GetMeter() ) out.SetMeter( 14 ); // Set the meters to the Pump scale, not DDR. - } - else if(sFName.find("hard") != string::npos || - sFName.find("hd") != string::npos || - sFName.find("freestyle") != string::npos || - sFName.find("fs") != string::npos || - sFName.find("double") != string::npos ) - { - out.SetDifficulty( Difficulty_Medium ); - if( !out.GetMeter() ) out.SetMeter( 8 ); - } - else if(sFName.find("easy") != string::npos || - sFName.find("ez") != string::npos || - sFName.find("normal") != string::npos ) - { - // I wonder if I should leave easy fall into the Beginner difficulty... -DaisuMaster - out.SetDifficulty( Difficulty_Easy ); - if( !out.GetMeter() ) out.SetMeter( 4 ); - } - else if(sFName.find("beginner") != string::npos || - sFName.find("practice") != string::npos || sFName.find("pr") != string::npos ) - { - out.SetDifficulty( Difficulty_Beginner ); - if( !out.GetMeter() ) out.SetMeter( 4 ); - } - else - { - out.SetDifficulty( Difficulty_Hard ); - if( !out.GetMeter() ) out.SetMeter( 10 ); - } - - out.m_StepsType = StepsType_pump_single; - - // Check for "halfdouble" before "double". - if(sFName.find("halfdouble") != string::npos || - sFName.find("half-double") != string::npos || - sFName.find("h_double") != string::npos || - sFName.find("hdb") != string::npos ) - out.m_StepsType = StepsType_pump_halfdouble; - // Handle bDoublesChart from above as well. -aj - else if(sFName.find("double") != string::npos || - sFName.find("nightmare") != string::npos || - sFName.find("freestyle") != string::npos || - sFName.find("db") != string::npos || - sFName.find("nm") != string::npos || - sFName.find("fs") != string::npos || bDoublesChart ) - out.m_StepsType = StepsType_pump_double; - else if( sFName.find("_1") != string::npos ) - out.m_StepsType = StepsType_pump_single; - else if( sFName.find("_2") != string::npos ) - out.m_StepsType = StepsType_pump_couple; - } - - switch( out.m_StepsType ) - { - case StepsType_pump_single: notedata.SetNumTracks( 5 ); break; - case StepsType_pump_couple: notedata.SetNumTracks( 10 ); break; - case StepsType_pump_double: notedata.SetNumTracks( 10 ); break; - case StepsType_pump_routine: notedata.SetNumTracks( 10 ); break; // future files may have this? - case StepsType_pump_halfdouble: notedata.SetNumTracks( 6 ); break; - default: FAIL_M( ssprintf("%i", out.m_StepsType) ); - } - - int t = 0; - int iHoldStartRow[13]; - for( t=0; t<13; t++ ) - iHoldStartRow[t] = -1; - - bool bTickChangeNeeded = false; - int newTick = -1; - float fCurBeat = 0.0f; - float prevBeat = 0.0f; // Used for hold tails. - - for( unsigned r=0; r song.GetSpecifiedLastSecond()) - //{ - // song.SetSpecifiedLastSecond(curTime); - //} - - song.SetSpecifiedLastSecond( song.GetSpecifiedLastSecond() + 4 ); - - break; - } - - else if( BeginsWith(sRowString, "|") ) - { - /* - if (bKIUCompliant) - { - // Log an error, ignore the line. - continue; - } - */ - // gotta do something tricky here: if the bpm is below one then a couple of calculations - // for scrollsegments will be made, example, bpm 0.2, tick 4000, the scrollsegment will - // be 0. if the tickcount is non a stepmania standard then it will be adapted, a scroll - // segment will then be added based on approximations. -DaisuMaster - // eh better do it considering the tickcount (high tickcounts) - - // I'm making some experiments, please spare me... - //continue; - - RString temp = sRowString.substr(2,sRowString.size()-3); - float numTemp = StringToFloat(temp); - if (BeginsWith(sRowString, "|T")) - { - // duh - iTickCount = static_cast(numTemp); - // I have been owned by the man -DaisuMaster - stepsTiming.SetTickcountAtBeat( fCurBeat, clamp(iTickCount, 0, ROWS_PER_BEAT) ); - } - else if (BeginsWith(sRowString, "|B")) - { - // BPM - stepsTiming.SetBPMAtBeat( fCurBeat, numTemp ); - } - else if (BeginsWith(sRowString, "|E")) - { - // DelayBeat - float fCurDelay = 60 / stepsTiming.GetBPMAtBeat(fCurBeat) * numTemp / iTickCount; - fCurDelay += stepsTiming.GetDelayAtRow(BeatToNoteRow(fCurBeat) ); - stepsTiming.SetDelayAtBeat( fCurBeat, fCurDelay ); - } - else if (BeginsWith(sRowString, "|D")) - { - // Delays - float fCurDelay = stepsTiming.GetStopAtRow(BeatToNoteRow(fCurBeat) ); - fCurDelay += numTemp / 1000; - stepsTiming.SetDelayAtBeat( fCurBeat, fCurDelay ); - } - else if (BeginsWith(sRowString, "|M") || BeginsWith(sRowString, "|C")) - { - // multipliers/combo - ComboSegment seg( BeatToNoteRow(fCurBeat), int(numTemp) ); - stepsTiming.AddSegment( seg ); - } - else if (BeginsWith(sRowString, "|S")) - { - // speed segments - } - else if (BeginsWith(sRowString, "|F")) - { - // fakes - } - else if (BeginsWith(sRowString, "|X")) - { - // scroll segments - ScrollSegment seg = ScrollSegment( BeatToNoteRow(fCurBeat), numTemp ); - stepsTiming.AddSegment( seg ); - //return true; - } - - continue; - } - - // Half-doubles is offset; "0011111100000". - if( out.m_StepsType == StepsType_pump_halfdouble ) - sRowString.erase( 0, 2 ); - - // Update TICKCOUNT for Direct Move files. - if( bTickChangeNeeded ) - { - iTickCount = newTick; - bTickChangeNeeded = false; - } - - for( t=0; t < notedata.GetNumTracks(); t++ ) - { - if( sRowString[t] == '4' ) - { - /* Remember when each hold starts; ignore the middle. */ - if( iHoldStartRow[t] == -1 ) - iHoldStartRow[t] = BeatToNoteRow(fCurBeat); - continue; - } - - if( iHoldStartRow[t] != -1 ) // this ends the hold - { - int iEndRow = BeatToNoteRow(prevBeat); - if( iHoldStartRow[t] == iEndRow ) - notedata.SetTapNote( t, iHoldStartRow[t], TAP_ORIGINAL_TAP ); - else - { - //notedata.AddHoldNote( t, iHoldStartRow[t], iEndRow , TAP_ORIGINAL_PUMP_HEAD ); - notedata.AddHoldNote( t, iHoldStartRow[t], iEndRow , TAP_ORIGINAL_HOLD_HEAD ); - } - iHoldStartRow[t] = -1; - } - - TapNote tap; - switch( sRowString[t] ) - { - case '0': tap = TAP_EMPTY; break; - case '1': tap = TAP_ORIGINAL_TAP; break; - //allow setting more notetypes on ksf files, this may come in handy (it should) -DaisuMaster - case 'M': - case 'm': - tap = TAP_ORIGINAL_MINE; - break; - case 'F': - case 'f': - tap = TAP_ORIGINAL_FAKE; - break; - case 'L': - case 'l': - tap = TAP_ORIGINAL_LIFT; - break; - default: - LOG->UserLog( "Song file", sPath, "has an invalid row \"%s\"; corrupt notes ignored.", - sRowString.c_str() ); - //return false; - tap = TAP_EMPTY; - break; - } - - notedata.SetTapNote(t, BeatToNoteRow(fCurBeat), tap); - } - prevBeat = fCurBeat; - fCurBeat = prevBeat + 1.0f / iTickCount; - } - - out.SetNoteData( notedata ); - out.m_Timing = stepsTiming; - - out.TidyUpData(); - - out.SetSavedToDisk( true ); // we're loading from disk, so this is by definintion already saved - - return true; -} - -static void LoadTags( const RString &str, Song &out ) -{ - /* str is either a #TITLE or a directory component. Fill in missing information. - * str is either "title", "artist - title", or "artist - title - difficulty". */ - vector asBits; - split( str, " - ", asBits, false ); - // Ignore the difficulty, since we get that elsewhere. - if( asBits.size() == 3 && ( - asBits[2].EqualsNoCase("double") || - asBits[2].EqualsNoCase("easy") || - asBits[2].EqualsNoCase("normal") || - asBits[2].EqualsNoCase("hard") || - asBits[2].EqualsNoCase("crazy") || - asBits[2].EqualsNoCase("nightmare")) - ) - { - asBits.erase( asBits.begin()+2, asBits.begin()+3 ); - } - - RString title, artist; - if( asBits.size() == 2 ) - { - artist = asBits[0]; - title = asBits[1]; - } - else if( asBits.size() == 1 ) - { - title = asBits[0]; - } - - // Convert, if possible. Most KSFs are in Korean encodings (CP942/EUC-KR). - if( !ConvertString( title, "korean" ) ) - title = ""; - if( !ConvertString( artist, "korean" ) ) - artist = ""; - - if( out.m_sMainTitle == "" ) - out.m_sMainTitle = title; - if( out.m_sArtist == "" ) - out.m_sArtist = artist; -} - -static void ProcessTickcounts( const RString & value, int & ticks, TimingData & timing ) -{ - /* TICKCOUNT will be used below if there are DM compliant BPM changes - * and stops. It will be called again in LoadFromKSFFile for the - * actual steps. */ - ticks = StringToInt( value ); - CLAMP( ticks, 0, ROWS_PER_BEAT ); - - if( ticks == 0 ) - ticks = TickcountSegment::DEFAULT_TICK_COUNT; - - timing.AddSegment( TickcountSegment(0, ticks) ); -} - -static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant ) -{ - MsdFile msd; - if( !msd.ReadFile( sPath, false ) ) // don't unescape - { - LOG->UserLog( "Song file", sPath, "couldn't be opened: %s", msd.GetError().c_str() ); - return false; - } - - // changed up there in case of something is found inside the SONGFILE tag in the head ksf -DaisuMaster - // search for music with song in the file name - vector arrayPossibleMusic; - GetDirListing( out.GetSongDir() + RString("song.mp3"), arrayPossibleMusic ); - GetDirListing( out.GetSongDir() + RString("song.oga"), arrayPossibleMusic ); - GetDirListing( out.GetSongDir() + RString("song.ogg"), arrayPossibleMusic ); - GetDirListing( out.GetSongDir() + RString("song.wav"), arrayPossibleMusic ); - - if( !arrayPossibleMusic.empty() ) // we found a match - out.m_sMusicFile = arrayPossibleMusic[0]; - // ^this was below, at the end - - float SMGap1 = 0, SMGap2 = 0, BPM1 = -1, BPMPos2 = -1, BPM2 = -1, BPMPos3 = -1, BPM3 = -1; - int iTickCount = -1; - bKIUCompliant = false; - vector vNoteRows; - - for( unsigned i=0; i < msd.GetNumValues(); i++ ) - { - const MsdFile::value_t &sParams = msd.GetValue(i); - RString sValueName = sParams[0]; - sValueName.MakeUpper(); - - // handle the data - if( sValueName=="TITLE" ) - LoadTags(sParams[1], out); - else if( sValueName=="BPM" ) - { - BPM1 = StringToFloat(sParams[1]); - out.m_SongTiming.AddSegment( BPMSegment(0, BPM1) ); - } - else if( sValueName=="BPM2" ) - { - bKIUCompliant = true; - BPM2 = StringToFloat( sParams[1] ); - } - else if( sValueName=="BPM3" ) - { - bKIUCompliant = true; - BPM3 = StringToFloat( sParams[1] ); - } - else if( sValueName=="BUNKI" ) - { - bKIUCompliant = true; - BPMPos2 = StringToFloat( sParams[1] ) / 100.0f; - } - else if( sValueName=="BUNKI2" ) - { - bKIUCompliant = true; - BPMPos3 = StringToFloat( sParams[1] ) / 100.0f; - } - else if( sValueName=="STARTTIME" ) - { - SMGap1 = -StringToFloat( sParams[1] )/100; - out.m_SongTiming.m_fBeat0OffsetInSeconds = SMGap1; - } - // This is currently required for more accurate KIU BPM changes. - else if( sValueName=="STARTTIME2" ) - { - bKIUCompliant = true; - SMGap2 = -StringToFloat( sParams[1] )/100; - } - else if ( sValueName=="STARTTIME3" ) - { - // STARTTIME3 only ensures this is a KIU compliant simfile. - //bKIUCompliant = true; - } - else if ( sValueName=="TICKCOUNT" ) - { - ProcessTickcounts(sParams[1], iTickCount, out.m_SongTiming); - } - else if ( sValueName=="STEP" ) - { - /* STEP will always be the last header in a KSF file by design. Due to - * the Direct Move syntax, it is best to get the rows of notes here. */ - RString theSteps = sParams[1]; - TrimLeft( theSteps ); - split( theSteps, "\n", vNoteRows, true ); - } - else if( sValueName=="DIFFICULTY" || sValueName=="PLAYER" ) - { - /* DIFFICULTY and PLAYER are handled only in LoadFromKSFFile. - Ignore those here. */ - continue; - } - // New cases noted in Aldo_MX's code: - else if( sValueName=="MUSICINTRO" || sValueName=="INTRO" ) - { - out.m_fMusicSampleStartSeconds = HHMMSSToSeconds( sParams[1] ); - } - else if( sValueName=="TITLEFILE" ) - { - out.m_sBackgroundFile = sParams[1]; - } - else if( sValueName=="DISCFILE" ) - { - out.m_sBannerFile = sParams[1]; - } - else if( sValueName=="SONGFILE" ) - { - out.m_sMusicFile = sParams[1]; - } - //else if( sValueName=="INTROFILE" ) - //{ - // nothing to add... - //} - // end new cases - else - { - LOG->UserLog( "Song file", sPath, "has an unexpected value named \"%s\".", - sValueName.c_str() ); - } - } - - //intro length in piu mixes is generally 7 seconds - out.m_fMusicSampleLengthSeconds = 7.0f; - - /* BPM Change checks are done here. If bKIUCompliant, it's short and sweet. - * Otherwise, the whole file has to be processed. Right now, this is only - * called once, for the initial file (often the Crazy steps). Hopefully that - * will end up changing soon. */ - if( bKIUCompliant ) - { - if( BPM2 > 0 && BPMPos2 > 0 ) - { - HandleBunki( out.m_SongTiming, BPM1, BPM2, SMGap1, BPMPos2 ); - } - - if( BPM3 > 0 && BPMPos3 > 0 ) - { - HandleBunki( out.m_SongTiming, BPM2, BPM3, SMGap2, BPMPos3 ); - } - } - else - { - float fCurBeat = 0.0f; - bool bDMRequired = false; - - for( unsigned i=0; i < vNoteRows.size(); ++i ) - { - RString& NoteRowString = vNoteRows[i]; - StripCrnl( NoteRowString ); - - if( NoteRowString == "" ) - continue; // ignore empty rows. - - if( NoteRowString == "2222222222222" ) // Row of 2s = end. Confirm KIUCompliency here. - { - if (!bDMRequired) - bKIUCompliant = true; - break; - } - - // This is where the DMRequired test will take place. - if ( BeginsWith( NoteRowString, "|" ) ) - { - // have a static timing for everything - bDMRequired = true; - continue; - } - else - { - // ignore whatever else... - //continue; - } - - fCurBeat += 1.0f / iTickCount; - } - } - - // Try to fill in missing bits of information from the pathname. - { - vector asBits; - split( sPath, "/", asBits, true); - - ASSERT( asBits.size() > 1 ); - LoadTags( asBits[asBits.size()-2], out ); - } - - return true; -} - -void KSFLoader::GetApplicableFiles( const RString &sPath, vector &out ) -{ - GetDirListing( sPath + RString("*.ksf"), out ); -} - -bool KSFLoader::LoadNoteDataFromSimfile( const RString & cachePath, Steps &out ) -{ - bool KIUCompliant = false; - Song dummy; - if (!LoadGlobalData(cachePath, dummy, KIUCompliant)) - return false; - Steps *notes = dummy.CreateSteps(); - if (LoadFromKSFFile(cachePath, *notes, dummy, KIUCompliant)) - { - KIUCompliant = true; // yeah, reusing a variable. - out.SetNoteData(notes->GetNoteData()); - } - delete notes; - return KIUCompliant; -} - -bool KSFLoader::LoadFromDir( const RString &sDir, Song &out ) -{ - LOG->Trace( "KSFLoader::LoadFromDir(%s)", sDir.c_str() ); - - vector arrayKSFFileNames; - GetDirListing( sDir + RString("*.ksf"), arrayKSFFileNames ); - - // We shouldn't have been called to begin with if there were no KSFs. - ASSERT( arrayKSFFileNames.size() != 0 ); - - bool bKIUCompliant = false; - /* With Split Timing, there has to be a backup Song Timing in case - * anything goes wrong. As these files are kept in alphabetical - * order (hopefully), it is best to use the LAST file for timing - * purposes, for that is the "normal", or easiest difficulty. - * Usually. */ - // Nevermind, kiu compilancy is screwing things up: - // IE, I have two simfiles, oh wich each have four ksf files, the first one has - // the first ksf with directmove timing changes, and the rest are not, everything - // goes fine. In the other hand I have my second simfile with the first ksf file - // without directmove timing changes and the rest have changes, changes are not - // loaded due to kiucompilancy in the first ksf file. - // About the "normal" thing, my simfiles' ksfs uses non-standard naming so - // the last chart is usually nightmare or normal, I use easy and normal - // indistinctly for SM so it shouldn't matter, I use piu fiesta/ex naming - // for directmove though, and we're just gathering basic info anyway, and - // most of the time all the KSF files have the same info in the #TITLE:; section - unsigned files = arrayKSFFileNames.size(); - RString dir = out.GetSongDir(); - if( !LoadGlobalData(dir + arrayKSFFileNames[files - 1], out, bKIUCompliant) ) - return false; - - out.m_sSongFileName = dir + arrayKSFFileNames[files - 1]; - // load the Steps from the rest of the KSF files - for( unsigned i=0; iSetFilename(dir + arrayKSFFileNames[i]); - out.AddSteps( pNewNotes ); - } - return true; -} - -/* - * (c) 2001-2006 Chris Danford, Glenn Maynard, Jason Felds - * 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. - */ +#include "global.h" +#include "NotesLoaderKSF.h" +#include "RageUtil_CharConversions.h" +#include "MsdFile.h" +#include "RageLog.h" +#include "RageUtil.h" +#include "NoteData.h" +#include "NoteTypes.h" +#include "Song.h" +#include "Steps.h" + +static void HandleBunki( TimingData &timing, const float fEarlyBPM, + const float fCurBPM, const float fGap, + const float fPos ) +{ + const float BeatsPerSecond = fEarlyBPM / 60.0f; + const float beat = (fPos + fGap) * BeatsPerSecond; + LOG->Trace( "BPM %f, BPS %f, BPMPos %f, beat %f", + fEarlyBPM, BeatsPerSecond, fPos, beat ); + timing.AddSegment( BPMSegment(BeatToNoteRow(beat), fCurBPM) ); +} + +static bool LoadFromKSFFile( const RString &sPath, Steps &out, Song &song, bool bKIUCompliant ) +{ + LOG->Trace( "Steps::LoadFromKSFFile( '%s' )", sPath.c_str() ); + + MsdFile msd; + if( !msd.ReadFile( sPath, false ) ) // don't unescape + { + LOG->UserLog( "Song file", sPath, "couldn't be opened: %s", msd.GetError().c_str() ); + return false; + } + + // this is the value we read for TICKCOUNT + int iTickCount = -1; + // used to adapt weird tickcounts + //float fScrollRatio = 1.0f; -- uncomment when ready to use. + vector vNoteRows; + + // According to Aldo_MX, there is a default BPM and it's 60. -aj + bool bDoublesChart = false; + + TimingData stepsTiming; + float SMGap1 = 0, SMGap2 = 0, BPM1 = -1, BPMPos2 = -1, BPM2 = -1, BPMPos3 = -1, BPM3 = -1; + + for( unsigned i=0; iUserLog( "Song file", sPath, "has an invalid tick count: %d.", iTickCount ); + return false; + } + stepsTiming.AddSegment( TickcountSegment(0, iTickCount)); + } + + else if( sValueName=="DIFFICULTY" ) + { + out.SetMeter( max(std::stoi(sParams[1]), 1) ); + } + // new cases from Aldo_MX's fork: + else if( sValueName=="PLAYER" ) + { + RString sPlayer = sParams[1]; + sPlayer.MakeLower(); + if( sPlayer.find( "double" ) != string::npos ) + bDoublesChart = true; + } + // This should always be last. + else if( sValueName=="STEP" ) + { + RString theSteps = sParams[1]; + TrimLeft( theSteps ); + split( theSteps, "\n", vNoteRows, true ); + } + } + + if( iTickCount == -1 ) + { + iTickCount = 4; + LOG->UserLog( "Song file", sPath, "doesn't have a TICKCOUNT. Defaulting to %i.", iTickCount ); + } + + // Prepare BPM stuff already if the file uses KSF syntax. + if( bKIUCompliant ) + { + if( BPM2 > 0 && BPMPos2 > 0 ) + { + HandleBunki( stepsTiming, BPM1, BPM2, SMGap1, BPMPos2 ); + } + + if( BPM3 > 0 && BPMPos3 > 0 ) + { + HandleBunki( stepsTiming, BPM2, BPM3, SMGap2, BPMPos3 ); + } + } + + NoteData notedata; // read it into here + + { + RString sDir, sFName, sExt; + splitpath( sPath, sDir, sFName, sExt ); + sFName.MakeLower(); + + out.SetDescription(sFName); + // Check another before anything else... is this okay? -DaisuMaster + if( sFName.find("another") != string::npos ) + { + out.SetDifficulty( Difficulty_Edit ); + if( !out.GetMeter() ) out.SetMeter( 25 ); + } + else if(sFName.find("wild") != string::npos || + sFName.find("wd") != string::npos || + sFName.find("crazy+") != string::npos || + sFName.find("cz+") != string::npos || + sFName.find("hardcore") != string::npos ) + { + out.SetDifficulty( Difficulty_Challenge ); + if( !out.GetMeter() ) out.SetMeter( 20 ); + } + else if(sFName.find("crazy") != string::npos || + sFName.find("cz") != string::npos || + sFName.find("nightmare") != string::npos || + sFName.find("nm") != string::npos || + sFName.find("crazydouble") != string::npos ) + { + out.SetDifficulty( Difficulty_Hard ); + if( !out.GetMeter() ) out.SetMeter( 14 ); // Set the meters to the Pump scale, not DDR. + } + else if(sFName.find("hard") != string::npos || + sFName.find("hd") != string::npos || + sFName.find("freestyle") != string::npos || + sFName.find("fs") != string::npos || + sFName.find("double") != string::npos ) + { + out.SetDifficulty( Difficulty_Medium ); + if( !out.GetMeter() ) out.SetMeter( 8 ); + } + else if(sFName.find("easy") != string::npos || + sFName.find("ez") != string::npos || + sFName.find("normal") != string::npos ) + { + // I wonder if I should leave easy fall into the Beginner difficulty... -DaisuMaster + out.SetDifficulty( Difficulty_Easy ); + if( !out.GetMeter() ) out.SetMeter( 4 ); + } + else if(sFName.find("beginner") != string::npos || + sFName.find("practice") != string::npos || sFName.find("pr") != string::npos ) + { + out.SetDifficulty( Difficulty_Beginner ); + if( !out.GetMeter() ) out.SetMeter( 4 ); + } + else + { + out.SetDifficulty( Difficulty_Hard ); + if( !out.GetMeter() ) out.SetMeter( 10 ); + } + + out.m_StepsType = StepsType_pump_single; + + // Check for "halfdouble" before "double". + if(sFName.find("halfdouble") != string::npos || + sFName.find("half-double") != string::npos || + sFName.find("h_double") != string::npos || + sFName.find("hdb") != string::npos ) + out.m_StepsType = StepsType_pump_halfdouble; + // Handle bDoublesChart from above as well. -aj + else if(sFName.find("double") != string::npos || + sFName.find("nightmare") != string::npos || + sFName.find("freestyle") != string::npos || + sFName.find("db") != string::npos || + sFName.find("nm") != string::npos || + sFName.find("fs") != string::npos || bDoublesChart ) + out.m_StepsType = StepsType_pump_double; + else if( sFName.find("_1") != string::npos ) + out.m_StepsType = StepsType_pump_single; + else if( sFName.find("_2") != string::npos ) + out.m_StepsType = StepsType_pump_couple; + } + + switch( out.m_StepsType ) + { + case StepsType_pump_single: notedata.SetNumTracks( 5 ); break; + case StepsType_pump_couple: notedata.SetNumTracks( 10 ); break; + case StepsType_pump_double: notedata.SetNumTracks( 10 ); break; + case StepsType_pump_routine: notedata.SetNumTracks( 10 ); break; // future files may have this? + case StepsType_pump_halfdouble: notedata.SetNumTracks( 6 ); break; + default: FAIL_M( ssprintf("%i", out.m_StepsType) ); + } + + int t = 0; + int iHoldStartRow[13]; + for( t=0; t<13; t++ ) + iHoldStartRow[t] = -1; + + bool bTickChangeNeeded = false; + int newTick = -1; + float fCurBeat = 0.0f; + float prevBeat = 0.0f; // Used for hold tails. + + for( unsigned r=0; r song.GetSpecifiedLastSecond()) + //{ + // song.SetSpecifiedLastSecond(curTime); + //} + + song.SetSpecifiedLastSecond( song.GetSpecifiedLastSecond() + 4 ); + + break; + } + + else if( BeginsWith(sRowString, "|") ) + { + /* + if (bKIUCompliant) + { + // Log an error, ignore the line. + continue; + } + */ + // gotta do something tricky here: if the bpm is below one then a couple of calculations + // for scrollsegments will be made, example, bpm 0.2, tick 4000, the scrollsegment will + // be 0. if the tickcount is non a stepmania standard then it will be adapted, a scroll + // segment will then be added based on approximations. -DaisuMaster + // eh better do it considering the tickcount (high tickcounts) + + // I'm making some experiments, please spare me... + //continue; + + RString temp = sRowString.substr(2,sRowString.size()-3); + float numTemp = StringToFloat(temp); + if (BeginsWith(sRowString, "|T")) + { + // duh + iTickCount = static_cast(numTemp); + // I have been owned by the man -DaisuMaster + stepsTiming.SetTickcountAtBeat( fCurBeat, clamp(iTickCount, 0, ROWS_PER_BEAT) ); + } + else if (BeginsWith(sRowString, "|B")) + { + // BPM + stepsTiming.SetBPMAtBeat( fCurBeat, numTemp ); + } + else if (BeginsWith(sRowString, "|E")) + { + // DelayBeat + float fCurDelay = 60 / stepsTiming.GetBPMAtBeat(fCurBeat) * numTemp / iTickCount; + fCurDelay += stepsTiming.GetDelayAtRow(BeatToNoteRow(fCurBeat) ); + stepsTiming.SetDelayAtBeat( fCurBeat, fCurDelay ); + } + else if (BeginsWith(sRowString, "|D")) + { + // Delays + float fCurDelay = stepsTiming.GetStopAtRow(BeatToNoteRow(fCurBeat) ); + fCurDelay += numTemp / 1000; + stepsTiming.SetDelayAtBeat( fCurBeat, fCurDelay ); + } + else if (BeginsWith(sRowString, "|M") || BeginsWith(sRowString, "|C")) + { + // multipliers/combo + ComboSegment seg( BeatToNoteRow(fCurBeat), int(numTemp) ); + stepsTiming.AddSegment( seg ); + } + else if (BeginsWith(sRowString, "|S")) + { + // speed segments + } + else if (BeginsWith(sRowString, "|F")) + { + // fakes + } + else if (BeginsWith(sRowString, "|X")) + { + // scroll segments + ScrollSegment seg = ScrollSegment( BeatToNoteRow(fCurBeat), numTemp ); + stepsTiming.AddSegment( seg ); + //return true; + } + + continue; + } + + // Half-doubles is offset; "0011111100000". + if( out.m_StepsType == StepsType_pump_halfdouble ) + sRowString.erase( 0, 2 ); + + // Update TICKCOUNT for Direct Move files. + if( bTickChangeNeeded ) + { + iTickCount = newTick; + bTickChangeNeeded = false; + } + + for( t=0; t < notedata.GetNumTracks(); t++ ) + { + if( sRowString[t] == '4' ) + { + /* Remember when each hold starts; ignore the middle. */ + if( iHoldStartRow[t] == -1 ) + iHoldStartRow[t] = BeatToNoteRow(fCurBeat); + continue; + } + + if( iHoldStartRow[t] != -1 ) // this ends the hold + { + int iEndRow = BeatToNoteRow(prevBeat); + if( iHoldStartRow[t] == iEndRow ) + notedata.SetTapNote( t, iHoldStartRow[t], TAP_ORIGINAL_TAP ); + else + { + //notedata.AddHoldNote( t, iHoldStartRow[t], iEndRow , TAP_ORIGINAL_PUMP_HEAD ); + notedata.AddHoldNote( t, iHoldStartRow[t], iEndRow , TAP_ORIGINAL_HOLD_HEAD ); + } + iHoldStartRow[t] = -1; + } + + TapNote tap; + switch( sRowString[t] ) + { + case '0': tap = TAP_EMPTY; break; + case '1': tap = TAP_ORIGINAL_TAP; break; + //allow setting more notetypes on ksf files, this may come in handy (it should) -DaisuMaster + case 'M': + case 'm': + tap = TAP_ORIGINAL_MINE; + break; + case 'F': + case 'f': + tap = TAP_ORIGINAL_FAKE; + break; + case 'L': + case 'l': + tap = TAP_ORIGINAL_LIFT; + break; + default: + LOG->UserLog( "Song file", sPath, "has an invalid row \"%s\"; corrupt notes ignored.", + sRowString.c_str() ); + //return false; + tap = TAP_EMPTY; + break; + } + + notedata.SetTapNote(t, BeatToNoteRow(fCurBeat), tap); + } + prevBeat = fCurBeat; + fCurBeat = prevBeat + 1.0f / iTickCount; + } + + out.SetNoteData( notedata ); + out.m_Timing = stepsTiming; + + out.TidyUpData(); + + out.SetSavedToDisk( true ); // we're loading from disk, so this is by definintion already saved + + return true; +} + +static void LoadTags( const RString &str, Song &out ) +{ + /* str is either a #TITLE or a directory component. Fill in missing information. + * str is either "title", "artist - title", or "artist - title - difficulty". */ + vector asBits; + split( str, " - ", asBits, false ); + // Ignore the difficulty, since we get that elsewhere. + if( asBits.size() == 3 && ( + asBits[2].EqualsNoCase("double") || + asBits[2].EqualsNoCase("easy") || + asBits[2].EqualsNoCase("normal") || + asBits[2].EqualsNoCase("hard") || + asBits[2].EqualsNoCase("crazy") || + asBits[2].EqualsNoCase("nightmare")) + ) + { + asBits.erase( asBits.begin()+2, asBits.begin()+3 ); + } + + RString title, artist; + if( asBits.size() == 2 ) + { + artist = asBits[0]; + title = asBits[1]; + } + else if( asBits.size() == 1 ) + { + title = asBits[0]; + } + + // Convert, if possible. Most KSFs are in Korean encodings (CP942/EUC-KR). + if( !ConvertString( title, "korean" ) ) + title = ""; + if( !ConvertString( artist, "korean" ) ) + artist = ""; + + if( out.m_sMainTitle == "" ) + out.m_sMainTitle = title; + if( out.m_sArtist == "" ) + out.m_sArtist = artist; +} + +static void ProcessTickcounts( const RString & value, int & ticks, TimingData & timing ) +{ + /* TICKCOUNT will be used below if there are DM compliant BPM changes + * and stops. It will be called again in LoadFromKSFFile for the + * actual steps. */ + ticks = std::stoi( value ); + CLAMP( ticks, 0, ROWS_PER_BEAT ); + + if( ticks == 0 ) + ticks = TickcountSegment::DEFAULT_TICK_COUNT; + + timing.AddSegment( TickcountSegment(0, ticks) ); +} + +static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant ) +{ + MsdFile msd; + if( !msd.ReadFile( sPath, false ) ) // don't unescape + { + LOG->UserLog( "Song file", sPath, "couldn't be opened: %s", msd.GetError().c_str() ); + return false; + } + + // changed up there in case of something is found inside the SONGFILE tag in the head ksf -DaisuMaster + // search for music with song in the file name + vector arrayPossibleMusic; + GetDirListing( out.GetSongDir() + RString("song.mp3"), arrayPossibleMusic ); + GetDirListing( out.GetSongDir() + RString("song.oga"), arrayPossibleMusic ); + GetDirListing( out.GetSongDir() + RString("song.ogg"), arrayPossibleMusic ); + GetDirListing( out.GetSongDir() + RString("song.wav"), arrayPossibleMusic ); + + if( !arrayPossibleMusic.empty() ) // we found a match + out.m_sMusicFile = arrayPossibleMusic[0]; + // ^this was below, at the end + + float SMGap1 = 0, SMGap2 = 0, BPM1 = -1, BPMPos2 = -1, BPM2 = -1, BPMPos3 = -1, BPM3 = -1; + int iTickCount = -1; + bKIUCompliant = false; + vector vNoteRows; + + for( unsigned i=0; i < msd.GetNumValues(); i++ ) + { + const MsdFile::value_t &sParams = msd.GetValue(i); + RString sValueName = sParams[0]; + sValueName.MakeUpper(); + + // handle the data + if( sValueName=="TITLE" ) + LoadTags(sParams[1], out); + else if( sValueName=="BPM" ) + { + BPM1 = StringToFloat(sParams[1]); + out.m_SongTiming.AddSegment( BPMSegment(0, BPM1) ); + } + else if( sValueName=="BPM2" ) + { + bKIUCompliant = true; + BPM2 = StringToFloat( sParams[1] ); + } + else if( sValueName=="BPM3" ) + { + bKIUCompliant = true; + BPM3 = StringToFloat( sParams[1] ); + } + else if( sValueName=="BUNKI" ) + { + bKIUCompliant = true; + BPMPos2 = StringToFloat( sParams[1] ) / 100.0f; + } + else if( sValueName=="BUNKI2" ) + { + bKIUCompliant = true; + BPMPos3 = StringToFloat( sParams[1] ) / 100.0f; + } + else if( sValueName=="STARTTIME" ) + { + SMGap1 = -StringToFloat( sParams[1] )/100; + out.m_SongTiming.m_fBeat0OffsetInSeconds = SMGap1; + } + // This is currently required for more accurate KIU BPM changes. + else if( sValueName=="STARTTIME2" ) + { + bKIUCompliant = true; + SMGap2 = -StringToFloat( sParams[1] )/100; + } + else if ( sValueName=="STARTTIME3" ) + { + // STARTTIME3 only ensures this is a KIU compliant simfile. + //bKIUCompliant = true; + } + else if ( sValueName=="TICKCOUNT" ) + { + ProcessTickcounts(sParams[1], iTickCount, out.m_SongTiming); + } + else if ( sValueName=="STEP" ) + { + /* STEP will always be the last header in a KSF file by design. Due to + * the Direct Move syntax, it is best to get the rows of notes here. */ + RString theSteps = sParams[1]; + TrimLeft( theSteps ); + split( theSteps, "\n", vNoteRows, true ); + } + else if( sValueName=="DIFFICULTY" || sValueName=="PLAYER" ) + { + /* DIFFICULTY and PLAYER are handled only in LoadFromKSFFile. + Ignore those here. */ + continue; + } + // New cases noted in Aldo_MX's code: + else if( sValueName=="MUSICINTRO" || sValueName=="INTRO" ) + { + out.m_fMusicSampleStartSeconds = HHMMSSToSeconds( sParams[1] ); + } + else if( sValueName=="TITLEFILE" ) + { + out.m_sBackgroundFile = sParams[1]; + } + else if( sValueName=="DISCFILE" ) + { + out.m_sBannerFile = sParams[1]; + } + else if( sValueName=="SONGFILE" ) + { + out.m_sMusicFile = sParams[1]; + } + //else if( sValueName=="INTROFILE" ) + //{ + // nothing to add... + //} + // end new cases + else + { + LOG->UserLog( "Song file", sPath, "has an unexpected value named \"%s\".", + sValueName.c_str() ); + } + } + + //intro length in piu mixes is generally 7 seconds + out.m_fMusicSampleLengthSeconds = 7.0f; + + /* BPM Change checks are done here. If bKIUCompliant, it's short and sweet. + * Otherwise, the whole file has to be processed. Right now, this is only + * called once, for the initial file (often the Crazy steps). Hopefully that + * will end up changing soon. */ + if( bKIUCompliant ) + { + if( BPM2 > 0 && BPMPos2 > 0 ) + { + HandleBunki( out.m_SongTiming, BPM1, BPM2, SMGap1, BPMPos2 ); + } + + if( BPM3 > 0 && BPMPos3 > 0 ) + { + HandleBunki( out.m_SongTiming, BPM2, BPM3, SMGap2, BPMPos3 ); + } + } + else + { + float fCurBeat = 0.0f; + bool bDMRequired = false; + + for( unsigned i=0; i < vNoteRows.size(); ++i ) + { + RString& NoteRowString = vNoteRows[i]; + StripCrnl( NoteRowString ); + + if( NoteRowString == "" ) + continue; // ignore empty rows. + + if( NoteRowString == "2222222222222" ) // Row of 2s = end. Confirm KIUCompliency here. + { + if (!bDMRequired) + bKIUCompliant = true; + break; + } + + // This is where the DMRequired test will take place. + if ( BeginsWith( NoteRowString, "|" ) ) + { + // have a static timing for everything + bDMRequired = true; + continue; + } + else + { + // ignore whatever else... + //continue; + } + + fCurBeat += 1.0f / iTickCount; + } + } + + // Try to fill in missing bits of information from the pathname. + { + vector asBits; + split( sPath, "/", asBits, true); + + ASSERT( asBits.size() > 1 ); + LoadTags( asBits[asBits.size()-2], out ); + } + + return true; +} + +void KSFLoader::GetApplicableFiles( const RString &sPath, vector &out ) +{ + GetDirListing( sPath + RString("*.ksf"), out ); +} + +bool KSFLoader::LoadNoteDataFromSimfile( const RString & cachePath, Steps &out ) +{ + bool KIUCompliant = false; + Song dummy; + if (!LoadGlobalData(cachePath, dummy, KIUCompliant)) + return false; + Steps *notes = dummy.CreateSteps(); + if (LoadFromKSFFile(cachePath, *notes, dummy, KIUCompliant)) + { + KIUCompliant = true; // yeah, reusing a variable. + out.SetNoteData(notes->GetNoteData()); + } + delete notes; + return KIUCompliant; +} + +bool KSFLoader::LoadFromDir( const RString &sDir, Song &out ) +{ + LOG->Trace( "KSFLoader::LoadFromDir(%s)", sDir.c_str() ); + + vector arrayKSFFileNames; + GetDirListing( sDir + RString("*.ksf"), arrayKSFFileNames ); + + // We shouldn't have been called to begin with if there were no KSFs. + ASSERT( arrayKSFFileNames.size() != 0 ); + + bool bKIUCompliant = false; + /* With Split Timing, there has to be a backup Song Timing in case + * anything goes wrong. As these files are kept in alphabetical + * order (hopefully), it is best to use the LAST file for timing + * purposes, for that is the "normal", or easiest difficulty. + * Usually. */ + // Nevermind, kiu compilancy is screwing things up: + // IE, I have two simfiles, oh wich each have four ksf files, the first one has + // the first ksf with directmove timing changes, and the rest are not, everything + // goes fine. In the other hand I have my second simfile with the first ksf file + // without directmove timing changes and the rest have changes, changes are not + // loaded due to kiucompilancy in the first ksf file. + // About the "normal" thing, my simfiles' ksfs uses non-standard naming so + // the last chart is usually nightmare or normal, I use easy and normal + // indistinctly for SM so it shouldn't matter, I use piu fiesta/ex naming + // for directmove though, and we're just gathering basic info anyway, and + // most of the time all the KSF files have the same info in the #TITLE:; section + unsigned files = arrayKSFFileNames.size(); + RString dir = out.GetSongDir(); + if( !LoadGlobalData(dir + arrayKSFFileNames[files - 1], out, bKIUCompliant) ) + return false; + + out.m_sSongFileName = dir + arrayKSFFileNames[files - 1]; + // load the Steps from the rest of the KSF files + for( unsigned i=0; iSetFilename(dir + arrayKSFFileNames[i]); + out.AddSteps( pNewNotes ); + } + return true; +} + +/* + * (c) 2001-2006 Chris Danford, Glenn Maynard, Jason Felds + * 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. + */ diff --git a/src/NotesLoaderSM.cpp b/src/NotesLoaderSM.cpp index eedddb7bf9..42183cf68f 100644 --- a/src/NotesLoaderSM.cpp +++ b/src/NotesLoaderSM.cpp @@ -109,7 +109,7 @@ void SMLoader::LoadFromTokens( // have a meter on certain steps. Make the meter 1 in these instances. sMeter = "1"; } - out.SetMeter( StringToInt(sMeter) ); + out.SetMeter( std::stoi(sMeter) ); out.SetSMNoteData( sNoteData ); @@ -393,8 +393,8 @@ void SMLoader::ProcessTimeSignatures( TimingData &out, const RString line, const } const float fBeat = RowToBeat( vs2[0], rowsPerBeat ); - const int iNumerator = StringToInt( vs2[1] ); - const int iDenominator = StringToInt( vs2[2] ); + const int iNumerator = std::stoi( vs2[1] ); + const int iDenominator = std::stoi( vs2[2] ); if( fBeat < 0 ) { @@ -486,7 +486,7 @@ void SMLoader::ProcessSpeeds( TimingData &out, const RString line, const int row const float fDelay = StringToFloat( vs2[2] ); // XXX: ugly... - int iUnit = StringToInt(vs2[3]); + int iUnit = std::stoi(vs2[3]); SpeedSegment::BaseUnit unit = (iUnit == 0) ? SpeedSegment::UNIT_BEATS : SpeedSegment::UNIT_SECONDS; @@ -587,7 +587,7 @@ bool SMLoader::LoadFromBGChangesString( BackgroundChange &change, const RString // Backward compatibility: if( change.m_def.m_sEffect.empty() ) { - bool bLoop = StringToInt( aBGChangeValues[5] ) != 0; + bool bLoop = std::stoi( aBGChangeValues[5] ) != 0; if( !bLoop ) change.m_def.m_sEffect = SBE_StretchNoLoop; } @@ -597,7 +597,7 @@ bool SMLoader::LoadFromBGChangesString( BackgroundChange &change, const RString // Backward compatibility: if( change.m_def.m_sEffect.empty() ) { - bool bRewindMovie = StringToInt( aBGChangeValues[4] ) != 0; + bool bRewindMovie = std::stoi( aBGChangeValues[4] ) != 0; if( bRewindMovie ) change.m_def.m_sEffect = SBE_StretchRewind; } @@ -606,7 +606,7 @@ bool SMLoader::LoadFromBGChangesString( BackgroundChange &change, const RString // param 9 overrides this. // Backward compatibility: if( change.m_sTransition.empty() ) - change.m_sTransition = (StringToInt( aBGChangeValues[3] ) != 0) ? "CrossFade" : ""; + change.m_sTransition = (std::stoi( aBGChangeValues[3] ) != 0) ? "CrossFade" : ""; // fall through case 3: change.m_fRate = StringToFloat( aBGChangeValues[2] ); @@ -869,7 +869,7 @@ bool SMLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCache * used 3.9+ features are not excluded here */ else if(sParams[1].EqualsNoCase("ES") || sParams[1].EqualsNoCase("OMES")) out.m_SelectionDisplay = out.SHOW_ALWAYS; - else if( StringToInt(sParams[1]) > 0 ) + else if( std::stoi(sParams[1]) > 0 ) out.m_SelectionDisplay = out.SHOW_ALWAYS; else LOG->UserLog( "Song file", sPath, "has an unknown #SELECTABLE value, \"%s\"; ignored.", sParams[1].c_str() ); diff --git a/src/NotesLoaderSMA.cpp b/src/NotesLoaderSMA.cpp index a4732c9317..710f4b6197 100644 --- a/src/NotesLoaderSMA.cpp +++ b/src/NotesLoaderSMA.cpp @@ -33,11 +33,11 @@ void SMALoader::ProcessMultipliers( TimingData &out, const int iRowsPerBeat, con continue; } const float fComboBeat = RowToBeat( arrayMultiplierValues[0], iRowsPerBeat ); - const int iCombos = StringToInt( arrayMultiplierValues[1] ); // always true. + const int iCombos = std::stoi( arrayMultiplierValues[1] ); // always true. // hoping I'm right here: SMA files can use 6 values after the row/beat. const int iMisses = (size == 2 || size == 4 ? iCombos : - StringToInt(arrayMultiplierValues[2])); + std::stoi(arrayMultiplierValues[2])); out.AddSegment( ComboSegment(BeatToNoteRow(fComboBeat), iCombos, iMisses) ); } } @@ -61,7 +61,7 @@ void SMALoader::ProcessBeatsPerMeasure( TimingData &out, const RString sParam ) continue; } const float fBeat = StringToFloat( vs2[0] ); - const int iNumerator = StringToInt( vs2[1] ); + const int iNumerator = std::stoi( vs2[1] ); if( fBeat < 0 ) { @@ -296,7 +296,7 @@ bool SMALoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCach vector arrayBeatChangeValues; split( arrayBeatChangeExpressions[0], "=", arrayBeatChangeValues ); - iRowsPerBeat = StringToInt(arrayBeatChangeValues[1]); + iRowsPerBeat = std::stoi(arrayBeatChangeValues[1]); } else { @@ -327,7 +327,7 @@ bool SMALoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCach * used 3.9+ features are not excluded here */ else if(sParams[1].EqualsNoCase("ES") || sParams[1].EqualsNoCase("OMES")) out.m_SelectionDisplay = out.SHOW_ALWAYS; - else if( StringToInt(sParams[1]) > 0 ) + else if( std::stoi(sParams[1]) > 0 ) out.m_SelectionDisplay = out.SHOW_ALWAYS; else LOG->UserLog("Song file", diff --git a/src/NotesLoaderSSC.cpp b/src/NotesLoaderSSC.cpp index 946bdee98f..5276b53d92 100644 --- a/src/NotesLoaderSSC.cpp +++ b/src/NotesLoaderSSC.cpp @@ -104,8 +104,8 @@ void SSCLoader::ProcessCombos( TimingData &out, const RString line, const int ro continue; } const float fComboBeat = StringToFloat( arrayComboValues[0] ); - const int iCombos = StringToInt( arrayComboValues[1] ); - const int iMisses = (size == 2 ? iCombos : StringToInt(arrayComboValues[2])); + const int iCombos = std::stoi( arrayComboValues[1] ); + const int iMisses = (size == 2 ? iCombos : std::stoi(arrayComboValues[2])); out.AddSegment( ComboSegment( BeatToNoteRow(fComboBeat), iCombos, iMisses ) ); } } @@ -206,7 +206,7 @@ bool SSCLoader::LoadNoteDataFromSimfile( const RString & cachePath, Steps &out ) else if( valueName=="METER" ) { - if (out.GetMeter() != StringToInt(matcher)) + if (out.GetMeter() != std::stoi(matcher)) tryingSteps = false; } @@ -426,7 +426,7 @@ bool SSCLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCach * used 3.9+ features are not excluded here */ else if(sParams[1].EqualsNoCase("ES") || sParams[1].EqualsNoCase("OMES")) out.m_SelectionDisplay = out.SHOW_ALWAYS; - else if( StringToInt(sParams[1]) > 0 ) + else if( std::stoi(sParams[1]) > 0 ) out.m_SelectionDisplay = out.SHOW_ALWAYS; else LOG->UserLog( "Song file", sPath, "has an unknown #SELECTABLE value, \"%s\"; ignored.", sParams[1].c_str() ); @@ -549,13 +549,13 @@ bool SSCLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCach else if( sValueName=="HASMUSIC" ) { if( bFromCache ) - out.m_bHasMusic = StringToInt( sParams[1] ) != 0; + out.m_bHasMusic = std::stoi( sParams[1] ) != 0; } else if( sValueName=="HASBANNER" ) { if( bFromCache ) - out.m_bHasBanner = StringToInt( sParams[1] ) != 0; + out.m_bHasBanner = std::stoi( sParams[1] ) != 0; } // This tag will get us to the next section. @@ -603,7 +603,7 @@ bool SSCLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCach else if( sValueName=="METER" ) { - pNewNotes->SetMeter( StringToInt( sParams[1] ) ); + pNewNotes->SetMeter( std::stoi( sParams[1] ) ); } else if( sValueName=="RADARVALUES" ) @@ -918,7 +918,7 @@ bool SSCLoader::LoadEditFromMsd(const MsdFile &msd, else if( sValueName=="METER" ) { - pNewNotes->SetMeter( StringToInt( sParams[1] ) ); + pNewNotes->SetMeter( std::stoi( sParams[1] ) ); bSSCFormat = true; } diff --git a/src/OptionRowHandler.cpp b/src/OptionRowHandler.cpp index 7dd22390aa..c0f3aeb816 100644 --- a/src/OptionRowHandler.cpp +++ b/src/OptionRowHandler.cpp @@ -156,7 +156,7 @@ public: RageException::Throw( "Parse error in \"ScreenOptionsMaster::%s\".", sParam.c_str() ); m_Def.m_bOneChoiceForAllPlayers = false; - const int NumCols = StringToInt( lCmds.v[0].m_vsArgs[0] ); + const int NumCols = std::stoi( lCmds.v[0].m_vsArgs[0] ); for( unsigned i=1; i= 0 && pn < NUM_PLAYERS ); m_Def.m_vEnabledForPlayers.insert( pn ); } diff --git a/src/PlayerOptions.cpp b/src/PlayerOptions.cpp index 5b501e7ebe..f7480614a2 100644 --- a/src/PlayerOptions.cpp +++ b/src/PlayerOptions.cpp @@ -312,7 +312,7 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut { // XXX: We know what they want, is there any reason not to handle it? // Yes. We should be strict in handling the format. -Chris - sErrorOut = ssprintf("Invalid player options \"%s\"; did you mean '*%d'?", s.c_str(), StringToInt(s) ); + sErrorOut = ssprintf("Invalid player options \"%s\"; did you mean '*%d'?", s.c_str(), std::stoi(s) ); return false; } else diff --git a/src/Profile.cpp b/src/Profile.cpp index 952af9c0fa..860673907a 100644 --- a/src/Profile.cpp +++ b/src/Profile.cpp @@ -1862,7 +1862,7 @@ RString Profile::MakeUniqueFileNameNoExtension( RString sDir, RString sFileNameB continue; ASSERT( matches.size() == 1 ); - iIndex = StringToInt( matches[0] )+1; + iIndex = std::stoi( matches[0] )+1; break; } diff --git a/src/ProfileManager.cpp b/src/ProfileManager.cpp index 6d8ce6ddd5..99fa834e23 100644 --- a/src/ProfileManager.cpp +++ b/src/ProfileManager.cpp @@ -429,7 +429,7 @@ bool ProfileManager::CreateLocalProfile( RString sName, RString &sProfileIDOut ) vector vs; GetLocalProfileIDs( vs ); for (RString const &s : vs) - iMaxProfileNumber = StringToInt( s ); + iMaxProfileNumber = std::stoi( s ); int iProfileNumber = iMaxProfileNumber + 1; RString sProfileID = ssprintf( "%08d", iProfileNumber ); diff --git a/src/RageBitmapTexture.cpp b/src/RageBitmapTexture.cpp index f7fa989890..98dc7cfb38 100644 --- a/src/RageBitmapTexture.cpp +++ b/src/RageBitmapTexture.cpp @@ -26,8 +26,8 @@ static void GetResolutionFromFileName( RString sPath, int &iWidth, int &iHeight if( !re.Compare(sPath, asMatches) ) return; - iWidth = StringToInt( asMatches[0] ); - iHeight = StringToInt( asMatches[1] ); + iWidth = std::stoi( asMatches[0] ); + iHeight = std::stoi( asMatches[1] ); } RageBitmapTexture::RageBitmapTexture( RageTextureID name ) : diff --git a/src/RageTexture.cpp b/src/RageTexture.cpp index 0d9e17fd90..2476ca66b4 100644 --- a/src/RageTexture.cpp +++ b/src/RageTexture.cpp @@ -1,118 +1,118 @@ -#include "global.h" - -#include "RageTexture.h" -#include "RageUtil.h" -#include "RageTextureManager.h" -#include - - -RageTexture::RageTexture( RageTextureID name ): - m_iRefCount(1), m_bWasUsed(false), m_ID(name), - m_iSourceWidth(0), m_iSourceHeight(0), - m_iTextureWidth(0), m_iTextureHeight(0), - m_iImageWidth(0), m_iImageHeight(0), - m_iFramesWide(1), m_iFramesHigh(1) {} - - -RageTexture::~RageTexture() -{ - -} - - -void RageTexture::CreateFrameRects() -{ - GetFrameDimensionsFromFileName( GetID().filename, &m_iFramesWide, &m_iFramesHigh ); - - // Fill in the m_FrameRects with the bounds of each frame in the animation. - m_TextureCoordRects.clear(); - - for( int j=0; jTrace( "Adding frect%d %f %f %f %f", (i + j*m_iFramesWide), frect.left, frect.top, frect.right, frect.bottom ); - } - } -} - -void RageTexture::GetFrameDimensionsFromFileName( RString sPath, int* piFramesWide, int* piFramesHigh ) -{ - static Regex match( " ([0-9]+)x([0-9]+)([\\. ]|$)" ); - vector asMatch; - if( !match.Compare(sPath, asMatch) ) - { - *piFramesWide = *piFramesHigh = 1; - return; - } - *piFramesWide = StringToInt(asMatch[0]); - *piFramesHigh = StringToInt(asMatch[1]); -} - -const RectF *RageTexture::GetTextureCoordRect( int iFrameNo ) const -{ - return &m_TextureCoordRects[iFrameNo]; -} - -// lua start -#include "LuaBinding.h" - -/** @brief Allow Lua to have access to the RageTexture. */ -class LunaRageTexture: public Luna -{ -public: - static int position( T* p, lua_State *L ) { p->SetPosition( FArg(1) ); return 0; } - static int loop( T* p, lua_State *L ) { p->SetLooping( BIArg(1) ); return 0; } - static int rate( T* p, lua_State *L ) { p->SetPlaybackRate( FArg(1) ); return 0; } - static int GetTextureCoordRect( T* p, lua_State *L ) - { - const RectF *pRect = p->GetTextureCoordRect( IArg(1) ); - lua_pushnumber( L, pRect->left ); - lua_pushnumber( L, pRect->top ); - lua_pushnumber( L, pRect->right ); - lua_pushnumber( L, pRect->bottom ); - return 4; - } - - LunaRageTexture() - { - ADD_METHOD( position ); - ADD_METHOD( loop ); - ADD_METHOD( rate ); - ADD_METHOD( GetTextureCoordRect ); - } -}; - -LUA_REGISTER_CLASS( RageTexture ) -// lua end - -/* - * Copyright (c) 2001-2004 Chris Danford - * 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. - */ +#include "global.h" + +#include "RageTexture.h" +#include "RageUtil.h" +#include "RageTextureManager.h" +#include + + +RageTexture::RageTexture( RageTextureID name ): + m_iRefCount(1), m_bWasUsed(false), m_ID(name), + m_iSourceWidth(0), m_iSourceHeight(0), + m_iTextureWidth(0), m_iTextureHeight(0), + m_iImageWidth(0), m_iImageHeight(0), + m_iFramesWide(1), m_iFramesHigh(1) {} + + +RageTexture::~RageTexture() +{ + +} + + +void RageTexture::CreateFrameRects() +{ + GetFrameDimensionsFromFileName( GetID().filename, &m_iFramesWide, &m_iFramesHigh ); + + // Fill in the m_FrameRects with the bounds of each frame in the animation. + m_TextureCoordRects.clear(); + + for( int j=0; jTrace( "Adding frect%d %f %f %f %f", (i + j*m_iFramesWide), frect.left, frect.top, frect.right, frect.bottom ); + } + } +} + +void RageTexture::GetFrameDimensionsFromFileName( RString sPath, int* piFramesWide, int* piFramesHigh ) +{ + static Regex match( " ([0-9]+)x([0-9]+)([\\. ]|$)" ); + vector asMatch; + if( !match.Compare(sPath, asMatch) ) + { + *piFramesWide = *piFramesHigh = 1; + return; + } + *piFramesWide = std::stoi(asMatch[0]); + *piFramesHigh = std::stoi(asMatch[1]); +} + +const RectF *RageTexture::GetTextureCoordRect( int iFrameNo ) const +{ + return &m_TextureCoordRects[iFrameNo]; +} + +// lua start +#include "LuaBinding.h" + +/** @brief Allow Lua to have access to the RageTexture. */ +class LunaRageTexture: public Luna +{ +public: + static int position( T* p, lua_State *L ) { p->SetPosition( FArg(1) ); return 0; } + static int loop( T* p, lua_State *L ) { p->SetLooping( BIArg(1) ); return 0; } + static int rate( T* p, lua_State *L ) { p->SetPlaybackRate( FArg(1) ); return 0; } + static int GetTextureCoordRect( T* p, lua_State *L ) + { + const RectF *pRect = p->GetTextureCoordRect( IArg(1) ); + lua_pushnumber( L, pRect->left ); + lua_pushnumber( L, pRect->top ); + lua_pushnumber( L, pRect->right ); + lua_pushnumber( L, pRect->bottom ); + return 4; + } + + LunaRageTexture() + { + ADD_METHOD( position ); + ADD_METHOD( loop ); + ADD_METHOD( rate ); + ADD_METHOD( GetTextureCoordRect ); + } +}; + +LUA_REGISTER_CLASS( RageTexture ) +// lua end + +/* + * Copyright (c) 2001-2004 Chris Danford + * 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. + */ diff --git a/src/RageUtil.cpp b/src/RageUtil.cpp index db596bb253..dacf2e49bf 100644 --- a/src/RageUtil.cpp +++ b/src/RageUtil.cpp @@ -263,8 +263,8 @@ float HHMMSSToSeconds( const RString &sHHMMSS ) arrayBits.insert(arrayBits.begin(), "0" ); // pad missing bits float fSeconds = 0; - fSeconds += StringToInt( arrayBits[0] ) * 60 * 60; - fSeconds += StringToInt( arrayBits[1] ) * 60; + fSeconds += std::stoi( arrayBits[0] ) * 60 * 60; + fSeconds += std::stoi( arrayBits[1] ) * 60; fSeconds += StringToFloat( arrayBits[2] ); return fSeconds; @@ -1751,13 +1751,6 @@ 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; -} - float StringToFloat( const RString &sString ) { float ret = strtof( sString, nullptr ); @@ -2207,7 +2200,7 @@ namespace StringConversion if( sValue.size() == 0 ) return false; - out = (StringToInt(sValue) != 0); + out = (std::stoi(sValue) != 0); return true; } diff --git a/src/RageUtil.h b/src/RageUtil.h index 07ab2d83fa..a44739f45a 100644 --- a/src/RageUtil.h +++ b/src/RageUtil.h @@ -405,11 +405,7 @@ void MakeUpper( char *p, size_t iLen ); void MakeLower( char *p, size_t iLen ); void MakeUpper( wchar_t *p, size_t iLen ); void MakeLower( wchar_t *p, size_t iLen ); -/** - * @brief Have a standard way of converting Strings to integers. - * @param sString the string to convert. - * @return the integer we are after. */ -int StringToInt( const RString &sString ); + float StringToFloat( const RString &sString ); /** * @brief Have a standard way of converting floats to strings. diff --git a/src/ScreenEdit.cpp b/src/ScreenEdit.cpp index 6ca6eba926..db6176ce87 100644 --- a/src/ScreenEdit.cpp +++ b/src/ScreenEdit.cpp @@ -3313,7 +3313,7 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) } else if( SM == SM_BackFromDifficultyMeterChange ) { - int i = StringToInt( ScreenTextEntry::s_sLastAnswer ); + int i = std::stoi( ScreenTextEntry::s_sLastAnswer ); GAMESTATE->m_pCurSteps[PLAYER_1]->SetMeter(i); SetDirty( true ); } @@ -3376,7 +3376,7 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) } else if ( SM == SM_BackFromTickcountChange && !ScreenTextEntry::s_bCancelledLast ) { - int iTick = StringToInt( ScreenTextEntry::s_sLastAnswer ); + int iTick = std::stoi( ScreenTextEntry::s_sLastAnswer ); if ( iTick >= 0 && iTick <= ROWS_PER_BEAT ) GetAppropriateTimingForUpdate().AddSegment( TickcountSegment( GetRow(), iTick) ); @@ -3441,7 +3441,7 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) } else { - int tmp = StringToInt(ScreenTextEntry::s_sLastAnswer ); + int tmp = std::stoi(ScreenTextEntry::s_sLastAnswer ); SpeedSegment::BaseUnit unit = (tmp == 0 ) ? SpeedSegment::UNIT_BEATS : SpeedSegment::UNIT_SECONDS; @@ -4091,7 +4091,7 @@ static void ChangeStepCredit( const RString &sNew ) static void ChangeStepMeter( const RString &sNew ) { - int diff = StringToInt(sNew); + int diff = std::stoi(sNew); GAMESTATE->m_pCurSteps[PLAYER_1]->SetMeter(max(diff, 1)); } diff --git a/src/ScreenGameplay.cpp b/src/ScreenGameplay.cpp index 7117b85d43..ec9bec5780 100644 --- a/src/ScreenGameplay.cpp +++ b/src/ScreenGameplay.cpp @@ -2801,7 +2801,7 @@ void ScreenGameplay::SaveReplay() continue; ASSERT( matches.size() == 1 ); - iIndex = StringToInt( matches[0] )+1; + iIndex = std::stoi( matches[0] )+1; break; } diff --git a/src/ScreenPackages.cpp b/src/ScreenPackages.cpp index 32ac7178cf..674ec0360d 100644 --- a/src/ScreenPackages.cpp +++ b/src/ScreenPackages.cpp @@ -1,741 +1,741 @@ -#include "global.h" - -#if !defined(WITHOUT_NETWORKING) -#include "ScreenPackages.h" -#include "GameConstantsAndTypes.h" -#include "GameState.h" -#include "ThemeManager.h" -#include "RageDisplay.h" -#include "RageLog.h" -#include "RageUtil.h" -#include "RageFile.h" -#include "ScreenTextEntry.h" -#include "ScreenManager.h" -#include -#include "LocalizedString.h" - -AutoScreenMessage( SM_BackFromURL ); - -REGISTER_SCREEN_CLASS( ScreenPackages ); - -static LocalizedString VISIT_URL( "ScreenPackages", "VisitURL" ); -void ScreenPackages::Init() -{ - EXISTINGBG_WIDTH.Load(m_sName, "PackagesBGWidth"); - WEBBG_WIDTH.Load(m_sName, "WebBGWidth"); - NUM_PACKAGES_SHOW.Load(m_sName, "NumPackagesShow"); - NUM_LINKS_SHOW.Load(m_sName, "NumLinksShow"); - DEFAULT_URL.Load(m_sName, "DefaultUrl"); - ScreenWithMenuElements::Init(); - - m_iPackagesPos = 0; - m_iLinksPos = 0; - m_iDLorLST = 0; - m_bIsDownloading = false; - m_bCanDL = THEME->GetMetricB( m_sName, "CanDL" ); - m_sStatus = ""; - m_fLastUpdate = 0; - m_iTotalBytes = 0; - m_iDownloaded = 0; - - FOREACH_PlayerNumber( pn ) - GAMESTATE->JoinPlayer( pn ); - - m_sprExistingBG.Load( THEME->GetPathG( m_sName, "PackagesBG" ) ); - m_sprExistingBG->SetName( "PackagesBG" ); - LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_sprExistingBG ); - this->AddChild( m_sprExistingBG ); - - m_sprWebBG.Load( THEME->GetPathG( m_sName, "WebBG" ) ); - m_sprWebBG->SetName( "WebBG" ); - LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_sprWebBG ); - this->AddChild( m_sprWebBG ); - - COMMAND( m_sprExistingBG, "Back" ); - COMMAND( m_sprWebBG, "Away" ); - -// m_fOutputFile. - m_textPackages.LoadFromFont( THEME->GetPathF(m_sName,"default") ); - m_textPackages.SetShadowLength( 0 ); - m_textPackages.SetName( "Packages" ); - m_textPackages.SetMaxWidth( EXISTINGBG_WIDTH ); - LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_textPackages ); - this->AddChild( &m_textPackages ); - RefreshPackages(); - - m_textWeb.LoadFromFont( THEME->GetPathF(m_sName,"default") ); - m_textWeb.SetShadowLength( 0 ); - m_textWeb.SetName( "Web" ); - m_textWeb.SetMaxWidth( WEBBG_WIDTH ); - LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_textWeb ); - this->AddChild( &m_textWeb); - m_Links.push_back( " " ); // what is this here for? -aj - m_LinkTitles.push_back( VISIT_URL ); - - m_textURL.LoadFromFont( THEME->GetPathF( m_sName,"default") ); - m_textURL.SetShadowLength( 0 ); - m_textURL.SetName( "WebURL" ); - LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_textURL ); - this->AddChild( &m_textURL ); - UpdateLinksList(); - - m_sprWebSel.SetName( "WebSel" ); - m_sprWebSel.Load( THEME->GetPathG( m_sName, "WebSel" ) ); - LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_sprWebSel ); - this->AddChild( &m_sprWebSel ); - - m_sprDLBG.SetName( "Download" ); - m_sprDLBG.Load( THEME->GetPathG( m_sName, "DownloadBG" ) ); - LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_sprDLBG ); - this->AddChild( &m_sprDLBG ); - - //(HTTP ELEMENTS) - m_sprDL.SetName( "Download" ); - m_sprDL.Load( THEME->GetPathG( m_sName, "Download" ) ); - LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_sprDL ); - this->AddChild( &m_sprDL ); - m_sprDL.RunCommands( THEME->GetMetricA( m_sName, "DownloadProgressCommand" ) ); - - m_textStatus.LoadFromFont( THEME->GetPathF(m_sName,"default") ); - m_textStatus.SetShadowLength( 0 ); - m_textStatus.SetName( "DownloadStatus" ); - LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_textStatus ); - this->AddChild( &m_textStatus ); - - // if the default url isn't empty, load it. - if( !DEFAULT_URL.GetValue().empty() ) - EnterURL( DEFAULT_URL ); - - UpdateProgress(); - - // Workaround: For some reason, the first download sometimes - // corrupts; by opening and closing the RageFile, this - // problem does not occur. Go figure? - - // XXX: This is a really dirty work around! - // Why does RageFile do this? - - // It's always some strange number of bytes at the end of the - // file when it corrupts. - m_fOutputFile.Open( "Packages/dummy.txt", RageFile::WRITE ); - m_fOutputFile.Close(); -} - -void ScreenPackages::HandleScreenMessage( const ScreenMessage SM ) -{ - if( SM == SM_GoToPrevScreen ) - { - SCREENMAN->SetNewScreen( THEME->GetMetric (m_sName, "PrevScreen") ); - } - else if( SM ==SM_GoToNextScreen ) - { - SCREENMAN->SetNewScreen( THEME->GetMetric (m_sName, "NextScreen") ); - } - else if( SM == SM_BackFromURL ) - { - if ( !ScreenTextEntry::s_bCancelledLast ) - EnterURL( ScreenTextEntry::s_sLastAnswer ); - } - ScreenWithMenuElements::HandleScreenMessage( SM ); -} - -static LocalizedString DOWNLOAD_PROGRESS( "ScreenPackages", "DL @ %d KB/s" ); -void ScreenPackages::Update( float fDeltaTime ) -{ - HTTPUpdate(); - - m_fLastUpdate += fDeltaTime; - if ( m_fLastUpdate >= 1.0 ) - { - if ( m_bIsDownloading && m_bGotHeader ) - m_sStatus = ssprintf( DOWNLOAD_PROGRESS.GetValue(), int((m_iDownloaded-m_bytesLastUpdate)/1024) ); - - m_bytesLastUpdate = m_iDownloaded; - UpdateProgress(); - m_fLastUpdate = 0; - } - - ScreenWithMenuElements::Update(fDeltaTime); -} - -static LocalizedString ENTER_URL ("ScreenPackages","Enter URL"); -bool ScreenPackages::MenuStart( const InputEventPlus &input ) -{ - if ( m_iDLorLST == 1 ) - { - if ( m_iLinksPos == 0 ) - ScreenTextEntry::TextEntry( SM_BackFromURL, ENTER_URL, "http://", 255 ); - else - EnterURL( m_Links[m_iLinksPos] ); - } - ScreenWithMenuElements::MenuStart( input ); - return true; -} - -bool ScreenPackages::MenuUp( const InputEventPlus &input ) -{ - if ( m_bIsDownloading ) - return false; - if ( m_iDLorLST == 0) - { - if ( m_iPackagesPos > 0) - { - m_iPackagesPos--; - UpdatePackagesList(); - } - } - else - { - if ( m_iLinksPos > 0 ) - { - m_iLinksPos--; - UpdateLinksList(); - } - } - ScreenWithMenuElements::MenuUp( input ); - return true; -} - -bool ScreenPackages::MenuDown( const InputEventPlus &input ) -{ - if ( m_bIsDownloading ) - return false; - - if ( m_iDLorLST == 0) - { - if( (unsigned) m_iPackagesPos < m_Packages.size() - 1 ) - { - m_iPackagesPos++; - UpdatePackagesList(); - } - } - else - { - if( (unsigned) m_iLinksPos < m_LinkTitles.size() - 1 ) - { - m_iLinksPos++; - UpdateLinksList(); - } - } - ScreenWithMenuElements::MenuDown( input ); - return true; -} - -bool ScreenPackages::MenuLeft( const InputEventPlus &input ) -{ - if ( m_bIsDownloading ) - return false; - if ( !m_bCanDL ) - return false; - - /* - m_sprExistingBG.StopTweening(); - m_sprWebBG.StopTweening(); - */ - - if ( m_iDLorLST == 0 ) - { - m_iDLorLST = 1; - COMMAND( m_sprExistingBG, "Away" ); - COMMAND( m_sprWebBG, "Back" ); - } - else - { - m_iDLorLST = 0; - COMMAND( m_sprExistingBG, "Back" ); - COMMAND( m_sprWebBG, "Away" ); - } - ScreenWithMenuElements::MenuLeft( input ); - return true; -} - -bool ScreenPackages::MenuRight( const InputEventPlus &input ) -{ - if ( m_bIsDownloading ) - return false; - - // Huh? - //MenuLeft( input ); - - if ( m_iDLorLST == 1 ) - { - m_iDLorLST = 0; - COMMAND( m_sprExistingBG, "Away" ); - COMMAND( m_sprWebBG, "Back" ); - } - else - { - m_iDLorLST = 1; - COMMAND( m_sprExistingBG, "Back" ); - COMMAND( m_sprWebBG, "Away" ); - } - - ScreenWithMenuElements::MenuRight( input ); - return true; -} - -static LocalizedString DOWNLOAD_CANCELLED( "ScreenPackages", "Download cancelled." ); -bool ScreenPackages::MenuBack( const InputEventPlus &input ) -{ - if ( m_bIsDownloading ) - { - SCREENMAN->SystemMessage( DOWNLOAD_CANCELLED ); - CancelDownload( ); - return true; - } - - TweenOffScreen(); - Cancel( SM_GoToPrevScreen ); - ScreenWithMenuElements::MenuBack( input ); - return true; -} - -void ScreenPackages::TweenOffScreen( ) -{ - OFF_COMMAND( m_sprExistingBG ); - OFF_COMMAND( m_sprWebBG ); - OFF_COMMAND( m_sprWebSel ); - OFF_COMMAND( m_textPackages ); - OFF_COMMAND( m_textWeb ); - OFF_COMMAND( m_sprDL ); - OFF_COMMAND( m_sprDLBG ); - OFF_COMMAND( m_textStatus ); - - m_fOutputFile.Close(); -} - -void ScreenPackages::RefreshPackages() -{ - GetDirListing( "Packages/*.zip", m_Packages, false, false ); - GetDirListing( "Packages/*.smzip", m_Packages, false, false ); - - if ( m_iPackagesPos < 0 ) - m_iPackagesPos = 0; - - if( (unsigned) m_iPackagesPos >= m_Packages.size() ) - m_iPackagesPos = m_Packages.size() - 1; - - UpdatePackagesList(); -} - -void ScreenPackages::UpdatePackagesList() -{ - RString TempText=""; - int min = m_iPackagesPos-NUM_PACKAGES_SHOW; - int max = m_iPackagesPos+NUM_PACKAGES_SHOW; - for (int i=min; i= 0 && (unsigned) i < m_Packages.size() ) - TempText += m_Packages[i] + '\n'; - else - TempText += '\n'; - } - m_textPackages.SetText( TempText ); -} - -void ScreenPackages::UpdateLinksList() -{ - RString TempText=""; - if( (unsigned) m_iLinksPos >= m_LinkTitles.size() ) - m_iLinksPos = m_LinkTitles.size() - 1; - int min = m_iLinksPos-NUM_LINKS_SHOW; - int max = m_iLinksPos+NUM_LINKS_SHOW; - for (int i=min; i= 0 && (unsigned) i < m_LinkTitles.size() ) - TempText += m_LinkTitles[i] + '\n'; - else - TempText += '\n'; - } - m_textWeb.SetText( TempText ); - if ( m_iLinksPos >= 0 && (unsigned) m_iLinksPos < m_Links.size() ) - m_textURL.SetText( m_Links[m_iLinksPos] ); - else - m_textURL.SetText( " " ); -} - -void ScreenPackages::HTMLParse() -{ - m_Links.clear(); - m_LinkTitles.clear(); - m_Links.push_back( " " ); - m_LinkTitles.push_back( VISIT_URL.GetValue() ); - - // XXX: VERY DIRTY HTML PARSER! - // Only designed to find links on websites. - size_t i = m_sBUFFER.find( "", i+1 ); - l = m_sBUFFER.find( "HREF", i+1); - m = m_sBUFFER.find( "=", l ); - - if( k == string::npos || l == string::npos || m == string::npos || l > k || m > k ) //no "href" in this tag. - { - if ( mode == 0 ) - i = m_sBUFFER.find( "", k+1 ); - if ( j < l ) - k = j; - - RString TempLink = StripOutContainers( m_sBUFFER.substr(m+1,k-m-1) ); - // xxx: handle https? -aj - if ( TempLink.substr(0,7).compare("http://") != 0 ) - TempLink = m_sBaseAddress + TempLink; - - RString TempTitle = m_sBUFFER.substr( k+1, l-k-1 ); - - m_Links.push_back( TempLink ); - m_LinkTitles.push_back( TempTitle ); - - if ( mode == 0 ) - i = m_sBUFFER.find( "Remove( "Packages/" + m_sEndName ) ) - SCREENMAN->SystemMessage( "Packages/" + m_sEndName ); -} - -static LocalizedString INVALID_URL( "ScreenPackages", "Invalid URL." ); -static LocalizedString FILE_ALREADY_EXISTS( "ScreenPackages", "File Already Exists" ); -static LocalizedString FAILED_TO_CONNECT( "ScreenPackages", "Failed to connect." ); -static LocalizedString HEADER_SENT( "ScreenPackages", "Header Sent." ); -void ScreenPackages::EnterURL( const RString & sURL ) -{ - RString Proto; - RString Server; - int Port=80; - RString sAddress; - - if( !ParseHTTPAddress( sURL, Proto, Server, Port, sAddress ) ) - { - m_sStatus = INVALID_URL.GetValue(); - UpdateProgress(); - return; - } - - // Determine if this is a website, or a package? - // Criteria: does it end with *zip? - if( sAddress.Right(3).CompareNoCase("zip") == 0 ) - m_bIsPackage=true; - else - m_bIsPackage = false; - - m_sBaseAddress = "http://" + Server; - if( Port != 80 ) - m_sBaseAddress += ssprintf( ":%d", Port ); - m_sBaseAddress += "/"; - - if( sAddress.Right(1) != "/" ) - { - m_sEndName = Basename( sAddress ); - m_sBaseAddress += Dirname( sAddress ); - } - else - { - m_sEndName = ""; - } - - // Open the file... - - // First find out if a file by this name already exists - // if so, then we gotta ditch out. - // XXX: This should be fixed by a prompt or something? - - // if we are not talking about a file, let's not worry - if( m_sEndName != "" && m_bIsPackage ) - { - vector AddTo; - GetDirListing( "Packages/"+m_sEndName, AddTo, false, false ); - if ( AddTo.size() > 0 ) - { - m_sStatus = FILE_ALREADY_EXISTS.GetValue(); - UpdateProgress(); - return; - } - - if( !m_fOutputFile.Open( "Packages/"+m_sEndName, RageFile::WRITE | RageFile::STREAMED ) ) - { - m_sStatus = m_fOutputFile.GetError(); - UpdateProgress(); - return; - } - } - // Continue... - - sAddress = URLEncode( StripOutContainers(sAddress) ); - - if ( sAddress != "/" ) - sAddress = "/" + sAddress; - - m_wSocket.close(); - m_wSocket.create(); - - m_wSocket.blocking = true; - - if( !m_wSocket.connect( Server, (short) Port ) ) - { - m_sStatus = FAILED_TO_CONNECT.GetValue(); - UpdateProgress(); - return; - } - - //Produce HTTP header - - RString Header=""; - - Header = "GET "+sAddress+" HTTP/1.0\r\n"; - Header+= "Host: " + Server + "\r\n"; - Header+= "Connection: closed\r\n\r\n"; - - m_wSocket.SendData( Header.c_str(), Header.length() ); - m_sStatus = HEADER_SENT.GetValue(); - m_wSocket.blocking = false; - m_bIsDownloading = true; - m_sBUFFER = ""; - m_bGotHeader = false; - UpdateProgress(); - return; -} - -static size_t FindEndOfHeaders( const RString &buf ) -{ - size_t iPos1 = buf.find( "\n\n" ); - size_t iPos2 = buf.find( "\r\n\r\n" ); - LOG->Trace("end: %u, %u", unsigned(iPos1), unsigned(iPos2)); - if( iPos1 != string::npos && (iPos2 == string::npos || iPos2 > iPos1) ) - return iPos1 + 2; - else if( iPos2 != string::npos && (iPos1 == string::npos || iPos1 > iPos2) ) - return iPos2 + 4; - else - return string::npos; -} - -static LocalizedString WAITING_FOR_HEADER( "ScreenPackages", "Waiting for header." ); -void ScreenPackages::HTTPUpdate() -{ - if( !m_bIsDownloading ) - return; - - int BytesGot=0; - // Keep this as a code block - // as there may be need to "if" it out some time. - /* If you need a conditional for a large block of code, stick it in - * a function and return. */ - while(1) - { - char Buffer[1024]; - int iSize = m_wSocket.ReadData( Buffer, 1024 ); - if( iSize <= 0 ) - break; - - m_sBUFFER.append( Buffer, iSize ); - BytesGot += iSize; - } - - if( !m_bGotHeader ) - { - m_sStatus = WAITING_FOR_HEADER.GetValue(); - // We don't know if we are using unix-style or dos-style - size_t iHeaderEnd = FindEndOfHeaders( m_sBUFFER ); - if( iHeaderEnd == m_sBUFFER.npos ) - return; - - // "HTTP/1.1 200 OK" - size_t i = m_sBUFFER.find(" "); - size_t j = m_sBUFFER.find(" ",i+1); - size_t k = m_sBUFFER.find("\n",j+1); - if ( i == string::npos || j == string::npos || k == string::npos ) - { - m_iResponseCode = -100; - m_sResponseName = "Malformed response."; - return; - } - m_iResponseCode = StringToInt(m_sBUFFER.substr(i+1,j-i)); - m_sResponseName = m_sBUFFER.substr( j+1, k-j ); - - i = m_sBUFFER.find("Content-Length:"); - j = m_sBUFFER.find("\n", i+1 ); - - if( i != string::npos ) - m_iTotalBytes = StringToInt(m_sBUFFER.substr(i+16,j-i)); - else - m_iTotalBytes = -1; //We don't know, so go until disconnect - - m_bGotHeader = true; - m_sBUFFER.erase( 0, iHeaderEnd ); - } - - if( m_bIsPackage ) - { - m_iDownloaded += m_sBUFFER.length(); - m_fOutputFile.Write( m_sBUFFER ); - m_sBUFFER = ""; - } - else - { - m_iDownloaded = m_sBUFFER.length(); - } - - if ( ( m_iTotalBytes <= m_iDownloaded && m_iTotalBytes != -1 ) || - //We have the full doc. (And we knew how big it was) - ( m_iTotalBytes == -1 && - ( m_wSocket.state == EzSockets::skERROR || m_wSocket.state == EzSockets::skDISCONNECTED ) ) ) - //We didn't know how big it was, and were disconnected - //So that means we have it all. - { - m_wSocket.close(); - m_bIsDownloading = false; - m_bGotHeader=false; - m_sStatus = ssprintf( "Done ;%dB", int(m_iDownloaded) ); - - if( m_iResponseCode < 200 || m_iResponseCode >= 400 ) - { - m_sStatus = ssprintf( "%ld", m_iResponseCode ) + m_sResponseName; - } - else - { - if( m_bIsPackage && m_iResponseCode < 300 ) - { - m_fOutputFile.Close(); - RefreshPackages(); - m_iDownloaded = 0; - } - else - HTMLParse(); - } - } -} - -bool ScreenPackages::ParseHTTPAddress( const RString &URL, RString &sProto, RString &sServer, int &iPort, RString &sAddress ) -{ - // [PROTO://]SERVER[:PORT][/URL] - - Regex re( - "^([A-Z]+)://" // [0]: HTTP:// - "([^/:]+)" // [1]: a.b.com - "(:([0-9]+))?" // [2], [3]: :1234 (optional, default 80) - "(/(.*))?$"); // [4], [5]: /foo.html (optional) - vector asMatches; - if( !re.Compare( URL, asMatches ) ) - return false; - ASSERT( asMatches.size() == 6 ); - - sProto = asMatches[0]; - sServer = asMatches[1]; - if( asMatches[3] != "" ) - { - iPort = StringToInt(asMatches[3]); - if( iPort == 0 ) - return false; - } - else - iPort = 80; - - sAddress = asMatches[5]; - - return true; -} - -#endif -/* - * (c) 2004 Charles Lohr - * 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. - */ +#include "global.h" + +#if !defined(WITHOUT_NETWORKING) +#include "ScreenPackages.h" +#include "GameConstantsAndTypes.h" +#include "GameState.h" +#include "ThemeManager.h" +#include "RageDisplay.h" +#include "RageLog.h" +#include "RageUtil.h" +#include "RageFile.h" +#include "ScreenTextEntry.h" +#include "ScreenManager.h" +#include +#include "LocalizedString.h" + +AutoScreenMessage( SM_BackFromURL ); + +REGISTER_SCREEN_CLASS( ScreenPackages ); + +static LocalizedString VISIT_URL( "ScreenPackages", "VisitURL" ); +void ScreenPackages::Init() +{ + EXISTINGBG_WIDTH.Load(m_sName, "PackagesBGWidth"); + WEBBG_WIDTH.Load(m_sName, "WebBGWidth"); + NUM_PACKAGES_SHOW.Load(m_sName, "NumPackagesShow"); + NUM_LINKS_SHOW.Load(m_sName, "NumLinksShow"); + DEFAULT_URL.Load(m_sName, "DefaultUrl"); + ScreenWithMenuElements::Init(); + + m_iPackagesPos = 0; + m_iLinksPos = 0; + m_iDLorLST = 0; + m_bIsDownloading = false; + m_bCanDL = THEME->GetMetricB( m_sName, "CanDL" ); + m_sStatus = ""; + m_fLastUpdate = 0; + m_iTotalBytes = 0; + m_iDownloaded = 0; + + FOREACH_PlayerNumber( pn ) + GAMESTATE->JoinPlayer( pn ); + + m_sprExistingBG.Load( THEME->GetPathG( m_sName, "PackagesBG" ) ); + m_sprExistingBG->SetName( "PackagesBG" ); + LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_sprExistingBG ); + this->AddChild( m_sprExistingBG ); + + m_sprWebBG.Load( THEME->GetPathG( m_sName, "WebBG" ) ); + m_sprWebBG->SetName( "WebBG" ); + LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_sprWebBG ); + this->AddChild( m_sprWebBG ); + + COMMAND( m_sprExistingBG, "Back" ); + COMMAND( m_sprWebBG, "Away" ); + +// m_fOutputFile. + m_textPackages.LoadFromFont( THEME->GetPathF(m_sName,"default") ); + m_textPackages.SetShadowLength( 0 ); + m_textPackages.SetName( "Packages" ); + m_textPackages.SetMaxWidth( EXISTINGBG_WIDTH ); + LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_textPackages ); + this->AddChild( &m_textPackages ); + RefreshPackages(); + + m_textWeb.LoadFromFont( THEME->GetPathF(m_sName,"default") ); + m_textWeb.SetShadowLength( 0 ); + m_textWeb.SetName( "Web" ); + m_textWeb.SetMaxWidth( WEBBG_WIDTH ); + LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_textWeb ); + this->AddChild( &m_textWeb); + m_Links.push_back( " " ); // what is this here for? -aj + m_LinkTitles.push_back( VISIT_URL ); + + m_textURL.LoadFromFont( THEME->GetPathF( m_sName,"default") ); + m_textURL.SetShadowLength( 0 ); + m_textURL.SetName( "WebURL" ); + LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_textURL ); + this->AddChild( &m_textURL ); + UpdateLinksList(); + + m_sprWebSel.SetName( "WebSel" ); + m_sprWebSel.Load( THEME->GetPathG( m_sName, "WebSel" ) ); + LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_sprWebSel ); + this->AddChild( &m_sprWebSel ); + + m_sprDLBG.SetName( "Download" ); + m_sprDLBG.Load( THEME->GetPathG( m_sName, "DownloadBG" ) ); + LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_sprDLBG ); + this->AddChild( &m_sprDLBG ); + + //(HTTP ELEMENTS) + m_sprDL.SetName( "Download" ); + m_sprDL.Load( THEME->GetPathG( m_sName, "Download" ) ); + LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_sprDL ); + this->AddChild( &m_sprDL ); + m_sprDL.RunCommands( THEME->GetMetricA( m_sName, "DownloadProgressCommand" ) ); + + m_textStatus.LoadFromFont( THEME->GetPathF(m_sName,"default") ); + m_textStatus.SetShadowLength( 0 ); + m_textStatus.SetName( "DownloadStatus" ); + LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_textStatus ); + this->AddChild( &m_textStatus ); + + // if the default url isn't empty, load it. + if( !DEFAULT_URL.GetValue().empty() ) + EnterURL( DEFAULT_URL ); + + UpdateProgress(); + + // Workaround: For some reason, the first download sometimes + // corrupts; by opening and closing the RageFile, this + // problem does not occur. Go figure? + + // XXX: This is a really dirty work around! + // Why does RageFile do this? + + // It's always some strange number of bytes at the end of the + // file when it corrupts. + m_fOutputFile.Open( "Packages/dummy.txt", RageFile::WRITE ); + m_fOutputFile.Close(); +} + +void ScreenPackages::HandleScreenMessage( const ScreenMessage SM ) +{ + if( SM == SM_GoToPrevScreen ) + { + SCREENMAN->SetNewScreen( THEME->GetMetric (m_sName, "PrevScreen") ); + } + else if( SM ==SM_GoToNextScreen ) + { + SCREENMAN->SetNewScreen( THEME->GetMetric (m_sName, "NextScreen") ); + } + else if( SM == SM_BackFromURL ) + { + if ( !ScreenTextEntry::s_bCancelledLast ) + EnterURL( ScreenTextEntry::s_sLastAnswer ); + } + ScreenWithMenuElements::HandleScreenMessage( SM ); +} + +static LocalizedString DOWNLOAD_PROGRESS( "ScreenPackages", "DL @ %d KB/s" ); +void ScreenPackages::Update( float fDeltaTime ) +{ + HTTPUpdate(); + + m_fLastUpdate += fDeltaTime; + if ( m_fLastUpdate >= 1.0 ) + { + if ( m_bIsDownloading && m_bGotHeader ) + m_sStatus = ssprintf( DOWNLOAD_PROGRESS.GetValue(), int((m_iDownloaded-m_bytesLastUpdate)/1024) ); + + m_bytesLastUpdate = m_iDownloaded; + UpdateProgress(); + m_fLastUpdate = 0; + } + + ScreenWithMenuElements::Update(fDeltaTime); +} + +static LocalizedString ENTER_URL ("ScreenPackages","Enter URL"); +bool ScreenPackages::MenuStart( const InputEventPlus &input ) +{ + if ( m_iDLorLST == 1 ) + { + if ( m_iLinksPos == 0 ) + ScreenTextEntry::TextEntry( SM_BackFromURL, ENTER_URL, "http://", 255 ); + else + EnterURL( m_Links[m_iLinksPos] ); + } + ScreenWithMenuElements::MenuStart( input ); + return true; +} + +bool ScreenPackages::MenuUp( const InputEventPlus &input ) +{ + if ( m_bIsDownloading ) + return false; + if ( m_iDLorLST == 0) + { + if ( m_iPackagesPos > 0) + { + m_iPackagesPos--; + UpdatePackagesList(); + } + } + else + { + if ( m_iLinksPos > 0 ) + { + m_iLinksPos--; + UpdateLinksList(); + } + } + ScreenWithMenuElements::MenuUp( input ); + return true; +} + +bool ScreenPackages::MenuDown( const InputEventPlus &input ) +{ + if ( m_bIsDownloading ) + return false; + + if ( m_iDLorLST == 0) + { + if( (unsigned) m_iPackagesPos < m_Packages.size() - 1 ) + { + m_iPackagesPos++; + UpdatePackagesList(); + } + } + else + { + if( (unsigned) m_iLinksPos < m_LinkTitles.size() - 1 ) + { + m_iLinksPos++; + UpdateLinksList(); + } + } + ScreenWithMenuElements::MenuDown( input ); + return true; +} + +bool ScreenPackages::MenuLeft( const InputEventPlus &input ) +{ + if ( m_bIsDownloading ) + return false; + if ( !m_bCanDL ) + return false; + + /* + m_sprExistingBG.StopTweening(); + m_sprWebBG.StopTweening(); + */ + + if ( m_iDLorLST == 0 ) + { + m_iDLorLST = 1; + COMMAND( m_sprExistingBG, "Away" ); + COMMAND( m_sprWebBG, "Back" ); + } + else + { + m_iDLorLST = 0; + COMMAND( m_sprExistingBG, "Back" ); + COMMAND( m_sprWebBG, "Away" ); + } + ScreenWithMenuElements::MenuLeft( input ); + return true; +} + +bool ScreenPackages::MenuRight( const InputEventPlus &input ) +{ + if ( m_bIsDownloading ) + return false; + + // Huh? + //MenuLeft( input ); + + if ( m_iDLorLST == 1 ) + { + m_iDLorLST = 0; + COMMAND( m_sprExistingBG, "Away" ); + COMMAND( m_sprWebBG, "Back" ); + } + else + { + m_iDLorLST = 1; + COMMAND( m_sprExistingBG, "Back" ); + COMMAND( m_sprWebBG, "Away" ); + } + + ScreenWithMenuElements::MenuRight( input ); + return true; +} + +static LocalizedString DOWNLOAD_CANCELLED( "ScreenPackages", "Download cancelled." ); +bool ScreenPackages::MenuBack( const InputEventPlus &input ) +{ + if ( m_bIsDownloading ) + { + SCREENMAN->SystemMessage( DOWNLOAD_CANCELLED ); + CancelDownload( ); + return true; + } + + TweenOffScreen(); + Cancel( SM_GoToPrevScreen ); + ScreenWithMenuElements::MenuBack( input ); + return true; +} + +void ScreenPackages::TweenOffScreen( ) +{ + OFF_COMMAND( m_sprExistingBG ); + OFF_COMMAND( m_sprWebBG ); + OFF_COMMAND( m_sprWebSel ); + OFF_COMMAND( m_textPackages ); + OFF_COMMAND( m_textWeb ); + OFF_COMMAND( m_sprDL ); + OFF_COMMAND( m_sprDLBG ); + OFF_COMMAND( m_textStatus ); + + m_fOutputFile.Close(); +} + +void ScreenPackages::RefreshPackages() +{ + GetDirListing( "Packages/*.zip", m_Packages, false, false ); + GetDirListing( "Packages/*.smzip", m_Packages, false, false ); + + if ( m_iPackagesPos < 0 ) + m_iPackagesPos = 0; + + if( (unsigned) m_iPackagesPos >= m_Packages.size() ) + m_iPackagesPos = m_Packages.size() - 1; + + UpdatePackagesList(); +} + +void ScreenPackages::UpdatePackagesList() +{ + RString TempText=""; + int min = m_iPackagesPos-NUM_PACKAGES_SHOW; + int max = m_iPackagesPos+NUM_PACKAGES_SHOW; + for (int i=min; i= 0 && (unsigned) i < m_Packages.size() ) + TempText += m_Packages[i] + '\n'; + else + TempText += '\n'; + } + m_textPackages.SetText( TempText ); +} + +void ScreenPackages::UpdateLinksList() +{ + RString TempText=""; + if( (unsigned) m_iLinksPos >= m_LinkTitles.size() ) + m_iLinksPos = m_LinkTitles.size() - 1; + int min = m_iLinksPos-NUM_LINKS_SHOW; + int max = m_iLinksPos+NUM_LINKS_SHOW; + for (int i=min; i= 0 && (unsigned) i < m_LinkTitles.size() ) + TempText += m_LinkTitles[i] + '\n'; + else + TempText += '\n'; + } + m_textWeb.SetText( TempText ); + if ( m_iLinksPos >= 0 && (unsigned) m_iLinksPos < m_Links.size() ) + m_textURL.SetText( m_Links[m_iLinksPos] ); + else + m_textURL.SetText( " " ); +} + +void ScreenPackages::HTMLParse() +{ + m_Links.clear(); + m_LinkTitles.clear(); + m_Links.push_back( " " ); + m_LinkTitles.push_back( VISIT_URL.GetValue() ); + + // XXX: VERY DIRTY HTML PARSER! + // Only designed to find links on websites. + size_t i = m_sBUFFER.find( "", i+1 ); + l = m_sBUFFER.find( "HREF", i+1); + m = m_sBUFFER.find( "=", l ); + + if( k == string::npos || l == string::npos || m == string::npos || l > k || m > k ) //no "href" in this tag. + { + if ( mode == 0 ) + i = m_sBUFFER.find( "", k+1 ); + if ( j < l ) + k = j; + + RString TempLink = StripOutContainers( m_sBUFFER.substr(m+1,k-m-1) ); + // xxx: handle https? -aj + if ( TempLink.substr(0,7).compare("http://") != 0 ) + TempLink = m_sBaseAddress + TempLink; + + RString TempTitle = m_sBUFFER.substr( k+1, l-k-1 ); + + m_Links.push_back( TempLink ); + m_LinkTitles.push_back( TempTitle ); + + if ( mode == 0 ) + i = m_sBUFFER.find( "Remove( "Packages/" + m_sEndName ) ) + SCREENMAN->SystemMessage( "Packages/" + m_sEndName ); +} + +static LocalizedString INVALID_URL( "ScreenPackages", "Invalid URL." ); +static LocalizedString FILE_ALREADY_EXISTS( "ScreenPackages", "File Already Exists" ); +static LocalizedString FAILED_TO_CONNECT( "ScreenPackages", "Failed to connect." ); +static LocalizedString HEADER_SENT( "ScreenPackages", "Header Sent." ); +void ScreenPackages::EnterURL( const RString & sURL ) +{ + RString Proto; + RString Server; + int Port=80; + RString sAddress; + + if( !ParseHTTPAddress( sURL, Proto, Server, Port, sAddress ) ) + { + m_sStatus = INVALID_URL.GetValue(); + UpdateProgress(); + return; + } + + // Determine if this is a website, or a package? + // Criteria: does it end with *zip? + if( sAddress.Right(3).CompareNoCase("zip") == 0 ) + m_bIsPackage=true; + else + m_bIsPackage = false; + + m_sBaseAddress = "http://" + Server; + if( Port != 80 ) + m_sBaseAddress += ssprintf( ":%d", Port ); + m_sBaseAddress += "/"; + + if( sAddress.Right(1) != "/" ) + { + m_sEndName = Basename( sAddress ); + m_sBaseAddress += Dirname( sAddress ); + } + else + { + m_sEndName = ""; + } + + // Open the file... + + // First find out if a file by this name already exists + // if so, then we gotta ditch out. + // XXX: This should be fixed by a prompt or something? + + // if we are not talking about a file, let's not worry + if( m_sEndName != "" && m_bIsPackage ) + { + vector AddTo; + GetDirListing( "Packages/"+m_sEndName, AddTo, false, false ); + if ( AddTo.size() > 0 ) + { + m_sStatus = FILE_ALREADY_EXISTS.GetValue(); + UpdateProgress(); + return; + } + + if( !m_fOutputFile.Open( "Packages/"+m_sEndName, RageFile::WRITE | RageFile::STREAMED ) ) + { + m_sStatus = m_fOutputFile.GetError(); + UpdateProgress(); + return; + } + } + // Continue... + + sAddress = URLEncode( StripOutContainers(sAddress) ); + + if ( sAddress != "/" ) + sAddress = "/" + sAddress; + + m_wSocket.close(); + m_wSocket.create(); + + m_wSocket.blocking = true; + + if( !m_wSocket.connect( Server, (short) Port ) ) + { + m_sStatus = FAILED_TO_CONNECT.GetValue(); + UpdateProgress(); + return; + } + + //Produce HTTP header + + RString Header=""; + + Header = "GET "+sAddress+" HTTP/1.0\r\n"; + Header+= "Host: " + Server + "\r\n"; + Header+= "Connection: closed\r\n\r\n"; + + m_wSocket.SendData( Header.c_str(), Header.length() ); + m_sStatus = HEADER_SENT.GetValue(); + m_wSocket.blocking = false; + m_bIsDownloading = true; + m_sBUFFER = ""; + m_bGotHeader = false; + UpdateProgress(); + return; +} + +static size_t FindEndOfHeaders( const RString &buf ) +{ + size_t iPos1 = buf.find( "\n\n" ); + size_t iPos2 = buf.find( "\r\n\r\n" ); + LOG->Trace("end: %u, %u", unsigned(iPos1), unsigned(iPos2)); + if( iPos1 != string::npos && (iPos2 == string::npos || iPos2 > iPos1) ) + return iPos1 + 2; + else if( iPos2 != string::npos && (iPos1 == string::npos || iPos1 > iPos2) ) + return iPos2 + 4; + else + return string::npos; +} + +static LocalizedString WAITING_FOR_HEADER( "ScreenPackages", "Waiting for header." ); +void ScreenPackages::HTTPUpdate() +{ + if( !m_bIsDownloading ) + return; + + int BytesGot=0; + // Keep this as a code block + // as there may be need to "if" it out some time. + /* If you need a conditional for a large block of code, stick it in + * a function and return. */ + while(1) + { + char Buffer[1024]; + int iSize = m_wSocket.ReadData( Buffer, 1024 ); + if( iSize <= 0 ) + break; + + m_sBUFFER.append( Buffer, iSize ); + BytesGot += iSize; + } + + if( !m_bGotHeader ) + { + m_sStatus = WAITING_FOR_HEADER.GetValue(); + // We don't know if we are using unix-style or dos-style + size_t iHeaderEnd = FindEndOfHeaders( m_sBUFFER ); + if( iHeaderEnd == m_sBUFFER.npos ) + return; + + // "HTTP/1.1 200 OK" + size_t i = m_sBUFFER.find(" "); + size_t j = m_sBUFFER.find(" ",i+1); + size_t k = m_sBUFFER.find("\n",j+1); + if ( i == string::npos || j == string::npos || k == string::npos ) + { + m_iResponseCode = -100; + m_sResponseName = "Malformed response."; + return; + } + m_iResponseCode = std::stoi(m_sBUFFER.substr(i+1,j-i)); + m_sResponseName = m_sBUFFER.substr( j+1, k-j ); + + i = m_sBUFFER.find("Content-Length:"); + j = m_sBUFFER.find("\n", i+1 ); + + if( i != string::npos ) + m_iTotalBytes = std::stoi(m_sBUFFER.substr(i+16,j-i)); + else + m_iTotalBytes = -1; //We don't know, so go until disconnect + + m_bGotHeader = true; + m_sBUFFER.erase( 0, iHeaderEnd ); + } + + if( m_bIsPackage ) + { + m_iDownloaded += m_sBUFFER.length(); + m_fOutputFile.Write( m_sBUFFER ); + m_sBUFFER = ""; + } + else + { + m_iDownloaded = m_sBUFFER.length(); + } + + if ( ( m_iTotalBytes <= m_iDownloaded && m_iTotalBytes != -1 ) || + //We have the full doc. (And we knew how big it was) + ( m_iTotalBytes == -1 && + ( m_wSocket.state == EzSockets::skERROR || m_wSocket.state == EzSockets::skDISCONNECTED ) ) ) + //We didn't know how big it was, and were disconnected + //So that means we have it all. + { + m_wSocket.close(); + m_bIsDownloading = false; + m_bGotHeader=false; + m_sStatus = ssprintf( "Done ;%dB", int(m_iDownloaded) ); + + if( m_iResponseCode < 200 || m_iResponseCode >= 400 ) + { + m_sStatus = ssprintf( "%ld", m_iResponseCode ) + m_sResponseName; + } + else + { + if( m_bIsPackage && m_iResponseCode < 300 ) + { + m_fOutputFile.Close(); + RefreshPackages(); + m_iDownloaded = 0; + } + else + HTMLParse(); + } + } +} + +bool ScreenPackages::ParseHTTPAddress( const RString &URL, RString &sProto, RString &sServer, int &iPort, RString &sAddress ) +{ + // [PROTO://]SERVER[:PORT][/URL] + + Regex re( + "^([A-Z]+)://" // [0]: HTTP:// + "([^/:]+)" // [1]: a.b.com + "(:([0-9]+))?" // [2], [3]: :1234 (optional, default 80) + "(/(.*))?$"); // [4], [5]: /foo.html (optional) + vector asMatches; + if( !re.Compare( URL, asMatches ) ) + return false; + ASSERT( asMatches.size() == 6 ); + + sProto = asMatches[0]; + sServer = asMatches[1]; + if( asMatches[3] != "" ) + { + iPort = std::stoi(asMatches[3]); + if( iPort == 0 ) + return false; + } + else + iPort = 80; + + sAddress = asMatches[5]; + + return true; +} + +#endif +/* + * (c) 2004 Charles Lohr + * 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. + */ diff --git a/src/SongOptions.cpp b/src/SongOptions.cpp index 556bc33a83..f9ced59c0c 100644 --- a/src/SongOptions.cpp +++ b/src/SongOptions.cpp @@ -176,7 +176,7 @@ bool SongOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut ) Regex lives("^([0-9]+) ?(lives|life)$"); if( lives.Compare(sBit, matches) ) { - m_iBatteryLives = StringToInt( matches[0] ); + m_iBatteryLives = std::stoi( matches[0] ); return true; } diff --git a/src/XmlFile.cpp b/src/XmlFile.cpp index 2e3eb09e96..3ed893015f 100644 --- a/src/XmlFile.cpp +++ b/src/XmlFile.cpp @@ -50,9 +50,9 @@ void XNode::Free() } void XNodeStringValue::GetValue( RString &out ) const { out = m_sValue; } -void XNodeStringValue::GetValue( int &out ) const { out = StringToInt(m_sValue); } +void XNodeStringValue::GetValue( int &out ) const { out = std::stoi(m_sValue); } void XNodeStringValue::GetValue( float &out ) const { out = StringToFloat(m_sValue); } -void XNodeStringValue::GetValue( bool &out ) const { out = StringToInt(m_sValue) != 0; } +void XNodeStringValue::GetValue( bool &out ) const { out = std::stoi(m_sValue) != 0; } void XNodeStringValue::GetValue( unsigned &out ) const { out = strtoul(m_sValue,NULL,0); } void XNodeStringValue::PushValue( lua_State *L ) const {