void MsdTest() { g_TestFilename = "file"; /* Read check. */ do { g_TestFile = "#FOO;"; g_BytesUntilError = -1; MsdFile test; if( !test.ReadFile("test/file", false) ) Fail( "MSD: ReadFile failed: %s", test.GetError().c_str() ); if( test.GetNumValues() != 1 ) Fail( "MSD: GetNumValues: expected 1, got %i", test.GetNumValues() ); if( test.GetNumParams(0) != 1 ) Fail( "MSD: GetNumParams(0): expected 1, got %i", test.GetNumParams(0) ); RString sStr = test.GetValue(0)[0]; if( sStr != "FOO" ) Fail( "MSD: GetValue failed: expected \"FOO\", got \"%s\"", sStr.c_str() ); } while(false); /* Read error check. */ do { g_TestFile = "#FOO:BAR:BAZ;"; g_BytesUntilError = 5; MsdFile test; if( test.ReadFile("test/file", false) ) Fail( "MSD: ReadFile should have failed" ); if( test.GetError() != "Fake error" ) Fail( "MSD: ReadFile error check: wrong error return: got \"%s\"", test.GetError().c_str() ); } while(false); }
bool SMLoader::LoadEdit( CString sEditFilePath, ProfileSlot slot ) { LOG->Trace( "Song::LoadEdit(%s)", sEditFilePath.c_str() ); int iBytes = FILEMAN->GetFileSizeInBytes( sEditFilePath ); if( iBytes > MAX_EDIT_SIZE_BYTES ) { LOG->Warn( "The edit '%s' is unreasonably large. It won't be loaded.", sEditFilePath.c_str() ); return false; } MsdFile msd; if( !msd.ReadFile( sEditFilePath ) ) RageException::Throw( "Error opening file \"%s\": %s", sEditFilePath.c_str(), msd.GetError().c_str() ); Song* pSong = NULL; for( unsigned i=0; i<msd.GetNumValues(); i++ ) { int iNumParams = msd.GetNumParams(i); const MsdFile::value_t &sParams = msd.GetValue(i); const CString sValueName = sParams[0]; // handle the data if( 0==stricmp(sValueName,"SONG") ) { if( pSong ) { LOG->Warn( "The edit file '%s' has more than one #SONG tag.", sEditFilePath.c_str() ); return false; } CString sSongFullTitle = sParams[1]; sSongFullTitle.Replace( '\\', '/' ); pSong = SONGMAN->FindSong( sSongFullTitle ); if( pSong == NULL ) { LOG->Warn( "The edit file '%s' required a song '%s' that isn't present.", sEditFilePath.c_str(), sSongFullTitle.c_str() ); return false; } if( pSong->GetNumStepsLoadedFromProfile(slot) >= MAX_EDITS_PER_SONG_PER_PROFILE ) { LOG->Warn( "The song '%s' already has the maximum number of edits allowed for ProfileSlotP%d.", sSongFullTitle.c_str(), slot+1 ); return false; } } else if( 0==stricmp(sValueName,"NOTES") ) { if( pSong == NULL ) { LOG->Warn( "The edit file '%s' has doesn't have a #SONG tag preceeding the first #NOTES tag.", sEditFilePath.c_str() ); return false; } if( iNumParams < 7 ) { LOG->Trace( "The song file '%s' is has %d fields in a #NOTES tag, but should have at least %d.", sEditFilePath.c_str(), iNumParams, 7 ); continue; } Steps* pNewNotes = new Steps; ASSERT( pNewNotes ); LoadFromSMTokens( sParams[1], sParams[2], sParams[3], sParams[4], sParams[5], sParams[6], (iNumParams>=8)?sParams[7]:CString(""), *pNewNotes); pNewNotes->SetLoadedFromProfile( slot ); pNewNotes->SetDifficulty( DIFFICULTY_EDIT ); if( pSong->IsEditAlreadyLoaded(pNewNotes) ) { LOG->Warn( "The edit file '%s' is a duplicate of another edit that was already loaded.", sEditFilePath.c_str() ); SAFE_DELETE( pNewNotes ); return false; } pSong->AddSteps( pNewNotes ); return true; // Only allow one Steps per edit file! } else LOG->Trace( "Unexpected value named '%s'", sValueName.c_str() ); } return true; }
bool UnlockSystem::Load() { LOG->Trace( "UnlockSystem::Load()" ); if( !IsAFile(UNLOCKS_PATH) ) return false; MsdFile msd; if( !msd.ReadFile( UNLOCKS_PATH ) ) { LOG->Warn( "Error opening file '%s' for reading: %s.", UNLOCKS_PATH, msd.GetError().c_str() ); return false; } unsigned i; for( i=0; i<msd.GetNumValues(); i++ ) { int iNumParams = msd.GetNumParams(i); const MsdFile::value_t &sParams = msd.GetValue(i); CString sValueName = sParams[0]; if( iNumParams < 1 ) { LOG->Warn("Got \"%s\" tag with no parameters", sValueName.c_str()); continue; } if( !stricmp(sParams[0],"ROULETTE") ) { for( unsigned j = 1; j < sParams.params.size(); ++j ) m_RouletteCodes.insert( atoi(sParams[j]) ); continue; } if( stricmp(sParams[0],"UNLOCK") ) { LOG->Warn("Unrecognized unlock tag \"%s\", ignored.", sValueName.c_str()); continue; } UnlockEntry current; current.m_sSongName = sParams[1]; LOG->Trace("Song entry: %s", current.m_sSongName.c_str() ); CStringArray UnlockTypes; split( sParams[2], ",", UnlockTypes ); for( unsigned j=0; j<UnlockTypes.size(); ++j ) { CStringArray readparam; split( UnlockTypes[j], "=", readparam ); const CString &unlock_type = readparam[0]; LOG->Trace("UnlockTypes line: %s", UnlockTypes[j].c_str() ); const float fVal = strtof( readparam[1], NULL ); const int iVal = atoi( readparam[1] ); const UnlockType ut = StringToUnlockType( unlock_type ); if( ut != UNLOCK_INVALID ) current.m_fRequired[ut] = fVal; if( unlock_type == "CODE" ) current.m_iCode = iVal; if( unlock_type == "RO" ) { current.m_iCode = iVal; m_RouletteCodes.insert( iVal ); } } m_SongEntries.push_back(current); } UpdateSongs(); for(i=0; i < m_SongEntries.size(); i++) { CString str = ssprintf( "Unlock: %s; ", m_SongEntries[i].m_sSongName.c_str() ); for( int j = 0; j < NUM_UNLOCK_TYPES; ++j ) if( m_SongEntries[i].m_fRequired[j] ) str += ssprintf( "%s = %f; ", g_UnlockNames[j], m_SongEntries[i].m_fRequired[j] ); str += ssprintf( "code = %i ", m_SongEntries[i].m_iCode ); str += m_SongEntries[i].IsLocked()? "locked":"unlocked"; if( m_SongEntries[i].m_pSong ) str += ( " (found song)" ); if( m_SongEntries[i].m_pCourse ) str += ( " (found course)" ); LOG->Trace( "%s", str.c_str() ); } return true; }
bool SMLoader::LoadFromSMFile( CString sPath, Song &out ) { LOG->Trace( "Song::LoadFromSMFile(%s)", sPath.c_str() ); MsdFile msd; if( !msd.ReadFile( sPath ) ) RageException::Throw( "Error opening file \"%s\": %s", sPath.c_str(), msd.GetError().c_str() ); out.m_Timing.m_sFile = sPath; LoadTimingFromSMFile( msd, out.m_Timing ); for( unsigned i=0; i<msd.GetNumValues(); i++ ) { int iNumParams = msd.GetNumParams(i); const MsdFile::value_t &sParams = msd.GetValue(i); const CString sValueName = sParams[0]; // handle the data /* Don't use GetMainAndSubTitlesFromFullTitle; that's only for heuristically * splitting other formats that *don't* natively support #SUBTITLE. */ if( 0==stricmp(sValueName,"TITLE") ) out.m_sMainTitle = sParams[1]; else if( 0==stricmp(sValueName,"SUBTITLE") ) out.m_sSubTitle = sParams[1]; else if( 0==stricmp(sValueName,"ARTIST") ) out.m_sArtist = sParams[1]; else if( 0==stricmp(sValueName,"TITLETRANSLIT") ) out.m_sMainTitleTranslit = sParams[1]; else if( 0==stricmp(sValueName,"SUBTITLETRANSLIT") ) out.m_sSubTitleTranslit = sParams[1]; else if( 0==stricmp(sValueName,"ARTISTTRANSLIT") ) out.m_sArtistTranslit = sParams[1]; else if( 0==stricmp(sValueName,"CREDIT") ) out.m_sCredit = sParams[1]; else if( 0==stricmp(sValueName,"BANNER") ) out.m_sBannerFile = sParams[1]; else if( 0==stricmp(sValueName,"BACKGROUND") ) out.m_sBackgroundFile = sParams[1]; /* Save "#LYRICS" for later, so we can add an internal lyrics tag. */ else if( 0==stricmp(sValueName,"LYRICSPATH") ) out.m_sLyricsFile = sParams[1]; else if( 0==stricmp(sValueName,"CDTITLE") ) out.m_sCDTitleFile = sParams[1]; else if( 0==stricmp(sValueName,"MUSIC") ) out.m_sMusicFile = sParams[1]; else if( 0==stricmp(sValueName,"MUSICLENGTH") ) { if(!FromCache) continue; out.m_fMusicLengthSeconds = strtof( sParams[1], NULL ); } else if( 0==stricmp(sValueName,"MUSICBYTES") ) ; /* ignore */ /* We calculate these. Some SMs in circulation have bogus values for * these, so make sure we always calculate it ourself. */ else if( 0==stricmp(sValueName,"FIRSTBEAT") ) { if(!FromCache) continue; out.m_fFirstBeat = strtof( sParams[1], NULL ); } else if( 0==stricmp(sValueName,"LASTBEAT") ) { if(!FromCache) LOG->Trace("Ignored #LASTBEAT (cache only)"); out.m_fLastBeat = strtof( sParams[1], NULL ); } else if( 0==stricmp(sValueName,"SONGFILENAME") ) { if( FromCache ) out.m_sSongFileName = sParams[1]; } else if( 0==stricmp(sValueName,"HASMUSIC") ) { if( FromCache ) out.m_bHasMusic = atoi( sParams[1] ) != 0; } else if( 0==stricmp(sValueName,"HASBANNER") ) { if( FromCache ) out.m_bHasBanner = atoi( sParams[1] ) != 0; } else if( 0==stricmp(sValueName,"SAMPLESTART") ) out.m_fMusicSampleStartSeconds = HHMMSSToSeconds( sParams[1] ); else if( 0==stricmp(sValueName,"SAMPLELENGTH") ) out.m_fMusicSampleLengthSeconds = HHMMSSToSeconds( sParams[1] ); else if( 0==stricmp(sValueName,"DISPLAYBPM") ) { // #DISPLAYBPM:[xxx][xxx:xxx]|[*]; if( sParams[1] == "*" ) out.m_DisplayBPMType = Song::DISPLAY_RANDOM; else { out.m_DisplayBPMType = Song::DISPLAY_SPECIFIED; out.m_fSpecifiedBPMMin = strtof( sParams[1], NULL ); if( sParams[2].empty() ) out.m_fSpecifiedBPMMax = out.m_fSpecifiedBPMMin; else out.m_fSpecifiedBPMMax = strtof( sParams[2], NULL ); } } else if( 0==stricmp(sValueName,"SELECTABLE") ) { if(!stricmp(sParams[1],"YES")) out.m_SelectionDisplay = out.SHOW_ALWAYS; else if(!stricmp(sParams[1],"NO")) out.m_SelectionDisplay = out.SHOW_NEVER; else if(!stricmp(sParams[1],"ROULETTE")) out.m_SelectionDisplay = out.SHOW_ROULETTE; else LOG->Warn( "The song file '%s' has an unknown #SELECTABLE value, '%s'; ignored.", sPath.c_str(), sParams[1].c_str()); } else if( 0==stricmp(sValueName,"BGCHANGES") || 0==stricmp(sValueName,"ANIMATIONS") ) { CStringArray aBGChangeExpressions; split( sParams[1], ",", aBGChangeExpressions ); for( unsigned b=0; b<aBGChangeExpressions.size(); b++ ) { BackgroundChange change; if( LoadFromBGChangesString( change, aBGChangeExpressions[b] ) ) out.AddBackgroundChange( change ); } } else if( 0==stricmp(sValueName,"FGCHANGES") ) { CStringArray aFGChangeExpressions; split( sParams[1], ",", aFGChangeExpressions ); for( unsigned b=0; b<aFGChangeExpressions.size(); b++ ) { BackgroundChange change; if( LoadFromBGChangesString( change, aFGChangeExpressions[b] ) ) out.AddForegroundChange( change ); } } else if( 0==stricmp(sValueName,"NOTES") ) { if( iNumParams < 7 ) { LOG->Trace( "The song file '%s' is has %d fields in a #NOTES tag, but should have at least %d.", sPath.c_str(), iNumParams, 7 ); continue; } Steps* pNewNotes = new Steps; ASSERT( pNewNotes ); LoadFromSMTokens( sParams[1], sParams[2], sParams[3], sParams[4], sParams[5], sParams[6], (iNumParams>=8)?sParams[7]:CString(""), *pNewNotes); out.AddSteps( pNewNotes ); } else if( 0==stricmp(sValueName,"OFFSET") || 0==stricmp(sValueName,"BPMS") || 0==stricmp(sValueName,"STOPS") || 0==stricmp(sValueName,"FREEZES") ) ; else LOG->Trace( "Unexpected value named '%s'", sValueName.c_str() ); } return true; }