#include "beatmapreader.h" #include "osustring.h" #include "util.h" #include #include #include // Enumerates beatmap data from a stream. Calls keyValueCallback for key-value pairs and csvCallback for CSV rows. // Returns true if the read completed without errors. bool Osu_EnumerateBeatmapData(const char* data, Osu_ReadBeatmapKeyValueCallback* keyValueCallback, Osu_ReadBeatmapCSVCallback* csvCallback, void* userdata) { // TODO: Ask about what changes between versions of the file format. // if (!Osu_HasPrefix(data, "osu file format v14")) // { // OSU_LOG_TRACE("Invalid file version"); // return false; // } char* dataEnd = strchr(data, 0); char* cursor = strchr(data, '\n') + 1; SDL_assert(cursor < dataEnd); struct Osu_String* section = Osu_CreateString(); struct Osu_String* key = Osu_CreateString(); struct Osu_String* value = Osu_CreateString(); while (cursor < dataEnd) { if (!*cursor) { break; } char* lineEnd = strchr(cursor, '\n'); if (!lineEnd) { break; } while (isspace(*cursor) && cursor < dataEnd) { cursor++; } if (lineEnd < cursor) { continue; } while (isspace(*lineEnd) && lineEnd > cursor) { lineEnd--; } if (cursor >= lineEnd) { continue; } if (strncmp(cursor, "//", 2) == 0) { cursor = lineEnd + 1; continue; } if (*cursor == '[') { if (*lineEnd != ']') { SDL_LogError(OSU_LOG_CATEGORY_BEATMAP_PARSER, "Section end is '%c'", *lineEnd); Osu_DestroyString(section); Osu_DestroyString(key); Osu_DestroyString(value); return false; } Osu_SetStringData(section, cursor + 1, lineEnd - cursor - 1); OSU_LOG_TRACE(section->buf); cursor = lineEnd + 1; continue; } SDL_assert(cursor < lineEnd); SDL_assert(cursor > data); SDL_assert(lineEnd < dataEnd); if (strcmp(section->buf, "Events") == 0 || strcmp(section->buf, "HitObjects") == 0) { char** values = NULL; size_t valuesLen = 0; char* sectionEnd = cursor; while (sectionEnd <= lineEnd + 1) { if (*sectionEnd == ',' || sectionEnd == lineEnd + 1) { valuesLen++; if (values) { values = realloc(values, sizeof(char*) * valuesLen); // TODO: Reallocating every single time is slow! } else { values = malloc(sizeof(char*) * valuesLen); } values[valuesLen - 1] = malloc(sectionEnd - cursor + 1); strncpy(values[valuesLen - 1], cursor, sectionEnd - cursor); values[valuesLen - 1][sectionEnd - cursor] = 0; cursor = sectionEnd + 1; } sectionEnd++; } if (valuesLen > 0) { switch (csvCallback(section->buf, values, valuesLen, userdata)) { case SDL_ENUM_CONTINUE: break; case SDL_ENUM_SUCCESS: Osu_DestroyString(section); Osu_DestroyString(key); Osu_DestroyString(value); return true; case SDL_ENUM_FAILURE: Osu_DestroyString(section); Osu_DestroyString(key); Osu_DestroyString(value); return false; } for (size_t i = 0; i < valuesLen; i++) { free(values[i]); } free(values); } } else { char* delim = memchr(cursor, ':', lineEnd - cursor + 1); if (!delim || delim >= lineEnd) { OSU_LOG_TRACE("Colon delimiter is beyond line end"); cursor = lineEnd + 1; continue; } char* keyEnd = delim - 1; while (keyEnd > cursor && isspace(*keyEnd)) { keyEnd--; } SDL_assert(keyEnd > cursor); char* valueStart = delim + 1; while (valueStart < lineEnd && isspace(*valueStart)) { valueStart++; } SDL_assert(valueStart <= lineEnd); Osu_SetStringData(key, cursor, keyEnd - cursor + 1); Osu_SetStringData(value, valueStart, lineEnd - valueStart + 1); if (keyValueCallback) { switch (keyValueCallback(section->buf, key->buf, value->buf, userdata)) { case SDL_ENUM_CONTINUE: break; case SDL_ENUM_SUCCESS: Osu_DestroyString(section); Osu_DestroyString(key); Osu_DestroyString(value); return true; case SDL_ENUM_FAILURE: Osu_DestroyString(section); Osu_DestroyString(key); Osu_DestroyString(value); return false; } } } cursor = lineEnd + 1; } Osu_DestroyString(section); Osu_DestroyString(key); Osu_DestroyString(value); return true; }