From b82d9b1eb0bd95f213bb1385d022c8cb33982764 Mon Sep 17 00:00:00 2001
From: Fang Lu <cc2lufang@gmail.com>
Date: Wed, 6 Dec 2017 03:20:28 -0600
Subject: [PATCH] osufs - complete metadata parsing

* Added song sanity check
* Not tested
---
 osufs/osufs/osu.cpp | 207 +++++++++++++++++++++++++++++++++++++++++++-
 osufs/osufs/osu.h   |   1 +
 osufs/osufs/osufs.h |   2 +-
 3 files changed, 208 insertions(+), 2 deletions(-)

diff --git a/osufs/osufs/osu.cpp b/osufs/osufs/osu.cpp
index a890b0e..9be9cea 100644
--- a/osufs/osufs/osu.cpp
+++ b/osufs/osufs/osu.cpp
@@ -17,6 +17,8 @@
 #include "wavenc.h"
 
 
+#define ISHOT(ptr) (((uint8_t*)ptr)[0]==0xcc)
+
 using namespace std;
 
 osu_dir osu_open_dir(const char *dirname) {
@@ -49,6 +51,7 @@ osu_song osu_read_osu(const char *filename, mdev *dev, osu_meta *meta,
 	osu_song ret;
 	char line[1024];
 
+	memset(&ret, 0xcc, sizeof(ret));
 	strncpy(ret.magic, "DEAD", 4);
 
 	sprintf(line, "%s/%s", cont->dirname, filename);
@@ -119,6 +122,10 @@ osu_song osu_read_osu(const char *filename, mdev *dev, osu_meta *meta,
 	}
 
 	strncpy(ret.magic, "BEAT", 4);
+
+	printf("Parsing done. Performing sanity check...\n");
+	osu_check_beatmap_sanity(&ret);
+	
 	return ret;
 }
 
@@ -182,9 +189,10 @@ int osu_parse_general(char *line, osu_song *song,
 			fprintf(stderr, "Could not parse StackLeniency string `%s'\n", val);
 			return 1;
 		}
-		song->stack_leniency = round(slen * 256);
+		song->stack_leniency = round(slen * 0x100);
 		return 0;
 	}
+	
 	return 0;
 }
 
@@ -247,11 +255,131 @@ int osu_parse_metadata(char *line, osu_song *song,
 
 int osu_parse_difficulty(char *line, osu_song *song,
 						 mdev *dev, osu_meta *meta, osu_dir *cont) {
+	char *val = osu_util_str_split(line, ':'), *key = line;
+	if (!val) {
+		fprintf(stderr, "Difficulty Section: field has no value\n");
+		return 1;
+	}
+
+	// HPDrainRate (Decimal) specifies the HP Drain difficulty.
+	if (strcasecmp(key, "HPDrainRate") == 0) {
+		float drain;
+		if (sscanf(val, "%f", &drain) != 1) {
+			fprintf(stderr, "Could not parse HPDrainRate string `%s'\n", val);
+			return 1;
+		}
+		song->hp_drain = round(drain * 0x100);
+		return 0;
+	}
+	
+	// CircleSize (Decimal) specifies the size of hit object circles.
+	if (strcasecmp(key, "CircleSize") == 0) {
+		float circlesize;
+		if (sscanf(val, "%f", &circlesize) != 1) {
+			fprintf(stderr, "Could not parse CircleSize string `%s'\n", val);
+			return 1;
+		}
+		song->circle_size = round(circlesize * 0x100);
+		return 0;
+	}
+
+	// OverallDifficulty (Decimal) specifies the amount of time allowed to click
+	// a hit object on time.
+	if (strcasecmp(key, "OverallDifficulty") == 0) {
+		float diff;
+		if (sscanf(val, "%f", &diff) != 1) {
+			fprintf(stderr, "Could not parse OverallDifficulty string `%s'\n", val);
+			return 1;
+		}
+		song->difficulty = round(diff * 0x100);
+		return 0;
+	}
+
+	// ApproachRate (Decimal) specifies the amount of time taken for the
+	// approach circle and hit object to appear.
+	if (strcasecmp(key, "ApproachRate") == 0) {
+		float approach;
+		if (sscanf(val, "%f", &approach) != 1) {
+			fprintf(stderr, "Could not parse ApproachRate string `%s'\n", val);
+			return 1;
+		}
+		song->approach_rate = round(approach * 0x100);
+		return 0;
+	}
+
+	// SliderMultiplier (Decimal) specifies a multiplier for the slider velocity
+	// Default value is 1.4
+	if (strcasecmp(key, "SliderMultiplier") == 0) {
+		float slider_mult;
+		if (sscanf(val, "%f", &slider_mult) != 1) {
+			fprintf(stderr, "Could not parse SliderMultiplier string `%s'\n", val);
+			return 1;
+		}
+		song->slider_mult = round(slider_mult * 0x100);
+		return 0;
+	}
+	
+	// SliderTickRate (Decimal) specifies how often slider ticks appear
+	// Default value is 1
+	if (strcasecmp(key, "SliderTickRate") == 0) {
+		float slider_tick;
+		if (sscanf(val, "%f", &slider_tick) != 1) {
+			fprintf(stderr, "Could not parse SliderTickRate string `%s'\n", val);
+			return 1;
+		}
+		song->slider_tick = round(slider_tick * 0x100);
+		return 0;
+	}
+
 	return 0;
 }
 
 int osu_parse_events(char *line, osu_song *song,
 					 mdev *dev, osu_meta *meta, osu_dir *cont) {
+	// Events section is in CSV format
+	switch (line[0]) {
+		case '0': {
+			// Background image
+			if (strncmp(line, "0,0,\"", 5) != 0) {
+				fprintf(stderr, "Warning: bad background image: %s\n", line);
+				return 1;
+			}
+			char *fname = line + 5, *c = fname;
+			for (; *c; c++) {
+				if (*c == '"')
+					break;
+			}
+			if (*c != '"') {
+				fprintf(stderr, "Warning: background filename not quoted: %s\n", line);
+				return 1;
+			}
+			*c = '\0';
+			if (cont->fs.find(fname) == cont->fs.end()) {
+				printf("Allocating storage for image %s\n", fname);
+				uint16_t addr = meta->available_block;
+				song->cover_begin = addr;
+				
+				char path[1024];
+				sprintf(path, "%s/%s", cont->dirname, fname);
+				addr = img_write_dither128(path, dev, addr);
+				
+				song->cover_len = addr - song->cover_begin;
+				meta->available_block = addr;
+				cont->fs[fname] = make_pair(song->cover_begin, song->cover_len);
+			} else {
+				auto finfo = cont->fs[fname];
+				printf("Reusing storage (%x) for image %s\n", finfo.first, fname);
+				song->cover_begin = finfo.first;
+				song->cover_len = finfo.second;
+			}
+			return 0;
+		}
+			
+		case '2': {
+			// Break
+			return 0;
+		} 
+	}
 	return 0;
 }
 
@@ -262,6 +390,25 @@ int osu_parse_timing(char *line, osu_song *song,
 
 int osu_parse_colors(char *line, osu_song *song,
 					 mdev *dev, osu_meta *meta, osu_dir *cont) {
+	char *val = osu_util_str_split(line, ':'), *key = line;
+	if (!val) {
+		fprintf(stderr, "ComboColours Section: field has no value\n");
+		return 1;
+	}
+	
+	if (strncasecmp(key, "Combo",5) != 0) {
+		return 0;
+	}
+	
+	int n = key[6] - '1', r, g, b;
+	if (sscanf(val, "%d,%d,%d", &r, &g, &b) != 3) {
+		fprintf(stderr, "Warning: bad combo color: %s\n", val);
+		return 1;
+	}
+	song->combo_colors[n][0] = r;
+	song->combo_colors[n][1] = g;
+	song->combo_colors[n][2] = b;
+	
 	return 0;
 }
 
@@ -292,3 +439,61 @@ char *osu_util_str_split(char *line, char delim) {
 	}
 	return pval;
 }
+
+
+void osu_check_beatmap_sanity(osu_song *bm) {
+
+#define TEST_ENTRY_HOT(entryname) \
+if (ISHOT(&(bm->entryname))) {\
+	fprintf(stderr, "Warning: Beatmap has no " #entryname "\n");\
+}
+	
+	// Check header magic
+	if (strncmp(bm->magic, "BEAT", 4) != 0) {
+		fprintf(stderr, "CRITICAL: Beatmap has invalid magic\n");
+		return;
+	}
+	
+	// Check mandatory fields
+	TEST_ENTRY_HOT(cover_begin)
+	TEST_ENTRY_HOT(cover_len)
+	TEST_ENTRY_HOT(beatmap_begin)
+	TEST_ENTRY_HOT(beatmap_len)
+	TEST_ENTRY_HOT(audio_begin)
+	TEST_ENTRY_HOT(audio_len)
+	TEST_ENTRY_HOT(difficulty)
+	TEST_ENTRY_HOT(hp_drain)
+	TEST_ENTRY_HOT(circle_size)
+	TEST_ENTRY_HOT(approach_rate)
+	TEST_ENTRY_HOT(slider_mult)
+	TEST_ENTRY_HOT(slider_tick)
+	TEST_ENTRY_HOT(preview_blk_offset)
+	TEST_ENTRY_HOT(beatmap_id)
+	TEST_ENTRY_HOT(beatmap_setid)
+	TEST_ENTRY_HOT(audio_leadin)
+	TEST_ENTRY_HOT(stack_leniency)
+	TEST_ENTRY_HOT(title)
+	TEST_ENTRY_HOT(artist)
+	TEST_ENTRY_HOT(creator)
+	TEST_ENTRY_HOT(version)
+
+	// Check combo
+	TEST_ENTRY_HOT(combo_colors[0]);
+	bool combo_defined = true;
+	int ncombo = 1;
+	for (int i=1; i<8; i++) {
+		if (ISHOT(&(bm->combo_colors[i]))) {
+			combo_defined = false;
+		} else {
+			if (!combo_defined) {
+				fprintf(stderr, "Warning: extra combo color %d\n", i);
+			} else {
+				ncombo++;
+			}
+		}
+	}
+	printf("Combo colors defined: %d\n", ncombo);
+	
+	printf("Sanity check completed.\n");
+	
+}
diff --git a/osufs/osufs/osu.h b/osufs/osufs/osu.h
index 4d810da..3e59205 100644
--- a/osufs/osufs/osu.h
+++ b/osufs/osufs/osu.h
@@ -47,5 +47,6 @@ int osu_parse_hitobjs(char *line, osu_song *song,
 					  mdev *dev, osu_meta *meta, osu_dir *cont);
 
 char *osu_util_str_split(char *line, char delim);
+void osu_check_beatmap_sanity(osu_song *bm);
 
 #endif /* osu_h */
diff --git a/osufs/osufs/osufs.h b/osufs/osufs/osufs.h
index 64685cc..4c0c0dd 100644
--- a/osufs/osufs/osufs.h
+++ b/osufs/osufs/osufs.h
@@ -36,7 +36,7 @@ extern "C" {
 		int16_t approach_rate;					// 34  - 35
 		int16_t slider_mult;					// 36  - 37
 		int16_t slider_tick;					// 38  - 39
-		uint8_t combo_colors[3][8];				// 40  - 63
+		uint8_t combo_colors[8][3];				// 40  - 63
 		uint32_t preview_blk_offset;			// 64  - 67
 		uint32_t beatmap_id;					// 68  - 71
 		uint32_t beatmap_setid;					// 72  - 75
-- 
GitLab