Example #1
0
void NoteLoaderOM::LoadObjectsFromFile(GString filename, GString prefix, Song *Out)
{
#if (!defined _WIN32) || (defined STLP)
	std::ifstream filein (filename.c_str());
#else
	std::ifstream filein (Utility::Widen(filename).c_str());
#endif
	std::regex versionfmt("osu file format v(\\d+)");

	if (!filein.is_open())
		throw std::exception("Could not open file.");

	auto Diff = make_shared<Difficulty>();
	OsuLoadInfo Info;

	Info.TimingInfo = make_shared<OsuManiaTimingInfo>();
	Info.OsuSong = Out;
	Info.SliderVelocity = 1.4;
	Info.Diff = Diff;
	Info.last_sound_index = 1;

	Diff->Data = make_shared<DifficultyLoadInfo>();
	Diff->Data->TimingInfo = Info.TimingInfo;

	// osu! stores bpm information as the time in ms that a beat lasts.
	Diff->BPMType = Difficulty::BT_BEATSPACE;
	Out->SongDirectory = prefix;

	Diff->Filename = filename;
	Out->SongDirectory = prefix + "/";

	/* 
		Just like BMS, osu!mania charts have timing data separated by files
		and a set is implied using folders.
	*/

	GString Line;

	getline(filein, Line);
	int version = -1;
	std::smatch sm;

	// "search" was picked instead of "match" since a line can have a bunch of 
	// junk before the version declaration
	if (regex_search(Line.cbegin(), Line.cend(), sm, versionfmt))
		version = atoi(sm[1].str().c_str());
	else
		throw std::exception("Invalid .osu file.");

	// "osu file format v"
	if (version < 10) // why
		throw std::exception(Utility::Format("Unsupported osu! file version (%d < 10)", version).c_str());

	Info.Version = version;

	osuReadingMode ReadingMode = RNotKnown, ReadingModeOld = RNotKnown;

	try {
		while (filein)
		{
			Info.Line++;
			getline(filein, Line);
			Utility::ReplaceAll(Line, "\r", "");

			if (!Line.length())
				continue;

			SetReadingMode(Line, ReadingMode);

			if (ReadingMode != ReadingModeOld || ReadingMode == RNotKnown) // Skip this line since it changed modes, or it's not a valid section yet
			{
				if (ReadingModeOld == RTiming)
					stable_sort(Info.HitsoundSections.begin(), Info.HitsoundSections.end());
				if (ReadingModeOld == RGeneral)
					if (!Info.ReadAModeTag)
						throw std::exception("Not an osu!mania chart.");
				ReadingModeOld = ReadingMode;
				continue;
			}

			switch (ReadingMode)
			{
			case RGeneral: 
				if (!ReadGeneral(Line, &Info))  // don't load charts that we can't work with
				{
					throw std::exception("osu! file unusable on raindrop.");
				}
						   break;
			case RMetadata: ReadMetadata(Line, &Info); break;
			case RDifficulty: ReadDifficulty(Line, &Info); break;
			case REvents: ReadEvents(Line, &Info); break;
			case RTiming: ReadTiming(Line, &Info); break;
			case RHitobjects: ReadObjects(Line, &Info); break;
			default: break;
			}
		}

		if (Diff->TotalObjects)
		{
			// Calculate an alleged offset
			Offsetize(&Info);

			// Okay then, convert timing data into a measure-based format raindrop can use.
			MeasurizeFromTimingData(&Info);

			CopyTimingData(&Info);

			// Then copy notes into these measures.
			PushNotesToMeasures(&Info);

			// Copy all sounds we registered
			for (auto i : Info.Sounds)
				Diff->SoundList[i.second] = i.first;

			// Calculate level as NPS
			Diff->Level = Diff->TotalScoringObjects / Diff->Duration;
			Out->Difficulties.push_back(Diff);
		}
	} catch (std::exception &e)
	{
		// rethrow with line info
		throw std::exception(Utility::Format("Line %d: %s", Info.Line, e.what()).c_str());
	}
}