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