/* * scoretbl.c * * Common code to handle highscore tables. This code is included by * grvscored, so it must not include anything that prevents that from * working correctly. */ #include #include #include #include #include #include #include "highscore.h" #define MAXLINE 4096 /* Max length of a text line */ struct bests bests; /* * Insert a total; return a pointer to the player name string * if applicable. Return place [1,MAX_BEST] or 0 if no ranking */ int highscore_add_total(uint64_t gameid, int64_t score, int endlvl, unsigned char **pptr, uint8_t upload) { struct best_total *tp = bests.total; int i; for ( i = 0 ; i < MAX_BEST ; i++, tp++ ) { if ( score > tp->score ) { memmove(tp+1, tp, (MAX_BEST-1-i)*sizeof(*tp)); tp->gameid = gameid; tp->score = score; tp->endlvl = endlvl; tp->upload = upload; memset(tp->player, 0, sizeof tp->player); if ( pptr ) *pptr = tp->player; return i+1; } else if ( gameid == tp->gameid && score == tp->score ) { tp->upload &= upload; if ( pptr ) *pptr = tp->player; return i+1; } } if ( pptr ) *pptr = NULL; return 0; } /* * Insert a level best score, return rank (or 0 if N/A) */ int highscore_add_level_score(uint64_t gameid, int lvl, int64_t score, uint8_t upload) { struct best_level_score *tp; int i; if ( lvl < 0 || lvl >= MAX_LEVEL ) return 0; tp = bests.level[lvl].score; for ( i = 0 ; i < MAX_PER_LEVEL ; i++, tp++ ) { if ( score > tp->score ) { memmove(tp+1, tp, (MAX_PER_LEVEL-1-i)*sizeof(*tp)); tp->gameid = gameid; tp->score = score; tp->upload = upload; return i+1; } else if ( gameid == tp->gameid && score == tp->score ) { tp->upload &= upload; return i+1; } } return 0; } /* * Insert a level best time, return rank (or 0 if N/A) */ int highscore_add_level_time(uint64_t gameid, int lvl, int32_t time_ms, uint8_t upload) { struct best_level_time *tp; int i; if ( lvl < 0 || lvl >= MAX_LEVEL ) return 0; tp = bests.level[lvl].time_ms; for ( i = 0 ; i < MAX_PER_LEVEL ; i++, tp++ ) { if ( time_ms < tp->time_ms ) { memmove(tp+1, tp, (MAX_PER_LEVEL-1-i)*sizeof(*tp)); tp->gameid = gameid; tp->time_ms = time_ms; tp->upload = upload; return i+1; } else if ( gameid == tp->gameid && time_ms == tp->time_ms ) { tp->upload &= upload; return i+1; } } return 0; } /* * Initialize the highscore data structures to "nothing" */ void highscore_init(void) { int i, j; memset(&bests, 0, sizeof bests); for ( i = 0 ; i < MAX_LEVEL ; i++ ) for ( j = 0 ; j < MAX_PER_LEVEL ; j++ ) bests.level[i].time_ms[j].time_ms = NO_TIME; } /* * Unquote a string */ static unsigned char *unquote(const char *i) { static unsigned char buf[MAXLINE], *o; char sb[3]; sb[2] = 0; o = buf; while ( isxdigit(sb[0] = i[0]) && isxdigit(sb[1] = i[1]) ) { *o++ = (unsigned char)strtoul(sb, NULL, 16); i += 2; } *o++ = '\0'; return buf; } /* * Quote a string */ static char *quote(const unsigned char *i) { static char buf[MAXLINE], *o; unsigned char c; o = buf; while ( (c = *i++) ) { sprintf(o, "%02x", c); o += 2; } *o++ = '0'; /* End with 00 so even a null string is ok */ *o++ = '0'; *o++ = '\0'; return buf; } /* * Parse an incoming high-score table. It can come either from the network * or from a file. Because Windows is braindamaged and doesn't have any * kind of sane way to use the stdio library with sockets, we have to abstract * out the I/O function that is allowed to access this. Puke. */ void highscore_parse(void *f, char (*mygets)(char *, int, void *), uint8_t upload) { char line[MAXLINE], pname[MAXLINE]; uint64_t gameid; int64_t score; int32_t time_ms; int lvl; unsigned char *pptr; while ( mygets(line, MAXLINE, f) ) { if ( !strchr(line, '\n') ) return; /* Something bogus here... */ if ( sscanf(line, "TS %" SCNx64 " %" SCNd64 " %d %[0-9a-f]", &gameid, &score, &lvl, pname) == 4 ) { highscore_add_total(gameid, score, lvl-1, &pptr, upload); if ( pptr ) { strncpy(pptr, unquote(pname), PLAYER_LEN+1); pptr[PLAYER_LEN] = '\0'; } } else if ( sscanf(line, "LS %" SCNx64 " %d %" SCNd64, &gameid, &lvl, &score) == 3 ) { highscore_add_level_score(gameid, lvl-1, score, upload); } else if ( sscanf(line, "LT %" SCNx64 " %d %" SCNd32, &gameid, &lvl, &time_ms) == 3 ) { highscore_add_level_time(gameid, lvl-1, time_ms, upload); } } } /* * Write out a high score file; return 0 on success, -1 on failure */ int highscore_write(void *f, int (*myputs)(char *, void *), uint8_t leave) { char buffer[MAXLINE]; int i, j; myputs("VC 1.0\n", f); /* Version C 1.0 */ for ( i = 0 ; i < MAX_BEST ; i++ ) { if ( bests.total[i].score ) { sprintf(buffer, "TS %" PRIx64 " %" PRId64 " %d %s\n", bests.total[i].gameid, bests.total[i].score, bests.total[i].endlvl+1, quote(bests.total[i].player)); if ( myputs(buffer, f) == EOF ) return -1; bests.total[i].upload &= leave; } } for ( i = 0 ; i < MAX_LEVEL ; i++ ) { for ( j = 0 ; j < MAX_PER_LEVEL ; j++ ) { if ( bests.level[i].score[j].score ) sprintf(buffer, "LS %" PRIx64 " %d %" PRId64 "\n", bests.level[i].score[j].gameid, i+1, bests.level[i].score[j].score); if ( myputs(buffer, f) == EOF ) return -1; bests.level[i].score[j].upload &= leave; } for ( j = 0 ; j < MAX_PER_LEVEL ; j++ ) { if ( bests.level[i].time_ms[j].time_ms != NO_TIME ) sprintf(buffer, "LT %" PRIx64 " %d %" PRId32 "\n", bests.level[i].time_ms[j].gameid, i+1, bests.level[i].time_ms[j].time_ms); if ( myputs(buffer, f) == EOF ) return -1; bests.level[i].time_ms[j].upload &= leave; } } return 0; } int have_upload_scores(void) { int i, j; for ( i = 0 ; i < MAX_BEST ; i++ ) if ( bests.total[i].upload ) return 1; for ( i = 0 ; i < MAX_LEVEL ; i++ ) for ( j = 0 ; j < MAX_PER_LEVEL ; j++ ) if ( bests.level[i].score[j].upload || bests.level[i].time_ms[j].upload ) return 1; return 0; }