diff --git a/dep/rcheevos/include/rc_client.h b/dep/rcheevos/include/rc_client.h
index 2e828bb1d..9e45653cf 100644
--- a/dep/rcheevos/include/rc_client.h
+++ b/dep/rcheevos/include/rc_client.h
@@ -152,7 +152,8 @@ enum
   RC_CLIENT_LOG_LEVEL_ERROR = 1,
   RC_CLIENT_LOG_LEVEL_WARN = 2,
   RC_CLIENT_LOG_LEVEL_INFO = 3,
-  RC_CLIENT_LOG_LEVEL_VERBOSE = 4
+  RC_CLIENT_LOG_LEVEL_VERBOSE = 4,
+  NUM_RC_CLIENT_LOG_LEVELS = 5
 };
 
 /*****************************************************************************\
@@ -286,7 +287,8 @@ enum {
   RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE = 0, /* unprocessed */
   RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE = 1,   /* eligible to trigger */
   RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED = 2, /* earned by user */
-  RC_CLIENT_ACHIEVEMENT_STATE_DISABLED = 3  /* not supported by this version of the runtime */
+  RC_CLIENT_ACHIEVEMENT_STATE_DISABLED = 3, /* not supported by this version of the runtime */
+  NUM_RC_CLIENT_ACHIEVEMENT_STATES = 4
 };
 
 enum {
@@ -304,7 +306,8 @@ enum {
   RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL = 4,
   RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED = 5,
   RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE = 6,
-  RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE = 7
+  RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE = 7,
+  NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS = 8
 };
 
 enum {
@@ -370,6 +373,11 @@ rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* cli
  */
 void rc_client_destroy_achievement_list(rc_client_achievement_list_t* list);
 
+/**
+ * Returns non-zero if there are any achievements that can be queried through rc_client_create_achievement_list().
+ */
+int rc_client_has_achievements(rc_client_t* client);
+
 /*****************************************************************************\
 | Leaderboards                                                                |
 \*****************************************************************************/
@@ -378,15 +386,26 @@ enum {
   RC_CLIENT_LEADERBOARD_STATE_INACTIVE = 0,
   RC_CLIENT_LEADERBOARD_STATE_ACTIVE = 1,
   RC_CLIENT_LEADERBOARD_STATE_TRACKING = 2,
-  RC_CLIENT_LEADERBOARD_STATE_DISABLED = 3
+  RC_CLIENT_LEADERBOARD_STATE_DISABLED = 3,
+  NUM_RC_CLIENT_LEADERBOARD_STATES = 4
 };
 
+enum {
+  RC_CLIENT_LEADERBOARD_FORMAT_TIME = 0,
+  RC_CLIENT_LEADERBOARD_FORMAT_SCORE = 1,
+  RC_CLIENT_LEADERBOARD_FORMAT_VALUE = 2,
+  NUM_RC_CLIENT_LEADERBOARD_FORMATS = 3
+};
+
+#define RC_CLIENT_LEADERBOARD_DISPLAY_SIZE 24
+
 typedef struct rc_client_leaderboard_t {
   const char* title;
   const char* description;
   const char* tracker_value;
   uint32_t id;
   uint8_t state;
+  uint8_t format;
   uint8_t lower_is_better;
 } rc_client_leaderboard_t;
 
@@ -396,7 +415,7 @@ typedef struct rc_client_leaderboard_t {
 const rc_client_leaderboard_t* rc_client_get_leaderboard_info(const rc_client_t* client, uint32_t id);
 
 typedef struct rc_client_leaderboard_tracker_t {
-  char display[24];
+  char display[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE];
   uint32_t id;
 } rc_client_leaderboard_tracker_t;
 
@@ -419,7 +438,8 @@ enum {
   RC_CLIENT_LEADERBOARD_BUCKET_INACTIVE = 1,
   RC_CLIENT_LEADERBOARD_BUCKET_ACTIVE = 2,
   RC_CLIENT_LEADERBOARD_BUCKET_UNSUPPORTED = 3,
-  RC_CLIENT_LEADERBOARD_BUCKET_ALL = 4
+  RC_CLIENT_LEADERBOARD_BUCKET_ALL = 4,
+  NUM_RC_CLIENT_LEADERBOARD_BUCKETS = 5
 };
 
 enum {
@@ -438,9 +458,14 @@ rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* cli
  */
 void rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list);
 
+/**
+ * Returns non-zero if the current game has any leaderboards.
+ */
+int rc_client_has_leaderboards(rc_client_t* client);
+
 typedef struct rc_client_leaderboard_entry_t {
   const char* user;
-  char display[24];
+  char display[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE];
   time_t submitted;
   uint32_t rank;
   uint32_t index;
@@ -480,10 +505,46 @@ int rc_client_leaderboard_entry_get_user_image_url(const rc_client_leaderboard_e
  */
 void rc_client_destroy_leaderboard_entry_list(rc_client_leaderboard_entry_list_t* list);
 
+/**
+ * Used for scoreboard events. Contains the response from the server when a leaderboard entry is submitted.
+ * NOTE: This structure is only valid within the event callback. If you want to make use of the data outside
+ * of the callback, you should create copies of both the top entries and usernames within.
+ */
+typedef struct rc_client_leaderboard_scoreboard_entry_t {
+  /* The user associated to the entry */
+  const char* username;
+  /* The rank of the entry */
+  unsigned rank;
+  /* The value of the entry */
+  char score[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE];
+} rc_client_leaderboard_scoreboard_entry_t;
+typedef struct rc_client_leaderboard_scoreboard_t {
+  /* The ID of the leaderboard which was submitted */
+  uint32_t leaderboard_id;
+  /* The value that was submitted */
+  char submitted_score[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE];
+  /* The player's best submitted value */
+  char best_score[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE];
+  /* The player's new rank within the leaderboard */
+  unsigned new_rank;
+  /* The total number of entries in the leaderboard */
+  unsigned num_entries;
+
+  /* An array of the top entries for the leaderboard */
+  rc_client_leaderboard_scoreboard_entry_t* top_entries;
+  /* The number of items in the top_entries array */
+  unsigned num_top_entries;
+} rc_client_leaderboard_scoreboard_t;
+
 /*****************************************************************************\
 | Rich Presence                                                               |
 \*****************************************************************************/
 
+/**
+ * Returns non-zero if the current game supports rich presence.
+ */
+int rc_client_has_rich_presence(rc_client_t* client);
+
 /**
  * Gets the current rich presence message.
  * Returns the number of characters written to buffer.
@@ -508,11 +569,12 @@ enum {
   RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW = 10, /* [leaderboard_tracker] should be shown */
   RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE = 11, /* [leaderboard_tracker] should be hidden */
   RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE = 12, /* [leaderboard_tracker] updated */
-  RC_CLIENT_EVENT_RESET = 13, /* emulated system should be reset (as the result of enabling hardcore) */
-  RC_CLIENT_EVENT_GAME_COMPLETED = 14, /* all achievements for the game have been earned */
-  RC_CLIENT_EVENT_SERVER_ERROR = 15, /* an API response returned a [server_error] and will not be retried */
-  RC_CLIENT_EVENT_DISCONNECTED = 16, /* an unlock request could not be completed and is pending */
-  RC_CLIENT_EVENT_RECONNECTED = 17 /* all pending unlocks have been completed */
+  RC_CLIENT_EVENT_LEADERBOARD_SCOREBOARD = 13, /* [leaderboard_scoreboard] possibly-new ranking received */
+  RC_CLIENT_EVENT_RESET = 14, /* emulated system should be reset (as the result of enabling hardcore) */
+  RC_CLIENT_EVENT_GAME_COMPLETED = 15, /* all achievements for the game have been earned */
+  RC_CLIENT_EVENT_SERVER_ERROR = 16, /* an API response returned a [server_error] and will not be retried */
+  RC_CLIENT_EVENT_DISCONNECTED = 17, /* an unlock request could not be completed and is pending */
+  RC_CLIENT_EVENT_RECONNECTED = 18 /* all pending unlocks have been completed */
 };
 
 typedef struct rc_client_server_error_t
@@ -528,6 +590,7 @@ typedef struct rc_client_event_t
   rc_client_achievement_t* achievement;
   rc_client_leaderboard_t* leaderboard;
   rc_client_leaderboard_tracker_t* leaderboard_tracker;
+  rc_client_leaderboard_scoreboard_t* leaderboard_scoreboard;
   rc_client_server_error_t* server_error;
 
 } rc_client_event_t;
diff --git a/dep/rcheevos/src/rcheevos/rc_client.c b/dep/rcheevos/src/rcheevos/rc_client.c
index bb2776aaa..bc7189f50 100644
--- a/dep/rcheevos/src/rcheevos/rc_client.c
+++ b/dep/rcheevos/src/rcheevos/rc_client.c
@@ -1433,6 +1433,31 @@ static void rc_client_copy_achievements(rc_client_load_state_t* load_state,
   subset->achievements = achievements;
 }
 
+static uint8_t rc_client_map_leaderboard_format(const rc_api_leaderboard_definition_t* defn)
+{
+  switch (defn->format) {
+    case RC_FORMAT_SECONDS:
+    case RC_FORMAT_CENTISECS:
+    case RC_FORMAT_MINUTES:
+    case RC_FORMAT_SECONDS_AS_MINUTES:
+    case RC_FORMAT_FRAMES:
+      return RC_CLIENT_LEADERBOARD_FORMAT_TIME;
+
+    case RC_FORMAT_SCORE:
+      return RC_CLIENT_LEADERBOARD_FORMAT_SCORE;
+
+    case RC_FORMAT_VALUE:
+    case RC_FORMAT_FLOAT1:
+    case RC_FORMAT_FLOAT2:
+    case RC_FORMAT_FLOAT3:
+    case RC_FORMAT_FLOAT4:
+    case RC_FORMAT_FLOAT5:
+    case RC_FORMAT_FLOAT6:
+    default:
+      return RC_CLIENT_LEADERBOARD_FORMAT_VALUE;
+  }
+}
+
 static void rc_client_copy_leaderboards(rc_client_load_state_t* load_state,
     rc_client_subset_info_t* subset,
     const rc_api_leaderboard_definition_t* leaderboard_definitions, uint32_t num_leaderboards)
@@ -1477,6 +1502,7 @@ static void rc_client_copy_leaderboards(rc_client_load_state_t* load_state,
     leaderboard->public_.title = rc_buf_strcpy(buffer, read->title);
     leaderboard->public_.description = rc_buf_strcpy(buffer, read->description);
     leaderboard->public_.id = read->id;
+    leaderboard->public_.format = rc_client_map_leaderboard_format(read);
     leaderboard->public_.lower_is_better = read->lower_is_better;
     leaderboard->format = (uint8_t)read->format;
     leaderboard->hidden = (uint8_t)read->hidden;
@@ -2706,6 +2732,34 @@ void rc_client_destroy_achievement_list(rc_client_achievement_list_t* list)
     free(list);
 }
 
+int rc_client_has_achievements(rc_client_t* client)
+{
+  rc_client_subset_info_t* subset;
+  int result;
+
+  if (!client || !client->game)
+    return 0;
+
+  rc_mutex_lock(&client->state.mutex);
+
+  subset = client->game->subsets;
+  result = 0;
+  for (; subset; subset = subset->next)
+  {
+    if (!subset->active)
+      continue;
+
+    if (subset->public_.num_achievements > 0) {
+      result = 1;
+      break;
+    }
+  }
+
+  rc_mutex_unlock(&client->state.mutex);
+
+  return result;
+}
+
 static const rc_client_achievement_t* rc_client_subset_get_achievement_info(
     rc_client_t* client, rc_client_subset_info_t* subset, uint32_t id)
 {
@@ -2989,14 +3043,14 @@ static void rc_client_reset_achievements(rc_client_t* client)
 
 /* ===== Leaderboards ===== */
 
-static const rc_client_leaderboard_t* rc_client_subset_get_leaderboard_info(const rc_client_subset_info_t* subset, uint32_t id)
+static rc_client_leaderboard_info_t* rc_client_subset_get_leaderboard_info(const rc_client_subset_info_t* subset, uint32_t id)
 {
   rc_client_leaderboard_info_t* leaderboard = subset->leaderboards;
   rc_client_leaderboard_info_t* stop = leaderboard + subset->public_.num_leaderboards;
 
   for (; leaderboard < stop; ++leaderboard) {
     if (leaderboard->public_.id == id)
-      return &leaderboard->public_;
+      return leaderboard;
   }
 
   return NULL;
@@ -3010,9 +3064,9 @@ const rc_client_leaderboard_t* rc_client_get_leaderboard_info(const rc_client_t*
     return NULL;
 
   for (subset = client->game->subsets; subset; subset = subset->next) {
-    const rc_client_leaderboard_t* leaderboard = rc_client_subset_get_leaderboard_info(subset, id);
+    const rc_client_leaderboard_info_t* leaderboard = rc_client_subset_get_leaderboard_info(subset, id);
     if (leaderboard != NULL)
-      return leaderboard;
+      return &leaderboard->public_;
   }
  
   return NULL;
@@ -3242,6 +3296,34 @@ void rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list)
     free(list);
 }
 
+int rc_client_has_leaderboards(rc_client_t* client)
+{
+  rc_client_subset_info_t* subset;
+  int result;
+
+  if (!client || !client->game)
+    return 0;
+
+  rc_mutex_lock(&client->state.mutex);
+
+  subset = client->game->subsets;
+  result = 0;
+  for (; subset; subset = subset->next)
+  {
+    if (!subset->active)
+      continue;
+
+    if (subset->public_.num_leaderboards > 0) {
+      result = 1;
+      break;
+    }
+  }
+
+  rc_mutex_unlock(&client->state.mutex);
+
+  return result;
+}
+
 static void rc_client_allocate_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard)
 {
   rc_client_leaderboard_tracker_info_t* tracker;
@@ -3345,6 +3427,61 @@ static void rc_client_submit_leaderboard_entry_retry(rc_client_scheduled_callbac
   rc_client_submit_leaderboard_entry_server_call(lboard_data);
 }
 
+static void rc_client_raise_scoreboard_event(rc_client_submit_leaderboard_entry_callback_data_t* lboard_data,
+    const rc_api_submit_lboard_entry_response_t* response)
+{
+  rc_client_leaderboard_scoreboard_t sboard;
+  rc_client_event_t client_event;
+  rc_client_subset_info_t* subset;
+  rc_client_t* client = lboard_data->client;
+  rc_client_leaderboard_info_t* leaderboard = NULL;
+
+  if (!client || !client->game)
+    return;
+
+  for (subset = client->game->subsets; subset; subset = subset->next) {
+    leaderboard = rc_client_subset_get_leaderboard_info(subset, lboard_data->id);
+    if (leaderboard != NULL)
+      break;
+  }
+  if (leaderboard == NULL) {
+    RC_CLIENT_LOG_ERR_FORMATTED(client, "Trying to raise scoreboard for unknown leaderboard %u", lboard_data->id);
+    return;
+  }
+
+  memset(&sboard, 0, sizeof(sboard));
+  sboard.leaderboard_id = lboard_data->id;
+  rc_format_value(sboard.submitted_score, sizeof(sboard.submitted_score), response->submitted_score, leaderboard->format);
+  rc_format_value(sboard.best_score, sizeof(sboard.best_score), response->best_score, leaderboard->format);
+  sboard.new_rank = response->new_rank;
+  sboard.num_entries = response->num_entries;
+  sboard.num_top_entries = response->num_top_entries;
+  if (sboard.num_top_entries > 0) {
+    sboard.top_entries = (rc_client_leaderboard_scoreboard_entry_t*)calloc(
+      response->num_top_entries, sizeof(rc_client_leaderboard_scoreboard_entry_t));
+    if (sboard.top_entries != NULL) {
+      unsigned i;
+      for (i = 0; i < response->num_top_entries; i++) {
+        sboard.top_entries[i].username = response->top_entries[i].username;
+        sboard.top_entries[i].rank = response->top_entries[i].rank;
+        rc_format_value(sboard.top_entries[i].score, sizeof(sboard.top_entries[i].score), response->top_entries[i].score,
+            leaderboard->format);
+      }
+    }
+  }
+
+  memset(&client_event, 0, sizeof(client_event));
+  client_event.type = RC_CLIENT_EVENT_LEADERBOARD_SCOREBOARD;
+  client_event.leaderboard = &leaderboard->public_;
+  client_event.leaderboard_scoreboard = &sboard;
+
+  lboard_data->client->callbacks.event_handler(&client_event, lboard_data->client);
+
+  if (sboard.top_entries != NULL) {
+    free(sboard.top_entries);
+  }
+}
+
 static void rc_client_submit_leaderboard_entry_callback(const rc_api_server_response_t* server_response, void* callback_data)
 {
   rc_client_submit_leaderboard_entry_callback_data_t* lboard_data =
@@ -3394,7 +3531,10 @@ static void rc_client_submit_leaderboard_entry_callback(const rc_api_server_resp
     }
   }
   else {
-    /* TODO: raise event for scoreboard (if retry_count < 2) */
+    /* raise event for scoreboard */
+    if (lboard_data->retry_count < 2) {
+      rc_client_raise_scoreboard_event(lboard_data, &submit_lboard_entry_response);
+    }
 
     /* not currently doing anything with the response */
     if (lboard_data->retry_count) {
@@ -3700,6 +3840,17 @@ static void rc_client_ping(rc_client_scheduled_callback_data_t* callback_data, r
   rc_client_schedule_callback(client, callback_data);
 }
 
+int rc_client_has_rich_presence(rc_client_t* client)
+{
+  if (!client || !client->game)
+    return 0;
+
+  if (!client->game->runtime.richpresence || !client->game->runtime.richpresence)
+    return 0;
+
+  return 1;
+}
+
 size_t rc_client_get_rich_presence_message(rc_client_t* client, char buffer[], size_t buffer_size)
 {
   int result;