From bca06698339a3900e99bdd1af7bd170f9c9872b9 Mon Sep 17 00:00:00 2001 From: natinusala Date: Mon, 19 Nov 2018 10:23:55 +0100 Subject: [PATCH] ozone: fix cheevos badges, convert cheevos.c to LF --- cheevos-new/cheevos.c | 2 +- cheevos/cheevos.c | 7476 ++++++++++++++++++++-------------------- menu/drivers/ozone.c | 289 +- record/record_driver.c | 4 +- 4 files changed, 3890 insertions(+), 3881 deletions(-) diff --git a/cheevos-new/cheevos.c b/cheevos-new/cheevos.c index 3539db7938..78d7d0d6ab 100644 --- a/cheevos-new/cheevos.c +++ b/cheevos-new/cheevos.c @@ -1745,7 +1745,7 @@ found: settings_t *settings = config_get_ptr(); if (!( string_is_equal(settings->arrays.menu_driver, "xmb") || - !string_is_equal(settings->arrays.menu_driver, "ozone") + string_is_equal(settings->arrays.menu_driver, "ozone") ) || !settings->bools.cheevos_badges_enable) CORO_RET(); diff --git a/cheevos/cheevos.c b/cheevos/cheevos.c index 134e7e6513..c4f637255f 100644 --- a/cheevos/cheevos.c +++ b/cheevos/cheevos.c @@ -1,3738 +1,3738 @@ -/* RetroArch - A frontend for libretro. - * Copyright (C) 2015-2018 - Andre Leiradella - * - * RetroArch is free software: you can redistribute it and/or modify it under the terms - * of the GNU General Public License as published by the Free Software Found- - * ation, either version 3 of the License, or (at your option) any later version. - * - * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with RetroArch. - * If not, see . - */ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_CONFIG_H -#include "../config.h" -#endif - -#ifdef HAVE_MENU -#include "../menu/menu_driver.h" -#include "../menu/menu_entries.h" -#endif - -#ifdef HAVE_THREADS -#include -#endif - -#include "badges.h" -#include "cheevos.h" -#include "var.h" -#include "cond.h" - -#include "../file_path_special.h" -#include "../paths.h" -#include "../command.h" -#include "../dynamic.h" -#include "../configuration.h" -#include "../performance_counters.h" -#include "../msg_hash.h" -#include "../retroarch.h" -#include "../core.h" - -#include "../network/net_http_special.h" -#include "../tasks/tasks_internal.h" - -#include "../verbosity.h" - -/* Define this macro to prevent cheevos from being deactivated. */ -#undef CHEEVOS_DONT_DEACTIVATE - -/* Define this macro to log URLs (will log the user token). */ -#undef CHEEVOS_LOG_URLS - -/* Define this macro to dump all cheevos' addresses. */ -#undef CHEEVOS_DUMP_ADDRS - -/* Define this macro to remove HTTP timeouts. */ -#undef CHEEVOS_NO_TIMEOUT - -/* Define this macro to load a JSON file from disk instead of downloading - * from retroachievements.org. */ -#undef CHEEVOS_JSON_OVERRIDE - -/* Define this macro with a string to save the JSON file to disk with - * that name. */ -#undef CHEEVOS_SAVE_JSON - -/* Define this macro to have the password and token logged. THIS WILL DISCLOSE - * THE USER'S PASSWORD, TAKE CARE! */ -#undef CHEEVOS_LOG_PASSWORD - -/* Define this macro to log downloaded badge images. */ -#undef CHEEVOS_LOG_BADGES - -/* C89 wants only int values in enums. */ -#define CHEEVOS_JSON_KEY_GAMEID 0xb4960eecU -#define CHEEVOS_JSON_KEY_ACHIEVEMENTS 0x69749ae1U -#define CHEEVOS_JSON_KEY_ID 0x005973f2U -#define CHEEVOS_JSON_KEY_MEMADDR 0x1e76b53fU -#define CHEEVOS_JSON_KEY_TITLE 0x0e2a9a07U -#define CHEEVOS_JSON_KEY_DESCRIPTION 0xe61a1f69U -#define CHEEVOS_JSON_KEY_POINTS 0xca8fce22U -#define CHEEVOS_JSON_KEY_AUTHOR 0xa804edb8U -#define CHEEVOS_JSON_KEY_MODIFIED 0xdcea4fe6U -#define CHEEVOS_JSON_KEY_CREATED 0x3a84721dU -#define CHEEVOS_JSON_KEY_BADGENAME 0x887685d9U -#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 -#define CHEEVOS_JSON_KEY_SUCCESS 0x110461deU -#define CHEEVOS_JSON_KEY_ERROR 0x0d2011cfU - -typedef struct -{ - cheevos_cond_t *conds; - unsigned count; -} cheevos_condset_t; - -typedef struct -{ - cheevos_condset_t *condsets; - unsigned count; -} cheevos_condition_t; - -typedef struct -{ - unsigned id; - const char *title; - const char *description; - const char *author; - const char *badge; - unsigned points; - unsigned dirty; - int active; - int last; - int modified; - - cheevos_condition_t condition; -} cheevo_t; - -typedef struct -{ - cheevo_t *cheevos; - unsigned count; -} cheevoset_t; - -typedef struct -{ - int is_element; - int mode; -} cheevos_deactivate_t; - -typedef struct -{ - unsigned key_hash; - int is_key; - const char *value; - size_t length; -} cheevos_getvalueud_t; - -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 -{ - const char *string; - size_t length; -} cheevos_field_t; - -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, format; -} cheevos_readud_t; - -typedef struct -{ - int label; - const char *name; - const uint32_t *ext_hashes; -} cheevos_finder_t; - -typedef struct -{ - cheevos_var_t var; - double multiplier; - bool compare_next; -} cheevos_term_t; - -typedef struct -{ - cheevos_term_t *terms; - unsigned count; - unsigned compare_count; -} cheevos_expr_t; - -typedef struct -{ - unsigned id; - unsigned format; - const char *title; - const char *description; - int active; - int last_value; - - cheevos_condition_t start; - cheevos_condition_t cancel; - cheevos_condition_t submit; - cheevos_expr_t value; -} cheevos_leaderboard_t; - -typedef struct -{ - retro_task_t* task; -#ifdef HAVE_THREADS - slock_t* task_lock; -#endif - - cheevos_console_t console_id; - bool core_supports; - bool addrs_patched; - int add_buffer; - int add_hits; - - 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; - -typedef struct -{ - uint8_t id[4]; /* NES^Z */ - uint8_t rom_size; - uint8_t vrom_size; - uint8_t rom_type; - uint8_t rom_type2; - uint8_t reserve[8]; -} cheevos_nes_header_t; - -static cheevos_locals_t cheevos_locals = -{ - /* task */ NULL, -#ifdef HAVE_THREADS - /* task_lock */ NULL, -#endif - - /* console_id */ CHEEVOS_CONSOLE_NONE, - /* core_supports */ true, - /* addrs_patched */ false, - /* add_buffer */ 0, - /* add_hits */ 0, - - /* 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; -bool cheevos_hardcore_active = false; -bool cheevos_hardcore_paused = false; -bool cheevos_state_loaded_flag = false; -int cheats_are_enabled = 0; -int cheats_were_enabled = 0; - -#ifdef HAVE_THREADS -#define CHEEVOS_LOCK(l) do { slock_lock(l); } while (0) -#define CHEEVOS_UNLOCK(l) do { slock_unlock(l); } while (0) -#else -#define CHEEVOS_LOCK(l) -#define CHEEVOS_UNLOCK(l) -#endif - -/***************************************************************************** -Supporting functions. -*****************************************************************************/ - -#ifndef CHEEVOS_VERBOSE - -void cheevos_log(const char *fmt, ...) -{ - (void)fmt; -} - -#endif - -static unsigned size_in_megabytes(unsigned val) -{ - return (val * 1024 * 1024); -} - -#ifdef CHEEVOS_LOG_URLS -static void cheevos_log_url(const char* format, const char* url) -{ -#ifdef CHEEVOS_LOG_PASSWORD - CHEEVOS_LOG(format, url); -#else - char copy[256]; - char* aux = NULL; - char* next = NULL; - - if (!string_is_empty(url)) - strlcpy(copy, url, sizeof(copy)); - - aux = strstr(copy, "?p="); - - if (!aux) - aux = strstr(copy, "&p="); - - if (aux) - { - aux += 3; - next = strchr(aux, '&'); - - if (next) - { - do - { - *aux++ = *next++; - }while (next[-1] != 0); - } - else - *aux = 0; - } - - aux = strstr(copy, "?t="); - - if (!aux) - aux = strstr(copy, "&t="); - - if (aux) - { - aux += 3; - next = strchr(aux, '&'); - - if (next) - { - do - { - *aux++ = *next++; - }while (next[-1] != 0); - } - else - *aux = 0; - } - - CHEEVOS_LOG(format, copy); -#endif -} -#endif - -static uint32_t cheevos_djb2(const char* str, size_t length) -{ - const unsigned char *aux = (const unsigned char*)str; - const unsigned char *end = aux + length; - uint32_t hash = 5381; - - while (aux < end) - hash = (hash << 5) + hash + *aux++; - - return hash; -} - -static int cheevos_getvalue__json_key(void *userdata, - const char *name, size_t length) -{ - cheevos_getvalueud_t* ud = (cheevos_getvalueud_t*)userdata; - - if (ud) - ud->is_key = cheevos_djb2(name, length) == ud->key_hash; - return 0; -} - -static int cheevos_getvalue__json_string(void *userdata, - const char *string, size_t length) -{ - cheevos_getvalueud_t* ud = (cheevos_getvalueud_t*)userdata; - - if (ud && ud->is_key) - { - ud->value = string; - ud->length = length; - ud->is_key = 0; - } - - return 0; -} - -static int cheevos_getvalue__json_boolean(void *userdata, int istrue) -{ - cheevos_getvalueud_t* ud = (cheevos_getvalueud_t*)userdata; - - if (ud && ud->is_key) - { - if (istrue) - { - ud->value = "true"; - ud->length = 4; - } - else - { - ud->value = "false"; - ud->length = 5; - } - ud->is_key = 0; - } - - return 0; -} - -static int cheevos_getvalue__json_null(void *userdata) -{ - cheevos_getvalueud_t* ud = (cheevos_getvalueud_t*)userdata; - - if (ud && ud->is_key ) - { - ud->value = "null"; - ud->length = 4; - ud->is_key = 0; - } - - return 0; -} - -static int cheevos_get_value(const char *json, unsigned key_hash, - char *value, size_t length) -{ - static const jsonsax_handlers_t handlers = - { - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - cheevos_getvalue__json_key, - NULL, - cheevos_getvalue__json_string, - cheevos_getvalue__json_string, /* number */ - cheevos_getvalue__json_boolean, - cheevos_getvalue__json_null - }; - - cheevos_getvalueud_t ud; - - ud.key_hash = key_hash; - ud.is_key = 0; - ud.value = NULL; - ud.length = 0; - *value = 0; - - if ((jsonsax_parse(json, &handlers, (void*)&ud) == JSONSAX_OK) - && ud.value && ud.length < length) - { - if (!string_is_empty(ud.value)) - strlcpy(value, ud.value, ud.length + 1); - return 0; - } - - return -1; -} - -/***************************************************************************** -Count number of achievements in a JSON file. -*****************************************************************************/ - -static int cheevos_count__json_end_array(void *userdata) -{ - cheevos_countud_t* ud = (cheevos_countud_t*)userdata; - - if (ud) - { - ud->in_cheevos = 0; - ud->in_lboards = 0; - } - - return 0; -} - -static int cheevos_count__json_key(void *userdata, - const char *name, size_t length) -{ - cheevos_countud_t* ud = (cheevos_countud_t*)userdata; - - if (ud) - { - ud->field_hash = cheevos_djb2(name, length); - 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; -} - -static int cheevos_count__json_number(void *userdata, - const char *number, size_t length) -{ - cheevos_countud_t* ud = (cheevos_countud_t*)userdata; - - if (ud) - { - if (ud->in_cheevos && ud->field_hash == CHEEVOS_JSON_KEY_FLAGS) - { - long flags = strtol(number, NULL, 10); - - if (flags == 3) - ud->core_count++; /* Core achievements */ - 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 *lboard_count) -{ - static const jsonsax_handlers_t handlers = - { - NULL, - NULL, - NULL, - NULL, - NULL, - cheevos_count__json_end_array, - cheevos_count__json_key, - NULL, - NULL, - cheevos_count__json_number, - NULL, - NULL - }; - - int res; - cheevos_countud_t ud; - ud.in_cheevos = 0; - ud.in_lboards = 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; -} - -/***************************************************************************** -Parse the MemAddr field. -*****************************************************************************/ - -static unsigned cheevos_count_cond_sets(const char *memaddr) -{ - cheevos_cond_t cond; - unsigned count = 0; - - for (;;) - { - count++; - - for (;;) - { - cheevos_cond_parse(&cond, &memaddr); - - if (*memaddr != '_') - break; - - memaddr++; - } - - if (*memaddr != 'S') - break; - - memaddr++; - } - - return count; -} - -static int cheevos_parse_condition( - cheevos_condition_t *condition, - const char* memaddr) -{ - if (!condition) - return 0; - - condition->count = cheevos_count_cond_sets(memaddr); - - if (condition->count) - { - unsigned set = 0; - const cheevos_condset_t* end = NULL; - cheevos_condset_t *conds = NULL; - cheevos_condset_t *condset = NULL; - cheevos_condset_t *condsets = (cheevos_condset_t*) - calloc(condition->count, sizeof(cheevos_condset_t)); - - (void)conds; - - if (!condsets) - return -1; - - condition->condsets = condsets; - end = condition->condsets + condition->count; - - for (condset = condition->condsets; condset < end; condset++, set++) - { - condset->count = - cheevos_cond_count_in_set(memaddr, set); - condset->conds = NULL; - - CHEEVOS_LOG("[CHEEVOS]: set %p (index=%u)\n", condset, set); - CHEEVOS_LOG("[CHEEVOS]: conds: %u\n", condset->count); - - if (condset->count) - { - cheevos_cond_t *conds = (cheevos_cond_t*) - calloc(condset->count, sizeof(cheevos_cond_t)); - - if (!conds) - { - while (--condset >= condition->condsets) - { - if ((void*)condset->conds) - free((void*)condset->conds); - } - - return -1; - } - - condset->conds = conds; - cheevos_cond_parse_in_set(condset->conds, memaddr, set); - } - } - } - - return 0; -} - -static void cheevos_free_condition(cheevos_condition_t* condition) -{ - unsigned i; - - if (!condition) - return; - - if (condition->condsets) - { - for (i = 0; i < condition->count; i++) - { - if (condition->condsets[i].conds) - { - free(condition->condsets[i].conds); - condition->condsets[i].conds = NULL; - } - } - - if (condition->condsets) - { - free(condition->condsets); - condition->condsets = NULL; - } - } -} - -/***************************************************************************** -Parse the Mem field of leaderboards. -*****************************************************************************/ - -static int cheevos_parse_expression(cheevos_expr_t *expr, const char* mem) -{ - unsigned i; - const char *aux; - cheevos_term_t *terms = NULL; - char *end = NULL; - - if (!expr) - return -1; - - expr->count = 1; - expr->compare_count = 1; - - for (aux = mem;; aux++) - { - if (*aux == '"' || *aux == ':') - break; - expr->count += *aux == '_'; - } - - if (expr->count > 0) - terms = (cheevos_term_t*) - calloc(expr->count, sizeof(cheevos_term_t)); - - if (!terms) - return -1; - - expr->terms = terms; - - for (i = 0; i < expr->count; i++) - { - expr->terms[i].compare_next = false; - expr->terms[i].multiplier = 1; - } - - for (i = 0, aux = mem; i < expr->count;) - { - cheevos_var_parse(&expr->terms[i].var, &aux); - - if (*aux != '*') - { - /* expression has no multiplier */ - if (*aux == '_') - { - aux++; - i++; - } - else if (*aux == '$') - { - expr->terms[i].compare_next = true; - expr->compare_count++; - aux++; - i++; - } - - /* no multiplier at end of string */ - else if (*aux == '\0' || *aux == '"' || *aux == ',') - return 0; - - /* invalid character in expression */ - else - { - if (expr->terms) - { - free(expr->terms); - expr->terms = NULL; - } - return -1; - } - } - else - { - if (aux[1] == 'h' || aux[1] == 'H') - expr->terms[i].multiplier = (double)strtol(aux + 2, &end, 16); - else - expr->terms[i].multiplier = strtod(aux + 1, &end); - aux = end; - - if (*aux == '$') - { - aux++; - expr->terms[i].compare_next = true; - expr->compare_count++; - } - else - expr->terms[i].compare_next = false; - - aux++; - i++; - } - } - 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; - } - - 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); - if (lb->value.terms) - { - free((void*)lb->value.terms); - lb->value.terms = NULL; - } - return -1; -} - -/***************************************************************************** -Load achievements from a JSON string. -*****************************************************************************/ - -static INLINE const char *cheevos_dupstr(const cheevos_field_t *field) -{ - char *string = (char*)malloc(field->length + 1); - - if (!string) - return NULL; - - memcpy ((void*)string, (void*)field->string, field->length); - string[field->length] = 0; - - return string; -} - -static int cheevos_new_cheevo(cheevos_readud_t *ud) -{ - cheevo_t *cheevo = NULL; - long flags = strtol(ud->flags.string, NULL, 10); - - if (flags == 3) - cheevo = cheevos_locals.core.cheevos + ud->core_count++; - else if (flags == 5) - cheevo = cheevos_locals.unofficial.cheevos + ud->unofficial_count++; - else - return 0; - - cheevo->id = (unsigned)strtol(ud->id.string, NULL, 10); - cheevo->title = cheevos_dupstr(&ud->title); - cheevo->description = cheevos_dupstr(&ud->desc); - cheevo->author = cheevos_dupstr(&ud->author); - cheevo->badge = cheevos_dupstr(&ud->badge); - cheevo->points = (unsigned)strtol(ud->points.string, NULL, 10); - cheevo->dirty = 0; - cheevo->active = CHEEVOS_ACTIVE_SOFTCORE | CHEEVOS_ACTIVE_HARDCORE; - cheevo->last = 1; - cheevo->modified = 0; - - if ( !cheevo->title || - !cheevo->description || - !cheevo->author || - !cheevo->badge) - goto error; - - if (cheevos_parse_condition(&cheevo->condition, ud->memaddr.string)) - goto error; - - return 0; - -error: - if (cheevo->title) - { - free((void*)cheevo->title); - cheevo->title = NULL; - } - if (cheevo->description) - { - free((void*)cheevo->description); - cheevo->description = NULL; - } - if (cheevo->author) - { - free((void*)cheevo->author); - cheevo->author = NULL; - } - if (cheevo->badge) - { - free((void*)cheevo->badge); - cheevo->badge = NULL; - } - return -1; -} - -/***************************************************************************** -Helper functions for displaying leaderboard values. -*****************************************************************************/ - -static void cheevos_format_value(const unsigned value, const unsigned type, - char* formatted_value, size_t formatted_size) -{ - unsigned mins, secs, millis; - - switch(type) - { - case CHEEVOS_FORMAT_VALUE: - snprintf(formatted_value, formatted_size, "%u", value); - break; - - case CHEEVOS_FORMAT_SCORE: - snprintf(formatted_value, formatted_size, - "%06upts", value); - break; - - case CHEEVOS_FORMAT_FRAMES: - mins = value / 3600; - secs = (value % 3600) / 60; - millis = (int) (value % 60) * (10.00 / 6.00); - snprintf(formatted_value, formatted_size, - "%02u:%02u.%02u", mins, secs, millis); - break; - - case CHEEVOS_FORMAT_MILLIS: - mins = value / 6000; - secs = (value % 6000) / 100; - millis = (int) (value % 100); - snprintf(formatted_value, formatted_size, - "%02u:%02u.%02u", mins, secs, millis); - break; - - case CHEEVOS_FORMAT_SECS: - mins = value / 60; - secs = value % 60; - snprintf(formatted_value, formatted_size, - "%02u:%02u", mins, secs); - break; - - default: - snprintf(formatted_value, formatted_size, - "%u (?)", value); - } -} - -unsigned cheevos_parse_format(cheevos_field_t* format) -{ - /* Most likely */ - if (strncmp(format->string, "VALUE", format->length) == 0) - return CHEEVOS_FORMAT_VALUE; - else if (strncmp(format->string, "TIME", format->length) == 0) - return CHEEVOS_FORMAT_FRAMES; - else if (strncmp(format->string, "SCORE", format->length) == 0) - return CHEEVOS_FORMAT_SCORE; - - /* Less likely */ - else if (strncmp(format->string, "MILLISECS", format->length) == 0) - return CHEEVOS_FORMAT_MILLIS; - else if (strncmp(format->string, "TIMESECS", format->length) == 0) - return CHEEVOS_FORMAT_SECS; - - /* Rare (RPS only) */ - else if (strncmp(format->string, "POINTS", format->length) == 0) - return CHEEVOS_FORMAT_SCORE; - else if (strncmp(format->string, "FRAMES", format->length) == 0) - return CHEEVOS_FORMAT_FRAMES; - else - return CHEEVOS_FORMAT_OTHER; -} - -static int cheevos_new_lboard(cheevos_readud_t *ud) -{ - cheevos_leaderboard_t *lboard = NULL; - cheevos_leaderboard_t *ldb = cheevos_locals.leaderboards; - - if (!ldb || !ud) - return -1; - - lboard = ldb + ud->lboard_count++; - - lboard->id = (unsigned)strtol(ud->id.string, NULL, 10); - lboard->format = cheevos_parse_format(&ud->format); - 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; - - return 0; - -error: - if ((void*)lboard->title) - free((void*)lboard->title); - if ((void*)lboard->description) - 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; - - if (ud) - { - int common = ud->in_cheevos || ud->in_lboards; - uint32_t hash = cheevos_djb2(name, length); - ud->field = NULL; - - switch (hash) - { - 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 (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 (common) - ud->field = &ud->title; - break; - case CHEEVOS_JSON_KEY_DESCRIPTION: - if (common) - ud->field = &ud->desc; - break; - case CHEEVOS_JSON_KEY_POINTS: - if (ud->in_cheevos) - ud->field = &ud->points; - break; - case CHEEVOS_JSON_KEY_AUTHOR: - if (ud->in_cheevos) - ud->field = &ud->author; - break; - case CHEEVOS_JSON_KEY_MODIFIED: - if (ud->in_cheevos) - ud->field = &ud->modified; - break; - case CHEEVOS_JSON_KEY_CREATED: - if (ud->in_cheevos) - ud->field = &ud->created; - break; - case CHEEVOS_JSON_KEY_BADGENAME: - if (ud->in_cheevos) - ud->field = &ud->badge; - break; - case CHEEVOS_JSON_KEY_FLAGS: - 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; - } - } - - return 0; -} - -static int cheevos_read__json_string(void *userdata, - const char *string, size_t length) -{ - cheevos_readud_t *ud = (cheevos_readud_t*)userdata; - - if (ud && ud->field) - { - ud->field->string = string; - ud->field->length = length; - } - - return 0; -} - -static int cheevos_read__json_number(void *userdata, - const char *number, size_t length) -{ - cheevos_readud_t *ud = (cheevos_readud_t*)userdata; - - if (ud) - { - if (ud->field) - { - ud->field->string = number; - ud->field->length = length; - } - else if (ud->is_console_id) - { - cheevos_locals.console_id = (cheevos_console_t) - strtol(number, NULL, 10); - ud->is_console_id = 0; - } - } - - return 0; -} - -static int cheevos_read__json_end_object(void *userdata) -{ - cheevos_readud_t *ud = (cheevos_readud_t*)userdata; - - if (ud) - { - if (ud->in_cheevos) - return cheevos_new_cheevo(ud); - if (ud->in_lboards) - return cheevos_new_lboard(ud); - } - - return 0; -} - -static int cheevos_read__json_end_array(void *userdata) -{ - cheevos_readud_t *ud = (cheevos_readud_t*)userdata; - - if (ud) - { - ud->in_cheevos = 0; - ud->in_lboards = 0; - } - - return 0; -} - -static int cheevos_parse(const char *json) -{ - static const jsonsax_handlers_t handlers = - { - NULL, - NULL, - NULL, - cheevos_read__json_end_object, - NULL, - cheevos_read__json_end_array, - cheevos_read__json_key, - NULL, - cheevos_read__json_string, - cheevos_read__json_number, - NULL, - NULL - }; - - cheevos_readud_t ud; - unsigned core_count, unofficial_count, lboard_count; - /* Count the number of achievements in the JSON file. */ - int res = cheevos_count_cheevos(json, &core_count, &unofficial_count, - &lboard_count); - - if (res != JSONSAX_OK) - return -1; - - /* Allocate the achievements. */ - - cheevos_locals.core.cheevos = (cheevo_t*) - calloc(core_count, sizeof(cheevo_t)); - cheevos_locals.core.count = core_count; - - cheevos_locals.unofficial.cheevos = (cheevo_t*) - calloc(unofficial_count, sizeof(cheevo_t)); - cheevos_locals.unofficial.count = unofficial_count; - - 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) - { - if ((void*)cheevos_locals.core.cheevos) - free((void*)cheevos_locals.core.cheevos); - if ((void*)cheevos_locals.unofficial.cheevos) - free((void*)cheevos_locals.unofficial.cheevos); - if ((void*)cheevos_locals.leaderboards) - 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; - - return 0; - -error: - cheevos_unload(); - - return -1; -} - -/***************************************************************************** -Test all the achievements (call once per frame). -*****************************************************************************/ - -static int cheevos_test_condition(cheevos_cond_t *cond) -{ - unsigned sval = 0; - unsigned tval = 0; - - if (!cond) - return 0; - - sval = cheevos_var_get_value(&cond->source) + - cheevos_locals.add_buffer; - tval = cheevos_var_get_value(&cond->target); - - switch (cond->op) - { - case CHEEVOS_COND_OP_EQUALS: - return (sval == tval); - case CHEEVOS_COND_OP_LESS_THAN: - return (sval < tval); - case CHEEVOS_COND_OP_LESS_THAN_OR_EQUAL: - return (sval <= tval); - case CHEEVOS_COND_OP_GREATER_THAN: - return (sval > tval); - case CHEEVOS_COND_OP_GREATER_THAN_OR_EQUAL: - return (sval >= tval); - case CHEEVOS_COND_OP_NOT_EQUAL_TO: - return (sval != tval); - default: - break; - } - - return 1; -} - -static int cheevos_test_pause_cond_set(const cheevos_condset_t *condset, - int *dirty_conds, int *reset_conds, int process_pause) -{ - int cond_valid = 0; - int set_valid = 1; /* must start true so AND logic works */ - cheevos_cond_t *cond = NULL; - const cheevos_cond_t *end = condset->conds + condset->count; - - cheevos_locals.add_buffer = 0; - cheevos_locals.add_hits = 0; - - for (cond = condset->conds; cond < end; cond++) - { - if (cond->pause != process_pause) - continue; - - if (cond->type == CHEEVOS_COND_TYPE_ADD_SOURCE) - { - cheevos_locals.add_buffer += cheevos_var_get_value(&cond->source); - continue; - } - - if (cond->type == CHEEVOS_COND_TYPE_SUB_SOURCE) - { - cheevos_locals.add_buffer -= cheevos_var_get_value(&cond->source); - continue; - } - - if (cond->type == CHEEVOS_COND_TYPE_ADD_HITS) - { - if (cheevos_test_condition(cond)) - { - cond->curr_hits++; - *dirty_conds = 1; - } - - cheevos_locals.add_hits += cond->curr_hits; - continue; - } - - /* always evaluate the condition to ensure delta values get tracked correctly */ - cond_valid = cheevos_test_condition(cond); - - /* if the condition has a target hit count that has already been met, - * it's automatically true, even if not currently true. */ - if ( (cond->req_hits != 0) && - (cond->curr_hits + cheevos_locals.add_hits) >= cond->req_hits) - { - cond_valid = 1; - } - else if (cond_valid) - { - cond->curr_hits++; - *dirty_conds = 1; - - /* Process this logic, if this condition is true: */ - if (cond->req_hits == 0) - ; /* Not a hit-based requirement: ignore any additional logic! */ - else if ((cond->curr_hits + cheevos_locals.add_hits) < cond->req_hits) - cond_valid = 0; /* HitCount target has not yet been met, condition is not yet valid. */ - } - - cheevos_locals.add_buffer = 0; - cheevos_locals.add_hits = 0; - - if (cond->type == CHEEVOS_COND_TYPE_PAUSE_IF) - { - /* as soon as we find a PauseIf that evaluates to true, - * stop processing the rest of the group. */ - if (cond_valid) - return 1; - - /* if we make it to the end of the function, make sure we are - * indicating nothing matched. if we do find a later PauseIf match, - * it'll automatically return true via the previous condition. */ - set_valid = 0; - - if (cond->req_hits == 0) - { - /* PauseIf didn't evaluate true, and doesn't have a HitCount, - * reset the HitCount to indicate the condition didn't match. */ - if (cond->curr_hits != 0) - { - cond->curr_hits = 0; - *dirty_conds = 1; - } - } - else - { - /* PauseIf has a HitCount that hasn't been met, ignore it for now. */ - } - } - else if (cond->type == CHEEVOS_COND_TYPE_RESET_IF) - { - if (cond_valid) - { - *reset_conds = 1; /* Resets all hits found so far */ - set_valid = 0; /* Cannot be valid if we've hit a reset condition. */ - } - } - else /* Sequential or non-sequential? */ - set_valid &= cond_valid; - } - - return set_valid; -} - -static int cheevos_test_cond_set(const cheevos_condset_t *condset, - int *dirty_conds, int *reset_conds) -{ - int in_pause = 0; - int has_pause = 0; - cheevos_cond_t *cond = NULL; - - if (!condset) - return 1; /* important: empty group must evaluate true */ - - /* the ints below are used for Pause conditions and their dependent AddSource/AddHits. */ - - /* this loop needs to go backwards to check AddSource/AddHits */ - cond = condset->conds + condset->count - 1; - for (; cond >= condset->conds; cond--) - { - if (cond->type == CHEEVOS_COND_TYPE_PAUSE_IF) - { - has_pause = 1; - in_pause = 1; - cond->pause = 1; - } - else if (cond->type == CHEEVOS_COND_TYPE_ADD_SOURCE || - cond->type == CHEEVOS_COND_TYPE_SUB_SOURCE || - cond->type == CHEEVOS_COND_TYPE_ADD_HITS) - { - cond->pause = in_pause; - } - else - { - in_pause = 0; - cond->pause = 0; - } - } - - if (has_pause) - { /* one or more Pause conditions exists, if any of them are true, - * stop processing this group. */ - if (cheevos_test_pause_cond_set(condset, dirty_conds, reset_conds, 1)) - return 0; - } - - /* process the non-Pause conditions to see if the group is true */ - return cheevos_test_pause_cond_set(condset, dirty_conds, reset_conds, 0); -} - -static int cheevos_reset_cond_set(cheevos_condset_t *condset, int deltas) -{ - int dirty = 0; - const cheevos_cond_t *end = NULL; - - if (!condset) - return 0; - - end = condset->conds + condset->count; - - if (deltas) - { - cheevos_cond_t *cond = NULL; - for (cond = condset->conds; cond < end; cond++) - { - dirty |= cond->curr_hits != 0; - - cond->curr_hits = 0; - - cond->source.previous = cond->source.value; - cond->target.previous = cond->target.value; - } - } - else - { - cheevos_cond_t *cond = NULL; - for (cond = condset->conds; cond < end; cond++) - { - dirty |= cond->curr_hits != 0; - cond->curr_hits = 0; - } - } - - return dirty; -} - -static int cheevos_test_cheevo(cheevo_t *cheevo) -{ - int dirty_conds = 0; - int reset_conds = 0; - int ret_val = 0; - int ret_val_sub_cond = 0; - cheevos_condset_t *condset = NULL; - cheevos_condset_t *end = NULL; - - if (!cheevo) - return 0; - - ret_val_sub_cond = cheevo->condition.count == 1; - condset = cheevo->condition.condsets; - - if (!condset) - return 0; - - end = condset + cheevo->condition.count; - - if (condset < end) - { - ret_val = cheevos_test_cond_set(condset, &dirty_conds, &reset_conds); - condset++; - } - - while (condset < end) - { - ret_val_sub_cond |= cheevos_test_cond_set( - condset, &dirty_conds, &reset_conds); - condset++; - } - - if (dirty_conds) - cheevo->dirty |= CHEEVOS_DIRTY_CONDITIONS; - - if (reset_conds) - { - int dirty = 0; - - for (condset = cheevo->condition.condsets; condset < end; condset++) - dirty |= cheevos_reset_cond_set(condset, 0); - - if (dirty) - cheevo->dirty |= CHEEVOS_DIRTY_CONDITIONS; - } - - return (ret_val && ret_val_sub_cond); -} - -static void cheevos_url_encode(const char *str, char *encoded, size_t len) -{ - if (!str) - return; - - while (*str) - { - if ( isalnum((unsigned char)*str) || *str == '-' - || *str == '_' || *str == '.' - || *str == '~') - { - if (len >= 2) - { - *encoded++ = *str++; - len--; - } - else - break; - } - else - { - if (len >= 4) - { - snprintf(encoded, len, "%%%02x", (uint8_t)*str); - encoded += 3; - str++; - len -= 3; - } - else - break; - } - } - - *encoded = 0; -} - -static void cheevos_make_unlock_url(const cheevo_t *cheevo, - char* url, size_t url_size) -{ - settings_t *settings = config_get_ptr(); - - if (!settings) - return; - - snprintf( - url, url_size, - "http://retroachievements.org/dorequest.php?r=awardachievement&u=%s&t=%s&a=%u&h=%d", - settings->arrays.cheevos_username, - cheevos_locals.token, - cheevo->id, - settings->bools.cheevos_hardcore_mode_enable && !cheevos_hardcore_paused ? 1 : 0 - ); - - url[url_size - 1] = 0; - -#ifdef CHEEVOS_LOG_URLS - cheevos_log_url("[CHEEVOS]: url to award the cheevo: %s\n", url); -#endif -} - -static void cheevos_unlocked(void *task_data, void *user_data, - const char *error) -{ - cheevo_t *cheevo = (cheevo_t *)user_data; - - if (!error) - { - CHEEVOS_LOG("[CHEEVOS]: awarded achievement %u.\n", cheevo->id); - } - else - { - char url[256]; - url[0] = '\0'; - - CHEEVOS_ERR("[CHEEVOS]: error awarding achievement %u, retrying...\n", cheevo->id); - - cheevos_make_unlock_url(cheevo, url, sizeof(url)); - task_push_http_transfer(url, true, NULL, cheevos_unlocked, cheevo); - } -} - -static void cheevos_test_cheevo_set(const cheevoset_t *set) -{ - settings_t *settings = config_get_ptr(); - int mode = CHEEVOS_ACTIVE_SOFTCORE; - cheevo_t *cheevo = NULL; - const cheevo_t *end = NULL; - - if (!set) - return; - - end = set->cheevos + set->count; - - if (settings && settings->bools.cheevos_hardcore_mode_enable && !cheevos_hardcore_paused) - mode = CHEEVOS_ACTIVE_HARDCORE; - - for (cheevo = set->cheevos; cheevo < end; cheevo++) - { - if (cheevo->active & mode) - { - int valid = cheevos_test_cheevo(cheevo); - - if (cheevo->last) - { - cheevos_condset_t* condset = cheevo->condition.condsets; - const cheevos_condset_t* end = cheevo->condition.condsets - + cheevo->condition.count; - - for (; condset < end; condset++) - cheevos_reset_cond_set(condset, 0); - } - else if (valid) - { - char msg[256]; - char url[256]; - msg[0] = url[0] = '\0'; - - cheevo->active &= ~mode; - - if (mode == CHEEVOS_ACTIVE_HARDCORE) - cheevo->active &= ~CHEEVOS_ACTIVE_SOFTCORE; - - CHEEVOS_LOG("[CHEEVOS]: awarding cheevo %u: %s (%s).\n", - cheevo->id, cheevo->title, cheevo->description); - - snprintf(msg, sizeof(msg), "Achievement Unlocked: %s", - cheevo->title); - msg[sizeof(msg) - 1] = 0; - runloop_msg_queue_push(msg, 0, 2 * 60, false); - runloop_msg_queue_push(cheevo->description, 0, 3 * 60, false); - - cheevos_make_unlock_url(cheevo, url, sizeof(url)); - task_push_http_transfer(url, true, NULL, - cheevos_unlocked, cheevo); - - if (settings && settings->bools.cheevos_auto_screenshot) - { - char shotname[256]; - - snprintf(shotname, sizeof(shotname), "%s/%s-cheevo-%u", - settings->paths.directory_screenshot, - path_basename(path_get(RARCH_PATH_BASENAME)), - cheevo->id); - shotname[sizeof(shotname) - 1] = '\0'; - - if (take_screenshot(shotname, true, - video_driver_cached_frame_has_valid_framebuffer(), false, true)) - CHEEVOS_LOG("[CHEEVOS]: got a screenshot for cheevo %u\n", cheevo->id); - else - CHEEVOS_LOG("[CHEEVOS]: failed to get screenshot for cheevo %u\n", cheevo->id); - } - } - - cheevo->last = valid; - } - } -} - -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 = 0; - cheevos_condset_t *condset = NULL; - const cheevos_condset_t *end = NULL; - - if (!condition) - return 0; - - ret_val_sub_cond = condition->count == 1; - condset = condition->condsets; - end = condset + condition->count; - - if (condset < end) - { - ret_val = cheevos_test_cond_set( - condset, &dirty_conds, &reset_conds); - condset++; - } - - while (condset < end) - { - ret_val_sub_cond |= cheevos_test_cond_set( - condset, &dirty_conds, &reset_conds); - 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 int cheevos_expr_value(cheevos_expr_t* expr) -{ - unsigned i; - int values[16]; - /* Separate possible values with '$' operator, submit the largest */ - unsigned current_value = 0; - cheevos_term_t* term = NULL; - - if (!expr) - return 0; - - term = expr->terms; - - if (!term) - return 0; - - if (expr->compare_count >= ARRAY_SIZE(values)) - { - CHEEVOS_ERR("[CHEEVOS]: too many values in the leaderboard expression: %u\n", expr->compare_count); - return 0; - } - - memset(values, 0, sizeof values); - - for (i = expr->count; i != 0; i--, term++) - { - if (current_value >= ARRAY_SIZE(values)) - { - CHEEVOS_ERR("[CHEEVOS]: too many values in the leaderboard expression: %u\n", current_value); - return 0; - } - - values[current_value] += - cheevos_var_get_value(&term->var) * term->multiplier; - - if (term->compare_next) - current_value++; - } - - if (expr->compare_count > 1) - { - unsigned j; - int maximum = values[0]; - - for (j = 1; j < expr->compare_count; j++) - maximum = values[j] > maximum ? values[j] : maximum; - - return maximum; - } - else - return values[0]; -} - -static void cheevos_make_lboard_url(const cheevos_leaderboard_t *lboard, - char* url, size_t url_size) -{ - MD5_CTX ctx; - uint8_t hash[16]; - char signature[64]; - settings_t *settings = config_get_ptr(); - - hash[0] = '\0'; - - snprintf(signature, sizeof(signature), "%u%s%u", lboard->id, - settings->arrays.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=%d" - "&v=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", - settings->arrays.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 (!lboard) - return; - - if (!error) - { - CHEEVOS_ERR("[CHEEVOS]: error submitting leaderboard %u\n", lboard->id); - return; - } - - CHEEVOS_LOG("[CHEEVOS]: submitted leaderboard %u.\n", lboard->id); -} - -static void cheevos_test_leaderboards(void) -{ - unsigned i; - cheevos_leaderboard_t* lboard = cheevos_locals.leaderboards; - - if (!lboard) - return; - - for (i = cheevos_locals.lboard_count; i != 0; i--, lboard++) - { - if (lboard->active) - { - int value = cheevos_expr_value(&lboard->value); - - if (value != lboard->last_value) - { - CHEEVOS_LOG("[CHEEVOS]: value lboard %s %u\n", - lboard->title, value); - lboard->last_value = value; - } - - if (cheevos_test_lboard_condition(&lboard->submit)) - { - lboard->active = 0; - - /* failsafe for improper LBs */ - if (value == 0) - { - CHEEVOS_LOG("[CHEEVOS]: error: lboard %s tried to submit 0\n", - lboard->title); - runloop_msg_queue_push("Leaderboard attempt cancelled!", - 0, 2 * 60, false); - } - else - { - char url[256]; - char msg[256]; - char formatted_value[16]; - - cheevos_make_lboard_url(lboard, url, sizeof(url)); - task_push_http_transfer(url, true, NULL, - cheevos_lboard_submit, lboard); - CHEEVOS_LOG("[CHEEVOS]: submit lboard %s\n", lboard->title); - - cheevos_format_value(value, lboard->format, - formatted_value, sizeof(formatted_value)); - snprintf(msg, sizeof(msg), "Submitted %s for %s", - formatted_value, lboard->title); - msg[sizeof(msg) - 1] = 0; - runloop_msg_queue_push(msg, 0, 2 * 60, false); - } - } - - if (cheevos_test_lboard_condition(&lboard->cancel)) - { - CHEEVOS_LOG("[CHEEVOS]: cancel lboard %s\n", lboard->title); - lboard->active = 0; - runloop_msg_queue_push("Leaderboard attempt cancelled!", - 0, 2 * 60, false); - } - } - else - { - if (cheevos_test_lboard_condition(&lboard->start)) - { - char msg[256]; - - CHEEVOS_LOG("[CHEEVOS]: start lboard %s\n", lboard->title); - lboard->active = 1; - lboard->last_value = -1; - - snprintf(msg, sizeof(msg), - "Leaderboard Active: %s", lboard->title); - msg[sizeof(msg) - 1] = 0; - runloop_msg_queue_push(msg, 0, 2 * 60, false); - runloop_msg_queue_push(lboard->description, 0, 3*60, false); - } - } - } -} - -/***************************************************************************** -Free the loaded achievements. -*****************************************************************************/ - -static void cheevos_free_condset(const cheevos_condset_t *set) -{ - if (set && set->conds) - free((void*)set->conds); -} - -static void cheevos_free_cheevo(const cheevo_t *cheevo) -{ - if (!cheevo) - return; - - if (cheevo->title) - free((void*)cheevo->title); - if (cheevo->description) - free((void*)cheevo->description); - if (cheevo->author) - free((void*)cheevo->author); - if (cheevo->badge) - free((void*)cheevo->badge); - cheevos_free_condset(cheevo->condition.condsets); -} - -static void cheevos_free_cheevo_set(const cheevoset_t *set) -{ - const cheevo_t *cheevo = NULL; - const cheevo_t *end = NULL; - - if (!set) - return; - - cheevo = set->cheevos; - end = cheevo + set->count; - - while (cheevo < end) - cheevos_free_cheevo(cheevo++); - - if (set->cheevos) - free((void*)set->cheevos); -} - -#ifndef CHEEVOS_DONT_DEACTIVATE -static int cheevos_deactivate__json_index(void *userdata, unsigned int index) -{ - cheevos_deactivate_t *ud = (cheevos_deactivate_t*)userdata; - - if (ud) - ud->is_element = 1; - - return 0; -} - -static int cheevos_deactivate__json_number(void *userdata, - const char *number, size_t length) -{ - long id; - int found; - cheevo_t* cheevo = NULL; - const cheevo_t* end = NULL; - cheevos_deactivate_t *ud = (cheevos_deactivate_t*)userdata; - - if (ud && ud->is_element) - { - ud->is_element = 0; - id = strtol(number, NULL, 10); - found = 0; - cheevo = cheevos_locals.core.cheevos; - end = cheevo + cheevos_locals.core.count; - - for (; cheevo < end; cheevo++) - { - if (cheevo->id == (unsigned)id) - { - cheevo->active &= ~ud->mode; - found = 1; - break; - } - } - - if (!found) - { - cheevo = cheevos_locals.unofficial.cheevos; - end = cheevo + cheevos_locals.unofficial.count; - - for (; cheevo < end; cheevo++) - { - if (cheevo->id == (unsigned)id) - { - cheevo->active &= ~ud->mode; - found = 1; - break; - } - } - } - - if (found) - CHEEVOS_LOG("[CHEEVOS]: deactivated unlocked cheevo %u (%s).\n", - cheevo->id, cheevo->title); - else - CHEEVOS_ERR("[CHEEVOS]: unknown cheevo to deactivate: %u.\n", id); - } - - return 0; -} - -static int cheevos_deactivate_unlocks(const char* json, unsigned mode) -{ - static const jsonsax_handlers_t handlers = - { - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - cheevos_deactivate__json_index, - NULL, - cheevos_deactivate__json_number, - NULL, - NULL - }; - - cheevos_deactivate_t ud; - - ud.is_element = 0; - ud.mode = mode; - return jsonsax_parse(json, &handlers, (void*)&ud) != JSONSAX_OK; -} -#endif - -void cheevos_reset_game(void) -{ - cheevo_t *end = NULL; - cheevo_t *cheevo = cheevos_locals.core.cheevos; - - if (!cheevo) - return; - - 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) -{ -#ifdef HAVE_MENU - unsigned i = 0; - unsigned items_found = 0; - settings_t *settings = config_get_ptr(); - menu_displaylist_info_t *info = (menu_displaylist_info_t*)data; - cheevo_t *end = NULL; - cheevo_t *cheevo = cheevos_locals.core.cheevos; - end = cheevo + cheevos_locals.core.count; - - if(settings->bools.cheevos_enable && settings->bools.cheevos_hardcore_mode_enable - && cheevos_loaded) - { - if (!cheevos_hardcore_paused) - menu_entries_append_enum(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_PAUSE), - msg_hash_to_str(MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE), - MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE, - MENU_SETTING_ACTION_PAUSE_ACHIEVEMENTS, 0, 0); - else - menu_entries_append_enum(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_RESUME), - msg_hash_to_str(MENU_ENUM_LABEL_ACHIEVEMENT_RESUME), - MENU_ENUM_LABEL_ACHIEVEMENT_RESUME, - MENU_SETTING_ACTION_RESUME_ACHIEVEMENTS, 0, 0); - } - - if (cheevo) - { - for (i = 0; cheevo < end; i++, cheevo++) - { - if (!(cheevo->active & CHEEVOS_ACTIVE_HARDCORE)) - { - menu_entries_append_enum(info->list, cheevo->title, - cheevo->description, - MENU_ENUM_LABEL_CHEEVOS_UNLOCKED_ENTRY_HARDCORE, - MENU_SETTINGS_CHEEVOS_START + i, 0, 0); - set_badge_info(&badges_ctx, i, cheevo->badge, - (cheevo->active & CHEEVOS_ACTIVE_HARDCORE)); - } - else if (!(cheevo->active & CHEEVOS_ACTIVE_SOFTCORE)) - { - menu_entries_append_enum(info->list, cheevo->title, - cheevo->description, - MENU_ENUM_LABEL_CHEEVOS_UNLOCKED_ENTRY, - MENU_SETTINGS_CHEEVOS_START + i, 0, 0); - set_badge_info(&badges_ctx, i, cheevo->badge, - (cheevo->active & CHEEVOS_ACTIVE_SOFTCORE)); - } - else - { - menu_entries_append_enum(info->list, cheevo->title, - cheevo->description, - MENU_ENUM_LABEL_CHEEVOS_LOCKED_ENTRY, - MENU_SETTINGS_CHEEVOS_START + i, 0, 0); - set_badge_info(&badges_ctx, i, cheevo->badge, - (cheevo->active & CHEEVOS_ACTIVE_SOFTCORE)); - } - items_found++; - } - } - - cheevo = cheevos_locals.unofficial.cheevos; - - if (cheevo && settings->bools.cheevos_test_unofficial) - { - end = cheevo + cheevos_locals.unofficial.count; - - for (i = items_found; cheevo < end; i++, cheevo++) - { - if (!(cheevo->active & CHEEVOS_ACTIVE_HARDCORE)) - { - menu_entries_append_enum(info->list, cheevo->title, - cheevo->description, - MENU_ENUM_LABEL_CHEEVOS_UNLOCKED_ENTRY_HARDCORE, - MENU_SETTINGS_CHEEVOS_START + i, 0, 0); - set_badge_info(&badges_ctx, i, cheevo->badge, - (cheevo->active & CHEEVOS_ACTIVE_HARDCORE)); - } - else if (!(cheevo->active & CHEEVOS_ACTIVE_SOFTCORE)) - { - menu_entries_append_enum(info->list, cheevo->title, - cheevo->description, - MENU_ENUM_LABEL_CHEEVOS_UNLOCKED_ENTRY, - MENU_SETTINGS_CHEEVOS_START + i, 0, 0); - set_badge_info(&badges_ctx, i, cheevo->badge, - (cheevo->active & CHEEVOS_ACTIVE_SOFTCORE)); - } - else - { - menu_entries_append_enum(info->list, cheevo->title, - cheevo->description, - MENU_ENUM_LABEL_CHEEVOS_LOCKED_ENTRY, - MENU_SETTINGS_CHEEVOS_START + i, 0, 0); - set_badge_info(&badges_ctx, i, cheevo->badge, - (cheevo->active & CHEEVOS_ACTIVE_SOFTCORE)); - } - items_found++; - } - } - - if (items_found == 0) - { - menu_entries_append_enum(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_ACHIEVEMENTS_TO_DISPLAY), - msg_hash_to_str(MENU_ENUM_LABEL_NO_ACHIEVEMENTS_TO_DISPLAY), - MENU_ENUM_LABEL_NO_ACHIEVEMENTS_TO_DISPLAY, - FILE_TYPE_NONE, 0, 0); - } -#endif -} - -bool cheevos_get_description(cheevos_ctx_desc_t *desc) -{ - if (!desc) - return false; - - if (cheevos_loaded) - { - cheevo_t *cheevos = cheevos_locals.core.cheevos; - - if (!cheevos) - return false; - - if (desc->idx >= cheevos_locals.core.count) - { - cheevos = cheevos_locals.unofficial.cheevos; - desc->idx -= cheevos_locals.core.count; - } - - if (!string_is_empty(cheevos[desc->idx].description)) - strlcpy(desc->s, cheevos[desc->idx].description, desc->len); - } - else - *desc->s = 0; - - return true; -} - -bool cheevos_apply_cheats(bool *data_bool) -{ - cheats_are_enabled = *data_bool; - cheats_were_enabled |= cheats_are_enabled; - - return true; -} - -bool cheevos_unload(void) -{ - bool running; - CHEEVOS_LOCK(cheevos_locals.task_lock); - running = cheevos_locals.task != NULL; - CHEEVOS_UNLOCK(cheevos_locals.task_lock); - - if (running) - { - CHEEVOS_LOG("[CHEEVOS]: Asked the load thread to terminate\n"); - task_queue_cancel_task(cheevos_locals.task); - -#ifdef HAVE_THREADS - do - { - CHEEVOS_LOCK(cheevos_locals.task_lock); - running = cheevos_locals.task != NULL; - CHEEVOS_UNLOCK(cheevos_locals.task_lock); - } - while (running); -#endif - } - - if (cheevos_loaded) - { - cheevos_free_cheevo_set(&cheevos_locals.core); - cheevos_free_cheevo_set(&cheevos_locals.unofficial); - } - - cheevos_locals.core.cheevos = NULL; - cheevos_locals.unofficial.cheevos = NULL; - cheevos_locals.core.count = 0; - cheevos_locals.unofficial.count = 0; - - cheevos_loaded = false; - cheevos_hardcore_paused = false; - - return true; -} - -bool cheevos_toggle_hardcore_mode(void) -{ - settings_t *settings = config_get_ptr(); - - if (!settings) - return false; - - /* reset and deinit rewind to avoid cheat the score */ - if (settings->bools.cheevos_hardcore_mode_enable && !cheevos_hardcore_paused) - { - const char *msg = msg_hash_to_str( - MSG_CHEEVOS_HARDCORE_MODE_ENABLE); - - /* send reset core cmd to avoid any user - * savestate previusly loaded. */ - command_event(CMD_EVENT_RESET, NULL); - - if (settings->bools.rewind_enable) - command_event(CMD_EVENT_REWIND_DEINIT, NULL); - - CHEEVOS_LOG("%s\n", msg); - runloop_msg_queue_push(msg, 0, 3 * 60, true); - } - else - { - if (settings->bools.rewind_enable) - command_event(CMD_EVENT_REWIND_INIT, NULL); - } - - return true; -} - -static void cheevos_patch_addresses(cheevoset_t* set) -{ - unsigned i; - cheevo_t* cheevo = NULL; - - if (!set) - return; - - cheevo = set->cheevos; - - if (!cheevo) - return; - - for (i = set->count; i != 0; i--, cheevo++) - { - unsigned j; - cheevos_condset_t* condset = cheevo->condition.condsets; - - for (j = cheevo->condition.count; j != 0; j--, condset++) - { - unsigned k; - cheevos_cond_t* cond = condset->conds; - - for (k = condset->count; k != 0; k--, cond++) - { - switch (cond->source.type) - { - case CHEEVOS_VAR_TYPE_ADDRESS: - case CHEEVOS_VAR_TYPE_DELTA_MEM: - cheevos_var_patch_addr(&cond->source, - cheevos_locals.console_id); -#ifdef CHEEVOS_DUMP_ADDRS - CHEEVOS_LOG("[CHEEVOS]: s-var %03d:%08X\n", - cond->source.bank_id + 1, cond->source.value); -#endif - break; - - default: - break; - } - - switch (cond->target.type) - { - case CHEEVOS_VAR_TYPE_ADDRESS: - case CHEEVOS_VAR_TYPE_DELTA_MEM: - cheevos_var_patch_addr(&cond->target, - cheevos_locals.console_id); -#ifdef CHEEVOS_DUMP_ADDRS - CHEEVOS_LOG("[CHEEVOS]: t-var %03d:%08X\n", - cond->target.bank_id + 1, cond->target.value); -#endif - break; - - default: - break; - } - } - } - } -} - -static void cheevos_patch_lb_conditions(cheevos_condition_t* condition) -{ - unsigned i; - cheevos_condset_t* condset = NULL; - - if (!condition) - return; - - condset = condition->condsets; - - for (i = condition->count; i != 0; i--, condset++) - { - unsigned j; - cheevos_cond_t* cond = condset->conds; - - for (j = condset->count; j != 0; j--, cond++) - { - switch (cond->source.type) - { - case CHEEVOS_VAR_TYPE_ADDRESS: - case CHEEVOS_VAR_TYPE_DELTA_MEM: - cheevos_var_patch_addr(&cond->source, - cheevos_locals.console_id); -#ifdef CHEEVOS_DUMP_ADDRS - CHEEVOS_LOG("[CHEEVOS]: s-var %03d:%08X\n", - cond->source.bank_id + 1, cond->source.value); -#endif - break; - default: - break; - } - switch (cond->target.type) - { - case CHEEVOS_VAR_TYPE_ADDRESS: - case CHEEVOS_VAR_TYPE_DELTA_MEM: - cheevos_var_patch_addr(&cond->target, - cheevos_locals.console_id); -#ifdef CHEEVOS_DUMP_ADDRS - CHEEVOS_LOG("[CHEEVOS]: t-var %03d:%08X\n", - cond->target.bank_id + 1, cond->target.value); -#endif - break; - default: - break; - } - } - } -} - -static void cheevos_patch_lb_expressions(cheevos_expr_t* expression) -{ - unsigned i; - cheevos_term_t* term = NULL; - - if (!expression) - return; - - term = expression->terms; - - for (i = expression->count; i != 0; i--, term++) - { - switch (term->var.type) - { - case CHEEVOS_VAR_TYPE_ADDRESS: - case CHEEVOS_VAR_TYPE_DELTA_MEM: - cheevos_var_patch_addr(&term->var, cheevos_locals.console_id); -#ifdef CHEEVOS_DUMP_ADDRS - CHEEVOS_LOG("[CHEEVOS]: s-var %03d:%08X\n", - term->var.bank_id + 1, term->var.value); -#endif - break; - default: - break; - } - } -} - -static void cheevos_patch_lbs(cheevos_leaderboard_t *leaderboard) -{ - unsigned i; - - for (i = 0; i < cheevos_locals.lboard_count; i++) - { - cheevos_condition_t *start = &leaderboard[i].start; - cheevos_condition_t *cancel = &leaderboard[i].cancel; - cheevos_condition_t *submit = &leaderboard[i].submit; - cheevos_expr_t *value = &leaderboard[i].value; - - cheevos_patch_lb_conditions(start); - cheevos_patch_lb_conditions(cancel); - cheevos_patch_lb_conditions(submit); - cheevos_patch_lb_expressions(value); - } -} - -void cheevos_test(void) -{ - settings_t *settings = config_get_ptr(); - - if (!cheevos_locals.addrs_patched) - { - cheevos_patch_addresses(&cheevos_locals.core); - cheevos_patch_addresses(&cheevos_locals.unofficial); - cheevos_patch_lbs(cheevos_locals.leaderboards); - - cheevos_locals.addrs_patched = true; - } - - cheevos_test_cheevo_set(&cheevos_locals.core); - - if (settings) - { - if (settings->bools.cheevos_test_unofficial) - cheevos_test_cheevo_set(&cheevos_locals.unofficial); - - if (settings->bools.cheevos_hardcore_mode_enable && - settings->bools.cheevos_leaderboards_enable && - !cheevos_hardcore_paused) - cheevos_test_leaderboards(); - } -} - -bool cheevos_set_cheats(void) -{ - cheats_were_enabled = cheats_are_enabled; - return true; -} - -void cheevos_set_support_cheevos(bool state) -{ - cheevos_locals.core_supports = state; -} - -bool cheevos_get_support_cheevos(void) -{ - return cheevos_locals.core_supports; -} - -cheevos_console_t cheevos_get_console(void) -{ - return cheevos_locals.console_id; -} - -#include "coro.h" - -/* Uncomment the following two lines to debug cheevos_iterate, this will - * disable the coroutine yielding. - * - * The code is very easy to understand. It's meant to be like BASIC: - * CORO_GOTO will jump execution to another label, CORO_GOSUB will - * call another label, and CORO_RET will return from a CORO_GOSUB. - * - * This coroutine code is inspired in a very old pure C implementation - * that runs everywhere: - * - * https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html - */ -/*#undef CORO_YIELD -#define CORO_YIELD()*/ - -typedef struct -{ - /* variables used in the co-routine */ - char badge_name[16]; - char url[256]; - char badge_basepath[PATH_MAX_LENGTH]; - char badge_fullpath[PATH_MAX_LENGTH]; - unsigned char hash[16]; - bool round; - unsigned gameid; - unsigned i; - unsigned j; - unsigned k; - size_t bytes; - size_t count; - size_t offset; - size_t len; - size_t size; - MD5_CTX md5; - cheevos_nes_header_t header; - retro_time_t t0; - struct retro_system_info sysinfo; - void *data; - char *json; - const char *path; - const char *ext; - intfstream_t *stream; - cheevo_t *cheevo; - settings_t *settings; - struct http_connection_t *conn; - struct http_t *http; - const cheevo_t *cheevo_end; - - /* co-routine required fields */ - CORO_FIELDS -} coro_t; - -enum -{ - /* Negative values because CORO_SUB generates positive values */ - SNES_MD5 = -1, - GENESIS_MD5 = -2, - LYNX_MD5 = -3, - NES_MD5 = -4, - GENERIC_MD5 = -5, - FILENAME_MD5 = -6, - EVAL_MD5 = -7, - FILL_MD5 = -8, - GET_GAMEID = -9, - GET_CHEEVOS = -10, - GET_BADGES = -11, - LOGIN = -12, - HTTP_GET = -13, - DEACTIVATE = -14, - PLAYING = -15, - DELAY = -16 -}; - -static int cheevos_iterate(coro_t *coro) -{ - ssize_t num_read = 0; - size_t to_read = 4096; - uint8_t *buffer = NULL; - const char *end = NULL; - - static const uint32_t genesis_exts[] = - { - 0x0b888feeU, /* mdx */ - 0x005978b6U, /* md */ - 0x0b88aa89U, /* smd */ - 0x0b88767fU, /* gen */ - 0x0b8861beU, /* bin */ - 0x0b886782U, /* cue */ - 0x0b8880d0U, /* iso */ - 0x0b88aa98U, /* sms */ - 0x005977f3U, /* gg */ - 0x0059797fU, /* sg */ - 0 - }; - - static const uint32_t snes_exts[] = - { - 0x0b88aa88U, /* smc */ - 0x0b8872bbU, /* fig */ - 0x0b88a9a1U, /* sfc */ - 0x0b887623U, /* gd3 */ - 0x0b887627U, /* gd7 */ - 0x0b886bf3U, /* dx2 */ - 0x0b886312U, /* bsx */ - 0x0b88abd2U, /* swc */ - 0 - }; - - static const uint32_t lynx_exts[] = - { - 0x0b888cf7U, /* lnx */ - 0 - }; - - static cheevos_finder_t finders[] = - { - {SNES_MD5, "SNES (8Mb padding)", snes_exts}, - {GENESIS_MD5, "Genesis (6Mb padding)", genesis_exts}, - {LYNX_MD5, "Atari Lynx (only first 512 bytes)", lynx_exts}, - {NES_MD5, "NES (discards VROM)", NULL}, - {GENERIC_MD5, "Generic (plain content)", NULL}, - {FILENAME_MD5, "Generic (filename)", NULL} - }; - - CORO_ENTER(); - - - - cheevos_locals.addrs_patched = false; - - coro->settings = config_get_ptr(); - - cheevos_locals.meminfo[0].id = RETRO_MEMORY_SYSTEM_RAM; - core_get_memory(&cheevos_locals.meminfo[0]); - - cheevos_locals.meminfo[1].id = RETRO_MEMORY_SAVE_RAM; - core_get_memory(&cheevos_locals.meminfo[1]); - - cheevos_locals.meminfo[2].id = RETRO_MEMORY_VIDEO_RAM; - core_get_memory(&cheevos_locals.meminfo[2]); - - cheevos_locals.meminfo[3].id = RETRO_MEMORY_RTC; - core_get_memory(&cheevos_locals.meminfo[3]); - - CHEEVOS_LOG("[CHEEVOS]: system RAM: %p %u\n", - cheevos_locals.meminfo[0].data, - cheevos_locals.meminfo[0].size); - CHEEVOS_LOG("[CHEEVOS]: save RAM: %p %u\n", - cheevos_locals.meminfo[1].data, - cheevos_locals.meminfo[1].size); - CHEEVOS_LOG("[CHEEVOS]: video RAM: %p %u\n", - cheevos_locals.meminfo[2].data, - cheevos_locals.meminfo[2].size); - CHEEVOS_LOG("[CHEEVOS]: RTC: %p %u\n", - cheevos_locals.meminfo[3].data, - cheevos_locals.meminfo[3].size); - - /* Bail out if cheevos are disabled. - * But set the above anyways, - * command_read_ram needs it. */ - if (!coro->settings->bools.cheevos_enable) - CORO_STOP(); - - /* Load the content into memory, or copy it - * over to our own buffer */ - if (!coro->data) - { - coro->stream = intfstream_open_file( - coro->path, - RETRO_VFS_FILE_ACCESS_READ, - RETRO_VFS_FILE_ACCESS_HINT_NONE); - - if (!coro->stream) - CORO_STOP(); - - CORO_YIELD(); - coro->len = 0; - coro->count = intfstream_get_size(coro->stream); - - /* size limit */ - if (coro->count > size_in_megabytes(64)) - coro->count = size_in_megabytes(64); - - coro->data = malloc(coro->count); - - if (!coro->data) - { - intfstream_close(coro->stream); - free(coro->stream); - CORO_STOP(); - } - - for (;;) - { - buffer = (uint8_t*)coro->data + coro->len; - to_read = 4096; - - if (to_read > coro->count) - to_read = coro->count; - - num_read = intfstream_read(coro->stream, - (void*)buffer, to_read); - - if (num_read <= 0) - break; - - coro->len += num_read; - coro->count -= num_read; - - if (coro->count == 0) - break; - - CORO_YIELD(); - } - - intfstream_close(coro->stream); - free(coro->stream); - } - - /* Use the supported extensions as a hint - * to what method we should use. */ - core_get_system_info(&coro->sysinfo); - - for (coro->i = 0; coro->i < ARRAY_SIZE(finders); coro->i++) - { - if (finders[coro->i].ext_hashes) - { - coro->ext = coro->sysinfo.valid_extensions; - - while (coro->ext) - { - unsigned hash; - end = strchr(coro->ext, '|'); - - if (end) - { - hash = cheevos_djb2( - coro->ext, end - coro->ext); - coro->ext = end + 1; - } - else - { - hash = cheevos_djb2( - coro->ext, strlen(coro->ext)); - coro->ext = NULL; - } - - for (coro->j = 0; finders[coro->i].ext_hashes[coro->j]; coro->j++) - { - if (finders[coro->i].ext_hashes[coro->j] == hash) - { - CHEEVOS_LOG("[CHEEVOS]: testing %s.\n", - finders[coro->i].name); - - /* - * Inputs: CHEEVOS_VAR_INFO - * Outputs: CHEEVOS_VAR_GAMEID, the game was found if it's different from 0 - */ - CORO_GOSUB(finders[coro->i].label); - - if (coro->gameid != 0) - goto found; - - coro->ext = NULL; /* force next finder */ - break; - } - } - } - } - } - - for (coro->i = 0; coro->i < ARRAY_SIZE(finders); coro->i++) - { - if (finders[coro->i].ext_hashes) - continue; - - CHEEVOS_LOG("[CHEEVOS]: testing %s.\n", - finders[coro->i].name); - - /* - * Inputs: CHEEVOS_VAR_INFO - * Outputs: CHEEVOS_VAR_GAMEID - */ - CORO_GOSUB(finders[coro->i].label); - - if (coro->gameid != 0) - goto found; - } - - CHEEVOS_LOG("[CHEEVOS]: this game doesn't feature achievements.\n"); - CORO_STOP(); - -found: - -#ifdef CHEEVOS_JSON_OVERRIDE - { - size_t size = 0; - FILE *file = fopen(CHEEVOS_JSON_OVERRIDE, "rb"); - - fseek(file, 0, SEEK_END); - size = ftell(file); - fseek(file, 0, SEEK_SET); - - coro->json = (char*)malloc(size + 1); - fread((void*)coro->json, 1, size, file); - - fclose(file); - coro->json[size] = 0; - } -#else - CORO_GOSUB(GET_CHEEVOS); - - if (!coro->json) - { - runloop_msg_queue_push("Error loading achievements.", 0, 5 * 60, false); - CHEEVOS_ERR("[CHEEVOS]: error loading achievements.\n"); - CORO_STOP(); - } -#endif - -#ifdef CHEEVOS_SAVE_JSON - { - FILE *file = fopen(CHEEVOS_SAVE_JSON, "w"); - fwrite((void*)coro->json, 1, strlen(coro->json), file); - fclose(file); - } -#endif - if (cheevos_parse(coro->json)) - { - if ((void*)coro->json) - free((void*)coro->json); - CORO_STOP(); - } - - if ((void*)coro->json) - free((void*)coro->json); - - if ( cheevos_locals.core.count == 0 - && cheevos_locals.unofficial.count == 0 - && cheevos_locals.lboard_count == 0) - { - runloop_msg_queue_push( - "This game has no achievements.", - 0, 5 * 60, false); - - cheevos_free_cheevo_set(&cheevos_locals.core); - cheevos_free_cheevo_set(&cheevos_locals.unofficial); - - cheevos_locals.core.cheevos = NULL; - cheevos_locals.unofficial.cheevos = NULL; - cheevos_locals.core.count = 0; - cheevos_locals.unofficial.count = 0; - - cheevos_loaded = false; - cheevos_hardcore_paused = false; - CORO_STOP(); - } - - cheevos_loaded = true; - - /* - * Inputs: CHEEVOS_VAR_GAMEID - * Outputs: - */ - CORO_GOSUB(DEACTIVATE); - - /* - * Inputs: CHEEVOS_VAR_GAMEID - * Outputs: - */ - CORO_GOSUB(PLAYING); - - if (coro->settings->bools.cheevos_verbose_enable && cheevos_locals.core.count > 0) - { - char msg[256]; - int mode = CHEEVOS_ACTIVE_SOFTCORE; - const cheevo_t* cheevo = cheevos_locals.core.cheevos; - const cheevo_t* end = cheevo + cheevos_locals.core.count; - int number_of_unlocked = cheevos_locals.core.count; - - if (coro->settings->bools.cheevos_hardcore_mode_enable && !cheevos_hardcore_paused) - mode = CHEEVOS_ACTIVE_HARDCORE; - - for (; cheevo < end; cheevo++) - if (cheevo->active & mode) - number_of_unlocked--; - - snprintf(msg, sizeof(msg), - "You have %d of %d achievements unlocked.", - number_of_unlocked, cheevos_locals.core.count); - msg[sizeof(msg) - 1] = 0; - runloop_msg_queue_push(msg, 0, 6 * 60, false); - } - - CORO_GOSUB(GET_BADGES); - CORO_STOP(); - - /************************************************************************** - * Info Tries to identify a SNES game - * Input CHEEVOS_VAR_INFO the content info - * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found - *************************************************************************/ - CORO_SUB(SNES_MD5) - - MD5_Init(&coro->md5); - - coro->offset = 0; - coro->count = 0; - - CORO_GOSUB(EVAL_MD5); - - if (coro->count == 0) - { - MD5_Final(coro->hash, &coro->md5); - coro->gameid = 0; - CORO_RET(); - } - - if (coro->count < size_in_megabytes(8)) - { - /* - * Inputs: CHEEVOS_VAR_MD5, CHEEVOS_VAR_OFFSET, CHEEVOS_VAR_COUNT - * Outputs: CHEEVOS_VAR_MD5 - */ - coro->offset = 0; - coro->count = size_in_megabytes(8) - coro->count; - CORO_GOSUB(FILL_MD5); - } - - MD5_Final(coro->hash, &coro->md5); - CORO_GOTO(GET_GAMEID); - - /************************************************************************** - * Info Tries to identify a Genesis game - * Input CHEEVOS_VAR_INFO the content info - * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found - *************************************************************************/ - CORO_SUB(GENESIS_MD5) - - MD5_Init(&coro->md5); - - coro->offset = 0; - coro->count = 0; - CORO_GOSUB(EVAL_MD5); - - if (coro->count == 0) - { - MD5_Final(coro->hash, &coro->md5); - coro->gameid = 0; - CORO_RET(); - } - - if (coro->count < size_in_megabytes(6)) - { - coro->offset = 0; - coro->count = size_in_megabytes(6) - coro->count; - CORO_GOSUB(FILL_MD5); - } - - MD5_Final(coro->hash, &coro->md5); - CORO_GOTO(GET_GAMEID); - - /************************************************************************** - * Info Tries to identify an Atari Lynx game - * Input CHEEVOS_VAR_INFO the content info - * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found - *************************************************************************/ - CORO_SUB(LYNX_MD5) - - if (coro->len < 0x0240) - { - coro->gameid = 0; - CORO_RET(); - } - - MD5_Init(&coro->md5); - - coro->offset = 0x0040; - coro->count = 0x0200; - CORO_GOSUB(EVAL_MD5); - - MD5_Final(coro->hash, &coro->md5); - CORO_GOTO(GET_GAMEID); - - /************************************************************************** - * Info Tries to identify a NES game - * Input CHEEVOS_VAR_INFO the content info - * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found - *************************************************************************/ - CORO_SUB(NES_MD5) - - /* Note about the references to the FCEU emulator below. There is no - * core-specific code in this function, it's rather Retro Achievements - * specific code that must be followed to the letter so we compute - * the correct ROM hash. Retro Achievements does indeed use some - * FCEU related method to compute the hash, since its NES emulator - * is based on it. */ - - if (coro->len < sizeof(coro->header)) - { - coro->gameid = 0; - CORO_RET(); - } - - memcpy((void*)&coro->header, coro->data, - sizeof(coro->header)); - - if ( coro->header.id[0] != 'N' - || coro->header.id[1] != 'E' - || coro->header.id[2] != 'S' - || coro->header.id[3] != 0x1a) - { - coro->gameid = 0; - CORO_RET(); - } - - { - size_t romsize = 256; - /* from FCEU core - compute size using the cart mapper */ - int mapper = (coro->header.rom_type >> 4) | (coro->header.rom_type2 & 0xF0); - - if (coro->header.rom_size) - romsize = next_pow2(coro->header.rom_size); - - /* for games not to the power of 2, so we just read enough - * PRG rom from it, but we have to keep ROM_size to the power of 2 - * since PRGCartMapping wants ROM_size to be to the power of 2 - * so instead if not to power of 2, we just use head.ROM_size when - * we use FCEU_read. */ - coro->round = mapper != 53 && mapper != 198 && mapper != 228; - coro->bytes = coro->round ? romsize : coro->header.rom_size; - } - - /* from FCEU core - check if Trainer included in ROM data */ - MD5_Init(&coro->md5); - coro->offset = sizeof(coro->header) + (coro->header.rom_type & 4 - ? sizeof(coro->header) : 0); - coro->count = 0x4000 * coro->bytes; - CORO_GOSUB(EVAL_MD5); - - if (coro->count < 0x4000 * coro->bytes) - { - coro->offset = 0xff; - coro->count = 0x4000 * coro->bytes - coro->count; - CORO_GOSUB(FILL_MD5); - } - - MD5_Final(coro->hash, &coro->md5); - CORO_GOTO(GET_GAMEID); - - /************************************************************************** - * Info Tries to identify a "generic" game - * Input CHEEVOS_VAR_INFO the content info - * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found - *************************************************************************/ - CORO_SUB(GENERIC_MD5) - - MD5_Init(&coro->md5); - - coro->offset = 0; - coro->count = 0; - CORO_GOSUB(EVAL_MD5); - - MD5_Final(coro->hash, &coro->md5); - - if (coro->count == 0) - CORO_RET(); - - CORO_GOTO(GET_GAMEID); - - /************************************************************************** - * Info Tries to identify a game based on its filename (with no extension) - * Input CHEEVOS_VAR_INFO the content info - * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found - *************************************************************************/ - CORO_SUB(FILENAME_MD5) - if (!string_is_empty(coro->path)) - { - char base_noext[PATH_MAX_LENGTH]; - fill_pathname_base_noext(base_noext, coro->path, sizeof(base_noext)); - - MD5_Init(&coro->md5); - MD5_Update(&coro->md5, (void*)base_noext, strlen(base_noext)); - MD5_Final(coro->hash, &coro->md5); - - CORO_GOTO(GET_GAMEID); - } - CORO_RET(); - - /************************************************************************** - * Info Evaluates the CHEEVOS_VAR_MD5 hash - * Inputs CHEEVOS_VAR_INFO, CHEEVOS_VAR_OFFSET, CHEEVOS_VAR_COUNT - * Outputs CHEEVOS_VAR_MD5, CHEEVOS_VAR_COUNT - *************************************************************************/ - CORO_SUB(EVAL_MD5) - - if (coro->count == 0) - coro->count = coro->len; - - if (coro->len - coro->offset < coro->count) - coro->count = coro->len - coro->offset; - - /* size limit */ - if (coro->count > size_in_megabytes(64)) - coro->count = size_in_megabytes(64); - - MD5_Update(&coro->md5, - (void*)((uint8_t*)coro->data + coro->offset), - coro->count); - CORO_RET(); - - /************************************************************************** - * Info Updates the CHEEVOS_VAR_MD5 hash with a repeated value - * Inputs CHEEVOS_VAR_OFFSET, CHEEVOS_VAR_COUNT - * Outputs CHEEVOS_VAR_MD5 - *************************************************************************/ - CORO_SUB(FILL_MD5) - - { - char buffer[4096]; - - while (coro->count > 0) - { - size_t len = sizeof(buffer); - - if (len > coro->count) - len = coro->count; - - memset((void*)buffer, coro->offset, len); - MD5_Update(&coro->md5, (void*)buffer, len); - coro->count -= len; - } - } - - CORO_RET(); - - /************************************************************************** - * Info Gets the achievements from Retro Achievements - * Inputs coro->hash - * Outputs CHEEVOS_VAR_GAMEID - *************************************************************************/ - CORO_SUB(GET_GAMEID) - - { - char gameid[16]; - - CHEEVOS_LOG( - "[CHEEVOS]: getting game id for hash %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", - coro->hash[ 0], coro->hash[ 1], coro->hash[ 2], coro->hash[ 3], - coro->hash[ 4], coro->hash[ 5], coro->hash[ 6], coro->hash[ 7], - coro->hash[ 8], coro->hash[ 9], coro->hash[10], coro->hash[11], - coro->hash[12], coro->hash[13], coro->hash[14], coro->hash[15] - ); - - snprintf( - coro->url, sizeof(coro->url), - "http://retroachievements.org/dorequest.php?r=gameid&m=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", - coro->hash[ 0], coro->hash[ 1], coro->hash[ 2], coro->hash[ 3], - coro->hash[ 4], coro->hash[ 5], coro->hash[ 6], coro->hash[ 7], - coro->hash[ 8], coro->hash[ 9], coro->hash[10], coro->hash[11], - coro->hash[12], coro->hash[13], coro->hash[14], coro->hash[15] - ); - - coro->url[sizeof(coro->url) - 1] = 0; - -#ifdef CHEEVOS_LOG_URLS - cheevos_log_url("[CHEEVOS]: url to get the game's id: %s\n", coro->url); -#endif - - CORO_GOSUB(HTTP_GET); - - if (!coro->json) - CORO_RET(); - - if (cheevos_get_value(coro->json, - CHEEVOS_JSON_KEY_GAMEID, gameid, sizeof(gameid))) - { - if ((void*)coro->json) - free((void*)coro->json); - CHEEVOS_ERR("[CHEEVOS]: error getting game_id.\n"); - CORO_RET(); - } - - if ((void*)coro->json) - free((void*)coro->json); - CHEEVOS_LOG("[CHEEVOS]: got game id %s.\n", gameid); - coro->gameid = (unsigned)strtol(gameid, NULL, 10); - CORO_RET(); - } - - /************************************************************************** - * Info Gets the achievements from Retro Achievements - * Inputs CHEEVOS_VAR_GAMEID - * Outputs CHEEVOS_VAR_JSON - *************************************************************************/ - CORO_SUB(GET_CHEEVOS) - - CORO_GOSUB(LOGIN); - - snprintf(coro->url, sizeof(coro->url), - "http://retroachievements.org/dorequest.php?r=patch&g=%u&u=%s&t=%s", - coro->gameid, - coro->settings->arrays.cheevos_username, - cheevos_locals.token); - - coro->url[sizeof(coro->url) - 1] = 0; - -#ifdef CHEEVOS_LOG_URLS - cheevos_log_url("[CHEEVOS]: url to get the list of cheevos: %s\n", coro->url); -#endif - - CORO_GOSUB(HTTP_GET); - - if (!coro->json) - { - CHEEVOS_ERR("[CHEEVOS]: error getting achievements for game id %u.\n", coro->gameid); - CORO_STOP(); - } - - CHEEVOS_LOG("[CHEEVOS]: got achievements for game id %u.\n", coro->gameid); - CORO_RET(); - - /************************************************************************** - * Info Gets the achievements from Retro Achievements - * Inputs CHEEVOS_VAR_GAMEID - * Outputs CHEEVOS_VAR_JSON - *************************************************************************/ - CORO_SUB(GET_BADGES) - - badges_ctx = new_badges_ctx; - - { - settings_t *settings = config_get_ptr(); - if (!( - string_is_equal(settings->arrays.menu_driver, "xmb") || - !string_is_equal(settings->arrays.menu_driver, "ozone") - ) || - !settings->bools.cheevos_badges_enable) - CORO_RET(); - } - - coro->cheevo = cheevos_locals.core.cheevos; - coro->cheevo_end = cheevos_locals.core.cheevos + cheevos_locals.core.count; - - for (; coro->cheevo < coro->cheevo_end; coro->cheevo++) - { - for (coro->j = 0 ; coro->j < 2; coro->j++) - { - coro->badge_fullpath[0] = '\0'; - fill_pathname_application_special( - coro->badge_fullpath, - sizeof(coro->badge_fullpath), - APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_CHEEVOS_BADGES); - - if (!path_is_directory(coro->badge_fullpath)) - path_mkdir(coro->badge_fullpath); - CORO_YIELD(); - if (coro->j == 0) - snprintf(coro->badge_name, - sizeof(coro->badge_name), - "%s.png", coro->cheevo->badge); - else - snprintf(coro->badge_name, - sizeof(coro->badge_name), - "%s_lock.png", coro->cheevo->badge); - - fill_pathname_join( - coro->badge_fullpath, - coro->badge_fullpath, - coro->badge_name, - sizeof(coro->badge_fullpath)); - - if (!badge_exists(coro->badge_fullpath)) - { -#ifdef CHEEVOS_LOG_BADGES - CHEEVOS_LOG( - "[CHEEVOS]: downloading badge %s\n", - coro->badge_fullpath); -#endif - snprintf(coro->url, - sizeof(coro->url), - "http://i.retroachievements.org/Badge/%s", - coro->badge_name); - - CORO_GOSUB(HTTP_GET); - - if (coro->json) - { - if (!filestream_write_file(coro->badge_fullpath, - coro->json, coro->k)) - CHEEVOS_ERR("[CHEEVOS]: error writing badge %s\n", coro->badge_fullpath); - else - free(coro->json); - } - } - } - } - - CORO_RET(); - - /************************************************************************** - * Info Logs in the user at Retro Achievements - *************************************************************************/ - CORO_SUB(LOGIN) - - if (cheevos_locals.token[0]) - CORO_RET(); - - { - char urle_user[64]; - char urle_login[64]; - const char *username = coro ? coro->settings->arrays.cheevos_username : NULL; - const char *login = NULL; - bool via_token = false; - - if (coro) - { - if (string_is_empty(coro->settings->arrays.cheevos_password)) - { - via_token = true; - login = coro->settings->arrays.cheevos_token; - } - else - login = coro->settings->arrays.cheevos_password; - } - else - login = NULL; - - if (string_is_empty(username) || string_is_empty(login)) - { - runloop_msg_queue_push( - "Missing RetroAchievements account information.", - 0, 5 * 60, false); - runloop_msg_queue_push( - "Please fill in your account information in Settings.", - 0, 5 * 60, false); - CHEEVOS_ERR("[CHEEVOS]: login info not informed.\n"); - CORO_STOP(); - } - - cheevos_url_encode(username, urle_user, sizeof(urle_user)); - cheevos_url_encode(login, urle_login, sizeof(urle_login)); - - snprintf( - coro->url, sizeof(coro->url), - "http://retroachievements.org/dorequest.php?r=login&u=%s&%c=%s", - urle_user, via_token ? 't' : 'p', urle_login - ); - - coro->url[sizeof(coro->url) - 1] = 0; - } - -#ifdef CHEEVOS_LOG_URLS - cheevos_log_url("[CHEEVOS]: url to login: %s\n", - coro->url); -#endif - - CORO_GOSUB(HTTP_GET); - - if (coro->json) - { - char error_response[64]; - char error_message[256]; - - cheevos_get_value( - coro->json, - CHEEVOS_JSON_KEY_ERROR, - error_response, - sizeof(error_response) - ); - - /* No error, continue with login */ - if (string_is_empty(error_response)) - { - int res = cheevos_get_value( - coro->json, - CHEEVOS_JSON_KEY_TOKEN, - cheevos_locals.token, - sizeof(cheevos_locals.token)); - - if ((void*)coro->json) - free((void*)coro->json); - - if (!res) - { - if (coro->settings->bools.cheevos_verbose_enable) - { - char msg[256]; - snprintf(msg, sizeof(msg), - "RetroAchievements: Logged in as \"%s\".", - coro->settings->arrays.cheevos_username); - msg[sizeof(msg) - 1] = 0; - runloop_msg_queue_push(msg, 0, 3 * 60, false); - } - - /* Save token to config and clear pass on success */ - *coro->settings->arrays.cheevos_password = '\0'; - strncpy( - coro->settings->arrays.cheevos_token, - cheevos_locals.token, sizeof(cheevos_locals.token) - ); - CORO_RET(); - } - } - - if ((void*)coro->json) - free((void*)coro->json); - - /* Site returned error, display it */ - snprintf(error_message, sizeof(error_message), - "RetroAchievements: %s", - error_response); - error_message[sizeof(error_message) - 1] = 0; - runloop_msg_queue_push(error_message, 0, 5 * 60, false); - *coro->settings->arrays.cheevos_token = '\0'; - - CORO_STOP(); - } - - runloop_msg_queue_push("RetroAchievements: Error contacting server.", 0, 5 * 60, false); - CHEEVOS_ERR("[CHEEVOS]: error getting user token.\n"); - - CORO_STOP(); - - /************************************************************************** - * Info Pauses execution for five seconds - *************************************************************************/ - CORO_SUB(DELAY) - - { - retro_time_t t1; - coro->t0 = cpu_features_get_time_usec(); - - do - { - CORO_YIELD(); - t1 = cpu_features_get_time_usec(); - }while ((t1 - coro->t0) < 3000000); - } - - CORO_RET(); - - /************************************************************************** - * Info Makes a HTTP GET request - * Inputs CHEEVOS_VAR_URL - * Outputs CHEEVOS_VAR_JSON - *************************************************************************/ - CORO_SUB(HTTP_GET) - - for (coro->k = 0; coro->k < 5; coro->k++) - { - if (coro->k != 0) - CHEEVOS_LOG("[CHEEVOS]: Retrying HTTP request: %u of 5\n", coro->k + 1); - - coro->json = NULL; - coro->conn = net_http_connection_new( - coro->url, "GET", NULL); - - if (!coro->conn) - { - CORO_GOSUB(DELAY); - continue; - } - - /* Don't bother with timeouts here, it's just a string scan. */ - while (!net_http_connection_iterate(coro->conn)) {} - - /* Error finishing the connection descriptor. */ - if (!net_http_connection_done(coro->conn)) - { - net_http_connection_free(coro->conn); - continue; - } - - coro->http = net_http_new(coro->conn); - - /* Error connecting to the endpoint. */ - if (!coro->http) - { - net_http_connection_free(coro->conn); - CORO_GOSUB(DELAY); - continue; - } - - while (!net_http_update(coro->http, NULL, NULL)) - CORO_YIELD(); - - { - size_t length; - uint8_t *data = net_http_data(coro->http, - &length, false); - - if (data) - { - coro->json = (char*)malloc(length + 1); - - if (coro->json) - { - memcpy((void*)coro->json, (void*)data, length); - free(data); - coro->json[length] = 0; - } - - coro->k = (unsigned)length; - net_http_delete(coro->http); - net_http_connection_free(coro->conn); - CORO_RET(); - } - } - - net_http_delete(coro->http); - net_http_connection_free(coro->conn); - } - - CHEEVOS_LOG("[CHEEVOS]: Couldn't connect to server after 5 tries\n"); - CORO_RET(); - - /************************************************************************** - * Info Deactivates the achievements already awarded - * Inputs CHEEVOS_VAR_GAMEID - * Outputs - *************************************************************************/ - CORO_SUB(DEACTIVATE) - -#ifndef CHEEVOS_DONT_DEACTIVATE - CORO_GOSUB(LOGIN); - - /* Deactivate achievements in softcore mode. */ - snprintf( - coro->url, sizeof(coro->url), - "http://retroachievements.org/dorequest.php?r=unlocks&u=%s&t=%s&g=%u&h=0", - coro->settings->arrays.cheevos_username, - cheevos_locals.token, coro->gameid - ); - - coro->url[sizeof(coro->url) - 1] = 0; - -#ifdef CHEEVOS_LOG_URLS - cheevos_log_url("[CHEEVOS]: url to get the list of unlocked cheevos in softcore: %s\n", coro->url); -#endif - - CORO_GOSUB(HTTP_GET); - - if (coro->json) - { - if (!cheevos_deactivate_unlocks(coro->json, CHEEVOS_ACTIVE_SOFTCORE)) - CHEEVOS_LOG("[CHEEVOS]: deactivated unlocked achievements in softcore mode.\n"); - else - CHEEVOS_ERR("[CHEEVOS]: error deactivating unlocked achievements in softcore mode.\n"); - - if ((void*)coro->json) - free((void*)coro->json); - } - else - CHEEVOS_ERR("[CHEEVOS]: error retrieving list of unlocked achievements in softcore mode.\n"); - - /* Deactivate achievements in hardcore mode. */ - snprintf( - coro->url, sizeof(coro->url), - "http://retroachievements.org/dorequest.php?r=unlocks&u=%s&t=%s&g=%u&h=1", - coro->settings->arrays.cheevos_username, - cheevos_locals.token, coro->gameid - ); - - coro->url[sizeof(coro->url) - 1] = 0; - -#ifdef CHEEVOS_LOG_URLS - cheevos_log_url("[CHEEVOS]: url to get the list of unlocked cheevos in hardcore: %s\n", coro->url); -#endif - - CORO_GOSUB(HTTP_GET); - - if (coro->json) - { - if (!cheevos_deactivate_unlocks(coro->json, CHEEVOS_ACTIVE_HARDCORE)) - CHEEVOS_LOG("[CHEEVOS]: deactivated unlocked achievements in hardcore mode.\n"); - else - CHEEVOS_ERR("[CHEEVOS]: error deactivating unlocked achievements in hardcore mode.\n"); - - if ((void*)coro->json) - free((void*)coro->json); - } - else - CHEEVOS_ERR("[CHEEVOS]: error retrieving list of unlocked achievements in hardcore mode.\n"); - -#endif - CORO_RET(); - - /************************************************************************** - * Info Posts the "playing" activity to Retro Achievements - * Inputs CHEEVOS_VAR_GAMEID - * Outputs - *************************************************************************/ - CORO_SUB(PLAYING) - - snprintf( - coro->url, sizeof(coro->url), - "http://retroachievements.org/dorequest.php?r=postactivity&u=%s&t=%s&a=3&m=%u", - coro->settings->arrays.cheevos_username, - cheevos_locals.token, coro->gameid - ); - - coro->url[sizeof(coro->url) - 1] = 0; - -#ifdef CHEEVOS_LOG_URLS - cheevos_log_url("[CHEEVOS]: url to post the 'playing' activity: %s\n", coro->url); -#endif - - CORO_GOSUB(HTTP_GET); - - if (coro->json) - { - CHEEVOS_LOG("[CHEEVOS]: posted playing activity.\n"); - if ((void*)coro->json) - free((void*)coro->json); - } - else - CHEEVOS_ERR("[CHEEVOS]: error posting playing activity.\n"); - - CHEEVOS_LOG("[CHEEVOS]: posted playing activity.\n"); - CORO_RET(); - - CORO_LEAVE(); -} - -static void cheevos_task_handler(retro_task_t *task) -{ - coro_t *coro = (coro_t*)task->state; - - if (!coro) - return; - - if (!cheevos_iterate(coro) || task_get_cancelled(task)) - { - task_set_finished(task, true); - - CHEEVOS_LOCK(cheevos_locals.task_lock); - cheevos_locals.task = NULL; - CHEEVOS_UNLOCK(cheevos_locals.task_lock); - - if (task_get_cancelled(task)) - { - CHEEVOS_LOG("[CHEEVOS]: Load task cancelled\n"); - } - else - { - CHEEVOS_LOG("[CHEEVOS]: Load task finished\n"); - } - - if (coro->data) - free(coro->data); - - if ((void*)coro->path) - free((void*)coro->path); - - free((void*)coro); - } -} - -bool cheevos_load(const void *data) -{ - retro_task_t *task; - const struct retro_game_info *info = NULL; - coro_t *coro = NULL; - - cheevos_loaded = false; - cheevos_hardcore_paused = false; - - if (!cheevos_locals.core_supports || !data) - return false; - - coro = (coro_t*)calloc(1, sizeof(*coro)); - - if (!coro) - return false; - - task = (retro_task_t*)calloc(1, sizeof(*task)); - - if (!task) - { - if ((void*)coro) - free((void*)coro); - return false; - } - - CORO_SETUP(); - - info = (const struct retro_game_info*)data; - - if (info->data) - { - coro->len = info->size; - - /* size limit */ - if (coro->len > size_in_megabytes(64)) - coro->len = size_in_megabytes(64); - - coro->data = malloc(coro->len); - - if (!coro->data) - { - if ((void*)task) - free((void*)task); - if ((void*)coro) - free((void*)coro); - return false; - } - - memcpy(coro->data, info->data, coro->len); - coro->path = NULL; - } - else - { - coro->data = NULL; - coro->path = strdup(info->path); - } - - task->handler = cheevos_task_handler; - task->state = (void*)coro; - task->mute = true; - task->callback = NULL; - task->user_data = NULL; - task->progress = 0; - task->title = NULL; - -#ifdef HAVE_THREADS - if (cheevos_locals.task_lock == NULL) - { - cheevos_locals.task_lock = slock_new(); - } -#endif - - CHEEVOS_LOCK(cheevos_locals.task_lock); - cheevos_locals.task = task; - CHEEVOS_UNLOCK(cheevos_locals.task_lock); - - task_queue_push(task); - - return true; -} +/* RetroArch - A frontend for libretro. + * Copyright (C) 2015-2018 - Andre Leiradella + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "../config.h" +#endif + +#ifdef HAVE_MENU +#include "../menu/menu_driver.h" +#include "../menu/menu_entries.h" +#endif + +#ifdef HAVE_THREADS +#include +#endif + +#include "badges.h" +#include "cheevos.h" +#include "var.h" +#include "cond.h" + +#include "../file_path_special.h" +#include "../paths.h" +#include "../command.h" +#include "../dynamic.h" +#include "../configuration.h" +#include "../performance_counters.h" +#include "../msg_hash.h" +#include "../retroarch.h" +#include "../core.h" + +#include "../network/net_http_special.h" +#include "../tasks/tasks_internal.h" + +#include "../verbosity.h" + +/* Define this macro to prevent cheevos from being deactivated. */ +#undef CHEEVOS_DONT_DEACTIVATE + +/* Define this macro to log URLs (will log the user token). */ +#undef CHEEVOS_LOG_URLS + +/* Define this macro to dump all cheevos' addresses. */ +#undef CHEEVOS_DUMP_ADDRS + +/* Define this macro to remove HTTP timeouts. */ +#undef CHEEVOS_NO_TIMEOUT + +/* Define this macro to load a JSON file from disk instead of downloading + * from retroachievements.org. */ +#undef CHEEVOS_JSON_OVERRIDE + +/* Define this macro with a string to save the JSON file to disk with + * that name. */ +#undef CHEEVOS_SAVE_JSON + +/* Define this macro to have the password and token logged. THIS WILL DISCLOSE + * THE USER'S PASSWORD, TAKE CARE! */ +#undef CHEEVOS_LOG_PASSWORD + +/* Define this macro to log downloaded badge images. */ +#undef CHEEVOS_LOG_BADGES + +/* C89 wants only int values in enums. */ +#define CHEEVOS_JSON_KEY_GAMEID 0xb4960eecU +#define CHEEVOS_JSON_KEY_ACHIEVEMENTS 0x69749ae1U +#define CHEEVOS_JSON_KEY_ID 0x005973f2U +#define CHEEVOS_JSON_KEY_MEMADDR 0x1e76b53fU +#define CHEEVOS_JSON_KEY_TITLE 0x0e2a9a07U +#define CHEEVOS_JSON_KEY_DESCRIPTION 0xe61a1f69U +#define CHEEVOS_JSON_KEY_POINTS 0xca8fce22U +#define CHEEVOS_JSON_KEY_AUTHOR 0xa804edb8U +#define CHEEVOS_JSON_KEY_MODIFIED 0xdcea4fe6U +#define CHEEVOS_JSON_KEY_CREATED 0x3a84721dU +#define CHEEVOS_JSON_KEY_BADGENAME 0x887685d9U +#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 +#define CHEEVOS_JSON_KEY_SUCCESS 0x110461deU +#define CHEEVOS_JSON_KEY_ERROR 0x0d2011cfU + +typedef struct +{ + cheevos_cond_t *conds; + unsigned count; +} cheevos_condset_t; + +typedef struct +{ + cheevos_condset_t *condsets; + unsigned count; +} cheevos_condition_t; + +typedef struct +{ + unsigned id; + const char *title; + const char *description; + const char *author; + const char *badge; + unsigned points; + unsigned dirty; + int active; + int last; + int modified; + + cheevos_condition_t condition; +} cheevo_t; + +typedef struct +{ + cheevo_t *cheevos; + unsigned count; +} cheevoset_t; + +typedef struct +{ + int is_element; + int mode; +} cheevos_deactivate_t; + +typedef struct +{ + unsigned key_hash; + int is_key; + const char *value; + size_t length; +} cheevos_getvalueud_t; + +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 +{ + const char *string; + size_t length; +} cheevos_field_t; + +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, format; +} cheevos_readud_t; + +typedef struct +{ + int label; + const char *name; + const uint32_t *ext_hashes; +} cheevos_finder_t; + +typedef struct +{ + cheevos_var_t var; + double multiplier; + bool compare_next; +} cheevos_term_t; + +typedef struct +{ + cheevos_term_t *terms; + unsigned count; + unsigned compare_count; +} cheevos_expr_t; + +typedef struct +{ + unsigned id; + unsigned format; + const char *title; + const char *description; + int active; + int last_value; + + cheevos_condition_t start; + cheevos_condition_t cancel; + cheevos_condition_t submit; + cheevos_expr_t value; +} cheevos_leaderboard_t; + +typedef struct +{ + retro_task_t* task; +#ifdef HAVE_THREADS + slock_t* task_lock; +#endif + + cheevos_console_t console_id; + bool core_supports; + bool addrs_patched; + int add_buffer; + int add_hits; + + 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; + +typedef struct +{ + uint8_t id[4]; /* NES^Z */ + uint8_t rom_size; + uint8_t vrom_size; + uint8_t rom_type; + uint8_t rom_type2; + uint8_t reserve[8]; +} cheevos_nes_header_t; + +static cheevos_locals_t cheevos_locals = +{ + /* task */ NULL, +#ifdef HAVE_THREADS + /* task_lock */ NULL, +#endif + + /* console_id */ CHEEVOS_CONSOLE_NONE, + /* core_supports */ true, + /* addrs_patched */ false, + /* add_buffer */ 0, + /* add_hits */ 0, + + /* 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; +bool cheevos_hardcore_active = false; +bool cheevos_hardcore_paused = false; +bool cheevos_state_loaded_flag = false; +int cheats_are_enabled = 0; +int cheats_were_enabled = 0; + +#ifdef HAVE_THREADS +#define CHEEVOS_LOCK(l) do { slock_lock(l); } while (0) +#define CHEEVOS_UNLOCK(l) do { slock_unlock(l); } while (0) +#else +#define CHEEVOS_LOCK(l) +#define CHEEVOS_UNLOCK(l) +#endif + +/***************************************************************************** +Supporting functions. +*****************************************************************************/ + +#ifndef CHEEVOS_VERBOSE + +void cheevos_log(const char *fmt, ...) +{ + (void)fmt; +} + +#endif + +static unsigned size_in_megabytes(unsigned val) +{ + return (val * 1024 * 1024); +} + +#ifdef CHEEVOS_LOG_URLS +static void cheevos_log_url(const char* format, const char* url) +{ +#ifdef CHEEVOS_LOG_PASSWORD + CHEEVOS_LOG(format, url); +#else + char copy[256]; + char* aux = NULL; + char* next = NULL; + + if (!string_is_empty(url)) + strlcpy(copy, url, sizeof(copy)); + + aux = strstr(copy, "?p="); + + if (!aux) + aux = strstr(copy, "&p="); + + if (aux) + { + aux += 3; + next = strchr(aux, '&'); + + if (next) + { + do + { + *aux++ = *next++; + }while (next[-1] != 0); + } + else + *aux = 0; + } + + aux = strstr(copy, "?t="); + + if (!aux) + aux = strstr(copy, "&t="); + + if (aux) + { + aux += 3; + next = strchr(aux, '&'); + + if (next) + { + do + { + *aux++ = *next++; + }while (next[-1] != 0); + } + else + *aux = 0; + } + + CHEEVOS_LOG(format, copy); +#endif +} +#endif + +static uint32_t cheevos_djb2(const char* str, size_t length) +{ + const unsigned char *aux = (const unsigned char*)str; + const unsigned char *end = aux + length; + uint32_t hash = 5381; + + while (aux < end) + hash = (hash << 5) + hash + *aux++; + + return hash; +} + +static int cheevos_getvalue__json_key(void *userdata, + const char *name, size_t length) +{ + cheevos_getvalueud_t* ud = (cheevos_getvalueud_t*)userdata; + + if (ud) + ud->is_key = cheevos_djb2(name, length) == ud->key_hash; + return 0; +} + +static int cheevos_getvalue__json_string(void *userdata, + const char *string, size_t length) +{ + cheevos_getvalueud_t* ud = (cheevos_getvalueud_t*)userdata; + + if (ud && ud->is_key) + { + ud->value = string; + ud->length = length; + ud->is_key = 0; + } + + return 0; +} + +static int cheevos_getvalue__json_boolean(void *userdata, int istrue) +{ + cheevos_getvalueud_t* ud = (cheevos_getvalueud_t*)userdata; + + if (ud && ud->is_key) + { + if (istrue) + { + ud->value = "true"; + ud->length = 4; + } + else + { + ud->value = "false"; + ud->length = 5; + } + ud->is_key = 0; + } + + return 0; +} + +static int cheevos_getvalue__json_null(void *userdata) +{ + cheevos_getvalueud_t* ud = (cheevos_getvalueud_t*)userdata; + + if (ud && ud->is_key ) + { + ud->value = "null"; + ud->length = 4; + ud->is_key = 0; + } + + return 0; +} + +static int cheevos_get_value(const char *json, unsigned key_hash, + char *value, size_t length) +{ + static const jsonsax_handlers_t handlers = + { + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + cheevos_getvalue__json_key, + NULL, + cheevos_getvalue__json_string, + cheevos_getvalue__json_string, /* number */ + cheevos_getvalue__json_boolean, + cheevos_getvalue__json_null + }; + + cheevos_getvalueud_t ud; + + ud.key_hash = key_hash; + ud.is_key = 0; + ud.value = NULL; + ud.length = 0; + *value = 0; + + if ((jsonsax_parse(json, &handlers, (void*)&ud) == JSONSAX_OK) + && ud.value && ud.length < length) + { + if (!string_is_empty(ud.value)) + strlcpy(value, ud.value, ud.length + 1); + return 0; + } + + return -1; +} + +/***************************************************************************** +Count number of achievements in a JSON file. +*****************************************************************************/ + +static int cheevos_count__json_end_array(void *userdata) +{ + cheevos_countud_t* ud = (cheevos_countud_t*)userdata; + + if (ud) + { + ud->in_cheevos = 0; + ud->in_lboards = 0; + } + + return 0; +} + +static int cheevos_count__json_key(void *userdata, + const char *name, size_t length) +{ + cheevos_countud_t* ud = (cheevos_countud_t*)userdata; + + if (ud) + { + ud->field_hash = cheevos_djb2(name, length); + 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; +} + +static int cheevos_count__json_number(void *userdata, + const char *number, size_t length) +{ + cheevos_countud_t* ud = (cheevos_countud_t*)userdata; + + if (ud) + { + if (ud->in_cheevos && ud->field_hash == CHEEVOS_JSON_KEY_FLAGS) + { + long flags = strtol(number, NULL, 10); + + if (flags == 3) + ud->core_count++; /* Core achievements */ + 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 *lboard_count) +{ + static const jsonsax_handlers_t handlers = + { + NULL, + NULL, + NULL, + NULL, + NULL, + cheevos_count__json_end_array, + cheevos_count__json_key, + NULL, + NULL, + cheevos_count__json_number, + NULL, + NULL + }; + + int res; + cheevos_countud_t ud; + ud.in_cheevos = 0; + ud.in_lboards = 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; +} + +/***************************************************************************** +Parse the MemAddr field. +*****************************************************************************/ + +static unsigned cheevos_count_cond_sets(const char *memaddr) +{ + cheevos_cond_t cond; + unsigned count = 0; + + for (;;) + { + count++; + + for (;;) + { + cheevos_cond_parse(&cond, &memaddr); + + if (*memaddr != '_') + break; + + memaddr++; + } + + if (*memaddr != 'S') + break; + + memaddr++; + } + + return count; +} + +static int cheevos_parse_condition( + cheevos_condition_t *condition, + const char* memaddr) +{ + if (!condition) + return 0; + + condition->count = cheevos_count_cond_sets(memaddr); + + if (condition->count) + { + unsigned set = 0; + const cheevos_condset_t* end = NULL; + cheevos_condset_t *conds = NULL; + cheevos_condset_t *condset = NULL; + cheevos_condset_t *condsets = (cheevos_condset_t*) + calloc(condition->count, sizeof(cheevos_condset_t)); + + (void)conds; + + if (!condsets) + return -1; + + condition->condsets = condsets; + end = condition->condsets + condition->count; + + for (condset = condition->condsets; condset < end; condset++, set++) + { + condset->count = + cheevos_cond_count_in_set(memaddr, set); + condset->conds = NULL; + + CHEEVOS_LOG("[CHEEVOS]: set %p (index=%u)\n", condset, set); + CHEEVOS_LOG("[CHEEVOS]: conds: %u\n", condset->count); + + if (condset->count) + { + cheevos_cond_t *conds = (cheevos_cond_t*) + calloc(condset->count, sizeof(cheevos_cond_t)); + + if (!conds) + { + while (--condset >= condition->condsets) + { + if ((void*)condset->conds) + free((void*)condset->conds); + } + + return -1; + } + + condset->conds = conds; + cheevos_cond_parse_in_set(condset->conds, memaddr, set); + } + } + } + + return 0; +} + +static void cheevos_free_condition(cheevos_condition_t* condition) +{ + unsigned i; + + if (!condition) + return; + + if (condition->condsets) + { + for (i = 0; i < condition->count; i++) + { + if (condition->condsets[i].conds) + { + free(condition->condsets[i].conds); + condition->condsets[i].conds = NULL; + } + } + + if (condition->condsets) + { + free(condition->condsets); + condition->condsets = NULL; + } + } +} + +/***************************************************************************** +Parse the Mem field of leaderboards. +*****************************************************************************/ + +static int cheevos_parse_expression(cheevos_expr_t *expr, const char* mem) +{ + unsigned i; + const char *aux; + cheevos_term_t *terms = NULL; + char *end = NULL; + + if (!expr) + return -1; + + expr->count = 1; + expr->compare_count = 1; + + for (aux = mem;; aux++) + { + if (*aux == '"' || *aux == ':') + break; + expr->count += *aux == '_'; + } + + if (expr->count > 0) + terms = (cheevos_term_t*) + calloc(expr->count, sizeof(cheevos_term_t)); + + if (!terms) + return -1; + + expr->terms = terms; + + for (i = 0; i < expr->count; i++) + { + expr->terms[i].compare_next = false; + expr->terms[i].multiplier = 1; + } + + for (i = 0, aux = mem; i < expr->count;) + { + cheevos_var_parse(&expr->terms[i].var, &aux); + + if (*aux != '*') + { + /* expression has no multiplier */ + if (*aux == '_') + { + aux++; + i++; + } + else if (*aux == '$') + { + expr->terms[i].compare_next = true; + expr->compare_count++; + aux++; + i++; + } + + /* no multiplier at end of string */ + else if (*aux == '\0' || *aux == '"' || *aux == ',') + return 0; + + /* invalid character in expression */ + else + { + if (expr->terms) + { + free(expr->terms); + expr->terms = NULL; + } + return -1; + } + } + else + { + if (aux[1] == 'h' || aux[1] == 'H') + expr->terms[i].multiplier = (double)strtol(aux + 2, &end, 16); + else + expr->terms[i].multiplier = strtod(aux + 1, &end); + aux = end; + + if (*aux == '$') + { + aux++; + expr->terms[i].compare_next = true; + expr->compare_count++; + } + else + expr->terms[i].compare_next = false; + + aux++; + i++; + } + } + 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; + } + + 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); + if (lb->value.terms) + { + free((void*)lb->value.terms); + lb->value.terms = NULL; + } + return -1; +} + +/***************************************************************************** +Load achievements from a JSON string. +*****************************************************************************/ + +static INLINE const char *cheevos_dupstr(const cheevos_field_t *field) +{ + char *string = (char*)malloc(field->length + 1); + + if (!string) + return NULL; + + memcpy ((void*)string, (void*)field->string, field->length); + string[field->length] = 0; + + return string; +} + +static int cheevos_new_cheevo(cheevos_readud_t *ud) +{ + cheevo_t *cheevo = NULL; + long flags = strtol(ud->flags.string, NULL, 10); + + if (flags == 3) + cheevo = cheevos_locals.core.cheevos + ud->core_count++; + else if (flags == 5) + cheevo = cheevos_locals.unofficial.cheevos + ud->unofficial_count++; + else + return 0; + + cheevo->id = (unsigned)strtol(ud->id.string, NULL, 10); + cheevo->title = cheevos_dupstr(&ud->title); + cheevo->description = cheevos_dupstr(&ud->desc); + cheevo->author = cheevos_dupstr(&ud->author); + cheevo->badge = cheevos_dupstr(&ud->badge); + cheevo->points = (unsigned)strtol(ud->points.string, NULL, 10); + cheevo->dirty = 0; + cheevo->active = CHEEVOS_ACTIVE_SOFTCORE | CHEEVOS_ACTIVE_HARDCORE; + cheevo->last = 1; + cheevo->modified = 0; + + if ( !cheevo->title || + !cheevo->description || + !cheevo->author || + !cheevo->badge) + goto error; + + if (cheevos_parse_condition(&cheevo->condition, ud->memaddr.string)) + goto error; + + return 0; + +error: + if (cheevo->title) + { + free((void*)cheevo->title); + cheevo->title = NULL; + } + if (cheevo->description) + { + free((void*)cheevo->description); + cheevo->description = NULL; + } + if (cheevo->author) + { + free((void*)cheevo->author); + cheevo->author = NULL; + } + if (cheevo->badge) + { + free((void*)cheevo->badge); + cheevo->badge = NULL; + } + return -1; +} + +/***************************************************************************** +Helper functions for displaying leaderboard values. +*****************************************************************************/ + +static void cheevos_format_value(const unsigned value, const unsigned type, + char* formatted_value, size_t formatted_size) +{ + unsigned mins, secs, millis; + + switch(type) + { + case CHEEVOS_FORMAT_VALUE: + snprintf(formatted_value, formatted_size, "%u", value); + break; + + case CHEEVOS_FORMAT_SCORE: + snprintf(formatted_value, formatted_size, + "%06upts", value); + break; + + case CHEEVOS_FORMAT_FRAMES: + mins = value / 3600; + secs = (value % 3600) / 60; + millis = (int) (value % 60) * (10.00 / 6.00); + snprintf(formatted_value, formatted_size, + "%02u:%02u.%02u", mins, secs, millis); + break; + + case CHEEVOS_FORMAT_MILLIS: + mins = value / 6000; + secs = (value % 6000) / 100; + millis = (int) (value % 100); + snprintf(formatted_value, formatted_size, + "%02u:%02u.%02u", mins, secs, millis); + break; + + case CHEEVOS_FORMAT_SECS: + mins = value / 60; + secs = value % 60; + snprintf(formatted_value, formatted_size, + "%02u:%02u", mins, secs); + break; + + default: + snprintf(formatted_value, formatted_size, + "%u (?)", value); + } +} + +unsigned cheevos_parse_format(cheevos_field_t* format) +{ + /* Most likely */ + if (strncmp(format->string, "VALUE", format->length) == 0) + return CHEEVOS_FORMAT_VALUE; + else if (strncmp(format->string, "TIME", format->length) == 0) + return CHEEVOS_FORMAT_FRAMES; + else if (strncmp(format->string, "SCORE", format->length) == 0) + return CHEEVOS_FORMAT_SCORE; + + /* Less likely */ + else if (strncmp(format->string, "MILLISECS", format->length) == 0) + return CHEEVOS_FORMAT_MILLIS; + else if (strncmp(format->string, "TIMESECS", format->length) == 0) + return CHEEVOS_FORMAT_SECS; + + /* Rare (RPS only) */ + else if (strncmp(format->string, "POINTS", format->length) == 0) + return CHEEVOS_FORMAT_SCORE; + else if (strncmp(format->string, "FRAMES", format->length) == 0) + return CHEEVOS_FORMAT_FRAMES; + else + return CHEEVOS_FORMAT_OTHER; +} + +static int cheevos_new_lboard(cheevos_readud_t *ud) +{ + cheevos_leaderboard_t *lboard = NULL; + cheevos_leaderboard_t *ldb = cheevos_locals.leaderboards; + + if (!ldb || !ud) + return -1; + + lboard = ldb + ud->lboard_count++; + + lboard->id = (unsigned)strtol(ud->id.string, NULL, 10); + lboard->format = cheevos_parse_format(&ud->format); + 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; + + return 0; + +error: + if ((void*)lboard->title) + free((void*)lboard->title); + if ((void*)lboard->description) + 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; + + if (ud) + { + int common = ud->in_cheevos || ud->in_lboards; + uint32_t hash = cheevos_djb2(name, length); + ud->field = NULL; + + switch (hash) + { + 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 (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 (common) + ud->field = &ud->title; + break; + case CHEEVOS_JSON_KEY_DESCRIPTION: + if (common) + ud->field = &ud->desc; + break; + case CHEEVOS_JSON_KEY_POINTS: + if (ud->in_cheevos) + ud->field = &ud->points; + break; + case CHEEVOS_JSON_KEY_AUTHOR: + if (ud->in_cheevos) + ud->field = &ud->author; + break; + case CHEEVOS_JSON_KEY_MODIFIED: + if (ud->in_cheevos) + ud->field = &ud->modified; + break; + case CHEEVOS_JSON_KEY_CREATED: + if (ud->in_cheevos) + ud->field = &ud->created; + break; + case CHEEVOS_JSON_KEY_BADGENAME: + if (ud->in_cheevos) + ud->field = &ud->badge; + break; + case CHEEVOS_JSON_KEY_FLAGS: + 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; + } + } + + return 0; +} + +static int cheevos_read__json_string(void *userdata, + const char *string, size_t length) +{ + cheevos_readud_t *ud = (cheevos_readud_t*)userdata; + + if (ud && ud->field) + { + ud->field->string = string; + ud->field->length = length; + } + + return 0; +} + +static int cheevos_read__json_number(void *userdata, + const char *number, size_t length) +{ + cheevos_readud_t *ud = (cheevos_readud_t*)userdata; + + if (ud) + { + if (ud->field) + { + ud->field->string = number; + ud->field->length = length; + } + else if (ud->is_console_id) + { + cheevos_locals.console_id = (cheevos_console_t) + strtol(number, NULL, 10); + ud->is_console_id = 0; + } + } + + return 0; +} + +static int cheevos_read__json_end_object(void *userdata) +{ + cheevos_readud_t *ud = (cheevos_readud_t*)userdata; + + if (ud) + { + if (ud->in_cheevos) + return cheevos_new_cheevo(ud); + if (ud->in_lboards) + return cheevos_new_lboard(ud); + } + + return 0; +} + +static int cheevos_read__json_end_array(void *userdata) +{ + cheevos_readud_t *ud = (cheevos_readud_t*)userdata; + + if (ud) + { + ud->in_cheevos = 0; + ud->in_lboards = 0; + } + + return 0; +} + +static int cheevos_parse(const char *json) +{ + static const jsonsax_handlers_t handlers = + { + NULL, + NULL, + NULL, + cheevos_read__json_end_object, + NULL, + cheevos_read__json_end_array, + cheevos_read__json_key, + NULL, + cheevos_read__json_string, + cheevos_read__json_number, + NULL, + NULL + }; + + cheevos_readud_t ud; + unsigned core_count, unofficial_count, lboard_count; + /* Count the number of achievements in the JSON file. */ + int res = cheevos_count_cheevos(json, &core_count, &unofficial_count, + &lboard_count); + + if (res != JSONSAX_OK) + return -1; + + /* Allocate the achievements. */ + + cheevos_locals.core.cheevos = (cheevo_t*) + calloc(core_count, sizeof(cheevo_t)); + cheevos_locals.core.count = core_count; + + cheevos_locals.unofficial.cheevos = (cheevo_t*) + calloc(unofficial_count, sizeof(cheevo_t)); + cheevos_locals.unofficial.count = unofficial_count; + + 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) + { + if ((void*)cheevos_locals.core.cheevos) + free((void*)cheevos_locals.core.cheevos); + if ((void*)cheevos_locals.unofficial.cheevos) + free((void*)cheevos_locals.unofficial.cheevos); + if ((void*)cheevos_locals.leaderboards) + 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; + + return 0; + +error: + cheevos_unload(); + + return -1; +} + +/***************************************************************************** +Test all the achievements (call once per frame). +*****************************************************************************/ + +static int cheevos_test_condition(cheevos_cond_t *cond) +{ + unsigned sval = 0; + unsigned tval = 0; + + if (!cond) + return 0; + + sval = cheevos_var_get_value(&cond->source) + + cheevos_locals.add_buffer; + tval = cheevos_var_get_value(&cond->target); + + switch (cond->op) + { + case CHEEVOS_COND_OP_EQUALS: + return (sval == tval); + case CHEEVOS_COND_OP_LESS_THAN: + return (sval < tval); + case CHEEVOS_COND_OP_LESS_THAN_OR_EQUAL: + return (sval <= tval); + case CHEEVOS_COND_OP_GREATER_THAN: + return (sval > tval); + case CHEEVOS_COND_OP_GREATER_THAN_OR_EQUAL: + return (sval >= tval); + case CHEEVOS_COND_OP_NOT_EQUAL_TO: + return (sval != tval); + default: + break; + } + + return 1; +} + +static int cheevos_test_pause_cond_set(const cheevos_condset_t *condset, + int *dirty_conds, int *reset_conds, int process_pause) +{ + int cond_valid = 0; + int set_valid = 1; /* must start true so AND logic works */ + cheevos_cond_t *cond = NULL; + const cheevos_cond_t *end = condset->conds + condset->count; + + cheevos_locals.add_buffer = 0; + cheevos_locals.add_hits = 0; + + for (cond = condset->conds; cond < end; cond++) + { + if (cond->pause != process_pause) + continue; + + if (cond->type == CHEEVOS_COND_TYPE_ADD_SOURCE) + { + cheevos_locals.add_buffer += cheevos_var_get_value(&cond->source); + continue; + } + + if (cond->type == CHEEVOS_COND_TYPE_SUB_SOURCE) + { + cheevos_locals.add_buffer -= cheevos_var_get_value(&cond->source); + continue; + } + + if (cond->type == CHEEVOS_COND_TYPE_ADD_HITS) + { + if (cheevos_test_condition(cond)) + { + cond->curr_hits++; + *dirty_conds = 1; + } + + cheevos_locals.add_hits += cond->curr_hits; + continue; + } + + /* always evaluate the condition to ensure delta values get tracked correctly */ + cond_valid = cheevos_test_condition(cond); + + /* if the condition has a target hit count that has already been met, + * it's automatically true, even if not currently true. */ + if ( (cond->req_hits != 0) && + (cond->curr_hits + cheevos_locals.add_hits) >= cond->req_hits) + { + cond_valid = 1; + } + else if (cond_valid) + { + cond->curr_hits++; + *dirty_conds = 1; + + /* Process this logic, if this condition is true: */ + if (cond->req_hits == 0) + ; /* Not a hit-based requirement: ignore any additional logic! */ + else if ((cond->curr_hits + cheevos_locals.add_hits) < cond->req_hits) + cond_valid = 0; /* HitCount target has not yet been met, condition is not yet valid. */ + } + + cheevos_locals.add_buffer = 0; + cheevos_locals.add_hits = 0; + + if (cond->type == CHEEVOS_COND_TYPE_PAUSE_IF) + { + /* as soon as we find a PauseIf that evaluates to true, + * stop processing the rest of the group. */ + if (cond_valid) + return 1; + + /* if we make it to the end of the function, make sure we are + * indicating nothing matched. if we do find a later PauseIf match, + * it'll automatically return true via the previous condition. */ + set_valid = 0; + + if (cond->req_hits == 0) + { + /* PauseIf didn't evaluate true, and doesn't have a HitCount, + * reset the HitCount to indicate the condition didn't match. */ + if (cond->curr_hits != 0) + { + cond->curr_hits = 0; + *dirty_conds = 1; + } + } + else + { + /* PauseIf has a HitCount that hasn't been met, ignore it for now. */ + } + } + else if (cond->type == CHEEVOS_COND_TYPE_RESET_IF) + { + if (cond_valid) + { + *reset_conds = 1; /* Resets all hits found so far */ + set_valid = 0; /* Cannot be valid if we've hit a reset condition. */ + } + } + else /* Sequential or non-sequential? */ + set_valid &= cond_valid; + } + + return set_valid; +} + +static int cheevos_test_cond_set(const cheevos_condset_t *condset, + int *dirty_conds, int *reset_conds) +{ + int in_pause = 0; + int has_pause = 0; + cheevos_cond_t *cond = NULL; + + if (!condset) + return 1; /* important: empty group must evaluate true */ + + /* the ints below are used for Pause conditions and their dependent AddSource/AddHits. */ + + /* this loop needs to go backwards to check AddSource/AddHits */ + cond = condset->conds + condset->count - 1; + for (; cond >= condset->conds; cond--) + { + if (cond->type == CHEEVOS_COND_TYPE_PAUSE_IF) + { + has_pause = 1; + in_pause = 1; + cond->pause = 1; + } + else if (cond->type == CHEEVOS_COND_TYPE_ADD_SOURCE || + cond->type == CHEEVOS_COND_TYPE_SUB_SOURCE || + cond->type == CHEEVOS_COND_TYPE_ADD_HITS) + { + cond->pause = in_pause; + } + else + { + in_pause = 0; + cond->pause = 0; + } + } + + if (has_pause) + { /* one or more Pause conditions exists, if any of them are true, + * stop processing this group. */ + if (cheevos_test_pause_cond_set(condset, dirty_conds, reset_conds, 1)) + return 0; + } + + /* process the non-Pause conditions to see if the group is true */ + return cheevos_test_pause_cond_set(condset, dirty_conds, reset_conds, 0); +} + +static int cheevos_reset_cond_set(cheevos_condset_t *condset, int deltas) +{ + int dirty = 0; + const cheevos_cond_t *end = NULL; + + if (!condset) + return 0; + + end = condset->conds + condset->count; + + if (deltas) + { + cheevos_cond_t *cond = NULL; + for (cond = condset->conds; cond < end; cond++) + { + dirty |= cond->curr_hits != 0; + + cond->curr_hits = 0; + + cond->source.previous = cond->source.value; + cond->target.previous = cond->target.value; + } + } + else + { + cheevos_cond_t *cond = NULL; + for (cond = condset->conds; cond < end; cond++) + { + dirty |= cond->curr_hits != 0; + cond->curr_hits = 0; + } + } + + return dirty; +} + +static int cheevos_test_cheevo(cheevo_t *cheevo) +{ + int dirty_conds = 0; + int reset_conds = 0; + int ret_val = 0; + int ret_val_sub_cond = 0; + cheevos_condset_t *condset = NULL; + cheevos_condset_t *end = NULL; + + if (!cheevo) + return 0; + + ret_val_sub_cond = cheevo->condition.count == 1; + condset = cheevo->condition.condsets; + + if (!condset) + return 0; + + end = condset + cheevo->condition.count; + + if (condset < end) + { + ret_val = cheevos_test_cond_set(condset, &dirty_conds, &reset_conds); + condset++; + } + + while (condset < end) + { + ret_val_sub_cond |= cheevos_test_cond_set( + condset, &dirty_conds, &reset_conds); + condset++; + } + + if (dirty_conds) + cheevo->dirty |= CHEEVOS_DIRTY_CONDITIONS; + + if (reset_conds) + { + int dirty = 0; + + for (condset = cheevo->condition.condsets; condset < end; condset++) + dirty |= cheevos_reset_cond_set(condset, 0); + + if (dirty) + cheevo->dirty |= CHEEVOS_DIRTY_CONDITIONS; + } + + return (ret_val && ret_val_sub_cond); +} + +static void cheevos_url_encode(const char *str, char *encoded, size_t len) +{ + if (!str) + return; + + while (*str) + { + if ( isalnum((unsigned char)*str) || *str == '-' + || *str == '_' || *str == '.' + || *str == '~') + { + if (len >= 2) + { + *encoded++ = *str++; + len--; + } + else + break; + } + else + { + if (len >= 4) + { + snprintf(encoded, len, "%%%02x", (uint8_t)*str); + encoded += 3; + str++; + len -= 3; + } + else + break; + } + } + + *encoded = 0; +} + +static void cheevos_make_unlock_url(const cheevo_t *cheevo, + char* url, size_t url_size) +{ + settings_t *settings = config_get_ptr(); + + if (!settings) + return; + + snprintf( + url, url_size, + "http://retroachievements.org/dorequest.php?r=awardachievement&u=%s&t=%s&a=%u&h=%d", + settings->arrays.cheevos_username, + cheevos_locals.token, + cheevo->id, + settings->bools.cheevos_hardcore_mode_enable && !cheevos_hardcore_paused ? 1 : 0 + ); + + url[url_size - 1] = 0; + +#ifdef CHEEVOS_LOG_URLS + cheevos_log_url("[CHEEVOS]: url to award the cheevo: %s\n", url); +#endif +} + +static void cheevos_unlocked(void *task_data, void *user_data, + const char *error) +{ + cheevo_t *cheevo = (cheevo_t *)user_data; + + if (!error) + { + CHEEVOS_LOG("[CHEEVOS]: awarded achievement %u.\n", cheevo->id); + } + else + { + char url[256]; + url[0] = '\0'; + + CHEEVOS_ERR("[CHEEVOS]: error awarding achievement %u, retrying...\n", cheevo->id); + + cheevos_make_unlock_url(cheevo, url, sizeof(url)); + task_push_http_transfer(url, true, NULL, cheevos_unlocked, cheevo); + } +} + +static void cheevos_test_cheevo_set(const cheevoset_t *set) +{ + settings_t *settings = config_get_ptr(); + int mode = CHEEVOS_ACTIVE_SOFTCORE; + cheevo_t *cheevo = NULL; + const cheevo_t *end = NULL; + + if (!set) + return; + + end = set->cheevos + set->count; + + if (settings && settings->bools.cheevos_hardcore_mode_enable && !cheevos_hardcore_paused) + mode = CHEEVOS_ACTIVE_HARDCORE; + + for (cheevo = set->cheevos; cheevo < end; cheevo++) + { + if (cheevo->active & mode) + { + int valid = cheevos_test_cheevo(cheevo); + + if (cheevo->last) + { + cheevos_condset_t* condset = cheevo->condition.condsets; + const cheevos_condset_t* end = cheevo->condition.condsets + + cheevo->condition.count; + + for (; condset < end; condset++) + cheevos_reset_cond_set(condset, 0); + } + else if (valid) + { + char msg[256]; + char url[256]; + msg[0] = url[0] = '\0'; + + cheevo->active &= ~mode; + + if (mode == CHEEVOS_ACTIVE_HARDCORE) + cheevo->active &= ~CHEEVOS_ACTIVE_SOFTCORE; + + CHEEVOS_LOG("[CHEEVOS]: awarding cheevo %u: %s (%s).\n", + cheevo->id, cheevo->title, cheevo->description); + + snprintf(msg, sizeof(msg), "Achievement Unlocked: %s", + cheevo->title); + msg[sizeof(msg) - 1] = 0; + runloop_msg_queue_push(msg, 0, 2 * 60, false); + runloop_msg_queue_push(cheevo->description, 0, 3 * 60, false); + + cheevos_make_unlock_url(cheevo, url, sizeof(url)); + task_push_http_transfer(url, true, NULL, + cheevos_unlocked, cheevo); + + if (settings && settings->bools.cheevos_auto_screenshot) + { + char shotname[256]; + + snprintf(shotname, sizeof(shotname), "%s/%s-cheevo-%u", + settings->paths.directory_screenshot, + path_basename(path_get(RARCH_PATH_BASENAME)), + cheevo->id); + shotname[sizeof(shotname) - 1] = '\0'; + + if (take_screenshot(shotname, true, + video_driver_cached_frame_has_valid_framebuffer(), false, true)) + CHEEVOS_LOG("[CHEEVOS]: got a screenshot for cheevo %u\n", cheevo->id); + else + CHEEVOS_LOG("[CHEEVOS]: failed to get screenshot for cheevo %u\n", cheevo->id); + } + } + + cheevo->last = valid; + } + } +} + +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 = 0; + cheevos_condset_t *condset = NULL; + const cheevos_condset_t *end = NULL; + + if (!condition) + return 0; + + ret_val_sub_cond = condition->count == 1; + condset = condition->condsets; + end = condset + condition->count; + + if (condset < end) + { + ret_val = cheevos_test_cond_set( + condset, &dirty_conds, &reset_conds); + condset++; + } + + while (condset < end) + { + ret_val_sub_cond |= cheevos_test_cond_set( + condset, &dirty_conds, &reset_conds); + 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 int cheevos_expr_value(cheevos_expr_t* expr) +{ + unsigned i; + int values[16]; + /* Separate possible values with '$' operator, submit the largest */ + unsigned current_value = 0; + cheevos_term_t* term = NULL; + + if (!expr) + return 0; + + term = expr->terms; + + if (!term) + return 0; + + if (expr->compare_count >= ARRAY_SIZE(values)) + { + CHEEVOS_ERR("[CHEEVOS]: too many values in the leaderboard expression: %u\n", expr->compare_count); + return 0; + } + + memset(values, 0, sizeof values); + + for (i = expr->count; i != 0; i--, term++) + { + if (current_value >= ARRAY_SIZE(values)) + { + CHEEVOS_ERR("[CHEEVOS]: too many values in the leaderboard expression: %u\n", current_value); + return 0; + } + + values[current_value] += + cheevos_var_get_value(&term->var) * term->multiplier; + + if (term->compare_next) + current_value++; + } + + if (expr->compare_count > 1) + { + unsigned j; + int maximum = values[0]; + + for (j = 1; j < expr->compare_count; j++) + maximum = values[j] > maximum ? values[j] : maximum; + + return maximum; + } + else + return values[0]; +} + +static void cheevos_make_lboard_url(const cheevos_leaderboard_t *lboard, + char* url, size_t url_size) +{ + MD5_CTX ctx; + uint8_t hash[16]; + char signature[64]; + settings_t *settings = config_get_ptr(); + + hash[0] = '\0'; + + snprintf(signature, sizeof(signature), "%u%s%u", lboard->id, + settings->arrays.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=%d" + "&v=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + settings->arrays.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 (!lboard) + return; + + if (!error) + { + CHEEVOS_ERR("[CHEEVOS]: error submitting leaderboard %u\n", lboard->id); + return; + } + + CHEEVOS_LOG("[CHEEVOS]: submitted leaderboard %u.\n", lboard->id); +} + +static void cheevos_test_leaderboards(void) +{ + unsigned i; + cheevos_leaderboard_t* lboard = cheevos_locals.leaderboards; + + if (!lboard) + return; + + for (i = cheevos_locals.lboard_count; i != 0; i--, lboard++) + { + if (lboard->active) + { + int value = cheevos_expr_value(&lboard->value); + + if (value != lboard->last_value) + { + CHEEVOS_LOG("[CHEEVOS]: value lboard %s %u\n", + lboard->title, value); + lboard->last_value = value; + } + + if (cheevos_test_lboard_condition(&lboard->submit)) + { + lboard->active = 0; + + /* failsafe for improper LBs */ + if (value == 0) + { + CHEEVOS_LOG("[CHEEVOS]: error: lboard %s tried to submit 0\n", + lboard->title); + runloop_msg_queue_push("Leaderboard attempt cancelled!", + 0, 2 * 60, false); + } + else + { + char url[256]; + char msg[256]; + char formatted_value[16]; + + cheevos_make_lboard_url(lboard, url, sizeof(url)); + task_push_http_transfer(url, true, NULL, + cheevos_lboard_submit, lboard); + CHEEVOS_LOG("[CHEEVOS]: submit lboard %s\n", lboard->title); + + cheevos_format_value(value, lboard->format, + formatted_value, sizeof(formatted_value)); + snprintf(msg, sizeof(msg), "Submitted %s for %s", + formatted_value, lboard->title); + msg[sizeof(msg) - 1] = 0; + runloop_msg_queue_push(msg, 0, 2 * 60, false); + } + } + + if (cheevos_test_lboard_condition(&lboard->cancel)) + { + CHEEVOS_LOG("[CHEEVOS]: cancel lboard %s\n", lboard->title); + lboard->active = 0; + runloop_msg_queue_push("Leaderboard attempt cancelled!", + 0, 2 * 60, false); + } + } + else + { + if (cheevos_test_lboard_condition(&lboard->start)) + { + char msg[256]; + + CHEEVOS_LOG("[CHEEVOS]: start lboard %s\n", lboard->title); + lboard->active = 1; + lboard->last_value = -1; + + snprintf(msg, sizeof(msg), + "Leaderboard Active: %s", lboard->title); + msg[sizeof(msg) - 1] = 0; + runloop_msg_queue_push(msg, 0, 2 * 60, false); + runloop_msg_queue_push(lboard->description, 0, 3*60, false); + } + } + } +} + +/***************************************************************************** +Free the loaded achievements. +*****************************************************************************/ + +static void cheevos_free_condset(const cheevos_condset_t *set) +{ + if (set && set->conds) + free((void*)set->conds); +} + +static void cheevos_free_cheevo(const cheevo_t *cheevo) +{ + if (!cheevo) + return; + + if (cheevo->title) + free((void*)cheevo->title); + if (cheevo->description) + free((void*)cheevo->description); + if (cheevo->author) + free((void*)cheevo->author); + if (cheevo->badge) + free((void*)cheevo->badge); + cheevos_free_condset(cheevo->condition.condsets); +} + +static void cheevos_free_cheevo_set(const cheevoset_t *set) +{ + const cheevo_t *cheevo = NULL; + const cheevo_t *end = NULL; + + if (!set) + return; + + cheevo = set->cheevos; + end = cheevo + set->count; + + while (cheevo < end) + cheevos_free_cheevo(cheevo++); + + if (set->cheevos) + free((void*)set->cheevos); +} + +#ifndef CHEEVOS_DONT_DEACTIVATE +static int cheevos_deactivate__json_index(void *userdata, unsigned int index) +{ + cheevos_deactivate_t *ud = (cheevos_deactivate_t*)userdata; + + if (ud) + ud->is_element = 1; + + return 0; +} + +static int cheevos_deactivate__json_number(void *userdata, + const char *number, size_t length) +{ + long id; + int found; + cheevo_t* cheevo = NULL; + const cheevo_t* end = NULL; + cheevos_deactivate_t *ud = (cheevos_deactivate_t*)userdata; + + if (ud && ud->is_element) + { + ud->is_element = 0; + id = strtol(number, NULL, 10); + found = 0; + cheevo = cheevos_locals.core.cheevos; + end = cheevo + cheevos_locals.core.count; + + for (; cheevo < end; cheevo++) + { + if (cheevo->id == (unsigned)id) + { + cheevo->active &= ~ud->mode; + found = 1; + break; + } + } + + if (!found) + { + cheevo = cheevos_locals.unofficial.cheevos; + end = cheevo + cheevos_locals.unofficial.count; + + for (; cheevo < end; cheevo++) + { + if (cheevo->id == (unsigned)id) + { + cheevo->active &= ~ud->mode; + found = 1; + break; + } + } + } + + if (found) + CHEEVOS_LOG("[CHEEVOS]: deactivated unlocked cheevo %u (%s).\n", + cheevo->id, cheevo->title); + else + CHEEVOS_ERR("[CHEEVOS]: unknown cheevo to deactivate: %u.\n", id); + } + + return 0; +} + +static int cheevos_deactivate_unlocks(const char* json, unsigned mode) +{ + static const jsonsax_handlers_t handlers = + { + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + cheevos_deactivate__json_index, + NULL, + cheevos_deactivate__json_number, + NULL, + NULL + }; + + cheevos_deactivate_t ud; + + ud.is_element = 0; + ud.mode = mode; + return jsonsax_parse(json, &handlers, (void*)&ud) != JSONSAX_OK; +} +#endif + +void cheevos_reset_game(void) +{ + cheevo_t *end = NULL; + cheevo_t *cheevo = cheevos_locals.core.cheevos; + + if (!cheevo) + return; + + 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) +{ +#ifdef HAVE_MENU + unsigned i = 0; + unsigned items_found = 0; + settings_t *settings = config_get_ptr(); + menu_displaylist_info_t *info = (menu_displaylist_info_t*)data; + cheevo_t *end = NULL; + cheevo_t *cheevo = cheevos_locals.core.cheevos; + end = cheevo + cheevos_locals.core.count; + + if(settings->bools.cheevos_enable && settings->bools.cheevos_hardcore_mode_enable + && cheevos_loaded) + { + if (!cheevos_hardcore_paused) + menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_PAUSE), + msg_hash_to_str(MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE), + MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE, + MENU_SETTING_ACTION_PAUSE_ACHIEVEMENTS, 0, 0); + else + menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_RESUME), + msg_hash_to_str(MENU_ENUM_LABEL_ACHIEVEMENT_RESUME), + MENU_ENUM_LABEL_ACHIEVEMENT_RESUME, + MENU_SETTING_ACTION_RESUME_ACHIEVEMENTS, 0, 0); + } + + if (cheevo) + { + for (i = 0; cheevo < end; i++, cheevo++) + { + if (!(cheevo->active & CHEEVOS_ACTIVE_HARDCORE)) + { + menu_entries_append_enum(info->list, cheevo->title, + cheevo->description, + MENU_ENUM_LABEL_CHEEVOS_UNLOCKED_ENTRY_HARDCORE, + MENU_SETTINGS_CHEEVOS_START + i, 0, 0); + set_badge_info(&badges_ctx, i, cheevo->badge, + (cheevo->active & CHEEVOS_ACTIVE_HARDCORE)); + } + else if (!(cheevo->active & CHEEVOS_ACTIVE_SOFTCORE)) + { + menu_entries_append_enum(info->list, cheevo->title, + cheevo->description, + MENU_ENUM_LABEL_CHEEVOS_UNLOCKED_ENTRY, + MENU_SETTINGS_CHEEVOS_START + i, 0, 0); + set_badge_info(&badges_ctx, i, cheevo->badge, + (cheevo->active & CHEEVOS_ACTIVE_SOFTCORE)); + } + else + { + menu_entries_append_enum(info->list, cheevo->title, + cheevo->description, + MENU_ENUM_LABEL_CHEEVOS_LOCKED_ENTRY, + MENU_SETTINGS_CHEEVOS_START + i, 0, 0); + set_badge_info(&badges_ctx, i, cheevo->badge, + (cheevo->active & CHEEVOS_ACTIVE_SOFTCORE)); + } + items_found++; + } + } + + cheevo = cheevos_locals.unofficial.cheevos; + + if (cheevo && settings->bools.cheevos_test_unofficial) + { + end = cheevo + cheevos_locals.unofficial.count; + + for (i = items_found; cheevo < end; i++, cheevo++) + { + if (!(cheevo->active & CHEEVOS_ACTIVE_HARDCORE)) + { + menu_entries_append_enum(info->list, cheevo->title, + cheevo->description, + MENU_ENUM_LABEL_CHEEVOS_UNLOCKED_ENTRY_HARDCORE, + MENU_SETTINGS_CHEEVOS_START + i, 0, 0); + set_badge_info(&badges_ctx, i, cheevo->badge, + (cheevo->active & CHEEVOS_ACTIVE_HARDCORE)); + } + else if (!(cheevo->active & CHEEVOS_ACTIVE_SOFTCORE)) + { + menu_entries_append_enum(info->list, cheevo->title, + cheevo->description, + MENU_ENUM_LABEL_CHEEVOS_UNLOCKED_ENTRY, + MENU_SETTINGS_CHEEVOS_START + i, 0, 0); + set_badge_info(&badges_ctx, i, cheevo->badge, + (cheevo->active & CHEEVOS_ACTIVE_SOFTCORE)); + } + else + { + menu_entries_append_enum(info->list, cheevo->title, + cheevo->description, + MENU_ENUM_LABEL_CHEEVOS_LOCKED_ENTRY, + MENU_SETTINGS_CHEEVOS_START + i, 0, 0); + set_badge_info(&badges_ctx, i, cheevo->badge, + (cheevo->active & CHEEVOS_ACTIVE_SOFTCORE)); + } + items_found++; + } + } + + if (items_found == 0) + { + menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_ACHIEVEMENTS_TO_DISPLAY), + msg_hash_to_str(MENU_ENUM_LABEL_NO_ACHIEVEMENTS_TO_DISPLAY), + MENU_ENUM_LABEL_NO_ACHIEVEMENTS_TO_DISPLAY, + FILE_TYPE_NONE, 0, 0); + } +#endif +} + +bool cheevos_get_description(cheevos_ctx_desc_t *desc) +{ + if (!desc) + return false; + + if (cheevos_loaded) + { + cheevo_t *cheevos = cheevos_locals.core.cheevos; + + if (!cheevos) + return false; + + if (desc->idx >= cheevos_locals.core.count) + { + cheevos = cheevos_locals.unofficial.cheevos; + desc->idx -= cheevos_locals.core.count; + } + + if (!string_is_empty(cheevos[desc->idx].description)) + strlcpy(desc->s, cheevos[desc->idx].description, desc->len); + } + else + *desc->s = 0; + + return true; +} + +bool cheevos_apply_cheats(bool *data_bool) +{ + cheats_are_enabled = *data_bool; + cheats_were_enabled |= cheats_are_enabled; + + return true; +} + +bool cheevos_unload(void) +{ + bool running; + CHEEVOS_LOCK(cheevos_locals.task_lock); + running = cheevos_locals.task != NULL; + CHEEVOS_UNLOCK(cheevos_locals.task_lock); + + if (running) + { + CHEEVOS_LOG("[CHEEVOS]: Asked the load thread to terminate\n"); + task_queue_cancel_task(cheevos_locals.task); + +#ifdef HAVE_THREADS + do + { + CHEEVOS_LOCK(cheevos_locals.task_lock); + running = cheevos_locals.task != NULL; + CHEEVOS_UNLOCK(cheevos_locals.task_lock); + } + while (running); +#endif + } + + if (cheevos_loaded) + { + cheevos_free_cheevo_set(&cheevos_locals.core); + cheevos_free_cheevo_set(&cheevos_locals.unofficial); + } + + cheevos_locals.core.cheevos = NULL; + cheevos_locals.unofficial.cheevos = NULL; + cheevos_locals.core.count = 0; + cheevos_locals.unofficial.count = 0; + + cheevos_loaded = false; + cheevos_hardcore_paused = false; + + return true; +} + +bool cheevos_toggle_hardcore_mode(void) +{ + settings_t *settings = config_get_ptr(); + + if (!settings) + return false; + + /* reset and deinit rewind to avoid cheat the score */ + if (settings->bools.cheevos_hardcore_mode_enable && !cheevos_hardcore_paused) + { + const char *msg = msg_hash_to_str( + MSG_CHEEVOS_HARDCORE_MODE_ENABLE); + + /* send reset core cmd to avoid any user + * savestate previusly loaded. */ + command_event(CMD_EVENT_RESET, NULL); + + if (settings->bools.rewind_enable) + command_event(CMD_EVENT_REWIND_DEINIT, NULL); + + CHEEVOS_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 0, 3 * 60, true); + } + else + { + if (settings->bools.rewind_enable) + command_event(CMD_EVENT_REWIND_INIT, NULL); + } + + return true; +} + +static void cheevos_patch_addresses(cheevoset_t* set) +{ + unsigned i; + cheevo_t* cheevo = NULL; + + if (!set) + return; + + cheevo = set->cheevos; + + if (!cheevo) + return; + + for (i = set->count; i != 0; i--, cheevo++) + { + unsigned j; + cheevos_condset_t* condset = cheevo->condition.condsets; + + for (j = cheevo->condition.count; j != 0; j--, condset++) + { + unsigned k; + cheevos_cond_t* cond = condset->conds; + + for (k = condset->count; k != 0; k--, cond++) + { + switch (cond->source.type) + { + case CHEEVOS_VAR_TYPE_ADDRESS: + case CHEEVOS_VAR_TYPE_DELTA_MEM: + cheevos_var_patch_addr(&cond->source, + cheevos_locals.console_id); +#ifdef CHEEVOS_DUMP_ADDRS + CHEEVOS_LOG("[CHEEVOS]: s-var %03d:%08X\n", + cond->source.bank_id + 1, cond->source.value); +#endif + break; + + default: + break; + } + + switch (cond->target.type) + { + case CHEEVOS_VAR_TYPE_ADDRESS: + case CHEEVOS_VAR_TYPE_DELTA_MEM: + cheevos_var_patch_addr(&cond->target, + cheevos_locals.console_id); +#ifdef CHEEVOS_DUMP_ADDRS + CHEEVOS_LOG("[CHEEVOS]: t-var %03d:%08X\n", + cond->target.bank_id + 1, cond->target.value); +#endif + break; + + default: + break; + } + } + } + } +} + +static void cheevos_patch_lb_conditions(cheevos_condition_t* condition) +{ + unsigned i; + cheevos_condset_t* condset = NULL; + + if (!condition) + return; + + condset = condition->condsets; + + for (i = condition->count; i != 0; i--, condset++) + { + unsigned j; + cheevos_cond_t* cond = condset->conds; + + for (j = condset->count; j != 0; j--, cond++) + { + switch (cond->source.type) + { + case CHEEVOS_VAR_TYPE_ADDRESS: + case CHEEVOS_VAR_TYPE_DELTA_MEM: + cheevos_var_patch_addr(&cond->source, + cheevos_locals.console_id); +#ifdef CHEEVOS_DUMP_ADDRS + CHEEVOS_LOG("[CHEEVOS]: s-var %03d:%08X\n", + cond->source.bank_id + 1, cond->source.value); +#endif + break; + default: + break; + } + switch (cond->target.type) + { + case CHEEVOS_VAR_TYPE_ADDRESS: + case CHEEVOS_VAR_TYPE_DELTA_MEM: + cheevos_var_patch_addr(&cond->target, + cheevos_locals.console_id); +#ifdef CHEEVOS_DUMP_ADDRS + CHEEVOS_LOG("[CHEEVOS]: t-var %03d:%08X\n", + cond->target.bank_id + 1, cond->target.value); +#endif + break; + default: + break; + } + } + } +} + +static void cheevos_patch_lb_expressions(cheevos_expr_t* expression) +{ + unsigned i; + cheevos_term_t* term = NULL; + + if (!expression) + return; + + term = expression->terms; + + for (i = expression->count; i != 0; i--, term++) + { + switch (term->var.type) + { + case CHEEVOS_VAR_TYPE_ADDRESS: + case CHEEVOS_VAR_TYPE_DELTA_MEM: + cheevos_var_patch_addr(&term->var, cheevos_locals.console_id); +#ifdef CHEEVOS_DUMP_ADDRS + CHEEVOS_LOG("[CHEEVOS]: s-var %03d:%08X\n", + term->var.bank_id + 1, term->var.value); +#endif + break; + default: + break; + } + } +} + +static void cheevos_patch_lbs(cheevos_leaderboard_t *leaderboard) +{ + unsigned i; + + for (i = 0; i < cheevos_locals.lboard_count; i++) + { + cheevos_condition_t *start = &leaderboard[i].start; + cheevos_condition_t *cancel = &leaderboard[i].cancel; + cheevos_condition_t *submit = &leaderboard[i].submit; + cheevos_expr_t *value = &leaderboard[i].value; + + cheevos_patch_lb_conditions(start); + cheevos_patch_lb_conditions(cancel); + cheevos_patch_lb_conditions(submit); + cheevos_patch_lb_expressions(value); + } +} + +void cheevos_test(void) +{ + settings_t *settings = config_get_ptr(); + + if (!cheevos_locals.addrs_patched) + { + cheevos_patch_addresses(&cheevos_locals.core); + cheevos_patch_addresses(&cheevos_locals.unofficial); + cheevos_patch_lbs(cheevos_locals.leaderboards); + + cheevos_locals.addrs_patched = true; + } + + cheevos_test_cheevo_set(&cheevos_locals.core); + + if (settings) + { + if (settings->bools.cheevos_test_unofficial) + cheevos_test_cheevo_set(&cheevos_locals.unofficial); + + if (settings->bools.cheevos_hardcore_mode_enable && + settings->bools.cheevos_leaderboards_enable && + !cheevos_hardcore_paused) + cheevos_test_leaderboards(); + } +} + +bool cheevos_set_cheats(void) +{ + cheats_were_enabled = cheats_are_enabled; + return true; +} + +void cheevos_set_support_cheevos(bool state) +{ + cheevos_locals.core_supports = state; +} + +bool cheevos_get_support_cheevos(void) +{ + return cheevos_locals.core_supports; +} + +cheevos_console_t cheevos_get_console(void) +{ + return cheevos_locals.console_id; +} + +#include "coro.h" + +/* Uncomment the following two lines to debug cheevos_iterate, this will + * disable the coroutine yielding. + * + * The code is very easy to understand. It's meant to be like BASIC: + * CORO_GOTO will jump execution to another label, CORO_GOSUB will + * call another label, and CORO_RET will return from a CORO_GOSUB. + * + * This coroutine code is inspired in a very old pure C implementation + * that runs everywhere: + * + * https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html + */ +/*#undef CORO_YIELD +#define CORO_YIELD()*/ + +typedef struct +{ + /* variables used in the co-routine */ + char badge_name[16]; + char url[256]; + char badge_basepath[PATH_MAX_LENGTH]; + char badge_fullpath[PATH_MAX_LENGTH]; + unsigned char hash[16]; + bool round; + unsigned gameid; + unsigned i; + unsigned j; + unsigned k; + size_t bytes; + size_t count; + size_t offset; + size_t len; + size_t size; + MD5_CTX md5; + cheevos_nes_header_t header; + retro_time_t t0; + struct retro_system_info sysinfo; + void *data; + char *json; + const char *path; + const char *ext; + intfstream_t *stream; + cheevo_t *cheevo; + settings_t *settings; + struct http_connection_t *conn; + struct http_t *http; + const cheevo_t *cheevo_end; + + /* co-routine required fields */ + CORO_FIELDS +} coro_t; + +enum +{ + /* Negative values because CORO_SUB generates positive values */ + SNES_MD5 = -1, + GENESIS_MD5 = -2, + LYNX_MD5 = -3, + NES_MD5 = -4, + GENERIC_MD5 = -5, + FILENAME_MD5 = -6, + EVAL_MD5 = -7, + FILL_MD5 = -8, + GET_GAMEID = -9, + GET_CHEEVOS = -10, + GET_BADGES = -11, + LOGIN = -12, + HTTP_GET = -13, + DEACTIVATE = -14, + PLAYING = -15, + DELAY = -16 +}; + +static int cheevos_iterate(coro_t *coro) +{ + ssize_t num_read = 0; + size_t to_read = 4096; + uint8_t *buffer = NULL; + const char *end = NULL; + + static const uint32_t genesis_exts[] = + { + 0x0b888feeU, /* mdx */ + 0x005978b6U, /* md */ + 0x0b88aa89U, /* smd */ + 0x0b88767fU, /* gen */ + 0x0b8861beU, /* bin */ + 0x0b886782U, /* cue */ + 0x0b8880d0U, /* iso */ + 0x0b88aa98U, /* sms */ + 0x005977f3U, /* gg */ + 0x0059797fU, /* sg */ + 0 + }; + + static const uint32_t snes_exts[] = + { + 0x0b88aa88U, /* smc */ + 0x0b8872bbU, /* fig */ + 0x0b88a9a1U, /* sfc */ + 0x0b887623U, /* gd3 */ + 0x0b887627U, /* gd7 */ + 0x0b886bf3U, /* dx2 */ + 0x0b886312U, /* bsx */ + 0x0b88abd2U, /* swc */ + 0 + }; + + static const uint32_t lynx_exts[] = + { + 0x0b888cf7U, /* lnx */ + 0 + }; + + static cheevos_finder_t finders[] = + { + {SNES_MD5, "SNES (8Mb padding)", snes_exts}, + {GENESIS_MD5, "Genesis (6Mb padding)", genesis_exts}, + {LYNX_MD5, "Atari Lynx (only first 512 bytes)", lynx_exts}, + {NES_MD5, "NES (discards VROM)", NULL}, + {GENERIC_MD5, "Generic (plain content)", NULL}, + {FILENAME_MD5, "Generic (filename)", NULL} + }; + + CORO_ENTER(); + + + + cheevos_locals.addrs_patched = false; + + coro->settings = config_get_ptr(); + + cheevos_locals.meminfo[0].id = RETRO_MEMORY_SYSTEM_RAM; + core_get_memory(&cheevos_locals.meminfo[0]); + + cheevos_locals.meminfo[1].id = RETRO_MEMORY_SAVE_RAM; + core_get_memory(&cheevos_locals.meminfo[1]); + + cheevos_locals.meminfo[2].id = RETRO_MEMORY_VIDEO_RAM; + core_get_memory(&cheevos_locals.meminfo[2]); + + cheevos_locals.meminfo[3].id = RETRO_MEMORY_RTC; + core_get_memory(&cheevos_locals.meminfo[3]); + + CHEEVOS_LOG("[CHEEVOS]: system RAM: %p %u\n", + cheevos_locals.meminfo[0].data, + cheevos_locals.meminfo[0].size); + CHEEVOS_LOG("[CHEEVOS]: save RAM: %p %u\n", + cheevos_locals.meminfo[1].data, + cheevos_locals.meminfo[1].size); + CHEEVOS_LOG("[CHEEVOS]: video RAM: %p %u\n", + cheevos_locals.meminfo[2].data, + cheevos_locals.meminfo[2].size); + CHEEVOS_LOG("[CHEEVOS]: RTC: %p %u\n", + cheevos_locals.meminfo[3].data, + cheevos_locals.meminfo[3].size); + + /* Bail out if cheevos are disabled. + * But set the above anyways, + * command_read_ram needs it. */ + if (!coro->settings->bools.cheevos_enable) + CORO_STOP(); + + /* Load the content into memory, or copy it + * over to our own buffer */ + if (!coro->data) + { + coro->stream = intfstream_open_file( + coro->path, + RETRO_VFS_FILE_ACCESS_READ, + RETRO_VFS_FILE_ACCESS_HINT_NONE); + + if (!coro->stream) + CORO_STOP(); + + CORO_YIELD(); + coro->len = 0; + coro->count = intfstream_get_size(coro->stream); + + /* size limit */ + if (coro->count > size_in_megabytes(64)) + coro->count = size_in_megabytes(64); + + coro->data = malloc(coro->count); + + if (!coro->data) + { + intfstream_close(coro->stream); + free(coro->stream); + CORO_STOP(); + } + + for (;;) + { + buffer = (uint8_t*)coro->data + coro->len; + to_read = 4096; + + if (to_read > coro->count) + to_read = coro->count; + + num_read = intfstream_read(coro->stream, + (void*)buffer, to_read); + + if (num_read <= 0) + break; + + coro->len += num_read; + coro->count -= num_read; + + if (coro->count == 0) + break; + + CORO_YIELD(); + } + + intfstream_close(coro->stream); + free(coro->stream); + } + + /* Use the supported extensions as a hint + * to what method we should use. */ + core_get_system_info(&coro->sysinfo); + + for (coro->i = 0; coro->i < ARRAY_SIZE(finders); coro->i++) + { + if (finders[coro->i].ext_hashes) + { + coro->ext = coro->sysinfo.valid_extensions; + + while (coro->ext) + { + unsigned hash; + end = strchr(coro->ext, '|'); + + if (end) + { + hash = cheevos_djb2( + coro->ext, end - coro->ext); + coro->ext = end + 1; + } + else + { + hash = cheevos_djb2( + coro->ext, strlen(coro->ext)); + coro->ext = NULL; + } + + for (coro->j = 0; finders[coro->i].ext_hashes[coro->j]; coro->j++) + { + if (finders[coro->i].ext_hashes[coro->j] == hash) + { + CHEEVOS_LOG("[CHEEVOS]: testing %s.\n", + finders[coro->i].name); + + /* + * Inputs: CHEEVOS_VAR_INFO + * Outputs: CHEEVOS_VAR_GAMEID, the game was found if it's different from 0 + */ + CORO_GOSUB(finders[coro->i].label); + + if (coro->gameid != 0) + goto found; + + coro->ext = NULL; /* force next finder */ + break; + } + } + } + } + } + + for (coro->i = 0; coro->i < ARRAY_SIZE(finders); coro->i++) + { + if (finders[coro->i].ext_hashes) + continue; + + CHEEVOS_LOG("[CHEEVOS]: testing %s.\n", + finders[coro->i].name); + + /* + * Inputs: CHEEVOS_VAR_INFO + * Outputs: CHEEVOS_VAR_GAMEID + */ + CORO_GOSUB(finders[coro->i].label); + + if (coro->gameid != 0) + goto found; + } + + CHEEVOS_LOG("[CHEEVOS]: this game doesn't feature achievements.\n"); + CORO_STOP(); + +found: + +#ifdef CHEEVOS_JSON_OVERRIDE + { + size_t size = 0; + FILE *file = fopen(CHEEVOS_JSON_OVERRIDE, "rb"); + + fseek(file, 0, SEEK_END); + size = ftell(file); + fseek(file, 0, SEEK_SET); + + coro->json = (char*)malloc(size + 1); + fread((void*)coro->json, 1, size, file); + + fclose(file); + coro->json[size] = 0; + } +#else + CORO_GOSUB(GET_CHEEVOS); + + if (!coro->json) + { + runloop_msg_queue_push("Error loading achievements.", 0, 5 * 60, false); + CHEEVOS_ERR("[CHEEVOS]: error loading achievements.\n"); + CORO_STOP(); + } +#endif + +#ifdef CHEEVOS_SAVE_JSON + { + FILE *file = fopen(CHEEVOS_SAVE_JSON, "w"); + fwrite((void*)coro->json, 1, strlen(coro->json), file); + fclose(file); + } +#endif + if (cheevos_parse(coro->json)) + { + if ((void*)coro->json) + free((void*)coro->json); + CORO_STOP(); + } + + if ((void*)coro->json) + free((void*)coro->json); + + if ( cheevos_locals.core.count == 0 + && cheevos_locals.unofficial.count == 0 + && cheevos_locals.lboard_count == 0) + { + runloop_msg_queue_push( + "This game has no achievements.", + 0, 5 * 60, false); + + cheevos_free_cheevo_set(&cheevos_locals.core); + cheevos_free_cheevo_set(&cheevos_locals.unofficial); + + cheevos_locals.core.cheevos = NULL; + cheevos_locals.unofficial.cheevos = NULL; + cheevos_locals.core.count = 0; + cheevos_locals.unofficial.count = 0; + + cheevos_loaded = false; + cheevos_hardcore_paused = false; + CORO_STOP(); + } + + cheevos_loaded = true; + + /* + * Inputs: CHEEVOS_VAR_GAMEID + * Outputs: + */ + CORO_GOSUB(DEACTIVATE); + + /* + * Inputs: CHEEVOS_VAR_GAMEID + * Outputs: + */ + CORO_GOSUB(PLAYING); + + if (coro->settings->bools.cheevos_verbose_enable && cheevos_locals.core.count > 0) + { + char msg[256]; + int mode = CHEEVOS_ACTIVE_SOFTCORE; + const cheevo_t* cheevo = cheevos_locals.core.cheevos; + const cheevo_t* end = cheevo + cheevos_locals.core.count; + int number_of_unlocked = cheevos_locals.core.count; + + if (coro->settings->bools.cheevos_hardcore_mode_enable && !cheevos_hardcore_paused) + mode = CHEEVOS_ACTIVE_HARDCORE; + + for (; cheevo < end; cheevo++) + if (cheevo->active & mode) + number_of_unlocked--; + + snprintf(msg, sizeof(msg), + "You have %d of %d achievements unlocked.", + number_of_unlocked, cheevos_locals.core.count); + msg[sizeof(msg) - 1] = 0; + runloop_msg_queue_push(msg, 0, 6 * 60, false); + } + + CORO_GOSUB(GET_BADGES); + CORO_STOP(); + + /************************************************************************** + * Info Tries to identify a SNES game + * Input CHEEVOS_VAR_INFO the content info + * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found + *************************************************************************/ + CORO_SUB(SNES_MD5) + + MD5_Init(&coro->md5); + + coro->offset = 0; + coro->count = 0; + + CORO_GOSUB(EVAL_MD5); + + if (coro->count == 0) + { + MD5_Final(coro->hash, &coro->md5); + coro->gameid = 0; + CORO_RET(); + } + + if (coro->count < size_in_megabytes(8)) + { + /* + * Inputs: CHEEVOS_VAR_MD5, CHEEVOS_VAR_OFFSET, CHEEVOS_VAR_COUNT + * Outputs: CHEEVOS_VAR_MD5 + */ + coro->offset = 0; + coro->count = size_in_megabytes(8) - coro->count; + CORO_GOSUB(FILL_MD5); + } + + MD5_Final(coro->hash, &coro->md5); + CORO_GOTO(GET_GAMEID); + + /************************************************************************** + * Info Tries to identify a Genesis game + * Input CHEEVOS_VAR_INFO the content info + * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found + *************************************************************************/ + CORO_SUB(GENESIS_MD5) + + MD5_Init(&coro->md5); + + coro->offset = 0; + coro->count = 0; + CORO_GOSUB(EVAL_MD5); + + if (coro->count == 0) + { + MD5_Final(coro->hash, &coro->md5); + coro->gameid = 0; + CORO_RET(); + } + + if (coro->count < size_in_megabytes(6)) + { + coro->offset = 0; + coro->count = size_in_megabytes(6) - coro->count; + CORO_GOSUB(FILL_MD5); + } + + MD5_Final(coro->hash, &coro->md5); + CORO_GOTO(GET_GAMEID); + + /************************************************************************** + * Info Tries to identify an Atari Lynx game + * Input CHEEVOS_VAR_INFO the content info + * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found + *************************************************************************/ + CORO_SUB(LYNX_MD5) + + if (coro->len < 0x0240) + { + coro->gameid = 0; + CORO_RET(); + } + + MD5_Init(&coro->md5); + + coro->offset = 0x0040; + coro->count = 0x0200; + CORO_GOSUB(EVAL_MD5); + + MD5_Final(coro->hash, &coro->md5); + CORO_GOTO(GET_GAMEID); + + /************************************************************************** + * Info Tries to identify a NES game + * Input CHEEVOS_VAR_INFO the content info + * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found + *************************************************************************/ + CORO_SUB(NES_MD5) + + /* Note about the references to the FCEU emulator below. There is no + * core-specific code in this function, it's rather Retro Achievements + * specific code that must be followed to the letter so we compute + * the correct ROM hash. Retro Achievements does indeed use some + * FCEU related method to compute the hash, since its NES emulator + * is based on it. */ + + if (coro->len < sizeof(coro->header)) + { + coro->gameid = 0; + CORO_RET(); + } + + memcpy((void*)&coro->header, coro->data, + sizeof(coro->header)); + + if ( coro->header.id[0] != 'N' + || coro->header.id[1] != 'E' + || coro->header.id[2] != 'S' + || coro->header.id[3] != 0x1a) + { + coro->gameid = 0; + CORO_RET(); + } + + { + size_t romsize = 256; + /* from FCEU core - compute size using the cart mapper */ + int mapper = (coro->header.rom_type >> 4) | (coro->header.rom_type2 & 0xF0); + + if (coro->header.rom_size) + romsize = next_pow2(coro->header.rom_size); + + /* for games not to the power of 2, so we just read enough + * PRG rom from it, but we have to keep ROM_size to the power of 2 + * since PRGCartMapping wants ROM_size to be to the power of 2 + * so instead if not to power of 2, we just use head.ROM_size when + * we use FCEU_read. */ + coro->round = mapper != 53 && mapper != 198 && mapper != 228; + coro->bytes = coro->round ? romsize : coro->header.rom_size; + } + + /* from FCEU core - check if Trainer included in ROM data */ + MD5_Init(&coro->md5); + coro->offset = sizeof(coro->header) + (coro->header.rom_type & 4 + ? sizeof(coro->header) : 0); + coro->count = 0x4000 * coro->bytes; + CORO_GOSUB(EVAL_MD5); + + if (coro->count < 0x4000 * coro->bytes) + { + coro->offset = 0xff; + coro->count = 0x4000 * coro->bytes - coro->count; + CORO_GOSUB(FILL_MD5); + } + + MD5_Final(coro->hash, &coro->md5); + CORO_GOTO(GET_GAMEID); + + /************************************************************************** + * Info Tries to identify a "generic" game + * Input CHEEVOS_VAR_INFO the content info + * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found + *************************************************************************/ + CORO_SUB(GENERIC_MD5) + + MD5_Init(&coro->md5); + + coro->offset = 0; + coro->count = 0; + CORO_GOSUB(EVAL_MD5); + + MD5_Final(coro->hash, &coro->md5); + + if (coro->count == 0) + CORO_RET(); + + CORO_GOTO(GET_GAMEID); + + /************************************************************************** + * Info Tries to identify a game based on its filename (with no extension) + * Input CHEEVOS_VAR_INFO the content info + * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found + *************************************************************************/ + CORO_SUB(FILENAME_MD5) + if (!string_is_empty(coro->path)) + { + char base_noext[PATH_MAX_LENGTH]; + fill_pathname_base_noext(base_noext, coro->path, sizeof(base_noext)); + + MD5_Init(&coro->md5); + MD5_Update(&coro->md5, (void*)base_noext, strlen(base_noext)); + MD5_Final(coro->hash, &coro->md5); + + CORO_GOTO(GET_GAMEID); + } + CORO_RET(); + + /************************************************************************** + * Info Evaluates the CHEEVOS_VAR_MD5 hash + * Inputs CHEEVOS_VAR_INFO, CHEEVOS_VAR_OFFSET, CHEEVOS_VAR_COUNT + * Outputs CHEEVOS_VAR_MD5, CHEEVOS_VAR_COUNT + *************************************************************************/ + CORO_SUB(EVAL_MD5) + + if (coro->count == 0) + coro->count = coro->len; + + if (coro->len - coro->offset < coro->count) + coro->count = coro->len - coro->offset; + + /* size limit */ + if (coro->count > size_in_megabytes(64)) + coro->count = size_in_megabytes(64); + + MD5_Update(&coro->md5, + (void*)((uint8_t*)coro->data + coro->offset), + coro->count); + CORO_RET(); + + /************************************************************************** + * Info Updates the CHEEVOS_VAR_MD5 hash with a repeated value + * Inputs CHEEVOS_VAR_OFFSET, CHEEVOS_VAR_COUNT + * Outputs CHEEVOS_VAR_MD5 + *************************************************************************/ + CORO_SUB(FILL_MD5) + + { + char buffer[4096]; + + while (coro->count > 0) + { + size_t len = sizeof(buffer); + + if (len > coro->count) + len = coro->count; + + memset((void*)buffer, coro->offset, len); + MD5_Update(&coro->md5, (void*)buffer, len); + coro->count -= len; + } + } + + CORO_RET(); + + /************************************************************************** + * Info Gets the achievements from Retro Achievements + * Inputs coro->hash + * Outputs CHEEVOS_VAR_GAMEID + *************************************************************************/ + CORO_SUB(GET_GAMEID) + + { + char gameid[16]; + + CHEEVOS_LOG( + "[CHEEVOS]: getting game id for hash %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", + coro->hash[ 0], coro->hash[ 1], coro->hash[ 2], coro->hash[ 3], + coro->hash[ 4], coro->hash[ 5], coro->hash[ 6], coro->hash[ 7], + coro->hash[ 8], coro->hash[ 9], coro->hash[10], coro->hash[11], + coro->hash[12], coro->hash[13], coro->hash[14], coro->hash[15] + ); + + snprintf( + coro->url, sizeof(coro->url), + "http://retroachievements.org/dorequest.php?r=gameid&m=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + coro->hash[ 0], coro->hash[ 1], coro->hash[ 2], coro->hash[ 3], + coro->hash[ 4], coro->hash[ 5], coro->hash[ 6], coro->hash[ 7], + coro->hash[ 8], coro->hash[ 9], coro->hash[10], coro->hash[11], + coro->hash[12], coro->hash[13], coro->hash[14], coro->hash[15] + ); + + coro->url[sizeof(coro->url) - 1] = 0; + +#ifdef CHEEVOS_LOG_URLS + cheevos_log_url("[CHEEVOS]: url to get the game's id: %s\n", coro->url); +#endif + + CORO_GOSUB(HTTP_GET); + + if (!coro->json) + CORO_RET(); + + if (cheevos_get_value(coro->json, + CHEEVOS_JSON_KEY_GAMEID, gameid, sizeof(gameid))) + { + if ((void*)coro->json) + free((void*)coro->json); + CHEEVOS_ERR("[CHEEVOS]: error getting game_id.\n"); + CORO_RET(); + } + + if ((void*)coro->json) + free((void*)coro->json); + CHEEVOS_LOG("[CHEEVOS]: got game id %s.\n", gameid); + coro->gameid = (unsigned)strtol(gameid, NULL, 10); + CORO_RET(); + } + + /************************************************************************** + * Info Gets the achievements from Retro Achievements + * Inputs CHEEVOS_VAR_GAMEID + * Outputs CHEEVOS_VAR_JSON + *************************************************************************/ + CORO_SUB(GET_CHEEVOS) + + CORO_GOSUB(LOGIN); + + snprintf(coro->url, sizeof(coro->url), + "http://retroachievements.org/dorequest.php?r=patch&g=%u&u=%s&t=%s", + coro->gameid, + coro->settings->arrays.cheevos_username, + cheevos_locals.token); + + coro->url[sizeof(coro->url) - 1] = 0; + +#ifdef CHEEVOS_LOG_URLS + cheevos_log_url("[CHEEVOS]: url to get the list of cheevos: %s\n", coro->url); +#endif + + CORO_GOSUB(HTTP_GET); + + if (!coro->json) + { + CHEEVOS_ERR("[CHEEVOS]: error getting achievements for game id %u.\n", coro->gameid); + CORO_STOP(); + } + + CHEEVOS_LOG("[CHEEVOS]: got achievements for game id %u.\n", coro->gameid); + CORO_RET(); + + /************************************************************************** + * Info Gets the achievements from Retro Achievements + * Inputs CHEEVOS_VAR_GAMEID + * Outputs CHEEVOS_VAR_JSON + *************************************************************************/ + CORO_SUB(GET_BADGES) + + badges_ctx = new_badges_ctx; + + { + settings_t *settings = config_get_ptr(); + if (!( + string_is_equal(settings->arrays.menu_driver, "xmb") || + string_is_equal(settings->arrays.menu_driver, "ozone") + ) || + !settings->bools.cheevos_badges_enable) + CORO_RET(); + } + + coro->cheevo = cheevos_locals.core.cheevos; + coro->cheevo_end = cheevos_locals.core.cheevos + cheevos_locals.core.count; + + for (; coro->cheevo < coro->cheevo_end; coro->cheevo++) + { + for (coro->j = 0 ; coro->j < 2; coro->j++) + { + coro->badge_fullpath[0] = '\0'; + fill_pathname_application_special( + coro->badge_fullpath, + sizeof(coro->badge_fullpath), + APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_CHEEVOS_BADGES); + + if (!path_is_directory(coro->badge_fullpath)) + path_mkdir(coro->badge_fullpath); + CORO_YIELD(); + if (coro->j == 0) + snprintf(coro->badge_name, + sizeof(coro->badge_name), + "%s.png", coro->cheevo->badge); + else + snprintf(coro->badge_name, + sizeof(coro->badge_name), + "%s_lock.png", coro->cheevo->badge); + + fill_pathname_join( + coro->badge_fullpath, + coro->badge_fullpath, + coro->badge_name, + sizeof(coro->badge_fullpath)); + + if (!badge_exists(coro->badge_fullpath)) + { +#ifdef CHEEVOS_LOG_BADGES + CHEEVOS_LOG( + "[CHEEVOS]: downloading badge %s\n", + coro->badge_fullpath); +#endif + snprintf(coro->url, + sizeof(coro->url), + "http://i.retroachievements.org/Badge/%s", + coro->badge_name); + + CORO_GOSUB(HTTP_GET); + + if (coro->json) + { + if (!filestream_write_file(coro->badge_fullpath, + coro->json, coro->k)) + CHEEVOS_ERR("[CHEEVOS]: error writing badge %s\n", coro->badge_fullpath); + else + free(coro->json); + } + } + } + } + + CORO_RET(); + + /************************************************************************** + * Info Logs in the user at Retro Achievements + *************************************************************************/ + CORO_SUB(LOGIN) + + if (cheevos_locals.token[0]) + CORO_RET(); + + { + char urle_user[64]; + char urle_login[64]; + const char *username = coro ? coro->settings->arrays.cheevos_username : NULL; + const char *login = NULL; + bool via_token = false; + + if (coro) + { + if (string_is_empty(coro->settings->arrays.cheevos_password)) + { + via_token = true; + login = coro->settings->arrays.cheevos_token; + } + else + login = coro->settings->arrays.cheevos_password; + } + else + login = NULL; + + if (string_is_empty(username) || string_is_empty(login)) + { + runloop_msg_queue_push( + "Missing RetroAchievements account information.", + 0, 5 * 60, false); + runloop_msg_queue_push( + "Please fill in your account information in Settings.", + 0, 5 * 60, false); + CHEEVOS_ERR("[CHEEVOS]: login info not informed.\n"); + CORO_STOP(); + } + + cheevos_url_encode(username, urle_user, sizeof(urle_user)); + cheevos_url_encode(login, urle_login, sizeof(urle_login)); + + snprintf( + coro->url, sizeof(coro->url), + "http://retroachievements.org/dorequest.php?r=login&u=%s&%c=%s", + urle_user, via_token ? 't' : 'p', urle_login + ); + + coro->url[sizeof(coro->url) - 1] = 0; + } + +#ifdef CHEEVOS_LOG_URLS + cheevos_log_url("[CHEEVOS]: url to login: %s\n", + coro->url); +#endif + + CORO_GOSUB(HTTP_GET); + + if (coro->json) + { + char error_response[64]; + char error_message[256]; + + cheevos_get_value( + coro->json, + CHEEVOS_JSON_KEY_ERROR, + error_response, + sizeof(error_response) + ); + + /* No error, continue with login */ + if (string_is_empty(error_response)) + { + int res = cheevos_get_value( + coro->json, + CHEEVOS_JSON_KEY_TOKEN, + cheevos_locals.token, + sizeof(cheevos_locals.token)); + + if ((void*)coro->json) + free((void*)coro->json); + + if (!res) + { + if (coro->settings->bools.cheevos_verbose_enable) + { + char msg[256]; + snprintf(msg, sizeof(msg), + "RetroAchievements: Logged in as \"%s\".", + coro->settings->arrays.cheevos_username); + msg[sizeof(msg) - 1] = 0; + runloop_msg_queue_push(msg, 0, 3 * 60, false); + } + + /* Save token to config and clear pass on success */ + *coro->settings->arrays.cheevos_password = '\0'; + strncpy( + coro->settings->arrays.cheevos_token, + cheevos_locals.token, sizeof(cheevos_locals.token) + ); + CORO_RET(); + } + } + + if ((void*)coro->json) + free((void*)coro->json); + + /* Site returned error, display it */ + snprintf(error_message, sizeof(error_message), + "RetroAchievements: %s", + error_response); + error_message[sizeof(error_message) - 1] = 0; + runloop_msg_queue_push(error_message, 0, 5 * 60, false); + *coro->settings->arrays.cheevos_token = '\0'; + + CORO_STOP(); + } + + runloop_msg_queue_push("RetroAchievements: Error contacting server.", 0, 5 * 60, false); + CHEEVOS_ERR("[CHEEVOS]: error getting user token.\n"); + + CORO_STOP(); + + /************************************************************************** + * Info Pauses execution for five seconds + *************************************************************************/ + CORO_SUB(DELAY) + + { + retro_time_t t1; + coro->t0 = cpu_features_get_time_usec(); + + do + { + CORO_YIELD(); + t1 = cpu_features_get_time_usec(); + }while ((t1 - coro->t0) < 3000000); + } + + CORO_RET(); + + /************************************************************************** + * Info Makes a HTTP GET request + * Inputs CHEEVOS_VAR_URL + * Outputs CHEEVOS_VAR_JSON + *************************************************************************/ + CORO_SUB(HTTP_GET) + + for (coro->k = 0; coro->k < 5; coro->k++) + { + if (coro->k != 0) + CHEEVOS_LOG("[CHEEVOS]: Retrying HTTP request: %u of 5\n", coro->k + 1); + + coro->json = NULL; + coro->conn = net_http_connection_new( + coro->url, "GET", NULL); + + if (!coro->conn) + { + CORO_GOSUB(DELAY); + continue; + } + + /* Don't bother with timeouts here, it's just a string scan. */ + while (!net_http_connection_iterate(coro->conn)) {} + + /* Error finishing the connection descriptor. */ + if (!net_http_connection_done(coro->conn)) + { + net_http_connection_free(coro->conn); + continue; + } + + coro->http = net_http_new(coro->conn); + + /* Error connecting to the endpoint. */ + if (!coro->http) + { + net_http_connection_free(coro->conn); + CORO_GOSUB(DELAY); + continue; + } + + while (!net_http_update(coro->http, NULL, NULL)) + CORO_YIELD(); + + { + size_t length; + uint8_t *data = net_http_data(coro->http, + &length, false); + + if (data) + { + coro->json = (char*)malloc(length + 1); + + if (coro->json) + { + memcpy((void*)coro->json, (void*)data, length); + free(data); + coro->json[length] = 0; + } + + coro->k = (unsigned)length; + net_http_delete(coro->http); + net_http_connection_free(coro->conn); + CORO_RET(); + } + } + + net_http_delete(coro->http); + net_http_connection_free(coro->conn); + } + + CHEEVOS_LOG("[CHEEVOS]: Couldn't connect to server after 5 tries\n"); + CORO_RET(); + + /************************************************************************** + * Info Deactivates the achievements already awarded + * Inputs CHEEVOS_VAR_GAMEID + * Outputs + *************************************************************************/ + CORO_SUB(DEACTIVATE) + +#ifndef CHEEVOS_DONT_DEACTIVATE + CORO_GOSUB(LOGIN); + + /* Deactivate achievements in softcore mode. */ + snprintf( + coro->url, sizeof(coro->url), + "http://retroachievements.org/dorequest.php?r=unlocks&u=%s&t=%s&g=%u&h=0", + coro->settings->arrays.cheevos_username, + cheevos_locals.token, coro->gameid + ); + + coro->url[sizeof(coro->url) - 1] = 0; + +#ifdef CHEEVOS_LOG_URLS + cheevos_log_url("[CHEEVOS]: url to get the list of unlocked cheevos in softcore: %s\n", coro->url); +#endif + + CORO_GOSUB(HTTP_GET); + + if (coro->json) + { + if (!cheevos_deactivate_unlocks(coro->json, CHEEVOS_ACTIVE_SOFTCORE)) + CHEEVOS_LOG("[CHEEVOS]: deactivated unlocked achievements in softcore mode.\n"); + else + CHEEVOS_ERR("[CHEEVOS]: error deactivating unlocked achievements in softcore mode.\n"); + + if ((void*)coro->json) + free((void*)coro->json); + } + else + CHEEVOS_ERR("[CHEEVOS]: error retrieving list of unlocked achievements in softcore mode.\n"); + + /* Deactivate achievements in hardcore mode. */ + snprintf( + coro->url, sizeof(coro->url), + "http://retroachievements.org/dorequest.php?r=unlocks&u=%s&t=%s&g=%u&h=1", + coro->settings->arrays.cheevos_username, + cheevos_locals.token, coro->gameid + ); + + coro->url[sizeof(coro->url) - 1] = 0; + +#ifdef CHEEVOS_LOG_URLS + cheevos_log_url("[CHEEVOS]: url to get the list of unlocked cheevos in hardcore: %s\n", coro->url); +#endif + + CORO_GOSUB(HTTP_GET); + + if (coro->json) + { + if (!cheevos_deactivate_unlocks(coro->json, CHEEVOS_ACTIVE_HARDCORE)) + CHEEVOS_LOG("[CHEEVOS]: deactivated unlocked achievements in hardcore mode.\n"); + else + CHEEVOS_ERR("[CHEEVOS]: error deactivating unlocked achievements in hardcore mode.\n"); + + if ((void*)coro->json) + free((void*)coro->json); + } + else + CHEEVOS_ERR("[CHEEVOS]: error retrieving list of unlocked achievements in hardcore mode.\n"); + +#endif + CORO_RET(); + + /************************************************************************** + * Info Posts the "playing" activity to Retro Achievements + * Inputs CHEEVOS_VAR_GAMEID + * Outputs + *************************************************************************/ + CORO_SUB(PLAYING) + + snprintf( + coro->url, sizeof(coro->url), + "http://retroachievements.org/dorequest.php?r=postactivity&u=%s&t=%s&a=3&m=%u", + coro->settings->arrays.cheevos_username, + cheevos_locals.token, coro->gameid + ); + + coro->url[sizeof(coro->url) - 1] = 0; + +#ifdef CHEEVOS_LOG_URLS + cheevos_log_url("[CHEEVOS]: url to post the 'playing' activity: %s\n", coro->url); +#endif + + CORO_GOSUB(HTTP_GET); + + if (coro->json) + { + CHEEVOS_LOG("[CHEEVOS]: posted playing activity.\n"); + if ((void*)coro->json) + free((void*)coro->json); + } + else + CHEEVOS_ERR("[CHEEVOS]: error posting playing activity.\n"); + + CHEEVOS_LOG("[CHEEVOS]: posted playing activity.\n"); + CORO_RET(); + + CORO_LEAVE(); +} + +static void cheevos_task_handler(retro_task_t *task) +{ + coro_t *coro = (coro_t*)task->state; + + if (!coro) + return; + + if (!cheevos_iterate(coro) || task_get_cancelled(task)) + { + task_set_finished(task, true); + + CHEEVOS_LOCK(cheevos_locals.task_lock); + cheevos_locals.task = NULL; + CHEEVOS_UNLOCK(cheevos_locals.task_lock); + + if (task_get_cancelled(task)) + { + CHEEVOS_LOG("[CHEEVOS]: Load task cancelled\n"); + } + else + { + CHEEVOS_LOG("[CHEEVOS]: Load task finished\n"); + } + + if (coro->data) + free(coro->data); + + if ((void*)coro->path) + free((void*)coro->path); + + free((void*)coro); + } +} + +bool cheevos_load(const void *data) +{ + retro_task_t *task; + const struct retro_game_info *info = NULL; + coro_t *coro = NULL; + + cheevos_loaded = false; + cheevos_hardcore_paused = false; + + if (!cheevos_locals.core_supports || !data) + return false; + + coro = (coro_t*)calloc(1, sizeof(*coro)); + + if (!coro) + return false; + + task = (retro_task_t*)calloc(1, sizeof(*task)); + + if (!task) + { + if ((void*)coro) + free((void*)coro); + return false; + } + + CORO_SETUP(); + + info = (const struct retro_game_info*)data; + + if (info->data) + { + coro->len = info->size; + + /* size limit */ + if (coro->len > size_in_megabytes(64)) + coro->len = size_in_megabytes(64); + + coro->data = malloc(coro->len); + + if (!coro->data) + { + if ((void*)task) + free((void*)task); + if ((void*)coro) + free((void*)coro); + return false; + } + + memcpy(coro->data, info->data, coro->len); + coro->path = NULL; + } + else + { + coro->data = NULL; + coro->path = strdup(info->path); + } + + task->handler = cheevos_task_handler; + task->state = (void*)coro; + task->mute = true; + task->callback = NULL; + task->user_data = NULL; + task->progress = 0; + task->title = NULL; + +#ifdef HAVE_THREADS + if (cheevos_locals.task_lock == NULL) + { + cheevos_locals.task_lock = slock_new(); + } +#endif + + CHEEVOS_LOCK(cheevos_locals.task_lock); + cheevos_locals.task = task; + CHEEVOS_UNLOCK(cheevos_locals.task_lock); + + task_queue_push(task); + + return true; +} diff --git a/menu/drivers/ozone.c b/menu/drivers/ozone.c index 286d0ca766..074b86a737 100644 --- a/menu/drivers/ozone.c +++ b/menu/drivers/ozone.c @@ -973,67 +973,67 @@ switch (id) return icon_name; } -static unsigned ozone_entries_icon_get_id(ozone_handle_t *ozone, +static menu_texture_item ozone_entries_icon_get_texture(ozone_handle_t *ozone, enum msg_hash_enums enum_idx, unsigned type, bool active) { switch (enum_idx) { case MENU_ENUM_LABEL_CORE_OPTIONS: case MENU_ENUM_LABEL_NAVIGATION_BROWSER_FILTER_SUPPORTED_EXTENSIONS_ENABLE: - return OZONE_ENTRIES_ICONS_TEXTURE_CORE_OPTIONS; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CORE_OPTIONS]; case MENU_ENUM_LABEL_ADD_TO_FAVORITES: case MENU_ENUM_LABEL_ADD_TO_FAVORITES_PLAYLIST: - return OZONE_ENTRIES_ICONS_TEXTURE_ADD_FAVORITE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_ADD_FAVORITE]; case MENU_ENUM_LABEL_RESET_CORE_ASSOCIATION: - return OZONE_ENTRIES_ICONS_TEXTURE_UNDO; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_UNDO]; case MENU_ENUM_LABEL_CORE_INPUT_REMAPPING_OPTIONS: - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_REMAPPING_OPTIONS; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_REMAPPING_OPTIONS]; case MENU_ENUM_LABEL_CORE_CHEAT_OPTIONS: - return OZONE_ENTRIES_ICONS_TEXTURE_CHEAT_OPTIONS; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CHEAT_OPTIONS]; case MENU_ENUM_LABEL_DISK_OPTIONS: - return OZONE_ENTRIES_ICONS_TEXTURE_DISK_OPTIONS; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_DISK_OPTIONS]; case MENU_ENUM_LABEL_SHADER_OPTIONS: - return OZONE_ENTRIES_ICONS_TEXTURE_SHADER_OPTIONS; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SHADER_OPTIONS]; case MENU_ENUM_LABEL_ACHIEVEMENT_LIST: - return OZONE_ENTRIES_ICONS_TEXTURE_ACHIEVEMENT_LIST; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_ACHIEVEMENT_LIST]; case MENU_ENUM_LABEL_ACHIEVEMENT_LIST_HARDCORE: - return OZONE_ENTRIES_ICONS_TEXTURE_ACHIEVEMENT_LIST; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_ACHIEVEMENT_LIST]; case MENU_ENUM_LABEL_SAVE_STATE: - return OZONE_ENTRIES_ICONS_TEXTURE_SAVESTATE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SAVESTATE]; case MENU_ENUM_LABEL_LOAD_STATE: - return OZONE_ENTRIES_ICONS_TEXTURE_LOADSTATE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_LOADSTATE]; case MENU_ENUM_LABEL_PARENT_DIRECTORY: case MENU_ENUM_LABEL_UNDO_LOAD_STATE: case MENU_ENUM_LABEL_UNDO_SAVE_STATE: - return OZONE_ENTRIES_ICONS_TEXTURE_UNDO; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_UNDO]; case MENU_ENUM_LABEL_TAKE_SCREENSHOT: - return OZONE_ENTRIES_ICONS_TEXTURE_SCREENSHOT; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SCREENSHOT]; case MENU_ENUM_LABEL_DELETE_ENTRY: - return OZONE_ENTRIES_ICONS_TEXTURE_CLOSE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CLOSE]; case MENU_ENUM_LABEL_RESTART_CONTENT: - return OZONE_ENTRIES_ICONS_TEXTURE_RELOAD; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RELOAD]; case MENU_ENUM_LABEL_RENAME_ENTRY: - return OZONE_ENTRIES_ICONS_TEXTURE_RENAME; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RENAME]; case MENU_ENUM_LABEL_RESUME_CONTENT: - return OZONE_ENTRIES_ICONS_TEXTURE_RESUME; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RESUME]; case MENU_ENUM_LABEL_FAVORITES: case MENU_ENUM_LABEL_DOWNLOADED_FILE_DETECT_CORE_LIST: - return OZONE_ENTRIES_ICONS_TEXTURE_FOLDER; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_FOLDER]; case MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR: - return OZONE_ENTRIES_ICONS_TEXTURE_RDB; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RDB]; /* Menu collection submenus*/ case MENU_ENUM_LABEL_CONTENT_COLLECTION_LIST: - return OZONE_ENTRIES_ICONS_TEXTURE_ZIP; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_ZIP]; case MENU_ENUM_LABEL_GOTO_FAVORITES: - return OZONE_ENTRIES_ICONS_TEXTURE_FAVORITE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_FAVORITE]; case MENU_ENUM_LABEL_GOTO_IMAGES: - return OZONE_ENTRIES_ICONS_TEXTURE_IMAGE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_IMAGE]; case MENU_ENUM_LABEL_GOTO_VIDEO: - return OZONE_ENTRIES_ICONS_TEXTURE_MOVIE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_MOVIE]; case MENU_ENUM_LABEL_GOTO_MUSIC: - return OZONE_ENTRIES_ICONS_TEXTURE_MUSIC; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_MUSIC]; /* Menu icons */ case MENU_ENUM_LABEL_CONTENT_SETTINGS: @@ -1041,48 +1041,48 @@ static unsigned ozone_entries_icon_get_id(ozone_handle_t *ozone, case MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_GAME: case MENU_ENUM_LABEL_REMAP_FILE_SAVE_GAME: case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_SAVE_GAME: - return OZONE_ENTRIES_ICONS_TEXTURE_QUICKMENU; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_QUICKMENU]; case MENU_ENUM_LABEL_START_CORE: case MENU_ENUM_LABEL_CHEAT_START_OR_CONT: - return OZONE_ENTRIES_ICONS_TEXTURE_RUN; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RUN]; case MENU_ENUM_LABEL_CORE_LIST: case MENU_ENUM_LABEL_CORE_SETTINGS: case MENU_ENUM_LABEL_CORE_UPDATER_LIST: case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_SAVE_CORE: case MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_CORE: case MENU_ENUM_LABEL_REMAP_FILE_SAVE_CORE: - return OZONE_ENTRIES_ICONS_TEXTURE_CORE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CORE]; case MENU_ENUM_LABEL_LOAD_CONTENT_LIST: case MENU_ENUM_LABEL_SCAN_FILE: - return OZONE_ENTRIES_ICONS_TEXTURE_FILE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_FILE]; case MENU_ENUM_LABEL_ONLINE_UPDATER: case MENU_ENUM_LABEL_UPDATER_SETTINGS: - return OZONE_ENTRIES_ICONS_TEXTURE_UPDATER; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_UPDATER]; case MENU_ENUM_LABEL_UPDATE_LAKKA: - return OZONE_ENTRIES_ICONS_TEXTURE_MAIN_MENU; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_MAIN_MENU]; case MENU_ENUM_LABEL_UPDATE_CHEATS: - return OZONE_ENTRIES_ICONS_TEXTURE_CHEAT_OPTIONS; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CHEAT_OPTIONS]; case MENU_ENUM_LABEL_THUMBNAILS_UPDATER_LIST: - return OZONE_ENTRIES_ICONS_TEXTURE_IMAGE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_IMAGE]; case MENU_ENUM_LABEL_UPDATE_OVERLAYS: case MENU_ENUM_LABEL_ONSCREEN_OVERLAY_SETTINGS: - return OZONE_ENTRIES_ICONS_TEXTURE_OVERLAY; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_OVERLAY]; case MENU_ENUM_LABEL_UPDATE_CG_SHADERS: case MENU_ENUM_LABEL_UPDATE_GLSL_SHADERS: case MENU_ENUM_LABEL_UPDATE_SLANG_SHADERS: case MENU_ENUM_LABEL_AUTO_SHADERS_ENABLE: case MENU_ENUM_LABEL_VIDEO_SHADER_PARAMETERS: - return OZONE_ENTRIES_ICONS_TEXTURE_SHADER_OPTIONS; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SHADER_OPTIONS]; case MENU_ENUM_LABEL_INFORMATION: case MENU_ENUM_LABEL_INFORMATION_LIST: case MENU_ENUM_LABEL_SYSTEM_INFORMATION: case MENU_ENUM_LABEL_UPDATE_CORE_INFO_FILES: - return OZONE_ENTRIES_ICONS_TEXTURE_INFO; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INFO]; case MENU_ENUM_LABEL_UPDATE_DATABASES: case MENU_ENUM_LABEL_DATABASE_MANAGER_LIST: - return OZONE_ENTRIES_ICONS_TEXTURE_RDB; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RDB]; case MENU_ENUM_LABEL_CURSOR_MANAGER_LIST: - return OZONE_ENTRIES_ICONS_TEXTURE_CURSOR; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CURSOR]; case MENU_ENUM_LABEL_HELP_LIST: case MENU_ENUM_LABEL_HELP_CONTROLS: case MENU_ENUM_LABEL_HELP_LOADING_CONTENT: @@ -1090,18 +1090,18 @@ static unsigned ozone_entries_icon_get_id(ozone_handle_t *ozone, case MENU_ENUM_LABEL_HELP_WHAT_IS_A_CORE: case MENU_ENUM_LABEL_HELP_CHANGE_VIRTUAL_GAMEPAD: case MENU_ENUM_LABEL_HELP_AUDIO_VIDEO_TROUBLESHOOTING: - return OZONE_ENTRIES_ICONS_TEXTURE_HELP; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_HELP]; case MENU_ENUM_LABEL_QUIT_RETROARCH: - return OZONE_ENTRIES_ICONS_TEXTURE_EXIT; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_EXIT]; /* Settings icons*/ case MENU_ENUM_LABEL_DRIVER_SETTINGS: - return OZONE_ENTRIES_ICONS_TEXTURE_DRIVERS; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_DRIVERS]; case MENU_ENUM_LABEL_VIDEO_SETTINGS: - return OZONE_ENTRIES_ICONS_TEXTURE_VIDEO; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_VIDEO]; case MENU_ENUM_LABEL_AUDIO_SETTINGS: - return OZONE_ENTRIES_ICONS_TEXTURE_AUDIO; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_AUDIO]; case MENU_ENUM_LABEL_AUDIO_MIXER_SETTINGS: - return OZONE_ENTRIES_ICONS_TEXTURE_MIXER; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_MIXER]; case MENU_ENUM_LABEL_INPUT_SETTINGS: case MENU_ENUM_LABEL_UPDATE_AUTOCONFIG_PROFILES: case MENU_ENUM_LABEL_INPUT_USER_1_BINDS: @@ -1120,25 +1120,25 @@ static unsigned ozone_entries_icon_get_id(ozone_handle_t *ozone, case MENU_ENUM_LABEL_INPUT_USER_14_BINDS: case MENU_ENUM_LABEL_INPUT_USER_15_BINDS: case MENU_ENUM_LABEL_INPUT_USER_16_BINDS: - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_SETTINGS; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_SETTINGS]; case MENU_ENUM_LABEL_LATENCY_SETTINGS: - return OZONE_ENTRIES_ICONS_TEXTURE_LATENCY; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_LATENCY]; case MENU_ENUM_LABEL_SAVING_SETTINGS: case MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG: case MENU_ENUM_LABEL_SAVE_NEW_CONFIG: case MENU_ENUM_LABEL_CONFIG_SAVE_ON_EXIT: case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_SAVE_AS: case MENU_ENUM_LABEL_CHEAT_FILE_SAVE_AS: - return OZONE_ENTRIES_ICONS_TEXTURE_SAVING; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SAVING]; case MENU_ENUM_LABEL_LOGGING_SETTINGS: - return OZONE_ENTRIES_ICONS_TEXTURE_LOG; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_LOG]; case MENU_ENUM_LABEL_FRAME_THROTTLE_SETTINGS: - return OZONE_ENTRIES_ICONS_TEXTURE_FRAMESKIP; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_FRAMESKIP]; case MENU_ENUM_LABEL_QUICK_MENU_START_RECORDING: case MENU_ENUM_LABEL_RECORDING_SETTINGS: - return OZONE_ENTRIES_ICONS_TEXTURE_RECORD; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RECORD]; case MENU_ENUM_LABEL_QUICK_MENU_START_STREAMING: - return OZONE_ENTRIES_ICONS_TEXTURE_STREAM; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_STREAM]; case MENU_ENUM_LABEL_QUICK_MENU_STOP_STREAMING: case MENU_ENUM_LABEL_QUICK_MENU_STOP_RECORDING: case MENU_ENUM_LABEL_CHEAT_DELETE_ALL: @@ -1146,60 +1146,60 @@ static unsigned ozone_entries_icon_get_id(ozone_handle_t *ozone, case MENU_ENUM_LABEL_REMAP_FILE_REMOVE_GAME: case MENU_ENUM_LABEL_REMAP_FILE_REMOVE_CONTENT_DIR: case MENU_ENUM_LABEL_CORE_DELETE: - return OZONE_ENTRIES_ICONS_TEXTURE_CLOSE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CLOSE]; case MENU_ENUM_LABEL_ONSCREEN_DISPLAY_SETTINGS: - return OZONE_ENTRIES_ICONS_TEXTURE_OSD; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_OSD]; case MENU_ENUM_LABEL_SHOW_WIMP: case MENU_ENUM_LABEL_USER_INTERFACE_SETTINGS: - return OZONE_ENTRIES_ICONS_TEXTURE_UI; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_UI]; #ifdef HAVE_LAKKA_SWITCH case MENU_ENUM_LABEL_SWITCH_GPU_PROFILE: case MENU_ENUM_LABEL_SWITCH_CPU_PROFILE: #endif case MENU_ENUM_LABEL_POWER_MANAGEMENT_SETTINGS: - return OZONE_ENTRIES_ICONS_TEXTURE_POWER; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_POWER]; case MENU_ENUM_LABEL_RETRO_ACHIEVEMENTS_SETTINGS: - return OZONE_ENTRIES_ICONS_TEXTURE_ACHIEVEMENTS; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_ACHIEVEMENTS]; case MENU_ENUM_LABEL_NETWORK_INFORMATION: case MENU_ENUM_LABEL_NETWORK_SETTINGS: case MENU_ENUM_LABEL_WIFI_SETTINGS: - return OZONE_ENTRIES_ICONS_TEXTURE_NETWORK; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_NETWORK]; case MENU_ENUM_LABEL_PLAYLIST_SETTINGS: - return OZONE_ENTRIES_ICONS_TEXTURE_PLAYLIST; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_PLAYLIST]; case MENU_ENUM_LABEL_USER_SETTINGS: - return OZONE_ENTRIES_ICONS_TEXTURE_USER; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_USER]; case MENU_ENUM_LABEL_DIRECTORY_SETTINGS: case MENU_ENUM_LABEL_SCAN_DIRECTORY: case MENU_ENUM_LABEL_REMAP_FILE_SAVE_CONTENT_DIR: case MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_CONTENT_DIR: case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_SAVE_PARENT: - return OZONE_ENTRIES_ICONS_TEXTURE_FOLDER; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_FOLDER]; case MENU_ENUM_LABEL_PRIVACY_SETTINGS: - return OZONE_ENTRIES_ICONS_TEXTURE_PRIVACY; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_PRIVACY]; case MENU_ENUM_LABEL_REWIND_SETTINGS: - return OZONE_ENTRIES_ICONS_TEXTURE_REWIND; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_REWIND]; case MENU_ENUM_LABEL_QUICK_MENU_OVERRIDE_OPTIONS: - return OZONE_ENTRIES_ICONS_TEXTURE_OVERRIDE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_OVERRIDE]; case MENU_ENUM_LABEL_ONSCREEN_NOTIFICATIONS_SETTINGS: - return OZONE_ENTRIES_ICONS_TEXTURE_NOTIFICATIONS; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_NOTIFICATIONS]; #ifdef HAVE_NETWORKING case MENU_ENUM_LABEL_NETPLAY_ENABLE_HOST: - return OZONE_ENTRIES_ICONS_TEXTURE_RUN; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RUN]; case MENU_ENUM_LABEL_NETPLAY_DISCONNECT: - return OZONE_ENTRIES_ICONS_TEXTURE_CLOSE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CLOSE]; case MENU_ENUM_LABEL_NETPLAY_ENABLE_CLIENT: - return OZONE_ENTRIES_ICONS_TEXTURE_ROOM; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_ROOM]; case MENU_ENUM_LABEL_NETPLAY_REFRESH_ROOMS: - return OZONE_ENTRIES_ICONS_TEXTURE_RELOAD; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RELOAD]; #endif case MENU_ENUM_LABEL_REBOOT: case MENU_ENUM_LABEL_RESET_TO_DEFAULT_CONFIG: case MENU_ENUM_LABEL_CHEAT_RELOAD_CHEATS: case MENU_ENUM_LABEL_RESTART_RETROARCH: - return OZONE_ENTRIES_ICONS_TEXTURE_RELOAD; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RELOAD]; case MENU_ENUM_LABEL_SHUTDOWN: - return OZONE_ENTRIES_ICONS_TEXTURE_SHUTDOWN; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SHUTDOWN]; case MENU_ENUM_LABEL_CONFIGURATIONS: case MENU_ENUM_LABEL_GAME_SPECIFIC_OPTIONS: case MENU_ENUM_LABEL_REMAP_FILE_LOAD: @@ -1208,10 +1208,10 @@ static unsigned ozone_entries_icon_get_id(ozone_handle_t *ozone, case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET: case MENU_ENUM_LABEL_CHEAT_FILE_LOAD: case MENU_ENUM_LABEL_CHEAT_FILE_LOAD_APPEND: - return OZONE_ENTRIES_ICONS_TEXTURE_LOADSTATE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_LOADSTATE]; case MENU_ENUM_LABEL_CHEAT_APPLY_CHANGES: case MENU_ENUM_LABEL_SHADER_APPLY_CHANGES: - return OZONE_ENTRIES_ICONS_TEXTURE_CHECKMARK; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CHECKMARK]; default: break; } @@ -1219,80 +1219,80 @@ static unsigned ozone_entries_icon_get_id(ozone_handle_t *ozone, switch(type) { case FILE_TYPE_DIRECTORY: - return OZONE_ENTRIES_ICONS_TEXTURE_FOLDER; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_FOLDER]; case FILE_TYPE_PLAIN: case FILE_TYPE_IN_CARCHIVE: case FILE_TYPE_RPL_ENTRY: - return OZONE_ENTRIES_ICONS_TEXTURE_FILE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_FILE]; case FILE_TYPE_SHADER: case FILE_TYPE_SHADER_PRESET: - return OZONE_ENTRIES_ICONS_TEXTURE_SHADER_OPTIONS; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SHADER_OPTIONS]; case FILE_TYPE_CARCHIVE: - return OZONE_ENTRIES_ICONS_TEXTURE_ZIP; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_ZIP]; case FILE_TYPE_MUSIC: - return OZONE_ENTRIES_ICONS_TEXTURE_MUSIC; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_MUSIC]; case FILE_TYPE_IMAGE: case FILE_TYPE_IMAGEVIEWER: - return OZONE_ENTRIES_ICONS_TEXTURE_IMAGE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_IMAGE]; case FILE_TYPE_MOVIE: - return OZONE_ENTRIES_ICONS_TEXTURE_MOVIE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_MOVIE]; case FILE_TYPE_CORE: case FILE_TYPE_DIRECT_LOAD: - return OZONE_ENTRIES_ICONS_TEXTURE_CORE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CORE]; case FILE_TYPE_RDB: - return OZONE_ENTRIES_ICONS_TEXTURE_RDB; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RDB]; case FILE_TYPE_CURSOR: - return OZONE_ENTRIES_ICONS_TEXTURE_CURSOR; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CURSOR]; case FILE_TYPE_PLAYLIST_ENTRY: case MENU_SETTING_ACTION_RUN: case MENU_SETTING_ACTION_RESUME_ACHIEVEMENTS: - return OZONE_ENTRIES_ICONS_TEXTURE_RUN; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RUN]; case MENU_SETTING_ACTION_CLOSE: case MENU_SETTING_ACTION_DELETE_ENTRY: - return OZONE_ENTRIES_ICONS_TEXTURE_CLOSE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CLOSE]; case MENU_SETTING_ACTION_SAVESTATE: - return OZONE_ENTRIES_ICONS_TEXTURE_SAVESTATE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SAVESTATE]; case MENU_SETTING_ACTION_LOADSTATE: - return OZONE_ENTRIES_ICONS_TEXTURE_LOADSTATE; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_LOADSTATE]; case FILE_TYPE_RDB_ENTRY: case MENU_SETTING_ACTION_CORE_INFORMATION: - return OZONE_ENTRIES_ICONS_TEXTURE_CORE_INFO; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CORE_INFO]; case MENU_SETTING_ACTION_CORE_OPTIONS: - return OZONE_ENTRIES_ICONS_TEXTURE_CORE_OPTIONS; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CORE_OPTIONS]; case MENU_SETTING_ACTION_CORE_INPUT_REMAPPING_OPTIONS: - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_REMAPPING_OPTIONS; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_REMAPPING_OPTIONS]; case MENU_SETTING_ACTION_CORE_CHEAT_OPTIONS: - return OZONE_ENTRIES_ICONS_TEXTURE_CHEAT_OPTIONS; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CHEAT_OPTIONS]; case MENU_SETTING_ACTION_CORE_DISK_OPTIONS: - return OZONE_ENTRIES_ICONS_TEXTURE_DISK_OPTIONS; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_DISK_OPTIONS]; case MENU_SETTING_ACTION_CORE_SHADER_OPTIONS: - return OZONE_ENTRIES_ICONS_TEXTURE_SHADER_OPTIONS; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SHADER_OPTIONS]; case MENU_SETTING_ACTION_SCREENSHOT: - return OZONE_ENTRIES_ICONS_TEXTURE_SCREENSHOT; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SCREENSHOT]; case MENU_SETTING_ACTION_RESET: - return OZONE_ENTRIES_ICONS_TEXTURE_RELOAD; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RELOAD]; case MENU_SETTING_ACTION_PAUSE_ACHIEVEMENTS: - return OZONE_ENTRIES_ICONS_TEXTURE_RESUME; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RESUME]; case MENU_SETTING_GROUP: #ifdef HAVE_LAKKA_SWITCH case MENU_SET_SWITCH_BRIGHTNESS: #endif - return OZONE_ENTRIES_ICONS_TEXTURE_SETTING; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SETTING]; case MENU_INFO_MESSAGE: - return OZONE_ENTRIES_ICONS_TEXTURE_CORE_INFO; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CORE_INFO]; case MENU_WIFI: - return OZONE_ENTRIES_ICONS_TEXTURE_WIFI; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_WIFI]; #ifdef HAVE_NETWORKING case MENU_ROOM: - return OZONE_ENTRIES_ICONS_TEXTURE_ROOM; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_ROOM]; case MENU_ROOM_LAN: - return OZONE_ENTRIES_ICONS_TEXTURE_ROOM_LAN; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_ROOM_LAN]; case MENU_ROOM_RELAY: - return OZONE_ENTRIES_ICONS_TEXTURE_ROOM_RELAY; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_ROOM_RELAY]; #endif case MENU_SETTING_ACTION: - return OZONE_ENTRIES_ICONS_TEXTURE_SETTING; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SETTING]; } #ifdef HAVE_CHEEVOS @@ -1305,7 +1305,7 @@ static unsigned ozone_entries_icon_get_id(ozone_handle_t *ozone, if (get_badge_texture(new_id) != 0) return get_badge_texture(new_id); /* Should be replaced with placeholder badge icon. */ - return OZONE_ENTRIES_ICONS_TEXTURE_ACHIEVEMENTS; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_ACHIEVEMENTS]; } #endif @@ -1319,11 +1319,11 @@ static unsigned ozone_entries_icon_get_id(ozone_handle_t *ozone, { input_id = MENU_SETTINGS_INPUT_BEGIN; if ( type == input_id + 2) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_SETTINGS; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_SETTINGS]; if ( type == input_id + 4) - return OZONE_ENTRIES_ICONS_TEXTURE_RELOAD; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RELOAD]; if ( type == input_id + 5) - return OZONE_ENTRIES_ICONS_TEXTURE_SAVING; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SAVING]; input_id = input_id + 7; } else @@ -1335,55 +1335,55 @@ static unsigned ozone_entries_icon_get_id(ozone_handle_t *ozone, } } if ( type == input_id ) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_BTN_D; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_BTN_D]; if ( type == (input_id + 1)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_BTN_L; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_BTN_L]; if ( type == (input_id + 2)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_SELECT; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_SELECT]; if ( type == (input_id + 3)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_START; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_START]; if ( type == (input_id + 4)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_DPAD_U; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_DPAD_U]; if ( type == (input_id + 5)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_DPAD_D; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_DPAD_D]; if ( type == (input_id + 6)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_DPAD_L; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_DPAD_L]; if ( type == (input_id + 7)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_DPAD_R; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_DPAD_R]; if ( type == (input_id + 8)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_BTN_R; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_BTN_R]; if ( type == (input_id + 9)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_BTN_U; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_BTN_U]; if ( type == (input_id + 10)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_LB; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_LB]; if ( type == (input_id + 11)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_RB; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_RB]; if ( type == (input_id + 12)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_LT; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_LT]; if ( type == (input_id + 13)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_RT; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_RT]; if ( type == (input_id + 14)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_STCK_P; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_STCK_P]; if ( type == (input_id + 15)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_STCK_P; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_STCK_P]; if ( type == (input_id + 16)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_STCK_R; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_STCK_R]; if ( type == (input_id + 17)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_STCK_L; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_STCK_L]; if ( type == (input_id + 18)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_STCK_D; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_STCK_D]; if ( type == (input_id + 19)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_STCK_U; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_STCK_U]; if ( type == (input_id + 20)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_STCK_R; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_STCK_R]; if ( type == (input_id + 21)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_STCK_L; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_STCK_L]; if ( type == (input_id + 22)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_STCK_D; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_STCK_D]; if ( type == (input_id + 23)) - return OZONE_ENTRIES_ICONS_TEXTURE_INPUT_STCK_U; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_STCK_U]; } - return OZONE_ENTRIES_ICONS_TEXTURE_SUBSETTING; + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SUBSETTING]; } static void ozone_draw_text( @@ -2617,8 +2617,8 @@ static void ozone_compute_entries_position(ozone_handle_t *ozone) OZONE_ENTRIES_ICONS_TEXTURE_CORE_INFO */ if (ozone->is_playlist && entries_end == 1) { - unsigned icon = ozone_entries_icon_get_id(ozone, entry.enum_idx, entry.type, false); - ozone->empty_playlist = icon == OZONE_ENTRIES_ICONS_TEXTURE_CORE_INFO; + menu_texture_item tex = ozone_entries_icon_get_texture(ozone, entry.enum_idx, entry.type, false); + ozone->empty_playlist = tex == ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CORE_INFO]; } else { @@ -3271,7 +3271,7 @@ border_iterate: for (i = 0; i < entries_end; i++) { - unsigned icon; + menu_texture_item tex; menu_entry_t entry; menu_animation_ctx_ticker_t ticker; char entry_value[255]; @@ -3282,6 +3282,7 @@ border_iterate: char *entry_rich_label = NULL; bool entry_selected = false; int text_offset = -40; + float *icon_color = NULL; entry_value[0] = '\0'; entry_selected = selection == i; @@ -3323,10 +3324,10 @@ border_iterate: word_wrap(sublabel_str, sublabel_str, (video_info->width - 548) / ozone->sublabel_font_glyph_width, false); /* Icon */ - icon = ozone_entries_icon_get_id(ozone, entry.enum_idx, entry.type, entry_selected); - if (icon != OZONE_ENTRIES_ICONS_TEXTURE_SUBSETTING) + tex = ozone_entries_icon_get_texture(ozone, entry.enum_idx, entry.type, entry_selected); + if (tex != ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SUBSETTING]) { - uintptr_t texture = ozone->icons_textures[icon]; + uintptr_t texture = tex; /* Console specific icons */ if (entry.type == FILE_TYPE_RPL_ENTRY && ozone->horizontal_list && ozone->categories_selection_ptr > ozone->system_tab_end) @@ -3334,15 +3335,23 @@ border_iterate: ozone_node_t *sidebar_node = (ozone_node_t*) file_list_get_userdata_at_offset(ozone->horizontal_list, ozone->categories_selection_ptr - ozone->system_tab_end-1); if (!sidebar_node || !sidebar_node->content_icon) - texture = ozone->icons_textures[icon]; + texture = tex; else texture = sidebar_node->content_icon; } - ozone_color_alpha(ozone->theme_dynamic.entries_icon, alpha); + /* Cheevos badges should not be recolored */ + if (!( + (entry.type >= MENU_SETTINGS_CHEEVOS_START) && + (entry.type < MENU_SETTINGS_NETPLAY_ROOMS_START) + )) + { + icon_color = ozone->theme_dynamic.entries_icon; + ozone_color_alpha(ozone->theme_dynamic.entries_icon, alpha); + } menu_display_blend_begin(video_info); - ozone_draw_icon(video_info, 46, 46, texture, x_offset + 451+5+10, y + scroll_y, video_info->width, video_info->height, 0, 1, ozone->theme_dynamic.entries_icon); + ozone_draw_icon(video_info, 46, 46, texture, x_offset + 451+5+10, y + scroll_y, video_info->width, video_info->height, 0, 1, icon_color); menu_display_blend_end(video_info); text_offset = 0; diff --git a/record/record_driver.c b/record/record_driver.c index 9b2f4afe37..9426b58e2f 100644 --- a/record/record_driver.c +++ b/record/record_driver.c @@ -539,7 +539,7 @@ void recording_driver_update_streaming_url(void) else { /* To-Do: Show input box for twitch_stream_key*/ - RARCH_LOG("[recording] twitch streaming key empty"); + RARCH_LOG("[recording] twitch streaming key empty\n"); } break; } @@ -553,7 +553,7 @@ void recording_driver_update_streaming_url(void) else { /* To-Do: Show input box for youtube_stream_key*/ - RARCH_LOG("[recording] youtube streaming key empty"); + RARCH_LOG("[recording] youtube streaming key empty\n"); } break; }