Added preliminary support for Retro Achievements leaderboards

This commit is contained in:
Andre Leiradella 2017-02-05 16:23:13 +00:00
parent 0c8ee93e3e
commit 6b9ba1e2b1

View File

@ -82,6 +82,9 @@
#define CHEEVOS_JSON_KEY_CONSOLE_ID 0x071656e5U
#define CHEEVOS_JSON_KEY_TOKEN 0x0e2dbd26U
#define CHEEVOS_JSON_KEY_FLAGS 0x0d2e96b2U
#define CHEEVOS_JSON_KEY_LEADERBOARDS 0xf1247d2dU
#define CHEEVOS_JSON_KEY_MEM 0x0b8807e4U
#define CHEEVOS_JSON_KEY_FORMAT 0xb341208eU
enum
{
@ -241,9 +244,11 @@ typedef struct
typedef struct
{
int in_cheevos;
int in_lboards;
uint32_t field_hash;
unsigned core_count;
unsigned unofficial_count;
unsigned lboard_count;
} cheevos_countud_t;
typedef struct
@ -255,13 +260,15 @@ typedef struct
typedef struct
{
int in_cheevos;
int in_lboards;
int is_console_id;
unsigned core_count;
unsigned unofficial_count;
unsigned lboard_count;
cheevos_field_t *field;
cheevos_field_t id, memaddr, title, desc, points, author;
cheevos_field_t modified, created, badge, flags;
cheevos_field_t modified, created, badge, flags, format;
} cheevos_readud_t;
typedef struct
@ -271,6 +278,32 @@ typedef struct
const uint32_t *ext_hashes;
} cheevos_finder_t;
typedef struct
{
cheevos_var_t var;
unsigned multiplier;
} cheevos_term_t;
typedef struct
{
cheevos_term_t *terms;
unsigned count;
} cheevos_expr_t;
typedef struct
{
unsigned id;
const char *title;
const char *description;
int active;
unsigned last_value;
cheevos_condition_t start;
cheevos_condition_t cancel;
cheevos_condition_t submit;
cheevos_expr_t value;
} cheevos_leaderboard_t;
typedef struct
{
int console_id;
@ -278,20 +311,29 @@ typedef struct
cheevoset_t core;
cheevoset_t unofficial;
cheevos_leaderboard_t *leaderboards;
unsigned lboard_count;
char token[32];
retro_ctx_memory_info_t meminfo[4];
} cheevos_locals_t;
static cheevos_locals_t cheevos_locals =
{
0,
true,
{NULL, 0},
{NULL, 0},
{0}
/* console_id */ 0,
/* core_supports */ true,
/* core */ {NULL, 0},
/* unofficial */ {NULL, 0},
/* leaderboards */ NULL,
/* lboard_count */ 0,
/* token */ {0},
{
/* meminfo[0] */ {NULL, 0, 0},
/* meminfo[1] */ {NULL, 0, 0},
/* meminfo[2] */ {NULL, 0, 0},
/* meminfo[3] */ {NULL, 0, 0}
}
};
bool cheevos_loaded = false;
@ -608,6 +650,43 @@ static void cheevos_post_log_cheevo(const cheevo_t* cheevo)
cheevos_build_memaddr(&cheevo->condition, memaddr, sizeof(memaddr));
RARCH_LOG("CHEEVOS memaddr (computed): %s\n", memaddr);
}
static void cheevos_log_lboard(const cheevos_leaderboard_t* lb)
{
char mem[256];
char* aux;
size_t left;
unsigned i;
RARCH_LOG("CHEEVOS leaderboard %p\n", lb);
RARCH_LOG("CHEEVOS id: %u\n", lb->id);
RARCH_LOG("CHEEVOS title: %s\n", lb->title);
RARCH_LOG("CHEEVOS desc: %s\n", lb->description);
cheevos_build_memaddr(&lb->start, mem, sizeof(mem));
RARCH_LOG("CHEEVOS start: %s\n", mem);
cheevos_build_memaddr(&lb->cancel, mem, sizeof(mem));
RARCH_LOG("CHEEVOS cancel: %s\n", mem);
cheevos_build_memaddr(&lb->submit, mem, sizeof(mem));
RARCH_LOG("CHEEVOS submit: %s\n", mem);
left = sizeof(mem);
aux = mem;
for (i = 0; i < lb->value.count; i++)
{
if (i != 0)
cheevos_add_char(&aux, &left, '_');
cheevos_add_var(&lb->value.terms[i].var, &aux, &left);
cheevos_add_char(&aux, &left, '*');
cheevos_add_uint(&aux, &left, lb->value.terms[i].multiplier);
}
RARCH_LOG("CHEEVOS value: %s\n", mem);
}
#endif
static uint32_t cheevos_djb2(const char* str, size_t length)
@ -655,7 +734,7 @@ static int cheevos_http_get(const char **result, size_t *size,
break;
}
RARCH_ERR("CHEEVOS error getting %s: %s.\n", url, msg);
RARCH_ERR("CHEEVOS error during HTTP GET: %s.\n", msg);
return ret;
}
@ -756,6 +835,7 @@ static int cheevos_count__json_end_array(void *userdata)
{
cheevos_countud_t* ud = (cheevos_countud_t*)userdata;
ud->in_cheevos = 0;
ud->in_lboards = 0;
return 0;
}
@ -767,6 +847,8 @@ static int cheevos_count__json_key(void *userdata,
if (ud->field_hash == CHEEVOS_JSON_KEY_ACHIEVEMENTS)
ud->in_cheevos = 1;
else if (ud->field_hash == CHEEVOS_JSON_KEY_LEADERBOARDS)
ud->in_lboards = 1;
return 0;
}
@ -785,12 +867,15 @@ static int cheevos_count__json_number(void *userdata,
else if (flags == 5)
ud->unofficial_count++; /* Unofficial achievements */
}
else if (ud->in_lboards && ud->field_hash == CHEEVOS_JSON_KEY_ID)
ud->lboard_count++;
return 0;
}
static int cheevos_count_cheevos(const char *json,
unsigned *core_count, unsigned *unofficial_count)
unsigned *core_count, unsigned *unofficial_count,
unsigned *lboard_count)
{
static const jsonsax_handlers_t handlers =
{
@ -813,11 +898,13 @@ static int cheevos_count_cheevos(const char *json,
ud.in_cheevos = 0;
ud.core_count = 0;
ud.unofficial_count = 0;
ud.lboard_count = 0;
res = jsonsax_parse(json, &handlers, (void*)&ud);
*core_count = ud.core_count;
*unofficial_count = ud.unofficial_count;
*lboard_count = ud.lboard_count;
return res;
}
@ -1256,6 +1343,111 @@ static int cheevos_parse_condition(cheevos_condition_t *condition, const char* m
return 0;
}
static void cheevos_free_condition(cheevos_condition_t* condition)
{
unsigned i;
if (condition->condsets)
{
for (i = 0; i < condition->count; i++)
{
free((void*)condition->condsets[i].conds);
}
free((void*)condition->condsets);
}
}
/*****************************************************************************
Parse the Mem field of leaderboards.
*****************************************************************************/
static int cheevos_parse_expression(cheevos_expr_t *expr, const char* mem)
{
const char* aux;
char* end;
unsigned i;
expr->count = 1;
for (aux = mem; *aux != '"'; aux++)
{
expr->count += *aux == '_';
}
expr->terms = (cheevos_term_t*)calloc(expr->count, sizeof(cheevos_term_t));
if (!expr->terms)
return -1;
for (i = 0, aux = mem; i < expr->count; i++)
{
cheevos_parse_var(&expr->terms[i].var, &aux);
if (*aux != '*')
{
free((void*)expr->terms);
return -1;
}
expr->terms[i].multiplier = strtol(aux + 1, &end, 10);
aux = end + 1;
}
return 0;
}
static int cheevos_parse_mem(cheevos_leaderboard_t *lb, const char* mem)
{
lb->start.condsets = NULL;
lb->cancel.condsets = NULL;
lb->submit.condsets = NULL;
lb->value.terms = NULL;
for (;;)
{
if (*mem == 'S' && mem[1] == 'T' && mem[2] == 'A' && mem[3] == ':')
{
if (cheevos_parse_condition(&lb->start, mem + 4))
goto error;
}
else if (*mem == 'C' && mem[1] == 'A' && mem[2] == 'N' && mem[3] == ':')
{
if (cheevos_parse_condition(&lb->cancel, mem + 4))
goto error;
}
else if (*mem == 'S' && mem[1] == 'U' && mem[2] == 'B' && mem[3] == ':')
{
if (cheevos_parse_condition(&lb->submit, mem + 4))
goto error;
}
else if (*mem == 'V' && mem[1] == 'A' && mem[2] == 'L' && mem[3] == ':')
{
if (cheevos_parse_expression(&lb->value, mem + 4))
goto error;
}
else
goto error;
for (mem += 4;; mem++)
{
if (*mem == ':' && mem[1] == ':')
{
mem += 2;
break;
}
else if (*mem == '"')
return 0;
}
}
error:
cheevos_free_condition(&lb->start);
cheevos_free_condition(&lb->cancel);
cheevos_free_condition(&lb->submit);
free((void*)lb->value.terms);
return -1;
}
/*****************************************************************************
Load achievements from a JSON string.
*****************************************************************************/
@ -1317,11 +1509,38 @@ error:
return -1;
}
static int cheevos_new_lboard(cheevos_readud_t *ud)
{
cheevos_leaderboard_t *lboard = cheevos_locals.leaderboards + ud->lboard_count++;
lboard->id = strtol(ud->id.string, NULL, 10);
lboard->title = cheevos_dupstr(&ud->title);
lboard->description = cheevos_dupstr(&ud->desc);
if (!lboard->title || !lboard->description)
goto error;
if (cheevos_parse_mem(lboard, ud->memaddr.string))
goto error;
#ifdef CHEEVOS_VERBOSE
cheevos_log_lboard(lboard);
#endif
return 0;
error:
free((void*)lboard->title);
free((void*)lboard->description);
return -1;
}
static int cheevos_read__json_key( void *userdata,
const char *name, size_t length)
{
cheevos_readud_t *ud = (cheevos_readud_t*)userdata;
uint32_t hash = cheevos_djb2(name, length);
int common = ud->in_cheevos || ud->in_lboards;
ud->field = NULL;
@ -1330,23 +1549,30 @@ static int cheevos_read__json_key( void *userdata,
case CHEEVOS_JSON_KEY_ACHIEVEMENTS:
ud->in_cheevos = 1;
break;
case CHEEVOS_JSON_KEY_LEADERBOARDS:
ud->in_lboards = 1;
break;
case CHEEVOS_JSON_KEY_CONSOLE_ID:
ud->is_console_id = 1;
break;
case CHEEVOS_JSON_KEY_ID:
if (ud->in_cheevos)
if (common)
ud->field = &ud->id;
break;
case CHEEVOS_JSON_KEY_MEMADDR:
if (ud->in_cheevos)
ud->field = &ud->memaddr;
break;
case CHEEVOS_JSON_KEY_MEM:
if (ud->in_lboards)
ud->field = &ud->memaddr;
break;
case CHEEVOS_JSON_KEY_TITLE:
if (ud->in_cheevos)
if (common)
ud->field = &ud->title;
break;
case CHEEVOS_JSON_KEY_DESCRIPTION:
if (ud->in_cheevos)
if (common)
ud->field = &ud->desc;
break;
case CHEEVOS_JSON_KEY_POINTS:
@ -1373,6 +1599,10 @@ static int cheevos_read__json_key( void *userdata,
if (ud->in_cheevos)
ud->field = &ud->flags;
break;
case CHEEVOS_JSON_KEY_FORMAT:
if (ud->in_lboards)
ud->field = &ud->format;
break;
default:
break;
}
@ -1419,6 +1649,8 @@ static int cheevos_read__json_end_object(void *userdata)
if (ud->in_cheevos)
return cheevos_new_cheevo(ud);
else if (ud->in_lboards)
return cheevos_new_lboard(ud);
return 0;
}
@ -1427,6 +1659,7 @@ static int cheevos_read__json_end_array(void *userdata)
{
cheevos_readud_t *ud = (cheevos_readud_t*)userdata;
ud->in_cheevos = 0;
ud->in_lboards = 0;
return 0;
}
@ -1448,7 +1681,7 @@ static int cheevos_parse(const char *json)
NULL
};
unsigned core_count, unofficial_count;
unsigned core_count, unofficial_count, lboard_count;
int res;
cheevos_readud_t ud;
settings_t *settings = config_get_ptr();
@ -1458,7 +1691,8 @@ static int cheevos_parse(const char *json)
return 0;
/* Count the number of achievements in the JSON file. */
res = cheevos_count_cheevos(json, &core_count, &unofficial_count);
res = cheevos_count_cheevos(json, &core_count, &unofficial_count,
&lboard_count);
if (res != JSONSAX_OK)
return -1;
@ -1473,21 +1707,30 @@ static int cheevos_parse(const char *json)
calloc(unofficial_count, sizeof(cheevo_t));
cheevos_locals.unofficial.count = unofficial_count;
if (!cheevos_locals.core.cheevos || !cheevos_locals.unofficial.cheevos)
cheevos_locals.leaderboards = (cheevos_leaderboard_t*)
calloc(lboard_count, sizeof(cheevos_leaderboard_t));
cheevos_locals.lboard_count = lboard_count;
if ( !cheevos_locals.core.cheevos || !cheevos_locals.unofficial.cheevos
|| !cheevos_locals.leaderboards)
{
free((void*)cheevos_locals.core.cheevos);
free((void*)cheevos_locals.unofficial.cheevos);
cheevos_locals.core.count = cheevos_locals.unofficial.count = 0;
free((void*)cheevos_locals.leaderboards);
cheevos_locals.core.count = cheevos_locals.unofficial.count =
cheevos_locals.lboard_count = 0;
return -1;
}
/* Load the achievements. */
ud.in_cheevos = 0;
ud.in_lboards = 0;
ud.is_console_id = 0;
ud.field = NULL;
ud.core_count = 0;
ud.unofficial_count = 0;
ud.lboard_count = 0;
if (jsonsax_parse(json, &handlers, (void*)&ud) != JSONSAX_OK)
goto error;
@ -1535,7 +1778,6 @@ static unsigned cheevos_get_var_value(cheevos_var_t *var)
if ( var->type == CHEEVOS_VAR_TYPE_ADDRESS
|| var->type == CHEEVOS_VAR_TYPE_DELTA_MEM)
{
/* TODO Check with Scott if the bank id is needed */
const uint8_t *memory = cheevos_get_memory(var);
unsigned live_val = 0;
@ -1950,6 +2192,155 @@ static void cheevos_test_cheevo_set(const cheevoset_t *set)
}
}
static int cheevos_test_lboard_condition(const cheevos_condition_t* condition)
{
int dirty_conds = 0;
int reset_conds = 0;
int ret_val = 0;
int ret_val_sub_cond = condition->count == 1;
cheevos_condset_t *condset = condition->condsets;
const cheevos_condset_t *end = condset + condition->count;
if (condset < end)
{
ret_val = cheevos_test_cond_set(condset, &dirty_conds, &reset_conds, 0);
condset++;
}
while (condset < end)
{
ret_val_sub_cond |= cheevos_test_cond_set(condset, &dirty_conds, &reset_conds, 0);
condset++;
}
if (reset_conds)
{
for (condset = condition->condsets; condset < end; condset++)
cheevos_reset_cond_set(condset, 0);
}
return ret_val && ret_val_sub_cond;
}
static unsigned cheevos_expr_value(cheevos_expr_t* expr)
{
cheevos_term_t* term = expr->terms;
unsigned i, value = 0;
for (i = expr->count; i != 0; i--, term++)
{
value += cheevos_get_var_value(&term->var) * term->multiplier;
}
return value;
}
static void cheevos_make_lboard_url(const cheevos_leaderboard_t *lboard,
char* url, size_t url_size)
{
settings_t *settings = config_get_ptr();
char signature[64];
MD5_CTX ctx;
uint8_t hash[16];
hash[0] = '\0';
snprintf(signature, sizeof(signature), "%u%s%u", lboard->id,
settings->cheevos.username, lboard->id);
MD5_Init(&ctx);
MD5_Update(&ctx, (void*)signature, strlen(signature));
MD5_Final(hash, &ctx);
snprintf(
url, url_size,
"http://retroachievements.org/dorequest.php?r=submitlbentry&u=%s&t=%s&i=%u&s=%u"
"&v=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
settings->cheevos.username, cheevos_locals.token, lboard->id,
lboard->last_value,
hash[ 0], hash[ 1], hash[ 2], hash[ 3],
hash[ 4], hash[ 5], hash[ 6], hash[ 7],
hash[ 8], hash[ 9], hash[10], hash[11],
hash[12], hash[13], hash[14], hash[15]
);
url[url_size - 1] = 0;
#ifdef CHEEVOS_LOG_URLS
cheevos_log_url("CHEEVOS url to submit the leaderboard: %s\n", url);
#endif
}
static void cheevos_lboard_submit(void *task_data, void *user_data, const char *error)
{
cheevos_leaderboard_t *lboard = (cheevos_leaderboard_t *)user_data;
if (error == NULL)
{
RARCH_LOG("CHEEVOS submitted leaderboard %u.\n", lboard->id);
}
else
RARCH_ERR("CHEEVOS error submitting leaderboard %u\n", lboard->id);
#if 0
{
char url[256];
url[0] = '\0';
RARCH_ERR("CHEEVOS error submitting leaderboard %u, retrying...\n", lboard->id);
cheevos_make_lboard_url(lboard, url, sizeof(url));
task_push_http_transfer(url, true, NULL, cheevos_lboard_submit, lboard);
}
#endif
}
static void cheevos_test_leaderboards(void)
{
cheevos_leaderboard_t* lboard = cheevos_locals.leaderboards;
unsigned i;
for (i = cheevos_locals.lboard_count; i != 0; i--, lboard++)
{
if (lboard->active)
{
unsigned value = cheevos_expr_value(&lboard->value);
if (value != lboard->last_value)
{
#ifdef CHEEVOS_VERBOSE
RARCH_LOG("CHEEVOS value %s %u\n", lboard->title, value);
#endif
lboard->last_value = value;
}
if (cheevos_test_lboard_condition(&lboard->submit))
{
char url[256];
cheevos_make_lboard_url(lboard, url, sizeof(url));
task_push_http_transfer(url, true, NULL, cheevos_lboard_submit, lboard);
RARCH_LOG("CHEEVOS submit %s\n", lboard->title);
}
if (cheevos_test_lboard_condition(&lboard->cancel))
{
RARCH_LOG("CHEEVOS cancel %s\n", lboard->title);
lboard->active = 0;
}
}
else
{
if (cheevos_test_lboard_condition(&lboard->start))
{
RARCH_LOG("CHEEVOS start %s\n", lboard->title);
lboard->active = 1;
lboard->last_value = (unsigned)-1;
}
}
}
}
/*****************************************************************************
Free the loaded achievements.
*****************************************************************************/
@ -2698,14 +3089,18 @@ void cheevos_reset_game(void)
const cheevo_t *end = cheevo + cheevos_locals.core.count;
for (; cheevo < end; cheevo++)
{
cheevo->last = 1;
}
cheevo = cheevos_locals.unofficial.cheevos;
end = cheevo + cheevos_locals.unofficial.count;
for (; cheevo < end; cheevo++)
{
cheevo->last = 1;
}
}
void cheevos_populate_menu(void *data, bool hardcore)
{
@ -2842,7 +3237,7 @@ bool cheevos_unload(void)
cheevos_free_cheevo_set(&cheevos_locals.core);
cheevos_free_cheevo_set(&cheevos_locals.unofficial);
cheevos_loaded = false;
cheevos_loaded = 0;
return true;
}
@ -2881,9 +3276,7 @@ void cheevos_test(void)
if (settings->cheevos.test_unofficial)
cheevos_test_cheevo_set(&cheevos_locals.unofficial);
#if 0
cheevos_test_leaderboards();
#endif
}
bool cheevos_set_cheats(void)