Skip to content

Instantly share code, notes, and snippets.

@pixelomer
Created March 7, 2021 12:35
Show Gist options
  • Save pixelomer/bf11b3c852747863ed901817ceaf93d4 to your computer and use it in GitHub Desktop.
Save pixelomer/bf11b3c852747863ed901817ceaf93d4 to your computer and use it in GitHub Desktop.
osu! beatmap metadata parser written in Objective-C
#import <Foundation/Foundation.h>
NSDictionary *OSUParseBeatmap(NSURL *beatmapURL);
#import "OSUBeatmap.h"
static void OSUBeatmapProcessKeyValuePair(NSMutableDictionary *output, NSString *line, NSString *separator, NSString *outputKey) {
NSMutableDictionary *subDictionary = output[outputKey];
if (!subDictionary) {
subDictionary = [NSMutableDictionary new];
output[outputKey] = subDictionary;
}
NSMutableArray *components = [[line componentsSeparatedByString:separator] mutableCopy];
NSString *key = components[0];
[components removeObjectAtIndex:0];
subDictionary[key] = [components componentsJoinedByString:separator];
}
static void OSUBeatmapProcessLine(NSMutableDictionary *output, NSMutableString *section, NSString *line, NSUInteger lineIndex) {
@autoreleasepool {
if (lineIndex == 0) {
output[@"Version"] = line;
}
else if ([line length] == 0) {
// Empty line
}
else if ([line hasPrefix:@"//"]) {
// Comment line
}
else if ([line hasPrefix:@"["] && [line hasSuffix:@"]"]) {
NSString *newSection = [line substringWithRange:NSMakeRange(1, line.length-2)];
[section setString:newSection];
}
else if ([section isEqualToString:@"Metadata"]) {
OSUBeatmapProcessKeyValuePair(output, line, @":", @"Metadata");
}
else if ([section isEqualToString:@"General"]) {
OSUBeatmapProcessKeyValuePair(output, line, @": ", @"General");
}
else if ([section isEqualToString:@"Events"]) {
NSArray *components = [line componentsSeparatedByString:@","];
if ((components.count >= 3) && ([components[0] isEqualToString:@"0"]) && ([components[1] isEqualToString:@"0"])) {
NSString *backgroundFilename = [components[2] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\""]];
output[@"BackgroundFilename"] = backgroundFilename;
}
}
}
}
NSDictionary *OSUParseBeatmap(NSURL *beatmapURL) {
NSInputStream *stream = [NSInputStream inputStreamWithURL:beatmapURL];
if (!stream) {
return nil;
}
[stream open];
NSUInteger lineIndex = 0;
NSMutableString *currentString = [NSMutableString new];
NSMutableDictionary *output = [NSMutableDictionary new];
NSMutableString *section = [NSMutableString new];
NSMutableData *buffer = [NSMutableData dataWithCapacity:4096];
while ([stream hasBytesAvailable]) {
NSInteger length = [stream read:[buffer mutableBytes] maxLength:4096];
NSString *nextChunk = [[NSString alloc] initWithBytes:[buffer bytes] length:length encoding:NSUTF8StringEncoding];
if (!nextChunk) {
[stream close];
return nil;
}
[currentString appendString:nextChunk];
NSRange lineRange;
NSString * const newline = @"\r\n";
while ((lineRange = [currentString rangeOfString:newline]).location != NSNotFound) {
NSString *line = [currentString substringWithRange:NSMakeRange(0, lineRange.location)];
[currentString deleteCharactersInRange:NSMakeRange(0, lineRange.location+newline.length)];
OSUBeatmapProcessLine(output, section, line, lineIndex++);
}
}
if ([currentString length]) {
OSUBeatmapProcessLine(output, section, currentString, lineIndex++);
}
[stream close];
return [[NSDictionary alloc] initWithDictionary:output copyItems:YES];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment