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()); } }