示例#1
0
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);
}
示例#2
0
static bool LoadGlobalData( const std::string &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<std::string> arrayPossibleMusic;
	GetDirListing( out.GetSongDir() + std::string("song.mp3"), arrayPossibleMusic );
	GetDirListing( out.GetSongDir() + std::string("song.oga"), arrayPossibleMusic );
	GetDirListing( out.GetSongDir() + std::string("song.ogg"), arrayPossibleMusic );
	GetDirListing( out.GetSongDir() + std::string("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<std::string> vNoteRows;

	for( unsigned i=0; i < msd.GetNumValues(); i++ )
	{
		const MsdFile::value_t &sParams = msd.GetValue(i);
		std::string sValueName = Rage::make_upper(sParams[0]);

		// 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.set_offset(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. */
			std::string theSteps = Rage::trim_left(sParams[1]);
			auto toDump = Rage::split(theSteps, "\n", Rage::EmptyEntries::skip);
			vNoteRows.insert(vNoteRows.end(), std::make_move_iterator(toDump.begin()), std::make_move_iterator(toDump.end()));
		}
		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 (auto &NoteRowString: vNoteRows)
		{
			NoteRowString = Rage::trim_right(NoteRowString, "\r\n");

			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 ( Rage::starts_with( 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.
	{
		auto asBits = Rage::split(sPath, "/", Rage::EmptyEntries::skip);

		ASSERT( asBits.size() > 1 );
		LoadTags( asBits[asBits.size()-2], out );
	}

	return true;
}
示例#3
0
static bool LoadFromKSFFile( const std::string &sPath, Steps &out, Song &song, bool bKIUCompliant )
{
	using std::max;
	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<std::string> 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; i<msd.GetNumValues(); i++ )
	{
		const MsdFile::value_t &sParams = msd.GetValue( i );
		std::string sValueName = Rage::make_upper(sParams[0]);

		/* handle the data...well, not this data: not related to steps.
		 * Skips INTRO, MUSICINTRO, TITLEFILE, DISCFILE, SONGFILE. */
		if (sValueName=="TITLE" || Rage::ends_with(sValueName, "INTRO")
			|| Rage::ends_with(sValueName, "FILE") )
		{

		}
		else if( sValueName=="BPM" )
		{
			BPM1 = StringToFloat(sParams[1]);
			stepsTiming.AddSegment( BPMSegment(0, BPM1) );
		}
		else if( sValueName=="BPM2" )
		{
			if (bKIUCompliant)
			{
				BPM2 = StringToFloat( sParams[1] );
			}
			else
			{
				// LOG an error.
			}
		}
		else if( sValueName=="BPM3" )
		{
			if (bKIUCompliant)
			{
				BPM3 = StringToFloat( sParams[1] );
			}
			else
			{
				// LOG an error.
			}
		}
		else if( sValueName=="BUNKI" )
		{
			if (bKIUCompliant)
			{
				BPMPos2 = StringToFloat( sParams[1] ) / 100.0f;
			}
			else
			{
				// LOG an error.
			}
		}
		else if( sValueName=="BUNKI2" )
		{
			if (bKIUCompliant)
			{
				BPMPos3 = StringToFloat( sParams[1] ) / 100.0f;
			}
			else
			{
				// LOG an error.
			}
		}
		else if( sValueName=="STARTTIME" )
		{
			SMGap1 = -StringToFloat( sParams[1] )/100;
			stepsTiming.set_offset(SMGap1);
		}
		// This is currently required for more accurate KIU BPM changes.
		else if( sValueName=="STARTTIME2" )
		{
			if (bKIUCompliant)
			{
				SMGap2 = -StringToFloat( sParams[1] )/100;
			}
			else
			{
				// LOG an error.
			}
		}
		else if ( sValueName=="STARTTIME3" )
		{
			// STARTTIME3 only ensures this is a KIU compliant simfile.
			bKIUCompliant = true;
		}

		else if( sValueName=="TICKCOUNT" )
		{
			iTickCount = StringToInt( sParams[1] );
			if( iTickCount <= 0 )
			{
				LOG->UserLog( "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" )
		{
			std::string sPlayer = Rage::make_lower(sParams[1]);
			if( sPlayer.find( "double" ) != string::npos )
			{
				bDoublesChart = true;
			}
		}
		// This should always be last.
		else if( sValueName=="STEP" )
		{
			std::string theSteps = Rage::trim_left(sParams[1]);
			auto toDump = Rage::split(theSteps, "\n", Rage::EmptyEntries::skip);
			vNoteRows.insert(vNoteRows.end(), std::make_move_iterator(toDump.begin()), std::make_move_iterator(toDump.end()));
		}
	}

	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

	{
		std::string sDir, sFName, sExt;
		splitpath( sPath, sDir, sFName, sExt );
		sFName = Rage::make_lower(sFName);

		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( fmt::sprintf("%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 (auto &sRowString: vNoteRows)
	{
		sRowString = Rage::trim_right(sRowString, "\r\n");

		if( sRowString == "" )
		{
			continue;	// skip
		}
		// All 2s indicates the end of the song.
		if( sRowString == "2222222222222" )
		{
			// Finish any holds that didn't get...well, finished.
			for( t=0; t < notedata.GetNumTracks(); t++ )
			{
				if( iHoldStartRow[t] != -1 )	// this ends the hold
				{
					if( iHoldStartRow[t] == BeatToNoteRow(prevBeat) )
					{
						notedata.SetTapNote( t, iHoldStartRow[t], TAP_ORIGINAL_TAP );
					}
					else
					{
						notedata.AddHoldNote(t,
								     iHoldStartRow[t],
								     BeatToNoteRow(prevBeat),
								     TAP_ORIGINAL_HOLD_HEAD );
					}
				}
			}
			/* have this row be the last moment in the song, unless
			 * a future step ends later. */
			//float curTime = stepsTiming.GetElapsedTimeFromBeat(fCurBeat);
			//if (curTime > song.GetSpecifiedLastSecond())
			//{
			//	song.SetSpecifiedLastSecond(curTime);
			//}

			song.SetSpecifiedLastSecond( song.GetSpecifiedLastSecond() + 4 );

			break;
		}

		else if( Rage::starts_with(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;

			std::string temp = sRowString.substr(2,sRowString.size()-3);
			float numTemp = StringToFloat(temp);
			if (Rage::starts_with(sRowString, "|T"))
			{
				// duh
				iTickCount = static_cast<int>(numTemp);
				// I have been owned by the man -DaisuMaster
				stepsTiming.SetTickcountAtBeat( fCurBeat, Rage::clamp(iTickCount, 0, ROWS_PER_BEAT) );
			}
			else if (Rage::starts_with(sRowString, "|B"))
			{
				// BPM
				stepsTiming.SetBPMAtBeat( fCurBeat, numTemp );
			}
			else if (Rage::starts_with(sRowString, "|E"))
			{
				// DelayBeat
				float fCurDelay = 60 / stepsTiming.GetBPMAtBeat(fCurBeat) * numTemp / iTickCount;
				fCurDelay += stepsTiming.GetDelayAtRow(BeatToNoteRow(fCurBeat) );
				stepsTiming.SetDelayAtBeat( fCurBeat, fCurDelay );
			}
			else if (Rage::starts_with(sRowString, "|D"))
			{
				// Delays
				float fCurDelay = stepsTiming.GetStopAtRow(BeatToNoteRow(fCurBeat) );
				fCurDelay += numTemp / 1000;
				stepsTiming.SetDelayAtBeat( fCurBeat, fCurDelay );
			}
			else if (Rage::starts_with(sRowString, "|M") || Rage::starts_with(sRowString, "|C"))
			{
				// multipliers/combo
				ComboSegment seg( BeatToNoteRow(fCurBeat), int(numTemp) );
				stepsTiming.AddSegment( seg );
			}
			else if (Rage::starts_with(sRowString, "|S"))
			{
				// speed segments
			}
			else if (Rage::starts_with(sRowString, "|F"))
			{
				// fakes
			}
			else if (Rage::starts_with(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;
}
示例#4
0
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;
}
示例#5
0
bool KSFLoader::LoadFromKSFFile( const CString &sPath, Steps &out, const Song &song )
{
    LOG->Trace( "Steps::LoadFromKSFFile( '%s' )", sPath.c_str() );

    MsdFile msd;
    if( !msd.ReadFile( sPath ) )
        RageException::Throw( "Error opening file '%s'.", sPath.c_str() );

    int iTickCount = -1;	// this is the value we read for TICKCOUNT
    CStringArray asRows;

    for( unsigned i=0; i<msd.GetNumValues(); i++ )
    {
        const MsdFile::value_t &sParams = msd.GetValue(i);
        CString sValueName = sParams[0];

        // handle the data
        if( 0==stricmp(sValueName,"TICKCOUNT") )
            iTickCount = atoi(sParams[1]);

        else if( 0==stricmp(sValueName,"STEP") )
        {
            CString step = sParams[1];
            TrimLeft(step);
            split( step, "\n", asRows, true );
        }
        else if( 0==stricmp(sValueName,"DIFFICULTY") )
            out.SetMeter(atoi(sParams[1]));
    }

    if( iTickCount == -1 )
    {
        iTickCount = 2;
        LOG->Warn( "\"%s\": TICKCOUNT not found; defaulting to %i", sPath.c_str(), iTickCount );
    }

    NoteData notedata;	// read it into here

    {
        CString sDir, sFName, sExt;
        splitpath( sPath, sDir, sFName, sExt );
        sFName.MakeLower();

        out.SetDescription(sFName);
        if( sFName.Find("crazy")!=-1 )
        {
            out.SetDifficulty(DIFFICULTY_HARD);
            if(!out.GetMeter()) out.SetMeter(8);
        }
        else if( sFName.Find("hard")!=-1 )
        {
            out.SetDifficulty(DIFFICULTY_MEDIUM);
            if(!out.GetMeter()) out.SetMeter(5);
        }
        else if( sFName.Find("easy")!=-1 )
        {
            out.SetDifficulty(DIFFICULTY_EASY);
            if(!out.GetMeter()) out.SetMeter(2);
        }
        else
        {
            out.SetDifficulty(DIFFICULTY_MEDIUM);
            if(!out.GetMeter()) out.SetMeter(5);
        }

        notedata.SetNumTracks( 5 );
        out.m_StepsType = STEPS_TYPE_PUMP_SINGLE;

        /* Check for "halfdouble" before "double". */
        if( sFName.Find("halfdouble") != -1 || sFName.Find("h_double") != -1 )
        {
            notedata.SetNumTracks( 6 );
            out.m_StepsType = STEPS_TYPE_PUMP_HALFDOUBLE;
        }
        else if( sFName.Find("double") != -1 )
        {
            notedata.SetNumTracks( 10 );
            out.m_StepsType = STEPS_TYPE_PUMP_DOUBLE;
        }
        else if( sFName.Find("_2") != -1 )
        {
            notedata.SetNumTracks( 10 );
            out.m_StepsType = STEPS_TYPE_PUMP_COUPLE;
        }
    }

    int iHoldStartRow[13];
    int t;
    for( t=0; t<13; t++ )
        iHoldStartRow[t] = -1;

    for( unsigned r=0; r<asRows.size(); r++ )
    {
        CString& sRowString = asRows[r];
        StripCrnl( sRowString );

        if( sRowString == "" )
            continue;	// skip

        /* All 2s indicates the end of the song. */
        if( sRowString == "2222222222222" )
            break;

        if(sRowString.size() != 13)
        {
            LOG->Warn("File %s had a RowString with an improper length (\"%s\"); corrupt notes ignored",
                      sPath.c_str(), sRowString.c_str());
            return false;
        }

        /* Half-doubles is offset; "0011111100000". */
        if( out.m_StepsType == STEPS_TYPE_PUMP_HALFDOUBLE )
            sRowString.erase( 0, 2 );

        // the length of a note in a row depends on TICKCOUNT
        float fBeatThisRow = r/(float)iTickCount;
        int row = BeatToNoteRow(fBeatThisRow);
        for( int t=0; t < notedata.GetNumTracks(); t++ )
        {
            if( sRowString[t] == '4' )
            {
                /* Remember when each hold starts; ignore the middle. */
                if( iHoldStartRow[t] == -1 )
                    iHoldStartRow[t] = r;

                continue;
            }

            if( iHoldStartRow[t] != -1 )	// this ends the hold
            {
                HoldNote hn (
                    t, /* button */
                    BeatToNoteRow(iHoldStartRow[t]/(float)iTickCount), /* start */
                    BeatToNoteRow((r-1)/(float)iTickCount) /* end */
                );
                notedata.AddHoldNote( hn );
                iHoldStartRow[t] = -1;
            }

            TapNote tap;
            switch(sRowString[t])
            {
            case '0':
                tap = TAP_EMPTY;
                break;
            case '1':
                tap = TAP_ORIGINAL_TAP;
                break;
            default:
                ASSERT(0);
                tap = TAP_EMPTY;
                break;
            }

            notedata.SetTapNote(t, row, tap);
        }
    }

    /* We need to remove holes where the BPM increases. */
//	if( song.m_Timing.m_BPMSegments.size() > 1 )
//		RemoveHoles( notedata, song );

    out.SetNoteData(&notedata);

    out.TidyUpData();

    return true;
}
示例#6
0
bool KSFLoader::LoadGlobalData( const CString &sPath, Song &out )
{
    MsdFile msd;
    if( !msd.ReadFile( sPath ) )
        RageException::Throw( "Error opening file \"%s\": %s", sPath.c_str(), msd.GetError().c_str() );

    float BPMPos2 = -1, BPM2 = -1, BPMPos3 = -1, BPM3 = -1;;

    for( unsigned i=0; i < msd.GetNumValues(); i++ )
    {
        const MsdFile::value_t &sParams = msd.GetValue(i);
        CString sValueName = sParams[0];

        // handle the data
        if( 0==stricmp(sValueName,"TITLE") )
            LoadTags(sParams[1], out);
        else if( 0==stricmp(sValueName,"BPM") )
            out.AddBPMSegment( BPMSegment(0, strtof(sParams[1], NULL)) );
        else if( 0==stricmp(sValueName,"BPM2") )
            BPM2 = strtof( sParams[1], NULL );
        else if( 0==stricmp(sValueName,"BPM3") )
            BPM3 = strtof( sParams[1], NULL );
        else if( 0==stricmp(sValueName,"BUNKI") )
            BPMPos2 = strtof( sParams[1], NULL ) / 100.0f;
        else if( 0==stricmp(sValueName,"BUNKI2") )
            BPMPos3 = strtof( sParams[1], NULL ) / 100.0f;
        else if( 0==stricmp(sValueName,"STARTTIME") )
            out.m_Timing.m_fBeat0OffsetInSeconds = -strtof( sParams[1], NULL )/100;
        else if( 0==stricmp(sValueName,"TICKCOUNT") ||
                 0==stricmp(sValueName,"STEP") ||
                 0==stricmp(sValueName,"DIFFICULTY"))
            ; /* Handled in LoadFromKSFFile; don't warn. */
        else
            LOG->Trace( "Unexpected value named '%s'", sValueName.c_str() );
    }

    /* This doesn't work yet: we also need to move the data around, I think, and
     * we should handle more than one BPM change. */
    if( BPM2 > 0 && BPMPos2 > 0 )
    {
        const float BeatsPerSecond = out.GetBPMAtBeat(0) / 60.0f;
        const float beat = BPMPos2 * BeatsPerSecond;
        LOG->Trace("BPM %f, BPS %f, BPMPos2 %f, beat %f",
                   out.GetBPMAtBeat(0), BeatsPerSecond, BPMPos2, beat);
        out.AddBPMSegment( BPMSegment(beat, BPM2) );
    }

    if( BPM3 > 0 && BPMPos3 > 0 )
    {
        const float BeatsPerSecond = out.GetBPMAtBeat(0) / 60.0f;
        const float beat = BPMPos3 * BeatsPerSecond;
        LOG->Trace("BPM %f, BPS %f, BPMPos3 %f, beat %f",
                   out.GetBPMAtBeat(0), BeatsPerSecond, BPMPos3, beat);
        out.AddBPMSegment( BPMSegment(beat, BPM3) );
    }

    /* Try to fill in missing bits of information from the pathname. */
    {
        CStringArray asBits;
        split( sPath, "/", asBits, true);

        ASSERT(asBits.size() > 1);
        LoadTags(asBits[asBits.size()-2], out);
    }

    // search for music with song in the file name
    CStringArray arrayPossibleMusic;
    GetDirListing( out.GetSongDir() + CString("song.mp3"), arrayPossibleMusic );
    GetDirListing( out.GetSongDir() + CString("song.ogg"), arrayPossibleMusic );
    GetDirListing( out.GetSongDir() + CString("song.wav"), arrayPossibleMusic );

    if( !arrayPossibleMusic.empty() )		// we found a match
        out.m_sMusicFile = arrayPossibleMusic[0];

    return true;
}
示例#7
0
void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out )
{
	out.m_fBeat0OffsetInSeconds = 0;
	out.m_BPMSegments.clear();
	out.m_StopSegments.clear();

	for( unsigned i=0; i<msd.GetNumValues(); i++ )
	{
		const MsdFile::value_t &sParams = msd.GetValue(i);
		const CString sValueName = sParams[0];

		if( 0==stricmp(sValueName,"OFFSET") )
			out.m_fBeat0OffsetInSeconds = strtof( sParams[1], NULL );
		else if( 0==stricmp(sValueName,"STOPS") || 0==stricmp(sValueName,"FREEZES") )
		{
			CStringArray arrayFreezeExpressions;
			split( sParams[1], ",", arrayFreezeExpressions );

			for( unsigned f=0; f<arrayFreezeExpressions.size(); f++ )
			{
				CStringArray arrayFreezeValues;
				split( arrayFreezeExpressions[f], "=", arrayFreezeValues );
				/* XXX: Once we have a way to display warnings that the user actually
				 * cares about (unlike most warnings), this should be one of them. */
				if( arrayFreezeValues.size() != 2 )
				{
					LOG->Warn( "Invalid #%s value \"%s\" (must have exactly one '='), ignored",
						sValueName.c_str(), arrayFreezeExpressions[f].c_str() );
					continue;
				}

				const float fFreezeBeat = strtof( arrayFreezeValues[0], NULL );
				const float fFreezeSeconds = strtof( arrayFreezeValues[1], NULL );
				
				StopSegment new_seg;
				new_seg.m_fStartBeat = fFreezeBeat;
				new_seg.m_fStopSeconds = fFreezeSeconds;

//				LOG->Trace( "Adding a freeze segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds );

				out.AddStopSegment( new_seg );
			}
		}

		else if( 0==stricmp(sValueName,"BPMS") )
		{
			CStringArray arrayBPMChangeExpressions;
			split( sParams[1], ",", arrayBPMChangeExpressions );

			for( unsigned b=0; b<arrayBPMChangeExpressions.size(); b++ )
			{
				CStringArray arrayBPMChangeValues;
				split( arrayBPMChangeExpressions[b], "=", arrayBPMChangeValues );
				/* XXX: Once we have a way to display warnings that the user actually
				 * cares about (unlike most warnings), this should be one of them. */
				if(arrayBPMChangeValues.size() != 2)
				{
					LOG->Warn( "Invalid #%s value \"%s\" (must have exactly one '='), ignored",
						sValueName.c_str(), arrayBPMChangeExpressions[b].c_str() );
					continue;
				}

				const float fBeat = strtof( arrayBPMChangeValues[0], NULL );
				const float fNewBPM = strtof( arrayBPMChangeValues[1], NULL );
				
				BPMSegment new_seg;
				new_seg.m_fStartBeat = fBeat;
				new_seg.m_fBPM = fNewBPM;
				
				out.AddBPMSegment( new_seg );
			}
		}
	}
}
示例#8
0
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;
	
}
示例#9
0
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;
}