967 lines
23 KiB
C
967 lines
23 KiB
C
|
/*
|
||
|
* this is free and unencumbered software released into the
|
||
|
* public domain.
|
||
|
*
|
||
|
* refer to the attached UNLICENSE or http://unlicense.org/
|
||
|
* ----------------------------------------------------------------
|
||
|
* command line interface for oppai
|
||
|
*/
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <stdarg.h>
|
||
|
#include <string.h>
|
||
|
#include <math.h>
|
||
|
#include <ctype.h>
|
||
|
#undef OPPAI_EXPORT
|
||
|
#undef OPPAI_IMPLEMENTATION
|
||
|
#include "oppai.c"
|
||
|
|
||
|
char* me = "oppai";
|
||
|
|
||
|
#define al_round(x) (float)floor((x) + 0.5f)
|
||
|
#define al_min(a, b) ((a) < (b) ? (a) : (b))
|
||
|
#define al_max(a, b) ((a) > (b) ? (a) : (b))
|
||
|
#define twodec(x) (al_round((x) * 100.0f) / 100.0f)
|
||
|
#define array_len(x) (sizeof(x) / sizeof((x)[0]))
|
||
|
|
||
|
static
|
||
|
float get_inf() {
|
||
|
static unsigned raw = 0x7F800000;
|
||
|
float* p = (float*)&raw;
|
||
|
return *p;
|
||
|
}
|
||
|
|
||
|
static
|
||
|
int is_nan(float b) {
|
||
|
int* p = (int*)&b;
|
||
|
return (
|
||
|
(*p > 0x7F800000 && *p < 0x80000000) ||
|
||
|
(*p > 0x7FBFFFFF && *p <= 0xFFFFFFFF)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
static
|
||
|
int info(char* fmt, ...) {
|
||
|
int res;
|
||
|
va_list va;
|
||
|
va_start(va, fmt);
|
||
|
res = vfprintf(stderr, fmt, va);
|
||
|
va_end(va);
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
void usage() {
|
||
|
/* logo by flesnuk https://github.com/Francesco149/oppai-ng/issues/10 */
|
||
|
|
||
|
info(
|
||
|
" /\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb"
|
||
|
"\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb/ /\xe2\x8e\xbb\xe2"
|
||
|
"\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb"
|
||
|
"\xe2\x8e\xbb/ /\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2"
|
||
|
"\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb/ /\xe2\x8e"
|
||
|
"\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2"
|
||
|
"\x8e\xbb\xe2\x8e\xbb/ /\xe2\x8e\xbb/ /\xe2\x8e\xbb"
|
||
|
"\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\\ /\xe2\x8e\xbb"
|
||
|
"\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e"
|
||
|
"\xbb\xe2\x8e\xbb/\n / /\xe2\x8e\xbb\xe2\x8e\xbb\xe2"
|
||
|
"\x8e\xbb/ / / /\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb/ / "
|
||
|
);
|
||
|
|
||
|
info(
|
||
|
"/ /\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb/ / \xe2\x8e\xbb"
|
||
|
"\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb/ / / / "
|
||
|
"___ / /\xe2\x8e\xbb\xe2\x8e\xbb\\ \\ / /\xe2\x8e\xbb"
|
||
|
"\xe2\x8e\xbb\xe2\x8e\xbb/ /\n / / / / / / / / / /"
|
||
|
" / / /\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb\xe2\x8e\xbb"
|
||
|
"\xe2\x8e\xbb/ / / / /__/ / / / / / / / /\n / /___/ "
|
||
|
"/ / /___/ / / /___/ / / /___/ / / / / / / / / /__"
|
||
|
"_/ /\n /_______/ / ______/ / ______/ /_______/ /_/ "
|
||
|
"/_/ /_/ /_____ /\n / / / / "
|
||
|
" / / \n / / /"
|
||
|
" / /\xe2\x8e\xbb/___/"
|
||
|
" / \n /_/ /_/ "
|
||
|
" /_______/"
|
||
|
);
|
||
|
|
||
|
info("\n\n");
|
||
|
info("usage: %s /path/to/file.osu parameters\n\n", me);
|
||
|
|
||
|
info(
|
||
|
"set filename to '-' to read from standard input\n"
|
||
|
"all parameters are case insensitive\n"
|
||
|
"\n"
|
||
|
"-o[output_module]\n"
|
||
|
" output module. pass ? to list modules (oppai - -o?)\n"
|
||
|
" default: text\n"
|
||
|
" example: -ojson\n"
|
||
|
"\n"
|
||
|
"[accuracy]%%\n"
|
||
|
" accuracy percentage\n"
|
||
|
" default: 100%%\n"
|
||
|
" example: 95%%\n"
|
||
|
"\n"
|
||
|
"[n]x100\n"
|
||
|
" amount of 100s\n"
|
||
|
" default: 0\n"
|
||
|
" example: 2x100\n"
|
||
|
"\n"
|
||
|
);
|
||
|
|
||
|
info(
|
||
|
"[n]x50\n"
|
||
|
" amount of 50s\n"
|
||
|
" default: 0\n"
|
||
|
" example: 2x50\n"
|
||
|
"\n"
|
||
|
"[n]xm\n"
|
||
|
"[n]xmiss\n"
|
||
|
"[n]m\n"
|
||
|
" amount of misses\n"
|
||
|
" default: 0\n"
|
||
|
" example: 1m\n"
|
||
|
"\n"
|
||
|
"[combo]x\n"
|
||
|
" highest combo achieved\n"
|
||
|
" default: full combo (calculated from map data)\n"
|
||
|
" example: 500x\n"
|
||
|
"\n"
|
||
|
"scorev[n]\n"
|
||
|
" scoring system\n"
|
||
|
" default: 1\n"
|
||
|
" example: scorev2\n"
|
||
|
"\n"
|
||
|
);
|
||
|
|
||
|
info(
|
||
|
"ar[n]\n"
|
||
|
" base approach rate override\n"
|
||
|
" default: map's base approach rate\n"
|
||
|
" example: AR5\n"
|
||
|
"\n"
|
||
|
"od[n]\n"
|
||
|
" base overall difficulty override\n"
|
||
|
" default: map's base overall difficulty\n"
|
||
|
" example: OD10\n"
|
||
|
"\n"
|
||
|
"cs[n]\n"
|
||
|
" base circle size override\n"
|
||
|
" default: map's base circle size\n"
|
||
|
" example: CS6.5\n"
|
||
|
"\n"
|
||
|
);
|
||
|
|
||
|
info(
|
||
|
"-m[n]\n"
|
||
|
" gamemode id override for converted maps\n"
|
||
|
" default: uses the map's gamemode\n"
|
||
|
" example: -m1\n"
|
||
|
"\n"
|
||
|
"-taiko\n"
|
||
|
" forces gamemode to taiko for converted maps\n"
|
||
|
" default: disabled\n"
|
||
|
"\n"
|
||
|
"-touch\n"
|
||
|
" calculates pp for touchscreen / touch devices. can \n"
|
||
|
" also be specified as mod TD\n"
|
||
|
"\n"
|
||
|
"[n]speed\n"
|
||
|
" override speed stars. "
|
||
|
"useful for maps with incorrect star rating\n"
|
||
|
" default: uses computed speed stars\n"
|
||
|
" example: 3.5speed\n"
|
||
|
"\n"
|
||
|
);
|
||
|
|
||
|
info(
|
||
|
"[n]aim\n"
|
||
|
" override aim stars. "
|
||
|
"useful for maps with incorrect star rating\n"
|
||
|
" default: uses computed aim stars\n"
|
||
|
" example: 2.4aim\n"
|
||
|
"\n"
|
||
|
"-end[n]\n"
|
||
|
" cuts map to a certain number of objects\n"
|
||
|
);
|
||
|
}
|
||
|
|
||
|
#define output_sig(name) void name(int result, ezpp_t ez, char* mods_str)
|
||
|
|
||
|
typedef output_sig(fnoutput);
|
||
|
|
||
|
/* null output --------------------------------------------------------- */
|
||
|
|
||
|
/* stdout must be left alone, outputting to stderr is fine tho */
|
||
|
output_sig(output_null) { (void)result; (void)ez; (void)mods_str; }
|
||
|
|
||
|
/* text output --------------------------------------------------------- */
|
||
|
|
||
|
#define ASCIIPLT_W 51
|
||
|
|
||
|
void asciiplt(float (* getvalue)(void* data, int i), int n, void* data) {
|
||
|
static char* charset[] = {
|
||
|
#ifdef OPPAI_UTF8GRAPH
|
||
|
"\xe2\x96\x81",
|
||
|
"\xe2\x96\x82",
|
||
|
"\xe2\x96\x83",
|
||
|
"\xe2\x96\x84",
|
||
|
"\xe2\x96\x85",
|
||
|
"\xe2\x96\x86",
|
||
|
"\xe2\x96\x87",
|
||
|
"\xe2\x96\x88"
|
||
|
#else
|
||
|
" ", "_", ".", "-", "^"
|
||
|
#endif
|
||
|
};
|
||
|
|
||
|
static int charsetsize = array_len(charset);
|
||
|
|
||
|
float values[ASCIIPLT_W];
|
||
|
float minval = (float)get_inf();
|
||
|
float maxval = (float)-get_inf();
|
||
|
float range;
|
||
|
int i;
|
||
|
int chunksize;
|
||
|
int w = al_min(ASCIIPLT_W, n);
|
||
|
|
||
|
memset(values, 0, sizeof(values));
|
||
|
chunksize = (int)ceil((float)n / w);
|
||
|
|
||
|
for (i = 0; i < n; ++i) {
|
||
|
int chunki = i / chunksize;
|
||
|
values[chunki] = al_max(values[chunki], getvalue(data, i));
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < n; ++i) {
|
||
|
int chunki = i / chunksize;
|
||
|
maxval = al_max(maxval, values[chunki]);
|
||
|
minval = al_min(minval, values[chunki]);
|
||
|
}
|
||
|
|
||
|
range = al_max(0.00001f, maxval - minval);
|
||
|
|
||
|
for (i = 0; i < w; ++i) {
|
||
|
int chari = (int)(((values[i] - minval) / range) * charsetsize);
|
||
|
chari = al_max(0, al_min(chari, charsetsize - 1));
|
||
|
printf("%s", charset[chari]);
|
||
|
}
|
||
|
|
||
|
puts("");
|
||
|
}
|
||
|
|
||
|
float getaim(void* data, int i) {
|
||
|
ezpp_t ez = data;
|
||
|
return ezpp_strain_at(ez, i, DIFF_AIM);
|
||
|
}
|
||
|
|
||
|
float getspeed(void* data, int i) {
|
||
|
ezpp_t ez = data;
|
||
|
return ezpp_strain_at(ez, i, DIFF_SPEED);
|
||
|
}
|
||
|
|
||
|
output_sig(output_text) {
|
||
|
float ar, od, cs, hp, stars, aim_stars, speed_stars, accuracy_percent;
|
||
|
float pp, aim_pp, speed_pp, acc_pp;
|
||
|
|
||
|
if (result < 0) {
|
||
|
puts(errstr(result));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
printf("%s - %s ", ezpp_artist(ez), ezpp_title(ez));
|
||
|
|
||
|
if (strcmp(ezpp_artist(ez), ezpp_artist_unicode(ez)) ||
|
||
|
strcmp(ezpp_title(ez), ezpp_title_unicode(ez)))
|
||
|
{
|
||
|
printf("(%s - %s) ", ezpp_artist_unicode(ez), ezpp_title_unicode(ez));
|
||
|
}
|
||
|
|
||
|
printf("[%s] mapped by %s ", ezpp_version(ez), ezpp_creator(ez));
|
||
|
puts("\n");
|
||
|
|
||
|
ar = twodec(ezpp_ar(ez));
|
||
|
od = twodec(ezpp_od(ez));
|
||
|
cs = twodec(ezpp_cs(ez));
|
||
|
hp = twodec(ezpp_hp(ez));
|
||
|
stars = twodec(ezpp_stars(ez));
|
||
|
aim_stars = twodec(ezpp_aim_stars(ez));
|
||
|
speed_stars = twodec(ezpp_speed_stars(ez));
|
||
|
accuracy_percent = twodec(ezpp_accuracy_percent(ez));
|
||
|
pp = twodec(ezpp_pp(ez));
|
||
|
aim_pp = twodec(ezpp_aim_pp(ez));
|
||
|
speed_pp = twodec(ezpp_speed_pp(ez));
|
||
|
acc_pp = twodec(ezpp_acc_pp(ez));
|
||
|
|
||
|
printf("AR%g OD%g ", ar, od);
|
||
|
|
||
|
if (ezpp_mode(ez) == MODE_STD) {
|
||
|
printf("CS%g ", cs);
|
||
|
}
|
||
|
|
||
|
printf("HP%g\n", hp);
|
||
|
printf("300 hitwindow: %g ms\n", ezpp_odms(ez));
|
||
|
|
||
|
printf("%d circles, %d sliders, %d spinners\n",
|
||
|
ezpp_ncircles(ez), ezpp_nsliders(ez), ezpp_nspinners(ez));
|
||
|
|
||
|
if (ezpp_mode(ez) == MODE_STD) {
|
||
|
printf("%g stars (%g aim, %g speed)\n", stars, aim_stars, speed_stars);
|
||
|
printf("\nspeed strain: ");
|
||
|
asciiplt(getspeed, ezpp_nobjects(ez), ez);
|
||
|
printf(" aim strain: ");
|
||
|
asciiplt(getaim, ezpp_nobjects(ez), ez);
|
||
|
} else {
|
||
|
printf("%g stars\n", ezpp_stars(ez));
|
||
|
}
|
||
|
|
||
|
printf("\n");
|
||
|
|
||
|
if (mods_str) {
|
||
|
printf("+%s ", mods_str);
|
||
|
}
|
||
|
|
||
|
printf("%d/%dx ", ezpp_combo(ez), ezpp_max_combo(ez));
|
||
|
printf("%g%%\n", accuracy_percent);
|
||
|
printf("%g pp (", pp);
|
||
|
|
||
|
if (ezpp_mode(ez) == MODE_STD) {
|
||
|
printf("%g aim, ", aim_pp);
|
||
|
}
|
||
|
|
||
|
printf("%g speed, ", speed_pp);
|
||
|
printf("%g acc)\n\n", acc_pp);
|
||
|
}
|
||
|
|
||
|
/* json output --------------------------------------------------------- */
|
||
|
|
||
|
void print_escaped_json_string_ex(char* str, int quotes) {
|
||
|
char* chars_to_escape = "\\\"";
|
||
|
char* p;
|
||
|
if (quotes) {
|
||
|
putchar('"');
|
||
|
}
|
||
|
for (; *str; ++str) {
|
||
|
/* escape all characters in chars_to_escape */
|
||
|
for (p = chars_to_escape; *p; ++p) {
|
||
|
if (*p == *str) {
|
||
|
putchar('\\');
|
||
|
}
|
||
|
}
|
||
|
putchar(*str);
|
||
|
}
|
||
|
if (quotes) {
|
||
|
putchar('"');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#define print_escaped_json_string(x) \
|
||
|
print_escaped_json_string_ex(x, 1)
|
||
|
|
||
|
/* https://www.doc.ic.ac.uk/%7Eeedwards/compsys/float/nan.html */
|
||
|
|
||
|
static int is_inf(float b) {
|
||
|
int* p = (int*)&b;
|
||
|
return *p == 0x7F800000 || *p == 0xFF800000;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* json is mentally challenged and can't handle inf and nan so
|
||
|
* we're gonna be mathematically incorrect
|
||
|
*/
|
||
|
void fix_json_flt(float* v) {
|
||
|
if (is_inf(*v)) {
|
||
|
*v = -1;
|
||
|
} else if (is_nan(*v)) {
|
||
|
*v = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
output_sig(output_json) {
|
||
|
float pp, aim_pp, speed_pp, acc_pp, stars, aim_stars, speed_stars;
|
||
|
printf("{\"oppai_version\":\"%s\",", oppai_version_str());
|
||
|
|
||
|
if (result < 0) {
|
||
|
printf("\"code\":%d,", result);
|
||
|
printf("\"errstr\":");
|
||
|
print_escaped_json_string(errstr(result));
|
||
|
printf("}");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
pp = ezpp_pp(ez);
|
||
|
aim_pp = ezpp_aim_pp(ez);
|
||
|
speed_pp = ezpp_speed_pp(ez);
|
||
|
acc_pp = ezpp_acc_pp(ez);
|
||
|
stars = ezpp_stars(ez);
|
||
|
aim_stars = ezpp_aim_stars(ez);
|
||
|
speed_stars = ezpp_speed_stars(ez);
|
||
|
fix_json_flt(&pp);
|
||
|
fix_json_flt(&aim_pp);
|
||
|
fix_json_flt(&speed_pp);
|
||
|
fix_json_flt(&acc_pp);
|
||
|
fix_json_flt(&stars);
|
||
|
fix_json_flt(&aim_stars);
|
||
|
fix_json_flt(&speed_stars);
|
||
|
|
||
|
printf("\"code\":200,\"errstr\":\"no error\",");
|
||
|
|
||
|
printf("\"artist\":");
|
||
|
print_escaped_json_string(ezpp_artist(ez));
|
||
|
|
||
|
if (strcmp(ezpp_artist(ez), ezpp_artist_unicode(ez))) {
|
||
|
printf(",\"artist_unicode\":");
|
||
|
print_escaped_json_string(ezpp_artist_unicode(ez));
|
||
|
}
|
||
|
|
||
|
printf(",\"title\":");
|
||
|
print_escaped_json_string(ezpp_title(ez));
|
||
|
|
||
|
if (strcmp(ezpp_title(ez), ezpp_title_unicode(ez))) {
|
||
|
printf(",\"title_unicode\":");
|
||
|
print_escaped_json_string(ezpp_title_unicode(ez));
|
||
|
}
|
||
|
|
||
|
printf(",\"creator\":");
|
||
|
print_escaped_json_string(ezpp_creator(ez));
|
||
|
|
||
|
printf(",\"version\":");
|
||
|
print_escaped_json_string(ezpp_version(ez));
|
||
|
|
||
|
printf(",");
|
||
|
|
||
|
if (!mods_str) {
|
||
|
mods_str = "";
|
||
|
}
|
||
|
|
||
|
printf(
|
||
|
"\"mods_str\":\"%s\",\"mods\":%d,"
|
||
|
"\"od\":%g,\"ar\":%g,\"cs\":%g,\"hp\":%g,"
|
||
|
"\"combo\":%d,\"max_combo\":%d,"
|
||
|
"\"num_circles\":%d,\"num_sliders\":%d,"
|
||
|
"\"num_spinners\":%d,\"misses\":%d,"
|
||
|
"\"score_version\":%d,\"stars\":%.17g,"
|
||
|
"\"speed_stars\":%.17g,\"aim_stars\":%.17g,"
|
||
|
"\"aim_pp\":%.17g,\"speed_pp\":%.17g,\"acc_pp\":%.17g,"
|
||
|
"\"pp\":%.17g}",
|
||
|
mods_str, ezpp_mods(ez), ezpp_od(ez), ezpp_ar(ez),
|
||
|
ezpp_cs(ez), ezpp_hp(ez), ezpp_combo(ez),
|
||
|
ezpp_max_combo(ez), ezpp_ncircles(ez), ezpp_nsliders(ez),
|
||
|
ezpp_nspinners(ez), ezpp_nmiss(ez), ezpp_score_version(ez),
|
||
|
ezpp_stars(ez), ezpp_speed_stars(ez), ezpp_aim_stars(ez),
|
||
|
ezpp_aim_pp(ez), ezpp_speed_pp(ez), ezpp_acc_pp(ez), ezpp_pp(ez)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/* csv output ---------------------------------------------------------- */
|
||
|
|
||
|
void print_escaped_csv_string(char* str) {
|
||
|
char* chars_to_escape = "\\;";
|
||
|
char* p;
|
||
|
for (; *str; ++str) {
|
||
|
/* escape all characters in chars_to_escape */
|
||
|
for (p = chars_to_escape; *p; ++p) {
|
||
|
if (*p == *str) {
|
||
|
putchar('\\');
|
||
|
}
|
||
|
}
|
||
|
putchar(*str);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
output_sig(output_csv) {
|
||
|
printf("oppai_version;%s\n", oppai_version_str());
|
||
|
|
||
|
if (result < 0) {
|
||
|
printf("code;%d\nerrstr;", result);
|
||
|
print_escaped_csv_string(errstr(result));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
printf("code;200\nerrstr;no error\n");
|
||
|
|
||
|
printf("artist;");
|
||
|
print_escaped_csv_string(ezpp_artist(ez));
|
||
|
puts("");
|
||
|
|
||
|
if (strcmp(ezpp_artist(ez), ezpp_artist_unicode(ez))) {
|
||
|
printf("artist_unicode;");
|
||
|
print_escaped_csv_string(ezpp_artist_unicode(ez));
|
||
|
puts("");
|
||
|
}
|
||
|
|
||
|
printf("title;");
|
||
|
print_escaped_csv_string(ezpp_title(ez));
|
||
|
puts("");
|
||
|
|
||
|
if (strcmp(ezpp_title(ez), ezpp_title_unicode(ez))) {
|
||
|
printf("title_unicode;");
|
||
|
print_escaped_csv_string(ezpp_title_unicode(ez));
|
||
|
puts("");
|
||
|
}
|
||
|
|
||
|
printf("version;");
|
||
|
print_escaped_csv_string(ezpp_version(ez));
|
||
|
puts("");
|
||
|
|
||
|
printf("creator;");
|
||
|
print_escaped_csv_string(ezpp_creator(ez));
|
||
|
puts("");
|
||
|
|
||
|
if (!mods_str) {
|
||
|
mods_str = "";
|
||
|
}
|
||
|
|
||
|
printf(
|
||
|
"mods_str;%s\nmods;%d\nod;%g\nar;%g\ncs;%g\nhp;%g\n"
|
||
|
"combo;%d\nmax_combo;%d\nnum_circles;%d\n"
|
||
|
"num_sliders;%d\nnum_spinners;%d\nmisses;%d\n"
|
||
|
"score_version;%d\nstars;%.17g\nspeed_stars;%.17g\n"
|
||
|
"aim_stars;%.17g\naim_pp;%.17g\nspeed_pp;%.17g\nacc_pp;%.17g\npp;%.17g",
|
||
|
mods_str, ezpp_mods(ez), ezpp_od(ez), ezpp_ar(ez),
|
||
|
ezpp_cs(ez), ezpp_hp(ez), ezpp_combo(ez),
|
||
|
ezpp_max_combo(ez), ezpp_ncircles(ez), ezpp_nsliders(ez),
|
||
|
ezpp_nspinners(ez), ezpp_nmiss(ez), ezpp_score_version(ez),
|
||
|
ezpp_stars(ez), ezpp_speed_stars(ez), ezpp_aim_stars(ez),
|
||
|
ezpp_aim_pp(ez), ezpp_speed_pp(ez), ezpp_acc_pp(ez), ezpp_pp(ez)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/* binary output ------------------------------------------------------- */
|
||
|
|
||
|
void write1(int v) {
|
||
|
char buf = (char)(v & 0xFF);
|
||
|
fwrite(&buf, 1, 1, stdout);
|
||
|
}
|
||
|
|
||
|
void write2(int v) {
|
||
|
char buf[2];
|
||
|
buf[0] = (char)(v & 0xFF);
|
||
|
buf[1] = (char)(v >> 8);
|
||
|
fwrite(buf, 1, 2, stdout);
|
||
|
}
|
||
|
|
||
|
void write4(int v) {
|
||
|
char buf[4];
|
||
|
buf[0] = (char)(v & 0xFF);
|
||
|
buf[1] = (char)((v >> 8) & 0xFF);
|
||
|
buf[2] = (char)((v >> 16) & 0xFF);
|
||
|
buf[3] = (char)((v >> 24) & 0xFF);
|
||
|
fwrite(buf, 1, 4, stdout);
|
||
|
}
|
||
|
|
||
|
void write_flt(float f) {
|
||
|
int* p = (int*)&f;
|
||
|
write4(*p);
|
||
|
}
|
||
|
|
||
|
void write_str(char* str) {
|
||
|
int len = al_min(0xFFFF, (int)strlen(str));
|
||
|
write2(len);
|
||
|
printf("%s", str);
|
||
|
write1(0);
|
||
|
}
|
||
|
|
||
|
output_sig(output_binary) {
|
||
|
int major, minor, patch;
|
||
|
(void)mods_str;
|
||
|
|
||
|
if (!freopen(0, "wb", stdout)) {
|
||
|
perror("freopen");
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
printf("binoppai");
|
||
|
oppai_version(&major, &minor, &patch);
|
||
|
write1(major);
|
||
|
write1(minor);
|
||
|
write1(patch);
|
||
|
write4(result);
|
||
|
|
||
|
if (result < 0) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* TODO: use varargs to group calls of the same func */
|
||
|
write_str(ezpp_artist(ez));
|
||
|
write_str(ezpp_artist_unicode(ez));
|
||
|
write_str(ezpp_title(ez));
|
||
|
write_str(ezpp_title_unicode(ez));
|
||
|
write_str(ezpp_version(ez));
|
||
|
write_str(ezpp_creator(ez));
|
||
|
write4(ezpp_mods(ez));
|
||
|
write_flt(ezpp_od(ez));
|
||
|
write_flt(ezpp_ar(ez));
|
||
|
write_flt(ezpp_cs(ez));
|
||
|
write_flt(ezpp_hp(ez));
|
||
|
write4(ezpp_combo(ez));
|
||
|
write4(ezpp_max_combo(ez));
|
||
|
write2(ezpp_ncircles(ez));
|
||
|
write2(ezpp_nsliders(ez));
|
||
|
write2(ezpp_nspinners(ez));
|
||
|
write4(ezpp_score_version(ez));
|
||
|
write_flt(ezpp_stars(ez));
|
||
|
write_flt(ezpp_speed_stars(ez));
|
||
|
write_flt(ezpp_aim_stars(ez));
|
||
|
write2(0); /* legacy (nsingles) */
|
||
|
write2(0); /* legacy (nsigles_threshold) */
|
||
|
write_flt(ezpp_aim_pp(ez));
|
||
|
write_flt(ezpp_speed_pp(ez));
|
||
|
write_flt(ezpp_acc_pp(ez));
|
||
|
write_flt(ezpp_pp(ez));
|
||
|
}
|
||
|
|
||
|
/* gnuplot output ------------------------------------------------------ */
|
||
|
|
||
|
#define gnuplot_string(x) print_escaped_json_string_ex(x, 0)
|
||
|
|
||
|
void gnuplot_strains(ezpp_t ez, int type) {
|
||
|
int i;
|
||
|
for (i = 0; i < ezpp_nobjects(ez); ++i) {
|
||
|
printf("%.17g %.17g\n", ezpp_time_at(ez, i),
|
||
|
ezpp_strain_at(ez, i, type));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
output_sig(output_gnuplot) {
|
||
|
if (result < 0 || ezpp_mode(ez) != MODE_STD) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
puts("set encoding utf8;");
|
||
|
|
||
|
printf("set title \"");
|
||
|
gnuplot_string(ezpp_artist(ez));
|
||
|
printf(" - ");
|
||
|
gnuplot_string(ezpp_title(ez));
|
||
|
|
||
|
if (strcmp(ezpp_artist(ez), ezpp_artist_unicode(ez)) ||
|
||
|
strcmp(ezpp_title(ez), ezpp_title_unicode(ez)))
|
||
|
{
|
||
|
printf("(");
|
||
|
gnuplot_string(ezpp_artist_unicode(ez));
|
||
|
printf(" - ");
|
||
|
gnuplot_string(ezpp_title_unicode(ez));
|
||
|
printf(")");
|
||
|
}
|
||
|
|
||
|
printf(" [");
|
||
|
gnuplot_string(ezpp_version(ez));
|
||
|
printf("] mapped by ");
|
||
|
gnuplot_string(ezpp_creator(ez));
|
||
|
if (mods_str) printf(" +%s", mods_str);
|
||
|
puts("\";");
|
||
|
|
||
|
puts(
|
||
|
"set xlabel 'time (ms)';"
|
||
|
"set ylabel 'strain';"
|
||
|
"set multiplot layout 2,1 rowsfirst;"
|
||
|
"plot '-' with lines lc 1 title 'speed'"
|
||
|
);
|
||
|
gnuplot_strains(ez, DIFF_SPEED);
|
||
|
puts("e");
|
||
|
puts("unset title;");
|
||
|
puts("plot '-' with lines lc 2 title 'aim'");
|
||
|
gnuplot_strains(ez, DIFF_AIM);
|
||
|
}
|
||
|
|
||
|
/* ------------------------------------------------------------- */
|
||
|
|
||
|
#define CODE_DESC "the code and errstr fields " \
|
||
|
"should be checked for errors. a negative value for code " \
|
||
|
"indicates an error"
|
||
|
|
||
|
typedef struct output_module {
|
||
|
char* name;
|
||
|
fnoutput* func;
|
||
|
char* description[4];
|
||
|
/* null terminated array of strings because of c90 literal limits */
|
||
|
} output_module_t;
|
||
|
|
||
|
output_module_t modules[] = {
|
||
|
{ "null", output_null, { "no output", 0 } },
|
||
|
{ "text", output_text, { "plain text", 0 } },
|
||
|
{
|
||
|
"json", output_json,
|
||
|
{ "a single utf-8 json object.\n" CODE_DESC, 0 }
|
||
|
},
|
||
|
{
|
||
|
"csv", output_csv,
|
||
|
{ "fieldname;value\n"
|
||
|
"one value per line. ';' characters in strings will be "
|
||
|
"escaped to \"\\;\". utf-8.\n" CODE_DESC, 0 }
|
||
|
},
|
||
|
{
|
||
|
"binary",
|
||
|
output_binary,
|
||
|
{ "binary stream of values, encoded in little endian.\n"
|
||
|
"negative code values indicate an error, which matches "
|
||
|
"the error codes defined in oppai.c\n"
|
||
|
"for an example on how to read this in C, check out "
|
||
|
"examples/binary.c in oppai-ng's source\n"
|
||
|
"\n"
|
||
|
"floats and floats are represented using whatever "
|
||
|
"convention the host machine and compiler use. unless you "
|
||
|
"are on a really exotic machine it shouldn't matter\n"
|
||
|
"\n"
|
||
|
"strings (str) are encoded as a 2-byte integer indicating "
|
||
|
"the length in bytes, followed by the string bytes and ",
|
||
|
"a null (zero) terminating byte\n"
|
||
|
"\n"
|
||
|
"binoppai (8-byte magic), "
|
||
|
"int8 oppai_ver_major, int8 oppai_ver_minor, "
|
||
|
"int8 oppai_ver_patch, int error_code, "
|
||
|
"str artist, str artist_utf8, str title, str title_utf8, "
|
||
|
"str version, str creator, "
|
||
|
"int mods_bitmask, float od, float ar, float cs, "
|
||
|
"float hp, int combo, int max_combo, "
|
||
|
"int16 ncircles, int16 nsliders, int16 nspinner, "
|
||
|
"int score_version, float total_stars, ",
|
||
|
"float speed_stars, float aim_stars, int16 nsingles, "
|
||
|
"int16 nsingles_threshold, float aim_pp, "
|
||
|
"float speed_pp, float acc_pp, float pp",
|
||
|
0 }
|
||
|
},
|
||
|
{ "gnuplot", output_gnuplot, { "gnuplot .gp script", 0 } },
|
||
|
};
|
||
|
|
||
|
output_module_t* output_by_name(char* name) {
|
||
|
int i;
|
||
|
for (i = 0; i < array_len(modules); ++i) {
|
||
|
if (!strcmp(modules[i].name, name)) {
|
||
|
return &modules[i];
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int cmpsuffix(char* str, char* suffix) {
|
||
|
int sufflen = (int)al_min(strlen(str), strlen(suffix));
|
||
|
return strcmp(str + strlen(str) - sufflen, suffix);
|
||
|
}
|
||
|
|
||
|
char lowercase(char c) {
|
||
|
if (c >= 'A' && c <= 'Z') {
|
||
|
return c + ('a' - 'A');
|
||
|
}
|
||
|
return c;
|
||
|
}
|
||
|
|
||
|
char uppercase(char c) {
|
||
|
if (c >= 'a' && c <= 'z') {
|
||
|
return c - ('a' - 'A');
|
||
|
}
|
||
|
return c;
|
||
|
}
|
||
|
|
||
|
int strcmp_nc(char* a, char* b) {
|
||
|
for (;; ++a, ++b) {
|
||
|
char la = lowercase(*a);
|
||
|
char lb = lowercase(*b);
|
||
|
if (la > lb) {
|
||
|
return 1;
|
||
|
}
|
||
|
else if (la < lb) {
|
||
|
return -1;
|
||
|
}
|
||
|
if (!*a || *b) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* TODO: split main into smaller funcs for readability? */
|
||
|
int main(int argc, char* argv[]) {
|
||
|
int i;
|
||
|
int result;
|
||
|
ezpp_t ez = ezpp_new();
|
||
|
output_module_t* m;
|
||
|
char* output_name = "text";
|
||
|
char* mods_str = 0;
|
||
|
int mods = MODS_NOMOD;
|
||
|
float tmpf, speed_stars = 0, aim_stars = 0, accuracy_percent = 0;
|
||
|
int tmpi, n100 = 0, n50 = 0;
|
||
|
|
||
|
/* parse arguments ------------------------------------------------- */
|
||
|
me = argv[0];
|
||
|
|
||
|
if (argc < 2) {
|
||
|
usage();
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
if (*argv[1] == '-' && strlen(argv[1]) > 1) {
|
||
|
char* a = argv[1] + 1;
|
||
|
if (!strcmp_nc(a, "version") || !strcmp_nc(a, "v")) {
|
||
|
puts(oppai_version_str());
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (i = 2; i < argc; ++i) {
|
||
|
char* a = argv[i];
|
||
|
char* p;
|
||
|
int iswhite = 1;
|
||
|
|
||
|
for (p = a; *p; ++p) {
|
||
|
if (!isspace(*p)) {
|
||
|
iswhite = 0;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (iswhite) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
for (p = a; *p; ++p) {
|
||
|
*p = lowercase(*p);
|
||
|
}
|
||
|
|
||
|
if (*a == '-' && a[1] == 'o') {
|
||
|
output_name = a + 2;
|
||
|
|
||
|
if (!strcmp(output_name, "?")) {
|
||
|
int j;
|
||
|
int nmodules = sizeof(modules) / sizeof(modules[0]);
|
||
|
for (j = 0; j < nmodules; ++j) {
|
||
|
char** d = modules[j].description;
|
||
|
puts(modules[j].name);
|
||
|
for (; *d; ++d) {
|
||
|
printf("%s", *d);
|
||
|
}
|
||
|
puts("\n-");
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!cmpsuffix(a, "%") && sscanf(a, "%f", &accuracy_percent) == 1) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!cmpsuffix(a, "x100") && sscanf(a, "%d", &n100) == 1) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!cmpsuffix(a, "x50") && sscanf(a, "%d", &n50) == 1) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!cmpsuffix(a, "speed") && sscanf(a, "%f", &speed_stars) == 1) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!cmpsuffix(a, "aim") && sscanf(a, "%f", &aim_stars) == 1) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!cmpsuffix(a, "xm") || !cmpsuffix(a, "xmiss") ||
|
||
|
!cmpsuffix(a, "m"))
|
||
|
{
|
||
|
if (sscanf(a, "%d", &tmpi) == 1) {
|
||
|
ezpp_set_nmiss(ez, tmpi);
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!cmpsuffix(a, "x") && sscanf(a, "%d", &tmpi) == 1) {
|
||
|
ezpp_set_combo(ez, tmpi);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (sscanf(a, "scorev%d", &tmpi)) {
|
||
|
ezpp_set_score_version(ez, tmpi);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (sscanf(a, "ar%f", &tmpf)) {
|
||
|
ezpp_set_base_ar(ez, tmpf);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (sscanf(a, "od%f", &tmpf)) {
|
||
|
ezpp_set_base_od(ez, tmpf);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (sscanf(a, "cs%f", &tmpf)) {
|
||
|
ezpp_set_base_cs(ez, tmpf);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (sscanf(a, "-m%d", &tmpi) == 1) {
|
||
|
ezpp_set_mode_override(ez, tmpi);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (sscanf(a, "-end%d", &tmpi) == 1) {
|
||
|
ezpp_set_end(ez, tmpi);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!strcmp(a, "-taiko")) {
|
||
|
ezpp_set_mode_override(ez, MODE_TAIKO);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!strcmp(a, "-touch")) {
|
||
|
mods |= MODS_TOUCH_DEVICE;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* this should be last because it uppercase's the string */
|
||
|
if (*a == '+') {
|
||
|
mods_str = a + 1;
|
||
|
for (p = mods_str; *p; ++p) {
|
||
|
*p = uppercase(*p);
|
||
|
}
|
||
|
|
||
|
#define m(mod) \
|
||
|
if (!strncmp(p, #mod, strlen(#mod))) { \
|
||
|
mods |= MODS_##mod; \
|
||
|
p += strlen(#mod); \
|
||
|
continue; \
|
||
|
}
|
||
|
|
||
|
for (p = mods_str; *p;) {
|
||
|
m(NF) m(EZ) m(TD) m(HD) m(HR) m(SD) m(DT) m(RX) m(HT) m(NC) m(FL)
|
||
|
m(AT) m(SO) m(AP) m(PF) m(NOMOD)
|
||
|
++p;
|
||
|
}
|
||
|
|
||
|
#undef m
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
info(">%s\n", a);
|
||
|
result = ERR_SYNTAX;
|
||
|
goto output;
|
||
|
}
|
||
|
|
||
|
if (accuracy_percent) {
|
||
|
ezpp_set_accuracy_percent(ez, accuracy_percent);
|
||
|
} else {
|
||
|
ezpp_set_accuracy(ez, n100, n50);
|
||
|
}
|
||
|
ezpp_set_mods(ez, mods);
|
||
|
ezpp_set_speed_stars(ez, speed_stars);
|
||
|
ezpp_set_aim_stars(ez, aim_stars);
|
||
|
result = ezpp(ez, argv[1]);
|
||
|
|
||
|
output:
|
||
|
m = output_by_name(output_name);
|
||
|
if (!m) {
|
||
|
info("output module '%s' does not exist. check 'oppai - -o?'\n",
|
||
|
output_name);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
m->func(result, ez, mods_str);
|
||
|
ezpp_free(ez); /* just so valgrind stops crying */
|
||
|
return result < 0;
|
||
|
}
|
||
|
|