From aef1954649a7bd3e262c1a114f030ec84d7f6d72 Mon Sep 17 00:00:00 2001 From: biscuitcakes Date: Thu, 22 Jan 2026 16:27:46 -0600 Subject: [PATCH 1/5] update rcheevos, setId -> gameId --- .gitmodules | 6 +- cmake/rcheevos.cmake | 8 +- include/rcheevos/module.modulemap | 141 +- include/rcheevos/rc_api_editor.h | 555 +++--- include/rcheevos/rc_api_info.h | 465 +++-- include/rcheevos/rc_api_request.h | 141 +- include/rcheevos/rc_api_runtime.h | 727 ++++---- include/rcheevos/rc_api_user.h | 414 +++-- include/rcheevos/rc_client.h | 1587 +++++++++-------- include/rcheevos/rc_client_raintegration.h | 190 +- include/rcheevos/rc_consoles.h | 275 +-- include/rcheevos/rc_error.h | 115 +- include/rcheevos/rc_export.h | 200 +-- include/rcheevos/rc_hash.h | 352 ++-- include/rcheevos/rc_libretro.h | 207 +-- include/rcheevos/rc_runtime.h | 306 ++-- include/rcheevos/rc_runtime_types.h | 883 ++++----- include/rcheevos/rc_util.h | 102 +- include/rcheevos/rcheevos.h | 16 +- qml/Main3.qml | 11 +- .../achievements/achievement_repository.hpp | 38 +- src/app/achievements/achievement_service.cpp | 28 +- src/app/achievements/achievement_service.hpp | 15 +- src/app/achievements/models/achievement.hpp | 2 +- .../sqlite_achievement_repository.cpp | 49 +- .../sqlite_achievement_repository.hpp | 22 +- .../offline/rcheevos_offline_client.cpp | 2 +- .../sqlite_achievement_repository_test.cpp | 10 +- .../rcheevos/rcheevos_offline_client_test.cpp | 12 +- 29 files changed, 3738 insertions(+), 3141 deletions(-) diff --git a/.gitmodules b/.gitmodules index 483266ef..83094a9b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ -[submodule "libs/rcheevos"] - path = libs/rcheevos - url = https://github.com/RetroAchievements/rcheevos.git [submodule "thirdparty/SQLiteCpp"] path = thirdparty/SQLiteCpp url = https://github.com/SRombauts/SQLiteCpp.git +[submodule "libs/rcheevos"] + path = libs/rcheevos + url = https://github.com/RetroAchievements/rcheevos.git diff --git a/cmake/rcheevos.cmake b/cmake/rcheevos.cmake index 7b765f2f..166a26ac 100644 --- a/cmake/rcheevos.cmake +++ b/cmake/rcheevos.cmake @@ -2,11 +2,17 @@ cmake_minimum_required(VERSION 3.22.1) project(rcheevos) add_compile_definitions(RC_DISABLE_LUA) +#add_compile_definitions(RC_CLIENT_SUPPORTS_EXTERNAL) set(CMAKE_CXX_STANDARD 20) file(GLOB RCHEEVOS_SOURCES - ${CMAKE_SOURCE_DIR}/libs/rcheevos/src/*.c + ${CMAKE_SOURCE_DIR}/libs/rcheevos/src/rc_client.c + ${CMAKE_SOURCE_DIR}/libs/rcheevos/src/rc_client_raintegration.c + ${CMAKE_SOURCE_DIR}/libs/rcheevos/src/rc_compat.c + ${CMAKE_SOURCE_DIR}/libs/rcheevos/src/rc_libretro.c + ${CMAKE_SOURCE_DIR}/libs/rcheevos/src/rc_util.c + ${CMAKE_SOURCE_DIR}/libs/rcheevos/src/rc_version.c ${CMAKE_SOURCE_DIR}/libs/rcheevos/src/**/*.c) add_library(rcheevos STATIC ${RCHEEVOS_SOURCES}) diff --git a/include/rcheevos/module.modulemap b/include/rcheevos/module.modulemap index 3b4c3ba2..2a53853d 100644 --- a/include/rcheevos/module.modulemap +++ b/include/rcheevos/module.modulemap @@ -1,71 +1,70 @@ -module rcheevos { - umbrella header "rcheevos.h" - - module rapi { - module rc_api_editor { - header "rc_api_editor.h" - export * - } - - module rc_api_info { - header "rc_api_info.h" - export * - } - - module rc_api_request { - header "rc_api_request.h" - export * - } - - module rc_api_runtime { - header "rc_api_runtime.h" - export * - } - - module rc_api_user { - header "rc_api_user.h" - export * - } - - export * - } - - module rc_client { - header "rc_client.h" - export * - } - - module rc_consoles { - header "rc_consoles.h" - export * - } - - module rc_error { - header "rc_error.h" - export * - } - - module rc_runtime { - header "rc_runtime.h" - export * - } - - module rc_runtime_types { - header "rc_runtime_types.h" - export * - } - - module rc_hash { - header "rc_hash.h" - export * - } - - export * - module * { export * } - - //rurl is deprecated - explicit module rc_url { - header "rc_url.h" - export * - } -} +module rcheevos { + umbrella header "rcheevos.h" + + module rapi { + module rc_api_editor { + header "rc_api_editor.h" + export * + } + + module rc_api_info { + header "rc_api_info.h" + export * + } + + module rc_api_request { + header "rc_api_request.h" + export * + } + + module rc_api_runtime { + header "rc_api_runtime.h" + export * + } + + module rc_api_user { + header "rc_api_user.h" + export * + } + + export * + } + + module rc_client { + header "rc_client.h" + export * + } + + explicit module rc_client_raintegration { + header "rc_client_raintegration.h" + export * + } + + module rc_consoles { + header "rc_consoles.h" + export * + } + + module rc_error { + header "rc_error.h" + export * + } + + module rc_runtime { + header "rc_runtime.h" + export * + } + + module rc_runtime_types { + header "rc_runtime_types.h" + export * + } + + module rc_hash { + header "rc_hash.h" + export * + } + + export * + module * { export * } +} diff --git a/include/rcheevos/rc_api_editor.h b/include/rcheevos/rc_api_editor.h index 533d57af..61e2fd4e 100644 --- a/include/rcheevos/rc_api_editor.h +++ b/include/rcheevos/rc_api_editor.h @@ -1,259 +1,296 @@ -#ifndef RC_API_EDITOR_H -#define RC_API_EDITOR_H - -#include "rc_api_request.h" - -#include - -RC_BEGIN_C_DECLS - -/* --- Fetch Code Notes --- */ - -/** - * API parameters for a fetch code notes request. - */ -typedef struct rc_api_fetch_code_notes_request_t { - /* The unique identifier of the game */ - uint32_t game_id; -} -rc_api_fetch_code_notes_request_t; - -/* A code note definiton */ -typedef struct rc_api_code_note_t { - /* The address the note is associated to */ - uint32_t address; - /* The name of the use who last updated the note */ - const char* author; - /* The contents of the note */ - const char* note; -} rc_api_code_note_t; - -/** - * Response data for a fetch code notes request. - */ -typedef struct rc_api_fetch_code_notes_response_t { - /* An array of code notes for the game */ - rc_api_code_note_t* notes; - /* The number of items in the notes array */ - uint32_t num_notes; - - /* Common server-provided response information */ - rc_api_response_t response; -} -rc_api_fetch_code_notes_response_t; - -RC_EXPORT int RC_CCONV rc_api_init_fetch_code_notes_request(rc_api_request_t* request, const rc_api_fetch_code_notes_request_t* api_params); -/* [deprecated] use rc_api_process_fetch_code_notes_server_response instead */ -RC_EXPORT int RC_CCONV rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response, const char* server_response); -RC_EXPORT int RC_CCONV rc_api_process_fetch_code_notes_server_response(rc_api_fetch_code_notes_response_t* response, const rc_api_server_response_t* server_response); -RC_EXPORT void RC_CCONV rc_api_destroy_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response); - -/* --- Update Code Note --- */ - -/** - * API parameters for an update code note request. - */ -typedef struct rc_api_update_code_note_request_t { - /* The username of the developer */ - const char* username; - /* The API token from the login request */ - const char* api_token; - /* The unique identifier of the game */ - uint32_t game_id; - /* The address the note is associated to */ - uint32_t address; - /* The contents of the note (NULL or empty to delete a note) */ - const char* note; -} -rc_api_update_code_note_request_t; - -/** - * Response data for an update code note request. - */ -typedef struct rc_api_update_code_note_response_t { - /* Common server-provided response information */ - rc_api_response_t response; -} -rc_api_update_code_note_response_t; - -RC_EXPORT int RC_CCONV rc_api_init_update_code_note_request(rc_api_request_t* request, const rc_api_update_code_note_request_t* api_params); -/* [deprecated] use rc_api_process_update_code_note_server_response instead */ -RC_EXPORT int RC_CCONV rc_api_process_update_code_note_response(rc_api_update_code_note_response_t* response, const char* server_response); -RC_EXPORT int RC_CCONV rc_api_process_update_code_note_server_response(rc_api_update_code_note_response_t* response, const rc_api_server_response_t* server_response); -RC_EXPORT void RC_CCONV rc_api_destroy_update_code_note_response(rc_api_update_code_note_response_t* response); - -/* --- Update Achievement --- */ - -/** - * API parameters for an update achievement request. - */ -typedef struct rc_api_update_achievement_request_t { - /* The username of the developer */ - const char* username; - /* The API token from the login request */ - const char* api_token; - /* The unique identifier of the achievement (0 to create a new achievement) */ - uint32_t achievement_id; - /* The unique identifier of the game */ - uint32_t game_id; - /* The name of the achievement */ - const char* title; - /* The description of the achievement */ - const char* description; - /* The badge name for the achievement */ - const char* badge; - /* The serialized trigger for the achievement */ - const char* trigger; - /* The number of points the achievement is worth */ - uint32_t points; - /* The category of the achievement */ - uint32_t category; - /* The type of the achievement */ - uint32_t type; -} -rc_api_update_achievement_request_t; - -/** - * Response data for an update achievement request. - */ -typedef struct rc_api_update_achievement_response_t { - /* The unique identifier of the achievement */ - uint32_t achievement_id; - - /* Common server-provided response information */ - rc_api_response_t response; -} -rc_api_update_achievement_response_t; - -RC_EXPORT int RC_CCONV rc_api_init_update_achievement_request(rc_api_request_t* request, const rc_api_update_achievement_request_t* api_params); -/* [deprecated] use rc_api_process_update_achievement_server_response instead */ -RC_EXPORT int RC_CCONV rc_api_process_update_achievement_response(rc_api_update_achievement_response_t* response, const char* server_response); -RC_EXPORT int RC_CCONV rc_api_process_update_achievement_server_response(rc_api_update_achievement_response_t* response, const rc_api_server_response_t* server_response); -RC_EXPORT void RC_CCONV rc_api_destroy_update_achievement_response(rc_api_update_achievement_response_t* response); - -/* --- Update Leaderboard --- */ - -/** - * API parameters for an update leaderboard request. - */ -typedef struct rc_api_update_leaderboard_request_t { - /* The username of the developer */ - const char* username; - /* The API token from the login request */ - const char* api_token; - /* The unique identifier of the leaderboard (0 to create a new leaderboard) */ - uint32_t leaderboard_id; - /* The unique identifier of the game */ - uint32_t game_id; - /* The name of the leaderboard */ - const char* title; - /* The description of the leaderboard */ - const char* description; - /* The start trigger for the leaderboard */ - const char* start_trigger; - /* The submit trigger for the leaderboard */ - const char* submit_trigger; - /* The cancel trigger for the leaderboard */ - const char* cancel_trigger; - /* The value definition for the leaderboard */ - const char* value_definition; - /* The format of leaderboard values */ - const char* format; - /* Whether or not lower scores are better for the leaderboard */ - uint32_t lower_is_better; -} -rc_api_update_leaderboard_request_t; - -/** - * Response data for an update leaderboard request. - */ -typedef struct rc_api_update_leaderboard_response_t { - /* The unique identifier of the leaderboard */ - uint32_t leaderboard_id; - - /* Common server-provided response information */ - rc_api_response_t response; -} -rc_api_update_leaderboard_response_t; - -RC_EXPORT int RC_CCONV rc_api_init_update_leaderboard_request(rc_api_request_t* request, const rc_api_update_leaderboard_request_t* api_params); -/* [deprecated] use rc_api_process_update_leaderboard_server_response instead */ -RC_EXPORT int RC_CCONV rc_api_process_update_leaderboard_response(rc_api_update_leaderboard_response_t* response, const char* server_response); -RC_EXPORT int RC_CCONV rc_api_process_update_leaderboard_server_response(rc_api_update_leaderboard_response_t* response, const rc_api_server_response_t* server_response); -RC_EXPORT void RC_CCONV rc_api_destroy_update_leaderboard_response(rc_api_update_leaderboard_response_t* response); - -/* --- Fetch Badge Range --- */ - -/** - * API parameters for a fetch badge range request. - */ -typedef struct rc_api_fetch_badge_range_request_t { - /* Unused */ - uint32_t unused; -} -rc_api_fetch_badge_range_request_t; - -/** - * Response data for a fetch badge range request. - */ -typedef struct rc_api_fetch_badge_range_response_t { - /* The numeric identifier of the first valid badge ID */ - uint32_t first_badge_id; - /* The numeric identifier of the first unassigned badge ID */ - uint32_t next_badge_id; - - /* Common server-provided response information */ - rc_api_response_t response; -} -rc_api_fetch_badge_range_response_t; - -RC_EXPORT int RC_CCONV rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_api_fetch_badge_range_request_t* api_params); -/* [deprecated] use rc_api_process_fetch_badge_range_server_response instead */ -RC_EXPORT int RC_CCONV rc_api_process_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response, const char* server_response); -RC_EXPORT int RC_CCONV rc_api_process_fetch_badge_range_server_response(rc_api_fetch_badge_range_response_t* response, const rc_api_server_response_t* server_response); -RC_EXPORT void RC_CCONV rc_api_destroy_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response); - -/* --- Add Game Hash --- */ - -/** - * API parameters for an add game hash request. - */ -typedef struct rc_api_add_game_hash_request_t { - /* The username of the developer */ - const char* username; - /* The API token from the login request */ - const char* api_token; - /* The unique identifier of the game (0 to create a new game entry) */ - uint32_t game_id; - /* The unique identifier of the console for the game */ - uint32_t console_id; - /* The title of the game */ - const char* title; - /* The hash being added */ - const char* hash; - /* A description of the hash being added (usually the normalized ROM name) */ - const char* hash_description; -} -rc_api_add_game_hash_request_t; - -/** - * Response data for an update code note request. - */ -typedef struct rc_api_add_game_hash_response_t { - /* The unique identifier of the game */ - uint32_t game_id; - - /* Common server-provided response information */ - rc_api_response_t response; -} -rc_api_add_game_hash_response_t; - -RC_EXPORT int RC_CCONV rc_api_init_add_game_hash_request(rc_api_request_t* request, const rc_api_add_game_hash_request_t* api_params); -/* [deprecated] use rc_api_process_add_game_hash_server_response instead */ -RC_EXPORT int RC_CCONV rc_api_process_add_game_hash_response(rc_api_add_game_hash_response_t* response, const char* server_response); -RC_EXPORT int RC_CCONV rc_api_process_add_game_hash_server_response(rc_api_add_game_hash_response_t* response, const rc_api_server_response_t* server_response); -RC_EXPORT void RC_CCONV rc_api_destroy_add_game_hash_response(rc_api_add_game_hash_response_t* response); - -RC_END_C_DECLS - -#endif /* RC_EDITOR_H */ +#ifndef RC_API_EDITOR_H +#define RC_API_EDITOR_H + +#include "rc_api_request.h" + +#include + +RC_BEGIN_C_DECLS + +/* --- Fetch Code Notes --- */ + +/** + * API parameters for a fetch code notes request. + */ +typedef struct rc_api_fetch_code_notes_request_t { + /* The unique identifier of the game */ + uint32_t game_id; +} +rc_api_fetch_code_notes_request_t; + +/* A code note definiton */ +typedef struct rc_api_code_note_t { + /* The address the note is associated to */ + uint32_t address; + /* The name of the use who last updated the note */ + const char* author; + /* The contents of the note */ + const char* note; +} rc_api_code_note_t; + +/** + * Response data for a fetch code notes request. + */ +typedef struct rc_api_fetch_code_notes_response_t { + /* An array of code notes for the game */ + rc_api_code_note_t* notes; + /* The number of items in the notes array */ + uint32_t num_notes; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_fetch_code_notes_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_fetch_code_notes_request(rc_api_request_t* request, const rc_api_fetch_code_notes_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_fetch_code_notes_request_hosted(rc_api_request_t* request, const rc_api_fetch_code_notes_request_t* api_params, const rc_api_host_t* host); +/* [deprecated] use rc_api_process_fetch_code_notes_server_response instead */ +RC_EXPORT int RC_CCONV rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_fetch_code_notes_server_response(rc_api_fetch_code_notes_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response); + +/* --- Update Code Note --- */ + +/** + * API parameters for an update code note request. + */ +typedef struct rc_api_update_code_note_request_t { + /* The username of the developer */ + const char* username; + /* The API token from the login request */ + const char* api_token; + /* The unique identifier of the game */ + uint32_t game_id; + /* The address the note is associated to */ + uint32_t address; + /* The contents of the note (NULL or empty to delete a note) */ + const char* note; +} +rc_api_update_code_note_request_t; + +/** + * Response data for an update code note request. + */ +typedef struct rc_api_update_code_note_response_t { + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_update_code_note_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_update_code_note_request(rc_api_request_t* request, const rc_api_update_code_note_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_update_code_note_request_hosted(rc_api_request_t* request, const rc_api_update_code_note_request_t* api_params, const rc_api_host_t* host); +/* [deprecated] use rc_api_process_update_code_note_server_response instead */ +RC_EXPORT int RC_CCONV rc_api_process_update_code_note_response(rc_api_update_code_note_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_update_code_note_server_response(rc_api_update_code_note_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_update_code_note_response(rc_api_update_code_note_response_t* response); + +/* --- Update Achievement --- */ + +/** + * API parameters for an update achievement request. + */ +typedef struct rc_api_update_achievement_request_t { + /* The username of the developer */ + const char* username; + /* The API token from the login request */ + const char* api_token; + /* The unique identifier of the achievement (0 to create a new achievement) */ + uint32_t achievement_id; + /* The unique identifier of the game */ + uint32_t game_id; + /* The name of the achievement */ + const char* title; + /* The description of the achievement */ + const char* description; + /* The badge name for the achievement */ + const char* badge; + /* The serialized trigger for the achievement */ + const char* trigger; + /* The number of points the achievement is worth */ + uint32_t points; + /* The category of the achievement */ + uint32_t category; + /* The type of the achievement */ + uint32_t type; +} +rc_api_update_achievement_request_t; + +/** + * Response data for an update achievement request. + */ +typedef struct rc_api_update_achievement_response_t { + /* The unique identifier of the achievement */ + uint32_t achievement_id; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_update_achievement_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_update_achievement_request(rc_api_request_t* request, const rc_api_update_achievement_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_update_achievement_request_hosted(rc_api_request_t* request, const rc_api_update_achievement_request_t* api_params, const rc_api_host_t* host); +/* [deprecated] use rc_api_process_update_achievement_server_response instead */ +RC_EXPORT int RC_CCONV rc_api_process_update_achievement_response(rc_api_update_achievement_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_update_achievement_server_response(rc_api_update_achievement_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_update_achievement_response(rc_api_update_achievement_response_t* response); + +/* --- Update Leaderboard --- */ + +/** + * API parameters for an update leaderboard request. + */ +typedef struct rc_api_update_leaderboard_request_t { + /* The username of the developer */ + const char* username; + /* The API token from the login request */ + const char* api_token; + /* The unique identifier of the leaderboard (0 to create a new leaderboard) */ + uint32_t leaderboard_id; + /* The unique identifier of the game */ + uint32_t game_id; + /* The name of the leaderboard */ + const char* title; + /* The description of the leaderboard */ + const char* description; + /* The start trigger for the leaderboard */ + const char* start_trigger; + /* The submit trigger for the leaderboard */ + const char* submit_trigger; + /* The cancel trigger for the leaderboard */ + const char* cancel_trigger; + /* The value definition for the leaderboard */ + const char* value_definition; + /* The format of leaderboard values */ + const char* format; + /* Whether or not lower scores are better for the leaderboard */ + uint32_t lower_is_better; +} +rc_api_update_leaderboard_request_t; + +/** + * Response data for an update leaderboard request. + */ +typedef struct rc_api_update_leaderboard_response_t { + /* The unique identifier of the leaderboard */ + uint32_t leaderboard_id; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_update_leaderboard_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_update_leaderboard_request(rc_api_request_t* request, const rc_api_update_leaderboard_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_update_leaderboard_request_hosted(rc_api_request_t* request, const rc_api_update_leaderboard_request_t* api_params, const rc_api_host_t* host); +/* [deprecated] use rc_api_process_update_leaderboard_server_response instead */ +RC_EXPORT int RC_CCONV rc_api_process_update_leaderboard_response(rc_api_update_leaderboard_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_update_leaderboard_server_response(rc_api_update_leaderboard_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_update_leaderboard_response(rc_api_update_leaderboard_response_t* response); + +/* --- Update Rich Presence --- */ + +/** + * API parameters for an update rich presence request. + */ +typedef struct rc_api_update_rich_presence_request_t { + /* The username of the developer */ + const char* username; + /* The API token from the login request */ + const char* api_token; + /* The unique identifier of the game */ + uint32_t game_id; + /* The script for the rich_presence */ + const char* script; +} +rc_api_update_rich_presence_request_t; + +/** + * Response data for an update rich presence request. + */ +typedef struct rc_api_update_rich_presence_response_t { + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_update_rich_presence_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_update_rich_presence_request(rc_api_request_t* request, const rc_api_update_rich_presence_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_update_rich_presence_request_hosted(rc_api_request_t* request, const rc_api_update_rich_presence_request_t* api_params, const rc_api_host_t* host); +RC_EXPORT int RC_CCONV rc_api_process_update_rich_presence_server_response(rc_api_update_rich_presence_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_update_rich_presence_response(rc_api_update_rich_presence_response_t* response); + +/* --- Fetch Badge Range --- */ + +/** + * API parameters for a fetch badge range request. + */ +typedef struct rc_api_fetch_badge_range_request_t { + /* Unused */ + uint32_t unused; +} +rc_api_fetch_badge_range_request_t; + +/** + * Response data for a fetch badge range request. + */ +typedef struct rc_api_fetch_badge_range_response_t { + /* The numeric identifier of the first valid badge ID */ + uint32_t first_badge_id; + /* The numeric identifier of the first unassigned badge ID */ + uint32_t next_badge_id; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_fetch_badge_range_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_api_fetch_badge_range_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_fetch_badge_range_request_hosted(rc_api_request_t* request, const rc_api_fetch_badge_range_request_t* api_params, const rc_api_host_t* host); +/* [deprecated] use rc_api_process_fetch_badge_range_server_response instead */ +RC_EXPORT int RC_CCONV rc_api_process_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_fetch_badge_range_server_response(rc_api_fetch_badge_range_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response); + +/* --- Add Game Hash --- */ + +/** + * API parameters for an add game hash request. + */ +typedef struct rc_api_add_game_hash_request_t { + /* The username of the developer */ + const char* username; + /* The API token from the login request */ + const char* api_token; + /* The unique identifier of the game (0 to create a new game entry) */ + uint32_t game_id; + /* The unique identifier of the console for the game */ + uint32_t console_id; + /* The title of the game */ + const char* title; + /* The hash being added */ + const char* hash; + /* A description of the hash being added (usually the normalized ROM name) */ + const char* hash_description; +} +rc_api_add_game_hash_request_t; + +/** + * Response data for an update code note request. + */ +typedef struct rc_api_add_game_hash_response_t { + /* The unique identifier of the game */ + uint32_t game_id; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_add_game_hash_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_add_game_hash_request(rc_api_request_t* request, const rc_api_add_game_hash_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_add_game_hash_request_hosted(rc_api_request_t* request, const rc_api_add_game_hash_request_t* api_params, const rc_api_host_t* host); +/* [deprecated] use rc_api_process_add_game_hash_server_response instead */ +RC_EXPORT int RC_CCONV rc_api_process_add_game_hash_response(rc_api_add_game_hash_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_add_game_hash_server_response(rc_api_add_game_hash_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_add_game_hash_response(rc_api_add_game_hash_response_t* response); + +RC_END_C_DECLS + +#endif /* RC_EDITOR_H */ diff --git a/include/rcheevos/rc_api_info.h b/include/rcheevos/rc_api_info.h index 2710c253..0051fe23 100644 --- a/include/rcheevos/rc_api_info.h +++ b/include/rcheevos/rc_api_info.h @@ -1,185 +1,280 @@ -#ifndef RC_API_INFO_H -#define RC_API_INFO_H - -#include "rc_api_request.h" - -#include -#include - -RC_BEGIN_C_DECLS - -/* --- Fetch Achievement Info --- */ - -/** - * API parameters for a fetch achievement info request. - */ -typedef struct rc_api_fetch_achievement_info_request_t { - /* The username of the player */ - const char* username; - /* The API token from the login request */ - const char* api_token; - /* The unique identifier of the achievement */ - uint32_t achievement_id; - /* The 1-based index of the first entry to retrieve */ - uint32_t first_entry; - /* The number of entries to retrieve */ - uint32_t count; - /* Non-zero to only return unlocks earned by the user's friends */ - uint32_t friends_only; -} -rc_api_fetch_achievement_info_request_t; - -/* An achievement awarded entry */ -typedef struct rc_api_achievement_awarded_entry_t { - /* The user associated to the entry */ - const char* username; - /* When the achievement was awarded */ - time_t awarded; -} -rc_api_achievement_awarded_entry_t; - -/** - * Response data for a fetch achievement info request. - */ -typedef struct rc_api_fetch_achievement_info_response_t { - /* The unique identifier of the achievement */ - uint32_t id; - /* The unique identifier of the game to which the leaderboard is associated */ - uint32_t game_id; - /* The number of times the achievement has been awarded */ - uint32_t num_awarded; - /* The number of players that have earned at least one achievement for the game */ - uint32_t num_players; - - /* An array of recently rewarded entries */ - rc_api_achievement_awarded_entry_t* recently_awarded; - /* The number of items in the recently_awarded array */ - uint32_t num_recently_awarded; - - /* Common server-provided response information */ - rc_api_response_t response; -} -rc_api_fetch_achievement_info_response_t; - -RC_EXPORT int RC_CCONV rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params); -/* [deprecated] use rc_api_process_fetch_achievement_info_server_response instead */ -RC_EXPORT int RC_CCONV rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response); -RC_EXPORT int RC_CCONV rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achievement_info_response_t* response, const rc_api_server_response_t* server_response); -RC_EXPORT void RC_CCONV rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response); - -/* --- Fetch Leaderboard Info --- */ - -/** - * API parameters for a fetch leaderboard info request. - */ -typedef struct rc_api_fetch_leaderboard_info_request_t { - /* The unique identifier of the leaderboard */ - uint32_t leaderboard_id; - /* The number of entries to retrieve */ - uint32_t count; - /* The 1-based index of the first entry to retrieve */ - uint32_t first_entry; - /* The username of the player around whom the entries should be returned */ - const char* username; -} -rc_api_fetch_leaderboard_info_request_t; - -/* A leaderboard info entry */ -typedef struct rc_api_lboard_info_entry_t { - /* The user associated to the entry */ - const char* username; - /* The rank of the entry */ - uint32_t rank; - /* The index of the entry */ - uint32_t index; - /* The value of the entry */ - int32_t score; - /* When the entry was submitted */ - time_t submitted; -} -rc_api_lboard_info_entry_t; - -/** - * Response data for a fetch leaderboard info request. - */ -typedef struct rc_api_fetch_leaderboard_info_response_t { - /* The unique identifier of the leaderboard */ - uint32_t id; - /* The format to pass to rc_format_value to format the leaderboard value */ - int format; - /* If non-zero, indicates that lower scores appear first */ - uint32_t lower_is_better; - /* The title of the leaderboard */ - const char* title; - /* The description of the leaderboard */ - const char* description; - /* The definition of the leaderboard to be passed to rc_runtime_activate_lboard */ - const char* definition; - /* The unique identifier of the game to which the leaderboard is associated */ - uint32_t game_id; - /* The author of the leaderboard */ - const char* author; - /* When the leaderboard was first uploaded to the server */ - time_t created; - /* When the leaderboard was last modified on the server */ - time_t updated; - - /* An array of requested entries */ - rc_api_lboard_info_entry_t* entries; - /* The number of items in the entries array */ - uint32_t num_entries; - - /* Common server-provided response information */ - rc_api_response_t response; -} -rc_api_fetch_leaderboard_info_response_t; - -RC_EXPORT int RC_CCONV rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params); -/* [deprecated] use rc_api_process_fetch_leaderboard_info_server_response instead */ -RC_EXPORT int RC_CCONV rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response); -RC_EXPORT int RC_CCONV rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboard_info_response_t* response, const rc_api_server_response_t* server_response); -RC_EXPORT void RC_CCONV rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response); - -/* --- Fetch Games List --- */ - -/** - * API parameters for a fetch games list request. - */ -typedef struct rc_api_fetch_games_list_request_t { - /* The unique identifier of the console to query */ - uint32_t console_id; -} -rc_api_fetch_games_list_request_t; - -/* A game list entry */ -typedef struct rc_api_game_list_entry_t { - /* The unique identifier of the game */ - uint32_t id; - /* The name of the game */ - const char* name; -} -rc_api_game_list_entry_t; - -/** - * Response data for a fetch games list request. - */ -typedef struct rc_api_fetch_games_list_response_t { - /* An array of requested entries */ - rc_api_game_list_entry_t* entries; - /* The number of items in the entries array */ - uint32_t num_entries; - - /* Common server-provided response information */ - rc_api_response_t response; -} -rc_api_fetch_games_list_response_t; - -RC_EXPORT int RC_CCONV rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params); -/* [deprecated] use rc_api_process_fetch_games_list_server_response instead */ -RC_EXPORT int RC_CCONV rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response); -RC_EXPORT int RC_CCONV rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_response_t* response, const rc_api_server_response_t* server_response); -RC_EXPORT void RC_CCONV rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response); - -RC_END_C_DECLS - -#endif /* RC_API_INFO_H */ +#ifndef RC_API_INFO_H +#define RC_API_INFO_H + +#include "rc_api_request.h" + +#include +#include + +RC_BEGIN_C_DECLS + +/* --- Fetch Achievement Info --- */ + +/** + * API parameters for a fetch achievement info request. + */ +typedef struct rc_api_fetch_achievement_info_request_t { + /* The username of the player */ + const char* username; + /* The API token from the login request */ + const char* api_token; + /* The unique identifier of the achievement */ + uint32_t achievement_id; + /* The 1-based index of the first entry to retrieve */ + uint32_t first_entry; + /* The number of entries to retrieve */ + uint32_t count; + /* Non-zero to only return unlocks earned by the user's friends */ + uint32_t friends_only; +} +rc_api_fetch_achievement_info_request_t; + +/* An achievement awarded entry */ +typedef struct rc_api_achievement_awarded_entry_t { + /* The user associated to the entry */ + const char* username; + /* A URL to the user's avatar image */ + const char* avatar_url; + /* When the achievement was awarded */ + time_t awarded; +} +rc_api_achievement_awarded_entry_t; + +/** + * Response data for a fetch achievement info request. + */ +typedef struct rc_api_fetch_achievement_info_response_t { + /* The unique identifier of the achievement */ + uint32_t id; + /* The unique identifier of the game to which the leaderboard is associated */ + uint32_t game_id; + /* The number of times the achievement has been awarded */ + uint32_t num_awarded; + /* The number of players that have earned at least one achievement for the game */ + uint32_t num_players; + + /* An array of recently rewarded entries */ + rc_api_achievement_awarded_entry_t* recently_awarded; + /* The number of items in the recently_awarded array */ + uint32_t num_recently_awarded; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_fetch_achievement_info_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_fetch_achievement_info_request_hosted(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params, const rc_api_host_t* host); +/* [deprecated] use rc_api_process_fetch_achievement_info_server_response instead */ +RC_EXPORT int RC_CCONV rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achievement_info_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response); + +/* --- Fetch Leaderboard Info --- */ + +/** + * API parameters for a fetch leaderboard info request. + */ +typedef struct rc_api_fetch_leaderboard_info_request_t { + /* The unique identifier of the leaderboard */ + uint32_t leaderboard_id; + /* The number of entries to retrieve */ + uint32_t count; + /* The 1-based index of the first entry to retrieve */ + uint32_t first_entry; + /* The username of the player around whom the entries should be returned */ + const char* username; +} +rc_api_fetch_leaderboard_info_request_t; + +/* A leaderboard info entry */ +typedef struct rc_api_lboard_info_entry_t { + /* The user associated to the entry */ + const char* username; + /* A URL to the user's avatar image */ + const char* avatar_url; + /* The rank of the entry */ + uint32_t rank; + /* The index of the entry */ + uint32_t index; + /* The value of the entry */ + int32_t score; + /* When the entry was submitted */ + time_t submitted; +} +rc_api_lboard_info_entry_t; + +/** + * Response data for a fetch leaderboard info request. + */ +typedef struct rc_api_fetch_leaderboard_info_response_t { + /* The unique identifier of the leaderboard */ + uint32_t id; + /* The format to pass to rc_format_value to format the leaderboard value */ + int format; + /* If non-zero, indicates that lower scores appear first */ + uint32_t lower_is_better; + /* The title of the leaderboard */ + const char* title; + /* The description of the leaderboard */ + const char* description; + /* The definition of the leaderboard to be passed to rc_runtime_activate_lboard */ + const char* definition; + /* The unique identifier of the game to which the leaderboard is associated */ + uint32_t game_id; + /* The author of the leaderboard */ + const char* author; + /* When the leaderboard was first uploaded to the server */ + time_t created; + /* When the leaderboard was last modified on the server */ + time_t updated; + + /* An array of requested entries */ + rc_api_lboard_info_entry_t* entries; + /* The number of items in the entries array */ + uint32_t num_entries; + + /* The total number of entries on the server */ + uint32_t total_entries; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_fetch_leaderboard_info_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_fetch_leaderboard_info_request_hosted(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params, const rc_api_host_t* host); +/* [deprecated] use rc_api_process_fetch_leaderboard_info_server_response instead */ +RC_EXPORT int RC_CCONV rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboard_info_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response); + +/* --- Fetch Games List --- */ + +/** + * API parameters for a fetch games list request. + */ +typedef struct rc_api_fetch_games_list_request_t { + /* The unique identifier of the console to query */ + uint32_t console_id; +} +rc_api_fetch_games_list_request_t; + +/* A game list entry */ +typedef struct rc_api_game_list_entry_t { + /* The unique identifier of the game */ + uint32_t id; + /* The name of the game */ + const char* name; +} +rc_api_game_list_entry_t; + +/** + * Response data for a fetch games list request. + */ +typedef struct rc_api_fetch_games_list_response_t { + /* An array of requested entries */ + rc_api_game_list_entry_t* entries; + /* The number of items in the entries array */ + uint32_t num_entries; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_fetch_games_list_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_fetch_games_list_request_hosted(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params, const rc_api_host_t* host); +/* [deprecated] use rc_api_process_fetch_games_list_server_response instead */ +RC_EXPORT int RC_CCONV rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response); + +/* --- Fetch Game Titles --- */ + +/** + * API parameters for a fetch games list request. + */ +typedef struct rc_api_fetch_game_titles_request_t { + /* An array of game ids to fetch titles for */ + const uint32_t* game_ids; + /* The number of items in the game_ids array */ + uint32_t num_game_ids; +} +rc_api_fetch_game_titles_request_t; + +/* A game title entry */ +typedef struct rc_api_game_title_entry_t { + /* The unique identifier of the game */ + uint32_t id; + /* The title of the game */ + const char* title; + /* The image name for the game badge */ + const char* image_name; + /* The URL for the game badge image */ + const char* image_url; +} +rc_api_game_title_entry_t; + +/** + * Response data for a fetch games title request. + */ +typedef struct rc_api_fetch_game_titles_response_t { + /* An array of requested entries */ + rc_api_game_title_entry_t* entries; + /* The number of items in the entries array */ + uint32_t num_entries; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_fetch_game_titles_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_fetch_game_titles_request(rc_api_request_t* request, const rc_api_fetch_game_titles_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_fetch_game_titles_request_hosted(rc_api_request_t* request, const rc_api_fetch_game_titles_request_t* api_params, const rc_api_host_t* host); +RC_EXPORT int RC_CCONV rc_api_process_fetch_game_titles_server_response(rc_api_fetch_game_titles_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_fetch_game_titles_response(rc_api_fetch_game_titles_response_t* response); + +/* --- Fetch Game Hashes --- */ + +/** + * API parameters for a fetch games list request. + */ +typedef struct rc_api_fetch_hash_library_request_t { + /** + * The unique identifier of the console to query. + * Passing RC_CONSOLE_UNKNOWN will return hashes for all consoles. + */ + uint32_t console_id; +} rc_api_fetch_hash_library_request_t; + +/* A hash library entry */ +typedef struct rc_api_hash_library_entry_t { + /* The hash for the game */ + const char* hash; + /* The unique identifier of the game */ + uint32_t game_id; +} rc_api_hash_library_entry_t; + +/** + * Response data for a fetch hash library request. + */ +typedef struct rc_api_fetch_hash_library_response_t { + /* An array of entries, one per game */ + rc_api_hash_library_entry_t* entries; + /* The number of items in the entries array */ + uint32_t num_entries; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_fetch_hash_library_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_fetch_hash_library_request(rc_api_request_t* request, const rc_api_fetch_hash_library_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_fetch_hash_library_request_hosted(rc_api_request_t* request, const rc_api_fetch_hash_library_request_t* api_params, const rc_api_host_t* host); +RC_EXPORT int RC_CCONV rc_api_process_fetch_hash_library_server_response(rc_api_fetch_hash_library_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_fetch_hash_library_response(rc_api_fetch_hash_library_response_t* response); + +RC_END_C_DECLS + +#endif /* RC_API_INFO_H */ diff --git a/include/rcheevos/rc_api_request.h b/include/rcheevos/rc_api_request.h index 8e2017f2..ddccc7bc 100644 --- a/include/rcheevos/rc_api_request.h +++ b/include/rcheevos/rc_api_request.h @@ -1,64 +1,77 @@ -#ifndef RC_API_REQUEST_H -#define RC_API_REQUEST_H - -#include "rc_error.h" -#include "rc_util.h" - -#include - -RC_BEGIN_C_DECLS - -/** - * A constructed request to send to the retroachievements server. - */ -typedef struct rc_api_request_t { - /* The URL to send the request to (contains protocol, host, path, and query args) */ - const char* url; - /* Additional query args that should be sent via a POST command. If null, GET may be used */ - const char* post_data; - /* The HTTP Content-Type of the POST data. */ - const char* content_type; - - /* Storage for the url and post_data */ - rc_buffer_t buffer; -} -rc_api_request_t; - -/** - * Common attributes for all server responses. - */ -typedef struct rc_api_response_t { - /* Server-provided success indicator (non-zero on success, zero on failure) */ - int succeeded; - /* Server-provided message associated to the failure */ - const char* error_message; - /* Server-provided error code associated to the failure */ - const char* error_code; - - /* Storage for the response data */ - rc_buffer_t buffer; -} -rc_api_response_t; - -RC_EXPORT void RC_CCONV rc_api_destroy_request(rc_api_request_t* request); - -RC_EXPORT void RC_CCONV rc_api_set_host(const char* hostname); -RC_EXPORT void RC_CCONV rc_api_set_image_host(const char* hostname); - -typedef struct rc_api_server_response_t { - /* Pointer to the data returned from the server */ - const char* body; - /* Length of data returned from the server (Content-Length) */ - size_t body_length; - /* HTTP status code returned from the server */ - int http_status_code; -} rc_api_server_response_t; - -enum { - RC_API_SERVER_RESPONSE_CLIENT_ERROR = -1, - RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR = -2 -}; - -RC_END_C_DECLS - -#endif /* RC_API_REQUEST_H */ +#ifndef RC_API_REQUEST_H +#define RC_API_REQUEST_H + +#include "rc_error.h" +#include "rc_util.h" + +#include + +RC_BEGIN_C_DECLS + +/** + * Information about the server being connected to. + */ +typedef struct rc_api_host_t { + /* The host name for the API calls (retroachievements.org) */ + const char* host; + /* The host name for media URLs (media.retroachievements.org) */ + const char* media_host; +} +rc_api_host_t; + +/** + * A constructed request to send to the retroachievements server. + */ +typedef struct rc_api_request_t { + /* The URL to send the request to (contains protocol, host, path, and query args) */ + const char* url; + /* Additional query args that should be sent via a POST command. If null, GET may be used */ + const char* post_data; + /* The HTTP Content-Type of the POST data. */ + const char* content_type; + + /* Storage for the url and post_data */ + rc_buffer_t buffer; +} +rc_api_request_t; + +/** + * Common attributes for all server responses. + */ +typedef struct rc_api_response_t { + /* Server-provided success indicator (non-zero on success, zero on failure) */ + int succeeded; + /* Server-provided message associated to the failure */ + const char* error_message; + /* Server-provided error code associated to the failure */ + const char* error_code; + + /* Storage for the response data */ + rc_buffer_t buffer; +} +rc_api_response_t; + +RC_EXPORT void RC_CCONV rc_api_destroy_request(rc_api_request_t* request); + +/* [deprecated] use rc_api_init_*_hosted instead */ +RC_EXPORT void RC_CCONV rc_api_set_host(const char* hostname); +/* [deprecated] use rc_api_init_*_hosted instead */ +RC_EXPORT void RC_CCONV rc_api_set_image_host(const char* hostname); + +typedef struct rc_api_server_response_t { + /* Pointer to the data returned from the server */ + const char* body; + /* Length of data returned from the server (Content-Length) */ + size_t body_length; + /* HTTP status code returned from the server */ + int http_status_code; +} rc_api_server_response_t; + +enum { + RC_API_SERVER_RESPONSE_CLIENT_ERROR = -1, + RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR = -2 +}; + +RC_END_C_DECLS + +#endif /* RC_API_REQUEST_H */ diff --git a/include/rcheevos/rc_api_runtime.h b/include/rcheevos/rc_api_runtime.h index 3f580f5f..8a0de01b 100644 --- a/include/rcheevos/rc_api_runtime.h +++ b/include/rcheevos/rc_api_runtime.h @@ -1,310 +1,417 @@ -#ifndef RC_API_RUNTIME_H -#define RC_API_RUNTIME_H - -#include "rc_api_request.h" - -#include -#include - -RC_BEGIN_C_DECLS - -/* --- Fetch Image --- */ - -/** - * API parameters for a fetch image request. - * NOTE: fetch image server response is the raw image data. There is no rc_api_process_fetch_image_response function. - */ -typedef struct rc_api_fetch_image_request_t { - /* The name of the image to fetch */ - const char* image_name; - /* The type of image to fetch */ - uint32_t image_type; -} -rc_api_fetch_image_request_t; - -#define RC_IMAGE_TYPE_GAME 1 -#define RC_IMAGE_TYPE_ACHIEVEMENT 2 -#define RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED 3 -#define RC_IMAGE_TYPE_USER 4 - -RC_EXPORT int RC_CCONV rc_api_init_fetch_image_request(rc_api_request_t* request, const rc_api_fetch_image_request_t* api_params); - -/* --- Resolve Hash --- */ - -/** - * API parameters for a resolve hash request. - */ -typedef struct rc_api_resolve_hash_request_t { - /* Unused - hash lookup does not require credentials */ - const char* username; - /* Unused - hash lookup does not require credentials */ - const char* api_token; - /* The generated hash of the game to be identified */ - const char* game_hash; -} -rc_api_resolve_hash_request_t; - -/** - * Response data for a resolve hash request. - */ -typedef struct rc_api_resolve_hash_response_t { - /* The unique identifier of the game, 0 if no match was found */ - uint32_t game_id; - - /* Common server-provided response information */ - rc_api_response_t response; -} -rc_api_resolve_hash_response_t; - -RC_EXPORT int RC_CCONV rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_resolve_hash_request_t* api_params); -RC_EXPORT int RC_CCONV rc_api_process_resolve_hash_response(rc_api_resolve_hash_response_t* response, const char* server_response); -RC_EXPORT int RC_CCONV rc_api_process_resolve_hash_server_response(rc_api_resolve_hash_response_t* response, const rc_api_server_response_t* server_response); -RC_EXPORT void RC_CCONV rc_api_destroy_resolve_hash_response(rc_api_resolve_hash_response_t* response); - -/* --- Fetch Game Data --- */ - -/** - * API parameters for a fetch game data request. - */ -typedef struct rc_api_fetch_game_data_request_t { - /* The username of the player */ - const char* username; - /* The API token from the login request */ - const char* api_token; - /* The unique identifier of the game */ - uint32_t game_id; -} -rc_api_fetch_game_data_request_t; - -/* A leaderboard definition */ -typedef struct rc_api_leaderboard_definition_t { - /* The unique identifier of the leaderboard */ - uint32_t id; - /* The format to pass to rc_format_value to format the leaderboard value */ - int format; - /* The title of the leaderboard */ - const char* title; - /* The description of the leaderboard */ - const char* description; - /* The definition of the leaderboard to be passed to rc_runtime_activate_lboard */ - const char* definition; - /* Non-zero if lower values are better for this leaderboard */ - uint8_t lower_is_better; - /* Non-zero if the leaderboard should not be displayed in a list of leaderboards */ - uint8_t hidden; -} -rc_api_leaderboard_definition_t; - -/* An achievement definition */ -typedef struct rc_api_achievement_definition_t { - /* The unique identifier of the achievement */ - uint32_t id; - /* The number of points the achievement is worth */ - uint32_t points; - /* The achievement category (core, unofficial) */ - uint32_t category; - /* The title of the achievement */ - const char* title; - /* The dscription of the achievement */ - const char* description; - /* The definition of the achievement to be passed to rc_runtime_activate_achievement */ - const char* definition; - /* The author of the achievment */ - const char* author; - /* The image name for the achievement badge */ - const char* badge_name; - /* When the achievement was first uploaded to the server */ - time_t created; - /* When the achievement was last modified on the server */ - time_t updated; - /* The achievement type (win/progression/missable) */ - uint32_t type; - /* The approximate rarity of the achievement (X% of users have earned the achievement) */ - float rarity; - /* The approximate rarity of the achievement in hardcore (X% of users have earned the achievement in hardcore) */ - float rarity_hardcore; -} -rc_api_achievement_definition_t; - -#define RC_ACHIEVEMENT_CATEGORY_CORE 3 -#define RC_ACHIEVEMENT_CATEGORY_UNOFFICIAL 5 - -#define RC_ACHIEVEMENT_TYPE_STANDARD 0 -#define RC_ACHIEVEMENT_TYPE_MISSABLE 1 -#define RC_ACHIEVEMENT_TYPE_PROGRESSION 2 -#define RC_ACHIEVEMENT_TYPE_WIN 3 - -/** - * Response data for a fetch game data request. - */ -typedef struct rc_api_fetch_game_data_response_t { - /* The unique identifier of the game */ - uint32_t id; - /* The console associated to the game */ - uint32_t console_id; - /* The title of the game */ - const char* title; - /* The image name for the game badge */ - const char* image_name; - /* The rich presence script for the game to be passed to rc_runtime_activate_richpresence */ - const char* rich_presence_script; - - /* An array of achievements for the game */ - rc_api_achievement_definition_t* achievements; - /* The number of items in the achievements array */ - uint32_t num_achievements; - - /* An array of leaderboards for the game */ - rc_api_leaderboard_definition_t* leaderboards; - /* The number of items in the leaderboards array */ - uint32_t num_leaderboards; - - /* Common server-provided response information */ - rc_api_response_t response; -} -rc_api_fetch_game_data_response_t; - -RC_EXPORT int RC_CCONV rc_api_init_fetch_game_data_request(rc_api_request_t* request, const rc_api_fetch_game_data_request_t* api_params); -RC_EXPORT int RC_CCONV rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* response, const char* server_response); -RC_EXPORT int RC_CCONV rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response); -RC_EXPORT void RC_CCONV rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t* response); - -/* --- Ping --- */ - -/** - * API parameters for a ping request. - */ -typedef struct rc_api_ping_request_t { - /* The username of the player */ - const char* username; - /* The API token from the login request */ - const char* api_token; - /* The unique identifier of the game */ - uint32_t game_id; - /* (optional) The current rich presence evaluation for the user */ - const char* rich_presence; - /* (recommended) The hash associated to the game being played */ - const char* game_hash; - /* Non-zero if hardcore is currently enabled (ignored if game_hash is null) */ - uint32_t hardcore; -} -rc_api_ping_request_t; - -/** - * Response data for a ping request. - */ -typedef struct rc_api_ping_response_t { - /* Common server-provided response information */ - rc_api_response_t response; -} -rc_api_ping_response_t; - -RC_EXPORT int RC_CCONV rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_request_t* api_params); -RC_EXPORT int RC_CCONV rc_api_process_ping_response(rc_api_ping_response_t* response, const char* server_response); -RC_EXPORT int RC_CCONV rc_api_process_ping_server_response(rc_api_ping_response_t* response, const rc_api_server_response_t* server_response); -RC_EXPORT void RC_CCONV rc_api_destroy_ping_response(rc_api_ping_response_t* response); - -/* --- Award Achievement --- */ - -/** - * API parameters for an award achievement request. - */ -typedef struct rc_api_award_achievement_request_t { - /* The username of the player */ - const char* username; - /* The API token from the login request */ - const char* api_token; - /* The unique identifier of the achievement */ - uint32_t achievement_id; - /* Non-zero if the achievement was earned in hardcore */ - uint32_t hardcore; - /* The hash associated to the game being played */ - const char* game_hash; -} -rc_api_award_achievement_request_t; - -/** - * Response data for an award achievement request. - */ -typedef struct rc_api_award_achievement_response_t { - /* The unique identifier of the achievement that was awarded */ - uint32_t awarded_achievement_id; - /* The updated player score */ - uint32_t new_player_score; - /* The updated player softcore score */ - uint32_t new_player_score_softcore; - /* The number of achievements the user has not yet unlocked for this game - * (in hardcore/non-hardcore per hardcore flag in request) */ - uint32_t achievements_remaining; - - /* Common server-provided response information */ - rc_api_response_t response; -} -rc_api_award_achievement_response_t; - -RC_EXPORT int RC_CCONV rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_api_award_achievement_request_t* api_params); -RC_EXPORT int RC_CCONV rc_api_process_award_achievement_response(rc_api_award_achievement_response_t* response, const char* server_response); -RC_EXPORT int RC_CCONV rc_api_process_award_achievement_server_response(rc_api_award_achievement_response_t* response, const rc_api_server_response_t* server_response); -RC_EXPORT void RC_CCONV rc_api_destroy_award_achievement_response(rc_api_award_achievement_response_t* response); - -/* --- Submit Leaderboard Entry --- */ - -/** - * API parameters for a submit lboard entry request. - */ -typedef struct rc_api_submit_lboard_entry_request_t { - /* The username of the player */ - const char* username; - /* The API token from the login request */ - const char* api_token; - /* The unique identifier of the leaderboard */ - uint32_t leaderboard_id; - /* The value being submitted */ - int32_t score; - /* The hash associated to the game being played */ - const char* game_hash; -} -rc_api_submit_lboard_entry_request_t; - -/* A leaderboard entry */ -typedef struct rc_api_lboard_entry_t { - /* The user associated to the entry */ - const char* username; - /* The rank of the entry */ - uint32_t rank; - /* The value of the entry */ - int32_t score; -} -rc_api_lboard_entry_t; - -/** - * Response data for a submit lboard entry request. - */ -typedef struct rc_api_submit_lboard_entry_response_t { - /* The value that was submitted */ - int32_t submitted_score; - /* The player's best submitted value */ - int32_t best_score; - /* The player's new rank within the leaderboard */ - uint32_t new_rank; - /* The total number of entries in the leaderboard */ - uint32_t num_entries; - - /* An array of the top entries for the leaderboard */ - rc_api_lboard_entry_t* top_entries; - /* The number of items in the top_entries array */ - uint32_t num_top_entries; - - /* Common server-provided response information */ - rc_api_response_t response; -} -rc_api_submit_lboard_entry_response_t; - -RC_EXPORT int RC_CCONV rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_api_submit_lboard_entry_request_t* api_params); -RC_EXPORT int RC_CCONV rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response, const char* server_response); -RC_EXPORT int RC_CCONV rc_api_process_submit_lboard_entry_server_response(rc_api_submit_lboard_entry_response_t* response, const rc_api_server_response_t* server_response); -RC_EXPORT void RC_CCONV rc_api_destroy_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response); - -RC_END_C_DECLS - -#endif /* RC_API_RUNTIME_H */ +#ifndef RC_API_RUNTIME_H +#define RC_API_RUNTIME_H + +#include "rc_api_request.h" + +#include +#include + +RC_BEGIN_C_DECLS + +/* --- Fetch Image --- */ + +/** + * API parameters for a fetch image request. + * NOTE: fetch image server response is the raw image data. There is no rc_api_process_fetch_image_response function. + */ +typedef struct rc_api_fetch_image_request_t { + /* The name of the image to fetch */ + const char* image_name; + /* The type of image to fetch */ + uint32_t image_type; +} +rc_api_fetch_image_request_t; + +#define RC_IMAGE_TYPE_GAME 1 +#define RC_IMAGE_TYPE_ACHIEVEMENT 2 +#define RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED 3 +#define RC_IMAGE_TYPE_USER 4 + +RC_EXPORT int RC_CCONV rc_api_init_fetch_image_request(rc_api_request_t* request, const rc_api_fetch_image_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_fetch_image_request_hosted(rc_api_request_t* request, const rc_api_fetch_image_request_t* api_params, const rc_api_host_t* host); + +/* --- Resolve Hash --- */ + +/** + * API parameters for a resolve hash request. + */ +typedef struct rc_api_resolve_hash_request_t { + /* Unused - hash lookup does not require credentials */ + const char* username; + /* Unused - hash lookup does not require credentials */ + const char* api_token; + /* The generated hash of the game to be identified */ + const char* game_hash; +} +rc_api_resolve_hash_request_t; + +/** + * Response data for a resolve hash request. + */ +typedef struct rc_api_resolve_hash_response_t { + /* The unique identifier of the game, 0 if no match was found */ + uint32_t game_id; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_resolve_hash_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_resolve_hash_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_resolve_hash_request_hosted(rc_api_request_t* request, const rc_api_resolve_hash_request_t* api_params, const rc_api_host_t* host); +/* [deprecated] use rc_api_process_resolve_hash_server_response instead */ +RC_EXPORT int RC_CCONV rc_api_process_resolve_hash_response(rc_api_resolve_hash_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_resolve_hash_server_response(rc_api_resolve_hash_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_resolve_hash_response(rc_api_resolve_hash_response_t* response); + +/* --- Fetch Game Data --- */ + +/** + * API parameters for a fetch game data request. + */ +typedef struct rc_api_fetch_game_data_request_t { + /* The username of the player */ + const char* username; + /* The API token from the login request */ + const char* api_token; + /* The unique identifier of the game */ + uint32_t game_id; + /* The generated hash of the game to be identified (ignored if game_id is not 0) */ + const char* game_hash; +} +rc_api_fetch_game_data_request_t; + +/* A leaderboard definition */ +typedef struct rc_api_leaderboard_definition_t { + /* The unique identifier of the leaderboard */ + uint32_t id; + /* The format to pass to rc_format_value to format the leaderboard value */ + int format; + /* The title of the leaderboard */ + const char* title; + /* The description of the leaderboard */ + const char* description; + /* The definition of the leaderboard to be passed to rc_runtime_activate_lboard */ + const char* definition; + /* Non-zero if lower values are better for this leaderboard */ + uint8_t lower_is_better; + /* Non-zero if the leaderboard should not be displayed in a list of leaderboards */ + uint8_t hidden; +} +rc_api_leaderboard_definition_t; + +/* An achievement definition */ +typedef struct rc_api_achievement_definition_t { + /* The unique identifier of the achievement */ + uint32_t id; + /* The number of points the achievement is worth */ + uint32_t points; + /* The achievement category (core, unofficial) */ + uint32_t category; + /* The title of the achievement */ + const char* title; + /* The description of the achievement */ + const char* description; + /* The definition of the achievement to be passed to rc_runtime_activate_achievement */ + const char* definition; + /* The author of the achievment */ + const char* author; + /* The image name for the achievement badge */ + const char* badge_name; + /* When the achievement was first uploaded to the server */ + time_t created; + /* When the achievement was last modified on the server */ + time_t updated; + /* The achievement type (win/progression/missable) */ + uint32_t type; + /* The approximate rarity of the achievement (X% of users have earned the achievement) */ + float rarity; + /* The approximate rarity of the achievement in hardcore (X% of users have earned the achievement in hardcore) */ + float rarity_hardcore; + /* The URL for the achievement badge */ + const char* badge_url; + /* The URL for the locked achievement badge */ + const char* badge_locked_url; +} +rc_api_achievement_definition_t; + +#define RC_ACHIEVEMENT_CATEGORY_CORE 3 +#define RC_ACHIEVEMENT_CATEGORY_UNOFFICIAL 5 + +#define RC_ACHIEVEMENT_TYPE_STANDARD 0 +#define RC_ACHIEVEMENT_TYPE_MISSABLE 1 +#define RC_ACHIEVEMENT_TYPE_PROGRESSION 2 +#define RC_ACHIEVEMENT_TYPE_WIN 3 + +/** + * Response data for a fetch game data request. + */ +typedef struct rc_api_fetch_game_data_response_t { + /* The unique identifier of the game */ + uint32_t id; + /* The console associated to the game */ + uint32_t console_id; + /* The title of the game */ + const char* title; + /* The image name for the game badge */ + const char* image_name; + /* The URL for the game badge */ + const char* image_url; + /* The rich presence script for the game to be passed to rc_runtime_activate_richpresence */ + const char* rich_presence_script; + + /* An array of achievements for the game */ + rc_api_achievement_definition_t* achievements; + /* The number of items in the achievements array */ + uint32_t num_achievements; + + /* An array of leaderboards for the game */ + rc_api_leaderboard_definition_t* leaderboards; + /* The number of items in the leaderboards array */ + uint32_t num_leaderboards; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_fetch_game_data_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_fetch_game_data_request(rc_api_request_t* request, const rc_api_fetch_game_data_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_fetch_game_data_request_hosted(rc_api_request_t* request, const rc_api_fetch_game_data_request_t* api_params, const rc_api_host_t* host); +/* [deprecated] use rc_api_process_fetch_game_data_server_response instead */ +RC_EXPORT int RC_CCONV rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t* response); + +/* --- Fetch Game Sets --- */ + +/** + * API parameters for a fetch game data request. + */ +typedef struct rc_api_fetch_game_sets_request_t { + /* The username of the player */ + const char* username; + /* The API token from the login request */ + const char* api_token; + /* The unique identifier of the game */ + uint32_t game_id; + /* The generated hash of the game to be identified (ignored if game_id is not 0) */ + const char* game_hash; +} +rc_api_fetch_game_sets_request_t; + +#define RC_ACHIEVEMENT_SET_TYPE_CORE 0 +#define RC_ACHIEVEMENT_SET_TYPE_BONUS 1 +#define RC_ACHIEVEMENT_SET_TYPE_SPECIALTY 2 +#define RC_ACHIEVEMENT_SET_TYPE_EXCLUSIVE 3 + +/* A game subset definition */ +typedef struct rc_api_achievement_set_definition_t { + /* The unique identifier of the achievement set */ + uint32_t id; + /* The legacy game_id of the achievement set (used for editor API calls) */ + uint32_t game_id; + /* The title of the achievement set */ + const char* title; + /* The image name for the achievement set badge */ + const char* image_name; + /* The URL for the achievement set badge */ + const char* image_url; + + /* An array of achievements for the achievement set */ + rc_api_achievement_definition_t* achievements; + /* The number of items in the achievements array */ + uint32_t num_achievements; + + /* An array of leaderboards for the achievement set */ + rc_api_leaderboard_definition_t* leaderboards; + /* The number of items in the leaderboards array */ + uint32_t num_leaderboards; + + /* The type of the achievement set */ + uint8_t type; +} +rc_api_achievement_set_definition_t; + +/** + * Response data for a fetch game sets request. + */ +typedef struct rc_api_fetch_game_sets_response_t { + /* The unique identifier of the game */ + uint32_t id; + /* The console associated to the game */ + uint32_t console_id; + /* The title of the game */ + const char* title; + /* The image name for the game badge */ + const char* image_name; + /* The URL for the game badge */ + const char* image_url; + /* The rich presence script for the game to be passed to rc_runtime_activate_richpresence */ + const char* rich_presence_script; + /* The unique identifier of the game to use for session requests (startsession/ping/etc) */ + uint32_t session_game_id; + + /* An array of sets for the game */ + rc_api_achievement_set_definition_t* sets; + /* The number of items in the sets array */ + uint32_t num_sets; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_fetch_game_sets_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_fetch_game_sets_request(rc_api_request_t* request, const rc_api_fetch_game_sets_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_fetch_game_sets_request_hosted(rc_api_request_t* request, const rc_api_fetch_game_sets_request_t* api_params, const rc_api_host_t* host); +RC_EXPORT int RC_CCONV rc_api_process_fetch_game_sets_server_response(rc_api_fetch_game_sets_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_fetch_game_sets_response(rc_api_fetch_game_sets_response_t* response); + +/* --- Ping --- */ + +/** + * API parameters for a ping request. + */ +typedef struct rc_api_ping_request_t { + /* The username of the player */ + const char* username; + /* The API token from the login request */ + const char* api_token; + /* The unique identifier of the game */ + uint32_t game_id; + /* (optional) The current rich presence evaluation for the user */ + const char* rich_presence; + /* (recommended) The hash associated to the game being played */ + const char* game_hash; + /* Non-zero if hardcore is currently enabled (ignored if game_hash is null) */ + uint32_t hardcore; +} +rc_api_ping_request_t; + +/** + * Response data for a ping request. + */ +typedef struct rc_api_ping_response_t { + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_ping_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_ping_request_hosted(rc_api_request_t* request, const rc_api_ping_request_t* api_params, const rc_api_host_t* host); +/* [deprecated] use rc_api_process_ping_server_response instead */ +RC_EXPORT int RC_CCONV rc_api_process_ping_response(rc_api_ping_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_ping_server_response(rc_api_ping_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_ping_response(rc_api_ping_response_t* response); + +/* --- Award Achievement --- */ + +/** + * API parameters for an award achievement request. + */ +typedef struct rc_api_award_achievement_request_t { + /* The username of the player */ + const char* username; + /* The API token from the login request */ + const char* api_token; + /* The unique identifier of the achievement */ + uint32_t achievement_id; + /* Non-zero if the achievement was earned in hardcore */ + uint32_t hardcore; + /* The hash associated to the game being played */ + const char* game_hash; + /* The number of seconds since the achievement was unlocked */ + uint32_t seconds_since_unlock; +} +rc_api_award_achievement_request_t; + +/** + * Response data for an award achievement request. + */ +typedef struct rc_api_award_achievement_response_t { + /* The unique identifier of the achievement that was awarded */ + uint32_t awarded_achievement_id; + /* The updated player score */ + uint32_t new_player_score; + /* The updated player softcore score */ + uint32_t new_player_score_softcore; + /* The number of achievements the user has not yet unlocked for this game + * (in hardcore/non-hardcore per hardcore flag in request) */ + uint32_t achievements_remaining; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_award_achievement_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_api_award_achievement_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_award_achievement_request_hosted(rc_api_request_t* request, const rc_api_award_achievement_request_t* api_params, const rc_api_host_t* host); +/* [deprecated] use rc_api_process_award_achievement_server_response instead */ +RC_EXPORT int RC_CCONV rc_api_process_award_achievement_response(rc_api_award_achievement_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_award_achievement_server_response(rc_api_award_achievement_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_award_achievement_response(rc_api_award_achievement_response_t* response); + +/* --- Submit Leaderboard Entry --- */ + +/** + * API parameters for a submit lboard entry request. + */ +typedef struct rc_api_submit_lboard_entry_request_t { + /* The username of the player */ + const char* username; + /* The API token from the login request */ + const char* api_token; + /* The unique identifier of the leaderboard */ + uint32_t leaderboard_id; + /* The value being submitted */ + int32_t score; + /* The hash associated to the game being played */ + const char* game_hash; + /* The number of seconds since the leaderboard attempt was completed */ + uint32_t seconds_since_completion; +} +rc_api_submit_lboard_entry_request_t; + +/* A leaderboard entry */ +typedef struct rc_api_lboard_entry_t { + /* The user associated to the entry */ + const char* username; + /* The rank of the entry */ + uint32_t rank; + /* The value of the entry */ + int32_t score; +} +rc_api_lboard_entry_t; + +/** + * Response data for a submit lboard entry request. + */ +typedef struct rc_api_submit_lboard_entry_response_t { + /* The value that was submitted */ + int32_t submitted_score; + /* The player's best submitted value */ + int32_t best_score; + /* The player's new rank within the leaderboard */ + uint32_t new_rank; + /* The total number of entries in the leaderboard */ + uint32_t num_entries; + + /* An array of the top entries for the leaderboard */ + rc_api_lboard_entry_t* top_entries; + /* The number of items in the top_entries array */ + uint32_t num_top_entries; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_submit_lboard_entry_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_api_submit_lboard_entry_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_submit_lboard_entry_request_hosted(rc_api_request_t* request, const rc_api_submit_lboard_entry_request_t* api_params, const rc_api_host_t* host); +/* [deprecated] use rc_api_process_submit_lboard_entry_server_response instead */ +RC_EXPORT int RC_CCONV rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_submit_lboard_entry_server_response(rc_api_submit_lboard_entry_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response); + +RC_END_C_DECLS + +#endif /* RC_API_RUNTIME_H */ diff --git a/include/rcheevos/rc_api_user.h b/include/rcheevos/rc_api_user.h index ed8e34a6..c977da95 100644 --- a/include/rcheevos/rc_api_user.h +++ b/include/rcheevos/rc_api_user.h @@ -1,152 +1,262 @@ -#ifndef RC_API_USER_H -#define RC_API_USER_H - -#include "rc_api_request.h" - -#include -#include - -RC_BEGIN_C_DECLS - -/* --- Login --- */ - -/** - * API parameters for a login request. - * If both password and api_token are provided, api_token will be ignored. - */ -typedef struct rc_api_login_request_t { - /* The username of the player being logged in */ - const char* username; - /* The API token from a previous login */ - const char* api_token; - /* The player's password */ - const char* password; -} -rc_api_login_request_t; - -/** - * Response data for a login request. - */ -typedef struct rc_api_login_response_t { - /* The case-corrected username of the player */ - const char* username; - /* The API token to use for all future requests */ - const char* api_token; - /* The current score of the player */ - uint32_t score; - /* The current softcore score of the player */ - uint32_t score_softcore; - /* The number of unread messages waiting for the player on the web site */ - uint32_t num_unread_messages; - /* The preferred name to display for the player */ - const char* display_name; - - /* Common server-provided response information */ - rc_api_response_t response; -} -rc_api_login_response_t; - -RC_EXPORT int RC_CCONV rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params); -/* [deprecated] use rc_api_process_login_server_response instead */ -RC_EXPORT int RC_CCONV rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response); -RC_EXPORT int RC_CCONV rc_api_process_login_server_response(rc_api_login_response_t* response, const rc_api_server_response_t* server_response); -RC_EXPORT void RC_CCONV rc_api_destroy_login_response(rc_api_login_response_t* response); - -/* --- Start Session --- */ - -/** - * API parameters for a start session request. - */ -typedef struct rc_api_start_session_request_t { - /* The username of the player */ - const char* username; - /* The API token from the login request */ - const char* api_token; - /* The unique identifier of the game */ - uint32_t game_id; - /* (recommended) The hash associated to the game being played */ - const char* game_hash; - /* Non-zero if hardcore is currently enabled (ignored if game_hash is null) */ - uint32_t hardcore; -} -rc_api_start_session_request_t; - -/** - * Response data for an achievement unlock. - */ -typedef struct rc_api_unlock_entry_t { - /* The unique identifier of the unlocked achievement */ - uint32_t achievement_id; - /* When the achievement was unlocked */ - time_t when; -} -rc_api_unlock_entry_t; - -/** - * Response data for a start session request. - */ -typedef struct rc_api_start_session_response_t { - /* An array of hardcore user unlocks */ - rc_api_unlock_entry_t* hardcore_unlocks; - /* An array of user unlocks */ - rc_api_unlock_entry_t* unlocks; - - /* The number of items in the hardcore_unlocks array */ - uint32_t num_hardcore_unlocks; - /* The number of items in the unlocks array */ - uint32_t num_unlocks; - - /* The server timestamp when the response was generated */ - time_t server_now; - - /* Common server-provided response information */ - rc_api_response_t response; -} -rc_api_start_session_response_t; - -RC_EXPORT int RC_CCONV rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_start_session_request_t* api_params); -/* [deprecated] use rc_api_process_start_session_server_response instead */ -RC_EXPORT int RC_CCONV rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response); -RC_EXPORT int RC_CCONV rc_api_process_start_session_server_response(rc_api_start_session_response_t* response, const rc_api_server_response_t* server_response); -RC_EXPORT void RC_CCONV rc_api_destroy_start_session_response(rc_api_start_session_response_t* response); - -/* --- Fetch User Unlocks --- */ - -/** - * API parameters for a fetch user unlocks request. - */ -typedef struct rc_api_fetch_user_unlocks_request_t { - /* The username of the player */ - const char* username; - /* The API token from the login request */ - const char* api_token; - /* The unique identifier of the game */ - uint32_t game_id; - /* Non-zero to fetch hardcore unlocks, 0 to fetch non-hardcore unlocks */ - uint32_t hardcore; -} -rc_api_fetch_user_unlocks_request_t; - -/** - * Response data for a fetch user unlocks request. - */ -typedef struct rc_api_fetch_user_unlocks_response_t { - /* An array of achievement IDs previously unlocked by the user */ - uint32_t* achievement_ids; - /* The number of items in the achievement_ids array */ - uint32_t num_achievement_ids; - - /* Common server-provided response information */ - rc_api_response_t response; -} -rc_api_fetch_user_unlocks_response_t; - -RC_EXPORT int RC_CCONV rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params); -/* [deprecated] use rc_api_process_fetch_user_unlocks_server_response instead */ -RC_EXPORT int RC_CCONV rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response); -RC_EXPORT int RC_CCONV rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_response_t* response, const rc_api_server_response_t* server_response); -RC_EXPORT void RC_CCONV rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response); - -RC_END_C_DECLS - -#endif /* RC_API_H */ +#ifndef RC_API_USER_H +#define RC_API_USER_H + +#include "rc_api_request.h" + +#include +#include + +RC_BEGIN_C_DECLS + +/* --- Login --- */ + +/** + * API parameters for a login request. + * If both password and api_token are provided, api_token will be ignored. + */ +typedef struct rc_api_login_request_t { + /* The username of the player being logged in */ + const char* username; + /* The API token from a previous login */ + const char* api_token; + /* The player's password */ + const char* password; +} +rc_api_login_request_t; + +/** + * Response data for a login request. + */ +typedef struct rc_api_login_response_t { + /* The case-corrected username of the player */ + const char* username; + /* The API token to use for all future requests */ + const char* api_token; + /* The current score of the player */ + uint32_t score; + /* The current softcore score of the player */ + uint32_t score_softcore; + /* The number of unread messages waiting for the player on the web site */ + uint32_t num_unread_messages; + /* The preferred name to display for the player */ + const char* display_name; + /* A URL to the user's avatar image */ + const char* avatar_url; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_login_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_login_request_hosted(rc_api_request_t* request, const rc_api_login_request_t* api_params, const rc_api_host_t* host); +/* [deprecated] use rc_api_process_login_server_response instead */ +RC_EXPORT int RC_CCONV rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_login_server_response(rc_api_login_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_login_response(rc_api_login_response_t* response); + +/* --- Start Session --- */ + +/** + * API parameters for a start session request. + */ +typedef struct rc_api_start_session_request_t { + /* The username of the player */ + const char* username; + /* The API token from the login request */ + const char* api_token; + /* The unique identifier of the game */ + uint32_t game_id; + /* (recommended) The hash associated to the game being played */ + const char* game_hash; + /* Non-zero if hardcore is currently enabled (ignored if game_hash is null) */ + uint32_t hardcore; +} +rc_api_start_session_request_t; + +/** + * Response data for an achievement unlock. + */ +typedef struct rc_api_unlock_entry_t { + /* The unique identifier of the unlocked achievement */ + uint32_t achievement_id; + /* When the achievement was unlocked */ + time_t when; +} +rc_api_unlock_entry_t; + +/** + * Response data for a start session request. + */ +typedef struct rc_api_start_session_response_t { + /* An array of hardcore user unlocks */ + rc_api_unlock_entry_t* hardcore_unlocks; + /* An array of user unlocks */ + rc_api_unlock_entry_t* unlocks; + + /* The number of items in the hardcore_unlocks array */ + uint32_t num_hardcore_unlocks; + /* The number of items in the unlocks array */ + uint32_t num_unlocks; + + /* The server timestamp when the response was generated */ + time_t server_now; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_start_session_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_start_session_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_start_session_request_hosted(rc_api_request_t* request, const rc_api_start_session_request_t* api_params, const rc_api_host_t* host); +/* [deprecated] use rc_api_process_start_session_server_response instead */ +RC_EXPORT int RC_CCONV rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_start_session_server_response(rc_api_start_session_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_start_session_response(rc_api_start_session_response_t* response); + +/* --- Fetch User Unlocks --- */ + +/** + * API parameters for a fetch user unlocks request. + */ +typedef struct rc_api_fetch_user_unlocks_request_t { + /* The username of the player */ + const char* username; + /* The API token from the login request */ + const char* api_token; + /* The unique identifier of the game */ + uint32_t game_id; + /* Non-zero to fetch hardcore unlocks, 0 to fetch non-hardcore unlocks */ + uint32_t hardcore; +} +rc_api_fetch_user_unlocks_request_t; + +/** + * Response data for a fetch user unlocks request. + */ +typedef struct rc_api_fetch_user_unlocks_response_t { + /* An array of achievement IDs previously unlocked by the user */ + uint32_t* achievement_ids; + /* The number of items in the achievement_ids array */ + uint32_t num_achievement_ids; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_fetch_user_unlocks_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_fetch_user_unlocks_request_hosted(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params, const rc_api_host_t* host); +/* [deprecated] use rc_api_process_fetch_user_unlocks_server_response instead */ +RC_EXPORT int RC_CCONV rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response); +RC_EXPORT int RC_CCONV rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response); + +/* --- Fetch Followed Users --- */ + +/** + * API parameters for a fetch followed users request. + */ +typedef struct rc_api_fetch_followed_users_request_t { + /* The username of the player */ + const char* username; + /* The API token from the login request */ + const char* api_token; +} +rc_api_fetch_followed_users_request_t; + +typedef struct rc_api_followed_user_activity_t { + /* The record associated to the activity */ + const char* context; + /* The image of the record associated to the activity */ + const char* context_image_url; + /* The description of the activity */ + const char* description; + /* The time of the activity */ + time_t when; + /* The unique identifier of the record associated to the activity */ + uint32_t context_id; +} +rc_api_followed_user_activity_t; + +/** + * Response data for a followed user. + */ +typedef struct rc_api_followed_user_t { + /* The preferred name to display for the player */ + const char* display_name; + /* A URL to the user's avatar image */ + const char* avatar_url; + /* The player's last registered activity */ + rc_api_followed_user_activity_t recent_activity; + /* The current score of the player */ + uint32_t score; +} +rc_api_followed_user_t; + +/** + * Response data for a fetch followed users request. + */ +typedef struct rc_api_fetch_followed_users_response_t { + /* An array of followed user information */ + rc_api_followed_user_t* users; + /* The number of items in the users array */ + uint32_t num_users; + + /* Common server-provided response information */ + rc_api_response_t response; +} +rc_api_fetch_followed_users_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_fetch_followed_users_request(rc_api_request_t* request, const rc_api_fetch_followed_users_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_fetch_followed_users_request_hosted(rc_api_request_t* request, const rc_api_fetch_followed_users_request_t* api_params, const rc_api_host_t* host); +RC_EXPORT int RC_CCONV rc_api_process_fetch_followed_users_server_response(rc_api_fetch_followed_users_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_fetch_followed_users_response(rc_api_fetch_followed_users_response_t* response); + +/* --- Fetch All Progress --- */ + +/** + * API parameters for a fetch all user progress request. + */ +typedef struct rc_api_fetch_all_user_progress_request_t { + /* The username of the player */ + const char* username; + /* The API token from the login request */ + const char* api_token; + /* The unique identifier of the console to query */ + uint32_t console_id; +} rc_api_fetch_all_user_progress_request_t; + +/* An all-user-progress entry */ +typedef struct rc_api_all_user_progress_entry_t { + /* The unique identifier of the game */ + uint32_t game_id; + /* The total number of achievements for this game */ + uint32_t num_achievements; + /* The total number of unlocked achievements for this game in softcore mode */ + uint32_t num_unlocked_achievements; + /* The total number of unlocked achievements for this game in hardcore mode */ + uint32_t num_unlocked_achievements_hardcore; +} rc_api_all_user_progress_entry_t; + +/** + * Response data for a fetch all user progress request. + */ +typedef struct rc_api_fetch_all_user_progress_response_t { + /* An array of entries, one per game */ + rc_api_all_user_progress_entry_t* entries; + /* The number of items in the entries array */ + uint32_t num_entries; + + /* Common server-provided response information */ + rc_api_response_t response; +} rc_api_fetch_all_user_progress_response_t; + +RC_EXPORT int RC_CCONV rc_api_init_fetch_all_user_progress_request(rc_api_request_t* request, const rc_api_fetch_all_user_progress_request_t* api_params); +RC_EXPORT int RC_CCONV rc_api_init_fetch_all_user_progress_request_hosted(rc_api_request_t* request, const rc_api_fetch_all_user_progress_request_t* api_params, const rc_api_host_t* host); +RC_EXPORT int RC_CCONV rc_api_process_fetch_all_user_progress_server_response(rc_api_fetch_all_user_progress_response_t* response, const rc_api_server_response_t* server_response); +RC_EXPORT void RC_CCONV rc_api_destroy_fetch_all_user_progress_response(rc_api_fetch_all_user_progress_response_t* response); + +RC_END_C_DECLS + +#endif /* RC_API_H */ diff --git a/include/rcheevos/rc_client.h b/include/rcheevos/rc_client.h index 84ac0897..de69cace 100644 --- a/include/rcheevos/rc_client.h +++ b/include/rcheevos/rc_client.h @@ -1,715 +1,872 @@ -#ifndef RC_CLIENT_H -#define RC_CLIENT_H - -#include "rc_api_request.h" -#include "rc_error.h" - -#include -#include -#include - -RC_BEGIN_C_DECLS - -/* implementation abstracted in rc_client_internal.h */ -typedef struct rc_client_t rc_client_t; -typedef struct rc_client_async_handle_t rc_client_async_handle_t; - -/*****************************************************************************\ -| Callbacks | -\*****************************************************************************/ - -/** - * Callback used to read num_bytes bytes from memory starting at address into buffer. - * Returns the number of bytes read. A return value of 0 indicates the address was invalid. - */ -typedef uint32_t (RC_CCONV *rc_client_read_memory_func_t)(uint32_t address, uint8_t* buffer, uint32_t num_bytes, rc_client_t* client); - -/** - * Internal method passed to rc_client_server_call_t to process the server response. - */ -typedef void (RC_CCONV *rc_client_server_callback_t)(const rc_api_server_response_t* server_response, void* callback_data); - -/** - * Callback used to issue a request to the server. - */ -typedef void (RC_CCONV *rc_client_server_call_t)(const rc_api_request_t* request, rc_client_server_callback_t callback, void* callback_data, rc_client_t* client); - -/** - * Generic callback for asynchronous eventing. - */ -typedef void (RC_CCONV *rc_client_callback_t)(int result, const char* error_message, rc_client_t* client, void* userdata); - -/** - * Callback for logging or displaying a message. - */ -typedef void (RC_CCONV *rc_client_message_callback_t)(const char* message, const rc_client_t* client); - -/*****************************************************************************\ -| Runtime | -\*****************************************************************************/ - -/** - * Creates a new rc_client_t object. - */ -RC_EXPORT rc_client_t* RC_CCONV rc_client_create(rc_client_read_memory_func_t read_memory_function, rc_client_server_call_t server_call_function); - -/** - * Releases resources associated to a rc_client_t object. - * Pointer will no longer be valid after making this call. - */ -RC_EXPORT void RC_CCONV rc_client_destroy(rc_client_t* client); - -/** - * Sets whether hardcore is enabled (on by default). - * Can be called with a game loaded. - * Enabling hardcore with a game loaded will raise an RC_CLIENT_EVENT_RESET - * event. Processing will be disabled until rc_client_reset is called. - */ -RC_EXPORT void RC_CCONV rc_client_set_hardcore_enabled(rc_client_t* client, int enabled); - -/** - * Gets whether hardcore is enabled (on by default). - */ -RC_EXPORT int RC_CCONV rc_client_get_hardcore_enabled(const rc_client_t* client); - -/** - * Sets whether encore mode is enabled (off by default). - * Evaluated when loading a game. Has no effect while a game is loaded. - */ -RC_EXPORT void RC_CCONV rc_client_set_encore_mode_enabled(rc_client_t* client, int enabled); - -/** - * Gets whether encore mode is enabled (off by default). - */ -RC_EXPORT int RC_CCONV rc_client_get_encore_mode_enabled(const rc_client_t* client); - -/** - * Sets whether unofficial achievements should be loaded. - * Evaluated when loading a game. Has no effect while a game is loaded. - */ -RC_EXPORT void RC_CCONV rc_client_set_unofficial_enabled(rc_client_t* client, int enabled); - -/** - * Gets whether unofficial achievements should be loaded. - */ -RC_EXPORT int RC_CCONV rc_client_get_unofficial_enabled(const rc_client_t* client); - -/** - * Sets whether spectator mode is enabled (off by default). - * If enabled, events for achievement unlocks and leaderboard submissions will be - * raised, but server calls to actually perform the unlock/submit will not occur. - * Can be modified while a game is loaded. Evaluated at unlock/submit time. - * Cannot be modified if disabled before a game is loaded. - */ -RC_EXPORT void RC_CCONV rc_client_set_spectator_mode_enabled(rc_client_t* client, int enabled); - -/** - * Gets whether spectator mode is enabled (off by default). - */ -RC_EXPORT int RC_CCONV rc_client_get_spectator_mode_enabled(const rc_client_t* client); - -/** - * Attaches client-specific data to the runtime. - */ -RC_EXPORT void RC_CCONV rc_client_set_userdata(rc_client_t* client, void* userdata); - -/** - * Gets the client-specific data attached to the runtime. - */ -RC_EXPORT void* RC_CCONV rc_client_get_userdata(const rc_client_t* client); - -/** - * Sets the name of the server to use. - */ -RC_EXPORT void RC_CCONV rc_client_set_host(const rc_client_t* client, const char* hostname); - -typedef uint64_t rc_clock_t; -typedef rc_clock_t (RC_CCONV *rc_get_time_millisecs_func_t)(const rc_client_t* client); - -/** - * Specifies a function that returns a value that increases once per millisecond. - */ -RC_EXPORT void RC_CCONV rc_client_set_get_time_millisecs_function(rc_client_t* client, rc_get_time_millisecs_func_t handler); - -/** - * Marks an async process as aborted. The associated callback will not be called. - */ -RC_EXPORT void RC_CCONV rc_client_abort_async(rc_client_t* client, rc_client_async_handle_t* async_handle); - -/** - * Gets a clause that can be added to the User-Agent to identify the version of rcheevos being used. - */ -RC_EXPORT size_t RC_CCONV rc_client_get_user_agent_clause(rc_client_t* client, char buffer[], size_t buffer_size); - -/*****************************************************************************\ -| Logging | -\*****************************************************************************/ - -/** - * Sets the logging level and provides a callback to be called to do the logging. - */ -RC_EXPORT void RC_CCONV rc_client_enable_logging(rc_client_t* client, int level, rc_client_message_callback_t callback); -enum { - RC_CLIENT_LOG_LEVEL_NONE = 0, - RC_CLIENT_LOG_LEVEL_ERROR = 1, - RC_CLIENT_LOG_LEVEL_WARN = 2, - RC_CLIENT_LOG_LEVEL_INFO = 3, - RC_CLIENT_LOG_LEVEL_VERBOSE = 4, - NUM_RC_CLIENT_LOG_LEVELS = 5 -}; - -/*****************************************************************************\ -| User | -\*****************************************************************************/ - -/** - * Attempt to login a user. - */ -RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_login_with_password(rc_client_t* client, - const char* username, const char* password, - rc_client_callback_t callback, void* callback_userdata); - -/** - * Attempt to login a user. - */ -RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_login_with_token(rc_client_t* client, - const char* username, const char* token, - rc_client_callback_t callback, void* callback_userdata); - -/** - * Logout the user. - */ -RC_EXPORT void RC_CCONV rc_client_logout(rc_client_t* client); - -typedef struct rc_client_user_t { - const char* display_name; - const char* username; - const char* token; - uint32_t score; - uint32_t score_softcore; - uint32_t num_unread_messages; -} rc_client_user_t; - -/** - * Gets information about the logged in user. Will return NULL if the user is not logged in. - */ -RC_EXPORT const rc_client_user_t* RC_CCONV rc_client_get_user_info(const rc_client_t* client); - -/** - * Gets the URL for the user's profile picture. - * Returns RC_OK on success. - */ -RC_EXPORT int RC_CCONV rc_client_user_get_image_url(const rc_client_user_t* user, char buffer[], size_t buffer_size); - -typedef struct rc_client_user_game_summary_t { - uint32_t num_core_achievements; - uint32_t num_unofficial_achievements; - uint32_t num_unlocked_achievements; - uint32_t num_unsupported_achievements; - - uint32_t points_core; - uint32_t points_unlocked; -} rc_client_user_game_summary_t; - -/** - * Gets a breakdown of the number of achievements in the game, and how many the user has unlocked. - * Used for the "You have unlocked X of Y achievements" message shown when the game starts. - */ -RC_EXPORT void RC_CCONV rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_game_summary_t* summary); - -/*****************************************************************************\ -| Game | -\*****************************************************************************/ - -#ifdef RC_CLIENT_SUPPORTS_HASH -/** - * Start loading an unidentified game. - */ -RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_identify_and_load_game(rc_client_t* client, - uint32_t console_id, const char* file_path, - const uint8_t* data, size_t data_size, - rc_client_callback_t callback, void* callback_userdata); -#endif - -/** - * Start loading a game. - */ -RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_load_game(rc_client_t* client, const char* hash, - rc_client_callback_t callback, void* callback_userdata); - -/** - * Gets the current progress of the asynchronous load game process. - */ -RC_EXPORT int RC_CCONV rc_client_get_load_game_state(const rc_client_t* client); -enum { - RC_CLIENT_LOAD_GAME_STATE_NONE, - RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME, - RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN, - RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA, - RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION, - RC_CLIENT_LOAD_GAME_STATE_DONE, - RC_CLIENT_LOAD_GAME_STATE_ABORTED -}; - -/** - * Unloads the current game. - */ -RC_EXPORT void RC_CCONV rc_client_unload_game(rc_client_t* client); - -typedef struct rc_client_game_t { - uint32_t id; - uint32_t console_id; - const char* title; - const char* hash; - const char* badge_name; -} rc_client_game_t; - -/** - * Get information about the current game. Returns NULL if no game is loaded. - */ -RC_EXPORT const rc_client_game_t* RC_CCONV rc_client_get_game_info(const rc_client_t* client); - -/** - * Gets the URL for the game image. - * Returns RC_OK on success. - */ -RC_EXPORT int RC_CCONV rc_client_game_get_image_url(const rc_client_game_t* game, char buffer[], size_t buffer_size); - -#ifdef RC_CLIENT_SUPPORTS_HASH -/** - * Changes the active disc in a multi-disc game. - */ -RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_change_media(rc_client_t* client, const char* file_path, - const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata); -#endif - -/** - * Changes the active disc in a multi-disc game. - */ -RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_change_media_from_hash(rc_client_t* client, const char* hash, - rc_client_callback_t callback, void* callback_userdata); - -/*****************************************************************************\ -| Subsets | -\*****************************************************************************/ - -typedef struct rc_client_subset_t { - uint32_t id; - const char* title; - char badge_name[16]; - - uint32_t num_achievements; - uint32_t num_leaderboards; -} rc_client_subset_t; - -RC_EXPORT const rc_client_subset_t* RC_CCONV rc_client_get_subset_info(rc_client_t* client, uint32_t subset_id); - -/*****************************************************************************\ -| Achievements | -\*****************************************************************************/ - -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 */ - NUM_RC_CLIENT_ACHIEVEMENT_STATES = 4 -}; - -enum { - RC_CLIENT_ACHIEVEMENT_CATEGORY_NONE = 0, - RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE = (1 << 0), - RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL = (1 << 1), - RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL = RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE | RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL -}; - -enum { - RC_CLIENT_ACHIEVEMENT_TYPE_STANDARD = 0, - RC_CLIENT_ACHIEVEMENT_TYPE_MISSABLE = 1, - RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION = 2, - RC_CLIENT_ACHIEVEMENT_TYPE_WIN = 3 -}; - -enum { - RC_CLIENT_ACHIEVEMENT_BUCKET_UNKNOWN = 0, - RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED = 1, - RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED = 2, - RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED = 3, - 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_UNSYNCED = 8, - NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS = 9 -}; - -enum { - RC_CLIENT_ACHIEVEMENT_UNLOCKED_NONE = 0, - RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE = (1 << 0), - RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE = (1 << 1), - RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH = RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE | RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE -}; - -typedef struct rc_client_achievement_t { - const char* title; - const char* description; - char badge_name[8]; - char measured_progress[24]; - float measured_percent; - uint32_t id; - uint32_t points; - time_t unlock_time; - uint8_t state; - uint8_t category; - uint8_t bucket; - uint8_t unlocked; - /* minimum version: 11.1 */ - float rarity; - float rarity_hardcore; - uint8_t type; -} rc_client_achievement_t; - -/** - * Get information about an achievement. Returns NULL if not found. - */ -RC_EXPORT const rc_client_achievement_t* RC_CCONV rc_client_get_achievement_info(rc_client_t* client, uint32_t id); - -/** - * Gets the URL for the achievement image. - * Returns RC_OK on success. - */ -RC_EXPORT int RC_CCONV rc_client_achievement_get_image_url(const rc_client_achievement_t* achievement, int state, char buffer[], size_t buffer_size); - -typedef struct rc_client_achievement_bucket_t { - rc_client_achievement_t** achievements; - uint32_t num_achievements; - - const char* label; - uint32_t subset_id; - uint8_t bucket_type; -} rc_client_achievement_bucket_t; - -typedef struct rc_client_achievement_list_t { - rc_client_achievement_bucket_t* buckets; - uint32_t num_buckets; -} rc_client_achievement_list_t; - -enum { - RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE = 0, - RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS = 1 -}; - -/** - * Creates a list of achievements matching the specified category and grouping. - * Returns an allocated list that must be free'd by calling rc_client_destroy_achievement_list. - */ -RC_EXPORT rc_client_achievement_list_t* RC_CCONV rc_client_create_achievement_list(rc_client_t* client, int category, int grouping); - -/** - * Destroys a list allocated by rc_client_get_achievement_list. - */ -RC_EXPORT void RC_CCONV 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(). - */ -RC_EXPORT int RC_CCONV rc_client_has_achievements(rc_client_t* client); - -/*****************************************************************************\ -| Leaderboards | -\*****************************************************************************/ - -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, - 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; - -/** - * Get information about a leaderboard. Returns NULL if not found. - */ -RC_EXPORT const rc_client_leaderboard_t* RC_CCONV rc_client_get_leaderboard_info(const rc_client_t* client, uint32_t id); - -typedef struct rc_client_leaderboard_tracker_t { - char display[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE]; - uint32_t id; -} rc_client_leaderboard_tracker_t; - -typedef struct rc_client_leaderboard_bucket_t { - rc_client_leaderboard_t** leaderboards; - uint32_t num_leaderboards; - - const char* label; - uint32_t subset_id; - uint8_t bucket_type; -} rc_client_leaderboard_bucket_t; - -typedef struct rc_client_leaderboard_list_t { - rc_client_leaderboard_bucket_t* buckets; - uint32_t num_buckets; -} rc_client_leaderboard_list_t; - -enum { - RC_CLIENT_LEADERBOARD_BUCKET_UNKNOWN = 0, - RC_CLIENT_LEADERBOARD_BUCKET_INACTIVE = 1, - RC_CLIENT_LEADERBOARD_BUCKET_ACTIVE = 2, - RC_CLIENT_LEADERBOARD_BUCKET_UNSUPPORTED = 3, - RC_CLIENT_LEADERBOARD_BUCKET_ALL = 4, - NUM_RC_CLIENT_LEADERBOARD_BUCKETS = 5 -}; - -enum { - RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE = 0, - RC_CLIENT_LEADERBOARD_LIST_GROUPING_TRACKING = 1 -}; - -/** - * Creates a list of leaderboards matching the specified grouping. - * Returns an allocated list that must be free'd by calling rc_client_destroy_leaderboard_list. - */ -RC_EXPORT rc_client_leaderboard_list_t* RC_CCONV rc_client_create_leaderboard_list(rc_client_t* client, int grouping); - -/** - * Destroys a list allocated by rc_client_get_leaderboard_list. - */ -RC_EXPORT void RC_CCONV rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list); - -/** - * Returns non-zero if the current game has any leaderboards. - */ -RC_EXPORT int RC_CCONV rc_client_has_leaderboards(rc_client_t* client); - -typedef struct rc_client_leaderboard_entry_t { - const char* user; - char display[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE]; - time_t submitted; - uint32_t rank; - uint32_t index; -} rc_client_leaderboard_entry_t; - -typedef struct rc_client_leaderboard_entry_list_t { - rc_client_leaderboard_entry_t* entries; - uint32_t num_entries; - int32_t user_index; -} rc_client_leaderboard_entry_list_t; - -typedef void (RC_CCONV *rc_client_fetch_leaderboard_entries_callback_t)(int result, const char* error_message, - rc_client_leaderboard_entry_list_t* list, rc_client_t* client, void* callback_userdata); - -/** - * Fetches a list of leaderboard entries from the server. - * Callback receives an allocated list that must be free'd by calling rc_client_destroy_leaderboard_entry_list. - */ -RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_leaderboard_entries(rc_client_t* client, uint32_t leaderboard_id, - uint32_t first_entry, uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata); - -/** - * Fetches a list of leaderboard entries from the server containing the logged-in user. - * Callback receives an allocated list that must be free'd by calling rc_client_destroy_leaderboard_entry_list. - */ -RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_leaderboard_entries_around_user(rc_client_t* client, uint32_t leaderboard_id, - uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata); - -/** - * Gets the URL for the profile picture of the user associated to a leaderboard entry. - * Returns RC_OK on success. - */ -RC_EXPORT int RC_CCONV rc_client_leaderboard_entry_get_user_image_url(const rc_client_leaderboard_entry_t* entry, char buffer[], size_t buffer_size); - -/** - * Destroys a list allocated by rc_client_begin_fetch_leaderboard_entries or rc_client_begin_fetch_leaderboard_entries_around_user. - */ -RC_EXPORT void RC_CCONV 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 */ - uint32_t 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 */ - uint32_t new_rank; - /* The total number of entries in the leaderboard */ - uint32_t 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 */ - uint32_t num_top_entries; -} rc_client_leaderboard_scoreboard_t; - -/*****************************************************************************\ -| Rich Presence | -\*****************************************************************************/ - -/** - * Returns non-zero if the current game supports rich presence. - */ -RC_EXPORT int RC_CCONV rc_client_has_rich_presence(rc_client_t* client); - -/** - * Gets the current rich presence message. - * Returns the number of characters written to buffer. - */ -RC_EXPORT size_t RC_CCONV rc_client_get_rich_presence_message(rc_client_t* client, char buffer[], size_t buffer_size); - -/*****************************************************************************\ -| Processing | -\*****************************************************************************/ - -enum { - RC_CLIENT_EVENT_TYPE_NONE = 0, - RC_CLIENT_EVENT_ACHIEVEMENT_TRIGGERED = 1, /* [achievement] was earned by the player */ - RC_CLIENT_EVENT_LEADERBOARD_STARTED = 2, /* [leaderboard] attempt has started */ - RC_CLIENT_EVENT_LEADERBOARD_FAILED = 3, /* [leaderboard] attempt failed */ - RC_CLIENT_EVENT_LEADERBOARD_SUBMITTED = 4, /* [leaderboard] attempt submitted */ - RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW = 5, /* [achievement] challenge indicator should be shown */ - RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE = 6, /* [achievement] challenge indicator should be hidden */ - RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_SHOW = 7, /* progress indicator should be shown for [achievement] */ - RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_HIDE = 8, /* progress indicator should be hidden */ - RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_UPDATE = 9, /* progress indicator should be updated to reflect new badge/progress for [achievement] */ - 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_LEADERBOARD_SCOREBOARD = 13, /* [leaderboard_scoreboard] possibly-new ranking received for [leaderboard] */ - 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 { - const char* error_message; - const char* api; - int result; - uint32_t related_id; -} rc_client_server_error_t; - -typedef struct rc_client_event_t { - uint32_t type; - - 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; - -/** - * Callback used to notify the client when certain events occur. - */ -typedef void (RC_CCONV *rc_client_event_handler_t)(const rc_client_event_t* event, rc_client_t* client); - -/** - * Provides a callback for event handling. - */ -RC_EXPORT void RC_CCONV rc_client_set_event_handler(rc_client_t* client, rc_client_event_handler_t handler); - -/** - * Provides a callback for reading memory. - */ -RC_EXPORT void RC_CCONV rc_client_set_read_memory_function(rc_client_t* client, rc_client_read_memory_func_t handler); - -/** - * Determines if there are any active achievements/leaderboards/rich presence that need processing. - */ -RC_EXPORT int RC_CCONV rc_client_is_processing_required(rc_client_t* client); - -/** - * Processes achievements for the current frame. - */ -RC_EXPORT void RC_CCONV rc_client_do_frame(rc_client_t* client); - -/** - * Processes the periodic queue. - * Called internally by rc_client_do_frame. - * Should be explicitly called if rc_client_do_frame is not being called because emulation is paused. - */ -RC_EXPORT void RC_CCONV rc_client_idle(rc_client_t* client); - -/** - * Determines if a sufficient amount of frames have been processed since the last call to rc_client_can_pause. - * Should not be called unless the client is trying to pause. - * If false is returned, and frames_remaining is not NULL, frames_remaining will be set to the number of frames - * still required before pause is allowed, which can be converted to a time in seconds for displaying to the user. - */ -RC_EXPORT int RC_CCONV rc_client_can_pause(rc_client_t* client, uint32_t* frames_remaining); - -/** - * Informs the runtime that the emulator has been reset. Will reset all achievements and leaderboards - * to their initial state (includes hiding indicators/trackers). - */ -RC_EXPORT void RC_CCONV rc_client_reset(rc_client_t* client); - -/** - * Gets the number of bytes needed to serialized the runtime state. - */ -RC_EXPORT size_t RC_CCONV rc_client_progress_size(rc_client_t* client); - -/** - * Serializes the runtime state into a buffer. - * Returns RC_OK on success, or an error indicator. - * [deprecated] use rc_client_serialize_progress_sized instead - */ -RC_EXPORT int RC_CCONV rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer); - -/** - * Serializes the runtime state into a buffer. - * Returns RC_OK on success, or an error indicator. - */ -RC_EXPORT int RC_CCONV rc_client_serialize_progress_sized(rc_client_t* client, uint8_t* buffer, size_t buffer_size); - -/** - * Deserializes the runtime state from a buffer. - * Returns RC_OK on success, or an error indicator. - * [deprecated] use rc_client_deserialize_progress_sized instead - */ -RC_EXPORT int RC_CCONV rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialized); - -/** - * Serializes the runtime state into a buffer. - * Returns RC_OK on success, or an error indicator. - */ -RC_EXPORT int RC_CCONV rc_client_deserialize_progress_sized(rc_client_t* client, const uint8_t* serialized, size_t serialized_size); - -RC_END_C_DECLS - -#endif /* RC_RUNTIME_H */ +#ifndef RC_CLIENT_H +#define RC_CLIENT_H + +#include "rc_api_request.h" +#include "rc_error.h" + +#include +#include +#include + +RC_BEGIN_C_DECLS + +/* implementation abstracted in rc_client_internal.h */ +typedef struct rc_client_t rc_client_t; +typedef struct rc_client_async_handle_t rc_client_async_handle_t; + +/*****************************************************************************\ +| Callbacks | +\*****************************************************************************/ + +/** + * Callback used to read num_bytes bytes from memory starting at address into buffer. + * Returns the number of bytes read. A return value of 0 indicates the address was invalid. + */ +typedef uint32_t (RC_CCONV *rc_client_read_memory_func_t)(uint32_t address, uint8_t* buffer, uint32_t num_bytes, rc_client_t* client); + +/** + * Internal method passed to rc_client_server_call_t to process the server response. + */ +typedef void (RC_CCONV *rc_client_server_callback_t)(const rc_api_server_response_t* server_response, void* callback_data); + +/** + * Callback used to issue a request to the server. + */ +typedef void (RC_CCONV *rc_client_server_call_t)(const rc_api_request_t* request, rc_client_server_callback_t callback, void* callback_data, rc_client_t* client); + +/** + * Generic callback for asynchronous eventing. + */ +typedef void (RC_CCONV *rc_client_callback_t)(int result, const char* error_message, rc_client_t* client, void* userdata); + +/** + * Callback for logging or displaying a message. + */ +typedef void (RC_CCONV *rc_client_message_callback_t)(const char* message, const rc_client_t* client); + +/*****************************************************************************\ +| Runtime | +\*****************************************************************************/ + +/** + * Creates a new rc_client_t object. + */ +RC_EXPORT rc_client_t* RC_CCONV rc_client_create(rc_client_read_memory_func_t read_memory_function, rc_client_server_call_t server_call_function); + +/** + * Releases resources associated to a rc_client_t object. + * Pointer will no longer be valid after making this call. + */ +RC_EXPORT void RC_CCONV rc_client_destroy(rc_client_t* client); + +/** + * Sets whether hardcore is enabled (on by default). + * Can be called with a game loaded. + * Enabling hardcore with a game loaded will raise an RC_CLIENT_EVENT_RESET + * event. Processing will be disabled until rc_client_reset is called. + */ +RC_EXPORT void RC_CCONV rc_client_set_hardcore_enabled(rc_client_t* client, int enabled); + +/** + * Gets whether hardcore is enabled (on by default). + */ +RC_EXPORT int RC_CCONV rc_client_get_hardcore_enabled(const rc_client_t* client); + +/** + * Sets whether encore mode is enabled (off by default). + * Evaluated when loading a game. Has no effect while a game is loaded. + */ +RC_EXPORT void RC_CCONV rc_client_set_encore_mode_enabled(rc_client_t* client, int enabled); + +/** + * Gets whether encore mode is enabled (off by default). + */ +RC_EXPORT int RC_CCONV rc_client_get_encore_mode_enabled(const rc_client_t* client); + +/** + * Sets whether unofficial achievements should be loaded. + * Evaluated when loading a game. Has no effect while a game is loaded. + */ +RC_EXPORT void RC_CCONV rc_client_set_unofficial_enabled(rc_client_t* client, int enabled); + +/** + * Gets whether unofficial achievements should be loaded. + */ +RC_EXPORT int RC_CCONV rc_client_get_unofficial_enabled(const rc_client_t* client); + +/** + * Sets whether spectator mode is enabled (off by default). + * If enabled, events for achievement unlocks and leaderboard submissions will be + * raised, but server calls to actually perform the unlock/submit will not occur. + * Can be modified while a game is loaded. Evaluated at unlock/submit time. + * Cannot be modified if disabled before a game is loaded. + */ +RC_EXPORT void RC_CCONV rc_client_set_spectator_mode_enabled(rc_client_t* client, int enabled); + +/** + * Gets whether spectator mode is enabled (off by default). + */ +RC_EXPORT int RC_CCONV rc_client_get_spectator_mode_enabled(const rc_client_t* client); + +/** + * Attaches client-specific data to the runtime. + */ +RC_EXPORT void RC_CCONV rc_client_set_userdata(rc_client_t* client, void* userdata); + +/** + * Gets the client-specific data attached to the runtime. + */ +RC_EXPORT void* RC_CCONV rc_client_get_userdata(const rc_client_t* client); + +/** + * Sets the name of the server to use. + */ +RC_EXPORT void RC_CCONV rc_client_set_host(rc_client_t* client, const char* hostname); + +typedef uint64_t rc_clock_t; +typedef rc_clock_t (RC_CCONV *rc_get_time_millisecs_func_t)(const rc_client_t* client); + +/** + * Specifies a function that returns a value that increases once per millisecond. + */ +RC_EXPORT void RC_CCONV rc_client_set_get_time_millisecs_function(rc_client_t* client, rc_get_time_millisecs_func_t handler); + +/** + * Marks an async process as aborted. The associated callback will not be called. + */ +RC_EXPORT void RC_CCONV rc_client_abort_async(rc_client_t* client, rc_client_async_handle_t* async_handle); + +/** + * Gets a clause that can be added to the User-Agent to identify the version of rcheevos being used. + */ +RC_EXPORT size_t RC_CCONV rc_client_get_user_agent_clause(rc_client_t* client, char buffer[], size_t buffer_size); + +/*****************************************************************************\ +| Logging | +\*****************************************************************************/ + +/** + * Sets the logging level and provides a callback to be called to do the logging. + */ +RC_EXPORT void RC_CCONV rc_client_enable_logging(rc_client_t* client, int level, rc_client_message_callback_t callback); +enum { + RC_CLIENT_LOG_LEVEL_NONE = 0, + RC_CLIENT_LOG_LEVEL_ERROR = 1, + RC_CLIENT_LOG_LEVEL_WARN = 2, + RC_CLIENT_LOG_LEVEL_INFO = 3, + RC_CLIENT_LOG_LEVEL_VERBOSE = 4, + NUM_RC_CLIENT_LOG_LEVELS = 5 +}; + +/*****************************************************************************\ +| User | +\*****************************************************************************/ + +/** + * Attempt to login a user. + */ +RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_login_with_password(rc_client_t* client, + const char* username, const char* password, + rc_client_callback_t callback, void* callback_userdata); + +/** + * Attempt to login a user. + */ +RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_login_with_token(rc_client_t* client, + const char* username, const char* token, + rc_client_callback_t callback, void* callback_userdata); + +/** + * Logout the user. + */ +RC_EXPORT void RC_CCONV rc_client_logout(rc_client_t* client); + +typedef struct rc_client_user_t { + const char* display_name; + const char* username; + const char* token; + uint32_t score; + uint32_t score_softcore; + uint32_t num_unread_messages; + /* minimum version: 12.0 */ + const char* avatar_url; +} rc_client_user_t; + +/** + * Gets information about the logged in user. Will return NULL if the user is not logged in. + */ +RC_EXPORT const rc_client_user_t* RC_CCONV rc_client_get_user_info(const rc_client_t* client); + +/** + * Gets the URL for the user's profile picture. + * Returns RC_OK on success. + */ +RC_EXPORT int RC_CCONV rc_client_user_get_image_url(const rc_client_user_t* user, char buffer[], size_t buffer_size); + +typedef struct rc_client_user_game_summary_t { + uint32_t num_core_achievements; + uint32_t num_unofficial_achievements; + uint32_t num_unlocked_achievements; + uint32_t num_unsupported_achievements; + + uint32_t points_core; + uint32_t points_unlocked; + + /* minimum version: 12.1 */ + time_t beaten_time; + time_t completed_time; +} rc_client_user_game_summary_t; + +/** + * Gets a breakdown of the number of achievements in the game, and how many the user has unlocked. + * Used for the "You have unlocked X of Y achievements" message shown when the game starts. + */ +RC_EXPORT void RC_CCONV rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_game_summary_t* summary); + +typedef struct rc_client_all_user_progress_entry_t { + uint32_t game_id; + uint32_t num_achievements; + uint32_t num_unlocked_achievements; + uint32_t num_unlocked_achievements_hardcore; +} rc_client_all_user_progress_entry_t; + +typedef struct rc_client_all_user_progress_t { + rc_client_all_user_progress_entry_t* entries; + uint32_t num_entries; +} rc_client_all_user_progress_t; + +/** + * Callback that is fired when an all progress query completes. list may be null if the query failed. + */ +typedef void(RC_CCONV* rc_client_fetch_all_user_progress_callback_t)(int result, const char* error_message, + rc_client_all_user_progress_t* list, + rc_client_t* client, void* callback_userdata); + +/** + * Starts an asynchronous request for all progress for the given console. + * This query returns the total number of achievements for all games tracked by this console, as well as + * the user's achievement unlock count for both softcore and hardcore modes. + */ +RC_EXPORT rc_client_async_handle_t* RC_CCONV +rc_client_begin_fetch_all_user_progress(rc_client_t* client, uint32_t console_id, + rc_client_fetch_all_user_progress_callback_t callback, void* callback_userdata); + +/** + * Destroys a previously-allocated result from the rc_client_begin_fetch_all_progress_list() callback. + */ +RC_EXPORT void RC_CCONV rc_client_destroy_all_user_progress(rc_client_all_user_progress_t* list); + +/*****************************************************************************\ +| Game | +\*****************************************************************************/ + +#ifdef RC_CLIENT_SUPPORTS_HASH +/** + * Start loading an unidentified game. + */ +RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_identify_and_load_game(rc_client_t* client, + uint32_t console_id, const char* file_path, + const uint8_t* data, size_t data_size, + rc_client_callback_t callback, void* callback_userdata); + +struct rc_hash_callbacks; +/** + * Provide callback functions for interacting with the file system and processing disc-based files when generating hashes. + */ +RC_EXPORT void rc_client_set_hash_callbacks(rc_client_t* client, const struct rc_hash_callbacks* callbacks); +#endif + +/** + * Start loading a game. + */ +RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_load_game(rc_client_t* client, const char* hash, + rc_client_callback_t callback, void* callback_userdata); + +/** + * Gets the current progress of the asynchronous load game process. + */ +RC_EXPORT int RC_CCONV rc_client_get_load_game_state(const rc_client_t* client); +enum { + RC_CLIENT_LOAD_GAME_STATE_NONE, + RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN, + RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME, + RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA, /* [deprecated] - game data is now returned by identify call */ + RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION, + RC_CLIENT_LOAD_GAME_STATE_DONE, + RC_CLIENT_LOAD_GAME_STATE_ABORTED +}; + +/** + * Determines if a game was successfully identified and loaded. + */ +RC_EXPORT int RC_CCONV rc_client_is_game_loaded(const rc_client_t* client); + +/** + * Unloads the current game. + */ +RC_EXPORT void RC_CCONV rc_client_unload_game(rc_client_t* client); + +typedef struct rc_client_game_t { + uint32_t id; + uint32_t console_id; + const char* title; + const char* hash; + const char* badge_name; + /* minimum version: 12.0 */ + const char* badge_url; +} rc_client_game_t; + +/** + * Get information about the current game. Returns NULL if no game is loaded. + * NOTE: returns a dummy game record if an unidentified game is loaded. + */ +RC_EXPORT const rc_client_game_t* RC_CCONV rc_client_get_game_info(const rc_client_t* client); + +/** + * Gets the URL for the game image. + * Returns RC_OK on success. + */ +RC_EXPORT int RC_CCONV rc_client_game_get_image_url(const rc_client_game_t* game, char buffer[], size_t buffer_size); + +#ifdef RC_CLIENT_SUPPORTS_HASH +/** + * Changes the active disc in a multi-disc game. + */ +RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_identify_and_change_media(rc_client_t* client, const char* file_path, + const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata); +#endif + +/** + * Changes the active disc in a multi-disc game. + */ +RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_change_media(rc_client_t* client, const char* hash, + rc_client_callback_t callback, void* callback_userdata); +/* this function was renamed in rcheevos 12.0 */ +#define rc_client_begin_change_media_from_hash rc_client_begin_change_media + +/*****************************************************************************\ +| Subsets | +\*****************************************************************************/ + +typedef struct rc_client_subset_t { + uint32_t id; + const char* title; + char badge_name[16]; + + uint32_t num_achievements; + uint32_t num_leaderboards; + + /* minimum version: 12.0 */ + const char* badge_url; +} rc_client_subset_t; + +RC_EXPORT const rc_client_subset_t* RC_CCONV rc_client_get_subset_info(rc_client_t* client, uint32_t subset_id); + +RC_EXPORT void RC_CCONV rc_client_get_user_subset_summary(const rc_client_t* client, uint32_t subset_id, rc_client_user_game_summary_t* summary); + +typedef struct rc_client_subset_list_t { + const rc_client_subset_t** subsets; + uint32_t num_subsets; +} rc_client_subset_list_t; + +/** + * Creates a list of subsets for the currently loaded game. + * Returns an allocated list that must be free'd by calling rc_client_destroy_subset_list. + */ +RC_EXPORT rc_client_subset_list_t* RC_CCONV rc_client_create_subset_list(rc_client_t* client); + +/** + * Destroys a list allocated by rc_client_create_subset_list_list. + */ +RC_EXPORT void RC_CCONV rc_client_destroy_subset_list(rc_client_subset_list_t* list); + +/*****************************************************************************\ +| Fetch Game Hashes | +\*****************************************************************************/ + +typedef struct rc_client_hash_library_entry_t { + char hash[33]; + uint32_t game_id; +} rc_client_hash_library_entry_t; + +typedef struct rc_client_hash_library_t { + rc_client_hash_library_entry_t* entries; + uint32_t num_entries; +} rc_client_hash_library_t; + +/** + * Callback that is fired when a hash library request completes. list may be null if the query failed. + */ +typedef void(RC_CCONV* rc_client_fetch_hash_library_callback_t)(int result, const char* error_message, + rc_client_hash_library_t* list, rc_client_t* client, + void* callback_userdata); + +/** + * Starts an asynchronous request for all hashes for the given console. + * This request returns a mapping from hashes to the game's unique identifier. A single game may have multiple + * hashes in the case of multi-disc games, or variants that are still compatible with the same achievement set. + */ +RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_hash_library( + rc_client_t* client, uint32_t console_id, rc_client_fetch_hash_library_callback_t callback, void* callback_userdata); + +/** + * Destroys a previously-allocated result from the rc_client_destroy_hash_library() callback. + */ +RC_EXPORT void RC_CCONV rc_client_destroy_hash_library(rc_client_hash_library_t* list); + +/*****************************************************************************\ +| Fetch Game Titles | +\*****************************************************************************/ + +typedef struct rc_client_game_title_entry_t { + uint32_t game_id; + const char* title; + char badge_name[16]; + const char* badge_url; +} rc_client_game_title_entry_t; + +typedef struct rc_client_game_title_list_t { + rc_client_game_title_entry_t* entries; + uint32_t num_entries; +} rc_client_game_title_list_t; + +/** + * Callback that is fired when a game titles request completes. list may be null if the query failed. + */ +typedef void(RC_CCONV* rc_client_fetch_game_titles_callback_t)(int result, const char* error_message, + rc_client_game_title_list_t* list, rc_client_t* client, + void* callback_userdata); + +/** + * Starts an asynchronous request for titles and badge names for the specified games. + * The caller must provide an array of game IDs and the number of IDs in the array. + */ +RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_game_titles( + rc_client_t* client, const uint32_t* game_ids, uint32_t num_game_ids, + rc_client_fetch_game_titles_callback_t callback, void* callback_userdata); + +/** + * Destroys a previously-allocated result from the rc_client_begin_fetch_game_titles() callback. + */ +RC_EXPORT void RC_CCONV rc_client_destroy_game_title_list(rc_client_game_title_list_t* list); + +/*****************************************************************************\ +| Achievements | +\*****************************************************************************/ + +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 */ + NUM_RC_CLIENT_ACHIEVEMENT_STATES = 4 +}; + +enum { + RC_CLIENT_ACHIEVEMENT_CATEGORY_NONE = 0, + RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE = (1 << 0), + RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL = (1 << 1), + RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL = RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE | RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL +}; + +enum { + RC_CLIENT_ACHIEVEMENT_TYPE_STANDARD = 0, + RC_CLIENT_ACHIEVEMENT_TYPE_MISSABLE = 1, + RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION = 2, + RC_CLIENT_ACHIEVEMENT_TYPE_WIN = 3 +}; + +enum { + RC_CLIENT_ACHIEVEMENT_BUCKET_UNKNOWN = 0, + RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED = 1, + RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED = 2, + RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED = 3, + 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_UNSYNCED = 8, + NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS = 9 +}; + +enum { + RC_CLIENT_ACHIEVEMENT_UNLOCKED_NONE = 0, + RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE = (1 << 0), + RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE = (1 << 1), + RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH = RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE | RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE +}; + +typedef struct rc_client_achievement_t { + const char* title; + const char* description; + char badge_name[8]; + char measured_progress[24]; + float measured_percent; + uint32_t id; + uint32_t points; + time_t unlock_time; + uint8_t state; + uint8_t category; + uint8_t bucket; + uint8_t unlocked; + /* minimum version: 11.1 */ + float rarity; + float rarity_hardcore; + uint8_t type; + /* minimum version: 12.0 */ + const char* badge_url; + const char* badge_locked_url; +} rc_client_achievement_t; + +/** + * Get information about an achievement. Returns NULL if not found. + */ +RC_EXPORT const rc_client_achievement_t* RC_CCONV rc_client_get_achievement_info(rc_client_t* client, uint32_t id); + +/** + * Gets the URL for the achievement image. + * Returns RC_OK on success. + */ +RC_EXPORT int RC_CCONV rc_client_achievement_get_image_url(const rc_client_achievement_t* achievement, int state, char buffer[], size_t buffer_size); + +typedef struct rc_client_achievement_bucket_t { + const rc_client_achievement_t** achievements; + uint32_t num_achievements; + + const char* label; + uint32_t subset_id; + uint8_t bucket_type; +} rc_client_achievement_bucket_t; + +typedef struct rc_client_achievement_list_t { + const rc_client_achievement_bucket_t* buckets; + uint32_t num_buckets; +} rc_client_achievement_list_t; + +enum { + RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE = 0, + RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS = 1 +}; + +/** + * Creates a list of achievements matching the specified category and grouping. + * Returns an allocated list that must be free'd by calling rc_client_destroy_achievement_list. + */ +RC_EXPORT rc_client_achievement_list_t* RC_CCONV rc_client_create_achievement_list(rc_client_t* client, int category, int grouping); + +/** + * Destroys a list allocated by rc_client_create_achievement_list. + */ +RC_EXPORT void RC_CCONV 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(). + */ +RC_EXPORT int RC_CCONV rc_client_has_achievements(rc_client_t* client); + +/*****************************************************************************\ +| Leaderboards | +\*****************************************************************************/ + +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, + 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; + +/** + * Get information about a leaderboard. Returns NULL if not found. + */ +RC_EXPORT const rc_client_leaderboard_t* RC_CCONV rc_client_get_leaderboard_info(const rc_client_t* client, uint32_t id); + +typedef struct rc_client_leaderboard_tracker_t { + char display[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE]; + uint32_t id; +} rc_client_leaderboard_tracker_t; + +typedef struct rc_client_leaderboard_bucket_t { + const rc_client_leaderboard_t** leaderboards; + uint32_t num_leaderboards; + + const char* label; + uint32_t subset_id; + uint8_t bucket_type; +} rc_client_leaderboard_bucket_t; + +typedef struct rc_client_leaderboard_list_t { + const rc_client_leaderboard_bucket_t* buckets; + uint32_t num_buckets; +} rc_client_leaderboard_list_t; + +enum { + RC_CLIENT_LEADERBOARD_BUCKET_UNKNOWN = 0, + RC_CLIENT_LEADERBOARD_BUCKET_INACTIVE = 1, + RC_CLIENT_LEADERBOARD_BUCKET_ACTIVE = 2, + RC_CLIENT_LEADERBOARD_BUCKET_UNSUPPORTED = 3, + RC_CLIENT_LEADERBOARD_BUCKET_ALL = 4, + NUM_RC_CLIENT_LEADERBOARD_BUCKETS = 5 +}; + +enum { + RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE = 0, + RC_CLIENT_LEADERBOARD_LIST_GROUPING_TRACKING = 1 +}; + +/** + * Creates a list of leaderboards matching the specified grouping. + * Returns an allocated list that must be free'd by calling rc_client_destroy_leaderboard_list. + */ +RC_EXPORT rc_client_leaderboard_list_t* RC_CCONV rc_client_create_leaderboard_list(rc_client_t* client, int grouping); + +/** + * Destroys a list allocated by rc_client_create_leaderboard_list. + */ +RC_EXPORT void RC_CCONV rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list); + +/** + * Returns non-zero if the current game has any leaderboards. + */ +RC_EXPORT int RC_CCONV rc_client_has_leaderboards(rc_client_t* client); + +typedef struct rc_client_leaderboard_entry_t { + const char* user; + char display[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE]; + time_t submitted; + uint32_t rank; + uint32_t index; +} rc_client_leaderboard_entry_t; + +typedef struct rc_client_leaderboard_entry_list_t { + rc_client_leaderboard_entry_t* entries; + uint32_t num_entries; + uint32_t total_entries; + int32_t user_index; +} rc_client_leaderboard_entry_list_t; + +typedef void (RC_CCONV *rc_client_fetch_leaderboard_entries_callback_t)(int result, const char* error_message, + rc_client_leaderboard_entry_list_t* list, rc_client_t* client, void* callback_userdata); + +/** + * Fetches a list of leaderboard entries from the server. + * Callback receives an allocated list that must be free'd by calling rc_client_destroy_leaderboard_entry_list. + */ +RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_leaderboard_entries(rc_client_t* client, uint32_t leaderboard_id, + uint32_t first_entry, uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata); + +/** + * Fetches a list of leaderboard entries from the server containing the logged-in user. + * Callback receives an allocated list that must be free'd by calling rc_client_destroy_leaderboard_entry_list. + */ +RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_leaderboard_entries_around_user(rc_client_t* client, uint32_t leaderboard_id, + uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata); + +/** + * Gets the URL for the profile picture of the user associated to a leaderboard entry. + * Returns RC_OK on success. + */ +RC_EXPORT int RC_CCONV rc_client_leaderboard_entry_get_user_image_url(const rc_client_leaderboard_entry_t* entry, char buffer[], size_t buffer_size); + +/** + * Destroys a list allocated by rc_client_begin_fetch_leaderboard_entries or rc_client_begin_fetch_leaderboard_entries_around_user. + */ +RC_EXPORT void RC_CCONV 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 */ + uint32_t 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 */ + uint32_t new_rank; + /* The total number of entries in the leaderboard */ + uint32_t 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 */ + uint32_t num_top_entries; +} rc_client_leaderboard_scoreboard_t; + +/*****************************************************************************\ +| Rich Presence | +\*****************************************************************************/ + +/** + * Returns non-zero if the current game supports rich presence. + */ +RC_EXPORT int RC_CCONV rc_client_has_rich_presence(rc_client_t* client); + +/** + * Gets the current rich presence message. + * Returns the number of characters written to buffer. + */ +RC_EXPORT size_t RC_CCONV rc_client_get_rich_presence_message(rc_client_t* client, char buffer[], size_t buffer_size); + +/*****************************************************************************\ +| Processing | +\*****************************************************************************/ + +enum { + RC_CLIENT_EVENT_TYPE_NONE = 0, + RC_CLIENT_EVENT_ACHIEVEMENT_TRIGGERED = 1, /* [achievement] was earned by the player */ + RC_CLIENT_EVENT_LEADERBOARD_STARTED = 2, /* [leaderboard] attempt has started */ + RC_CLIENT_EVENT_LEADERBOARD_FAILED = 3, /* [leaderboard] attempt failed */ + RC_CLIENT_EVENT_LEADERBOARD_SUBMITTED = 4, /* [leaderboard] attempt submitted */ + RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW = 5, /* [achievement] challenge indicator should be shown */ + RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE = 6, /* [achievement] challenge indicator should be hidden */ + RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_SHOW = 7, /* progress indicator should be shown for [achievement] */ + RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_HIDE = 8, /* progress indicator should be hidden */ + RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_UPDATE = 9, /* progress indicator should be updated to reflect new badge/progress for [achievement] */ + 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_LEADERBOARD_SCOREBOARD = 13, /* [leaderboard_scoreboard] possibly-new ranking received for [leaderboard] */ + 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 */ + RC_CLIENT_EVENT_SUBSET_COMPLETED = 19 /* all achievements for the subset have been earned */ +}; + +typedef struct rc_client_server_error_t { + const char* error_message; + const char* api; + int result; + uint32_t related_id; +} rc_client_server_error_t; + +typedef struct rc_client_event_t { + uint32_t type; + + 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_subset_t* subset; + +} rc_client_event_t; + +/** + * Callback used to notify the client when certain events occur. + */ +typedef void (RC_CCONV *rc_client_event_handler_t)(const rc_client_event_t* event, rc_client_t* client); + +/** + * Provides a callback for event handling. + */ +RC_EXPORT void RC_CCONV rc_client_set_event_handler(rc_client_t* client, rc_client_event_handler_t handler); + +/** + * Provides a callback for reading memory. + */ +RC_EXPORT void RC_CCONV rc_client_set_read_memory_function(rc_client_t* client, rc_client_read_memory_func_t handler); + +/** + * Specifies whether rc_client is allowed to read memory outside of rc_client_do_frame/rc_client_idle. + */ +RC_EXPORT void RC_CCONV rc_client_set_allow_background_memory_reads(rc_client_t* client, int allowed); + +/** + * Determines if there are any active achievements/leaderboards/rich presence that need processing. + */ +RC_EXPORT int RC_CCONV rc_client_is_processing_required(rc_client_t* client); + +/** + * Processes achievements for the current frame. + */ +RC_EXPORT void RC_CCONV rc_client_do_frame(rc_client_t* client); + +/** + * Processes the periodic queue. + * Called internally by rc_client_do_frame. + * Should be explicitly called if rc_client_do_frame is not being called because emulation is paused. + */ +RC_EXPORT void RC_CCONV rc_client_idle(rc_client_t* client); + +/** + * Determines if a sufficient amount of frames have been processed since the last call to rc_client_can_pause. + * Should not be called unless the client is trying to pause. + * If false is returned, and frames_remaining is not NULL, frames_remaining will be set to the number of frames + * still required before pause is allowed, which can be converted to a time in seconds for displaying to the user. + */ +RC_EXPORT int RC_CCONV rc_client_can_pause(rc_client_t* client, uint32_t* frames_remaining); + +/** + * Informs the runtime that the emulator has been reset. Will reset all achievements and leaderboards + * to their initial state (includes hiding indicators/trackers). + */ +RC_EXPORT void RC_CCONV rc_client_reset(rc_client_t* client); + +/** + * Gets the number of bytes needed to serialized the runtime state. + */ +RC_EXPORT size_t RC_CCONV rc_client_progress_size(rc_client_t* client); + +/** + * Serializes the runtime state into a buffer. + * Returns RC_OK on success, or an error indicator. + * [deprecated] use rc_client_serialize_progress_sized instead + */ +RC_EXPORT int RC_CCONV rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer); + +/** + * Serializes the runtime state into a buffer. + * Returns RC_OK on success, or an error indicator. + */ +RC_EXPORT int RC_CCONV rc_client_serialize_progress_sized(rc_client_t* client, uint8_t* buffer, size_t buffer_size); + +/** + * Deserializes the runtime state from a buffer. + * Returns RC_OK on success, or an error indicator. + * [deprecated] use rc_client_deserialize_progress_sized instead + */ +RC_EXPORT int RC_CCONV rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialized); + +/** + * Serializes the runtime state into a buffer. + * Returns RC_OK on success, or an error indicator. + */ +RC_EXPORT int RC_CCONV rc_client_deserialize_progress_sized(rc_client_t* client, const uint8_t* serialized, size_t serialized_size); + +RC_END_C_DECLS + +#endif /* RC_RUNTIME_H */ diff --git a/include/rcheevos/rc_client_raintegration.h b/include/rcheevos/rc_client_raintegration.h index d73a3805..847c8ad2 100644 --- a/include/rcheevos/rc_client_raintegration.h +++ b/include/rcheevos/rc_client_raintegration.h @@ -1,89 +1,101 @@ -#ifndef RC_CLIENT_RAINTEGRATION_H -#define RC_CLIENT_RAINTEGRATION_H - -#ifndef _WIN32 - #undef RC_CLIENT_SUPPORTS_RAINTEGRATION /* Windows required for RAIntegration */ -#endif - -#include - -#include "rc_export.h" - -RC_BEGIN_C_DECLS - -typedef struct rc_client_t rc_client_t; /* forward reference; in rc_client.h */ - -/* types needed to implement raintegration */ - -typedef struct rc_client_raintegration_menu_item_t { - const char* label; - uint32_t id; - uint8_t checked; - uint8_t enabled; -} rc_client_raintegration_menu_item_t; - -typedef struct rc_client_raintegration_menu_t { - rc_client_raintegration_menu_item_t* items; - uint32_t num_items; -} rc_client_raintegration_menu_t; - -enum { - RC_CLIENT_RAINTEGRATION_EVENT_TYPE_NONE = 0, - RC_CLIENT_RAINTEGRATION_EVENT_MENUITEM_CHECKED_CHANGED = 1, /* [menu_item] checked changed */ - RC_CLIENT_RAINTEGRATION_EVENT_HARDCORE_CHANGED = 2, /* hardcore was enabled or disabled */ - RC_CLIENT_RAINTEGRATION_EVENT_PAUSE = 3 /* emulated system should be paused */ -}; - -typedef struct rc_client_raintegration_event_t { - uint32_t type; - - const rc_client_raintegration_menu_item_t* menu_item; -} rc_client_raintegration_event_t; - -typedef void (RC_CCONV *rc_client_raintegration_event_handler_t)(const rc_client_raintegration_event_t* event, - rc_client_t* client); - -typedef void (RC_CCONV *rc_client_raintegration_write_memory_func_t)(uint32_t address, uint8_t* buffer, - uint32_t num_bytes, rc_client_t* client); - -typedef void (RC_CCONV* rc_client_raintegration_get_game_name_func_t)(char* buffer, uint32_t buffer_size, rc_client_t* client); - -/* types needed to integrate raintegration */ - -#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION - -#ifndef RC_CLIENT_SUPPORTS_EXTERNAL - #define RC_CLIENT_SUPPORTS_EXTERNAL /* external rc_client required for RAIntegration */ -#endif - -#include /* HWND */ - -#include "rc_client.h" - -RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_load_raintegration(rc_client_t* client, - const wchar_t* search_directory, HWND main_window_handle, - const char* client_name, const char* client_version, - rc_client_callback_t callback, void* callback_userdata); - -RC_EXPORT void RC_CCONV rc_client_unload_raintegration(rc_client_t* client); - -RC_EXPORT void RC_CCONV rc_client_raintegration_update_main_window_handle(rc_client_t* client, HWND main_window_handle); - -RC_EXPORT const rc_client_raintegration_menu_t* RC_CCONV rc_client_raintegration_get_menu(const rc_client_t* client); - -RC_EXPORT void RC_CCONV rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu); -RC_EXPORT void RC_CCONV rc_client_raintegration_update_menu_item(const rc_client_t* client, const rc_client_raintegration_menu_item_t* menu_item); -RC_EXPORT int RC_CCONV rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t menu_item_id); - -RC_EXPORT void RC_CCONV rc_client_raintegration_set_write_memory_function(rc_client_t* client, rc_client_raintegration_write_memory_func_t handler); -RC_EXPORT void RC_CCONV rc_client_raintegration_set_get_game_name_function(rc_client_t* client, rc_client_raintegration_get_game_name_func_t handler); -RC_EXPORT int RC_CCONV rc_client_raintegration_has_modifications(const rc_client_t* client); - -RC_EXPORT void RC_CCONV rc_client_raintegration_set_event_handler(rc_client_t* client, - rc_client_raintegration_event_handler_t handler); - -#endif /* RC_CLIENT_SUPPORTS_RAINTEGRATION */ - -RC_END_C_DECLS - -#endif /* RC_CLIENT_RAINTEGRATION_H */ +#ifndef RC_CLIENT_RAINTEGRATION_H +#define RC_CLIENT_RAINTEGRATION_H + +#ifndef _WIN32 + #undef RC_CLIENT_SUPPORTS_RAINTEGRATION /* Windows required for RAIntegration */ +#endif + +#include + +#include "rc_export.h" + +RC_BEGIN_C_DECLS + +typedef struct rc_client_t rc_client_t; /* forward reference; in rc_client.h */ + +/* types needed to implement raintegration */ + +typedef struct rc_client_raintegration_menu_item_t { + const char* label; + uint32_t id; + uint8_t checked; + uint8_t enabled; +} rc_client_raintegration_menu_item_t; + +typedef struct rc_client_raintegration_menu_t { + rc_client_raintegration_menu_item_t* items; + uint32_t num_items; +} rc_client_raintegration_menu_t; + +enum { + RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_NONE = 0, + RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_PUBLISHED = 1, + RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_LOCAL = 2, + RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_MODIFIED = 3, + RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_INSECURE = 4, +}; + +enum { + RC_CLIENT_RAINTEGRATION_EVENT_TYPE_NONE = 0, + RC_CLIENT_RAINTEGRATION_EVENT_MENUITEM_CHECKED_CHANGED = 1, /* [menu_item] checked changed */ + RC_CLIENT_RAINTEGRATION_EVENT_HARDCORE_CHANGED = 2, /* hardcore was enabled or disabled */ + RC_CLIENT_RAINTEGRATION_EVENT_PAUSE = 3, /* emulated system should be paused */ + RC_CLIENT_RAINTEGRATION_EVENT_MENU_CHANGED = 4 /* one or more items were added/removed from the menu and it should be rebuilt */ +}; + +typedef struct rc_client_raintegration_event_t { + uint32_t type; + + const rc_client_raintegration_menu_item_t* menu_item; +} rc_client_raintegration_event_t; + +typedef void (RC_CCONV *rc_client_raintegration_event_handler_t)(const rc_client_raintegration_event_t* event, + rc_client_t* client); + +typedef void (RC_CCONV *rc_client_raintegration_write_memory_func_t)(uint32_t address, uint8_t* buffer, + uint32_t num_bytes, rc_client_t* client); + +typedef void (RC_CCONV* rc_client_raintegration_get_game_name_func_t)(char* buffer, uint32_t buffer_size, rc_client_t* client); + +/* types needed to integrate raintegration */ + +#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION + +#ifndef RC_CLIENT_SUPPORTS_EXTERNAL + #define RC_CLIENT_SUPPORTS_EXTERNAL /* external rc_client required for RAIntegration */ +#endif + +#include /* HWND */ + +#include "rc_client.h" + +RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_load_raintegration(rc_client_t* client, + const wchar_t* search_directory, HWND main_window_handle, + const char* client_name, const char* client_version, + rc_client_callback_t callback, void* callback_userdata); + +RC_EXPORT void RC_CCONV rc_client_unload_raintegration(rc_client_t* client); + +RC_EXPORT void RC_CCONV rc_client_raintegration_update_main_window_handle(rc_client_t* client, HWND main_window_handle); + +RC_EXPORT const rc_client_raintegration_menu_t* RC_CCONV rc_client_raintegration_get_menu(const rc_client_t* client); + +RC_EXPORT void RC_CCONV rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu); +RC_EXPORT void RC_CCONV rc_client_raintegration_update_menu_item(const rc_client_t* client, const rc_client_raintegration_menu_item_t* menu_item); +RC_EXPORT int RC_CCONV rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t menu_item_id); + +RC_EXPORT void RC_CCONV rc_client_raintegration_set_write_memory_function(rc_client_t* client, rc_client_raintegration_write_memory_func_t handler); +RC_EXPORT void RC_CCONV rc_client_raintegration_set_get_game_name_function(rc_client_t* client, rc_client_raintegration_get_game_name_func_t handler); +RC_EXPORT void RC_CCONV rc_client_raintegration_set_console_id(rc_client_t* client, uint32_t console_id); +RC_EXPORT int RC_CCONV rc_client_raintegration_has_modifications(const rc_client_t* client); + +RC_EXPORT void RC_CCONV rc_client_raintegration_set_event_handler(rc_client_t* client, + rc_client_raintegration_event_handler_t handler); + +RC_EXPORT int RC_CCONV rc_client_raintegration_get_achievement_state(const rc_client_t* client, uint32_t achievement_id); + +#endif /* RC_CLIENT_SUPPORTS_RAINTEGRATION */ + +RC_END_C_DECLS + +#endif /* RC_CLIENT_RAINTEGRATION_H */ diff --git a/include/rcheevos/rc_consoles.h b/include/rcheevos/rc_consoles.h index bcf91d66..5f788855 100644 --- a/include/rcheevos/rc_consoles.h +++ b/include/rcheevos/rc_consoles.h @@ -1,137 +1,138 @@ -#ifndef RC_CONSOLES_H -#define RC_CONSOLES_H - -#include "rc_export.h" - -#include - -RC_BEGIN_C_DECLS - -/*****************************************************************************\ -| Console identifiers | -\*****************************************************************************/ - -enum { - RC_CONSOLE_UNKNOWN = 0, - RC_CONSOLE_MEGA_DRIVE = 1, - RC_CONSOLE_NINTENDO_64 = 2, - RC_CONSOLE_SUPER_NINTENDO = 3, - RC_CONSOLE_GAMEBOY = 4, - RC_CONSOLE_GAMEBOY_ADVANCE = 5, - RC_CONSOLE_GAMEBOY_COLOR = 6, - RC_CONSOLE_NINTENDO = 7, - RC_CONSOLE_PC_ENGINE = 8, - RC_CONSOLE_SEGA_CD = 9, - RC_CONSOLE_SEGA_32X = 10, - RC_CONSOLE_MASTER_SYSTEM = 11, - RC_CONSOLE_PLAYSTATION = 12, - RC_CONSOLE_ATARI_LYNX = 13, - RC_CONSOLE_NEOGEO_POCKET = 14, - RC_CONSOLE_GAME_GEAR = 15, - RC_CONSOLE_GAMECUBE = 16, - RC_CONSOLE_ATARI_JAGUAR = 17, - RC_CONSOLE_NINTENDO_DS = 18, - RC_CONSOLE_WII = 19, - RC_CONSOLE_WII_U = 20, - RC_CONSOLE_PLAYSTATION_2 = 21, - RC_CONSOLE_XBOX = 22, - RC_CONSOLE_MAGNAVOX_ODYSSEY2 = 23, - RC_CONSOLE_POKEMON_MINI = 24, - RC_CONSOLE_ATARI_2600 = 25, - RC_CONSOLE_MS_DOS = 26, - RC_CONSOLE_ARCADE = 27, - RC_CONSOLE_VIRTUAL_BOY = 28, - RC_CONSOLE_MSX = 29, - RC_CONSOLE_COMMODORE_64 = 30, - RC_CONSOLE_ZX81 = 31, - RC_CONSOLE_ORIC = 32, - RC_CONSOLE_SG1000 = 33, - RC_CONSOLE_VIC20 = 34, - RC_CONSOLE_AMIGA = 35, - RC_CONSOLE_ATARI_ST = 36, - RC_CONSOLE_AMSTRAD_PC = 37, - RC_CONSOLE_APPLE_II = 38, - RC_CONSOLE_SATURN = 39, - RC_CONSOLE_DREAMCAST = 40, - RC_CONSOLE_PSP = 41, - RC_CONSOLE_CDI = 42, - RC_CONSOLE_3DO = 43, - RC_CONSOLE_COLECOVISION = 44, - RC_CONSOLE_INTELLIVISION = 45, - RC_CONSOLE_VECTREX = 46, - RC_CONSOLE_PC8800 = 47, - RC_CONSOLE_PC9800 = 48, - RC_CONSOLE_PCFX = 49, - RC_CONSOLE_ATARI_5200 = 50, - RC_CONSOLE_ATARI_7800 = 51, - RC_CONSOLE_X68K = 52, - RC_CONSOLE_WONDERSWAN = 53, - RC_CONSOLE_CASSETTEVISION = 54, - RC_CONSOLE_SUPER_CASSETTEVISION = 55, - RC_CONSOLE_NEO_GEO_CD = 56, - RC_CONSOLE_FAIRCHILD_CHANNEL_F = 57, - RC_CONSOLE_FM_TOWNS = 58, - RC_CONSOLE_ZX_SPECTRUM = 59, - RC_CONSOLE_GAME_AND_WATCH = 60, - RC_CONSOLE_NOKIA_NGAGE = 61, - RC_CONSOLE_NINTENDO_3DS = 62, - RC_CONSOLE_SUPERVISION = 63, - RC_CONSOLE_SHARPX1 = 64, - RC_CONSOLE_TIC80 = 65, - RC_CONSOLE_THOMSONTO8 = 66, - RC_CONSOLE_PC6000 = 67, - RC_CONSOLE_PICO = 68, - RC_CONSOLE_MEGADUCK = 69, - RC_CONSOLE_ZEEBO = 70, - RC_CONSOLE_ARDUBOY = 71, - RC_CONSOLE_WASM4 = 72, - RC_CONSOLE_ARCADIA_2001 = 73, - RC_CONSOLE_INTERTON_VC_4000 = 74, - RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER = 75, - RC_CONSOLE_PC_ENGINE_CD = 76, - RC_CONSOLE_ATARI_JAGUAR_CD = 77, - RC_CONSOLE_NINTENDO_DSI = 78, - RC_CONSOLE_TI83 = 79, - RC_CONSOLE_UZEBOX = 80, - - RC_CONSOLE_HUBS = 100, - RC_CONSOLE_EVENTS = 101, - RC_CONSOLE_STANDALONE = 102 -}; - -RC_EXPORT const char* RC_CCONV rc_console_name(uint32_t console_id); - -/*****************************************************************************\ -| Memory mapping | -\*****************************************************************************/ - -enum { - RC_MEMORY_TYPE_SYSTEM_RAM, /* normal system memory */ - RC_MEMORY_TYPE_SAVE_RAM, /* memory that persists between sessions */ - RC_MEMORY_TYPE_VIDEO_RAM, /* memory reserved for graphical processing */ - RC_MEMORY_TYPE_READONLY, /* memory that maps to read only data */ - RC_MEMORY_TYPE_HARDWARE_CONTROLLER, /* memory for interacting with system components */ - RC_MEMORY_TYPE_VIRTUAL_RAM, /* secondary address space that maps to real memory in system RAM */ - RC_MEMORY_TYPE_UNUSED /* these addresses don't really exist */ -}; - -typedef struct rc_memory_region_t { - uint32_t start_address; /* first address of block as queried by RetroAchievements */ - uint32_t end_address; /* last address of block as queried by RetroAchievements */ - uint32_t real_address; /* real address for first address of block */ - uint8_t type; /* RC_MEMORY_TYPE_ for block */ - const char* description; /* short description of block */ -} -rc_memory_region_t; - -typedef struct rc_memory_regions_t { - const rc_memory_region_t* region; - uint32_t num_regions; -} -rc_memory_regions_t; - -RC_EXPORT const rc_memory_regions_t* RC_CCONV rc_console_memory_regions(uint32_t console_id); - -RC_END_C_DECLS - -#endif /* RC_CONSOLES_H */ +#ifndef RC_CONSOLES_H +#define RC_CONSOLES_H + +#include "rc_export.h" + +#include + +RC_BEGIN_C_DECLS + +/*****************************************************************************\ +| Console identifiers | +\*****************************************************************************/ + +enum { + RC_CONSOLE_UNKNOWN = 0, + RC_CONSOLE_MEGA_DRIVE = 1, + RC_CONSOLE_NINTENDO_64 = 2, + RC_CONSOLE_SUPER_NINTENDO = 3, + RC_CONSOLE_GAMEBOY = 4, + RC_CONSOLE_GAMEBOY_ADVANCE = 5, + RC_CONSOLE_GAMEBOY_COLOR = 6, + RC_CONSOLE_NINTENDO = 7, + RC_CONSOLE_PC_ENGINE = 8, + RC_CONSOLE_SEGA_CD = 9, + RC_CONSOLE_SEGA_32X = 10, + RC_CONSOLE_MASTER_SYSTEM = 11, + RC_CONSOLE_PLAYSTATION = 12, + RC_CONSOLE_ATARI_LYNX = 13, + RC_CONSOLE_NEOGEO_POCKET = 14, + RC_CONSOLE_GAME_GEAR = 15, + RC_CONSOLE_GAMECUBE = 16, + RC_CONSOLE_ATARI_JAGUAR = 17, + RC_CONSOLE_NINTENDO_DS = 18, + RC_CONSOLE_WII = 19, + RC_CONSOLE_WII_U = 20, + RC_CONSOLE_PLAYSTATION_2 = 21, + RC_CONSOLE_XBOX = 22, + RC_CONSOLE_MAGNAVOX_ODYSSEY2 = 23, + RC_CONSOLE_POKEMON_MINI = 24, + RC_CONSOLE_ATARI_2600 = 25, + RC_CONSOLE_MS_DOS = 26, + RC_CONSOLE_ARCADE = 27, + RC_CONSOLE_VIRTUAL_BOY = 28, + RC_CONSOLE_MSX = 29, + RC_CONSOLE_COMMODORE_64 = 30, + RC_CONSOLE_ZX81 = 31, + RC_CONSOLE_ORIC = 32, + RC_CONSOLE_SG1000 = 33, + RC_CONSOLE_VIC20 = 34, + RC_CONSOLE_AMIGA = 35, + RC_CONSOLE_ATARI_ST = 36, + RC_CONSOLE_AMSTRAD_PC = 37, + RC_CONSOLE_APPLE_II = 38, + RC_CONSOLE_SATURN = 39, + RC_CONSOLE_DREAMCAST = 40, + RC_CONSOLE_PSP = 41, + RC_CONSOLE_CDI = 42, + RC_CONSOLE_3DO = 43, + RC_CONSOLE_COLECOVISION = 44, + RC_CONSOLE_INTELLIVISION = 45, + RC_CONSOLE_VECTREX = 46, + RC_CONSOLE_PC8800 = 47, + RC_CONSOLE_PC9800 = 48, + RC_CONSOLE_PCFX = 49, + RC_CONSOLE_ATARI_5200 = 50, + RC_CONSOLE_ATARI_7800 = 51, + RC_CONSOLE_X68K = 52, + RC_CONSOLE_WONDERSWAN = 53, + RC_CONSOLE_CASSETTEVISION = 54, + RC_CONSOLE_SUPER_CASSETTEVISION = 55, + RC_CONSOLE_NEO_GEO_CD = 56, + RC_CONSOLE_FAIRCHILD_CHANNEL_F = 57, + RC_CONSOLE_FM_TOWNS = 58, + RC_CONSOLE_ZX_SPECTRUM = 59, + RC_CONSOLE_GAME_AND_WATCH = 60, + RC_CONSOLE_NOKIA_NGAGE = 61, + RC_CONSOLE_NINTENDO_3DS = 62, + RC_CONSOLE_SUPERVISION = 63, + RC_CONSOLE_SHARPX1 = 64, + RC_CONSOLE_TIC80 = 65, + RC_CONSOLE_THOMSONTO8 = 66, + RC_CONSOLE_PC6000 = 67, + RC_CONSOLE_PICO = 68, + RC_CONSOLE_MEGADUCK = 69, + RC_CONSOLE_ZEEBO = 70, + RC_CONSOLE_ARDUBOY = 71, + RC_CONSOLE_WASM4 = 72, + RC_CONSOLE_ARCADIA_2001 = 73, + RC_CONSOLE_INTERTON_VC_4000 = 74, + RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER = 75, + RC_CONSOLE_PC_ENGINE_CD = 76, + RC_CONSOLE_ATARI_JAGUAR_CD = 77, + RC_CONSOLE_NINTENDO_DSI = 78, + RC_CONSOLE_TI83 = 79, + RC_CONSOLE_UZEBOX = 80, + RC_CONSOLE_FAMICOM_DISK_SYSTEM = 81, + + RC_CONSOLE_HUBS = 100, + RC_CONSOLE_EVENTS = 101, + RC_CONSOLE_STANDALONE = 102 +}; + +RC_EXPORT const char* RC_CCONV rc_console_name(uint32_t console_id); + +/*****************************************************************************\ +| Memory mapping | +\*****************************************************************************/ + +enum { + RC_MEMORY_TYPE_SYSTEM_RAM, /* normal system memory */ + RC_MEMORY_TYPE_SAVE_RAM, /* memory that persists between sessions */ + RC_MEMORY_TYPE_VIDEO_RAM, /* memory reserved for graphical processing */ + RC_MEMORY_TYPE_READONLY, /* memory that maps to read only data */ + RC_MEMORY_TYPE_HARDWARE_CONTROLLER, /* memory for interacting with system components */ + RC_MEMORY_TYPE_VIRTUAL_RAM, /* secondary address space that maps to real memory in system RAM */ + RC_MEMORY_TYPE_UNUSED /* these addresses don't really exist */ +}; + +typedef struct rc_memory_region_t { + uint32_t start_address; /* first address of block as queried by RetroAchievements */ + uint32_t end_address; /* last address of block as queried by RetroAchievements */ + uint32_t real_address; /* real address for first address of block */ + uint8_t type; /* RC_MEMORY_TYPE_ for block */ + const char* description; /* short description of block */ +} +rc_memory_region_t; + +typedef struct rc_memory_regions_t { + const rc_memory_region_t* region; + uint32_t num_regions; +} +rc_memory_regions_t; + +RC_EXPORT const rc_memory_regions_t* RC_CCONV rc_console_memory_regions(uint32_t console_id); + +RC_END_C_DECLS + +#endif /* RC_CONSOLES_H */ diff --git a/include/rcheevos/rc_error.h b/include/rcheevos/rc_error.h index f6c5f4a2..5d253442 100644 --- a/include/rcheevos/rc_error.h +++ b/include/rcheevos/rc_error.h @@ -1,56 +1,59 @@ -#ifndef RC_ERROR_H -#define RC_ERROR_H - -#include "rc_export.h" - -RC_BEGIN_C_DECLS - -/*****************************************************************************\ -| Return values | -\*****************************************************************************/ - -enum { - RC_OK = 0, - RC_INVALID_LUA_OPERAND = -1, - RC_INVALID_MEMORY_OPERAND = -2, - RC_INVALID_CONST_OPERAND = -3, - RC_INVALID_FP_OPERAND = -4, - RC_INVALID_CONDITION_TYPE = -5, - RC_INVALID_OPERATOR = -6, - RC_INVALID_REQUIRED_HITS = -7, - RC_DUPLICATED_START = -8, - RC_DUPLICATED_CANCEL = -9, - RC_DUPLICATED_SUBMIT = -10, - RC_DUPLICATED_VALUE = -11, - RC_DUPLICATED_PROGRESS = -12, - RC_MISSING_START = -13, - RC_MISSING_CANCEL = -14, - RC_MISSING_SUBMIT = -15, - RC_MISSING_VALUE = -16, - RC_INVALID_LBOARD_FIELD = -17, - RC_MISSING_DISPLAY_STRING = -18, - RC_OUT_OF_MEMORY = -19, - RC_INVALID_VALUE_FLAG = -20, - RC_MISSING_VALUE_MEASURED = -21, - RC_MULTIPLE_MEASURED = -22, - RC_INVALID_MEASURED_TARGET = -23, - RC_INVALID_COMPARISON = -24, - RC_INVALID_STATE = -25, - RC_INVALID_JSON = -26, - RC_API_FAILURE = -27, - RC_LOGIN_REQUIRED = -28, - RC_NO_GAME_LOADED = -29, - RC_HARDCORE_DISABLED = -30, - RC_ABORTED = -31, - RC_NO_RESPONSE = -32, - RC_ACCESS_DENIED = -33, - RC_INVALID_CREDENTIALS = -34, - RC_EXPIRED_TOKEN = -35, - RC_INSUFFICIENT_BUFFER = -36 -}; - -RC_EXPORT const char* RC_CCONV rc_error_str(int ret); - -RC_END_C_DECLS - -#endif /* RC_ERROR_H */ +#ifndef RC_ERROR_H +#define RC_ERROR_H + +#include "rc_export.h" + +RC_BEGIN_C_DECLS + +/*****************************************************************************\ +| Return values | +\*****************************************************************************/ + +enum { + RC_OK = 0, + RC_INVALID_FUNC_OPERAND = -1, + RC_INVALID_MEMORY_OPERAND = -2, + RC_INVALID_CONST_OPERAND = -3, + RC_INVALID_FP_OPERAND = -4, + RC_INVALID_CONDITION_TYPE = -5, + RC_INVALID_OPERATOR = -6, + RC_INVALID_REQUIRED_HITS = -7, + RC_DUPLICATED_START = -8, + RC_DUPLICATED_CANCEL = -9, + RC_DUPLICATED_SUBMIT = -10, + RC_DUPLICATED_VALUE = -11, + RC_DUPLICATED_PROGRESS = -12, + RC_MISSING_START = -13, + RC_MISSING_CANCEL = -14, + RC_MISSING_SUBMIT = -15, + RC_MISSING_VALUE = -16, + RC_INVALID_LBOARD_FIELD = -17, + RC_MISSING_DISPLAY_STRING = -18, + RC_OUT_OF_MEMORY = -19, + RC_INVALID_VALUE_FLAG = -20, + RC_MISSING_VALUE_MEASURED = -21, + RC_MULTIPLE_MEASURED = -22, + RC_INVALID_MEASURED_TARGET = -23, + RC_INVALID_COMPARISON = -24, + RC_INVALID_STATE = -25, + RC_INVALID_JSON = -26, + RC_API_FAILURE = -27, + RC_LOGIN_REQUIRED = -28, + RC_NO_GAME_LOADED = -29, + RC_HARDCORE_DISABLED = -30, + RC_ABORTED = -31, + RC_NO_RESPONSE = -32, + RC_ACCESS_DENIED = -33, + RC_INVALID_CREDENTIALS = -34, + RC_EXPIRED_TOKEN = -35, + RC_INSUFFICIENT_BUFFER = -36, + RC_INVALID_VARIABLE_NAME = -37, + RC_UNKNOWN_VARIABLE_NAME = -38, + RC_NOT_FOUND = -39 +}; + +RC_EXPORT const char* RC_CCONV rc_error_str(int ret); + +RC_END_C_DECLS + +#endif /* RC_ERROR_H */ diff --git a/include/rcheevos/rc_export.h b/include/rcheevos/rc_export.h index 27b2b267..9471acd4 100644 --- a/include/rcheevos/rc_export.h +++ b/include/rcheevos/rc_export.h @@ -1,100 +1,100 @@ -#ifndef RC_EXPORT_H -#define RC_EXPORT_H - -/* These macros control how callbacks and public functions are defined */ - -/* RC_SHARED should be defined when building rcheevos as a shared library (e.g. dll/dylib/so). External code should not define this macro. */ -/* RC_STATIC should be defined when building rcheevos as a static library. External code should also define this macro. */ -/* RC_IMPORT should be defined for external code using rcheevos as a shared library. */ - -/* For compatibility, if none of these three macros are defined, then the build is assumed to be RC_STATIC */ - -#if !defined(RC_SHARED) && !defined(RC_STATIC) && !defined(RC_IMPORT) - #define RC_STATIC -#endif - -#if (defined(RC_SHARED) && defined(RC_STATIC)) || (defined(RC_SHARED) && defined(RC_IMPORT)) || (defined(RC_STATIC) && defined(RC_IMPORT)) - #error RC_SHARED, RC_STATIC, and RC_IMPORT are mutually exclusive -#endif - -/* RC_BEGIN_C_DECLS and RC_END_C_DECLS should be used for all headers, to enforce C linkage and the C calling convention */ -/* RC_BEGIN_C_DECLS should be placed after #include's and before header declarations */ -/* RC_END_C_DECLS should be placed after header declarations */ - -/* example usage - * - * #ifndef RC_HEADER_H - * #define RC_HEADER_H - * - * #include - * - * RC_BEGIN_C_DECLS - * - * uint8_t rc_function(void); - * - * RC_END_C_DECLS - * - * #endif - */ - -#ifdef __cplusplus - #define RC_BEGIN_C_DECLS extern "C" { - #define RC_END_C_DECLS } -#else - #define RC_BEGIN_C_DECLS - #define RC_END_C_DECLS -#endif - -/* RC_CCONV should be used for public functions and callbacks, to enforce the cdecl calling convention, if applicable */ -/* RC_CCONV should be placed after the return type, and between the ( and * for callbacks */ - -/* example usage */ -/* void RC_CCONV rc_function(void) */ -/* void (RC_CCONV *rc_callback)(void) */ - -#if defined(_WIN32) - /* Windows compilers will ignore __cdecl when not applicable */ - #define RC_CCONV __cdecl -#elif defined(__GNUC__) && defined(__i386__) - /* GNU C compilers will warn if cdecl is defined on an unsupported platform */ - #define RC_CCONV __attribute__((cdecl)) -#else - #define RC_CCONV -#endif - -/* RC_EXPORT should be used for public functions */ -/* RC_EXPORT will provide necessary hints for shared library usage, if applicable */ -/* RC_EXPORT should be placed before the return type */ - -/* example usage */ -/* RC_EXPORT void rc_function(void) */ - -#ifdef RC_SHARED - #if defined(_WIN32) - #define RC_EXPORT __declspec(dllexport) - #elif defined(__GNUC__) && __GNUC__ >= 4 - #define RC_EXPORT __attribute__((visibility("default"))) - #else - #define RC_EXPORT - #endif -#endif - -#ifdef RC_IMPORT - #if defined(_WIN32) - #define RC_EXPORT __declspec(dllimport) - #elif defined(__GNUC__) && __GNUC__ >= 4 - #define RC_EXPORT __attribute__((visibility("default"))) - #else - #define RC_EXPORT - #endif -#endif - -#ifdef RC_STATIC - #if defined(__GNUC__) && __GNUC__ >= 4 - #define RC_EXPORT __attribute__((visibility("default"))) - #else - #define RC_EXPORT - #endif -#endif - -#endif /* RC_EXPORT_H */ +#ifndef RC_EXPORT_H +#define RC_EXPORT_H + +/* These macros control how callbacks and public functions are defined */ + +/* RC_SHARED should be defined when building rcheevos as a shared library (e.g. dll/dylib/so). External code should not define this macro. */ +/* RC_STATIC should be defined when building rcheevos as a static library. External code should also define this macro. */ +/* RC_IMPORT should be defined for external code using rcheevos as a shared library. */ + +/* For compatibility, if none of these three macros are defined, then the build is assumed to be RC_STATIC */ + +#if !defined(RC_SHARED) && !defined(RC_STATIC) && !defined(RC_IMPORT) + #define RC_STATIC +#endif + +#if (defined(RC_SHARED) && defined(RC_STATIC)) || (defined(RC_SHARED) && defined(RC_IMPORT)) || (defined(RC_STATIC) && defined(RC_IMPORT)) + #error RC_SHARED, RC_STATIC, and RC_IMPORT are mutually exclusive +#endif + +/* RC_BEGIN_C_DECLS and RC_END_C_DECLS should be used for all headers, to enforce C linkage and the C calling convention */ +/* RC_BEGIN_C_DECLS should be placed after #include's and before header declarations */ +/* RC_END_C_DECLS should be placed after header declarations */ + +/* example usage + * + * #ifndef RC_HEADER_H + * #define RC_HEADER_H + * + * #include + * + * RC_BEGIN_C_DECLS + * + * uint8_t rc_function(void); + * + * RC_END_C_DECLS + * + * #endif + */ + +#if defined(__cplusplus) && !defined(CXX_BUILD) + #define RC_BEGIN_C_DECLS extern "C" { + #define RC_END_C_DECLS } +#else + #define RC_BEGIN_C_DECLS + #define RC_END_C_DECLS +#endif + +/* RC_CCONV should be used for public functions and callbacks, to enforce the cdecl calling convention, if applicable */ +/* RC_CCONV should be placed after the return type, and between the ( and * for callbacks */ + +/* example usage */ +/* void RC_CCONV rc_function(void) */ +/* void (RC_CCONV *rc_callback)(void) */ + +#if defined(_WIN32) + /* Windows compilers will ignore __cdecl when not applicable */ + #define RC_CCONV __cdecl +#elif defined(__GNUC__) && defined(__i386__) + /* GNU C compilers will warn if cdecl is defined on an unsupported platform */ + #define RC_CCONV __attribute__((cdecl)) +#else + #define RC_CCONV +#endif + +/* RC_EXPORT should be used for public functions */ +/* RC_EXPORT will provide necessary hints for shared library usage, if applicable */ +/* RC_EXPORT should be placed before the return type */ + +/* example usage */ +/* RC_EXPORT void rc_function(void) */ + +#ifdef RC_SHARED + #if defined(_WIN32) + #define RC_EXPORT __declspec(dllexport) + #elif defined(__GNUC__) && __GNUC__ >= 4 + #define RC_EXPORT __attribute__((visibility("default"))) + #else + #define RC_EXPORT + #endif +#endif + +#ifdef RC_IMPORT + #if defined(_WIN32) + #define RC_EXPORT __declspec(dllimport) + #elif defined(__GNUC__) && __GNUC__ >= 4 + #define RC_EXPORT __attribute__((visibility("default"))) + #else + #define RC_EXPORT + #endif +#endif + +#ifdef RC_STATIC + #if defined(__GNUC__) && __GNUC__ >= 4 + #define RC_EXPORT __attribute__((visibility("default"))) + #else + #define RC_EXPORT + #endif +#endif + +#endif /* RC_EXPORT_H */ diff --git a/include/rcheevos/rc_hash.h b/include/rcheevos/rc_hash.h index fb00f739..be1d3781 100644 --- a/include/rcheevos/rc_hash.h +++ b/include/rcheevos/rc_hash.h @@ -1,152 +1,200 @@ -#ifndef RC_HASH_H -#define RC_HASH_H - -#include -#include -#include - -#include "rc_consoles.h" - -RC_BEGIN_C_DECLS - - /* ===================================================== */ - - /* generates a hash from a block of memory. - * returns non-zero on success, or zero on failure. - */ - RC_EXPORT int RC_CCONV rc_hash_generate_from_buffer(char hash[33], uint32_t console_id, const uint8_t* buffer, size_t buffer_size); - - /* generates a hash from a file. - * returns non-zero on success, or zero on failure. - */ - RC_EXPORT int RC_CCONV rc_hash_generate_from_file(char hash[33], uint32_t console_id, const char* path); - - /* ===================================================== */ - - /* data for rc_hash_iterate - */ - typedef struct rc_hash_iterator - { - const uint8_t* buffer; - size_t buffer_size; - uint8_t consoles[12]; - int index; - const char* path; - } rc_hash_iterator_t; - - /* initializes a rc_hash_iterator - * - path must be provided - * - if buffer and buffer_size are provided, path may be a filename (i.e. for something extracted from a zip file) - */ - RC_EXPORT void RC_CCONV rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, const uint8_t* buffer, size_t buffer_size); - - /* releases resources associated to a rc_hash_iterator - */ - RC_EXPORT void RC_CCONV rc_hash_destroy_iterator(struct rc_hash_iterator* iterator); - - /* generates the next hash for the data in the rc_hash_iterator. - * returns non-zero if a hash was generated, or zero if no more hashes can be generated for the data. - */ - RC_EXPORT int RC_CCONV rc_hash_iterate(char hash[33], struct rc_hash_iterator* iterator); - - /* ===================================================== */ - - /* specifies a function to call when an error occurs to display the error message */ - typedef void (RC_CCONV *rc_hash_message_callback)(const char*); - RC_EXPORT void RC_CCONV rc_hash_init_error_message_callback(rc_hash_message_callback callback); - - /* specifies a function to call for verbose logging */ - RC_EXPORT void rc_hash_init_verbose_message_callback(rc_hash_message_callback callback); - - /* ===================================================== */ - - /* opens a file */ - typedef void* (RC_CCONV *rc_hash_filereader_open_file_handler)(const char* path_utf8); - - /* moves the file pointer - standard fseek parameters */ - typedef void (RC_CCONV *rc_hash_filereader_seek_handler)(void* file_handle, int64_t offset, int origin); - - /* locates the file pointer */ - typedef int64_t (RC_CCONV *rc_hash_filereader_tell_handler)(void* file_handle); - - /* reads the specified number of bytes from the file starting at the read pointer. - * returns the number of bytes actually read. - */ - typedef size_t (RC_CCONV *rc_hash_filereader_read_handler)(void* file_handle, void* buffer, size_t requested_bytes); - - /* closes the file */ - typedef void (RC_CCONV *rc_hash_filereader_close_file_handler)(void* file_handle); - - struct rc_hash_filereader - { - rc_hash_filereader_open_file_handler open; - rc_hash_filereader_seek_handler seek; - rc_hash_filereader_tell_handler tell; - rc_hash_filereader_read_handler read; - rc_hash_filereader_close_file_handler close; - }; - - RC_EXPORT void RC_CCONV rc_hash_init_custom_filereader(struct rc_hash_filereader* reader); - - /* ===================================================== */ - - #define RC_HASH_CDTRACK_FIRST_DATA ((uint32_t)-1) /* the first data track (skip audio tracks) */ - #define RC_HASH_CDTRACK_LAST ((uint32_t)-2) /* the last data/audio track */ - #define RC_HASH_CDTRACK_LARGEST ((uint32_t)-3) /* the largest data/audio track */ - #define RC_HASH_CDTRACK_FIRST_OF_SECOND_SESSION ((uint32_t)-4) /* the first data/audio track of the second session */ - - /* opens a track from the specified file. see the RC_HASH_CDTRACK_ defines for special tracks. - * returns a handle to be passed to the other functions, or NULL if the track could not be opened. - */ - typedef void* (RC_CCONV *rc_hash_cdreader_open_track_handler)(const char* path, uint32_t track); - - /* attempts to read the specified number of bytes from the file starting at the specified absolute sector. - * returns the number of bytes actually read. - */ - typedef size_t (RC_CCONV *rc_hash_cdreader_read_sector_handler)(void* track_handle, uint32_t sector, void* buffer, size_t requested_bytes); - - /* closes the track handle */ - typedef void (RC_CCONV *rc_hash_cdreader_close_track_handler)(void* track_handle); - - /* gets the absolute sector index for the first sector of a track */ - typedef uint32_t(RC_CCONV *rc_hash_cdreader_first_track_sector_handler)(void* track_handle); - - struct rc_hash_cdreader - { - rc_hash_cdreader_open_track_handler open_track; - rc_hash_cdreader_read_sector_handler read_sector; - rc_hash_cdreader_close_track_handler close_track; - rc_hash_cdreader_first_track_sector_handler first_track_sector; - }; - - RC_EXPORT void RC_CCONV rc_hash_get_default_cdreader(struct rc_hash_cdreader* cdreader); - RC_EXPORT void RC_CCONV rc_hash_init_default_cdreader(void); - RC_EXPORT void RC_CCONV rc_hash_init_custom_cdreader(struct rc_hash_cdreader* reader); - - /* specifies a function called to obtain a 3DS CIA decryption normal key. - * this key would be derived from slot0x3DKeyX and the common key specified by the passed index. - * the normal key should be written in big endian format - * returns non-zero on success, or zero on failure. - */ - typedef int (RC_CCONV *rc_hash_3ds_get_cia_normal_key_func)(uint8_t common_key_index, uint8_t out_normal_key[16]); - RC_EXPORT void RC_CCONV rc_hash_init_3ds_get_cia_normal_key_func(rc_hash_3ds_get_cia_normal_key_func func); - - /* specifies a function called to obtain 3DS NCCH decryption normal keys. - * the primary key will always use slot0x2CKeyX and the passed primary KeyY. - * the secondary key will use the KeyX slot passed - * the secondary KeyY will be identical to the primary keyY if the passed program id is NULL - * if the program id is not null, then the secondary KeyY will be obtained with "seed crypto" - * with "seed crypto" the 8 byte program id can be used to obtain a 16 byte "seed" within the seeddb.bin firmware file - * the primary KeyY then the seed will then be hashed with SHA256, and the upper 16 bytes of the digest will be the secondary KeyY used - * the normal keys should be written in big endian format - * returns non-zero on success, or zero on failure. - */ - typedef int (RC_CCONV *rc_hash_3ds_get_ncch_normal_keys_func)(uint8_t primary_key_y[16], uint8_t secondary_key_x_slot, uint8_t* optional_program_id, - uint8_t out_primary_key[16], uint8_t out_secondary_key[16]); - RC_EXPORT void RC_CCONV rc_hash_init_3ds_get_ncch_normal_keys_func(rc_hash_3ds_get_ncch_normal_keys_func func); - - /* ===================================================== */ - -RC_END_C_DECLS - -#endif /* RC_HASH_H */ +#ifndef RC_HASH_H +#define RC_HASH_H + +#include +#include +#include + +#include "rc_consoles.h" + +RC_BEGIN_C_DECLS + + struct rc_hash_iterator; + + /* ===================================================== */ + + typedef void (RC_CCONV *rc_hash_message_callback_deprecated)(const char*); + + /* specifies a function to call when an error occurs to display the error message */ + /* [deprecated] set callbacks in rc_hash_iterator_t */ + RC_EXPORT void RC_CCONV rc_hash_init_error_message_callback(rc_hash_message_callback_deprecated callback); + + /* specifies a function to call for verbose logging */ + /* [deprecated] set callbacks in rc_hash_iterator_t */ + RC_EXPORT void rc_hash_init_verbose_message_callback(rc_hash_message_callback_deprecated callback); + + /* ===================================================== */ + + /* opens a file */ + typedef void* (RC_CCONV *rc_hash_filereader_open_file_handler)(const char* path_utf8); + + /* moves the file pointer - standard fseek parameters */ + typedef void (RC_CCONV *rc_hash_filereader_seek_handler)(void* file_handle, int64_t offset, int origin); + + /* locates the file pointer */ + typedef int64_t (RC_CCONV *rc_hash_filereader_tell_handler)(void* file_handle); + + /* reads the specified number of bytes from the file starting at the read pointer. + * returns the number of bytes actually read. + */ + typedef size_t (RC_CCONV *rc_hash_filereader_read_handler)(void* file_handle, void* buffer, size_t requested_bytes); + + /* closes the file */ + typedef void (RC_CCONV *rc_hash_filereader_close_file_handler)(void* file_handle); + + typedef struct rc_hash_filereader + { + rc_hash_filereader_open_file_handler open; + rc_hash_filereader_seek_handler seek; + rc_hash_filereader_tell_handler tell; + rc_hash_filereader_read_handler read; + rc_hash_filereader_close_file_handler close; + } rc_hash_filereader_t; + + /* [deprecated] set callbacks in rc_hash_iterator_t */ + RC_EXPORT void RC_CCONV rc_hash_init_custom_filereader(struct rc_hash_filereader* reader); + + /* ===================================================== */ + +#ifndef RC_HASH_NO_DISC + + #define RC_HASH_CDTRACK_FIRST_DATA ((uint32_t)-1) /* the first data track (skip audio tracks) */ + #define RC_HASH_CDTRACK_LAST ((uint32_t)-2) /* the last data/audio track */ + #define RC_HASH_CDTRACK_LARGEST ((uint32_t)-3) /* the largest data/audio track */ + #define RC_HASH_CDTRACK_FIRST_OF_SECOND_SESSION ((uint32_t)-4) /* the first data/audio track of the second session */ + + /* opens a track from the specified file. see the RC_HASH_CDTRACK_ defines for special tracks. + * returns a handle to be passed to the other functions, or NULL if the track could not be opened. + */ + typedef void* (RC_CCONV *rc_hash_cdreader_open_track_handler)(const char* path, uint32_t track); + typedef void* (RC_CCONV* rc_hash_cdreader_open_track_iterator_handler)(const char* path, uint32_t track, const struct rc_hash_iterator* iterator); + + /* attempts to read the specified number of bytes from the file starting at the specified absolute sector. + * returns the number of bytes actually read. + */ + typedef size_t (RC_CCONV *rc_hash_cdreader_read_sector_handler)(void* track_handle, uint32_t sector, void* buffer, size_t requested_bytes); + + /* closes the track handle */ + typedef void (RC_CCONV *rc_hash_cdreader_close_track_handler)(void* track_handle); + + /* gets the absolute sector index for the first sector of a track */ + typedef uint32_t(RC_CCONV *rc_hash_cdreader_first_track_sector_handler)(void* track_handle); + + typedef struct rc_hash_cdreader + { + rc_hash_cdreader_open_track_handler open_track; + rc_hash_cdreader_read_sector_handler read_sector; + rc_hash_cdreader_close_track_handler close_track; + rc_hash_cdreader_first_track_sector_handler first_track_sector; + rc_hash_cdreader_open_track_iterator_handler open_track_iterator; + } rc_hash_cdreader_t; + + RC_EXPORT void RC_CCONV rc_hash_get_default_cdreader(struct rc_hash_cdreader* cdreader); + /* [deprecated] don't set callbacks in rc_hash_iterator_t */ + RC_EXPORT void RC_CCONV rc_hash_init_default_cdreader(void); + /* [deprecated] set callbacks in rc_hash_iterator_t */ + RC_EXPORT void RC_CCONV rc_hash_init_custom_cdreader(struct rc_hash_cdreader* reader); + +#endif /* RC_HASH_NO_DISC */ + +#ifndef RC_HASH_NO_ENCRYPTED + + /* specifies a function called to obtain a 3DS CIA decryption normal key. + * this key would be derived from slot0x3DKeyX and the common key specified by the passed index. + * the normal key should be written in big endian format + * returns non-zero on success, or zero on failure. + */ + typedef int (RC_CCONV *rc_hash_3ds_get_cia_normal_key_func)(uint8_t common_key_index, uint8_t out_normal_key[16]); + /* [deprecated] set callbacks in rc_hash_iterator_t */ + RC_EXPORT void RC_CCONV rc_hash_init_3ds_get_cia_normal_key_func(rc_hash_3ds_get_cia_normal_key_func func); + + /* specifies a function called to obtain 3DS NCCH decryption normal keys. + * the primary key will always use slot0x2CKeyX and the passed primary KeyY. + * the secondary key will use the KeyX slot passed + * the secondary KeyY will be identical to the primary keyY if the passed program id is NULL + * if the program id is not null, then the secondary KeyY will be obtained with "seed crypto" + * with "seed crypto" the 8 byte program id can be used to obtain a 16 byte "seed" within the seeddb.bin firmware file + * the primary KeyY then the seed will then be hashed with SHA256, and the upper 16 bytes of the digest will be the secondary KeyY used + * the normal keys should be written in big endian format + * returns non-zero on success, or zero on failure. + */ + typedef int (RC_CCONV *rc_hash_3ds_get_ncch_normal_keys_func)(uint8_t primary_key_y[16], uint8_t secondary_key_x_slot, uint8_t* optional_program_id, + uint8_t out_primary_key[16], uint8_t out_secondary_key[16]); + /* [deprecated] set callbacks in rc_hash_iterator_t */ + RC_EXPORT void RC_CCONV rc_hash_init_3ds_get_ncch_normal_keys_func(rc_hash_3ds_get_ncch_normal_keys_func func); + +#endif /* RC_HASH_NO_ENCRYPTED */ + +/* ===================================================== */ + +typedef void (RC_CCONV* rc_hash_message_callback_func)(const char*, const struct rc_hash_iterator* iterator); + +typedef struct rc_hash_callbacks { + rc_hash_message_callback_func verbose_message; + rc_hash_message_callback_func error_message; + + rc_hash_filereader_t filereader; +#ifndef RC_HASH_NO_DISC + rc_hash_cdreader_t cdreader; +#endif + +#ifndef RC_HASH_NO_ENCRYPTED + struct rc_hash_encryption_callbacks { + rc_hash_3ds_get_cia_normal_key_func get_3ds_cia_normal_key; + rc_hash_3ds_get_ncch_normal_keys_func get_3ds_ncch_normal_keys; + } encryption; +#endif +} rc_hash_callbacks_t; + +/* data for rc_hash_iterate + */ +typedef struct rc_hash_iterator { + const uint8_t* buffer; + size_t buffer_size; + uint8_t consoles[12]; + int index; + const char* path; + void* userdata; + + rc_hash_callbacks_t callbacks; +} rc_hash_iterator_t; + +/* initializes a rc_hash_iterator + * - path must be provided + * - if buffer and buffer_size are provided, path may be a filename (i.e. for something extracted from a zip file) + */ +RC_EXPORT void RC_CCONV rc_hash_initialize_iterator(rc_hash_iterator_t* iterator, const char* path, const uint8_t* buffer, size_t buffer_size); + +/* releases resources associated to a rc_hash_iterator + */ +RC_EXPORT void RC_CCONV rc_hash_destroy_iterator(rc_hash_iterator_t* iterator); + +/* generates the next hash for the data in the rc_hash_iterator. + * returns non-zero if a hash was generated, or zero if no more hashes can be generated for the data. + */ +RC_EXPORT int RC_CCONV rc_hash_iterate(char hash[33], rc_hash_iterator_t* iterator); + +/* generates a hash for the data in the rc_hash_iterator. + * returns non-zero if a hash was generated. + */ +RC_EXPORT int RC_CCONV rc_hash_generate(char hash[33], uint32_t console_id, const rc_hash_iterator_t* iterator); + +/* ===================================================== */ + +/* generates a hash from a block of memory. + * returns non-zero on success, or zero on failure. + */ +/* [deprecated] use rc_hash_generate instead */ +RC_EXPORT int RC_CCONV rc_hash_generate_from_buffer(char hash[33], uint32_t console_id, const uint8_t* buffer, size_t buffer_size); + +/* generates a hash from a file. + * returns non-zero on success, or zero on failure. + */ +/* [deprecated] use rc_hash_generate instead */ +RC_EXPORT int RC_CCONV rc_hash_generate_from_file(char hash[33], uint32_t console_id, const char* path); + +/* ===================================================== */ + +RC_END_C_DECLS + +#endif /* RC_HASH_H */ diff --git a/include/rcheevos/rc_libretro.h b/include/rcheevos/rc_libretro.h index 37f9a9b7..9e96acef 100644 --- a/include/rcheevos/rc_libretro.h +++ b/include/rcheevos/rc_libretro.h @@ -1,109 +1,98 @@ -#ifndef RC_LIBRETRO_H -#define RC_LIBRETRO_H - -#include "rc_export.h" - -/* this file comes from the libretro repository, which is not an explicit - * submodule. the integration must set up paths appropriately to find it. */ -#include - -#include -#include - -RC_BEGIN_C_DECLS - -/*****************************************************************************\ -| Disallowed Settings | -\*****************************************************************************/ - -typedef struct rc_disallowed_setting_t { - const char *setting; - const char *value; -} rc_disallowed_setting_t; - -RC_EXPORT const rc_disallowed_setting_t *RC_CCONV -rc_libretro_get_disallowed_settings(const char *library_name); -RC_EXPORT int RC_CCONV rc_libretro_is_setting_allowed( - const rc_disallowed_setting_t *disallowed_settings, const char *setting, - const char *value); -RC_EXPORT int RC_CCONV rc_libretro_is_system_allowed(const char *library_name, - uint32_t console_id); - -/*****************************************************************************\ -| Memory Mapping | -\*****************************************************************************/ - -/* specifies a function to call for verbose logging */ -typedef void(RC_CCONV *rc_libretro_message_callback)(const char *); -RC_EXPORT void RC_CCONV rc_libretro_init_verbose_message_callback( - rc_libretro_message_callback callback); - -#define RC_LIBRETRO_MAX_MEMORY_REGIONS 32 -typedef struct rc_libretro_memory_regions_t { - uint8_t *data[RC_LIBRETRO_MAX_MEMORY_REGIONS]; - size_t size[RC_LIBRETRO_MAX_MEMORY_REGIONS]; - size_t total_size; - uint32_t count; -} rc_libretro_memory_regions_t; - -typedef struct rc_libretro_core_memory_info_t { - uint8_t *data; - size_t size; -} rc_libretro_core_memory_info_t; - -typedef void(RC_CCONV *rc_libretro_get_core_memory_info_func)( - uint32_t id, rc_libretro_core_memory_info_t *info); - -RC_EXPORT int RC_CCONV rc_libretro_memory_init( - rc_libretro_memory_regions_t *regions, const struct retro_memory_map *mmap, - rc_libretro_get_core_memory_info_func get_core_memory_info, - uint32_t console_id); -RC_EXPORT void RC_CCONV -rc_libretro_memory_destroy(rc_libretro_memory_regions_t *regions); - -RC_EXPORT uint8_t *RC_CCONV rc_libretro_memory_find( - const rc_libretro_memory_regions_t *regions, uint32_t address); -RC_EXPORT uint8_t *RC_CCONV -rc_libretro_memory_find_avail(const rc_libretro_memory_regions_t *regions, - uint32_t address, uint32_t *avail); -RC_EXPORT uint32_t RC_CCONV -rc_libretro_memory_read(const rc_libretro_memory_regions_t *regions, - uint32_t address, uint8_t *buffer, uint32_t num_bytes); - -/*****************************************************************************\ -| Disk Identification | -\*****************************************************************************/ - -typedef struct rc_libretro_hash_entry_t { - uint32_t path_djb2; - uint32_t game_id; - char hash[33]; -} rc_libretro_hash_entry_t; - -typedef struct rc_libretro_hash_set_t { - struct rc_libretro_hash_entry_t *entries; - uint16_t entries_count; - uint16_t entries_size; -} rc_libretro_hash_set_t; - -typedef int(RC_CCONV *rc_libretro_get_image_path_func)(uint32_t index, - char *buffer, - size_t buffer_size); - -RC_EXPORT void RC_CCONV rc_libretro_hash_set_init( - struct rc_libretro_hash_set_t *hash_set, const char *m3u_path, - rc_libretro_get_image_path_func get_image_path); -RC_EXPORT void RC_CCONV -rc_libretro_hash_set_destroy(struct rc_libretro_hash_set_t *hash_set); - -RC_EXPORT void RC_CCONV rc_libretro_hash_set_add( - struct rc_libretro_hash_set_t *hash_set, const char *path, uint32_t game_id, - const char hash[33]); -RC_EXPORT const char *RC_CCONV rc_libretro_hash_set_get_hash( - const struct rc_libretro_hash_set_t *hash_set, const char *path); -RC_EXPORT int RC_CCONV rc_libretro_hash_set_get_game_id( - const struct rc_libretro_hash_set_t *hash_set, const char *hash); - -RC_END_C_DECLS - -#endif /* RC_LIBRETRO_H */ +#ifndef RC_LIBRETRO_H +#define RC_LIBRETRO_H + +#include "rc_export.h" + +#include "rc_hash.h" + +/* this file comes from the libretro repository, which is not an explicit submodule. + * the integration must set up paths appropriately to find it. */ +#include + +#include +#include + +RC_BEGIN_C_DECLS + +/*****************************************************************************\ +| Disallowed Settings | +\*****************************************************************************/ + +typedef struct rc_disallowed_setting_t +{ + const char* setting; + const char* value; +} rc_disallowed_setting_t; + +RC_EXPORT const rc_disallowed_setting_t* RC_CCONV rc_libretro_get_disallowed_settings(const char* library_name); +RC_EXPORT int RC_CCONV rc_libretro_is_setting_allowed(const rc_disallowed_setting_t* disallowed_settings, const char* setting, const char* value); +RC_EXPORT int RC_CCONV rc_libretro_is_system_allowed(const char* library_name, uint32_t console_id); + +/*****************************************************************************\ +| Memory Mapping | +\*****************************************************************************/ + +/* specifies a function to call for verbose logging */ +typedef void (RC_CCONV *rc_libretro_message_callback)(const char*); +RC_EXPORT void RC_CCONV rc_libretro_init_verbose_message_callback(rc_libretro_message_callback callback); + +#define RC_LIBRETRO_MAX_MEMORY_REGIONS 32 +typedef struct rc_libretro_memory_regions_t +{ + uint8_t* data[RC_LIBRETRO_MAX_MEMORY_REGIONS]; + size_t size[RC_LIBRETRO_MAX_MEMORY_REGIONS]; + size_t total_size; + uint32_t count; +} rc_libretro_memory_regions_t; + +typedef struct rc_libretro_core_memory_info_t +{ + uint8_t* data; + size_t size; +} rc_libretro_core_memory_info_t; + +typedef void (RC_CCONV *rc_libretro_get_core_memory_info_func)(uint32_t id, rc_libretro_core_memory_info_t* info); + +RC_EXPORT int RC_CCONV rc_libretro_memory_init(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap, + rc_libretro_get_core_memory_info_func get_core_memory_info, uint32_t console_id); +RC_EXPORT void RC_CCONV rc_libretro_memory_destroy(rc_libretro_memory_regions_t* regions); + +RC_EXPORT uint8_t* RC_CCONV rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, uint32_t address); +RC_EXPORT uint8_t* RC_CCONV rc_libretro_memory_find_avail(const rc_libretro_memory_regions_t* regions, uint32_t address, uint32_t* avail); +RC_EXPORT uint32_t RC_CCONV rc_libretro_memory_read(const rc_libretro_memory_regions_t* regions, uint32_t address, uint8_t* buffer, uint32_t num_bytes); + +/*****************************************************************************\ +| Disk Identification | +\*****************************************************************************/ + +typedef struct rc_libretro_hash_entry_t +{ + uint32_t path_djb2; + uint32_t game_id; + char hash[33]; +} rc_libretro_hash_entry_t; + +typedef struct rc_libretro_hash_set_t +{ + struct rc_libretro_hash_entry_t* entries; + uint16_t entries_count; + uint16_t entries_size; + + rc_hash_callbacks_t callbacks; +} rc_libretro_hash_set_t; + +typedef int (RC_CCONV *rc_libretro_get_image_path_func)(uint32_t index, char* buffer, size_t buffer_size); + +RC_EXPORT void RC_CCONV rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set, + const char* m3u_path, rc_libretro_get_image_path_func get_image_path, + const rc_hash_filereader_t* file_reader); +RC_EXPORT void RC_CCONV rc_libretro_hash_set_destroy(struct rc_libretro_hash_set_t* hash_set); + +RC_EXPORT void RC_CCONV rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set, + const char* path, uint32_t game_id, const char hash[33]); +RC_EXPORT const char* RC_CCONV rc_libretro_hash_set_get_hash(const struct rc_libretro_hash_set_t* hash_set, const char* path); +RC_EXPORT int RC_CCONV rc_libretro_hash_set_get_game_id(const struct rc_libretro_hash_set_t* hash_set, const char* hash); + +RC_END_C_DECLS + +#endif /* RC_LIBRETRO_H */ diff --git a/include/rcheevos/rc_runtime.h b/include/rcheevos/rc_runtime.h index e253ecba..88b12dea 100644 --- a/include/rcheevos/rc_runtime.h +++ b/include/rcheevos/rc_runtime.h @@ -1,158 +1,148 @@ -#ifndef RC_RUNTIME_H -#define RC_RUNTIME_H - -#include "rc_error.h" - -#include -#include - -RC_BEGIN_C_DECLS - -/*****************************************************************************\ -| Forward Declarations (defined in rc_runtime_types.h) | -\*****************************************************************************/ - -#ifndef RC_RUNTIME_TYPES_H /* prevents pedantic redefinition error */ - -typedef struct lua_State lua_State; - -typedef struct rc_trigger_t rc_trigger_t; -typedef struct rc_lboard_t rc_lboard_t; -typedef struct rc_richpresence_t rc_richpresence_t; -typedef struct rc_memref_t rc_memref_t; -typedef struct rc_value_t rc_value_t; - -#endif - -/*****************************************************************************\ -| Callbacks | -\*****************************************************************************/ - -/** - * Callback used to read num_bytes bytes from memory starting at address. If - * num_bytes is greater than 1, the value is read in little-endian from - * memory. - */ -typedef uint32_t(RC_CCONV *rc_runtime_peek_t)(uint32_t address, uint32_t num_bytes, void* ud); - -/*****************************************************************************\ -| Runtime | -\*****************************************************************************/ - -typedef struct rc_runtime_trigger_t { - uint32_t id; - rc_trigger_t* trigger; - void* buffer; - rc_memref_t* invalid_memref; - uint8_t md5[16]; - int32_t serialized_size; - uint8_t owns_memrefs; -} -rc_runtime_trigger_t; - -typedef struct rc_runtime_lboard_t { - uint32_t id; - int32_t value; - rc_lboard_t* lboard; - void* buffer; - rc_memref_t* invalid_memref; - uint8_t md5[16]; - uint32_t serialized_size; - uint8_t owns_memrefs; -} -rc_runtime_lboard_t; - -typedef struct rc_runtime_richpresence_t { - rc_richpresence_t* richpresence; - void* buffer; - struct rc_runtime_richpresence_t* previous; - uint8_t md5[16]; - uint8_t owns_memrefs; -} -rc_runtime_richpresence_t; - -typedef struct rc_runtime_t { - rc_runtime_trigger_t* triggers; - uint32_t trigger_count; - uint32_t trigger_capacity; - - rc_runtime_lboard_t* lboards; - uint32_t lboard_count; - uint32_t lboard_capacity; - - rc_runtime_richpresence_t* richpresence; - - rc_memref_t* memrefs; - rc_memref_t** next_memref; - - rc_value_t* variables; - rc_value_t** next_variable; - - uint8_t owns_self; -} -rc_runtime_t; - -RC_EXPORT rc_runtime_t* RC_CCONV rc_runtime_alloc(void); -RC_EXPORT void RC_CCONV rc_runtime_init(rc_runtime_t* runtime); -RC_EXPORT void RC_CCONV rc_runtime_destroy(rc_runtime_t* runtime); - -RC_EXPORT int RC_CCONV rc_runtime_activate_achievement(rc_runtime_t* runtime, uint32_t id, const char* memaddr, lua_State* L, int funcs_idx); -RC_EXPORT void RC_CCONV rc_runtime_deactivate_achievement(rc_runtime_t* runtime, uint32_t id); -RC_EXPORT rc_trigger_t* RC_CCONV rc_runtime_get_achievement(const rc_runtime_t* runtime, uint32_t id); -RC_EXPORT int RC_CCONV rc_runtime_get_achievement_measured(const rc_runtime_t* runtime, uint32_t id, unsigned* measured_value, unsigned* measured_target); -RC_EXPORT int RC_CCONV rc_runtime_format_achievement_measured(const rc_runtime_t* runtime, uint32_t id, char *buffer, size_t buffer_size); - -RC_EXPORT int RC_CCONV rc_runtime_activate_lboard(rc_runtime_t* runtime, uint32_t id, const char* memaddr, lua_State* L, int funcs_idx); -RC_EXPORT void RC_CCONV rc_runtime_deactivate_lboard(rc_runtime_t* runtime, uint32_t id); -RC_EXPORT rc_lboard_t* RC_CCONV rc_runtime_get_lboard(const rc_runtime_t* runtime, uint32_t id); -RC_EXPORT int RC_CCONV rc_runtime_format_lboard_value(char* buffer, int size, int32_t value, int format); - - -RC_EXPORT int RC_CCONV rc_runtime_activate_richpresence(rc_runtime_t* runtime, const char* script, lua_State* L, int funcs_idx); -RC_EXPORT int RC_CCONV rc_runtime_get_richpresence(const rc_runtime_t* runtime, char* buffer, size_t buffersize, rc_runtime_peek_t peek, void* peek_ud, lua_State* L); - -enum { - RC_RUNTIME_EVENT_ACHIEVEMENT_ACTIVATED, /* from WAITING, PAUSED, or PRIMED to ACTIVE */ - RC_RUNTIME_EVENT_ACHIEVEMENT_PAUSED, - RC_RUNTIME_EVENT_ACHIEVEMENT_RESET, - RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED, - RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED, - RC_RUNTIME_EVENT_LBOARD_STARTED, - RC_RUNTIME_EVENT_LBOARD_CANCELED, - RC_RUNTIME_EVENT_LBOARD_UPDATED, - RC_RUNTIME_EVENT_LBOARD_TRIGGERED, - RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED, - RC_RUNTIME_EVENT_LBOARD_DISABLED, - RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED, - RC_RUNTIME_EVENT_ACHIEVEMENT_PROGRESS_UPDATED -}; - -typedef struct rc_runtime_event_t { - uint32_t id; - int32_t value; - uint8_t type; -} -rc_runtime_event_t; - -typedef void (RC_CCONV *rc_runtime_event_handler_t)(const rc_runtime_event_t* runtime_event); - -RC_EXPORT void RC_CCONV rc_runtime_do_frame(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_runtime_peek_t peek, void* ud, lua_State* L); -RC_EXPORT void RC_CCONV rc_runtime_reset(rc_runtime_t* runtime); - -typedef int (RC_CCONV *rc_runtime_validate_address_t)(uint32_t address); -RC_EXPORT void RC_CCONV rc_runtime_validate_addresses(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_runtime_validate_address_t validate_handler); -RC_EXPORT void RC_CCONV rc_runtime_invalidate_address(rc_runtime_t* runtime, uint32_t address); - -RC_EXPORT uint32_t RC_CCONV rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L); - -/* [deprecated] use rc_runtime_serialize_progress_sized instead */ -RC_EXPORT int RC_CCONV rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua_State* L); -RC_EXPORT int RC_CCONV rc_runtime_serialize_progress_sized(uint8_t* buffer, uint32_t buffer_size, const rc_runtime_t* runtime, lua_State* L); - -/* [deprecated] use rc_runtime_deserialize_progress_sized instead */ -RC_EXPORT int RC_CCONV rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serialized, lua_State* L); -RC_EXPORT int RC_CCONV rc_runtime_deserialize_progress_sized(rc_runtime_t* runtime, const uint8_t* serialized, uint32_t serialized_size, lua_State* L); - -RC_END_C_DECLS - -#endif /* RC_RUNTIME_H */ +#ifndef RC_RUNTIME_H +#define RC_RUNTIME_H + +#include "rc_error.h" + +#include +#include + +RC_BEGIN_C_DECLS + +/*****************************************************************************\ +| Forward Declarations (defined in rc_runtime_types.h) | +\*****************************************************************************/ + +#ifndef RC_RUNTIME_TYPES_H /* prevents pedantic redefinition error */ + +typedef struct rc_trigger_t rc_trigger_t; +typedef struct rc_lboard_t rc_lboard_t; +typedef struct rc_richpresence_t rc_richpresence_t; +typedef struct rc_memref_t rc_memref_t; +typedef struct rc_value_t rc_value_t; + +#endif + +/*****************************************************************************\ +| Callbacks | +\*****************************************************************************/ + +/** + * Callback used to read num_bytes bytes from memory starting at address. If + * num_bytes is greater than 1, the value is read in little-endian from + * memory. + */ +typedef uint32_t(RC_CCONV *rc_runtime_peek_t)(uint32_t address, uint32_t num_bytes, void* ud); + +/*****************************************************************************\ +| Runtime | +\*****************************************************************************/ + +typedef struct rc_runtime_trigger_t { + uint32_t id; + rc_trigger_t* trigger; + void* buffer; + rc_memref_t* invalid_memref; + uint8_t md5[16]; + int32_t serialized_size; +} +rc_runtime_trigger_t; + +typedef struct rc_runtime_lboard_t { + uint32_t id; + int32_t value; + rc_lboard_t* lboard; + void* buffer; + rc_memref_t* invalid_memref; + uint8_t md5[16]; + uint32_t serialized_size; +} +rc_runtime_lboard_t; + +typedef struct rc_runtime_richpresence_t { + rc_richpresence_t* richpresence; + void* buffer; + uint8_t md5[16]; +} +rc_runtime_richpresence_t; + +typedef struct rc_runtime_t { + rc_runtime_trigger_t* triggers; + uint32_t trigger_count; + uint32_t trigger_capacity; + + rc_runtime_lboard_t* lboards; + uint32_t lboard_count; + uint32_t lboard_capacity; + + rc_runtime_richpresence_t* richpresence; + + struct rc_memrefs_t* memrefs; + + uint8_t owns_self; +} +rc_runtime_t; + +RC_EXPORT rc_runtime_t* RC_CCONV rc_runtime_alloc(void); +RC_EXPORT void RC_CCONV rc_runtime_init(rc_runtime_t* runtime); +RC_EXPORT void RC_CCONV rc_runtime_destroy(rc_runtime_t* runtime); + +RC_EXPORT int RC_CCONV rc_runtime_activate_achievement(rc_runtime_t* runtime, uint32_t id, const char* memaddr, void* unused_L, int unused_funcs_idx); +RC_EXPORT void RC_CCONV rc_runtime_deactivate_achievement(rc_runtime_t* runtime, uint32_t id); +RC_EXPORT rc_trigger_t* RC_CCONV rc_runtime_get_achievement(const rc_runtime_t* runtime, uint32_t id); +RC_EXPORT int RC_CCONV rc_runtime_get_achievement_measured(const rc_runtime_t* runtime, uint32_t id, unsigned* measured_value, unsigned* measured_target); +RC_EXPORT int RC_CCONV rc_runtime_format_achievement_measured(const rc_runtime_t* runtime, uint32_t id, char *buffer, size_t buffer_size); + +RC_EXPORT int RC_CCONV rc_runtime_activate_lboard(rc_runtime_t* runtime, uint32_t id, const char* memaddr, void* unused_L, int unused_funcs_idx); +RC_EXPORT void RC_CCONV rc_runtime_deactivate_lboard(rc_runtime_t* runtime, uint32_t id); +RC_EXPORT rc_lboard_t* RC_CCONV rc_runtime_get_lboard(const rc_runtime_t* runtime, uint32_t id); +RC_EXPORT int RC_CCONV rc_runtime_format_lboard_value(char* buffer, int size, int32_t value, int format); + + +RC_EXPORT int RC_CCONV rc_runtime_activate_richpresence(rc_runtime_t* runtime, const char* script, void* unused_L, int unused_funcs_idx); +RC_EXPORT int RC_CCONV rc_runtime_get_richpresence(const rc_runtime_t* runtime, char* buffer, size_t buffersize, rc_runtime_peek_t peek, void* peek_ud, void* unused_L); + +enum { + RC_RUNTIME_EVENT_ACHIEVEMENT_ACTIVATED, /* from WAITING, PAUSED, or PRIMED to ACTIVE */ + RC_RUNTIME_EVENT_ACHIEVEMENT_PAUSED, + RC_RUNTIME_EVENT_ACHIEVEMENT_RESET, + RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED, + RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED, + RC_RUNTIME_EVENT_LBOARD_STARTED, + RC_RUNTIME_EVENT_LBOARD_CANCELED, + RC_RUNTIME_EVENT_LBOARD_UPDATED, + RC_RUNTIME_EVENT_LBOARD_TRIGGERED, + RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED, + RC_RUNTIME_EVENT_LBOARD_DISABLED, + RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED, + RC_RUNTIME_EVENT_ACHIEVEMENT_PROGRESS_UPDATED +}; + +typedef struct rc_runtime_event_t { + uint32_t id; + int32_t value; + uint8_t type; +} +rc_runtime_event_t; + +typedef void (RC_CCONV *rc_runtime_event_handler_t)(const rc_runtime_event_t* runtime_event); + +RC_EXPORT void RC_CCONV rc_runtime_do_frame(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_runtime_peek_t peek, void* ud, void* unused_L); +RC_EXPORT void RC_CCONV rc_runtime_reset(rc_runtime_t* runtime); + +typedef int (RC_CCONV *rc_runtime_validate_address_t)(uint32_t address); +RC_EXPORT void RC_CCONV rc_runtime_validate_addresses(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_runtime_validate_address_t validate_handler); +RC_EXPORT void RC_CCONV rc_runtime_invalidate_address(rc_runtime_t* runtime, uint32_t address); + +RC_EXPORT uint32_t RC_CCONV rc_runtime_progress_size(const rc_runtime_t* runtime, void* unused_L); + +/* [deprecated] use rc_runtime_serialize_progress_sized instead */ +RC_EXPORT int RC_CCONV rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, void* unused_L); +RC_EXPORT int RC_CCONV rc_runtime_serialize_progress_sized(uint8_t* buffer, uint32_t buffer_size, const rc_runtime_t* runtime, void* unused_L); + +/* [deprecated] use rc_runtime_deserialize_progress_sized instead */ +RC_EXPORT int RC_CCONV rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serialized, void* unused_L); +RC_EXPORT int RC_CCONV rc_runtime_deserialize_progress_sized(rc_runtime_t* runtime, const uint8_t* serialized, uint32_t serialized_size, void* unused_L); + +RC_END_C_DECLS + +#endif /* RC_RUNTIME_H */ diff --git a/include/rcheevos/rc_runtime_types.h b/include/rcheevos/rc_runtime_types.h index 01dfe680..e1779706 100644 --- a/include/rcheevos/rc_runtime_types.h +++ b/include/rcheevos/rc_runtime_types.h @@ -1,431 +1,452 @@ -#ifndef RC_RUNTIME_TYPES_H -#define RC_RUNTIME_TYPES_H - -#include "rc_error.h" - -#include -#include - -RC_BEGIN_C_DECLS - -#ifndef RC_RUNTIME_H /* prevents pedantic redefiniton error */ - -typedef struct lua_State lua_State; - -typedef struct rc_trigger_t rc_trigger_t; -typedef struct rc_lboard_t rc_lboard_t; -typedef struct rc_richpresence_t rc_richpresence_t; -typedef struct rc_memref_t rc_memref_t; -typedef struct rc_value_t rc_value_t; - -#endif - -/*****************************************************************************\ -| Callbacks | -\*****************************************************************************/ - -/** - * Callback used to read num_bytes bytes from memory starting at address. If - * num_bytes is greater than 1, the value is read in little-endian from - * memory. - */ -typedef uint32_t(RC_CCONV *rc_peek_t)(uint32_t address, uint32_t num_bytes, void* ud); - -/*****************************************************************************\ -| Memory References | -\*****************************************************************************/ - -/* Sizes. */ -enum { - RC_MEMSIZE_8_BITS, - RC_MEMSIZE_16_BITS, - RC_MEMSIZE_24_BITS, - RC_MEMSIZE_32_BITS, - RC_MEMSIZE_LOW, - RC_MEMSIZE_HIGH, - RC_MEMSIZE_BIT_0, - RC_MEMSIZE_BIT_1, - RC_MEMSIZE_BIT_2, - RC_MEMSIZE_BIT_3, - RC_MEMSIZE_BIT_4, - RC_MEMSIZE_BIT_5, - RC_MEMSIZE_BIT_6, - RC_MEMSIZE_BIT_7, - RC_MEMSIZE_BITCOUNT, - RC_MEMSIZE_16_BITS_BE, - RC_MEMSIZE_24_BITS_BE, - RC_MEMSIZE_32_BITS_BE, - RC_MEMSIZE_FLOAT, - RC_MEMSIZE_MBF32, - RC_MEMSIZE_MBF32_LE, - RC_MEMSIZE_FLOAT_BE, - RC_MEMSIZE_DOUBLE32, - RC_MEMSIZE_DOUBLE32_BE, - RC_MEMSIZE_VARIABLE -}; - -typedef struct rc_memref_value_t { - /* The current value of this memory reference. */ - uint32_t value; - /* The last differing value of this memory reference. */ - uint32_t prior; - - /* The size of the value. */ - uint8_t size; - /* True if the value changed this frame. */ - uint8_t changed; - /* The value type of the value (for variables) */ - uint8_t type; - /* True if the reference will be used in indirection. - * NOTE: This is actually a property of the rc_memref_t, but we put it here to save space */ - uint8_t is_indirect; -} -rc_memref_value_t; - -struct rc_memref_t { - /* The current value at the specified memory address. */ - rc_memref_value_t value; - - /* The memory address of this variable. */ - uint32_t address; - - /* The next memory reference in the chain. */ - rc_memref_t* next; -}; - -/*****************************************************************************\ -| Operands | -\*****************************************************************************/ - -/* types */ -enum { - RC_OPERAND_ADDRESS, /* The value of a live address in RAM. */ - RC_OPERAND_DELTA, /* The value last known at this address. */ - RC_OPERAND_CONST, /* A 32-bit unsigned integer. */ - RC_OPERAND_FP, /* A floating point value. */ - RC_OPERAND_LUA, /* A Lua function that provides the value. */ - RC_OPERAND_PRIOR, /* The last differing value at this address. */ - RC_OPERAND_BCD, /* The BCD-decoded value of a live address in RAM. */ - RC_OPERAND_INVERTED /* The twos-complement value of a live address in RAM. */ -}; - -typedef struct rc_operand_t { - union { - /* A value read from memory. */ - rc_memref_t* memref; - - /* An integer value. */ - uint32_t num; - - /* A floating point value. */ - double dbl; - - /* A reference to the Lua function that provides the value. */ - int luafunc; - } value; - - /* specifies which member of the value union is being used */ - uint8_t type; - - /* the actual RC_MEMSIZE of the operand - memref.size may differ */ - uint8_t size; -} -rc_operand_t; - -RC_EXPORT int RC_CCONV rc_operand_is_memref(const rc_operand_t* operand); - -/*****************************************************************************\ -| Conditions | -\*****************************************************************************/ - -/* types */ -enum { - /* NOTE: this enum is ordered to optimize the switch statements in rc_test_condset_internal. the values may change between releases */ - - /* non-combining conditions (third switch) */ - RC_CONDITION_STANDARD, /* this should always be 0 */ - RC_CONDITION_PAUSE_IF, - RC_CONDITION_RESET_IF, - RC_CONDITION_MEASURED_IF, - RC_CONDITION_TRIGGER, - RC_CONDITION_MEASURED, /* measured also appears in the first switch, so place it at the border between them */ - - /* modifiers (first switch) */ - RC_CONDITION_ADD_SOURCE, /* everything from this point on affects the condition after it */ - RC_CONDITION_SUB_SOURCE, - RC_CONDITION_ADD_ADDRESS, - - /* logic flags (second switch) */ - RC_CONDITION_ADD_HITS, - RC_CONDITION_SUB_HITS, - RC_CONDITION_RESET_NEXT_IF, - RC_CONDITION_AND_NEXT, - RC_CONDITION_OR_NEXT -}; - -/* operators */ -enum { - RC_OPERATOR_EQ, - RC_OPERATOR_LT, - RC_OPERATOR_LE, - RC_OPERATOR_GT, - RC_OPERATOR_GE, - RC_OPERATOR_NE, - RC_OPERATOR_NONE, - RC_OPERATOR_MULT, - RC_OPERATOR_DIV, - RC_OPERATOR_AND, - RC_OPERATOR_XOR -}; - -typedef struct rc_condition_t rc_condition_t; - -struct rc_condition_t { - /* The condition's operands. */ - rc_operand_t operand1; - rc_operand_t operand2; - - /* Required hits to fire this condition. */ - uint32_t required_hits; - /* Number of hits so far. */ - uint32_t current_hits; - - /* The next condition in the chain. */ - rc_condition_t* next; - - /* The type of the condition. (RC_CONDITION_*) */ - uint8_t type; - - /* The comparison operator to use. (RC_OPERATOR_*) */ - uint8_t oper; /* operator is a reserved word in C++. */ - - /* Set if the condition needs to processed as part of the "check if paused" pass. (bool) */ - uint8_t pause; - - /* Whether or not the condition evaluated true on the last check. (bool) */ - uint8_t is_true; - - /* Unique identifier of optimized comparator to use. (RC_PROCESSING_COMPARE_*) */ - uint8_t optimized_comparator; -}; - -/*****************************************************************************\ -| Condition sets | -\*****************************************************************************/ - -typedef struct rc_condset_t rc_condset_t; - -struct rc_condset_t { - /* The next condition set in the chain. */ - rc_condset_t* next; - - /* The list of conditions in this condition set. */ - rc_condition_t* conditions; - - /* True if any condition in the set is a pause condition. */ - uint8_t has_pause; - - /* True if the set is currently paused. */ - uint8_t is_paused; - - /* True if the set has indirect memory references. */ - uint8_t has_indirect_memrefs; -}; - -/*****************************************************************************\ -| Trigger | -\*****************************************************************************/ - -enum { - RC_TRIGGER_STATE_INACTIVE, /* achievement is not being processed */ - RC_TRIGGER_STATE_WAITING, /* achievement cannot trigger until it has been false for at least one frame */ - RC_TRIGGER_STATE_ACTIVE, /* achievement is active and may trigger */ - RC_TRIGGER_STATE_PAUSED, /* achievement is currently paused and will not trigger */ - RC_TRIGGER_STATE_RESET, /* achievement hit counts were reset */ - RC_TRIGGER_STATE_TRIGGERED, /* achievement has triggered */ - RC_TRIGGER_STATE_PRIMED, /* all non-Trigger conditions are true */ - RC_TRIGGER_STATE_DISABLED /* achievement cannot be processed at this time */ -}; - -struct rc_trigger_t { - /* The main condition set. */ - rc_condset_t* requirement; - - /* The list of sub condition sets in this test. */ - rc_condset_t* alternative; - - /* The memory references required by the trigger. */ - rc_memref_t* memrefs; - - /* The current state of the MEASURED condition. */ - uint32_t measured_value; - - /* The target state of the MEASURED condition */ - uint32_t measured_target; - - /* The current state of the trigger */ - uint8_t state; - - /* True if at least one condition has a non-zero hit count */ - uint8_t has_hits; - - /* True if at least one condition has a non-zero required hit count */ - uint8_t has_required_hits; - - /* True if the measured value should be displayed as a percentage */ - uint8_t measured_as_percent; -}; - -RC_EXPORT int RC_CCONV rc_trigger_size(const char* memaddr); -RC_EXPORT rc_trigger_t* RC_CCONV rc_parse_trigger(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx); -RC_EXPORT int RC_CCONV rc_evaluate_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L); -RC_EXPORT int RC_CCONV rc_test_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L); -RC_EXPORT void RC_CCONV rc_reset_trigger(rc_trigger_t* self); - -/*****************************************************************************\ -| Values | -\*****************************************************************************/ - -struct rc_value_t { - /* The current value of the variable. */ - rc_memref_value_t value; - - /* The list of conditions to evaluate. */ - rc_condset_t* conditions; - - /* The memory references required by the variable. */ - rc_memref_t* memrefs; - - /* The name of the variable. */ - const char* name; - - /* The next variable in the chain. */ - rc_value_t* next; -}; - -RC_EXPORT int RC_CCONV rc_value_size(const char* memaddr); -RC_EXPORT rc_value_t* RC_CCONV rc_parse_value(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx); -RC_EXPORT int32_t RC_CCONV rc_evaluate_value(rc_value_t* value, rc_peek_t peek, void* ud, lua_State* L); - -/*****************************************************************************\ -| Leaderboards | -\*****************************************************************************/ - -/* Return values for rc_evaluate_lboard. */ -enum { - RC_LBOARD_STATE_INACTIVE, /* leaderboard is not being processed */ - RC_LBOARD_STATE_WAITING, /* leaderboard cannot activate until the start condition has been false for at least one frame */ - RC_LBOARD_STATE_ACTIVE, /* leaderboard is active and may start */ - RC_LBOARD_STATE_STARTED, /* leaderboard attempt in progress */ - RC_LBOARD_STATE_CANCELED, /* leaderboard attempt canceled */ - RC_LBOARD_STATE_TRIGGERED, /* leaderboard attempt complete, value should be submitted */ - RC_LBOARD_STATE_DISABLED /* leaderboard cannot be processed at this time */ -}; - -struct rc_lboard_t { - rc_trigger_t start; - rc_trigger_t submit; - rc_trigger_t cancel; - rc_value_t value; - rc_value_t* progress; - rc_memref_t* memrefs; - - uint8_t state; -}; - -RC_EXPORT int RC_CCONV rc_lboard_size(const char* memaddr); -RC_EXPORT rc_lboard_t* RC_CCONV rc_parse_lboard(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx); -RC_EXPORT int RC_CCONV rc_evaluate_lboard(rc_lboard_t* lboard, int32_t* value, rc_peek_t peek, void* peek_ud, lua_State* L); -RC_EXPORT void RC_CCONV rc_reset_lboard(rc_lboard_t* lboard); - -/*****************************************************************************\ -| Value formatting | -\*****************************************************************************/ - -/* Supported formats. */ -enum { - RC_FORMAT_FRAMES, - RC_FORMAT_SECONDS, - RC_FORMAT_CENTISECS, - RC_FORMAT_SCORE, - RC_FORMAT_VALUE, - RC_FORMAT_MINUTES, - RC_FORMAT_SECONDS_AS_MINUTES, - RC_FORMAT_FLOAT1, - RC_FORMAT_FLOAT2, - RC_FORMAT_FLOAT3, - RC_FORMAT_FLOAT4, - RC_FORMAT_FLOAT5, - RC_FORMAT_FLOAT6, - RC_FORMAT_FIXED1, - RC_FORMAT_FIXED2, - RC_FORMAT_FIXED3, - RC_FORMAT_TENS, - RC_FORMAT_HUNDREDS, - RC_FORMAT_THOUSANDS, - RC_FORMAT_UNSIGNED_VALUE -}; - -RC_EXPORT int RC_CCONV rc_parse_format(const char* format_str); -RC_EXPORT int RC_CCONV rc_format_value(char* buffer, int size, int32_t value, int format); - -/*****************************************************************************\ -| Rich Presence | -\*****************************************************************************/ - -typedef struct rc_richpresence_lookup_item_t rc_richpresence_lookup_item_t; - -struct rc_richpresence_lookup_item_t { - uint32_t first; - uint32_t last; - rc_richpresence_lookup_item_t* left; - rc_richpresence_lookup_item_t* right; - const char* label; -}; - -typedef struct rc_richpresence_lookup_t rc_richpresence_lookup_t; - -struct rc_richpresence_lookup_t { - rc_richpresence_lookup_item_t* root; - rc_richpresence_lookup_t* next; - const char* name; - const char* default_label; - uint8_t format; -}; - -typedef struct rc_richpresence_display_part_t rc_richpresence_display_part_t; - -struct rc_richpresence_display_part_t { - rc_richpresence_display_part_t* next; - const char* text; - rc_richpresence_lookup_t* lookup; - rc_memref_value_t *value; - uint8_t display_type; -}; - -typedef struct rc_richpresence_display_t rc_richpresence_display_t; - -struct rc_richpresence_display_t { - rc_trigger_t trigger; - rc_richpresence_display_t* next; - rc_richpresence_display_part_t* display; -}; - -struct rc_richpresence_t { - rc_richpresence_display_t* first_display; - rc_richpresence_lookup_t* first_lookup; - rc_memref_t* memrefs; - rc_value_t* variables; -}; - -RC_EXPORT int RC_CCONV rc_richpresence_size(const char* script); -RC_EXPORT int RC_CCONV rc_richpresence_size_lines(const char* script, int* lines_read); -RC_EXPORT rc_richpresence_t* RC_CCONV rc_parse_richpresence(void* buffer, const char* script, lua_State* L, int funcs_ndx); -RC_EXPORT int RC_CCONV rc_evaluate_richpresence(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, lua_State* L); -RC_EXPORT void RC_CCONV rc_update_richpresence(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud, lua_State* L); -RC_EXPORT int RC_CCONV rc_get_richpresence_display_string(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, lua_State* L); -RC_EXPORT void RC_CCONV rc_reset_richpresence(rc_richpresence_t* self); - -RC_END_C_DECLS - -#endif /* RC_RUNTIME_TYPES_H */ +#ifndef RC_RUNTIME_TYPES_H +#define RC_RUNTIME_TYPES_H + +#include "rc_error.h" + +#include +#include + +RC_BEGIN_C_DECLS + +#ifndef RC_RUNTIME_H /* prevents pedantic redefiniton error */ + +typedef struct rc_trigger_t rc_trigger_t; +typedef struct rc_lboard_t rc_lboard_t; +typedef struct rc_richpresence_t rc_richpresence_t; +typedef struct rc_memref_t rc_memref_t; +typedef struct rc_value_t rc_value_t; + +#endif + +/*****************************************************************************\ +| Callbacks | +\*****************************************************************************/ + +/** + * Callback used to read num_bytes bytes from memory starting at address. If + * num_bytes is greater than 1, the value is read in little-endian from + * memory. + */ +typedef uint32_t(RC_CCONV* rc_peek_t)(uint32_t address, uint32_t num_bytes, void* ud); + +/*****************************************************************************\ +| Memory References | +\*****************************************************************************/ + +/* Sizes. */ +enum { + RC_MEMSIZE_8_BITS, + RC_MEMSIZE_16_BITS, + RC_MEMSIZE_24_BITS, + RC_MEMSIZE_32_BITS, + RC_MEMSIZE_LOW, + RC_MEMSIZE_HIGH, + RC_MEMSIZE_BIT_0, + RC_MEMSIZE_BIT_1, + RC_MEMSIZE_BIT_2, + RC_MEMSIZE_BIT_3, + RC_MEMSIZE_BIT_4, + RC_MEMSIZE_BIT_5, + RC_MEMSIZE_BIT_6, + RC_MEMSIZE_BIT_7, + RC_MEMSIZE_BITCOUNT, + RC_MEMSIZE_16_BITS_BE, + RC_MEMSIZE_24_BITS_BE, + RC_MEMSIZE_32_BITS_BE, + RC_MEMSIZE_FLOAT, + RC_MEMSIZE_MBF32, + RC_MEMSIZE_MBF32_LE, + RC_MEMSIZE_FLOAT_BE, + RC_MEMSIZE_DOUBLE32, + RC_MEMSIZE_DOUBLE32_BE, + RC_MEMSIZE_VARIABLE +}; + +typedef struct rc_memref_value_t { + /* The current value of this memory reference. */ + uint32_t value; + /* The last differing value of this memory reference. */ + uint32_t prior; + + /* The size of the value. (RC_MEMSIZE_*) */ + uint8_t size; + /* True if the value changed this frame. */ + uint8_t changed; + /* The value type of the value. (RC_VALUE_TYPE_*) */ + uint8_t type; + /* The type of memref (RC_MEMREF_TYPE_*) */ + uint8_t memref_type; +} +rc_memref_value_t; + +struct rc_memref_t { + /* The current value at the specified memory address. */ + rc_memref_value_t value; + + /* The memory address of this variable. */ + uint32_t address; +}; + +/*****************************************************************************\ +| Operands | +\*****************************************************************************/ + +/* types */ +enum { + RC_OPERAND_ADDRESS, /* The value of a live address in RAM. */ + RC_OPERAND_DELTA, /* The value last known at this address. */ + RC_OPERAND_CONST, /* A 32-bit unsigned integer. */ + RC_OPERAND_FP, /* A floating point value. */ + RC_OPERAND_FUNC, /* A function that provides the value. */ + RC_OPERAND_PRIOR, /* The last differing value at this address. */ + RC_OPERAND_BCD, /* The BCD-decoded value of a live address in RAM. */ + RC_OPERAND_INVERTED, /* The twos-complement value of a live address in RAM. */ + RC_OPERAND_RECALL /* The value captured by the last RC_CONDITION_REMEMBER condition */ +}; + +typedef struct rc_operand_t { + union { + /* A value read from memory. */ + rc_memref_t* memref; + + /* An integer value. */ + uint32_t num; + + /* A floating point value. */ + double dbl; + } value; + + /* specifies which member of the value union is being used (RC_OPERAND_*) */ + uint8_t type; + + /* the RC_MEMSIZE of the operand specified in the condition definition - memref.size may differ */ + uint8_t size; + + /* specifies how to read the memref for some types (RC_OPERAND_*) */ + uint8_t memref_access_type; + + /* if set, this operand is combining the current condition with the previous one */ + uint8_t is_combining; +} +rc_operand_t; + +RC_EXPORT int RC_CCONV rc_operand_is_memref(const rc_operand_t* operand); + +/*****************************************************************************\ +| Conditions | +\*****************************************************************************/ + +/* types */ +enum { + RC_CONDITION_STANDARD, /* this should always be 0 */ + RC_CONDITION_PAUSE_IF, + RC_CONDITION_RESET_IF, + RC_CONDITION_MEASURED_IF, + RC_CONDITION_TRIGGER, + RC_CONDITION_MEASURED, + RC_CONDITION_ADD_SOURCE, + RC_CONDITION_SUB_SOURCE, + RC_CONDITION_ADD_ADDRESS, + RC_CONDITION_REMEMBER, + RC_CONDITION_ADD_HITS, + RC_CONDITION_SUB_HITS, + RC_CONDITION_RESET_NEXT_IF, + RC_CONDITION_AND_NEXT, + RC_CONDITION_OR_NEXT +}; + +/* operators */ +enum { + RC_OPERATOR_EQ, + RC_OPERATOR_LT, + RC_OPERATOR_LE, + RC_OPERATOR_GT, + RC_OPERATOR_GE, + RC_OPERATOR_NE, + RC_OPERATOR_NONE, + RC_OPERATOR_MULT, + RC_OPERATOR_DIV, + RC_OPERATOR_AND, + RC_OPERATOR_XOR, + RC_OPERATOR_MOD, + RC_OPERATOR_ADD, + RC_OPERATOR_SUB, + + RC_OPERATOR_SUB_PARENT, /* internal use */ + RC_OPERATOR_ADD_ACCUMULATOR, /* internal use */ + RC_OPERATOR_SUB_ACCUMULATOR, /* internal use */ + RC_OPERATOR_INDIRECT_READ /* internal use */ +}; + +typedef struct rc_condition_t rc_condition_t; + +struct rc_condition_t { + /* The condition's operands. */ + rc_operand_t operand1; + rc_operand_t operand2; + + /* Required hits to fire this condition. */ + uint32_t required_hits; + /* Number of hits so far. */ + uint32_t current_hits; + + /* The next condition in the chain. */ + rc_condition_t* next; + + /* The type of the condition. (RC_CONDITION_*) */ + uint8_t type; + + /* The comparison operator to use. (RC_OPERATOR_*) */ + uint8_t oper; /* operator is a reserved word in C++. */ + + /* Will be non-zero if the condition evaluated true on the last check. + * - The lowest bit indicates whether the condition itself was true. + * - The second lowest bit will only ever be set on ResetIf conditions. + * If set, it indicates that the condition was responsible for resetting the + * trigger. A reset clears all hit counts, so the condition may not appear to + * be true just from looking at it (in which case the lower bit will be 0). + * Also, the condition might have only met its required_hits target though + * an AddHits chain which will have also been reset. + */ + uint8_t is_true; + + /* Unique identifier of optimized comparator to use. (RC_PROCESSING_COMPARE_*) */ + uint8_t optimized_comparator; +}; + +/*****************************************************************************\ +| Condition sets | +\*****************************************************************************/ + +typedef struct rc_condset_t rc_condset_t; + +struct rc_condset_t { + /* The next condition set in the chain. */ + rc_condset_t* next; + + /* The first condition in this condition set. Then follow ->next chain. */ + rc_condition_t* conditions; + + /* The number of pause conditions in this condition set. */ + /* The first pause condition is at "this + RC_ALIGN(sizeof(this)). */ + uint16_t num_pause_conditions; + + /* The number of reset conditions in this condition set. */ + uint16_t num_reset_conditions; + + /* The number of hittarget conditions in this condition set. */ + uint16_t num_hittarget_conditions; + + /* The number of non-hittarget measured conditions in this condition set. */ + uint16_t num_measured_conditions; + + /* The number of other conditions in this condition set. */ + uint16_t num_other_conditions; + + /* The number of indirect conditions in this condition set. */ + uint16_t num_indirect_conditions; + + /* True if any condition in the set is a pause condition. */ + uint8_t has_pause; /* DEPRECATED - just check num_pause_conditions != 0 */ + /* True if the set is currently paused. */ + uint8_t is_paused; +}; + +/*****************************************************************************\ +| Trigger | +\*****************************************************************************/ + +enum { + RC_TRIGGER_STATE_INACTIVE, /* achievement is not being processed */ + RC_TRIGGER_STATE_WAITING, /* achievement cannot trigger until it has been false for at least one frame */ + RC_TRIGGER_STATE_ACTIVE, /* achievement is active and may trigger */ + RC_TRIGGER_STATE_PAUSED, /* achievement is currently paused and will not trigger */ + RC_TRIGGER_STATE_RESET, /* achievement hit counts were reset */ + RC_TRIGGER_STATE_TRIGGERED, /* achievement has triggered */ + RC_TRIGGER_STATE_PRIMED, /* all non-Trigger conditions are true */ + RC_TRIGGER_STATE_DISABLED /* achievement cannot be processed at this time */ +}; + +struct rc_trigger_t { + /* The main condition set. */ + rc_condset_t* requirement; + + /* The list of sub condition sets in this test. */ + rc_condset_t* alternative; + + /* The current state of the MEASURED condition. */ + uint32_t measured_value; + + /* The target state of the MEASURED condition */ + uint32_t measured_target; + + /* The current state of the trigger */ + uint8_t state; + + /* True if at least one condition has a non-zero hit count */ + uint8_t has_hits; + + /* True if the measured value should be displayed as a percentage */ + uint8_t measured_as_percent; + + /* True if the trigger has its own rc_memrefs_t */ + uint8_t has_memrefs; +}; + +RC_EXPORT int RC_CCONV rc_trigger_size(const char* memaddr); +RC_EXPORT rc_trigger_t* RC_CCONV rc_parse_trigger(void* buffer, const char* memaddr, void* unused_L, int unused_funcs_idx); +RC_EXPORT int RC_CCONV rc_evaluate_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, void* unused_L); +RC_EXPORT int RC_CCONV rc_test_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, void* unused_L); +RC_EXPORT void RC_CCONV rc_reset_trigger(rc_trigger_t* self); + +/*****************************************************************************\ +| Values | +\*****************************************************************************/ + +#define RC_VALUE_MAX_NAME_LENGTH 15 + +struct rc_value_t { + /* The current value of the variable. */ + rc_memref_value_t value; + + /* True if the value has its own rc_memrefs_t */ + uint8_t has_memrefs; + + /* The list of possible values (traverse next chain, pick max). */ + rc_condset_t* conditions; + + /* The name of the variable. */ + const char* name; + + /* The next variable in the chain. */ + rc_value_t* next; +}; + +RC_EXPORT int RC_CCONV rc_value_size(const char* memaddr); +RC_EXPORT rc_value_t* RC_CCONV rc_parse_value(void* buffer, const char* memaddr, void* unused_L, int unused_funcs_idx); +RC_EXPORT int32_t RC_CCONV rc_evaluate_value(rc_value_t* value, rc_peek_t peek, void* ud, void* unused_L); + +/*****************************************************************************\ +| Leaderboards | +\*****************************************************************************/ + +/* Return values for rc_evaluate_lboard. */ +enum { + RC_LBOARD_STATE_INACTIVE, /* leaderboard is not being processed */ + RC_LBOARD_STATE_WAITING, /* leaderboard cannot activate until the start condition has been false for at least one frame */ + RC_LBOARD_STATE_ACTIVE, /* leaderboard is active and may start */ + RC_LBOARD_STATE_STARTED, /* leaderboard attempt in progress */ + RC_LBOARD_STATE_CANCELED, /* leaderboard attempt canceled */ + RC_LBOARD_STATE_TRIGGERED, /* leaderboard attempt complete, value should be submitted */ + RC_LBOARD_STATE_DISABLED /* leaderboard cannot be processed at this time */ +}; + +struct rc_lboard_t { + rc_trigger_t start; + rc_trigger_t submit; + rc_trigger_t cancel; + rc_value_t value; + rc_value_t* progress; + + uint8_t state; + uint8_t has_memrefs; +}; + +RC_EXPORT int RC_CCONV rc_lboard_size(const char* memaddr); +RC_EXPORT rc_lboard_t* RC_CCONV rc_parse_lboard(void* buffer, const char* memaddr, void* unused_L, int unused_funcs_idx); +RC_EXPORT int RC_CCONV rc_evaluate_lboard(rc_lboard_t* lboard, int32_t* value, rc_peek_t peek, void* peek_ud, void* unused_L); +RC_EXPORT void RC_CCONV rc_reset_lboard(rc_lboard_t* lboard); + +/*****************************************************************************\ +| Value formatting | +\*****************************************************************************/ + +/* Supported formats. */ +enum { + RC_FORMAT_FRAMES, + RC_FORMAT_SECONDS, + RC_FORMAT_CENTISECS, + RC_FORMAT_SCORE, + RC_FORMAT_VALUE, + RC_FORMAT_MINUTES, + RC_FORMAT_SECONDS_AS_MINUTES, + RC_FORMAT_FLOAT1, + RC_FORMAT_FLOAT2, + RC_FORMAT_FLOAT3, + RC_FORMAT_FLOAT4, + RC_FORMAT_FLOAT5, + RC_FORMAT_FLOAT6, + RC_FORMAT_FIXED1, + RC_FORMAT_FIXED2, + RC_FORMAT_FIXED3, + RC_FORMAT_TENS, + RC_FORMAT_HUNDREDS, + RC_FORMAT_THOUSANDS, + RC_FORMAT_UNSIGNED_VALUE, + RC_FORMAT_UNFORMATTED +}; + +RC_EXPORT int RC_CCONV rc_parse_format(const char* format_str); +RC_EXPORT int RC_CCONV rc_format_value(char* buffer, int size, int32_t value, int format); + +/*****************************************************************************\ +| Rich Presence | +\*****************************************************************************/ + +typedef struct rc_richpresence_lookup_item_t rc_richpresence_lookup_item_t; + +struct rc_richpresence_lookup_item_t { + uint32_t first; + uint32_t last; + rc_richpresence_lookup_item_t* left; + rc_richpresence_lookup_item_t* right; + const char* label; +}; + +typedef struct rc_richpresence_lookup_t rc_richpresence_lookup_t; + +struct rc_richpresence_lookup_t { + rc_richpresence_lookup_item_t* root; + rc_richpresence_lookup_t* next; + const char* name; + const char* default_label; + uint8_t format; +}; + +typedef struct rc_richpresence_display_part_t rc_richpresence_display_part_t; + +struct rc_richpresence_display_part_t { + rc_richpresence_display_part_t* next; + const char* text; + rc_richpresence_lookup_t* lookup; + rc_operand_t value; + uint8_t display_type; +}; + +typedef struct rc_richpresence_display_t rc_richpresence_display_t; + +struct rc_richpresence_display_t { + rc_trigger_t trigger; + rc_richpresence_display_t* next; + rc_richpresence_display_part_t* display; + uint8_t has_required_hits; +}; + +struct rc_richpresence_t { + rc_richpresence_display_t* first_display; + rc_richpresence_lookup_t* first_lookup; + rc_value_t* values; + uint8_t has_memrefs; +}; + +RC_EXPORT int RC_CCONV rc_richpresence_size(const char* script); +RC_EXPORT int RC_CCONV rc_richpresence_size_lines(const char* script, int* lines_read); +RC_EXPORT rc_richpresence_t* RC_CCONV rc_parse_richpresence(void* buffer, const char* script, void* unused_L, int unused_funcs_idx); +RC_EXPORT int RC_CCONV rc_evaluate_richpresence(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, void* unused_L); +RC_EXPORT void RC_CCONV rc_update_richpresence(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud, void* unused_L); +RC_EXPORT int RC_CCONV rc_get_richpresence_display_string(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, void* unused_L); +RC_EXPORT void RC_CCONV rc_reset_richpresence(rc_richpresence_t* self); + +RC_END_C_DECLS + +#endif /* RC_RUNTIME_TYPES_H */ diff --git a/include/rcheevos/rc_util.h b/include/rcheevos/rc_util.h index 0cfe1aa4..18d27e0f 100644 --- a/include/rcheevos/rc_util.h +++ b/include/rcheevos/rc_util.h @@ -1,51 +1,51 @@ -#ifndef RC_UTIL_H -#define RC_UTIL_H - -#include "rc_export.h" - -#include -#include - -RC_BEGIN_C_DECLS - -/** - * A block of memory for variable length data (like strings and arrays). - */ -typedef struct rc_buffer_chunk_t { - /* The current location where data is being written */ - uint8_t* write; - /* The first byte past the end of data where writing cannot occur */ - uint8_t* end; - /* The first byte of the data */ - uint8_t* start; - /* The next block in the allocated memory chain */ - struct rc_buffer_chunk_t* next; -} -rc_buffer_chunk_t; - -/** - * A preallocated block of memory for variable length data (like strings and arrays). - */ -typedef struct rc_buffer_t { - /* The chunk data (will point at the local data member) */ - struct rc_buffer_chunk_t chunk; - /* Small chunk of memory pre-allocated for the chunk */ - uint8_t data[256]; -} -rc_buffer_t; - -void rc_buffer_init(rc_buffer_t* buffer); -void rc_buffer_destroy(rc_buffer_t* buffer); -uint8_t* rc_buffer_reserve(rc_buffer_t* buffer, size_t amount); -void rc_buffer_consume(rc_buffer_t* buffer, const uint8_t* start, uint8_t* end); -void* rc_buffer_alloc(rc_buffer_t* buffer, size_t amount); -char* rc_buffer_strcpy(rc_buffer_t* buffer, const char* src); -char* rc_buffer_strncpy(rc_buffer_t* buffer, const char* src, size_t len); - -uint32_t rc_djb2(const char* input); - -void rc_format_md5(char checksum[33], const uint8_t digest[16]); - -RC_END_C_DECLS - -#endif /* RC_UTIL_H */ +#ifndef RC_UTIL_H +#define RC_UTIL_H + +#include "rc_export.h" + +#include +#include + +RC_BEGIN_C_DECLS + +/** + * A block of memory for variable length data (like strings and arrays). + */ +typedef struct rc_buffer_chunk_t { + /* The current location where data is being written */ + uint8_t* write; + /* The first byte past the end of data where writing cannot occur */ + uint8_t* end; + /* The first byte of the data */ + uint8_t* start; + /* The next block in the allocated memory chain */ + struct rc_buffer_chunk_t* next; +} +rc_buffer_chunk_t; + +/** + * A preallocated block of memory for variable length data (like strings and arrays). + */ +typedef struct rc_buffer_t { + /* The chunk data (will point at the local data member) */ + struct rc_buffer_chunk_t chunk; + /* Small chunk of memory pre-allocated for the chunk */ + uint8_t data[256]; +} +rc_buffer_t; + +void rc_buffer_init(rc_buffer_t* buffer); +void rc_buffer_destroy(rc_buffer_t* buffer); +uint8_t* rc_buffer_reserve(rc_buffer_t* buffer, size_t amount); +void rc_buffer_consume(rc_buffer_t* buffer, const uint8_t* start, uint8_t* end); +void* rc_buffer_alloc(rc_buffer_t* buffer, size_t amount); +char* rc_buffer_strcpy(rc_buffer_t* buffer, const char* src); +char* rc_buffer_strncpy(rc_buffer_t* buffer, const char* src, size_t len); + +uint32_t rc_djb2(const char* input); + +void rc_format_md5(char checksum[33], const uint8_t digest[16]); + +RC_END_C_DECLS + +#endif /* RC_UTIL_H */ diff --git a/include/rcheevos/rcheevos.h b/include/rcheevos/rcheevos.h index c7e0cec6..0d960021 100644 --- a/include/rcheevos/rcheevos.h +++ b/include/rcheevos/rcheevos.h @@ -1,8 +1,8 @@ -#ifndef RCHEEVOS_H -#define RCHEEVOS_H - -#include "rc_runtime.h" -#include "rc_runtime_types.h" -#include "rc_consoles.h" - -#endif /* RCHEEVOS_H */ +#ifndef RCHEEVOS_H +#define RCHEEVOS_H + +#include "rc_runtime.h" +#include "rc_runtime_types.h" +#include "rc_consoles.h" + +#endif /* RCHEEVOS_H */ diff --git a/qml/Main3.qml b/qml/Main3.qml index 21a514cc..21bfdc45 100644 --- a/qml/Main3.qml +++ b/qml/Main3.qml @@ -180,13 +180,12 @@ ApplicationWindow { gameRunning: EmulationService.isGameRunning } - Component { + + LibraryPage { id: allGamesPage - LibraryPage { - currentEntryId: EmulationService.currentEntryId - onStartGame: function (entryId) { - window.startGame(entryId) - } + currentEntryId: EmulationService.currentEntryId + onStartGame: function (entryId) { + window.startGame(entryId) } } diff --git a/src/app/achievements/achievement_repository.hpp b/src/app/achievements/achievement_repository.hpp index a90251fa..a2d9f61b 100644 --- a/src/app/achievements/achievement_repository.hpp +++ b/src/app/achievements/achievement_repository.hpp @@ -38,7 +38,8 @@ class IAchievementRepository { * @param username The username to retrieve data for * @return User data if found, std::nullopt otherwise */ - [[nodiscard]] virtual std::optional getUser(const std::string &username) const = 0; + [[nodiscard]] virtual std::optional + getUser(const std::string &username) const = 0; /** * @brief Retrieves all users from the repository @@ -116,11 +117,11 @@ class IAchievementRepository { * ordered by their display order. Only achievements with active flags are * included. * - * @param setId The unique identifier of the achievement set + * @param gameId The unique identifier of the achievement set * @return The achievement set if found, std::nullopt otherwise */ [[nodiscard]] virtual std::optional - getAchievementSet(unsigned setId) const = 0; + getAchievementSet(unsigned gameId) const = 0; /** * @brief Retrieves an achievement set by content hash @@ -162,23 +163,29 @@ class IAchievementRepository { * - Debugging hash mapping configurations * - Content verification and validation workflows * - Implementing cache invalidation based on content changes - * - Supporting multi-hash scenarios where games may have multiple content variants + * - Supporting multi-hash scenarios where games may have multiple content + * variants * - * @param setId The achievement set ID to look up the associated content hash for + * @param gameId The achievement set ID to look up the associated content hash + * for * @return The content hash string if a mapping exists, std::nullopt if no * mapping is found or on database error * * @note This method performs the inverse operation of getGameId() - they form * a bidirectional lookup pair - * @note If an achievement set has no content hash mapping, std::nullopt is returned - * @note Database errors are handled gracefully with std::nullopt return values - * @note The returned hash string may be empty if an empty hash was explicitly mapped + * @note If an achievement set has no content hash mapping, std::nullopt is + * returned + * @note Database errors are handled gracefully with std::nullopt return + * values + * @note The returned hash string may be empty if an empty hash was explicitly + * mapped * * @see getGameId() for the forward lookup (hash -> set ID) * @see setGameId() to create or update hash mappings * @see getAchievementSetByContentHash() to retrieve achievement sets by hash */ - [[nodiscard]] virtual std::optional getGameHash(unsigned setId) const = 0; + [[nodiscard]] virtual std::optional + getGameHash(unsigned gameId) const = 0; // Individual Achievement Operations @@ -232,10 +239,10 @@ class IAchievementRepository { * new set ID. * * @param contentHash The game's content hash - * @param setId The achievement set ID to associate with the hash + * @param gameId The achievement set ID to associate with the hash * @return true if the mapping was created successfully, false otherwise */ - virtual bool setGameId(const std::string &contentHash, int setId) = 0; + virtual bool setGameId(const std::string &contentHash, int gameId) = 0; // User Unlock Operations @@ -273,11 +280,11 @@ class IAchievementRepository { * for displaying a user's progress through all achievements in a game. * * @param username The username to query unlock records for - * @param setId The achievement set ID to filter unlocks by + * @param gameId The achievement set ID to filter unlocks by * @return Vector of UserUnlock records, empty if none found or on error */ - [[nodiscard]] virtual std::vector getAllUserUnlocks(const std::string &username, - unsigned setId) const = 0; + [[nodiscard]] virtual std::vector + getAllUserUnlocks(const std::string &username, unsigned gameId) const = 0; /** * @brief Retrieves all unsynced user unlock records for a specific user @@ -326,7 +333,8 @@ class IAchievementRepository { * @param gameId The game ID to retrieve patch data for * @return The cached patch response if found, std::nullopt otherwise */ - [[nodiscard]] virtual std::optional getPatchResponse(int gameId) const = 0; + [[nodiscard]] virtual std::optional + getPatchResponse(int gameId) const = 0; }; } // namespace firelight::achievements \ No newline at end of file diff --git a/src/app/achievements/achievement_service.cpp b/src/app/achievements/achievement_service.cpp index a94df282..34836600 100644 --- a/src/app/achievements/achievement_service.cpp +++ b/src/app/achievements/achievement_service.cpp @@ -28,8 +28,8 @@ AchievementService::getAchievementSetByContentHash( } bool AchievementService::setGameId(const std::string &contentHash, - const int setId) { - return m_repository.setGameId(contentHash, setId); + const int gameId) { + return m_repository.setGameId(contentHash, gameId); } std::optional @@ -61,8 +61,8 @@ bool AchievementService::createOrUpdate(const UserUnlock &unlock) { std::vector AchievementService::getAllUserUnlocks(const std::string &username, - unsigned setId) const { - return m_repository.getAllUserUnlocks(username, setId); + unsigned gameId) const { + return m_repository.getAllUserUnlocks(username, gameId); } std::optional @@ -85,7 +85,7 @@ bool AchievementService::processPatchResponse(const std::string &username, .points = a.Points, .type = a.Type, .displayOrder = i++, - .setId = response.PatchData.ID, + .gameId = response.PatchData.ID, .flags = a.Flags}; if (a.Flags == 5) { @@ -134,7 +134,7 @@ bool AchievementService::processPatchResponse(const std::string &username, return true; } bool AchievementService::processStartSessionResponse( - const std::string &username, const unsigned setId, + const std::string &username, const unsigned gameId, const StartSessionResponse &startSessionResponse) { auto foundUnsupportedEmu = false; @@ -190,7 +190,7 @@ bool AchievementService::processStartSessionResponse( // TODO: Could check to only update those that are synced // TODO: Update user score - for (auto &unlock : m_repository.getAllUserUnlocks(username, setId)) { + for (auto &unlock : m_repository.getAllUserUnlocks(username, gameId)) { auto foundInUnlocks = std::ranges::find_if( startSessionResponse.Unlocks, [&unlock](const Unlock &u) { return u.ID == unlock.achievementId; }); @@ -334,10 +334,10 @@ void AchievementService::syncOfflineAchievements() { continue; } - auto gameHash = m_repository.getGameHash(achievement->setId); + auto gameHash = m_repository.getGameHash(achievement->gameId); if (!gameHash.has_value()) { spdlog::warn("Could not find game hash for achievement set ID: {}", - achievement->setId); + achievement->gameId); continue; } @@ -405,16 +405,16 @@ void AchievementService::syncOfflineAchievements() { } void AchievementService::startSession(const std::string &username, - const unsigned setId, + const unsigned gameId, const bool hardcore) { m_inActiveSession = true; m_currentSessionUsername = username; - m_currentSessionSetId = setId; + m_currentSessionGameId = gameId; m_currentSessionHardcore = hardcore; EventDispatcher::instance().publish(AchievementSessionStartedEvent{ .username = m_currentSessionUsername, - .setId = m_currentSessionSetId, + .gameId = m_currentSessionGameId, .hardcore = m_currentSessionHardcore, }); } @@ -425,12 +425,12 @@ void AchievementService::endSession() { const auto event = AchievementSessionEndedEvent{ .username = m_currentSessionUsername, - .setId = m_currentSessionSetId, + .gameId = m_currentSessionGameId, .hardcore = m_currentSessionHardcore, }; m_currentSessionUsername.clear(); - m_currentSessionSetId = 0; + m_currentSessionGameId = 0; m_currentSessionHardcore = false; EventDispatcher::instance().publish(event); diff --git a/src/app/achievements/achievement_service.hpp b/src/app/achievements/achievement_service.hpp index 09310c56..ea15d005 100644 --- a/src/app/achievements/achievement_service.hpp +++ b/src/app/achievements/achievement_service.hpp @@ -13,13 +13,13 @@ namespace firelight::achievements { struct AchievementSessionStartedEvent { std::string username; - unsigned setId; + unsigned gameId; bool hardcore; }; struct AchievementSessionEndedEvent { std::string username; - unsigned setId; + unsigned gameId; bool hardcore; }; @@ -55,7 +55,7 @@ class AchievementService { [[nodiscard]] std::optional getAchievementSetByContentHash(const std::string &contentHash) const; - bool setGameId(const std::string &contentHash, int setId); + bool setGameId(const std::string &contentHash, int gameId); [[nodiscard]] std::optional getAchievement(unsigned achievementId) const; @@ -82,19 +82,20 @@ class AchievementService { bool createOrUpdate(const UserUnlock &unlock); [[nodiscard]] std::vector - getAllUserUnlocks(const std::string &username, unsigned setId) const; + getAllUserUnlocks(const std::string &username, unsigned gameId) const; [[nodiscard]] std::optional getPatchResponse(int gameId) const; bool processPatchResponse(const std::string &username, const PatchResponse &response); bool - processStartSessionResponse(const std::string &username, unsigned setId, + processStartSessionResponse(const std::string &username, unsigned gameId, const StartSessionResponse &startSessionResponse); void syncOfflineAchievements(); - void startSession(const std::string &username, unsigned setId, bool hardcore); + void startSession(const std::string &username, unsigned gameId, + bool hardcore); void endSession(); [[nodiscard]] bool inHardcoreSession() const; @@ -111,7 +112,7 @@ class AchievementService { bool m_inActiveSession = false; std::string m_currentSessionUsername; - unsigned m_currentSessionSetId = 0; + unsigned m_currentSessionGameId = 0; bool m_currentSessionHardcore = false; std::vector m_currentSessionHardcoreUnlocks; diff --git a/src/app/achievements/models/achievement.hpp b/src/app/achievements/models/achievement.hpp index 5ff52457..19130274 100644 --- a/src/app/achievements/models/achievement.hpp +++ b/src/app/achievements/models/achievement.hpp @@ -11,7 +11,7 @@ struct Achievement { int points; std::string type; int displayOrder; - unsigned setId; + unsigned gameId; int flags; }; } // namespace firelight::achievements diff --git a/src/app/achievements/sqlite_achievement_repository.cpp b/src/app/achievements/sqlite_achievement_repository.cpp index 8d88145b..47d660c0 100644 --- a/src/app/achievements/sqlite_achievement_repository.cpp +++ b/src/app/achievements/sqlite_achievement_repository.cpp @@ -44,7 +44,7 @@ SqliteAchievementRepository::SqliteAchievementRepository(std::string dbPath) -- Content hash to achievement set mapping for automatic loading CREATE TABLE IF NOT EXISTS hashes ( hash TEXT NOT NULL, -- Game content hash - achievement_set_id INTEGER NOT NULL, -- Associated achievement set + achievement_set_id INTEGER NOT NULL, -- Associated 0 set PRIMARY KEY (hash) ); @@ -253,13 +253,13 @@ bool SqliteAchievementRepository::update(const AchievementSet achievementSet) { } std::optional -SqliteAchievementRepository::getAchievementSet(unsigned setId) const { +SqliteAchievementRepository::getAchievementSet(unsigned gameId) const { try { SQLite::Statement query( *m_database, "SELECT id, name, num_achievements, num_points, icon_url " "FROM achievement_sets " - "WHERE id = :setId"); - query.bind(":setId", setId); + "WHERE id = :gameId"); + query.bind(":gameId", gameId); if (query.executeStep()) { AchievementSet achievementSet; @@ -275,9 +275,9 @@ SqliteAchievementRepository::getAchievementSet(unsigned setId) const { "SELECT id, name, description, points, achievement_set_id, flags, " "type, icon_url, display_order " "FROM achievements " - "WHERE achievement_set_id = :setId " + "WHERE achievement_set_id = :gameId " "ORDER BY display_order"); - achievementQuery.bind(":setId", achievementSet.id); + achievementQuery.bind(":gameId", achievementSet.id); while (achievementQuery.executeStep()) { Achievement achievement; @@ -285,7 +285,7 @@ SqliteAchievementRepository::getAchievementSet(unsigned setId) const { achievement.name = achievementQuery.getColumn(1).getString(); achievement.description = achievementQuery.getColumn(2).getString(); achievement.points = achievementQuery.getColumn(3); - achievement.setId = achievementQuery.getColumn(4); + achievement.gameId = achievementQuery.getColumn(4); achievement.flags = achievementQuery.getColumn(5); achievement.type = achievementQuery.getColumn(6).getString(); achievement.imageUrl = achievementQuery.getColumn(7).getString(); @@ -310,7 +310,7 @@ bool SqliteAchievementRepository::create(const Achievement achievement) { *m_database, "INSERT INTO achievements " "(id, name, description, points, achievement_set_id, " "flags, type, icon_url, display_order) " - "VALUES (:id, :name, :description, :points, :setId, " + "VALUES (:id, :name, :description, :points, :gameId, " ":flags, :type, :iconUrl, :displayOrder) " "ON CONFLICT(id) DO UPDATE SET " "name = excluded.name, " @@ -325,7 +325,7 @@ bool SqliteAchievementRepository::create(const Achievement achievement) { query.bind(":name", achievement.name); query.bind(":description", achievement.description); query.bind(":points", achievement.points); - query.bind(":setId", achievement.setId); + query.bind(":gameId", achievement.gameId); query.bind(":flags", achievement.flags); query.bind(":type", achievement.type); query.bind(":iconUrl", achievement.imageUrl); @@ -369,7 +369,7 @@ SqliteAchievementRepository::getAchievement(unsigned achievementId) const { achievement.points = query.getColumn(4); achievement.type = query.getColumn(5).getString(); achievement.displayOrder = query.getColumn(6); - achievement.setId = query.getColumn(7); + achievement.gameId = query.getColumn(7); achievement.flags = query.getColumn(8); return achievement; } @@ -405,16 +405,16 @@ bool SqliteAchievementRepository::create(AchievementProgress progress) { } } bool SqliteAchievementRepository::setGameId(const std::string &contentHash, - int setId) { + int gameId) { try { SQLite::Statement query(*m_database, "INSERT INTO hashes " "(hash, achievement_set_id) " - "VALUES (:contentHash, :setId) " + "VALUES (:contentHash, :gameId) " "ON CONFLICT(hash) DO UPDATE SET " "achievement_set_id = excluded.achievement_set_id"); query.bind(":contentHash", contentHash); - query.bind(":setId", setId); + query.bind(":gameId", gameId); query.exec(); return true; @@ -450,9 +450,9 @@ SqliteAchievementRepository::getAchievementSetByContentHash( "SELECT id, name, description, points, achievement_set_id, flags, " "type, icon_url, display_order " "FROM achievements " - "WHERE achievement_set_id = :setId " + "WHERE achievement_set_id = :gameId " "ORDER BY display_order"); - achievementQuery.bind(":setId", achievementSet.id); + achievementQuery.bind(":gameId", achievementSet.id); unsigned totalPoints = 0; while (achievementQuery.executeStep()) { @@ -461,7 +461,7 @@ SqliteAchievementRepository::getAchievementSetByContentHash( achievement.name = achievementQuery.getColumn(1).getString(); achievement.description = achievementQuery.getColumn(2).getString(); achievement.points = achievementQuery.getColumn(3); - achievement.setId = achievementQuery.getColumn(4); + achievement.gameId = achievementQuery.getColumn(4); achievement.flags = achievementQuery.getColumn(5); achievement.type = achievementQuery.getColumn(6).getString(); achievement.imageUrl = achievementQuery.getColumn(7).getString(); @@ -498,9 +498,8 @@ std::optional SqliteAchievementRepository::getGameId(const std::string &contentHash) const { try { SQLite::Statement query( - *m_database, - "SELECT achievement_set_id FROM hashes " - "WHERE hash = :contentHash AND achievement_set_id != 0"); + *m_database, "SELECT achievement_set_id FROM hashes " + "WHERE hash = :contentHash AND achievement_set_id != 0"); query.bind(":contentHash", contentHash); if (query.executeStep()) { @@ -514,11 +513,11 @@ SqliteAchievementRepository::getGameId(const std::string &contentHash) const { } } std::optional -SqliteAchievementRepository::getGameHash(unsigned setId) const { +SqliteAchievementRepository::getGameHash(unsigned gameId) const { try { SQLite::Statement query(*m_database, "SELECT hash FROM hashes " - "WHERE achievement_set_id = :setId"); - query.bind(":setId", setId); + "WHERE achievement_set_id = :gameId"); + query.bind(":gameId", gameId); if (query.executeStep()) { return query.getColumn(0).getString(); @@ -611,7 +610,7 @@ bool SqliteAchievementRepository::createOrUpdate(const UserUnlock unlock) { std::vector SqliteAchievementRepository::getAllUserUnlocks(const std::string &username, - unsigned setId) const { + unsigned gameId) const { std::vector unlocks; try { @@ -621,11 +620,11 @@ SqliteAchievementRepository::getAllUserUnlocks(const std::string &username, "u.\"when\", u.when_hardcore, u.synced " "FROM user_unlocks u " "JOIN achievements a ON u.achievement_id = a.id " - "WHERE u.username = :username AND a.achievement_set_id = :setId " + "WHERE u.username = :username AND a.achievement_set_id = :gameId " "ORDER BY a.display_order"); query.bind(":username", username); - query.bind(":setId", setId); + query.bind(":gameId", gameId); while (query.executeStep()) { UserUnlock unlock; diff --git a/src/app/achievements/sqlite_achievement_repository.hpp b/src/app/achievements/sqlite_achievement_repository.hpp index 8aa3eae4..4b36968a 100644 --- a/src/app/achievements/sqlite_achievement_repository.hpp +++ b/src/app/achievements/sqlite_achievement_repository.hpp @@ -110,11 +110,11 @@ class SqliteAchievementRepository final : public IAchievementRepository { * ordered by display order. Populates the achievements vector with * complete achievement data including all metadata fields. * - * @param setId The unique identifier of the achievement set + * @param gameId The unique identifier of the achievement set * @return The complete achievement set if found, std::nullopt otherwise */ std::optional - getAchievementSet(unsigned setId) const override; + getAchievementSet(unsigned gameId) const override; /** * @brief Retrieves achievement set by game content hash @@ -133,7 +133,8 @@ class SqliteAchievementRepository final : public IAchievementRepository { std::optional getGameId(const std::string &contentHash) const override; /** - * @brief Retrieves the content hash associated with an achievement set ID from SQLite + * @brief Retrieves the content hash associated with an achievement set ID + * from SQLite * * Executes a SQL query to perform reverse lookup from achievement set ID to * its associated content hash. Queries the hashes table to find the hash @@ -144,7 +145,7 @@ class SqliteAchievementRepository final : public IAchievementRepository { * Database errors are logged and result in std::nullopt return values for * graceful error handling. * - * SQL Query: `SELECT hash FROM hashes WHERE achievement_set_id = :setId` + * SQL Query: `SELECT hash FROM hashes WHERE achievement_set_id = :gameId` * * Use cases: * - Verifying content hash mappings for loaded achievement sets @@ -153,7 +154,8 @@ class SqliteAchievementRepository final : public IAchievementRepository { * - Cache invalidation based on content hash changes * - Multi-version game support where different content hashes map to same set * - * @param setId The achievement set ID to lookup the associated content hash for + * @param gameId The achievement set ID to lookup the associated content hash + * for * @return The content hash string if a mapping exists, std::nullopt if no * mapping found or on database error * @@ -167,7 +169,7 @@ class SqliteAchievementRepository final : public IAchievementRepository { * @see setGameId() to create or update hash <-> set ID mappings * @see getAchievementSetByContentHash() to retrieve sets directly by hash */ - std::optional getGameHash(unsigned setId) const override; + std::optional getGameHash(unsigned gameId) const override; // Individual Achievement Operations @@ -210,11 +212,11 @@ class SqliteAchievementRepository final : public IAchievementRepository { * game content fingerprinting. Uses upsert semantics to allow remapping. * * @param contentHash The game's content hash - * @param setId The achievement set ID to associate + * @param gameId The achievement set ID to associate * @return true if the mapping was stored successfully, false on database * error */ - bool setGameId(const std::string &contentHash, int setId) override; + bool setGameId(const std::string &contentHash, int gameId) override; // User Unlock Operations @@ -252,11 +254,11 @@ class SqliteAchievementRepository final : public IAchievementRepository { * for displaying a user's progress through all achievements in a game. * * @param username The username to query unlock records for - * @param setId The achievement set ID to filter unlocks by + * @param gameId The achievement set ID to filter unlocks by * @return Vector of UserUnlock records, empty if none found or on error */ std::vector getAllUserUnlocks(const std::string &username, - unsigned setId) const override; + unsigned gameId) const override; /** * @brief Retrieves all unsynced user unlock records for a specific user diff --git a/src/app/rcheevos/offline/rcheevos_offline_client.cpp b/src/app/rcheevos/offline/rcheevos_offline_client.cpp index 3d7f38a9..e70cd93b 100644 --- a/src/app/rcheevos/offline/rcheevos_offline_client.cpp +++ b/src/app/rcheevos/offline/rcheevos_offline_client.cpp @@ -282,7 +282,7 @@ RetroAchievementsOfflineClient::handleAwardAchievementRequest( } auto unlocks = - m_achievementService.getAllUserUnlocks(username, achievement->setId); + m_achievementService.getAllUserUnlocks(username, achievement->gameId); auto numLocked = 0; for (const auto &u : unlocks) { diff --git a/tests/app/achievements/sqlite_achievement_repository_test.cpp b/tests/app/achievements/sqlite_achievement_repository_test.cpp index b166d770..23ad789c 100644 --- a/tests/app/achievements/sqlite_achievement_repository_test.cpp +++ b/tests/app/achievements/sqlite_achievement_repository_test.cpp @@ -40,7 +40,7 @@ class SqliteAchievementRepositoryTest : public testing::Test { achievement.points = 50; achievement.type = "progression"; achievement.displayOrder = 1; - achievement.setId = setId; + achievement.gameId = setId; achievement.flags = 3; // Active achievement return achievement; } @@ -469,7 +469,7 @@ TEST_F(SqliteAchievementRepositoryTest, GetAchievement_ExistingAchievement) { EXPECT_EQ(result->type, "progression"); EXPECT_EQ(result->flags, 3); EXPECT_EQ(result->displayOrder, 1); - EXPECT_EQ(result->setId, 1); + EXPECT_EQ(result->gameId, 1); EXPECT_EQ(result->imageUrl, "https://example.com/achievement.png"); } @@ -503,7 +503,7 @@ TEST_F(SqliteAchievementRepositoryTest, GetAchievement_VerifyAllFields) { EXPECT_EQ(result->points, 75); EXPECT_EQ(result->type, "challenge"); EXPECT_EQ(result->displayOrder, 5); - EXPECT_EQ(result->setId, 1); + EXPECT_EQ(result->gameId, 1); EXPECT_EQ(result->flags, 3); } @@ -529,7 +529,7 @@ TEST_F(SqliteAchievementRepositoryTest, GetAchievement_AfterUpdate) { EXPECT_EQ(result->name, "Updated Achievement"); EXPECT_EQ(result->points, 50); EXPECT_EQ(result->description, "Updated description"); - EXPECT_EQ(result->setId, 1); + EXPECT_EQ(result->gameId, 1); } TEST_F(SqliteAchievementRepositoryTest, GetAchievement_MultipleAchievements) { @@ -696,7 +696,7 @@ TEST_F(SqliteAchievementRepositoryTest, GetAchievementSet_WithAchievements) { EXPECT_EQ(firstAchievement.imageUrl, "https://example.com/achievement.png"); EXPECT_EQ(firstAchievement.points, 50); EXPECT_EQ(firstAchievement.type, "progression"); - EXPECT_EQ(firstAchievement.setId, 1); + EXPECT_EQ(firstAchievement.gameId, 1); EXPECT_EQ(firstAchievement.flags, 3); } diff --git a/tests/app/rcheevos/rcheevos_offline_client_test.cpp b/tests/app/rcheevos/rcheevos_offline_client_test.cpp index 26a40a1d..8f2891e9 100644 --- a/tests/app/rcheevos/rcheevos_offline_client_test.cpp +++ b/tests/app/rcheevos/rcheevos_offline_client_test.cpp @@ -2756,7 +2756,7 @@ TEST_F(RetroAchievementsOfflineClientTest, ASSERT_EQ(achievement->description, achieve.Description); ASSERT_EQ(achievement->points, achieve.Points); ASSERT_EQ(achievement->flags, achieve.Flags); - ASSERT_EQ(achievement->setId, 228); + ASSERT_EQ(achievement->gameId, 228); auto unlock = service.getUserUnlock("testuser", achieve.ID); ASSERT_TRUE(unlock.has_value()); @@ -2910,7 +2910,7 @@ TEST_F(RetroAchievementsOfflineClientTest, .points = 25, .type = "progression", .displayOrder = 0, - .setId = 1, + .gameId = 1, .flags = 3}; repo.create(achievement); @@ -2948,7 +2948,7 @@ TEST_F(RetroAchievementsOfflineClientTest, AwardAchievementFirstTimeHardcore) { .points = 25, .type = "progression", .displayOrder = 0, - .setId = 1, + .gameId = 1, .flags = 3}; repo.create(achievement); @@ -2987,7 +2987,7 @@ TEST_F(RetroAchievementsOfflineClientTest, .points = 25, .type = "progression", .displayOrder = 0, - .setId = 1, + .gameId = 1, .flags = 3}; repo.create(achievement); @@ -3044,7 +3044,7 @@ TEST_F(RetroAchievementsOfflineClientTest, .points = 25, .type = "progression", .displayOrder = 0, - .setId = 1, + .gameId = 1, .flags = 3}; repo.create(achievement); @@ -3101,7 +3101,7 @@ TEST_F(RetroAchievementsOfflineClientTest, .points = 25, .type = "progression", .displayOrder = 0, - .setId = 1, + .gameId = 1, .flags = 3}; repo.create(achievement); From 0eb1c41c1c388ead948cce3ff2da6bddfba11ca6 Mon Sep 17 00:00:00 2001 From: biscuitcakes Date: Thu, 22 Jan 2026 18:12:27 -0600 Subject: [PATCH 2/5] WIP --- .../achievements/achievement_repository.hpp | 23 +- src/app/achievements/achievement_service.cpp | 31 +- .../achievements/gui/AchievementSetItem.cpp | 4 +- .../gui/achievement_list_model.cpp | 4 +- src/app/achievements/models/achievement.hpp | 18 +- .../achievements/models/achievement_set.hpp | 7 +- src/app/achievements/models/game.hpp | 19 + .../sqlite_achievement_repository.cpp | 468 +++++---- .../sqlite_achievement_repository.hpp | 28 +- .../achievements/user_achievement_status.hpp | 12 - .../rcheevos/offline/cached_achievement.hpp | 15 - .../rcheevos/offline/earned_achievement.hpp | 13 - .../offline/rcheevos_offline_client.cpp | 35 +- src/app/rcheevos/patch_response.hpp | 939 +++++++++++++++--- src/app/rcheevos/ra_client.cpp | 4 +- src/app/rcheevos/user.h | 13 +- .../achievements/achievement_service_test.cpp | 32 +- .../sqlite_achievement_repository_test.cpp | 292 +++--- .../rcheevos/rcheevos_offline_client_test.cpp | 56 +- 19 files changed, 1391 insertions(+), 622 deletions(-) create mode 100644 src/app/achievements/models/game.hpp delete mode 100644 src/app/achievements/user_achievement_status.hpp delete mode 100644 src/app/rcheevos/offline/cached_achievement.hpp delete mode 100644 src/app/rcheevos/offline/earned_achievement.hpp diff --git a/src/app/achievements/achievement_repository.hpp b/src/app/achievements/achievement_repository.hpp index a2d9f61b..65d47665 100644 --- a/src/app/achievements/achievement_repository.hpp +++ b/src/app/achievements/achievement_repository.hpp @@ -2,6 +2,7 @@ #include "models/achievement_progress.hpp" #include "models/achievement_set.hpp" +#include "models/game.hpp" #include "models/user_unlock.hpp" #include @@ -98,18 +99,6 @@ class IAchievementRepository { */ virtual bool create(AchievementSet achievementSet) = 0; - /** - * @brief Updates an existing achievement set - * - * Modifies the metadata of an existing achievement set. The achievement set - * must already exist in the repository. - * - * @param achievementSet The achievement set with updated data - * @return true if the update succeeded, false if the set doesn't exist or - * update failed - */ - virtual bool update(AchievementSet achievementSet) = 0; - /** * @brief Retrieves an achievement set by its unique identifier * @@ -118,10 +107,10 @@ class IAchievementRepository { * included. * * @param gameId The unique identifier of the achievement set - * @return The achievement set if found, std::nullopt otherwise + * @return The list of achievement sets if found, empty vector otherwise */ - [[nodiscard]] virtual std::optional - getAchievementSet(unsigned gameId) const = 0; + [[nodiscard]] virtual std::vector + getAchievementSetsByGameId(unsigned gameId) const = 0; /** * @brief Retrieves an achievement set by content hash @@ -137,6 +126,10 @@ class IAchievementRepository { [[nodiscard]] virtual std::optional getAchievementSetByContentHash(const std::string &contentHash) const = 0; + [[nodiscard]] virtual std::optional getGameById(int gameId) const = 0; + + virtual bool create(Game game) = 0; + /** * @brief Retrieves the achievement set ID associated with a content hash * diff --git a/src/app/achievements/achievement_service.cpp b/src/app/achievements/achievement_service.cpp index 34836600..5c64cba0 100644 --- a/src/app/achievements/achievement_service.cpp +++ b/src/app/achievements/achievement_service.cpp @@ -79,14 +79,21 @@ bool AchievementService::processPatchResponse(const std::string &username, auto i = 0; for (const auto &a : response.PatchData.Achievements) { auto achievement = Achievement{.id = a.ID, - .name = a.Title, + .achievementSetId = response.PatchData.ID, + .memAddr = a.MemAddr, + .title = a.Title, .description = a.Description, - .imageUrl = a.BadgeURL, .points = a.Points, + .author = a.Author, + .modified = a.Modified, + .created = a.Created, + .flags = a.Flags, .type = a.Type, - .displayOrder = i++, - .gameId = response.PatchData.ID, - .flags = a.Flags}; + .rarity = a.Rarity, + .rarityHardcore = a.RarityHardcore, + .badgeUrl = a.BadgeURL, + .badgeLockedUrl = a.BadgeLockedURL, + .displayOrder = i++}; if (a.Flags == 5) { continue; @@ -97,8 +104,10 @@ bool AchievementService::processPatchResponse(const std::string &username, } auto set = AchievementSet{.id = response.PatchData.ID, - .name = response.PatchData.Title, - .iconUrl = response.PatchData.ImageIconURL, + .title = response.PatchData.Title, + .type = "core", + .gameId = response.PatchData.ID, + .imageIconUrl = response.PatchData.ImageIconURL, .numAchievements = static_cast(achievements.size()), .totalPoints = totalPoints}; @@ -334,10 +343,10 @@ void AchievementService::syncOfflineAchievements() { continue; } - auto gameHash = m_repository.getGameHash(achievement->gameId); + auto gameHash = m_repository.getGameHash(achievement->achievementSetId); if (!gameHash.has_value()) { spdlog::warn("Could not find game hash for achievement set ID: {}", - achievement->gameId); + achievement->achievementSetId); continue; } @@ -385,12 +394,12 @@ void AchievementService::syncOfflineAchievements() { unlock.synced = true; if (unlockJson.contains("Score") && unlockJson["Score"].is_number()) { - user.hardcore_points = unlockJson["Score"]; + user.score = unlockJson["Score"]; } if (unlockJson.contains("SoftcoreScore") && unlockJson["SoftcoreScore"].is_number()) { - user.points = unlockJson["SoftcoreScore"]; + user.softcoreScore = unlockJson["SoftcoreScore"]; } m_repository.createOrUpdate(unlock); diff --git a/src/app/achievements/gui/AchievementSetItem.cpp b/src/app/achievements/gui/AchievementSetItem.cpp index f7a57852..7c000b9f 100644 --- a/src/app/achievements/gui/AchievementSetItem.cpp +++ b/src/app/achievements/gui/AchievementSetItem.cpp @@ -36,8 +36,8 @@ void AchievementSetItem::setContentHash(const QString &contentHash) { m_hasAchievements = false; } else { m_setId = set->id; - m_setName = QString::fromStdString(set->name); - m_iconUrl = QString::fromStdString(set->iconUrl); + m_setName = QString::fromStdString(set->title); + m_iconUrl = QString::fromStdString(set->imageIconUrl); m_numAchievements = set->numAchievements; m_totalNumPoints = set->totalPoints; m_hasAchievements = m_numAchievements > 0; diff --git a/src/app/achievements/gui/achievement_list_model.cpp b/src/app/achievements/gui/achievement_list_model.cpp index eb535463..d09e74a9 100644 --- a/src/app/achievements/gui/achievement_list_model.cpp +++ b/src/app/achievements/gui/achievement_list_model.cpp @@ -40,13 +40,13 @@ QVariant AchievementListModel::data(const QModelIndex &index, int role) const { case Id: return QVariant::fromValue(item.achievement.id); case Name: - return QVariant::fromValue(QString::fromStdString(item.achievement.name)); + return QVariant::fromValue(QString::fromStdString(item.achievement.title)); case Description: return QVariant::fromValue( QString::fromStdString(item.achievement.description)); case ImageUrl: return QVariant::fromValue( - QString::fromStdString(item.achievement.imageUrl)); + QString::fromStdString(item.achievement.badgeUrl)); case Earned: return QVariant::fromValue(m_hardcore ? item.unlockState.earnedHardcore : item.unlockState.earned); diff --git a/src/app/achievements/models/achievement.hpp b/src/app/achievements/models/achievement.hpp index 19130274..01cb0ad1 100644 --- a/src/app/achievements/models/achievement.hpp +++ b/src/app/achievements/models/achievement.hpp @@ -5,13 +5,21 @@ namespace firelight::achievements { struct Achievement { unsigned id; - std::string name; + unsigned achievementSetId; + std::string memAddr; + std::string title; std::string description; - std::string imageUrl; - int points; + unsigned points; + std::string author; + unsigned modified; + unsigned created; + std::string badgeName; + int flags; std::string type; + float rarity; + float rarityHardcore; + std::string badgeUrl; + std::string badgeLockedUrl; int displayOrder; - unsigned gameId; - int flags; }; } // namespace firelight::achievements diff --git a/src/app/achievements/models/achievement_set.hpp b/src/app/achievements/models/achievement_set.hpp index e5e9f7c8..e55d5721 100644 --- a/src/app/achievements/models/achievement_set.hpp +++ b/src/app/achievements/models/achievement_set.hpp @@ -7,8 +7,11 @@ namespace firelight::achievements { struct AchievementSet { unsigned id; - std::string name; - std::string iconUrl; + std::string title; + std::string type; + unsigned gameId; + std::string imageIconUrl; + unsigned numAchievements; unsigned totalPoints; diff --git a/src/app/achievements/models/game.hpp b/src/app/achievements/models/game.hpp new file mode 100644 index 00000000..032c23bc --- /dev/null +++ b/src/app/achievements/models/game.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "achievement_set.hpp" + +#include +#include + +namespace firelight::achievements { +struct Game { + unsigned id; + std::string title; + std::string imageIconUrl; + unsigned richPresenceGameId; + std::string richPresencePatch; + unsigned consoleId; + + std::vector achievementSets; +}; +} // namespace firelight::achievements diff --git a/src/app/achievements/sqlite_achievement_repository.cpp b/src/app/achievements/sqlite_achievement_repository.cpp index 47d660c0..c4566f9f 100644 --- a/src/app/achievements/sqlite_achievement_repository.cpp +++ b/src/app/achievements/sqlite_achievement_repository.cpp @@ -9,7 +9,6 @@ #include namespace firelight::achievements { - /** * @brief Constructs the SQLite achievement repository * @@ -41,40 +40,72 @@ SqliteAchievementRepository::SqliteAchievementRepository(std::string dbPath) // Create all required tables with proper schema const std::string setupQueryString = R"( - -- Content hash to achievement set mapping for automatic loading - CREATE TABLE IF NOT EXISTS hashes ( + -- Content hash to game mapping for automatic loading + CREATE TABLE IF NOT EXISTS game_hashes ( hash TEXT NOT NULL, -- Game content hash - achievement_set_id INTEGER NOT NULL, -- Associated 0 set - PRIMARY KEY (hash) + game_id INTEGER NOT NULL, -- Associated set + PRIMARY KEY (hash, game_id) ); - -- Achievement set metadata - CREATE TABLE IF NOT EXISTS achievement_sets ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, -- Achievement set name - num_achievements INTEGER NOT NULL, -- Total achievement count - num_points INTEGER NOT NULL DEFAULT 0, -- Total point value - icon_url TEXT NOT NULL DEFAULT '' -- Set icon URL + -- Content hash to achievement set mapping for automatic loading + CREATE TABLE IF NOT EXISTS achievement_set_hashes ( + hash TEXT NOT NULL, -- Game content hash + achievement_set_id INTEGER NOT NULL, -- Associated set + PRIMARY KEY (hash, achievement_set_id) ); -- User account information CREATE TABLE IF NOT EXISTS users ( username TEXT PRIMARY KEY NOT NULL, + avatar_url TEXT NOT NULL DEFAULT '', -- User avatar URL token TEXT NOT NULL, -- Authentication token - points INTEGER NOT NULL, -- Total points earned - hardcore_points INTEGER NOT NULL -- Hardcore mode points + score INTEGER NOT NULL, -- Total score earned + softcore_score INTEGER NOT NULL, -- Hardcore mode score + messages INTEGER NOT NULL DEFAULT 0, -- Unread message count + permissions INTEGER NOT NULL DEFAULT 0, -- User permission flags + account_type TEXT NOT NULL DEFAULT '' + ); + + -- Game metadata + CREATE TABLE IF NOT EXISTS games ( + id INTEGER NOT NULL PRIMARY KEY, + title TEXT NOT NULL, -- Game name + image_icon_url TEXT NOT NULL DEFAULT '', -- Set icon URL + rich_presence_game_id INTEGER NOT NULL DEFAULT 0, -- Rich Presence ID + rich_presence_patch TEXT NOT NULL DEFAULT '', -- Rich Presence Patch + console_id INTEGER NOT NULL DEFAULT 0 -- Console ID + ); + + -- Achievement set metadata + CREATE TABLE IF NOT EXISTS achievement_sets ( + id INTEGER NOT NULL PRIMARY KEY, + title TEXT NOT NULL, -- Achievement set name + type TEXT NOT NULL, -- Achievement set type + game_id INTEGER NOT NULL, -- Associated game ID + image_icon_url TEXT NOT NULL DEFAULT '', -- Set icon URL, + num_achievements INTEGER NOT NULL, -- Total achievement count + num_points INTEGER NOT NULL DEFAULT 0, -- Total point value + FOREIGN KEY (game_id) REFERENCES games(id) ); -- Individual achievement data CREATE TABLE IF NOT EXISTS achievements ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, -- Achievement name + id INTEGER NOT NULL PRIMARY KEY, + achievement_set_id INTEGER NOT NULL, -- Parent set ID + mem_address TEXT NOT NULL DEFAULT '', -- Memory address + title TEXT NOT NULL, -- Achievement name description TEXT NOT NULL, -- Achievement description points INTEGER NOT NULL, -- Point value - achievement_set_id INTEGER NOT NULL, -- Parent set ID + author TEXT NOT NULL DEFAULT '', -- Achievement author + modified INTEGER NOT NULL DEFAULT 0, -- Last modified timestamp + created INTEGER NOT NULL DEFAULT 0, -- Creation timestamp + badge_name TEXT NOT NULL DEFAULT '', -- Badge internal name flags INTEGER NOT NULL, -- Status flags (3 = active) type TEXT NOT NULL DEFAULT '', -- Achievement type/category - icon_url TEXT NOT NULL DEFAULT '', -- Achievement icon URL + rarity REAL NOT NULL DEFAULT 0.0, -- Normal mode rarity % + rarity_hardcore REAL NOT NULL DEFAULT 0.0, -- Hardcore mode rarity % + badge_url TEXT NOT NULL DEFAULT '', -- Achievement icon URL + badge_locked_url TEXT NOT NULL DEFAULT '', -- Locked icon URL display_order INTEGER NOT NULL DEFAULT 0, -- Display sort order FOREIGN KEY (achievement_set_id) REFERENCES achievement_sets(id) ); @@ -101,12 +132,6 @@ SqliteAchievementRepository::SqliteAchievementRepository(std::string dbPath) PRIMARY KEY (username, achievement_id), FOREIGN KEY (achievement_id) REFERENCES achievements(id) ); - - -- Cached API responses for offline functionality - CREATE TABLE IF NOT EXISTS patch_response_cache ( - game_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - cached_value BLOB NOT NULL -- JSON response as binary data - ); )"; m_database->exec(setupQueryString); @@ -129,7 +154,7 @@ std::optional SqliteAchievementRepository::getUser(const std::string &username) const { try { SQLite::Statement query(*m_database, - "SELECT username, token, points, hardcore_points " + "SELECT username, token, softcoreScore, score " "FROM users " "WHERE username = :username"); query.bind(":username", username); @@ -138,8 +163,8 @@ SqliteAchievementRepository::getUser(const std::string &username) const { User user; user.username = query.getColumn(0).getString(); user.token = query.getColumn(1).getString(); - user.points = query.getColumn(2); - user.hardcore_points = query.getColumn(3); + user.softcoreScore = query.getColumn(2); + user.score = query.getColumn(3); return user; } @@ -154,7 +179,7 @@ std::vector SqliteAchievementRepository::listUsers() const { try { SQLite::Statement query(*m_database, - "SELECT username, token, points, hardcore_points " + "SELECT username, token, softcoreScore, score " "FROM users " "ORDER BY username"); @@ -162,8 +187,8 @@ std::vector SqliteAchievementRepository::listUsers() const { User user; user.username = query.getColumn(0).getString(); user.token = query.getColumn(1).getString(); - user.points = query.getColumn(2); - user.hardcore_points = query.getColumn(3); + user.softcoreScore = query.getColumn(2); + user.score = query.getColumn(3); users.emplace_back(user); } } catch (const std::exception &e) { @@ -186,17 +211,18 @@ std::vector SqliteAchievementRepository::listUsers() const { bool SqliteAchievementRepository::createOrUpdateUser(const User &user) { try { SQLite::Statement query( - *m_database, "INSERT INTO users " - "(username, token, points, hardcore_points) " - "VALUES (:username, :token, :points, :hardcorePoints) " - "ON CONFLICT(username) DO UPDATE SET " - "token = excluded.token, " - "points = excluded.points, " - "hardcore_points = excluded.hardcore_points"); + *m_database, + "INSERT INTO users " + "(username, token, softcoreScore, score) " + "VALUES (:username, :token, :softcoreScore, :hardcorePoints) " + "ON CONFLICT(username) DO UPDATE SET " + "token = excluded.token, " + "softcoreScore = excluded.softcoreScore, " + "score = excluded.score"); query.bind(":username", user.username); query.bind(":token", user.token); - query.bind(":points", user.points); - query.bind(":hardcorePoints", user.hardcore_points); + query.bind(":softcoreScore", user.softcoreScore); + query.bind(":hardcorePoints", user.score); query.exec(); return true; @@ -208,21 +234,26 @@ bool SqliteAchievementRepository::createOrUpdateUser(const User &user) { bool SqliteAchievementRepository::create(const AchievementSet achievementSet) { try { - SQLite::Statement query( - *m_database, - "INSERT INTO achievement_sets " - "(id, name, num_achievements, num_points, icon_url) " - "VALUES (:id, :name, :numAchievements, :numPoints, :iconUrl) " - "ON CONFLICT(id) DO UPDATE SET " - "name = excluded.name, " - "num_achievements = excluded.num_achievements, " - "num_points = excluded.num_points, " - "icon_url = excluded.icon_url"); + SQLite::Statement query(*m_database, + "INSERT INTO achievement_sets " + "(id, title, type, game_id, image_icon_url, " + "num_achievements, num_points) " + "VALUES (:id, :title, :type, :gameId, " + ":imageIconUrl, :numAchievements, :numPoints) " + "ON CONFLICT(id) DO UPDATE SET " + "title = excluded.title, " + "type = excluded.type, " + "game_id = excluded.game_id, " + "image_icon_url = excluded.image_icon_url, " + "num_achievements = excluded.num_achievements, " + "num_points = excluded.num_points"); query.bind(":id", achievementSet.id); - query.bind(":name", achievementSet.name); + query.bind(":title", achievementSet.title); + query.bind(":type", achievementSet.type); + query.bind(":gameId", achievementSet.gameId); + query.bind(":imageIconUrl", achievementSet.imageIconUrl); query.bind(":numAchievements", achievementSet.numAchievements); query.bind(":numPoints", achievementSet.totalPoints); - query.bind(":iconUrl", achievementSet.iconUrl); query.exec(); return true; @@ -231,104 +262,119 @@ bool SqliteAchievementRepository::create(const AchievementSet achievementSet) { return false; } } -bool SqliteAchievementRepository::update(const AchievementSet achievementSet) { - try { - SQLite::Statement query(*m_database, "UPDATE achievement_sets " - "SET name = :name, " - "num_achievements = :numAchievements, " - "num_points = :numPoints, " - "icon_url = :iconUrl " - "WHERE id = :id"); - query.bind(":id", achievementSet.id); - query.bind(":name", achievementSet.name); - query.bind(":numAchievements", achievementSet.numAchievements); - query.bind(":numPoints", achievementSet.totalPoints); - query.bind(":iconUrl", achievementSet.iconUrl); - - return query.exec() > 0; - } catch (const std::exception &e) { - spdlog::error("Failed to update achievement set: {}", e.what()); - return false; - } -} -std::optional -SqliteAchievementRepository::getAchievementSet(unsigned gameId) const { +std::vector +SqliteAchievementRepository::getAchievementSetsByGameId(unsigned gameId) const { try { - SQLite::Statement query( - *m_database, "SELECT id, name, num_achievements, num_points, icon_url " - "FROM achievement_sets " - "WHERE id = :gameId"); + SQLite::Statement query(*m_database, + "SELECT id, title, type, game_id, image_icon_url, " + "num_achievements, num_points " + "FROM achievement_sets " + "WHERE game_id = :gameId"); query.bind(":gameId", gameId); - if (query.executeStep()) { + std::vector sets; + while (query.executeStep()) { AchievementSet achievementSet; achievementSet.id = query.getColumn(0); - achievementSet.name = query.getColumn(1).getString(); - achievementSet.numAchievements = query.getColumn(2); - achievementSet.totalPoints = query.getColumn(3); - achievementSet.iconUrl = query.getColumn(4).getString(); + achievementSet.title = query.getColumn(1).getString(); + achievementSet.type = query.getColumn(2).getString(); + achievementSet.gameId = query.getColumn(3); + achievementSet.imageIconUrl = query.getColumn(4).getString(); + achievementSet.numAchievements = query.getColumn(5); + achievementSet.totalPoints = query.getColumn(6); // Load achievements for this set SQLite::Statement achievementQuery( - *m_database, - "SELECT id, name, description, points, achievement_set_id, flags, " - "type, icon_url, display_order " - "FROM achievements " - "WHERE achievement_set_id = :gameId " - "ORDER BY display_order"); - achievementQuery.bind(":gameId", achievementSet.id); + *m_database, "SELECT id, achievement_set_id, mem_address, title, " + "description, points, " + "author, modified, created, badge_name, flags, type, " + "rarity, rarity_hardcore, " + "badge_url, badge_locked_url, display_order " + "FROM achievements " + "WHERE achievement_set_id = :setId " + "ORDER BY display_order"); + achievementQuery.bind(":setId", achievementSet.id); while (achievementQuery.executeStep()) { Achievement achievement; achievement.id = achievementQuery.getColumn(0); - achievement.name = achievementQuery.getColumn(1).getString(); - achievement.description = achievementQuery.getColumn(2).getString(); - achievement.points = achievementQuery.getColumn(3); - achievement.gameId = achievementQuery.getColumn(4); - achievement.flags = achievementQuery.getColumn(5); - achievement.type = achievementQuery.getColumn(6).getString(); - achievement.imageUrl = achievementQuery.getColumn(7).getString(); - achievement.displayOrder = achievementQuery.getColumn(8); + achievement.achievementSetId = achievementQuery.getColumn(1); + achievement.memAddr = achievementQuery.getColumn(2).getString(); + achievement.title = achievementQuery.getColumn(3).getString(); + achievement.description = achievementQuery.getColumn(4).getString(); + achievement.points = achievementQuery.getColumn(5); + achievement.author = achievementQuery.getColumn(6).getString(); + achievement.modified = achievementQuery.getColumn(7); + achievement.created = achievementQuery.getColumn(8); + achievement.badgeName = achievementQuery.getColumn(9).getString(); + achievement.flags = achievementQuery.getColumn(10); + achievement.type = achievementQuery.getColumn(11).getString(); + achievement.rarity = achievementQuery.getColumn(12).getDouble(); + achievement.rarityHardcore = achievementQuery.getColumn(13).getDouble(); + achievement.badgeUrl = achievementQuery.getColumn(14).getString(); + achievement.badgeLockedUrl = achievementQuery.getColumn(15).getString(); + achievement.displayOrder = achievementQuery.getColumn(16); achievementSet.achievements.emplace_back(achievement); } - return achievementSet; + sets.emplace_back(achievementSet); } - return std::nullopt; + return sets; + } catch (const std::exception &e) { spdlog::error("Failed to get achievement set: {}", e.what()); - return std::nullopt; + return {}; } } bool SqliteAchievementRepository::create(const Achievement achievement) { try { SQLite::Statement query( - *m_database, "INSERT INTO achievements " - "(id, name, description, points, achievement_set_id, " - "flags, type, icon_url, display_order) " - "VALUES (:id, :name, :description, :points, :gameId, " - ":flags, :type, :iconUrl, :displayOrder) " - "ON CONFLICT(id) DO UPDATE SET " - "name = excluded.name, " - "description = excluded.description, " - "points = excluded.points, " - "achievement_set_id = excluded.achievement_set_id, " - "flags = excluded.flags, " - "type = excluded.type, " - "icon_url = excluded.icon_url, " - "display_order = excluded.display_order"); + *m_database, + "INSERT INTO achievements " + "(id, achievement_set_id, mem_address, title, description, points, " + "author, modified, created, badge_name, flags, type, rarity, " + "rarity_hardcore, badge_url, badge_locked_url, display_order) " + "VALUES (:id, :achievementSetId, :memAddr, :title, :description, " + ":points, " + ":author, :modified, :created, :badgeName, :flags, :type, :rarity, " + ":rarityHardcore, :badgeUrl, :badgeLockedUrl, :displayOrder) " + "ON CONFLICT(id) DO UPDATE SET " + "achievement_set_id = excluded.achievement_set_id, " + "mem_address = excluded.mem_address, " + "title = excluded.title, " + "description = excluded.description, " + "points = excluded.points, " + "author = excluded.author, " + "modified = excluded.modified, " + "created = excluded.created, " + "badge_name = excluded.badge_name, " + "flags = excluded.flags, " + "type = excluded.type, " + "rarity = excluded.rarity, " + "rarity_hardcore = excluded.rarity_hardcore, " + "badge_url = excluded.badge_url, " + "badge_locked_url = excluded.badge_locked_url, " + "display_order = excluded.display_order"); query.bind(":id", achievement.id); - query.bind(":name", achievement.name); + query.bind(":achievementSetId", achievement.achievementSetId); + query.bind(":memAddr", achievement.memAddr); + query.bind(":title", achievement.title); query.bind(":description", achievement.description); query.bind(":points", achievement.points); - query.bind(":gameId", achievement.gameId); + query.bind(":author", achievement.author); + query.bind(":modified", achievement.modified); + query.bind(":created", achievement.created); + query.bind(":badgeName", achievement.badgeName); query.bind(":flags", achievement.flags); query.bind(":type", achievement.type); - query.bind(":iconUrl", achievement.imageUrl); + query.bind(":rarity", achievement.rarity); + query.bind(":rarityHardcore", achievement.rarityHardcore); + query.bind(":badgeUrl", achievement.badgeUrl); + query.bind(":badgeLockedUrl", achievement.badgeLockedUrl); query.bind(":displayOrder", achievement.displayOrder); query.exec(); @@ -342,7 +388,7 @@ bool SqliteAchievementRepository::create(const Achievement achievement) { * @brief Retrieves an individual achievement by its ID from the SQLite database * * Queries the achievements table to fetch complete achievement information - * including name, description, points, type, display order, flags, and parent + * including title, description, points, type, display order, flags, and parent * achievement set ID. Used for displaying achievement details or validating * individual achievement data. * @@ -353,24 +399,37 @@ bool SqliteAchievementRepository::create(const Achievement achievement) { std::optional SqliteAchievementRepository::getAchievement(unsigned achievementId) const { try { - SQLite::Statement query(*m_database, - "SELECT id, name, description, icon_url, points, " - "type, display_order, achievement_set_id, flags " - "FROM achievements " - "WHERE id = :achievementId"); + SQLite::Statement query( + *m_database, + "SELECT id, achievement_set_id, mem_address, title, description, " + "points, " + "author, modified, created, badge_name, flags, type, rarity, " + "rarity_hardcore, badge_url, badge_locked_url, display_order " + "FROM achievements " + "WHERE id = :achievementId"); query.bind(":achievementId", achievementId); if (query.executeStep()) { Achievement achievement; achievement.id = query.getColumn(0); - achievement.name = query.getColumn(1).getString(); - achievement.description = query.getColumn(2).getString(); - achievement.imageUrl = query.getColumn(3).getString(); - achievement.points = query.getColumn(4); - achievement.type = query.getColumn(5).getString(); - achievement.displayOrder = query.getColumn(6); - achievement.gameId = query.getColumn(7); - achievement.flags = query.getColumn(8); + achievement.achievementSetId = query.getColumn(1); + achievement.memAddr = query.getColumn(2).getString(); + achievement.title = query.getColumn(3).getString(); + achievement.description = query.getColumn(4).getString(); + achievement.points = query.getColumn(5); + achievement.author = query.getColumn(6).getString(); + achievement.modified = query.getColumn(7); + achievement.created = query.getColumn(8); + achievement.badgeName = query.getColumn(9).getString(); + achievement.flags = query.getColumn(10); + achievement.type = query.getColumn(11).getString(); + achievement.rarity = query.getColumn(12).getDouble(); + ; + achievement.rarityHardcore = query.getColumn(13).getDouble(); + ; + achievement.badgeUrl = query.getColumn(14).getString(); + achievement.badgeLockedUrl = query.getColumn(15).getString(); + achievement.displayOrder = query.getColumn(16); return achievement; } @@ -381,7 +440,7 @@ SqliteAchievementRepository::getAchievement(unsigned achievementId) const { } } -bool SqliteAchievementRepository::create(AchievementProgress progress) { +bool SqliteAchievementRepository::create(const AchievementProgress progress) { try { SQLite::Statement query( *m_database, @@ -404,15 +463,74 @@ bool SqliteAchievementRepository::create(AchievementProgress progress) { return false; } } + +std::optional +SqliteAchievementRepository::getGameById(const int gameId) const { + try { + SQLite::Statement query( + *m_database, "SELECT id, title, image_icon_url, rich_presence_game_id, " + "rich_presence_patch, console_id " + "FROM games " + "WHERE id = :gameId"); + query.bind(":gameId", gameId); + + if (query.executeStep()) { + Game game; + game.id = query.getColumn(0); + game.title = query.getColumn(1).getString(); + game.imageIconUrl = query.getColumn(2).getString(); + game.richPresenceGameId = query.getColumn(3); + game.richPresencePatch = query.getColumn(4).getString(); + game.consoleId = query.getColumn(5); + game.achievementSets = getAchievementSetsByGameId(game.id); + + return game; + } + + return std::nullopt; + } catch (const std::exception &e) { + spdlog::error("Failed to get game by id: {}", e.what()); + return std::nullopt; + } +} + +bool SqliteAchievementRepository::create(const Game game) { + try { + SQLite::Statement query( + *m_database, "INSERT INTO games " + "(id, title, image_icon_url, rich_presence_game_id, " + "rich_presence_patch, console_id) " + "VALUES (:id, :title, :imageIconUrl, :richPresenceGameId, " + ":richPresencePatch, :consoleId) " + "ON CONFLICT(id) DO UPDATE SET " + "title = excluded.title, " + "image_icon_url = excluded.image_icon_url, " + "rich_presence_game_id = excluded.rich_presence_game_id, " + "rich_presence_patch = excluded.rich_presence_patch, " + "console_id = excluded.console_id"); + query.bind(":id", game.id); + query.bind(":title", game.title); + query.bind(":imageIconUrl", game.imageIconUrl); + query.bind(":richPresenceGameId", game.richPresenceGameId); + query.bind(":richPresencePatch", game.richPresencePatch); + query.bind(":consoleId", game.consoleId); + + query.exec(); + return true; + } catch (const std::exception &e) { + spdlog::error("Failed to createOrUpdate game: {}", e.what()); + return false; + } +} + bool SqliteAchievementRepository::setGameId(const std::string &contentHash, - int gameId) { + const int gameId) { try { - SQLite::Statement query(*m_database, - "INSERT INTO hashes " - "(hash, achievement_set_id) " - "VALUES (:contentHash, :gameId) " - "ON CONFLICT(hash) DO UPDATE SET " - "achievement_set_id = excluded.achievement_set_id"); + SQLite::Statement query(*m_database, "INSERT INTO game_hashes " + "(hash, game_id) " + "VALUES (:contentHash, :gameId) " + "ON CONFLICT(hash) DO UPDATE SET " + "game_id = excluded.game_id"); query.bind(":contentHash", contentHash); query.bind(":gameId", gameId); @@ -429,43 +547,55 @@ SqliteAchievementRepository::getAchievementSetByContentHash( const std::string &contentHash) const { try { SQLite::Statement query( - *m_database, - "SELECT s.id, s.name, s.num_achievements, s.num_points, s.icon_url " - "FROM achievement_sets s " - "JOIN hashes h ON s.id = h.achievement_set_id " - "WHERE h.hash = :contentHash"); + *m_database, "SELECT s.id, s.title, s.type, s.game_id, " + "s.image_icon_url, s.num_achievements, s.num_points " + "FROM achievement_sets s " + "JOIN game_hashes h ON s.id = h.game_id " + "WHERE h.hash = :contentHash"); query.bind(":contentHash", contentHash); if (query.executeStep()) { AchievementSet achievementSet; achievementSet.id = query.getColumn(0); - achievementSet.name = query.getColumn(1).getString(); - achievementSet.numAchievements = query.getColumn(2); - achievementSet.totalPoints = query.getColumn(3); - achievementSet.iconUrl = query.getColumn(4).getString(); + achievementSet.title = query.getColumn(1).getString(); + achievementSet.type = query.getColumn(2).getString(); + achievementSet.gameId = query.getColumn(3); + achievementSet.imageIconUrl = query.getColumn(4).getString(); + achievementSet.numAchievements = query.getColumn(5); + achievementSet.totalPoints = query.getColumn(6); // Load achievements for this set SQLite::Statement achievementQuery( - *m_database, - "SELECT id, name, description, points, achievement_set_id, flags, " - "type, icon_url, display_order " - "FROM achievements " - "WHERE achievement_set_id = :gameId " - "ORDER BY display_order"); + *m_database, "SELECT id, achievement_set_id, mem_address, title, " + "description, points, " + "author, modified, created, badge_name, flags, type, " + "rarity, rarity_hardcore, " + "badge_url, badge_locked_url, display_order " + "FROM achievements " + "WHERE achievement_set_id = :gameId " + "ORDER BY display_order"); achievementQuery.bind(":gameId", achievementSet.id); unsigned totalPoints = 0; while (achievementQuery.executeStep()) { Achievement achievement; achievement.id = achievementQuery.getColumn(0); - achievement.name = achievementQuery.getColumn(1).getString(); - achievement.description = achievementQuery.getColumn(2).getString(); - achievement.points = achievementQuery.getColumn(3); - achievement.gameId = achievementQuery.getColumn(4); - achievement.flags = achievementQuery.getColumn(5); - achievement.type = achievementQuery.getColumn(6).getString(); - achievement.imageUrl = achievementQuery.getColumn(7).getString(); - achievement.displayOrder = achievementQuery.getColumn(8); + achievement.achievementSetId = achievementQuery.getColumn(1); + achievement.memAddr = achievementQuery.getColumn(2).getString(); + achievement.title = achievementQuery.getColumn(3).getString(); + achievement.description = achievementQuery.getColumn(4).getString(); + achievement.points = achievementQuery.getColumn(5); + achievement.author = achievementQuery.getColumn(6).getString(); + achievement.modified = achievementQuery.getColumn(7); + achievement.created = achievementQuery.getColumn(8); + achievement.badgeName = achievementQuery.getColumn(9).getString(); + achievement.flags = achievementQuery.getColumn(10); + achievement.type = achievementQuery.getColumn(11).getString(); + achievement.rarity = achievementQuery.getColumn(12).getDouble(); + achievement.rarityHardcore = achievementQuery.getColumn(13).getDouble(); + achievement.badgeUrl = achievementQuery.getColumn(14).getString(); + achievement.badgeLockedUrl = achievementQuery.getColumn(15).getString(); + achievement.displayOrder = achievementQuery.getColumn(16); if (achievement.flags == 3) { totalPoints += achievement.points; @@ -497,9 +627,9 @@ SqliteAchievementRepository::getAchievementSetByContentHash( std::optional SqliteAchievementRepository::getGameId(const std::string &contentHash) const { try { - SQLite::Statement query( - *m_database, "SELECT achievement_set_id FROM hashes " - "WHERE hash = :contentHash AND achievement_set_id != 0"); + SQLite::Statement query(*m_database, + "SELECT achievement_set_id FROM game_hashes " + "WHERE hash = :contentHash AND game_id != 0"); query.bind(":contentHash", contentHash); if (query.executeStep()) { @@ -513,10 +643,10 @@ SqliteAchievementRepository::getGameId(const std::string &contentHash) const { } } std::optional -SqliteAchievementRepository::getGameHash(unsigned gameId) const { +SqliteAchievementRepository::getGameHash(const unsigned gameId) const { try { - SQLite::Statement query(*m_database, "SELECT hash FROM hashes " - "WHERE achievement_set_id = :gameId"); + SQLite::Statement query(*m_database, "SELECT hash FROM game_hashes " + "WHERE game_id = :gameId"); query.bind(":gameId", gameId); if (query.executeStep()) { @@ -610,7 +740,7 @@ bool SqliteAchievementRepository::createOrUpdate(const UserUnlock unlock) { std::vector SqliteAchievementRepository::getAllUserUnlocks(const std::string &username, - unsigned gameId) const { + const unsigned gameId) const { std::vector unlocks; try { diff --git a/src/app/achievements/sqlite_achievement_repository.hpp b/src/app/achievements/sqlite_achievement_repository.hpp index 4b36968a..08eb46a2 100644 --- a/src/app/achievements/sqlite_achievement_repository.hpp +++ b/src/app/achievements/sqlite_achievement_repository.hpp @@ -58,7 +58,7 @@ class SqliteAchievementRepository final : public IAchievementRepository { * errors are logged and result in an empty vector return rather than throwing * exceptions. * - * SQL Query: `SELECT username, token, points, hardcore_points FROM users + * SQL Query: `SELECT username, token, points, score FROM users * ORDER BY username` * * @return Vector of User objects containing all users in alphabetical order @@ -74,7 +74,7 @@ class SqliteAchievementRepository final : public IAchievementRepository { * @see getUser() for retrieving a single user by username * @see createOrUpdateUser() for adding or updating user data */ - std::vector listUsers() const override; + [[nodiscard]] std::vector listUsers() const override; bool createOrUpdateUser(const User &user) override; @@ -91,18 +91,6 @@ class SqliteAchievementRepository final : public IAchievementRepository { */ bool create(AchievementSet achievementSet) override; - /** - * @brief Updates an existing achievement set's metadata - * - * Modifies the stored data for an achievement set that must already exist. - * Does not affect associated achievements. - * - * @param achievementSet The achievement set with updated data - * @return true if update succeeded, false if set doesn't exist or database - * error - */ - bool update(AchievementSet achievementSet) override; - /** * @brief Retrieves a complete achievement set by ID * @@ -111,10 +99,10 @@ class SqliteAchievementRepository final : public IAchievementRepository { * complete achievement data including all metadata fields. * * @param gameId The unique identifier of the achievement set - * @return The complete achievement set if found, std::nullopt otherwise + * @return The list of achievement sets if found, empty vector otherwise */ - std::optional - getAchievementSet(unsigned gameId) const override; + [[nodiscard]] std::vector + getAchievementSetsByGameId(unsigned gameId) const override; /** * @brief Retrieves achievement set by game content hash @@ -130,7 +118,11 @@ class SqliteAchievementRepository final : public IAchievementRepository { [[nodiscard]] std::optional getAchievementSetByContentHash(const std::string &contentHash) const override; - std::optional getGameId(const std::string &contentHash) const override; + [[nodiscard]] std::optional getGameById(int gameId) const override; + bool create(Game game) override; + + [[nodiscard]] std::optional + getGameId(const std::string &contentHash) const override; /** * @brief Retrieves the content hash associated with an achievement set ID diff --git a/src/app/achievements/user_achievement_status.hpp b/src/app/achievements/user_achievement_status.hpp deleted file mode 100644 index 54c0cdf2..00000000 --- a/src/app/achievements/user_achievement_status.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include - -namespace firelight::achievements { -struct UserAchievementStatus { - unsigned achievementId = 0; - bool achieved = false; - bool achievedHardcore = false; - int64_t timestamp = 0; - int64_t timestampHardcore = 0; -}; -} // namespace firelight::achievements \ No newline at end of file diff --git a/src/app/rcheevos/offline/cached_achievement.hpp b/src/app/rcheevos/offline/cached_achievement.hpp deleted file mode 100644 index 857c201f..00000000 --- a/src/app/rcheevos/offline/cached_achievement.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -namespace firelight::achievements { - struct CachedAchievement { - unsigned ID{}; - unsigned GameID{}; - unsigned long long When{}; - unsigned long long WhenHardcore{}; - int Points{}; - bool Earned{}; - bool EarnedHardcore{}; - bool Synced{}; - bool SyncedHardcore{}; - }; -} diff --git a/src/app/rcheevos/offline/earned_achievement.hpp b/src/app/rcheevos/offline/earned_achievement.hpp deleted file mode 100644 index 6ad2ee51..00000000 --- a/src/app/rcheevos/offline/earned_achievement.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once -#include -#include - -namespace firelight::achievements { - struct EarnedAchievement { - int id; - int gameId; - long long earnedTimestampEpochMs; - std::string hash; - bool hardcore; - }; -} // namespace firelight::retroachievements diff --git a/src/app/rcheevos/offline/rcheevos_offline_client.cpp b/src/app/rcheevos/offline/rcheevos_offline_client.cpp index e70cd93b..80e6ea0e 100644 --- a/src/app/rcheevos/offline/rcheevos_offline_client.cpp +++ b/src/app/rcheevos/offline/rcheevos_offline_client.cpp @@ -6,7 +6,6 @@ #include "../patch_response.hpp" #include "../ra_constants.h" #include "../startsession_response.hpp" -#include "cached_achievement.hpp" #include #include @@ -228,9 +227,9 @@ RetroAchievementsOfflineClient::handleAwardAchievementRequest( // Add points for first-time unlock if (hardcore) { - user->hardcore_points += achievement->points; + user->score += achievement->points; } else { - user->points += achievement->points; + user->softcoreScore += achievement->points; } m_achievementService.createOrUpdate(newUnlock); @@ -251,12 +250,12 @@ RetroAchievementsOfflineClient::handleAwardAchievementRequest( // If already earned in non-hardcore, move points from non-hardcore to // hardcore if (unlock->earned && !unlock->earnedHardcore) { - user->points -= achievement->points; - user->hardcore_points += achievement->points; + user->softcoreScore -= achievement->points; + user->score += achievement->points; } // If never earned before, add to hardcore else if (!unlock->earned && !unlock->earnedHardcore) { - user->hardcore_points += achievement->points; + user->score += achievement->points; } unlock->earned = true; @@ -269,7 +268,7 @@ RetroAchievementsOfflineClient::handleAwardAchievementRequest( } else { // Only add points to non-hardcore if not already earned in any mode if (!unlock->earned && !unlock->earnedHardcore) { - user->points += achievement->points; + user->softcoreScore += achievement->points; } unlock->earned = true; @@ -281,8 +280,8 @@ RetroAchievementsOfflineClient::handleAwardAchievementRequest( m_achievementService.createOrUpdateUser(*user); } - auto unlocks = - m_achievementService.getAllUserUnlocks(username, achievement->gameId); + auto unlocks = m_achievementService.getAllUserUnlocks( + username, achievement->achievementSetId); auto numLocked = 0; for (const auto &u : unlocks) { @@ -293,8 +292,8 @@ RetroAchievementsOfflineClient::handleAwardAchievementRequest( // Build the response AwardAchievementResponse resp{.Success = true, - .Score = user->hardcore_points, - .SoftcoreScore = user->points, + .Score = user->score, + .SoftcoreScore = user->softcoreScore, .AchievementID = achievementId, .AchievementsRemaining = numLocked}; @@ -316,7 +315,7 @@ rc_api_server_response_t RetroAchievementsOfflineClient::handleLogin2Request( } auto user = User{ - .username = username, .token = token, .points = 0, .hardcore_points = 0}; + .username = username, .token = token, .softcoreScore = 0, .score = 0}; auto userOpt = m_achievementService.getUser(username); if (!userOpt.has_value()) { m_achievementService.createOrUpdateUser(user); @@ -327,8 +326,8 @@ rc_api_server_response_t RetroAchievementsOfflineClient::handleLogin2Request( Login2Response response{.AccountType = "1", .Messages = 0, .Permissions = 0, - .Score = user.hardcore_points, - .SoftcoreScore = user.points, + .Score = user.score, + .SoftcoreScore = user.softcoreScore, .Success = true, .Token = token, // TODO: uhh .User = username}; @@ -366,11 +365,11 @@ void RetroAchievementsOfflineClient::processLogin2Response( } if (json.contains("Score") && json["Score"].is_number()) { - user.hardcore_points = json["Score"]; + user.score = json["Score"]; } if (json.contains("SoftcoreScore") && json["SoftcoreScore"].is_number()) { - user.points = json["SoftcoreScore"]; + user.softcoreScore = json["SoftcoreScore"]; } m_achievementService.createOrUpdateUser(user); @@ -429,11 +428,11 @@ void RetroAchievementsOfflineClient::processAwardAchievementResponse( } if (json.contains("Score") && json["Score"].is_number()) { - user.hardcore_points = json["Score"]; + user.score = json["Score"]; } if (json.contains("SoftcoreScore") && json["SoftcoreScore"].is_number()) { - user.points = json["SoftcoreScore"]; + user.softcoreScore = json["SoftcoreScore"]; } m_achievementService.createOrUpdateUser(user); diff --git a/src/app/rcheevos/patch_response.hpp b/src/app/rcheevos/patch_response.hpp index 3b360e54..5a808d6e 100644 --- a/src/app/rcheevos/patch_response.hpp +++ b/src/app/rcheevos/patch_response.hpp @@ -4,156 +4,793 @@ #include namespace firelight::achievements { - // {\"ID\":1998,\"Mem\":\"STA:0xh13bf=h44_0xh1411=1_0xhf31=3_0xRda4=1::CAN:0xh77>0::SUB:0xh1dff=hc::VAL:0xhf30_0xhf33*100_0xhf32*1000_0xhf31*10000\",\"Format\":\"VALUE\",\"LowerIsBetter\":0,\"Title\":\"Everything Is Lava Achievement\",\"Description\":\"Wall touch alert and best time (does not track enemy contact)\",\"Hidden\":true} - struct PatchLeaderboard { - int ID{}; - std::string Mem{}; - std::string Format{}; - int LowerIsBetter{}; - std::string Title{}; - std::string Description{}; - bool Hidden{}; - }; - - NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(PatchLeaderboard, ID, Mem, Format, LowerIsBetter, Title, Description, Hidden) - - struct PatchAchievement { - unsigned ID{}; - std::string MemAddr{}; - std::string Title{}; - std::string Description{}; - int Points{}; - std::string Author{}; - int Modified{}; - int Created{}; - std::string BadgeName{}; - int Flags{}; - std::string Type{}; - int Rarity{}; - int RarityHardcore{}; - std::string BadgeURL{}; - std::string BadgeLockedURL{}; - }; - - inline void from_json(const nlohmann::json &j, PatchAchievement &p) { - j.at("ID").get_to(p.ID); - if (j.contains("MemAddr") && !j.at("MemAddr").is_null()) { - j.at("MemAddr").get_to(p.MemAddr); - } - if (j.contains("Title") && !j.at("Title").is_null()) { - j.at("Title").get_to(p.Title); - } - if (j.contains("Description") && !j.at("Description").is_null()) { - j.at("Description").get_to(p.Description); - } - - j.at("Points").get_to(p.Points); - - if (j.contains("Author") && !j.at("Author").is_null()) { - j.at("Author").get_to(p.Author); - } - - j.at("Modified").get_to(p.Modified); - j.at("Created").get_to(p.Created); - - if (j.contains("BadgeName") && !j.at("BadgeName").is_null()) { - j.at("BadgeName").get_to(p.BadgeName); - } - - j.at("Flags").get_to(p.Flags); - - if (j.contains("Type") && !j.at("Type").is_null()) { - j.at("Type").get_to(p.Type); - } - - j.at("Rarity").get_to(p.Rarity); - j.at("RarityHardcore").get_to(p.RarityHardcore); - - if (j.contains("BadgeURL") && !j.at("BadgeURL").is_null()) { - j.at("BadgeURL").get_to(p.BadgeURL); - } - - if (j.contains("BadgeLockedURL") && !j.at("BadgeLockedURL").is_null()) { - j.at("BadgeLockedURL").get_to(p.BadgeLockedURL); - } - } - - inline void to_json(nlohmann::json &j, const PatchAchievement &p) { - j = nlohmann::json{ - {"ID", p.ID}, - {"MemAddr", p.MemAddr}, - {"Title", p.Title}, - {"Description", p.Description}, - {"Points", p.Points}, - {"Author", p.Author}, - {"Modified", p.Modified}, - {"Created", p.Created}, - {"BadgeName", p.BadgeName}, - {"Flags", p.Flags}, - {"Type", p.Type}, - {"Rarity", p.Rarity}, - {"RarityHardcore", p.RarityHardcore}, - {"BadgeURL", p.BadgeURL}, - {"BadgeLockedURL", p.BadgeLockedURL} - }; - } - - struct PatchDataStruct { - unsigned ID{}; - std::string Title{}; - std::string ImageIcon{}; - std::string RichPresencePatch{}; - int ConsoleID{}; - std::string ImageIconURL{}; - std::vector Achievements{}; - std::vector Leaderboards{}; - }; - - - inline void from_json(const nlohmann::json &j, PatchDataStruct &p) { - j.at("ID").get_to(p.ID); - if (j.contains("Title") && !j.at("Title").is_null()) { - j.at("Title").get_to(p.Title); - } - if (j.contains("ImageIcon") && !j.at("ImageIcon").is_null()) { - j.at("ImageIcon").get_to(p.ImageIcon); - } - if (j.contains("RichPresencePatch") && !j.at("RichPresencePatch").is_null()) { - j.at("RichPresencePatch").get_to(p.RichPresencePatch); - } - j.at("ConsoleID").get_to(p.ConsoleID); - if (j.contains("ImageIconURL") && !j.at("ImageIconURL").is_null()) { - j.at("ImageIconURL").get_to(p.ImageIconURL); - } - j.at("Achievements").get_to(p.Achievements); - if (j.contains("Leaderboards") && !j.at("Leaderboards").is_null()) { - j.at("Leaderboards").get_to(p.Leaderboards); - } - } - - inline void to_json(nlohmann::json &j, const PatchDataStruct &p) { - j = nlohmann::json{ - {"ID", p.ID}, - {"Title", p.Title}, - {"ImageIcon", p.ImageIcon}, - {"RichPresencePatch", p.RichPresencePatch}, - {"ConsoleID", p.ConsoleID}, - {"ImageIconURL", p.ImageIconURL}, - {"Achievements", p.Achievements}, - {"Leaderboards", p.Leaderboards} - }; - } - - struct PatchResponse { - bool Success{}; - PatchDataStruct PatchData{}; - }; - - // NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(PatchAchievement, ID, MemAddr, Title, Description, Points, Author, Modified, Created, - // BadgeName, Flags, Type, Rarity, RarityHardcore, BadgeURL, BadgeLockedURL) - // NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(PatchDataStruct, ID, Title, ImageIcon, RichPresencePatch, ConsoleID, - // ImageIconURL, Achievements) - NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(PatchResponse, Success, PatchData) +// {\"ID\":1998,\"Mem\":\"STA:0xh13bf=h44_0xh1411=1_0xhf31=3_0xRda4=1::CAN:0xh77>0::SUB:0xh1dff=hc::VAL:0xhf30_0xhf33*100_0xhf32*1000_0xhf31*10000\",\"Format\":\"VALUE\",\"LowerIsBetter\":0,\"Title\":\"Everything +// Is Lava Achievement\",\"Description\":\"Wall touch alert and best time (does +// not track enemy contact)\",\"Hidden\":true} +struct PatchLeaderboard { + int ID{}; + std::string Mem{}; + std::string Format{}; + int LowerIsBetter{}; + std::string Title{}; + std::string Description{}; + bool Hidden{}; +}; + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(PatchLeaderboard, ID, Mem, Format, + LowerIsBetter, Title, Description, Hidden) + +struct PatchAchievement { + unsigned ID{}; + std::string MemAddr{}; + std::string Title{}; + std::string Description{}; + unsigned Points{}; + std::string Author{}; + unsigned Modified{}; + unsigned Created{}; + std::string BadgeName{}; + int Flags{}; + std::string Type{}; + float Rarity{}; + float RarityHardcore{}; + std::string BadgeURL{}; + std::string BadgeLockedURL{}; +}; + +inline void from_json(const nlohmann::json &j, PatchAchievement &p) { + j.at("ID").get_to(p.ID); + if (j.contains("MemAddr") && !j.at("MemAddr").is_null()) { + j.at("MemAddr").get_to(p.MemAddr); + } + if (j.contains("Title") && !j.at("Title").is_null()) { + j.at("Title").get_to(p.Title); + } + if (j.contains("Description") && !j.at("Description").is_null()) { + j.at("Description").get_to(p.Description); + } + + j.at("Points").get_to(p.Points); + + if (j.contains("Author") && !j.at("Author").is_null()) { + j.at("Author").get_to(p.Author); + } + + j.at("Modified").get_to(p.Modified); + j.at("Created").get_to(p.Created); + + if (j.contains("BadgeName") && !j.at("BadgeName").is_null()) { + j.at("BadgeName").get_to(p.BadgeName); + } + + j.at("Flags").get_to(p.Flags); + + if (j.contains("Type") && !j.at("Type").is_null()) { + j.at("Type").get_to(p.Type); + } + + j.at("Rarity").get_to(p.Rarity); + j.at("RarityHardcore").get_to(p.RarityHardcore); + + if (j.contains("BadgeURL") && !j.at("BadgeURL").is_null()) { + j.at("BadgeURL").get_to(p.BadgeURL); + } + + if (j.contains("BadgeLockedURL") && !j.at("BadgeLockedURL").is_null()) { + j.at("BadgeLockedURL").get_to(p.BadgeLockedURL); + } +} + +inline void to_json(nlohmann::json &j, const PatchAchievement &p) { + j = nlohmann::json{{"ID", p.ID}, + {"MemAddr", p.MemAddr}, + {"Title", p.Title}, + {"Description", p.Description}, + {"Points", p.Points}, + {"Author", p.Author}, + {"Modified", p.Modified}, + {"Created", p.Created}, + {"BadgeName", p.BadgeName}, + {"Flags", p.Flags}, + {"Type", p.Type}, + {"Rarity", p.Rarity}, + {"RarityHardcore", p.RarityHardcore}, + {"BadgeURL", p.BadgeURL}, + {"BadgeLockedURL", p.BadgeLockedURL}}; +} + +struct PatchDataStruct { + unsigned ID{}; + std::string Title{}; + std::string ImageIcon{}; + std::string RichPresencePatch{}; + int ConsoleID{}; + std::string ImageIconURL{}; + std::vector Achievements{}; + std::vector Leaderboards{}; +}; + +inline void from_json(const nlohmann::json &j, PatchDataStruct &p) { + j.at("ID").get_to(p.ID); + if (j.contains("Title") && !j.at("Title").is_null()) { + j.at("Title").get_to(p.Title); + } + if (j.contains("ImageIcon") && !j.at("ImageIcon").is_null()) { + j.at("ImageIcon").get_to(p.ImageIcon); + } + if (j.contains("RichPresencePatch") && !j.at("RichPresencePatch").is_null()) { + j.at("RichPresencePatch").get_to(p.RichPresencePatch); + } + j.at("ConsoleID").get_to(p.ConsoleID); + if (j.contains("ImageIconURL") && !j.at("ImageIconURL").is_null()) { + j.at("ImageIconURL").get_to(p.ImageIconURL); + } + j.at("Achievements").get_to(p.Achievements); + if (j.contains("Leaderboards") && !j.at("Leaderboards").is_null()) { + j.at("Leaderboards").get_to(p.Leaderboards); + } +} + +inline void to_json(nlohmann::json &j, const PatchDataStruct &p) { + j = nlohmann::json{{"ID", p.ID}, + {"Title", p.Title}, + {"ImageIcon", p.ImageIcon}, + {"RichPresencePatch", p.RichPresencePatch}, + {"ConsoleID", p.ConsoleID}, + {"ImageIconURL", p.ImageIconURL}, + {"Achievements", p.Achievements}, + {"Leaderboards", p.Leaderboards}}; } -// "{\"Success\":true,\"PatchData\":{\"ID\":228,\"Title\":\"Super Mario World\",\"ImageIcon\":\"\\/Images\\/066393.png\",\"RichPresencePatch\":\"\",\"ConsoleID\":3,\"ImageIconURL\":\"https:\\/\\/media.retroachievements.org\\/Images\\/066393.png\",\"Achievements\":[{\"ID\":4934,\"MemAddr\":\"R:0xH0013c7=4\",\"Title\":\"Yellow Yoshi (old prototype)\",\"Description\":\"old abandoned prototype\",\"Points\":0,\"Author\":\"UNHchabo\",\"Modified\":1504474480,\"Created\":1392011140,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":4935,\"MemAddr\":\"R:0xH0013c7=6\",\"Title\":\"Blue Yoshi (old prototype)\",\"Description\":\"old abandoned prototype\",\"Points\":0,\"Author\":\"UNHchabo\",\"Modified\":1504474488,\"Created\":1392011155,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":4936,\"MemAddr\":\"R:0xH0013c7=8\",\"Title\":\"Red Yoshi (old prototype)\",\"Description\":\"old abandoned prototype\",\"Points\":0,\"Author\":\"UNHchabo\",\"Modified\":1504474493,\"Created\":1392011190,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":4937,\"MemAddr\":\"R:0xH000dbe>=98_R:0xH000019=1\",\"Title\":\"Super 99 (old prototype)\",\"Description\":\"Get 99 Lives and be not small\",\"Points\":0,\"Author\":\"UNHchabo\",\"Modified\":1504474496,\"Created\":1392018537,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":5756,\"MemAddr\":\"R:0xH0013c5>0\",\"Title\":\"Moon (old prototype)\",\"Description\":\"Collect a 3-up Moon\",\"Points\":0,\"Author\":\"UNHchabo\",\"Modified\":1504474501,\"Created\":1393710657,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":45815,\"MemAddr\":\"0xT001ecc>d0xT001ecc.1._R:0xH000db5=0_R:0xH000db3=1_0xT001ec9>d0xT001ec9.1._0xT001ec8>d0xT001ec8.1._0xT001ec7>d0xT001ec7.1._R:0xH000019>0_R:0xH001490>0_R:0xH001f28>0_R:0xH001f29>0_R:0x 001f2a>0_0xH001ecc=0.1._R:0xH00187a>0_0xT001eb7>d0xT001eb7.1._0xT001eab>d0xT001eab.1._0xT001ea6>d0xT001ea6.1._0xT001ea8>d0xT001ea8.1._0xT001ea9>d0xT001ea9.1._0xT001ee0>d0xT001ee0.1._0xT001ede>d0xT001ede.1._0xT001ed0>d0xT001ed0.1._0xT001edf>d0xT001edf.1._0xT001ee2>d0xT001ee2.1._0xT001ea7>d0xT001ea7.1.\",\"Title\":\"Mini-Mario vs the World 1 (duplicate)\",\"Description\":\"not an achievement is a duplicate.\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474529,\"Created\":1488837144,\"BadgeName\":\"46672\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46672.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46672_lock.png\"},{\"ID\":45857,\"MemAddr\":\"R:0xH000100<=10_R:0xH000db3=1_R:0xH000dbed0xT001ecb.1._0xT001ecc>d0xT001ecc.1.\",\"Title\":\"Deathless Two Stages (prototype)\",\"Description\":\"prototype - not an achievement\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474132,\"Created\":1488840902,\"BadgeName\":\"00000\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00000.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00000_lock.png\"},{\"ID\":45998,\"MemAddr\":\"R:0xH0013ef=1_R:0xH0013bf!=1_0xH000dbe>d0xH000dbe.20._0xH001411=1\",\"Title\":\"I could do this all day (unofficial)\",\"Description\":\"Get 20 1-ups on Vanilla Secret 2 without touching the ground\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474504,\"Created\":1489073027,\"BadgeName\":\"46617\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46617.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46617_lock.png\"},{\"ID\":46009,\"MemAddr\":\"R:0xH0013bf!=42_d0xH0014c9=8_d0xH0014ca=8_d0xH0014cb=8_d0xH0014cc=8_d0xH0014cd=8_d0xH0014ce=8_d0xH0014cf=8_d0xH0014d0=8_0xH0014c9=9_0xH0014ca=9_0xH0014cb=9_0xH0014cc=9_0xH0014cd=9_0xH0014ce=9_0xH0014cf=9_0xH0014d0=9_0xH0013ef=1_0x 001408=51201\",\"Title\":\"A Mighty Quake (duplicate)\",\"Description\":\"(duplicate)\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474509,\"Created\":1489073081,\"BadgeName\":\"46678\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46678.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46678_lock.png\"},{\"ID\":46011,\"MemAddr\":\"R:0xH0013bf!=76_0xH001dff=12_R:0xH0013ef=1_0x 000094<464.1._R:0xH00187a>0\",\"Title\":\"A Groovy Flight (duplicate)\",\"Description\":\"Pass SPECIAL-Groovy flying. No landing past the first ? box. No Yoshi\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474513,\"Created\":1489086171,\"BadgeName\":\"46671\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46671.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46671_lock.png\"},{\"ID\":46062,\"MemAddr\":\"0xH0013f3=1.20._0xH001411=1.20._R:0xH001411=0_0xH000019!=0_R:0xH000019=0\",\"Title\":\"Another Kind of Flying (legacy)\",\"Description\":\"Collect a P-Balloon\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474516,\"Created\":1489171212,\"BadgeName\":\"46564\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46564.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46564_lock.png\"},{\"ID\":46119,\"MemAddr\":\"P:0xH0013bf!=49_0xH000065=6.1._P:0xH000100!=15_0xH000065=100.1._0xH000065=1.1._0xH000065=62.1._0xH000065=96.1._0xH000065=21.1._0xH000065=118.1._0xH000065=168.1.\",\"Title\":\"Bowser Castle Explorer (dup)\",\"Description\":\"Complete all 8 numbered rooms in one game session\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474465,\"Created\":1489285071,\"BadgeName\":\"46769\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46769.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46769_lock.png\"},{\"ID\":46330,\"MemAddr\":\"R:0xH0013bf!=46_R:0x 000094<3926_R:0x 000094>4512_R:0xH0014d0=2_R:0xH0014d0=3_R:0xH0014d0=4_0x 000094>4369_0xH0014d0=8\",\"Title\":\"Suicide Prevention (duplicate)\",\"Description\":\"O n Vanilla Dome 3 Stop the koopa by the midflag from getting killed\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474531,\"Created\":1489701495,\"BadgeName\":\"00000\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00000.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00000_lock.png\"},{\"ID\":59018,\"MemAddr\":\"B:0xH0dbf=0_d0xH0dbf=4294967295_0xH0dbf=5\",\"Title\":\"bug\",\"Description\":\"buggy\",\"Points\":1,\"Author\":\"April\",\"Modified\":1549934619,\"Created\":1522626529,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":85062,\"MemAddr\":\"0xH000071=9\",\"Title\":\"I failed you, my princess\",\"Description\":\"die for the first time\",\"Points\":0,\"Author\":\"matisteve\",\"Modified\":1570325049,\"Created\":1570325049,\"BadgeName\":\"92297\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/92297.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/92297_lock.png\"},{\"ID\":85977,\"MemAddr\":\"0xH000071=9\",\"Title\":\"I failed you, my princess\",\"Description\":\"die for the first time\",\"Points\":0,\"Author\":\"matisteveTWO\",\"Modified\":1571010788,\"Created\":1571010788,\"BadgeName\":\"92297\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/92297.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/92297_lock.png\"},{\"ID\":102935,\"MemAddr\":\"0xH000dbf=99\",\"Title\":\"A Man's Worth of Coins\",\"Description\":\"Have 99 coins. Simple enough.\",\"Points\":2,\"Author\":\"ApplemunchRA\",\"Modified\":1584212198,\"Created\":1584212198,\"BadgeName\":\"00000\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00000.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00000_lock.png\"},{\"ID\":113670,\"MemAddr\":\"0xH000065=51_Q:0xH0013bf=7\",\"Title\":\"Hey, look There's a shortcut here!\",\"Description\":\"Find the secret room on #2 Castle\",\"Points\":5,\"Author\":\"Koakuma\",\"Modified\":1591759773,\"Created\":1591759773,\"BadgeName\":\"124010\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/124010.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/124010_lock.png\"},{\"ID\":128311,\"MemAddr\":\"0xH000f2c>=252\",\"Title\":\"Wellcome\",\"Description\":\"Open Super Mario Word\",\"Points\":1,\"Author\":\"freeman016\",\"Modified\":1602001511,\"Created\":1602001511,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":128312,\"MemAddr\":\"0xH000f2c>=252\",\"Title\":\"Wellcome\",\"Description\":\"Open the game\",\"Points\":1,\"Author\":\"freeman016\",\"Modified\":1602004389,\"Created\":1602004389,\"BadgeName\":\"141067\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/141067.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/141067_lock.png\"},{\"ID\":342,\"MemAddr\":\"0xH000dc1=1_0xH0013bf>0\",\"Title\":\"Giddy Up!\",\"Description\":\"Catch a ride with a friend\",\"Points\":1,\"Author\":\"Scott\",\"Modified\":1561707447,\"Created\":1367266931,\"BadgeName\":\"46580\",\"Flags\":3,\"Type\":null,\"Rarity\":94.83,\"RarityHardcore\":46.63,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46580.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46580_lock.png\"},{\"ID\":341,\"MemAddr\":\"0x 001420=5\",\"Title\":\"Unleash The Dragon\",\"Description\":\"Collect 5 Dragon Coins in a level\",\"Points\":2,\"Author\":\"Scott\",\"Modified\":1561707451,\"Created\":1367266583,\"BadgeName\":\"46591\",\"Flags\":3,\"Type\":null,\"Rarity\":84.33,\"RarityHardcore\":42.96,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46591.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46591_lock.png\"},{\"ID\":340,\"MemAddr\":\"0xH000dbf=99\",\"Title\":\"Rich Mario (demoted)\",\"Description\":\"Collect 99 coins\",\"Points\":0,\"Author\":\"Scott\",\"Modified\":1504474537,\"Created\":1367254976,\"BadgeName\":\"46582\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46582.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46582_lock.png\"},{\"ID\":4874,\"MemAddr\":\"0xH000019=2\",\"Title\":\"I Believe I Can Fly\",\"Description\":\"Collect a feather\",\"Points\":1,\"Author\":\"UNHchabo\",\"Modified\":1564447174,\"Created\":1391908064,\"BadgeName\":\"46577\",\"Flags\":3,\"Type\":null,\"Rarity\":60.99,\"RarityHardcore\":32.97,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46577.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46577_lock.png\"},{\"ID\":2251,\"MemAddr\":\"0xH0013f3=1_0xH000dda!=0\",\"Title\":\"Another Kind of Flying\",\"Description\":\"Collect a P-Balloon\",\"Points\":2,\"Author\":\"Jaarl\",\"Modified\":1564448705,\"Created\":1376615078,\"BadgeName\":\"47083\",\"Flags\":3,\"Type\":null,\"Rarity\":34.48,\"RarityHardcore\":20.7,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47083.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47083_lock.png\"},{\"ID\":4933,\"MemAddr\":\"0xH0018c2=1\",\"Title\":\"Floating Through The Clouds\",\"Description\":\"Hijack a Lakitu's cloud\",\"Points\":2,\"Author\":\"UNHchabo\",\"Modified\":1564450921,\"Created\":1392010935,\"BadgeName\":\"46571\",\"Flags\":3,\"Type\":null,\"Rarity\":22.91,\"RarityHardcore\":14.7,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46571.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46571_lock.png\"},{\"ID\":1706,\"MemAddr\":\"0xH001900=80\",\"Title\":\"Maximum Finish\",\"Description\":\"Cross the finish line at the end of the stage and collect the max 50 stars\",\"Points\":5,\"Author\":\"jackolantern\",\"Modified\":1561707461,\"Created\":1372674230,\"BadgeName\":\"47084\",\"Flags\":3,\"Type\":null,\"Rarity\":13.06,\"RarityHardcore\":8.48,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47084.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47084_lock.png\"},{\"ID\":2246,\"MemAddr\":\"0xH000f31=0.20._R:0xH000f31!=0_0xH000f32=0.20._R:0xH000f32!=0_0xH000f33=0.20._R:0xH000f33!=0_0xH000dbe>d0xH000dbe.8._0xH001411=1.20._R:0xH001411=0\",\"Title\":\"Perfect Bonus Stage\",\"Description\":\"Score 8 extra lives in the 'Bonus Game'\",\"Points\":10,\"Author\":\"Jaarl\",\"Modified\":1561707465,\"Created\":1376582613,\"BadgeName\":\"47085\",\"Flags\":3,\"Type\":null,\"Rarity\":12.36,\"RarityHardcore\":7.14,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47085.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47085_lock.png\"},{\"ID\":46003,\"MemAddr\":\"0xH0018da=106_0xH0018e3=10_0xH001411=1S0xH0016e1=13S0xH0016e2=13S0xH0016e3=13S0xH0016e4=13S0xH0016e5=13S0xH0016e6=13\",\"Title\":\"Yoshi's a Mommy?\",\"Description\":\"Help Yoshi lay a cloud egg and get 10 happy coins to summon a 1up\",\"Points\":5,\"Author\":\"kdecks\",\"Modified\":1603575778,\"Created\":1489073051,\"BadgeName\":\"46668\",\"Flags\":3,\"Type\":null,\"Rarity\":8.86,\"RarityHardcore\":6.66,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46668.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46668_lock.png\"},{\"ID\":2253,\"MemAddr\":\"0xH0013bf=37_0xH000dd5=1\",\"Title\":\"I is for Icky Iggy\",\"Description\":\"Defeat Iggy Koopa of Castle #1\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1564806766,\"Created\":1376616356,\"BadgeName\":\"46598\",\"Flags\":3,\"Type\":\"progression\",\"Rarity\":62.79,\"RarityHardcore\":34.54,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46598.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46598_lock.png\"},{\"ID\":347,\"MemAddr\":\"0xH0013bf=7_0xH000dd5=1\",\"Title\":\"Morton Enough\",\"Description\":\"Defeat Morton Koopa Jr. of Castle #2\",\"Points\":5,\"Author\":\"Scott\",\"Modified\":1561707476,\"Created\":1367322700,\"BadgeName\":\"46599\",\"Flags\":3,\"Type\":null,\"Rarity\":36.4,\"RarityHardcore\":20.97,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46599.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46599_lock.png\"},{\"ID\":2261,\"MemAddr\":\"0xH0013bf=64_0xH000dd5=1\",\"Title\":\"Lemmy Down Slowly\",\"Description\":\"Defeat Lemmy Koopa of Castle #3\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1561707478,\"Created\":1376652522,\"BadgeName\":\"46600\",\"Flags\":3,\"Type\":null,\"Rarity\":24.01,\"RarityHardcore\":14.45,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46600.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46600_lock.png\"},{\"ID\":2262,\"MemAddr\":\"0xH0013bf=14_0xH000dd5=1\",\"Title\":\"Ludwig's Last Symphony\",\"Description\":\"Defeat Ludwig von Koopa of Castle #4\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1561707482,\"Created\":1376653163,\"BadgeName\":\"46601\",\"Flags\":3,\"Type\":null,\"Rarity\":22.74,\"RarityHardcore\":13.62,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46601.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46601_lock.png\"},{\"ID\":2306,\"MemAddr\":\"0xH0013bf=32_0xH000dd5=1\",\"Title\":\"Roy's Ploy Destroy-ed\",\"Description\":\"Defeat Roy Koopa of Castle #5\",\"Points\":10,\"Author\":\"Jaarl\",\"Modified\":1561707484,\"Created\":1376938808,\"BadgeName\":\"46602\",\"Flags\":3,\"Type\":null,\"Rarity\":17.77,\"RarityHardcore\":10.93,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46602.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46602_lock.png\"},{\"ID\":2307,\"MemAddr\":\"0xH0013bf=31.20._0xH000906=1.20._R:0xH001411!=1\",\"Title\":\"Reznor Again? (demoted)\",\"Description\":\"Defeat the Reznor in the clearing of the Forest of Illusion\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474541,\"Created\":1376938811,\"BadgeName\":\"46597\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597_lock.png\"},{\"ID\":2309,\"MemAddr\":\"0xH000906=1.400._R:0xH0013bf!=26\",\"Title\":\"Wendy Chips Are Down\",\"Description\":\"Defeat Wendy O. Koopa of Castle #6\",\"Points\":10,\"Author\":\"Jaarl\",\"Modified\":1561707487,\"Created\":1376939582,\"BadgeName\":\"46603\",\"Flags\":3,\"Type\":null,\"Rarity\":15.19,\"RarityHardcore\":9.48,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46603.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46603_lock.png\"},{\"ID\":2308,\"MemAddr\":\"0xH0013bf=27.20._0xH000906=1.20._R:0xH001411!=1\",\"Title\":\"Reznor, Do You Ever Give Up? (dmt)\",\"Description\":\"(demoted) Defeat the Reznor at the center of Chocolate Island\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474546,\"Created\":1376938815,\"BadgeName\":\"46597\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597_lock.png\"},{\"ID\":2342,\"MemAddr\":\"0xH0013bf=52_0xH000dd5=1\",\"Title\":\"Larry in the Airy\",\"Description\":\"Defeat Larry Koopa of Castle #7\",\"Points\":10,\"Author\":\"Jaarl\",\"Modified\":1561707489,\"Created\":1376970283,\"BadgeName\":\"46604\",\"Flags\":3,\"Type\":null,\"Rarity\":13,\"RarityHardcore\":8.27,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46604.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46604_lock.png\"},{\"ID\":2275,\"MemAddr\":\"0xH0013f9=3_0xH0013ef=1_O:0xH0013bf=50_0xH0013bf=49\",\"Title\":\"Bowser Disposer\",\"Description\":\"Beat Bowser and save the princess\",\"Points\":25,\"Author\":\"Jaarl\",\"Modified\":1571582041,\"Created\":1376742802,\"BadgeName\":\"46760\",\"Flags\":3,\"Type\":\"win_condition\",\"Rarity\":16.14,\"RarityHardcore\":10.13,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46760.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46760_lock.png\"},{\"ID\":2338,\"MemAddr\":\"0xH0013bf=53.20._0xH000906=1.20._R:0xH001411!=1\",\"Title\":\"Reznor... (demoted)\",\"Description\":\"Defeat the Reznor in the Valley of Bowser\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474549,\"Created\":1376969412,\"BadgeName\":\"46597\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597_lock.png\"},{\"ID\":2299,\"MemAddr\":\"0xH001f11=0_0xH0013bf=19_0xH000dd5=2_0xH000dda=0\",\"Title\":\"Shoo! Shoo! Big Boo\",\"Description\":\"Find and defeat the hidden Big Boo in Donut Secret House\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1564459621,\"Created\":1376918022,\"BadgeName\":\"46596\",\"Flags\":3,\"Type\":null,\"Rarity\":24.52,\"RarityHardcore\":16.54,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46596.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46596_lock.png\"},{\"ID\":2250,\"MemAddr\":\"0xH0013bf=11_0xH000dd5=1\",\"Title\":\"Reznor - Flaming Wheel of Death\",\"Description\":\"Defeat the Reznor atop Vanilla Dome\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1575640223,\"Created\":1376615073,\"BadgeName\":\"46597\",\"Flags\":3,\"Type\":null,\"Rarity\":19.72,\"RarityHardcore\":12.66,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597_lock.png\"},{\"ID\":2276,\"MemAddr\":\"0xH0013bf=49_R:0xH0013bf!=49_0xH0013f9=3_R:0xH0013f9!=3_0xH0013ef=1_R:0xH0013ef!=1_0xH000019=0_R:0xH000019!=0\",\"Title\":\"Baby's First Kiss (demoted)\",\"Description\":\"Get the princess kiss as Little Mario (Front Door!)\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474554,\"Created\":1376742805,\"BadgeName\":\"46592\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46592.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46592_lock.png\"},{\"ID\":2277,\"MemAddr\":\"T:0xH0013f9=3_T:0xH0013ef=1_0xH000019=3_T:0xH0003da=131_0xH0013bf=49\",\"Title\":\"Burning Bowser\",\"Description\":\"Get the princess kiss as Fire Mario (Front Door!)\",\"Points\":10,\"Author\":\"Jaarl\",\"Modified\":1625960412,\"Created\":1376742808,\"BadgeName\":\"46593\",\"Flags\":3,\"Type\":null,\"Rarity\":6.07,\"RarityHardcore\":4.85,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46593.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46593_lock.png\"},{\"ID\":2278,\"MemAddr\":\"0xH0013bf=49_R:0xH0013bf!=49_0xH0013f9=3_R:0xH0013f9!=3_0xH0013ef=1_R:0xH0013ef!=1_0xH000019=2_R:0xH000019!=2\",\"Title\":\"Flying Finish (demoted)\",\"Description\":\"Get the princess kiss as Cape Mario (Front Door!)\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474556,\"Created\":1376742811,\"BadgeName\":\"46594\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46594.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46594_lock.png\"},{\"ID\":2345,\"MemAddr\":\"0xH0013ef=1_0xH0013f9=3_0xH0013bf=50\",\"Title\":\"Backdooring Bowser (demoted)\",\"Description\":\"Beat Bowser through the Back Door\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474560,\"Created\":1376973534,\"BadgeName\":\"47082\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47082.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47082_lock.png\"},{\"ID\":2199,\"MemAddr\":\"A:d0xH001f28_A:d0xH001f27_A:d0xH001f29_d0xH001f2a=3_A:0xH001f28_A:0xH001f27_A:0xH001f29_0xH001f2a=4_C:0xH001f27=1.1._C:0xH001f28=1.1._C:0xH001f29=1.1._C:0xH001f2a=1.1._M:0!=0.4._R:0x 001f28112_0xH000007<128_0xH000008>112_0xH000008<128\",\"Title\":\"I Could've Sworn... (demoted)\",\"Description\":\"Get lost in the Forest of Illusion\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474567,\"Created\":1376657732,\"BadgeName\":\"47070\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47070.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47070_lock.png\"},{\"ID\":2305,\"MemAddr\":\"0xH00001e=169_0xH000020=28_0xH001f11=0_0xH000dda=0_0xH0013d4=0\",\"Title\":\"Chocolate Donut\",\"Description\":\"Walk in a circle on Chocolate Island\",\"Points\":1,\"Author\":\"Jaarl\",\"Modified\":1571622934,\"Created\":1376938805,\"BadgeName\":\"47071\",\"Flags\":3,\"Type\":null,\"Rarity\":15.14,\"RarityHardcore\":9.61,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47071.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47071_lock.png\"},{\"ID\":2298,\"MemAddr\":\"0xH0013c3=6\",\"Title\":\"To the Stars!\",\"Description\":\"Reach the Star Road\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1504475594,\"Created\":1376918019,\"BadgeName\":\"47072\",\"Flags\":3,\"Type\":null,\"Rarity\":28.61,\"RarityHardcore\":18.04,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47072.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47072_lock.png\"},{\"ID\":2300,\"MemAddr\":\"0xH0013c3=5\",\"Title\":\"Mario's Special Place\",\"Description\":\"Get to the challenging Special Zone\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1504475595,\"Created\":1376918026,\"BadgeName\":\"47073\",\"Flags\":3,\"Type\":null,\"Rarity\":16.61,\"RarityHardcore\":11.08,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47073.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47073_lock.png\"},{\"ID\":2302,\"MemAddr\":\"N:d0xM001eea=0_0xM001eea=1.1._d0xH001f11=5_N:0xH001eec!=d0xH001eec_N:0xH001eed!=d0xH001eed_R:0xH001eee!=d0xH001eeeS0xH001f11=1S0xH001f11=6\",\"Title\":\"Change of Scenery\",\"Description\":\"Clear the Special Zone and change the seasons in Dinosaur Land\",\"Points\":25,\"Author\":\"Jaarl\",\"Modified\":1599744063,\"Created\":1376929303,\"BadgeName\":\"47074\",\"Flags\":3,\"Type\":null,\"Rarity\":10.71,\"RarityHardcore\":7.15,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47074.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47074_lock.png\"},{\"ID\":2304,\"MemAddr\":\"R:0xH001f2e=1.1._C:0xH001f2e>=2.1._C:0xH001f2e>=3.1._C:0xH001f2e>=4.1._C:0xH001f2e>=5.1._C:0xH001f2e>=6.1._C:0xH001f2e>=7.1._C:0xH001f2e>=8.1._C:0xH001f2e>=9.1._C:0xH001f2e>=10.1._C:0xH001f2e>=11.1._C:0xH001f2e>=12.1._C:0xH001f2e>=13.1._C:0xH001f2e>=14.1._C:0xH001f2e>=15.1._C:0xH001f2e>=16.1._C:0xH001f2e>=17.1._C:0xH001f2e>=18.1._C:0xH001f2e>=19.1._C:0xH001f2e>=20.1._C:0xH001f2e>=21.1._C:0xH001f2e>=22.1._C:0xH001f2e>=23.1._C:0xH001f2e>=24.1._C:0xH001f2e>=25.1._C:0xH001f2e>=26.1._C:0xH001f2e>=27.1._C:0xH001f2e>=28.1._C:0xH001f2e>=29.1._C:0xH001f2e>=30.1._C:0xH001f2e>=31.1._C:0xH001f2e>=32.1._C:0xH001f2e>=33.1._C:0xH001f2e>=34.1._C:0xH001f2e>=35.1._C:0xH001f2e>=36.1._C:0xH001f2e>=37.1._C:0xH001f2e>=38.1._C:0xH001f2e>=39.1._C:0xH001f2e>=40.1._C:0xH001f2e>=41.1._C:0xH001f2e>=42.1._C:0xH001f2e>=43.1._C:0xH001f2e>=44.1._C:0xH001f2e>=45.1._C:0xH001f2e>=46.1._C:0xH001f2e>=47.1._C:0xH001f2e>=48.1._C:0xH001f2e>=49.1._C:0xH001f2e>=50.1._C:0xH001f2e>=51.1._C:0xH001f2e>=52.1._C:0xH001f2e>=53.1._C:0xH001f2e>=54.1._C:0xH001f2e>=55.1._C:0xH001f2e>=56.1._C:0xH001f2e>=57.1._C:0xH001f2e>=58.1._C:0xH001f2e>=59.1._C:0xH001f2e>=60.1._C:0xH001f2e>=61.1._C:0xH001f2e>=62.1._C:0xH001f2e>=63.1._C:0xH001f2e>=64.1._C:0xH001f2e>=65.1._C:0xH001f2e>=66.1._C:0xH001f2e>=67.1._C:0xH001f2e>=68.1._C:0xH001f2e>=69.1._C:0xH001f2e>=70.1._C:0xH001f2e>=71.1._C:0xH001f2e>=72.1._C:0xH001f2e>=73.1._C:0xH001f2e>=74.1._C:0xH001f2e>=75.1._C:0xH001f2e>=76.1._C:0xH001f2e>=77.1._C:0xH001f2e>=78.1._C:0xH001f2e>=79.1._C:0xH001f2e>=80.1._C:0xH001f2e>=81.1._C:0xH001f2e>=82.1._C:0xH001f2e>=83.1._C:0xH001f2e>=84.1._C:0xH001f2e>=85.1._C:0xH001f2e>=86.1._C:0xH001f2e>=87.1._C:0xH001f2e>=88.1._C:0xH001f2e>=89.1._C:0xH001f2e>=90.1._C:0xH001f2e>=91.1._C:0xH001f2e>=92.1._C:0xH001f2e>=93.1._C:0xH001f2e>=94.1._C:0xH001f2e>=95.1._C:0xH001f2e=96.1._M:0!=0.96.\",\"Title\":\"All Exits\",\"Description\":\"100% clear the game\",\"Points\":50,\"Author\":\"Jaarl\",\"Modified\":1625960414,\"Created\":1376929308,\"BadgeName\":\"46761\",\"Flags\":3,\"Type\":null,\"Rarity\":7.49,\"RarityHardcore\":5.74,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46761.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46761_lock.png\"},{\"ID\":2985,\"MemAddr\":\"0x 0018f2=3598_0xH0013bf=31\",\"Title\":\"The Investigator\",\"Description\":\"Access a secret area in the Forest Fortress\",\"Points\":5,\"Author\":\"mrvsonic87\",\"Modified\":1504475601,\"Created\":1379656738,\"BadgeName\":\"47075\",\"Flags\":3,\"Type\":null,\"Rarity\":9.14,\"RarityHardcore\":6.24,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47075.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47075_lock.png\"},{\"ID\":2303,\"MemAddr\":\"0xH001f11=5.7200._R:0xH001f11!=5_R:0xH000100<13_R:0xH000100>14\",\"Title\":\"That Oh-So-Familiar Tune\",\"Description\":\"Find the secret in the Special Zone\",\"Points\":2,\"Author\":\"Jaarl\",\"Modified\":1626262445,\"Created\":1376929305,\"BadgeName\":\"46563\",\"Flags\":3,\"Type\":null,\"Rarity\":9.1,\"RarityHardcore\":6.95,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46563.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46563_lock.png\"},{\"ID\":383324,\"MemAddr\":\"N:d0xH000100=19_N:0xH000100=20_O:0xH0013cf=64_0xH000071=10S0xH0013bf=37_0xT001ec7=1S0xH0013bf=7_0xT001ea9=1S0xH0013bf=64_0xT001ee2=1S0xH0013bf=14_0xT001eb0=1S0xH0013bf=32_0xT001ec2=1S0xH0013bf=26_0xT001ebc=1S0xH0013bf=52_0xT001ed6=1S0xH0013bf=11_0xT001ead=1S0xH0013bf=31_0xT001ec1=1S0xH0013bf=27_0xT001ebd=1S0xH0013bf=53_0xT001ed7=1\",\"Title\":\"Entering the Castle Ruins\",\"Description\":\"Reenter a Castle after it was destroyed by pressing L + R.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1717162497,\"Created\":1703596667,\"BadgeName\":\"431957\",\"Flags\":3,\"Type\":null,\"Rarity\":4.45,\"RarityHardcore\":3.51,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431957.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431957_lock.png\"},{\"ID\":45901,\"MemAddr\":\"R:0xH0013bf!=39_R:0xH000dc0!=0_R:0x 00001e<2032_R:0x 00001e>2128Sd0xH0016e6!=13_0xH0016e6=13Sd0xH0016e5!=13_0xH0016e5=13Sd0xH0016e4!=13_0xH0016e4=13Sd0xH0016e3!=13_0xH0016e3=13Sd0xH0016e2!=13_0xH0016e2=13Sd0xH0016e1!=13_0xH0016e1=13\",\"Title\":\"Bonus on the Island\",\"Description\":\"Obtain 1-up from Bonus Block in Yoshi's Island 3\",\"Points\":5,\"Author\":\"Salsa\",\"Modified\":1576437920,\"Created\":1488942160,\"BadgeName\":\"46551\",\"Flags\":3,\"Type\":null,\"Rarity\":13.13,\"RarityHardcore\":9.13,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46551.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46551_lock.png\"},{\"ID\":45902,\"MemAddr\":\"R:0xH0013bf!=5_R:0xH000dc0!=0_R:0x 00001e<2224_R:0x 00001e>2352Sd0xH0016e6!=13_0xH0016e6=13Sd0xH0016e5!=13_0xH0016e5=13Sd0xH0016e4!=13_0xH0016e4=13Sd0xH0016e3!=13_0xH0016e3=13Sd0xH0016e2!=13_0xH0016e2=13Sd0xH0016e1!=13_0xH0016e1=13\",\"Title\":\"Bonus of the Donuts\",\"Description\":\"Obtain 1-up from Bonus Block in Donut Plains 3\",\"Points\":5,\"Author\":\"Salsa\",\"Modified\":1576437924,\"Created\":1488942165,\"BadgeName\":\"46552\",\"Flags\":3,\"Type\":null,\"Rarity\":13.46,\"RarityHardcore\":9.32,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46552.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46552_lock.png\"},{\"ID\":45903,\"MemAddr\":\"R:0xH0013bf!=12_R:0xH000dc0!=0_R:0x 00001e<1404_R:0x 00001e>1404_R:0xH000094<192_R:0xH00009c!=22Sd0xH0016e6!=13_0xH0016e6=13Sd0xH0016e5!=13_0xH0016e5=13Sd0xH0016e4!=13_0xH0016e4=13Sd0xH0016e3!=13_0xH0016e3=13Sd0xH0016e2!=13_0xH0016e2=13Sd0xH0016e1!=13_0xH0016e1=13\",\"Title\":\"Bonus on the Butter\",\"Description\":\"Obtain 1-up from Bonus Block in Butter Bridge 1\",\"Points\":5,\"Author\":\"Salsa\",\"Modified\":1576437928,\"Created\":1488942170,\"BadgeName\":\"46553\",\"Flags\":3,\"Type\":null,\"Rarity\":5.48,\"RarityHardcore\":4.56,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46553.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46553_lock.png\"},{\"ID\":45904,\"MemAddr\":\"R:0xH0013bf!=35_R:0xH000dc0!=0_R:0x 00001e<2160_R:0x 00001e>2304Sd0xH0016e6!=13_0xH0016e6=13Sd0xH0016e5!=13_0xH0016e5=13Sd0xH0016e4!=13_0xH0016e4=13Sd0xH0016e3!=13_0xH0016e3=13Sd0xH0016e2!=13_0xH0016e2=13Sd0xH0016e1!=13_0xH0016e1=13\",\"Title\":\"Bonus of the Chocolate\",\"Description\":\"Obtain 1-up from Bonus Block in Chocolate Island 3\",\"Points\":5,\"Author\":\"Salsa\",\"Modified\":1576437931,\"Created\":1488942175,\"BadgeName\":\"46554\",\"Flags\":3,\"Type\":null,\"Rarity\":11.29,\"RarityHardcore\":7.4,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46554.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46554_lock.png\"},{\"ID\":383328,\"MemAddr\":\"Q:0xH0013bf=38_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=509428\",\"Title\":\"Secrets of the Cheep Cheeps\",\"Description\":\"Trigger the hidden 1-Up in Yoshi's Island 4.\",\"Points\":3,\"Author\":\"SporyTike\",\"Modified\":1703596668,\"Created\":1703596668,\"BadgeName\":\"431961\",\"Flags\":3,\"Type\":null,\"Rarity\":2.18,\"RarityHardcore\":1.87,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431961.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431961_lock.png\"},{\"ID\":383325,\"MemAddr\":\"Q:0xH0013bf=21_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=509653\",\"Title\":\"Secrets of the Chucks\",\"Description\":\"Trigger the hidden 1-Up in Donut Plains 1.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596667,\"Created\":1703596667,\"BadgeName\":\"431958\",\"Flags\":3,\"Type\":null,\"Rarity\":2.2,\"RarityHardcore\":1.89,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431958.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431958_lock.png\"},{\"ID\":383326,\"MemAddr\":\"Q:0xH0013bf=6_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=510020\",\"Title\":\"Secrets of the Galoombas\",\"Description\":\"Trigger the hidden 1-Up in Donut Plains 4.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596667,\"Created\":1703596667,\"BadgeName\":\"431959\",\"Flags\":3,\"Type\":null,\"Rarity\":2.07,\"RarityHardcore\":1.79,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431959.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431959_lock.png\"},{\"ID\":383332,\"MemAddr\":\"Q:0xH0013bf=7_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=510246\",\"Title\":\"Secrets of Morton\",\"Description\":\"Trigger the hidden 1-Up in #2 Morton's Castle.\",\"Points\":1,\"Author\":\"SporyTike\",\"Modified\":1703596668,\"Created\":1703596668,\"BadgeName\":\"431965\",\"Flags\":3,\"Type\":null,\"Rarity\":2.5,\"RarityHardcore\":2.06,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431965.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431965_lock.png\"},{\"ID\":383333,\"MemAddr\":\"Q:0xH0013bf=43_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=510940\",\"Title\":\"Secrets of the Boos\",\"Description\":\"Trigger the hidden 1-Up in Vanilla Ghost House.\",\"Points\":3,\"Author\":\"SporyTike\",\"Modified\":1703596669,\"Created\":1703596669,\"BadgeName\":\"431966\",\"Flags\":3,\"Type\":null,\"Rarity\":2.04,\"RarityHardcore\":1.77,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431966.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431966_lock.png\"},{\"ID\":383335,\"MemAddr\":\"Q:0xH0013bf=61_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=511188\",\"Title\":\"Secrets of the Bullet Bills\",\"Description\":\"Trigger the hidden 1-Up in Vanilla Dome 4.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596675,\"Created\":1703596675,\"BadgeName\":\"431974\",\"Flags\":3,\"Type\":null,\"Rarity\":2.03,\"RarityHardcore\":1.74,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431974.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431974_lock.png\"},{\"ID\":383331,\"MemAddr\":\"Q:0xH0013bf=11_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=511821\",\"Title\":\"Secrets of the Fishbones\",\"Description\":\"Trigger the hidden 1-Up in Vanilla Fortress.\",\"Points\":3,\"Author\":\"SporyTike\",\"Modified\":1703596668,\"Created\":1703596668,\"BadgeName\":\"431964\",\"Flags\":3,\"Type\":null,\"Rarity\":1.86,\"RarityHardcore\":1.63,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431964.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431964_lock.png\"},{\"ID\":383327,\"MemAddr\":\"Q:0xH0013bf=16_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=512067\",\"Title\":\"Secrets of the Sumo Bros\",\"Description\":\"Trigger the hidden 1-Up in Cookie Mountain.\",\"Points\":1,\"Author\":\"SporyTike\",\"Modified\":1703596668,\"Created\":1703596668,\"BadgeName\":\"431960\",\"Flags\":3,\"Type\":null,\"Rarity\":4.05,\"RarityHardcore\":2.84,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431960.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431960_lock.png\"},{\"ID\":383336,\"MemAddr\":\"Q:0xH0013bf=71_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=513093\",\"Title\":\"Secrets of the Bubbles\",\"Description\":\"Trigger the hidden 1-Up in Forest of Illusion 3.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596675,\"Created\":1703596675,\"BadgeName\":\"431975\",\"Flags\":3,\"Type\":null,\"Rarity\":1.89,\"RarityHardcore\":1.65,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431975.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431975_lock.png\"},{\"ID\":46332,\"MemAddr\":\"R:0xH0013bf!=46_R:0x 000094<4194_R:0x 000094>4306_0x 000096>=276_0xH000013>d0xH000013.240._P:0xH0013d4=1S0xH0000a6=2_P:0xH0014d0!=8S0xH0000a5=2_P:0xH0014d1!=8\",\"Title\":\"Suicide Prevention (moved to bonus)\",\"Description\":\"On 3-3 prevent kicking koopa by checkpoint from dying. Stay by him.\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1505703949,\"Created\":1489701538,\"BadgeName\":\"47077\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47077.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47077_lock.png\"},{\"ID\":383330,\"MemAddr\":\"Q:0xH0013bf=36_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=514026\",\"Title\":\"Secrets of Chocolate\",\"Description\":\"Trigger the hidden 1-Up in Chocolate Island 2.\",\"Points\":3,\"Author\":\"SporyTike\",\"Modified\":1703596668,\"Created\":1703596668,\"BadgeName\":\"431963\",\"Flags\":3,\"Type\":null,\"Rarity\":1.75,\"RarityHardcore\":1.56,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431963.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431963_lock.png\"},{\"ID\":46061,\"MemAddr\":\"R:0xH0013bf!=1_R:0xH001411=0_R:0xH000f31=1_0xH000dbe>d0xH000dbe.50.\",\"Title\":\"Silver Worth More than Gold (moved to bonus)\",\"Description\":\"On Vanilla Secret 2 Get 50 1ups with 200 or more on the clock\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474573,\"Created\":1489170843,\"BadgeName\":\"46762\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46762.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46762_lock.png\"},{\"ID\":383329,\"MemAddr\":\"Q:0xH0013bf=26_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=515003\",\"Title\":\"Secrets of Wendy\",\"Description\":\"Trigger the hidden 1-Up in #6 Wendy's Castle.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596668,\"Created\":1703596668,\"BadgeName\":\"431962\",\"Flags\":3,\"Type\":null,\"Rarity\":2.22,\"RarityHardcore\":1.8,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431962.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431962_lock.png\"},{\"ID\":46008,\"MemAddr\":\"R:0xH0013bf!=15_R:0xH000019!=3_0xH000095=1.1._R:0xH001411=0_0x 000094>=2288_0x 000096>=341_R:0xH00187a=1_R:0xO000077=1\",\"Title\":\"Inferno Tornado (moved to bonus)\",\"Description\":\"As Fire Mario in Cheese Bridge cross 1st gap with only spin jump. No platforms\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474576,\"Created\":1489073077,\"BadgeName\":\"46606\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46606.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46606_lock.png\"},{\"ID\":383340,\"MemAddr\":\"Q:0xH0013bf=57_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=515507_Q:0x 000094<2048\",\"Title\":\"Secrets of the Moving Walls\",\"Description\":\"Trigger the first hidden 1-Up in Valley of Bowser 2.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596683,\"Created\":1703596683,\"BadgeName\":\"431979\",\"Flags\":3,\"Type\":null,\"Rarity\":1.68,\"RarityHardcore\":1.48,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431979.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431979_lock.png\"},{\"ID\":46002,\"MemAddr\":\"R:0xH0013bf!=14_0xH00c685>=204_0xH00c685<=205_0xH0017c3=2.11._P:d0xH0017c3=2_R:0xH00c680=152_R:0xH000065!=59\",\"Title\":\"Surfing the Shell (moved to bonus)\",\"Description\":\"In #4 Ludwig's Castle bouce on Ludwig's shell 11 times before he leaps\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1516573766,\"Created\":1489073044,\"BadgeName\":\"46667\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46667.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46667_lock.png\"},{\"ID\":383337,\"MemAddr\":\"Q:0xH0013bf=57_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=515507_Q:0x 000094>2048\",\"Title\":\"Secrets of the Moving Walls II\",\"Description\":\"Trigger the second hidden 1-Up in Valley of Bowser 2.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596675,\"Created\":1703596675,\"BadgeName\":\"431976\",\"Flags\":3,\"Type\":null,\"Rarity\":1.58,\"RarityHardcore\":1.4,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431976.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431976_lock.png\"},{\"ID\":2263,\"MemAddr\":\"0xH001697=12_0xH0013bf=66_R:0xH001697=0\",\"Title\":\"Bother The Wigglers (moved to bonus)\",\"Description\":\"Jump on yellow Wigglers in Forest of Illusion 12 times in a row for a surprise!\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474582,\"Created\":1376655155,\"BadgeName\":\"46574\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46574.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46574_lock.png\"},{\"ID\":383339,\"MemAddr\":\"Q:0xH0013bf=52_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=516017\",\"Title\":\"Secrets of Larry\",\"Description\":\"Trigger the hidden 1-Up in #7 Larry's Castle.\",\"Points\":3,\"Author\":\"SporyTike\",\"Modified\":1703596679,\"Created\":1703596679,\"BadgeName\":\"431978\",\"Flags\":3,\"Type\":null,\"Rarity\":1.64,\"RarityHardcore\":1.45,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431978.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431978_lock.png\"},{\"ID\":383334,\"MemAddr\":\"Q:0xH0013bf=78_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=517084\",\"Title\":\"Secrets of the Vines\",\"Description\":\"Trigger the hidden 1-Up in Gnarly.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596669,\"Created\":1703596669,\"BadgeName\":\"431970\",\"Flags\":3,\"Type\":null,\"Rarity\":1.87,\"RarityHardcore\":1.62,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431970.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431970_lock.png\"},{\"ID\":46171,\"MemAddr\":\"R:0xH0018a7=50_R:0xH00009c=13_R:0xH00005a!=254_0xH0000d2=0.10._0xH0000d2=6_0xH0000d1>=213_0xH0000d1<=242\",\"Title\":\"These Platforms are so Slow... (demoted)\",\"Description\":\"Arrive at the First Door of Larry's Castle without Activating the Platform\",\"Points\":0,\"Author\":\"GalacticSpear\",\"Modified\":1504474585,\"Created\":1489319085,\"BadgeName\":\"46770\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46770.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46770_lock.png\"},{\"ID\":46006,\"MemAddr\":\"0xH000dba=4_0xH000dc1>=1_0xH000f2d!=d0xH000f2d.10._R:0xH0018ac=0_R:0xH0013bf!=28_R:0xH001411=0_R:0x 000094>1680\",\"Title\":\"A Trip to Pound Town (dmt, fals pos)\",\"Description\":\"Pound 10* spinys hard on 6-5 with Y. Yoshi. Don't spit or swallow til the end\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474587,\"Created\":1489073069,\"BadgeName\":\"46670\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46670.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46670_lock.png\"},{\"ID\":45909,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>90_P:0xH000dd5<1_P:0xH000dd5>2_R:d0x 000f34>0x 000f34_0xH0013bf=42.1._0xH0013bf=41.1._0xH0013bf=39.1._0xH0013bf=38.1._0xH0013bf=37.1.\",\"Title\":\"Mario vs. the Score I (Yoshi's Island) (demoted)\",\"Description\":\"Score 900 points or less playing through all stages in Yoshi Island\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474594,\"Created\":1488942350,\"BadgeName\":\"46713\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46713.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46713_lock.png\"},{\"ID\":45910,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>100_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x 000f34_0xH0013bf=21.1._0xH0013bf=9.1._0xH0013bf=5.1._0xH0013bf=6.1._0xH0013bf=10.1._0xH0013bf=47.1._0xH0013bf=19.1._0xH0013bf=4.1._0xH0013bf=7.1.\",\"Title\":\"Mario vs. the Score II (Donut Plains) (demoted)\",\"Description\":\"Score 1000 points or less playing through all stages in Donut Plains\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474597,\"Created\":1488942354,\"BadgeName\":\"46714\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46714.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46714_lock.png\"},{\"ID\":45911,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>301_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x 000f34_0xH0013bf=62.1._0xH0013bf=60.1._0xH0013bf=61.1._0xH0013bf=43.1._0xH0013bf=64.1._0xH0013bf=1.1._0xH0013bf=2.1._0xH0013bf=11.1._0xH0013bf=45.1._0xH0013bf=46.1.\",\"Title\":\"Mario vs. the Score III (Vanilla Dome) (demoted)\",\"Description\":\"Score 3010 points or less playing through all stages in Vanilla Dome\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474603,\"Created\":1488942359,\"BadgeName\":\"46715\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46715.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46715_lock.png\"},{\"ID\":45912,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>110_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x 000f34_0xH0013bf=12.1._0xH0013bf=13.1._0xH0013bf=15.1._0xH0013bf=17.1._0xH0013bf=16.1._0xH0013bf=14.1.\",\"Title\":\"Mario vs. the Score IV (Twin Bridges) (demoted)\",\"Description\":\"Score 1100 points or less playing through all stages in Twin Bridges\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474609,\"Created\":1488942364,\"BadgeName\":\"46716\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46716.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46716_lock.png\"},{\"ID\":45913,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>95_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x 000f34_0xH0013bf=66.1._0xH0013bf=68.1._0xH0013bf=71.1._0xH0013bf=67.1._0xH0013bf=65.1._0xH0013bf=70.1._0xH0013bf=31.1._0xH0013bf=32.1.\",\"Title\":\"Mario vs. the Score V (Forest of Illusion) (demoted)\",\"Description\":\"Score 950 points or less playing through all stages in Forest of Illusion\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474613,\"Created\":1488942370,\"BadgeName\":\"46717\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46717.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46717_lock.png\"},{\"ID\":46304,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>0_P:0xH000dd5<1_P:0xH000dd5>2_R:d0x 000f34>0x 000f34_0xH0013bf=21.1._0xH0013bf=9.1._0xH0013bf=10.1._0xH0013bf=62.1._0xH0013bf=60.1._0xH0013bf=15.1.\",\"Title\":\"Loopholes by Keyholes - Donut Vanilla Butter (moved to bonus)\",\"Description\":\"Don't score any points reaching keyhole exits on stages in Worlds 2, 3 and 4\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474617,\"Created\":1489616634,\"BadgeName\":\"47079\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47079.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47079_lock.png\"},{\"ID\":45914,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>285_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x 000f34_0xH0013bf=34.1._0xH0013bf=36.1._0xH0013bf=35.1._0xH0013bf=29.1._0xH0013bf=28.1._0xH0013bf=59.1._0xH0013bf=33.1._0xH0013bf=27.1._0xH0013bf=26.1._0xH0013bf=24.1.\",\"Title\":\"Mario vs. the Score VI (Chocolate Island) (demoted)\",\"Description\":\"Score 2850 points or less playing through all stages in Chocolate Island\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474620,\"Created\":1488942396,\"BadgeName\":\"46718\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46718.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46718_lock.png\"},{\"ID\":46305,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>0_P:0xH000dd5<1_P:0xH000dd5>2_R:d0x 000f34>0x 000f34_0xH0013bf=66.1._0xH0013bf=68.1._0xH0013bf=67.1._0xH0013bf=36.1._0xH0013bf=57.1._0xH0013bf=56.1._0xH0013bf=51.1.\",\"Title\":\"Loopholes by Keyholes - Chocolate Forest Bowser (bonus)\",\"Description\":\"Don't score any points reaching keyhole exits on stages in Worlds 4, 6 and 7\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474637,\"Created\":1489616639,\"BadgeName\":\"47080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47080_lock.png\"},{\"ID\":45915,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>95_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x 000f34_0xH0013bf=58.1._0xH0013bf=57.1._0xH0013bf=55.1._0xH0013bf=51.1._0xH0013bf=56.1._0xH0013bf=52.1._0xH0013bf=53.1._0xH0013bf<=50_0xH0013bf>=49_0xH0013ef=1_0xH0013f9=3\",\"Title\":\"Mario vs. the Score VII (Valley of Bowser) (demoted)\",\"Description\":\"Score 950 points or less playing through all stages in Valley of Bowser\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474640,\"Created\":1488942402,\"BadgeName\":\"46719\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46719.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46719_lock.png\"},{\"ID\":45916,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>110_P:0xH000dd5>2_R:d0x 000f34>0x 000f34_0xH0013bf=88.1._0xH0013bf=84.1._0xH0013bf=86.1._0xH0013bf=89.1._0xH0013bf=90.1._P:0xH000dd5<1\",\"Title\":\"Loopholes by Keyholes - Star World (moved to bonus)\",\"Description\":\"Score 1100 points or less playing through all stages in Star World\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474645,\"Created\":1488942407,\"BadgeName\":\"47081\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47081.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47081_lock.png\"},{\"ID\":46307,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=42.1._0xH0013bf=41.1._0xH0013bf=39.1._0xH0013bf=38.1._0xH0013bf=37_0x 0000ce=50896_R:0xH000db3=1_R:0xH000dd5=128_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0\",\"Title\":\"Super Pacifist Mario feat. Hungry Yoshi I (Yoshi Island) (bon)\",\"Description\":\"See achievement comments for details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474058,\"Created\":1489623727,\"BadgeName\":\"46908\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46908.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46908_lock.png\"},{\"ID\":45917,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>100_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x 000f34_0xH0013bf=78.1._0xH0013bf=79.1._0xH0013bf=80.1._0xH0013bf=81.1._0xH0013bf=76.1._0xH0013bf=75.1._0xH0013bf=74.1._0xH0013bf=73.1.\",\"Title\":\"Mario vs. the Score IX (Special World) (demoted)\",\"Description\":\"Score 1000 points or less playing through all stages in Special World\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474649,\"Created\":1488942411,\"BadgeName\":\"46721\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46721.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46721_lock.png\"},{\"ID\":46308,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=21.1._0xH0013bf=9.1._0xH0013bf=5.1._0xH0013bf=6.1._0xH0013bf=10.1._0xH0013bf=47.1._0xH0013bf=19.1._0xH0013bf=4.1._0xH0013bf=7.1._0x 0000ce=51523_R:0xH000db3=1_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0\",\"Title\":\"Super Pacifist Marioi II (Donut Plains) (bonus)\",\"Description\":\"See achievement comments for details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474682,\"Created\":1489635441,\"BadgeName\":\"46909\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46909.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46909_lock.png\"},{\"ID\":46309,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=62.1._0xH0013bf=60.1._0xH0013bf=61.1._0xH0013bf=43.1._0xH0013bf=64_0xH0013bf=1.1._0xH0013bf=2.1._0xH0013bf=45.1._0x 0000ce=52672_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_R:0xH000db3=1_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0_P:d0xH0000a6=2\",\"Title\":\"Super Pacifist Mario III (Vanilla Dome) (bonus)\",\"Description\":\"See achievement comments for details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474749,\"Created\":1489635447,\"BadgeName\":\"46910\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46910.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46910_lock.png\"},{\"ID\":46310,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=12.1._0xH0013bf=15.1._0xH0013bf=17.1._0xH0013bf=16.1._0xH0013bf=14_0x 0000ce=53586_R:0xH000db3=1_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0_0xH0013bf=13.1.\",\"Title\":\"Super Pacifist Mario IV (Twin Bridges) (bonus)\",\"Description\":\"See achievement comments for details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474894,\"Created\":1489635455,\"BadgeName\":\"46911\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46911.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46911_lock.png\"},{\"ID\":46311,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=66.1._0xH0013bf=68.1._0xH0013bf=71.1._0xH0013bf=67.1._0xH0013bf=65.1._0xH0013bf=70.1._0xH0013bf=32_0x 0000ce=54557_R:0xH000db3=1_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0_P:0x 0000a2=30840\",\"Title\":\"Super Pacifist Mario V (Forest of Illusion) (moved to bonus)\",\"Description\":\"See achievement comments for details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474914,\"Created\":1489635462,\"BadgeName\":\"46912\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46912.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46912_lock.png\"},{\"ID\":46312,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=34.1._0xH0013bf=36.1._0xH0013bf=35.1._0xH0013bf=29.1._0xH0013bf=28.1._0xH0013bf=59.1._0xH0013bf=33.1._0xH0013bf=26_0xH0013bf=24.1._0x 0000ce=56354_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_R:0xH000db3=1_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0\",\"Title\":\"Super Pacifist Mario VI (Chocolate Island) (moved to bonus)\",\"Description\":\"See achievement comments for details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474928,\"Created\":1489635469,\"BadgeName\":\"46913\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46913.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46913_lock.png\"},{\"ID\":46306,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=57.1._0xH0013bf=55.1._0xH0013bf=51.1._0xH0013bf=56.1._0xH0013bf=49.1._0xH0013bf=58.1._0x 0000ce=57792_R:0xH000db3=1_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0\",\"Title\":\"Super Pacifist Mario VII (Valley of Bowser) (moved to bonus)\",\"Description\":\"See achievement comments for details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474937,\"Created\":1489623613,\"BadgeName\":\"46916\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46916.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46916_lock.png\"},{\"ID\":46313,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=88.1._0xH0013bf=84.1._0xH0013bf=86.1._0xH0013bf=89.1._0xH0013bf=90.1._0xH000dd5>0_R:0xH000db3=1_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0\",\"Title\":\"Super Pacifist Mario VIII (Star World) (moved to bonus)\",\"Description\":\"See achievement comments for details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474946,\"Created\":1489635474,\"BadgeName\":\"46914\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46914.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46914_lock.png\"},{\"ID\":46314,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=78.1._0xH0013bf=79.1._0xH0013bf=80.1._0xH0013bf=76.1._0xH0013bf=75.1._0xH0013bf=74.1._0xH0013bf=73.1._0xH001dff=12_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_R:0xH000db3=1_R:0xH0013cf=64_R:0xH000100=0_0xH0013bf=81.1._P:d0xH000dd5=1_P:d0xH000dd5=2\",\"Title\":\"Super Pacifist Mario IX (Special World) (moved to bonus)\",\"Description\":\"See achievement comments for details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474957,\"Created\":1489635478,\"BadgeName\":\"46915\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46915.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46915_lock.png\"},{\"ID\":29653,\"MemAddr\":\"C:0xS001f34=1.1._C:0xR001f34=1.1._C:0xM001f33=1.1._C:0xN001f33=1.1._M:0!=0.4._0xS001f34=1_0xR001f34=1_0xM001f33=1_0xN001f33=1_0xH000008!=85_R:0xH001f33d0xH000dbe.8._R:0xH001411=0\",\"Title\":\"Fence Offense\",\"Description\":\"Get 8 1-ups at #1 Iggy's Castle with 230 or more on the time clock\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1625960433,\"Created\":1489073056,\"BadgeName\":\"46608\",\"Flags\":3,\"Type\":null,\"Rarity\":4.9,\"RarityHardcore\":4.24,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46608.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46608_lock.png\"},{\"ID\":46170,\"MemAddr\":\"N:0xH0013bf=4_N:d0xH000dda=0_0xH000dda=7.1._R:0xH0013e0=21_R:0xH000019=2_T:0xH000dd5=2_R:0xH0013bf!=4_R:0xH000dd5=128\",\"Title\":\"Who Needs Vines?\",\"Description\":\"Get the Normal Exit in Donut Ghost House without a Feather or Climbing on a Vine\",\"Points\":10,\"Author\":\"GalacticSpear\",\"Modified\":1625960437,\"Created\":1489319082,\"BadgeName\":\"46771\",\"Flags\":3,\"Type\":null,\"Rarity\":6.03,\"RarityHardcore\":5.08,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46771.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46771_lock.png\"},{\"ID\":46331,\"MemAddr\":\"0xH0000aa=64_0xH0014c8=2_R:0xH0013bf!=19_R:0xH00005a!=237\",\"Title\":\"Spooking Big Boo\",\"Description\":\"In the first room of Donut Secret House kill Big Boo\",\"Points\":5,\"Author\":\"kdecks\",\"Modified\":1571057401,\"Created\":1489701500,\"BadgeName\":\"47076\",\"Flags\":3,\"Type\":null,\"Rarity\":8.75,\"RarityHardcore\":7.06,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47076.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47076_lock.png\"},{\"ID\":46001,\"MemAddr\":\"R:0xH0013bf!=68_R:0xH000077>0_R:0xH000071=1_0xH000f31=3.1._R:0xH000071=9_T:0xH001dff=12ST:0xH001dff=12ST:0xH001dff=128\",\"Title\":\"Everything is Lava\",\"Description\":\"Get to the normal exit of Forest of Illusion 2 without touching any walls, enemies, blue blocks\",\"Points\":25,\"Author\":\"kdecks\",\"Modified\":1625960440,\"Created\":1489073039,\"BadgeName\":\"46666\",\"Flags\":3,\"Type\":null,\"Rarity\":3.44,\"RarityHardcore\":2.9,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46666.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46666_lock.png\"},{\"ID\":45997,\"MemAddr\":\"0xH001523=16_0xH001411=1_0xH000f31=3.1._T:0x 000094>=2448_R:0xH000019=2_R:0xH001410=2_R:0xH0018e0>0_R:0xH001523=0_R:0xH0013bf!=67\",\"Title\":\"Didn't Take the Bait\",\"Description\":\"On Forest of Illusion 4, get past Lakitu without killing him or taking the 1-up. No cape, wings.\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1671592204,\"Created\":1489073023,\"BadgeName\":\"46615\",\"Flags\":3,\"Type\":null,\"Rarity\":6.14,\"RarityHardcore\":5.15,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46615.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46615_lock.png\"},{\"ID\":46120,\"MemAddr\":\"P:0xH0013bf!=49_P:0xH000100!=15_C:0xH0000ce=103.1._C:0xH0000ce=141.1._C:0xH0000ce=197.1._C:0xH0000ce=232.1._C:0xH0000ce=20.1._C:0xH0000ce=49.1._C:0xH0000ce=96.1._C:0xH0000ce=131.1._M:0!=0.8.\",\"Title\":\"Bowser's Castle Explorer\",\"Description\":\"Complete all 8 numbered rooms in one game session\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1625960448,\"Created\":1489285260,\"BadgeName\":\"47078\",\"Flags\":3,\"Type\":null,\"Rarity\":4.64,\"RarityHardcore\":4.01,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47078.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47078_lock.png\"},{\"ID\":46007,\"MemAddr\":\"N:d0xH0000a5=44_0xH0000a5=45.1._P:0xH00187a!=0.1._N:d0xH0013fb=0_P:0xH0013fb!=0.1._P:0xH000dda=255.5._T:0x 0014cf=12_O:0xH001dff=12_T:0xH001dff=128_N:p0xH0000a5=45_T:0xH0000a5=120SR:0xH0013bf!=89_R:0xH000d9b=2_N:0xH0000a5!=45_R:0xH0000a5!=120\",\"Title\":\"I Starved Yoshi and All I Got Was...\",\"Description\":\"On Star world 4 bring Baby Yoshi to the end flag without feeding it to adulthood\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1669165514,\"Created\":1489073073,\"BadgeName\":\"46607\",\"Flags\":3,\"Type\":null,\"Rarity\":7,\"RarityHardcore\":5.76,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46607.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46607_lock.png\"},{\"ID\":45999,\"MemAddr\":\"R:0xH0013bf!=79_R:0xH000dba=6_R:0xH0013f3>0_T:0xH001dff=12_0xH000f31=3.1.\",\"Title\":\"Who Needs Helium?\",\"Description\":\"Complete SPECIAL-Tubular without Blue Yoshi, or Balloon Mario\",\"Points\":25,\"Author\":\"kdecks\",\"Modified\":1625960450,\"Created\":1489073032,\"BadgeName\":\"46677\",\"Flags\":3,\"Type\":null,\"Rarity\":4.05,\"RarityHardcore\":3.19,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46677.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46677_lock.png\"},{\"ID\":46000,\"MemAddr\":\"R:0xH0013bf!=76_T:0xH001dff=12_R:0xH0013ef=1_0x 000094<464.1._R:0xH00187a>0\",\"Title\":\"A Groovy Flight\",\"Description\":\"Complete SPECIAL-Groovy without landing past the first ? box. No Yoshi\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1625960453,\"Created\":1489073036,\"BadgeName\":\"46665\",\"Flags\":3,\"Type\":null,\"Rarity\":3.75,\"RarityHardcore\":3.15,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46665.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46665_lock.png\"},{\"ID\":46005,\"MemAddr\":\"R:0xH0013bf!=73_0xH000f31>=3_T:0xH001dff=12_T:0xH001411=1ST:0xH001dff=12ST:0xH001dff=128\",\"Title\":\"With More Than You Started\",\"Description\":\"Complete SPECIAL-Funky with 300 or more on the time clock\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1625960456,\"Created\":1489073065,\"BadgeName\":\"46669\",\"Flags\":3,\"Type\":null,\"Rarity\":3.41,\"RarityHardcore\":2.86,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46669.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46669_lock.png\"},{\"ID\":2274,\"MemAddr\":\"0xH0013bf=49_0xH0013f9=3_0xH001f2e=11_0xH0013ef=1\",\"Title\":\"Shortest Route\",\"Description\":\"Clear the fewest stages possible and beat the game\",\"Points\":25,\"Author\":\"Jaarl\",\"Modified\":1570010401,\"Created\":1376742799,\"BadgeName\":\"46575\",\"Flags\":3,\"Type\":null,\"Rarity\":6.08,\"RarityHardcore\":5.05,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46575.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46575_lock.png\"},{\"ID\":2297,\"MemAddr\":\"N:0xH0013ce=0_N:0xH0013cf=0_0xH001f2e=0.1._T:0xH001f2e=11.1._T:0xH0013f9=3_R:0xH000dbe1\",\"Title\":\"Starman Challenge\",\"Description\":\"Clear the game without dying (one session)\",\"Points\":25,\"Author\":\"Jaarl\",\"Modified\":1625960458,\"Created\":1376918015,\"BadgeName\":\"46579\",\"Flags\":3,\"Type\":null,\"Rarity\":5.76,\"RarityHardcore\":4.67,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46579.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46579_lock.png\"},{\"ID\":383338,\"MemAddr\":\"N:0xH0013bf=21_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=5_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=7_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=46_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=14_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=71_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=28_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=58_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=55_N:0xH000071=5_C:0xW0000ce=508910.1._M:0!=0.9.\",\"Title\":\"Gambling with Life\",\"Description\":\"Enter all 1-Up Chambers in a single Session.\",\"Points\":5,\"Author\":\"SporyTike\",\"Modified\":1703596675,\"Created\":1703596675,\"BadgeName\":\"431977\",\"Flags\":3,\"Type\":null,\"Rarity\":1.45,\"RarityHardcore\":1.3,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431977.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431977_lock.png\"},{\"ID\":175188,\"MemAddr\":\"0xH0013bf>=49_0xH0013bf<=50_0x 0000ce=0_d0xH0013de=0_0xH0013de=3\",\"Title\":\"Do The Mario!\",\"Description\":\"Make Mario dance during the credits\",\"Points\":0,\"Author\":\"stfN1337\",\"Modified\":1691443564,\"Created\":1632935877,\"BadgeName\":\"195401\",\"Flags\":3,\"Type\":null,\"Rarity\":3.44,\"RarityHardcore\":2.86,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/195401.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/195401_lock.png\"},{\"ID\":45856,\"MemAddr\":\"R:0xH000100<=10_R:0xH000db3=1_R:0xH001f27>0.1._R:0xH001f28>0_R:0xH001f29>0_R:0xH001f2a>0_R:0xH00187a>0_N:0xH0013bf=42_0xH000100=17.1._R:0xH000019>0_R:0xH001490>0SN:0xH0013bf=42_Q:0xH000100=17.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=60_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=60_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=43_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=60_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=43_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=46_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=60_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=43_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=46_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=61_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=60_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=43_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=46_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=61_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=64_N:d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.16.ST:0!=0\",\"Title\":\"Mini-Mario vs the World I\",\"Description\":\"From Yoshi's Island 2 reach and beat #3 Lemmy's Castle in one Session without using Power-Ups, without using Yoshi and without active Switch Palace Blocks.\",\"Points\":25,\"Author\":\"kdecks\",\"Modified\":1687365195,\"Created\":1488840058,\"BadgeName\":\"46755\",\"Flags\":3,\"Type\":\"missable\",\"Rarity\":3.03,\"RarityHardcore\":2.54,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46755.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46755_lock.png\"},{\"ID\":45994,\"MemAddr\":\"R:0xH000100<=10_R:0xH000db3=1_R:0xH001f27>0_R:0xH001f28>0_R:0xH001f29>0_R:0xH001f2a>0_R:0xH00187a>0_N:0xH0013bf=15_0xH000100=17.1._R:0xH001490>0SN:0xH0013bf=15_Q:0xH000100=17.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=31_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=32_N:d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.12.SN:0xH0013bf=15_Q:0xH000100=17.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=32_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=31_N:d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.12.SN:0xH0013bf=15_Q:0xH000100=17.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=32_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=31_N:d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.12.ST:0!=0_N:0xH0013bf!=71_R:0xH000019>0\",\"Title\":\"Mini-Mario vs the World II\",\"Description\":\"From Cheese Bridge Area reach and beat #5 Roy's Castle and Forest Fortress in one Session without using Power-Ups except the P-Balloon and in Forest of Illusion 3, without using Yoshi and without active Switch Palace Blocks.\",\"Points\":25,\"Author\":\"kdecks\",\"Modified\":1687365208,\"Created\":1489073010,\"BadgeName\":\"46756\",\"Flags\":3,\"Type\":\"missable\",\"Rarity\":2.29,\"RarityHardcore\":1.95,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46756.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46756_lock.png\"},{\"ID\":45995,\"MemAddr\":\"R:0xH000100<=10_R:0xH000db3=1_R:0xH001f27>0_R:0xH001f28>0_R:0xH001f29>0_R:0xH001f2a>0_R:0xH00187a>0_N:0xH0013bf=34_0xH000100=17.1._R:0xH000019>0SN:0xH0013bf=34_Q:0xH000100=17.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=56_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=56_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=55_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=56_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=55_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=51_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=56_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=55_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=51_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=52_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=56_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=55_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=51_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=52_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=49_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=53_N:d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.15.ST:0!=0_N:0xH0013bf!=24_R:0xH001490>0\",\"Title\":\"Mini-Mario vs the World III\",\"Description\":\"From Chocolate Island 1, unlock all Level Entrances in Valley of Bowser and beat the Front Door in one Session without using Power-Ups except Sunken Ghost Ships' Super Star, without using Yoshi and without active Switch Palace Blocks.\",\"Points\":25,\"Author\":\"kdecks\",\"Modified\":1687365228,\"Created\":1489073014,\"BadgeName\":\"46757\",\"Flags\":3,\"Type\":\"missable\",\"Rarity\":2.04,\"RarityHardcore\":1.72,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46757.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46757_lock.png\"},{\"ID\":45996,\"MemAddr\":\"R:0xH000100<=10_R:0xH000db3=1_R:0xH001f27>0_R:0xH001f28>0_R:0xH001f29>0_R:0xH001f2a>0_R:0xH00187a>0_N:0xH0013bf=78_0xH000100=17.1._R:0xH000019>0_R:0xH001490>0SN:0xH0013bf=78_Q:0xH000100=17.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=81_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=81_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=76_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=81_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=76_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=75_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=81_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=76_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=75_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=74_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=81_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=76_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=75_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=74_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=73_N:d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.8.ST:0!=0\",\"Title\":\"Mini-Mario vs the World SPECIAL\",\"Description\":\"From Gnarly reach and beat Funky in one Session without using Power-Ups except the P-Balloon, without using Yoshi and without active Switch Palace Blocks.\",\"Points\":50,\"Author\":\"kdecks\",\"Modified\":1687365239,\"Created\":1489073019,\"BadgeName\":\"46758\",\"Flags\":3,\"Type\":\"missable\",\"Rarity\":1.97,\"RarityHardcore\":1.65,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46758.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46758_lock.png\"}],\"Leaderboards\":[{\"ID\":1998,\"Mem\":\"STA:0xh13bf=h44_0xh1411=1_0xhf31=3_0xRda4=1::CAN:0xh77>0::SUB:0xh1dff=hc::VAL:0xhf30_0xhf33*100_0xhf32*1000_0xhf31*10000\",\"Format\":\"VALUE\",\"LowerIsBetter\":0,\"Title\":\"Everything Is Lava Achievement\",\"Description\":\"Wall touch alert and best time (does not track enemy contact)\",\"Hidden\":true},{\"ID\":53203,\"Mem\":\"STA:1=0_0xH0013bf=49_0xH000071=10_0xH000019=0_0xH000dc2=0::CAN:S0xH0013bf!=49S0xH000071=10_O:0xH000019!=0_0xH000dc2!=0::SUB:S0xH0013bf=49_0xH0013f9=3_0xH0013ef=1S0xH000906>d0xH000906::VAL:M:0=0\",\"Format\":\"TIME\",\"LowerIsBetter\":1,\"Title\":\"RAOlympics - Bowser Bashing\",\"Description\":\"Defeat Bowser the fastest starting as small mario with no backup powers (Front Door)\",\"Hidden\":true},{\"ID\":1991,\"Mem\":\"STA:d0x100=h90a_0x100=h90b_0xRda4=1_0xQda4=1_0xh1f2e=0_0xhdb2=0::CAN:0xh100=0::SUB:0xH001f2e<=96S0xH000dbf>0Sd0xH001f2e=95_0xH001f2e=96::VAL:0xh1f2e\",\"Format\":\"VALUE\",\"LowerIsBetter\":0,\"Title\":\"Penniless Plumbers\",\"Description\":\"Clear as many exits as possible without getting a coin\",\"Hidden\":false},{\"ID\":1992,\"Mem\":\"STA:d0x100=h90a_0x100=h90b_0xRda4=1_0xNda2=1_0xh1f2e=0_0xhdb2=0::CAN:0xh100=0::SUB:0xH001f2e<=96S0xH000dbed0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Yoshi's Island 1 Speedrun\",\"Description\":\"Complete Yoshi's Island 1 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104611,\"Mem\":\"STA:0xH0013bf=20_d0xH001493=0_0xH001493=8_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Yellow Switch Palace Speedrun\",\"Description\":\"Complete the Yellow Switch Palace with the most time remaining\",\"Hidden\":false},{\"ID\":104612,\"Mem\":\"STA:0xH0013bf=42_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Yoshi's Island 2 Speedrun\",\"Description\":\"Complete Yoshi's Island 2 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104613,\"Mem\":\"STA:0xH0013bf=39_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Yoshi's Island 3 Speedrun\",\"Description\":\"Complete Yoshi's Island 3 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104614,\"Mem\":\"STA:0xH0013bf=38_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Yoshi's Island 4 Speedrun\",\"Description\":\"Complete Yoshi's Island 4 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104615,\"Mem\":\"STA:0xH0013bf=37_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Iggy's Castle Speedrun\",\"Description\":\"Complete Iggy's Castle with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104721,\"Mem\":\"STA:0xH0013bf=21_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Plains 1 Speedrun\",\"Description\":\"Complete Donut Plains 1 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104722,\"Mem\":\"STA:0xH0013bf=9_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Plains 2 Speedrun\",\"Description\":\"Complete Donut Plains 2 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104723,\"Mem\":\"STA:0xH0013bf=8_d0xH001493=0_0xH001493=8_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Green Switch Palace Speedrun\",\"Description\":\"Complete the Green Switch Palace with the most time remaining\",\"Hidden\":false},{\"ID\":104724,\"Mem\":\"STA:0xH0013bf=4_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Ghost House Speedrun\",\"Description\":\"Complete the Donut Ghost House with the most time remaining\",\"Hidden\":false},{\"ID\":104727,\"Mem\":\"STA:0xH0013bf=10_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Secret 1 Speedrun\",\"Description\":\"Complete Donut Secret 1 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104728,\"Mem\":\"STA:0xH0013bf=19_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Secret House Speedrun\",\"Description\":\"Complete Donut Secret House with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104729,\"Mem\":\"STA:0xH0013bf=47_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Secret 2 Speedrun\",\"Description\":\"Complete Donut Secret 2 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104725,\"Mem\":\"STA:0xH0013bf=5_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Plains 3 Speedrun\",\"Description\":\"Complete Donut Plains 3 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104726,\"Mem\":\"STA:0xH0013bf=6_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Plains 4 Speedrun\",\"Description\":\"Complete Donut Plains 4 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104730,\"Mem\":\"STA:0xH0013bf=7_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Morton's Castle Speedrun\",\"Description\":\"Complete Morton's Castle with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104865,\"Mem\":\"STA:0xH0013bf=62_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Dome 1 Speedrun\",\"Description\":\"Complete Vanilla Dome 1 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104866,\"Mem\":\"STA:0xH0013bf=60_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Dome 2 Speedrun\",\"Description\":\"Complete Vanilla Dome 2 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104867,\"Mem\":\"STA:0xH0013bf=63_d0xH001493=0_0xH001493=8_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Red Switch Palace Speedrun\",\"Description\":\"Complete the Red Switch Palace with the most time remaining\",\"Hidden\":false},{\"ID\":104868,\"Mem\":\"STA:0xH0013bf=43_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Ghost House Speedrun\",\"Description\":\"Complete Vanilla Ghost House with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104869,\"Mem\":\"STA:0xH0013bf=46_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Dome 3 Speedrun\",\"Description\":\"Complete Vanilla Dome 3 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104870,\"Mem\":\"STA:0xH0013bf=61_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Dome 4 Speedrun\",\"Description\":\"Complete Vanilla Dome 4 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104871,\"Mem\":\"STA:0xH0013bf=45_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Secret 1 Speedrun\",\"Description\":\"Complete Vanilla Secret 1 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104872,\"Mem\":\"STA:0xH0013bf=1_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Secret 2 Speedrun\",\"Description\":\"Complete Vanilla Secret 2 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104873,\"Mem\":\"STA:0xH0013bf=2_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Secret 3 Speedrun\",\"Description\":\"Complete Vanilla Secret 3 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104874,\"Mem\":\"STA:0xH0013bf=11_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Fortress Speedrun\",\"Description\":\"Complete Vanilla Fortress with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104875,\"Mem\":\"STA:0xH0013bf=64_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Lemmy's Castle Speedrun\",\"Description\":\"Complete Lemmy's Castle with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104876,\"Mem\":\"STA:0xH0013bf=15_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Cheese Bridge Area Speedrun\",\"Description\":\"Complete Cheese Bridge Area with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104877,\"Mem\":\"STA:0xH0013bf=16_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Cookie Mountain Speedrun\",\"Description\":\"Complete Cookie Mountain with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104878,\"Mem\":\"STA:0xH0013bf=12_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Butter Bridge 1 Speedrun\",\"Description\":\"Complete Butter Bridge 1 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104879,\"Mem\":\"STA:0xH0013bf=13_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Butter Bridge 2 Speedrun\",\"Description\":\"Complete Butter Bridge 2 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104880,\"Mem\":\"STA:0xH0013bf=17_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Soda Lake Speedrun\",\"Description\":\"Complete Soda Lake with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104881,\"Mem\":\"STA:0xH0013bf=14_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Ludwig's Castle Speedrun\",\"Description\":\"Complete Ludwig's Castle with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104882,\"Mem\":\"STA:0xH0013bf=66_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest of Illusion 1 Speedrun\",\"Description\":\"Complete Forest of Illusion 1 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104883,\"Mem\":\"STA:0xH0013bf=68_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest of Illusion 2 Speedrun\",\"Description\":\"Complete Forest of Illusion 2 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104884,\"Mem\":\"STA:0xH0013bf=69_d0xH001493=0_0xH001493=8_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Blue Switch Palace Speedrun\",\"Description\":\"Complete the Blue Switch Palace with the most time remaining\",\"Hidden\":false},{\"ID\":104885,\"Mem\":\"STA:0xH0013bf=71_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest of Illusion 3 Speedrun\",\"Description\":\"Complete Forest of Illusion 3 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104886,\"Mem\":\"STA:0xH0013bf=65_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest Ghost House Speedrun\",\"Description\":\"Complete Forest Ghost House with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104887,\"Mem\":\"STA:0xH0013bf=67_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest of Illusion 4 Speedrun\",\"Description\":\"Complete Forest of Illusion 4 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104888,\"Mem\":\"STA:0xH0013bf=70_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest Secret Area Speedrun\",\"Description\":\"Complete Forest Secret Area with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104889,\"Mem\":\"STA:0xH0013bf=31_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest Fortress Speedrun\",\"Description\":\"Complete Forest Fortress with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104890,\"Mem\":\"STA:0xH0013bf=32_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Roy's Castle Speedrun\",\"Description\":\"Complete Roy's Castle with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104913,\"Mem\":\"STA:N:0x 0000d1=16_N:0x 0000d3=352_0xH000f31=3.1._0xH0013bf=34_0xM000906>d0xM000906_0xS0013cf=0_O:0xH000dc2=20_P:0xH000dc2=215.1.SR:0xH000100=14::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate Island 1 Speedrun\",\"Description\":\"Complete Chocolate Island 1 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104914,\"Mem\":\"STA:0xH0013bf=33_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Choco-Ghost House Speedrun\",\"Description\":\"Complete Choco-Ghost House with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104915,\"Mem\":\"STA:0xH0013bf=36_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate Island 2 Speedrun\",\"Description\":\"Complete Chocolate Island 2 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104916,\"Mem\":\"STA:0xH0013bf=35_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate Island 3 Speedrun\",\"Description\":\"Complete Chocolate Island 3 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104917,\"Mem\":\"STA:0xH0013bf=27_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate Fortress Speedrun\",\"Description\":\"Complete Chocolate Fortress with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104918,\"Mem\":\"STA:0xH0013bf=29_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate Island 4 Speedrun\",\"Description\":\"Complete Chocolate Island 4 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104919,\"Mem\":\"STA:0xH0013bf=28_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate Island 5 Speedrun\",\"Description\":\"Complete Chocolate Island 5 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104920,\"Mem\":\"STA:0xH0013bf=59_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate Secret Speedrun\",\"Description\":\"Complete Chocolate Secret with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104921,\"Mem\":\"STA:0xH0013bf=26_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Wendy's Castle Speedrun\",\"Description\":\"Complete Wendy's Castle with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104922,\"Mem\":\"STA:0xH0013bf=24_0xS0013cf=0_d0xH001493=0_0xH001493=255_0x 000096>=6144_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Sunken Ghost Ship Speedrun\",\"Description\":\"Complete Sunken Ghost Ship with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104931,\"Mem\":\"STA:0xH0013bf=58_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley of Bowser 1 Speedrun\",\"Description\":\"Complete Valley of Bowser 1 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104932,\"Mem\":\"STA:0xH0013bf=57_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley of Bowser 2 Speedrun\",\"Description\":\"Complete Valley of Bowser 2 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104933,\"Mem\":\"STA:0xH0013bf=56_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley Ghost House Speedrun\",\"Description\":\"Complete Valley Ghost House with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104934,\"Mem\":\"STA:0xH0013bf=55_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley of Bowser 3 Speedrun\",\"Description\":\"Complete Valley of Bowser 3 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104935,\"Mem\":\"STA:0xH0013bf=51_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley of Bowser 4 Speedrun\",\"Description\":\"Complete Valley of Bowser 4 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104936,\"Mem\":\"STA:0xH0013bf=53_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley Fortress Speedrun\",\"Description\":\"Complete Valley Fortress with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104937,\"Mem\":\"STA:0xH0013bf=52_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Larry's Castle Speedrun\",\"Description\":\"Complete Larry's Castle with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104938,\"Mem\":\"STA:0xH0013bf=88_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Star World 1 Speedrun\",\"Description\":\"Complete Star World 1 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104939,\"Mem\":\"STA:0xH0013bf=84_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Star World 2 Speedrun\",\"Description\":\"Complete Star World 2 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104940,\"Mem\":\"STA:0xH0013bf=86_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Star World 3 Speedrun\",\"Description\":\"Complete Star World 3 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104941,\"Mem\":\"STA:0xH0013bf=89_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Star World 4 Speedrun\",\"Description\":\"Complete Star World 4 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104942,\"Mem\":\"STA:0xH0013bf=90_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Star World 5 Speedrun\",\"Description\":\"Complete Star World 5 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104943,\"Mem\":\"STA:0xH0013bf=78_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Gnarly Speedrun\",\"Description\":\"Complete Gnarly with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104944,\"Mem\":\"STA:0xH0013bf=79_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Tubular Speedrun\",\"Description\":\"Complete Tubular with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104945,\"Mem\":\"STA:0xH0013bf=80_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Way Cool Speedrun\",\"Description\":\"Complete Way Cool with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104946,\"Mem\":\"STA:0xH0013bf=81_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Awesome Speedrun\",\"Description\":\"Complete Awesome with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104947,\"Mem\":\"STA:0xH0013bf=76_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Groovy Speedrun\",\"Description\":\"Complete Groovy with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104948,\"Mem\":\"STA:0xH0013bf=75_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Mondo Speedrun\",\"Description\":\"Complete Mondo with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104949,\"Mem\":\"STA:0xH0013bf=74_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Outrageous Speedrun\",\"Description\":\"Complete Outrageous with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104950,\"Mem\":\"STA:0xH0013bf=73_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Funky Speedrun\",\"Description\":\"Complete Funky with the most time remaining without using a checkpoint\",\"Hidden\":false}]}}" +struct PatchResponse { + bool Success{}; + PatchDataStruct PatchData{}; +}; + +// NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(PatchAchievement, ID, MemAddr, Title, +// Description, Points, Author, Modified, Created, +// BadgeName, Flags, Type, Rarity, +// RarityHardcore, BadgeURL, BadgeLockedURL) +// NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(PatchDataStruct, ID, Title, ImageIcon, +// RichPresencePatch, ConsoleID, +// ImageIconURL, Achievements) +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(PatchResponse, Success, PatchData) +} // namespace firelight::achievements + +// "{\"Success\":true,\"PatchData\":{\"ID\":228,\"Title\":\"Super Mario +// World\",\"ImageIcon\":\"\\/Images\\/066393.png\",\"RichPresencePatch\":\"\",\"ConsoleID\":3,\"ImageIconURL\":\"https:\\/\\/media.retroachievements.org\\/Images\\/066393.png\",\"Achievements\":[{\"ID\":4934,\"MemAddr\":\"R:0xH0013c7=4\",\"Title\":\"Yellow +// Yoshi (old prototype)\",\"Description\":\"old abandoned +// prototype\",\"Points\":0,\"Author\":\"UNHchabo\",\"Modified\":1504474480,\"Created\":1392011140,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":4935,\"MemAddr\":\"R:0xH0013c7=6\",\"Title\":\"Blue +// Yoshi (old prototype)\",\"Description\":\"old abandoned +// prototype\",\"Points\":0,\"Author\":\"UNHchabo\",\"Modified\":1504474488,\"Created\":1392011155,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":4936,\"MemAddr\":\"R:0xH0013c7=8\",\"Title\":\"Red +// Yoshi (old prototype)\",\"Description\":\"old abandoned +// prototype\",\"Points\":0,\"Author\":\"UNHchabo\",\"Modified\":1504474493,\"Created\":1392011190,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":4937,\"MemAddr\":\"R:0xH000dbe>=98_R:0xH000019=1\",\"Title\":\"Super +// 99 (old prototype)\",\"Description\":\"Get 99 Lives and be not +// small\",\"Points\":0,\"Author\":\"UNHchabo\",\"Modified\":1504474496,\"Created\":1392018537,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":5756,\"MemAddr\":\"R:0xH0013c5>0\",\"Title\":\"Moon +// (old prototype)\",\"Description\":\"Collect a 3-up +// Moon\",\"Points\":0,\"Author\":\"UNHchabo\",\"Modified\":1504474501,\"Created\":1393710657,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":45815,\"MemAddr\":\"0xT001ecc>d0xT001ecc.1._R:0xH000db5=0_R:0xH000db3=1_0xT001ec9>d0xT001ec9.1._0xT001ec8>d0xT001ec8.1._0xT001ec7>d0xT001ec7.1._R:0xH000019>0_R:0xH001490>0_R:0xH001f28>0_R:0xH001f29>0_R:0x +// 001f2a>0_0xH001ecc=0.1._R:0xH00187a>0_0xT001eb7>d0xT001eb7.1._0xT001eab>d0xT001eab.1._0xT001ea6>d0xT001ea6.1._0xT001ea8>d0xT001ea8.1._0xT001ea9>d0xT001ea9.1._0xT001ee0>d0xT001ee0.1._0xT001ede>d0xT001ede.1._0xT001ed0>d0xT001ed0.1._0xT001edf>d0xT001edf.1._0xT001ee2>d0xT001ee2.1._0xT001ea7>d0xT001ea7.1.\",\"Title\":\"Mini-Mario +// vs the World 1 (duplicate)\",\"Description\":\"not an achievement is a +// duplicate.\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474529,\"Created\":1488837144,\"BadgeName\":\"46672\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46672.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46672_lock.png\"},{\"ID\":45857,\"MemAddr\":\"R:0xH000100<=10_R:0xH000db3=1_R:0xH000dbed0xT001ecb.1._0xT001ecc>d0xT001ecc.1.\",\"Title\":\"Deathless +// Two Stages (prototype)\",\"Description\":\"prototype - not an +// achievement\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474132,\"Created\":1488840902,\"BadgeName\":\"00000\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00000.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00000_lock.png\"},{\"ID\":45998,\"MemAddr\":\"R:0xH0013ef=1_R:0xH0013bf!=1_0xH000dbe>d0xH000dbe.20._0xH001411=1\",\"Title\":\"I +// could do this all day (unofficial)\",\"Description\":\"Get 20 1-ups on +// Vanilla Secret 2 without touching the +// ground\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474504,\"Created\":1489073027,\"BadgeName\":\"46617\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46617.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46617_lock.png\"},{\"ID\":46009,\"MemAddr\":\"R:0xH0013bf!=42_d0xH0014c9=8_d0xH0014ca=8_d0xH0014cb=8_d0xH0014cc=8_d0xH0014cd=8_d0xH0014ce=8_d0xH0014cf=8_d0xH0014d0=8_0xH0014c9=9_0xH0014ca=9_0xH0014cb=9_0xH0014cc=9_0xH0014cd=9_0xH0014ce=9_0xH0014cf=9_0xH0014d0=9_0xH0013ef=1_0x +// 001408=51201\",\"Title\":\"A Mighty Quake +// (duplicate)\",\"Description\":\"(duplicate)\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474509,\"Created\":1489073081,\"BadgeName\":\"46678\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46678.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46678_lock.png\"},{\"ID\":46011,\"MemAddr\":\"R:0xH0013bf!=76_0xH001dff=12_R:0xH0013ef=1_0x +// 000094<464.1._R:0xH00187a>0\",\"Title\":\"A Groovy Flight +// (duplicate)\",\"Description\":\"Pass SPECIAL-Groovy flying. No landing past +// the first ? box. No +// Yoshi\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474513,\"Created\":1489086171,\"BadgeName\":\"46671\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46671.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46671_lock.png\"},{\"ID\":46062,\"MemAddr\":\"0xH0013f3=1.20._0xH001411=1.20._R:0xH001411=0_0xH000019!=0_R:0xH000019=0\",\"Title\":\"Another +// Kind of Flying (legacy)\",\"Description\":\"Collect a +// P-Balloon\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474516,\"Created\":1489171212,\"BadgeName\":\"46564\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46564.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46564_lock.png\"},{\"ID\":46119,\"MemAddr\":\"P:0xH0013bf!=49_0xH000065=6.1._P:0xH000100!=15_0xH000065=100.1._0xH000065=1.1._0xH000065=62.1._0xH000065=96.1._0xH000065=21.1._0xH000065=118.1._0xH000065=168.1.\",\"Title\":\"Bowser +// Castle Explorer (dup)\",\"Description\":\"Complete all 8 numbered rooms in +// one game +// session\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474465,\"Created\":1489285071,\"BadgeName\":\"46769\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46769.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46769_lock.png\"},{\"ID\":46330,\"MemAddr\":\"R:0xH0013bf!=46_R:0x +// 000094<3926_R:0x 000094>4512_R:0xH0014d0=2_R:0xH0014d0=3_R:0xH0014d0=4_0x +// 000094>4369_0xH0014d0=8\",\"Title\":\"Suicide Prevention +// (duplicate)\",\"Description\":\"O n Vanilla Dome 3 Stop the koopa by the +// midflag from getting +// killed\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474531,\"Created\":1489701495,\"BadgeName\":\"00000\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00000.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00000_lock.png\"},{\"ID\":59018,\"MemAddr\":\"B:0xH0dbf=0_d0xH0dbf=4294967295_0xH0dbf=5\",\"Title\":\"bug\",\"Description\":\"buggy\",\"Points\":1,\"Author\":\"April\",\"Modified\":1549934619,\"Created\":1522626529,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":85062,\"MemAddr\":\"0xH000071=9\",\"Title\":\"I +// failed you, my princess\",\"Description\":\"die for the first +// time\",\"Points\":0,\"Author\":\"matisteve\",\"Modified\":1570325049,\"Created\":1570325049,\"BadgeName\":\"92297\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/92297.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/92297_lock.png\"},{\"ID\":85977,\"MemAddr\":\"0xH000071=9\",\"Title\":\"I +// failed you, my princess\",\"Description\":\"die for the first +// time\",\"Points\":0,\"Author\":\"matisteveTWO\",\"Modified\":1571010788,\"Created\":1571010788,\"BadgeName\":\"92297\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/92297.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/92297_lock.png\"},{\"ID\":102935,\"MemAddr\":\"0xH000dbf=99\",\"Title\":\"A +// Man's Worth of Coins\",\"Description\":\"Have 99 coins. Simple +// enough.\",\"Points\":2,\"Author\":\"ApplemunchRA\",\"Modified\":1584212198,\"Created\":1584212198,\"BadgeName\":\"00000\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00000.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00000_lock.png\"},{\"ID\":113670,\"MemAddr\":\"0xH000065=51_Q:0xH0013bf=7\",\"Title\":\"Hey, +// look There's a shortcut here!\",\"Description\":\"Find the secret room on #2 +// Castle\",\"Points\":5,\"Author\":\"Koakuma\",\"Modified\":1591759773,\"Created\":1591759773,\"BadgeName\":\"124010\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/124010.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/124010_lock.png\"},{\"ID\":128311,\"MemAddr\":\"0xH000f2c>=252\",\"Title\":\"Wellcome\",\"Description\":\"Open +// Super Mario +// Word\",\"Points\":1,\"Author\":\"freeman016\",\"Modified\":1602001511,\"Created\":1602001511,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":128312,\"MemAddr\":\"0xH000f2c>=252\",\"Title\":\"Wellcome\",\"Description\":\"Open +// the +// game\",\"Points\":1,\"Author\":\"freeman016\",\"Modified\":1602004389,\"Created\":1602004389,\"BadgeName\":\"141067\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/141067.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/141067_lock.png\"},{\"ID\":342,\"MemAddr\":\"0xH000dc1=1_0xH0013bf>0\",\"Title\":\"Giddy +// Up!\",\"Description\":\"Catch a ride with a +// friend\",\"Points\":1,\"Author\":\"Scott\",\"Modified\":1561707447,\"Created\":1367266931,\"BadgeName\":\"46580\",\"Flags\":3,\"Type\":null,\"Rarity\":94.83,\"RarityHardcore\":46.63,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46580.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46580_lock.png\"},{\"ID\":341,\"MemAddr\":\"0x +// 001420=5\",\"Title\":\"Unleash The Dragon\",\"Description\":\"Collect 5 +// Dragon Coins in a +// level\",\"Points\":2,\"Author\":\"Scott\",\"Modified\":1561707451,\"Created\":1367266583,\"BadgeName\":\"46591\",\"Flags\":3,\"Type\":null,\"Rarity\":84.33,\"RarityHardcore\":42.96,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46591.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46591_lock.png\"},{\"ID\":340,\"MemAddr\":\"0xH000dbf=99\",\"Title\":\"Rich +// Mario (demoted)\",\"Description\":\"Collect 99 +// coins\",\"Points\":0,\"Author\":\"Scott\",\"Modified\":1504474537,\"Created\":1367254976,\"BadgeName\":\"46582\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46582.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46582_lock.png\"},{\"ID\":4874,\"MemAddr\":\"0xH000019=2\",\"Title\":\"I +// Believe I Can Fly\",\"Description\":\"Collect a +// feather\",\"Points\":1,\"Author\":\"UNHchabo\",\"Modified\":1564447174,\"Created\":1391908064,\"BadgeName\":\"46577\",\"Flags\":3,\"Type\":null,\"Rarity\":60.99,\"RarityHardcore\":32.97,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46577.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46577_lock.png\"},{\"ID\":2251,\"MemAddr\":\"0xH0013f3=1_0xH000dda!=0\",\"Title\":\"Another +// Kind of Flying\",\"Description\":\"Collect a +// P-Balloon\",\"Points\":2,\"Author\":\"Jaarl\",\"Modified\":1564448705,\"Created\":1376615078,\"BadgeName\":\"47083\",\"Flags\":3,\"Type\":null,\"Rarity\":34.48,\"RarityHardcore\":20.7,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47083.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47083_lock.png\"},{\"ID\":4933,\"MemAddr\":\"0xH0018c2=1\",\"Title\":\"Floating +// Through The Clouds\",\"Description\":\"Hijack a Lakitu's +// cloud\",\"Points\":2,\"Author\":\"UNHchabo\",\"Modified\":1564450921,\"Created\":1392010935,\"BadgeName\":\"46571\",\"Flags\":3,\"Type\":null,\"Rarity\":22.91,\"RarityHardcore\":14.7,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46571.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46571_lock.png\"},{\"ID\":1706,\"MemAddr\":\"0xH001900=80\",\"Title\":\"Maximum +// Finish\",\"Description\":\"Cross the finish line at the end of the stage and +// collect the max 50 +// stars\",\"Points\":5,\"Author\":\"jackolantern\",\"Modified\":1561707461,\"Created\":1372674230,\"BadgeName\":\"47084\",\"Flags\":3,\"Type\":null,\"Rarity\":13.06,\"RarityHardcore\":8.48,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47084.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47084_lock.png\"},{\"ID\":2246,\"MemAddr\":\"0xH000f31=0.20._R:0xH000f31!=0_0xH000f32=0.20._R:0xH000f32!=0_0xH000f33=0.20._R:0xH000f33!=0_0xH000dbe>d0xH000dbe.8._0xH001411=1.20._R:0xH001411=0\",\"Title\":\"Perfect +// Bonus Stage\",\"Description\":\"Score 8 extra lives in the 'Bonus +// Game'\",\"Points\":10,\"Author\":\"Jaarl\",\"Modified\":1561707465,\"Created\":1376582613,\"BadgeName\":\"47085\",\"Flags\":3,\"Type\":null,\"Rarity\":12.36,\"RarityHardcore\":7.14,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47085.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47085_lock.png\"},{\"ID\":46003,\"MemAddr\":\"0xH0018da=106_0xH0018e3=10_0xH001411=1S0xH0016e1=13S0xH0016e2=13S0xH0016e3=13S0xH0016e4=13S0xH0016e5=13S0xH0016e6=13\",\"Title\":\"Yoshi's +// a Mommy?\",\"Description\":\"Help Yoshi lay a cloud egg and get 10 happy +// coins to summon a +// 1up\",\"Points\":5,\"Author\":\"kdecks\",\"Modified\":1603575778,\"Created\":1489073051,\"BadgeName\":\"46668\",\"Flags\":3,\"Type\":null,\"Rarity\":8.86,\"RarityHardcore\":6.66,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46668.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46668_lock.png\"},{\"ID\":2253,\"MemAddr\":\"0xH0013bf=37_0xH000dd5=1\",\"Title\":\"I +// is for Icky Iggy\",\"Description\":\"Defeat Iggy Koopa of Castle +// #1\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1564806766,\"Created\":1376616356,\"BadgeName\":\"46598\",\"Flags\":3,\"Type\":\"progression\",\"Rarity\":62.79,\"RarityHardcore\":34.54,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46598.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46598_lock.png\"},{\"ID\":347,\"MemAddr\":\"0xH0013bf=7_0xH000dd5=1\",\"Title\":\"Morton +// Enough\",\"Description\":\"Defeat Morton Koopa Jr. of Castle +// #2\",\"Points\":5,\"Author\":\"Scott\",\"Modified\":1561707476,\"Created\":1367322700,\"BadgeName\":\"46599\",\"Flags\":3,\"Type\":null,\"Rarity\":36.4,\"RarityHardcore\":20.97,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46599.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46599_lock.png\"},{\"ID\":2261,\"MemAddr\":\"0xH0013bf=64_0xH000dd5=1\",\"Title\":\"Lemmy +// Down Slowly\",\"Description\":\"Defeat Lemmy Koopa of Castle +// #3\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1561707478,\"Created\":1376652522,\"BadgeName\":\"46600\",\"Flags\":3,\"Type\":null,\"Rarity\":24.01,\"RarityHardcore\":14.45,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46600.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46600_lock.png\"},{\"ID\":2262,\"MemAddr\":\"0xH0013bf=14_0xH000dd5=1\",\"Title\":\"Ludwig's +// Last Symphony\",\"Description\":\"Defeat Ludwig von Koopa of Castle +// #4\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1561707482,\"Created\":1376653163,\"BadgeName\":\"46601\",\"Flags\":3,\"Type\":null,\"Rarity\":22.74,\"RarityHardcore\":13.62,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46601.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46601_lock.png\"},{\"ID\":2306,\"MemAddr\":\"0xH0013bf=32_0xH000dd5=1\",\"Title\":\"Roy's +// Ploy Destroy-ed\",\"Description\":\"Defeat Roy Koopa of Castle +// #5\",\"Points\":10,\"Author\":\"Jaarl\",\"Modified\":1561707484,\"Created\":1376938808,\"BadgeName\":\"46602\",\"Flags\":3,\"Type\":null,\"Rarity\":17.77,\"RarityHardcore\":10.93,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46602.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46602_lock.png\"},{\"ID\":2307,\"MemAddr\":\"0xH0013bf=31.20._0xH000906=1.20._R:0xH001411!=1\",\"Title\":\"Reznor +// Again? (demoted)\",\"Description\":\"Defeat the Reznor in the clearing of the +// Forest of +// Illusion\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474541,\"Created\":1376938811,\"BadgeName\":\"46597\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597_lock.png\"},{\"ID\":2309,\"MemAddr\":\"0xH000906=1.400._R:0xH0013bf!=26\",\"Title\":\"Wendy +// Chips Are Down\",\"Description\":\"Defeat Wendy O. Koopa of Castle +// #6\",\"Points\":10,\"Author\":\"Jaarl\",\"Modified\":1561707487,\"Created\":1376939582,\"BadgeName\":\"46603\",\"Flags\":3,\"Type\":null,\"Rarity\":15.19,\"RarityHardcore\":9.48,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46603.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46603_lock.png\"},{\"ID\":2308,\"MemAddr\":\"0xH0013bf=27.20._0xH000906=1.20._R:0xH001411!=1\",\"Title\":\"Reznor, +// Do You Ever Give Up? (dmt)\",\"Description\":\"(demoted) Defeat the Reznor at +// the center of Chocolate +// Island\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474546,\"Created\":1376938815,\"BadgeName\":\"46597\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597_lock.png\"},{\"ID\":2342,\"MemAddr\":\"0xH0013bf=52_0xH000dd5=1\",\"Title\":\"Larry +// in the Airy\",\"Description\":\"Defeat Larry Koopa of Castle +// #7\",\"Points\":10,\"Author\":\"Jaarl\",\"Modified\":1561707489,\"Created\":1376970283,\"BadgeName\":\"46604\",\"Flags\":3,\"Type\":null,\"Rarity\":13,\"RarityHardcore\":8.27,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46604.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46604_lock.png\"},{\"ID\":2275,\"MemAddr\":\"0xH0013f9=3_0xH0013ef=1_O:0xH0013bf=50_0xH0013bf=49\",\"Title\":\"Bowser +// Disposer\",\"Description\":\"Beat Bowser and save the +// princess\",\"Points\":25,\"Author\":\"Jaarl\",\"Modified\":1571582041,\"Created\":1376742802,\"BadgeName\":\"46760\",\"Flags\":3,\"Type\":\"win_condition\",\"Rarity\":16.14,\"RarityHardcore\":10.13,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46760.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46760_lock.png\"},{\"ID\":2338,\"MemAddr\":\"0xH0013bf=53.20._0xH000906=1.20._R:0xH001411!=1\",\"Title\":\"Reznor... +// (demoted)\",\"Description\":\"Defeat the Reznor in the Valley of +// Bowser\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474549,\"Created\":1376969412,\"BadgeName\":\"46597\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597_lock.png\"},{\"ID\":2299,\"MemAddr\":\"0xH001f11=0_0xH0013bf=19_0xH000dd5=2_0xH000dda=0\",\"Title\":\"Shoo! +// Shoo! Big Boo\",\"Description\":\"Find and defeat the hidden Big Boo in Donut +// Secret +// House\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1564459621,\"Created\":1376918022,\"BadgeName\":\"46596\",\"Flags\":3,\"Type\":null,\"Rarity\":24.52,\"RarityHardcore\":16.54,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46596.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46596_lock.png\"},{\"ID\":2250,\"MemAddr\":\"0xH0013bf=11_0xH000dd5=1\",\"Title\":\"Reznor +// - Flaming Wheel of Death\",\"Description\":\"Defeat the Reznor atop Vanilla +// Dome\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1575640223,\"Created\":1376615073,\"BadgeName\":\"46597\",\"Flags\":3,\"Type\":null,\"Rarity\":19.72,\"RarityHardcore\":12.66,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597_lock.png\"},{\"ID\":2276,\"MemAddr\":\"0xH0013bf=49_R:0xH0013bf!=49_0xH0013f9=3_R:0xH0013f9!=3_0xH0013ef=1_R:0xH0013ef!=1_0xH000019=0_R:0xH000019!=0\",\"Title\":\"Baby's +// First Kiss (demoted)\",\"Description\":\"Get the princess kiss as Little +// Mario (Front +// Door!)\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474554,\"Created\":1376742805,\"BadgeName\":\"46592\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46592.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46592_lock.png\"},{\"ID\":2277,\"MemAddr\":\"T:0xH0013f9=3_T:0xH0013ef=1_0xH000019=3_T:0xH0003da=131_0xH0013bf=49\",\"Title\":\"Burning +// Bowser\",\"Description\":\"Get the princess kiss as Fire Mario (Front +// Door!)\",\"Points\":10,\"Author\":\"Jaarl\",\"Modified\":1625960412,\"Created\":1376742808,\"BadgeName\":\"46593\",\"Flags\":3,\"Type\":null,\"Rarity\":6.07,\"RarityHardcore\":4.85,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46593.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46593_lock.png\"},{\"ID\":2278,\"MemAddr\":\"0xH0013bf=49_R:0xH0013bf!=49_0xH0013f9=3_R:0xH0013f9!=3_0xH0013ef=1_R:0xH0013ef!=1_0xH000019=2_R:0xH000019!=2\",\"Title\":\"Flying +// Finish (demoted)\",\"Description\":\"Get the princess kiss as Cape Mario +// (Front +// Door!)\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474556,\"Created\":1376742811,\"BadgeName\":\"46594\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46594.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46594_lock.png\"},{\"ID\":2345,\"MemAddr\":\"0xH0013ef=1_0xH0013f9=3_0xH0013bf=50\",\"Title\":\"Backdooring +// Bowser (demoted)\",\"Description\":\"Beat Bowser through the Back +// Door\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474560,\"Created\":1376973534,\"BadgeName\":\"47082\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47082.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47082_lock.png\"},{\"ID\":2199,\"MemAddr\":\"A:d0xH001f28_A:d0xH001f27_A:d0xH001f29_d0xH001f2a=3_A:0xH001f28_A:0xH001f27_A:0xH001f29_0xH001f2a=4_C:0xH001f27=1.1._C:0xH001f28=1.1._C:0xH001f29=1.1._C:0xH001f2a=1.1._M:0!=0.4._R:0x +// 001f28112_0xH000007<128_0xH000008>112_0xH000008<128\",\"Title\":\"I +// Could've Sworn... (demoted)\",\"Description\":\"Get lost in the Forest of +// Illusion\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474567,\"Created\":1376657732,\"BadgeName\":\"47070\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47070.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47070_lock.png\"},{\"ID\":2305,\"MemAddr\":\"0xH00001e=169_0xH000020=28_0xH001f11=0_0xH000dda=0_0xH0013d4=0\",\"Title\":\"Chocolate +// Donut\",\"Description\":\"Walk in a circle on Chocolate +// Island\",\"Points\":1,\"Author\":\"Jaarl\",\"Modified\":1571622934,\"Created\":1376938805,\"BadgeName\":\"47071\",\"Flags\":3,\"Type\":null,\"Rarity\":15.14,\"RarityHardcore\":9.61,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47071.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47071_lock.png\"},{\"ID\":2298,\"MemAddr\":\"0xH0013c3=6\",\"Title\":\"To +// the Stars!\",\"Description\":\"Reach the Star +// Road\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1504475594,\"Created\":1376918019,\"BadgeName\":\"47072\",\"Flags\":3,\"Type\":null,\"Rarity\":28.61,\"RarityHardcore\":18.04,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47072.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47072_lock.png\"},{\"ID\":2300,\"MemAddr\":\"0xH0013c3=5\",\"Title\":\"Mario's +// Special Place\",\"Description\":\"Get to the challenging Special +// Zone\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1504475595,\"Created\":1376918026,\"BadgeName\":\"47073\",\"Flags\":3,\"Type\":null,\"Rarity\":16.61,\"RarityHardcore\":11.08,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47073.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47073_lock.png\"},{\"ID\":2302,\"MemAddr\":\"N:d0xM001eea=0_0xM001eea=1.1._d0xH001f11=5_N:0xH001eec!=d0xH001eec_N:0xH001eed!=d0xH001eed_R:0xH001eee!=d0xH001eeeS0xH001f11=1S0xH001f11=6\",\"Title\":\"Change +// of Scenery\",\"Description\":\"Clear the Special Zone and change the seasons +// in Dinosaur +// Land\",\"Points\":25,\"Author\":\"Jaarl\",\"Modified\":1599744063,\"Created\":1376929303,\"BadgeName\":\"47074\",\"Flags\":3,\"Type\":null,\"Rarity\":10.71,\"RarityHardcore\":7.15,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47074.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47074_lock.png\"},{\"ID\":2304,\"MemAddr\":\"R:0xH001f2e=1.1._C:0xH001f2e>=2.1._C:0xH001f2e>=3.1._C:0xH001f2e>=4.1._C:0xH001f2e>=5.1._C:0xH001f2e>=6.1._C:0xH001f2e>=7.1._C:0xH001f2e>=8.1._C:0xH001f2e>=9.1._C:0xH001f2e>=10.1._C:0xH001f2e>=11.1._C:0xH001f2e>=12.1._C:0xH001f2e>=13.1._C:0xH001f2e>=14.1._C:0xH001f2e>=15.1._C:0xH001f2e>=16.1._C:0xH001f2e>=17.1._C:0xH001f2e>=18.1._C:0xH001f2e>=19.1._C:0xH001f2e>=20.1._C:0xH001f2e>=21.1._C:0xH001f2e>=22.1._C:0xH001f2e>=23.1._C:0xH001f2e>=24.1._C:0xH001f2e>=25.1._C:0xH001f2e>=26.1._C:0xH001f2e>=27.1._C:0xH001f2e>=28.1._C:0xH001f2e>=29.1._C:0xH001f2e>=30.1._C:0xH001f2e>=31.1._C:0xH001f2e>=32.1._C:0xH001f2e>=33.1._C:0xH001f2e>=34.1._C:0xH001f2e>=35.1._C:0xH001f2e>=36.1._C:0xH001f2e>=37.1._C:0xH001f2e>=38.1._C:0xH001f2e>=39.1._C:0xH001f2e>=40.1._C:0xH001f2e>=41.1._C:0xH001f2e>=42.1._C:0xH001f2e>=43.1._C:0xH001f2e>=44.1._C:0xH001f2e>=45.1._C:0xH001f2e>=46.1._C:0xH001f2e>=47.1._C:0xH001f2e>=48.1._C:0xH001f2e>=49.1._C:0xH001f2e>=50.1._C:0xH001f2e>=51.1._C:0xH001f2e>=52.1._C:0xH001f2e>=53.1._C:0xH001f2e>=54.1._C:0xH001f2e>=55.1._C:0xH001f2e>=56.1._C:0xH001f2e>=57.1._C:0xH001f2e>=58.1._C:0xH001f2e>=59.1._C:0xH001f2e>=60.1._C:0xH001f2e>=61.1._C:0xH001f2e>=62.1._C:0xH001f2e>=63.1._C:0xH001f2e>=64.1._C:0xH001f2e>=65.1._C:0xH001f2e>=66.1._C:0xH001f2e>=67.1._C:0xH001f2e>=68.1._C:0xH001f2e>=69.1._C:0xH001f2e>=70.1._C:0xH001f2e>=71.1._C:0xH001f2e>=72.1._C:0xH001f2e>=73.1._C:0xH001f2e>=74.1._C:0xH001f2e>=75.1._C:0xH001f2e>=76.1._C:0xH001f2e>=77.1._C:0xH001f2e>=78.1._C:0xH001f2e>=79.1._C:0xH001f2e>=80.1._C:0xH001f2e>=81.1._C:0xH001f2e>=82.1._C:0xH001f2e>=83.1._C:0xH001f2e>=84.1._C:0xH001f2e>=85.1._C:0xH001f2e>=86.1._C:0xH001f2e>=87.1._C:0xH001f2e>=88.1._C:0xH001f2e>=89.1._C:0xH001f2e>=90.1._C:0xH001f2e>=91.1._C:0xH001f2e>=92.1._C:0xH001f2e>=93.1._C:0xH001f2e>=94.1._C:0xH001f2e>=95.1._C:0xH001f2e=96.1._M:0!=0.96.\",\"Title\":\"All +// Exits\",\"Description\":\"100% clear the +// game\",\"Points\":50,\"Author\":\"Jaarl\",\"Modified\":1625960414,\"Created\":1376929308,\"BadgeName\":\"46761\",\"Flags\":3,\"Type\":null,\"Rarity\":7.49,\"RarityHardcore\":5.74,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46761.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46761_lock.png\"},{\"ID\":2985,\"MemAddr\":\"0x +// 0018f2=3598_0xH0013bf=31\",\"Title\":\"The +// Investigator\",\"Description\":\"Access a secret area in the Forest +// Fortress\",\"Points\":5,\"Author\":\"mrvsonic87\",\"Modified\":1504475601,\"Created\":1379656738,\"BadgeName\":\"47075\",\"Flags\":3,\"Type\":null,\"Rarity\":9.14,\"RarityHardcore\":6.24,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47075.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47075_lock.png\"},{\"ID\":2303,\"MemAddr\":\"0xH001f11=5.7200._R:0xH001f11!=5_R:0xH000100<13_R:0xH000100>14\",\"Title\":\"That +// Oh-So-Familiar Tune\",\"Description\":\"Find the secret in the Special +// Zone\",\"Points\":2,\"Author\":\"Jaarl\",\"Modified\":1626262445,\"Created\":1376929305,\"BadgeName\":\"46563\",\"Flags\":3,\"Type\":null,\"Rarity\":9.1,\"RarityHardcore\":6.95,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46563.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46563_lock.png\"},{\"ID\":383324,\"MemAddr\":\"N:d0xH000100=19_N:0xH000100=20_O:0xH0013cf=64_0xH000071=10S0xH0013bf=37_0xT001ec7=1S0xH0013bf=7_0xT001ea9=1S0xH0013bf=64_0xT001ee2=1S0xH0013bf=14_0xT001eb0=1S0xH0013bf=32_0xT001ec2=1S0xH0013bf=26_0xT001ebc=1S0xH0013bf=52_0xT001ed6=1S0xH0013bf=11_0xT001ead=1S0xH0013bf=31_0xT001ec1=1S0xH0013bf=27_0xT001ebd=1S0xH0013bf=53_0xT001ed7=1\",\"Title\":\"Entering +// the Castle Ruins\",\"Description\":\"Reenter a Castle after it was destroyed +// by pressing L + +// R.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1717162497,\"Created\":1703596667,\"BadgeName\":\"431957\",\"Flags\":3,\"Type\":null,\"Rarity\":4.45,\"RarityHardcore\":3.51,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431957.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431957_lock.png\"},{\"ID\":45901,\"MemAddr\":\"R:0xH0013bf!=39_R:0xH000dc0!=0_R:0x +// 00001e<2032_R:0x +// 00001e>2128Sd0xH0016e6!=13_0xH0016e6=13Sd0xH0016e5!=13_0xH0016e5=13Sd0xH0016e4!=13_0xH0016e4=13Sd0xH0016e3!=13_0xH0016e3=13Sd0xH0016e2!=13_0xH0016e2=13Sd0xH0016e1!=13_0xH0016e1=13\",\"Title\":\"Bonus +// on the Island\",\"Description\":\"Obtain 1-up from Bonus Block in Yoshi's +// Island +// 3\",\"Points\":5,\"Author\":\"Salsa\",\"Modified\":1576437920,\"Created\":1488942160,\"BadgeName\":\"46551\",\"Flags\":3,\"Type\":null,\"Rarity\":13.13,\"RarityHardcore\":9.13,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46551.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46551_lock.png\"},{\"ID\":45902,\"MemAddr\":\"R:0xH0013bf!=5_R:0xH000dc0!=0_R:0x +// 00001e<2224_R:0x +// 00001e>2352Sd0xH0016e6!=13_0xH0016e6=13Sd0xH0016e5!=13_0xH0016e5=13Sd0xH0016e4!=13_0xH0016e4=13Sd0xH0016e3!=13_0xH0016e3=13Sd0xH0016e2!=13_0xH0016e2=13Sd0xH0016e1!=13_0xH0016e1=13\",\"Title\":\"Bonus +// of the Donuts\",\"Description\":\"Obtain 1-up from Bonus Block in Donut +// Plains +// 3\",\"Points\":5,\"Author\":\"Salsa\",\"Modified\":1576437924,\"Created\":1488942165,\"BadgeName\":\"46552\",\"Flags\":3,\"Type\":null,\"Rarity\":13.46,\"RarityHardcore\":9.32,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46552.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46552_lock.png\"},{\"ID\":45903,\"MemAddr\":\"R:0xH0013bf!=12_R:0xH000dc0!=0_R:0x +// 00001e<1404_R:0x +// 00001e>1404_R:0xH000094<192_R:0xH00009c!=22Sd0xH0016e6!=13_0xH0016e6=13Sd0xH0016e5!=13_0xH0016e5=13Sd0xH0016e4!=13_0xH0016e4=13Sd0xH0016e3!=13_0xH0016e3=13Sd0xH0016e2!=13_0xH0016e2=13Sd0xH0016e1!=13_0xH0016e1=13\",\"Title\":\"Bonus +// on the Butter\",\"Description\":\"Obtain 1-up from Bonus Block in Butter +// Bridge +// 1\",\"Points\":5,\"Author\":\"Salsa\",\"Modified\":1576437928,\"Created\":1488942170,\"BadgeName\":\"46553\",\"Flags\":3,\"Type\":null,\"Rarity\":5.48,\"RarityHardcore\":4.56,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46553.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46553_lock.png\"},{\"ID\":45904,\"MemAddr\":\"R:0xH0013bf!=35_R:0xH000dc0!=0_R:0x +// 00001e<2160_R:0x +// 00001e>2304Sd0xH0016e6!=13_0xH0016e6=13Sd0xH0016e5!=13_0xH0016e5=13Sd0xH0016e4!=13_0xH0016e4=13Sd0xH0016e3!=13_0xH0016e3=13Sd0xH0016e2!=13_0xH0016e2=13Sd0xH0016e1!=13_0xH0016e1=13\",\"Title\":\"Bonus +// of the Chocolate\",\"Description\":\"Obtain 1-up from Bonus Block in +// Chocolate Island +// 3\",\"Points\":5,\"Author\":\"Salsa\",\"Modified\":1576437931,\"Created\":1488942175,\"BadgeName\":\"46554\",\"Flags\":3,\"Type\":null,\"Rarity\":11.29,\"RarityHardcore\":7.4,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46554.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46554_lock.png\"},{\"ID\":383328,\"MemAddr\":\"Q:0xH0013bf=38_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=509428\",\"Title\":\"Secrets +// of the Cheep Cheeps\",\"Description\":\"Trigger the hidden 1-Up in Yoshi's +// Island 4.\",\"Points\":3,\"Author\":\"SporyTike\",\"Modified\":1703596668,\"Created\":1703596668,\"BadgeName\":\"431961\",\"Flags\":3,\"Type\":null,\"Rarity\":2.18,\"RarityHardcore\":1.87,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431961.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431961_lock.png\"},{\"ID\":383325,\"MemAddr\":\"Q:0xH0013bf=21_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=509653\",\"Title\":\"Secrets +// of the Chucks\",\"Description\":\"Trigger the hidden 1-Up in Donut +// Plains 1.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596667,\"Created\":1703596667,\"BadgeName\":\"431958\",\"Flags\":3,\"Type\":null,\"Rarity\":2.2,\"RarityHardcore\":1.89,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431958.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431958_lock.png\"},{\"ID\":383326,\"MemAddr\":\"Q:0xH0013bf=6_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=510020\",\"Title\":\"Secrets +// of the Galoombas\",\"Description\":\"Trigger the hidden 1-Up in Donut +// Plains 4.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596667,\"Created\":1703596667,\"BadgeName\":\"431959\",\"Flags\":3,\"Type\":null,\"Rarity\":2.07,\"RarityHardcore\":1.79,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431959.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431959_lock.png\"},{\"ID\":383332,\"MemAddr\":\"Q:0xH0013bf=7_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=510246\",\"Title\":\"Secrets +// of Morton\",\"Description\":\"Trigger the hidden 1-Up in #2 Morton's +// Castle.\",\"Points\":1,\"Author\":\"SporyTike\",\"Modified\":1703596668,\"Created\":1703596668,\"BadgeName\":\"431965\",\"Flags\":3,\"Type\":null,\"Rarity\":2.5,\"RarityHardcore\":2.06,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431965.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431965_lock.png\"},{\"ID\":383333,\"MemAddr\":\"Q:0xH0013bf=43_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=510940\",\"Title\":\"Secrets +// of the Boos\",\"Description\":\"Trigger the hidden 1-Up in Vanilla Ghost +// House.\",\"Points\":3,\"Author\":\"SporyTike\",\"Modified\":1703596669,\"Created\":1703596669,\"BadgeName\":\"431966\",\"Flags\":3,\"Type\":null,\"Rarity\":2.04,\"RarityHardcore\":1.77,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431966.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431966_lock.png\"},{\"ID\":383335,\"MemAddr\":\"Q:0xH0013bf=61_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=511188\",\"Title\":\"Secrets +// of the Bullet Bills\",\"Description\":\"Trigger the hidden 1-Up in Vanilla +// Dome 4.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596675,\"Created\":1703596675,\"BadgeName\":\"431974\",\"Flags\":3,\"Type\":null,\"Rarity\":2.03,\"RarityHardcore\":1.74,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431974.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431974_lock.png\"},{\"ID\":383331,\"MemAddr\":\"Q:0xH0013bf=11_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=511821\",\"Title\":\"Secrets +// of the Fishbones\",\"Description\":\"Trigger the hidden 1-Up in Vanilla +// Fortress.\",\"Points\":3,\"Author\":\"SporyTike\",\"Modified\":1703596668,\"Created\":1703596668,\"BadgeName\":\"431964\",\"Flags\":3,\"Type\":null,\"Rarity\":1.86,\"RarityHardcore\":1.63,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431964.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431964_lock.png\"},{\"ID\":383327,\"MemAddr\":\"Q:0xH0013bf=16_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=512067\",\"Title\":\"Secrets +// of the Sumo Bros\",\"Description\":\"Trigger the hidden 1-Up in Cookie +// Mountain.\",\"Points\":1,\"Author\":\"SporyTike\",\"Modified\":1703596668,\"Created\":1703596668,\"BadgeName\":\"431960\",\"Flags\":3,\"Type\":null,\"Rarity\":4.05,\"RarityHardcore\":2.84,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431960.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431960_lock.png\"},{\"ID\":383336,\"MemAddr\":\"Q:0xH0013bf=71_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=513093\",\"Title\":\"Secrets +// of the Bubbles\",\"Description\":\"Trigger the hidden 1-Up in Forest of +// Illusion 3.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596675,\"Created\":1703596675,\"BadgeName\":\"431975\",\"Flags\":3,\"Type\":null,\"Rarity\":1.89,\"RarityHardcore\":1.65,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431975.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431975_lock.png\"},{\"ID\":46332,\"MemAddr\":\"R:0xH0013bf!=46_R:0x +// 000094<4194_R:0x 000094>4306_0x +// 000096>=276_0xH000013>d0xH000013.240._P:0xH0013d4=1S0xH0000a6=2_P:0xH0014d0!=8S0xH0000a5=2_P:0xH0014d1!=8\",\"Title\":\"Suicide +// Prevention (moved to bonus)\",\"Description\":\"On 3-3 prevent kicking koopa +// by checkpoint from dying. Stay by +// him.\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1505703949,\"Created\":1489701538,\"BadgeName\":\"47077\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47077.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47077_lock.png\"},{\"ID\":383330,\"MemAddr\":\"Q:0xH0013bf=36_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=514026\",\"Title\":\"Secrets +// of Chocolate\",\"Description\":\"Trigger the hidden 1-Up in Chocolate +// Island 2.\",\"Points\":3,\"Author\":\"SporyTike\",\"Modified\":1703596668,\"Created\":1703596668,\"BadgeName\":\"431963\",\"Flags\":3,\"Type\":null,\"Rarity\":1.75,\"RarityHardcore\":1.56,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431963.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431963_lock.png\"},{\"ID\":46061,\"MemAddr\":\"R:0xH0013bf!=1_R:0xH001411=0_R:0xH000f31=1_0xH000dbe>d0xH000dbe.50.\",\"Title\":\"Silver +// Worth More than Gold (moved to bonus)\",\"Description\":\"On Vanilla Secret 2 +// Get 50 1ups with 200 or more on the +// clock\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474573,\"Created\":1489170843,\"BadgeName\":\"46762\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46762.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46762_lock.png\"},{\"ID\":383329,\"MemAddr\":\"Q:0xH0013bf=26_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=515003\",\"Title\":\"Secrets +// of Wendy\",\"Description\":\"Trigger the hidden 1-Up in #6 Wendy's +// Castle.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596668,\"Created\":1703596668,\"BadgeName\":\"431962\",\"Flags\":3,\"Type\":null,\"Rarity\":2.22,\"RarityHardcore\":1.8,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431962.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431962_lock.png\"},{\"ID\":46008,\"MemAddr\":\"R:0xH0013bf!=15_R:0xH000019!=3_0xH000095=1.1._R:0xH001411=0_0x +// 000094>=2288_0x 000096>=341_R:0xH00187a=1_R:0xO000077=1\",\"Title\":\"Inferno +// Tornado (moved to bonus)\",\"Description\":\"As Fire Mario in Cheese Bridge +// cross 1st gap with only spin jump. No +// platforms\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474576,\"Created\":1489073077,\"BadgeName\":\"46606\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46606.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46606_lock.png\"},{\"ID\":383340,\"MemAddr\":\"Q:0xH0013bf=57_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=515507_Q:0x +// 000094<2048\",\"Title\":\"Secrets of the Moving +// Walls\",\"Description\":\"Trigger the first hidden 1-Up in Valley of +// Bowser 2.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596683,\"Created\":1703596683,\"BadgeName\":\"431979\",\"Flags\":3,\"Type\":null,\"Rarity\":1.68,\"RarityHardcore\":1.48,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431979.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431979_lock.png\"},{\"ID\":46002,\"MemAddr\":\"R:0xH0013bf!=14_0xH00c685>=204_0xH00c685<=205_0xH0017c3=2.11._P:d0xH0017c3=2_R:0xH00c680=152_R:0xH000065!=59\",\"Title\":\"Surfing +// the Shell (moved to bonus)\",\"Description\":\"In #4 Ludwig's Castle bouce on +// Ludwig's shell 11 times before he +// leaps\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1516573766,\"Created\":1489073044,\"BadgeName\":\"46667\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46667.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46667_lock.png\"},{\"ID\":383337,\"MemAddr\":\"Q:0xH0013bf=57_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=515507_Q:0x +// 000094>2048\",\"Title\":\"Secrets of the Moving Walls +// II\",\"Description\":\"Trigger the second hidden 1-Up in Valley of +// Bowser 2.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596675,\"Created\":1703596675,\"BadgeName\":\"431976\",\"Flags\":3,\"Type\":null,\"Rarity\":1.58,\"RarityHardcore\":1.4,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431976.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431976_lock.png\"},{\"ID\":2263,\"MemAddr\":\"0xH001697=12_0xH0013bf=66_R:0xH001697=0\",\"Title\":\"Bother +// The Wigglers (moved to bonus)\",\"Description\":\"Jump on yellow Wigglers in +// Forest of Illusion 12 times in a row for a +// surprise!\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474582,\"Created\":1376655155,\"BadgeName\":\"46574\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46574.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46574_lock.png\"},{\"ID\":383339,\"MemAddr\":\"Q:0xH0013bf=52_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=516017\",\"Title\":\"Secrets +// of Larry\",\"Description\":\"Trigger the hidden 1-Up in #7 Larry's +// Castle.\",\"Points\":3,\"Author\":\"SporyTike\",\"Modified\":1703596679,\"Created\":1703596679,\"BadgeName\":\"431978\",\"Flags\":3,\"Type\":null,\"Rarity\":1.64,\"RarityHardcore\":1.45,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431978.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431978_lock.png\"},{\"ID\":383334,\"MemAddr\":\"Q:0xH0013bf=78_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=517084\",\"Title\":\"Secrets +// of the Vines\",\"Description\":\"Trigger the hidden 1-Up in +// Gnarly.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596669,\"Created\":1703596669,\"BadgeName\":\"431970\",\"Flags\":3,\"Type\":null,\"Rarity\":1.87,\"RarityHardcore\":1.62,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431970.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431970_lock.png\"},{\"ID\":46171,\"MemAddr\":\"R:0xH0018a7=50_R:0xH00009c=13_R:0xH00005a!=254_0xH0000d2=0.10._0xH0000d2=6_0xH0000d1>=213_0xH0000d1<=242\",\"Title\":\"These +// Platforms are so Slow... (demoted)\",\"Description\":\"Arrive at the First +// Door of Larry's Castle without Activating the +// Platform\",\"Points\":0,\"Author\":\"GalacticSpear\",\"Modified\":1504474585,\"Created\":1489319085,\"BadgeName\":\"46770\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46770.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46770_lock.png\"},{\"ID\":46006,\"MemAddr\":\"0xH000dba=4_0xH000dc1>=1_0xH000f2d!=d0xH000f2d.10._R:0xH0018ac=0_R:0xH0013bf!=28_R:0xH001411=0_R:0x +// 000094>1680\",\"Title\":\"A Trip to Pound Town (dmt, fals +// pos)\",\"Description\":\"Pound 10* spinys hard on 6-5 with Y. Yoshi. Don't +// spit or swallow til the +// end\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474587,\"Created\":1489073069,\"BadgeName\":\"46670\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46670.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46670_lock.png\"},{\"ID\":45909,\"MemAddr\":\"R:0xH000db3=1_R:0x +// 000f34>90_P:0xH000dd5<1_P:0xH000dd5>2_R:d0x 000f34>0x +// 000f34_0xH0013bf=42.1._0xH0013bf=41.1._0xH0013bf=39.1._0xH0013bf=38.1._0xH0013bf=37.1.\",\"Title\":\"Mario +// vs. the Score I (Yoshi's Island) (demoted)\",\"Description\":\"Score 900 +// points or less playing through all stages in Yoshi +// Island\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474594,\"Created\":1488942350,\"BadgeName\":\"46713\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46713.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46713_lock.png\"},{\"ID\":45910,\"MemAddr\":\"R:0xH000db3=1_R:0x +// 000f34>100_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x +// 000f34_0xH0013bf=21.1._0xH0013bf=9.1._0xH0013bf=5.1._0xH0013bf=6.1._0xH0013bf=10.1._0xH0013bf=47.1._0xH0013bf=19.1._0xH0013bf=4.1._0xH0013bf=7.1.\",\"Title\":\"Mario +// vs. the Score II (Donut Plains) (demoted)\",\"Description\":\"Score 1000 +// points or less playing through all stages in Donut +// Plains\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474597,\"Created\":1488942354,\"BadgeName\":\"46714\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46714.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46714_lock.png\"},{\"ID\":45911,\"MemAddr\":\"R:0xH000db3=1_R:0x +// 000f34>301_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x +// 000f34_0xH0013bf=62.1._0xH0013bf=60.1._0xH0013bf=61.1._0xH0013bf=43.1._0xH0013bf=64.1._0xH0013bf=1.1._0xH0013bf=2.1._0xH0013bf=11.1._0xH0013bf=45.1._0xH0013bf=46.1.\",\"Title\":\"Mario +// vs. the Score III (Vanilla Dome) (demoted)\",\"Description\":\"Score 3010 +// points or less playing through all stages in Vanilla +// Dome\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474603,\"Created\":1488942359,\"BadgeName\":\"46715\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46715.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46715_lock.png\"},{\"ID\":45912,\"MemAddr\":\"R:0xH000db3=1_R:0x +// 000f34>110_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x +// 000f34_0xH0013bf=12.1._0xH0013bf=13.1._0xH0013bf=15.1._0xH0013bf=17.1._0xH0013bf=16.1._0xH0013bf=14.1.\",\"Title\":\"Mario +// vs. the Score IV (Twin Bridges) (demoted)\",\"Description\":\"Score 1100 +// points or less playing through all stages in Twin +// Bridges\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474609,\"Created\":1488942364,\"BadgeName\":\"46716\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46716.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46716_lock.png\"},{\"ID\":45913,\"MemAddr\":\"R:0xH000db3=1_R:0x +// 000f34>95_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x +// 000f34_0xH0013bf=66.1._0xH0013bf=68.1._0xH0013bf=71.1._0xH0013bf=67.1._0xH0013bf=65.1._0xH0013bf=70.1._0xH0013bf=31.1._0xH0013bf=32.1.\",\"Title\":\"Mario +// vs. the Score V (Forest of Illusion) (demoted)\",\"Description\":\"Score 950 +// points or less playing through all stages in Forest of +// Illusion\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474613,\"Created\":1488942370,\"BadgeName\":\"46717\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46717.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46717_lock.png\"},{\"ID\":46304,\"MemAddr\":\"R:0xH000db3=1_R:0x +// 000f34>0_P:0xH000dd5<1_P:0xH000dd5>2_R:d0x 000f34>0x +// 000f34_0xH0013bf=21.1._0xH0013bf=9.1._0xH0013bf=10.1._0xH0013bf=62.1._0xH0013bf=60.1._0xH0013bf=15.1.\",\"Title\":\"Loopholes +// by Keyholes - Donut Vanilla Butter (moved to bonus)\",\"Description\":\"Don't +// score any points reaching keyhole exits on stages in Worlds 2, 3 and +// 4\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474617,\"Created\":1489616634,\"BadgeName\":\"47079\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47079.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47079_lock.png\"},{\"ID\":45914,\"MemAddr\":\"R:0xH000db3=1_R:0x +// 000f34>285_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x +// 000f34_0xH0013bf=34.1._0xH0013bf=36.1._0xH0013bf=35.1._0xH0013bf=29.1._0xH0013bf=28.1._0xH0013bf=59.1._0xH0013bf=33.1._0xH0013bf=27.1._0xH0013bf=26.1._0xH0013bf=24.1.\",\"Title\":\"Mario +// vs. the Score VI (Chocolate Island) (demoted)\",\"Description\":\"Score 2850 +// points or less playing through all stages in Chocolate +// Island\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474620,\"Created\":1488942396,\"BadgeName\":\"46718\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46718.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46718_lock.png\"},{\"ID\":46305,\"MemAddr\":\"R:0xH000db3=1_R:0x +// 000f34>0_P:0xH000dd5<1_P:0xH000dd5>2_R:d0x 000f34>0x +// 000f34_0xH0013bf=66.1._0xH0013bf=68.1._0xH0013bf=67.1._0xH0013bf=36.1._0xH0013bf=57.1._0xH0013bf=56.1._0xH0013bf=51.1.\",\"Title\":\"Loopholes +// by Keyholes - Chocolate Forest Bowser (bonus)\",\"Description\":\"Don't score +// any points reaching keyhole exits on stages in Worlds 4, 6 and +// 7\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474637,\"Created\":1489616639,\"BadgeName\":\"47080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47080_lock.png\"},{\"ID\":45915,\"MemAddr\":\"R:0xH000db3=1_R:0x +// 000f34>95_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x +// 000f34_0xH0013bf=58.1._0xH0013bf=57.1._0xH0013bf=55.1._0xH0013bf=51.1._0xH0013bf=56.1._0xH0013bf=52.1._0xH0013bf=53.1._0xH0013bf<=50_0xH0013bf>=49_0xH0013ef=1_0xH0013f9=3\",\"Title\":\"Mario +// vs. the Score VII (Valley of Bowser) (demoted)\",\"Description\":\"Score 950 +// points or less playing through all stages in Valley of +// Bowser\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474640,\"Created\":1488942402,\"BadgeName\":\"46719\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46719.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46719_lock.png\"},{\"ID\":45916,\"MemAddr\":\"R:0xH000db3=1_R:0x +// 000f34>110_P:0xH000dd5>2_R:d0x 000f34>0x +// 000f34_0xH0013bf=88.1._0xH0013bf=84.1._0xH0013bf=86.1._0xH0013bf=89.1._0xH0013bf=90.1._P:0xH000dd5<1\",\"Title\":\"Loopholes +// by Keyholes - Star World (moved to bonus)\",\"Description\":\"Score 1100 +// points or less playing through all stages in Star +// World\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474645,\"Created\":1488942407,\"BadgeName\":\"47081\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47081.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47081_lock.png\"},{\"ID\":46307,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=42.1._0xH0013bf=41.1._0xH0013bf=39.1._0xH0013bf=38.1._0xH0013bf=37_0x +// 0000ce=50896_R:0xH000db3=1_R:0xH000dd5=128_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0\",\"Title\":\"Super +// Pacifist Mario feat. Hungry Yoshi I (Yoshi Island) +// (bon)\",\"Description\":\"See achievement comments for +// details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474058,\"Created\":1489623727,\"BadgeName\":\"46908\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46908.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46908_lock.png\"},{\"ID\":45917,\"MemAddr\":\"R:0xH000db3=1_R:0x +// 000f34>100_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x +// 000f34_0xH0013bf=78.1._0xH0013bf=79.1._0xH0013bf=80.1._0xH0013bf=81.1._0xH0013bf=76.1._0xH0013bf=75.1._0xH0013bf=74.1._0xH0013bf=73.1.\",\"Title\":\"Mario +// vs. the Score IX (Special World) (demoted)\",\"Description\":\"Score 1000 +// points or less playing through all stages in Special +// World\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474649,\"Created\":1488942411,\"BadgeName\":\"46721\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46721.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46721_lock.png\"},{\"ID\":46308,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=21.1._0xH0013bf=9.1._0xH0013bf=5.1._0xH0013bf=6.1._0xH0013bf=10.1._0xH0013bf=47.1._0xH0013bf=19.1._0xH0013bf=4.1._0xH0013bf=7.1._0x +// 0000ce=51523_R:0xH000db3=1_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0\",\"Title\":\"Super +// Pacifist Marioi II (Donut Plains) (bonus)\",\"Description\":\"See achievement +// comments for +// details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474682,\"Created\":1489635441,\"BadgeName\":\"46909\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46909.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46909_lock.png\"},{\"ID\":46309,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=62.1._0xH0013bf=60.1._0xH0013bf=61.1._0xH0013bf=43.1._0xH0013bf=64_0xH0013bf=1.1._0xH0013bf=2.1._0xH0013bf=45.1._0x +// 0000ce=52672_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_R:0xH000db3=1_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0_P:d0xH0000a6=2\",\"Title\":\"Super +// Pacifist Mario III (Vanilla Dome) (bonus)\",\"Description\":\"See achievement +// comments for +// details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474749,\"Created\":1489635447,\"BadgeName\":\"46910\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46910.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46910_lock.png\"},{\"ID\":46310,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=12.1._0xH0013bf=15.1._0xH0013bf=17.1._0xH0013bf=16.1._0xH0013bf=14_0x +// 0000ce=53586_R:0xH000db3=1_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0_0xH0013bf=13.1.\",\"Title\":\"Super +// Pacifist Mario IV (Twin Bridges) (bonus)\",\"Description\":\"See achievement +// comments for +// details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474894,\"Created\":1489635455,\"BadgeName\":\"46911\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46911.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46911_lock.png\"},{\"ID\":46311,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=66.1._0xH0013bf=68.1._0xH0013bf=71.1._0xH0013bf=67.1._0xH0013bf=65.1._0xH0013bf=70.1._0xH0013bf=32_0x +// 0000ce=54557_R:0xH000db3=1_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0_P:0x +// 0000a2=30840\",\"Title\":\"Super Pacifist Mario V (Forest of Illusion) (moved +// to bonus)\",\"Description\":\"See achievement comments for +// details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474914,\"Created\":1489635462,\"BadgeName\":\"46912\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46912.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46912_lock.png\"},{\"ID\":46312,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=34.1._0xH0013bf=36.1._0xH0013bf=35.1._0xH0013bf=29.1._0xH0013bf=28.1._0xH0013bf=59.1._0xH0013bf=33.1._0xH0013bf=26_0xH0013bf=24.1._0x +// 0000ce=56354_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_R:0xH000db3=1_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0\",\"Title\":\"Super +// Pacifist Mario VI (Chocolate Island) (moved to bonus)\",\"Description\":\"See +// achievement comments for +// details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474928,\"Created\":1489635469,\"BadgeName\":\"46913\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46913.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46913_lock.png\"},{\"ID\":46306,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=57.1._0xH0013bf=55.1._0xH0013bf=51.1._0xH0013bf=56.1._0xH0013bf=49.1._0xH0013bf=58.1._0x +// 0000ce=57792_R:0xH000db3=1_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0\",\"Title\":\"Super +// Pacifist Mario VII (Valley of Bowser) (moved to +// bonus)\",\"Description\":\"See achievement comments for +// details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474937,\"Created\":1489623613,\"BadgeName\":\"46916\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46916.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46916_lock.png\"},{\"ID\":46313,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=88.1._0xH0013bf=84.1._0xH0013bf=86.1._0xH0013bf=89.1._0xH0013bf=90.1._0xH000dd5>0_R:0xH000db3=1_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0\",\"Title\":\"Super +// Pacifist Mario VIII (Star World) (moved to bonus)\",\"Description\":\"See +// achievement comments for +// details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474946,\"Created\":1489635474,\"BadgeName\":\"46914\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46914.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46914_lock.png\"},{\"ID\":46314,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=78.1._0xH0013bf=79.1._0xH0013bf=80.1._0xH0013bf=76.1._0xH0013bf=75.1._0xH0013bf=74.1._0xH0013bf=73.1._0xH001dff=12_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_R:0xH000db3=1_R:0xH0013cf=64_R:0xH000100=0_0xH0013bf=81.1._P:d0xH000dd5=1_P:d0xH000dd5=2\",\"Title\":\"Super +// Pacifist Mario IX (Special World) (moved to bonus)\",\"Description\":\"See +// achievement comments for +// details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474957,\"Created\":1489635478,\"BadgeName\":\"46915\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46915.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46915_lock.png\"},{\"ID\":29653,\"MemAddr\":\"C:0xS001f34=1.1._C:0xR001f34=1.1._C:0xM001f33=1.1._C:0xN001f33=1.1._M:0!=0.4._0xS001f34=1_0xR001f34=1_0xM001f33=1_0xN001f33=1_0xH000008!=85_R:0xH001f33d0xH000dbe.8._R:0xH001411=0\",\"Title\":\"Fence +// Offense\",\"Description\":\"Get 8 1-ups at #1 Iggy's Castle with 230 or more +// on the time +// clock\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1625960433,\"Created\":1489073056,\"BadgeName\":\"46608\",\"Flags\":3,\"Type\":null,\"Rarity\":4.9,\"RarityHardcore\":4.24,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46608.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46608_lock.png\"},{\"ID\":46170,\"MemAddr\":\"N:0xH0013bf=4_N:d0xH000dda=0_0xH000dda=7.1._R:0xH0013e0=21_R:0xH000019=2_T:0xH000dd5=2_R:0xH0013bf!=4_R:0xH000dd5=128\",\"Title\":\"Who +// Needs Vines?\",\"Description\":\"Get the Normal Exit in Donut Ghost House +// without a Feather or Climbing on a +// Vine\",\"Points\":10,\"Author\":\"GalacticSpear\",\"Modified\":1625960437,\"Created\":1489319082,\"BadgeName\":\"46771\",\"Flags\":3,\"Type\":null,\"Rarity\":6.03,\"RarityHardcore\":5.08,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46771.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46771_lock.png\"},{\"ID\":46331,\"MemAddr\":\"0xH0000aa=64_0xH0014c8=2_R:0xH0013bf!=19_R:0xH00005a!=237\",\"Title\":\"Spooking +// Big Boo\",\"Description\":\"In the first room of Donut Secret House kill Big +// Boo\",\"Points\":5,\"Author\":\"kdecks\",\"Modified\":1571057401,\"Created\":1489701500,\"BadgeName\":\"47076\",\"Flags\":3,\"Type\":null,\"Rarity\":8.75,\"RarityHardcore\":7.06,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47076.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47076_lock.png\"},{\"ID\":46001,\"MemAddr\":\"R:0xH0013bf!=68_R:0xH000077>0_R:0xH000071=1_0xH000f31=3.1._R:0xH000071=9_T:0xH001dff=12ST:0xH001dff=12ST:0xH001dff=128\",\"Title\":\"Everything +// is Lava\",\"Description\":\"Get to the normal exit of Forest of Illusion 2 +// without touching any walls, enemies, blue +// blocks\",\"Points\":25,\"Author\":\"kdecks\",\"Modified\":1625960440,\"Created\":1489073039,\"BadgeName\":\"46666\",\"Flags\":3,\"Type\":null,\"Rarity\":3.44,\"RarityHardcore\":2.9,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46666.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46666_lock.png\"},{\"ID\":45997,\"MemAddr\":\"0xH001523=16_0xH001411=1_0xH000f31=3.1._T:0x +// 000094>=2448_R:0xH000019=2_R:0xH001410=2_R:0xH0018e0>0_R:0xH001523=0_R:0xH0013bf!=67\",\"Title\":\"Didn't +// Take the Bait\",\"Description\":\"On Forest of Illusion 4, get past Lakitu +// without killing him or taking the 1-up. No cape, +// wings.\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1671592204,\"Created\":1489073023,\"BadgeName\":\"46615\",\"Flags\":3,\"Type\":null,\"Rarity\":6.14,\"RarityHardcore\":5.15,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46615.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46615_lock.png\"},{\"ID\":46120,\"MemAddr\":\"P:0xH0013bf!=49_P:0xH000100!=15_C:0xH0000ce=103.1._C:0xH0000ce=141.1._C:0xH0000ce=197.1._C:0xH0000ce=232.1._C:0xH0000ce=20.1._C:0xH0000ce=49.1._C:0xH0000ce=96.1._C:0xH0000ce=131.1._M:0!=0.8.\",\"Title\":\"Bowser's +// Castle Explorer\",\"Description\":\"Complete all 8 numbered rooms in one game +// session\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1625960448,\"Created\":1489285260,\"BadgeName\":\"47078\",\"Flags\":3,\"Type\":null,\"Rarity\":4.64,\"RarityHardcore\":4.01,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47078.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47078_lock.png\"},{\"ID\":46007,\"MemAddr\":\"N:d0xH0000a5=44_0xH0000a5=45.1._P:0xH00187a!=0.1._N:d0xH0013fb=0_P:0xH0013fb!=0.1._P:0xH000dda=255.5._T:0x +// 0014cf=12_O:0xH001dff=12_T:0xH001dff=128_N:p0xH0000a5=45_T:0xH0000a5=120SR:0xH0013bf!=89_R:0xH000d9b=2_N:0xH0000a5!=45_R:0xH0000a5!=120\",\"Title\":\"I +// Starved Yoshi and All I Got Was...\",\"Description\":\"On Star world 4 bring +// Baby Yoshi to the end flag without feeding it to +// adulthood\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1669165514,\"Created\":1489073073,\"BadgeName\":\"46607\",\"Flags\":3,\"Type\":null,\"Rarity\":7,\"RarityHardcore\":5.76,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46607.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46607_lock.png\"},{\"ID\":45999,\"MemAddr\":\"R:0xH0013bf!=79_R:0xH000dba=6_R:0xH0013f3>0_T:0xH001dff=12_0xH000f31=3.1.\",\"Title\":\"Who +// Needs Helium?\",\"Description\":\"Complete SPECIAL-Tubular without Blue +// Yoshi, or Balloon +// Mario\",\"Points\":25,\"Author\":\"kdecks\",\"Modified\":1625960450,\"Created\":1489073032,\"BadgeName\":\"46677\",\"Flags\":3,\"Type\":null,\"Rarity\":4.05,\"RarityHardcore\":3.19,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46677.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46677_lock.png\"},{\"ID\":46000,\"MemAddr\":\"R:0xH0013bf!=76_T:0xH001dff=12_R:0xH0013ef=1_0x +// 000094<464.1._R:0xH00187a>0\",\"Title\":\"A Groovy +// Flight\",\"Description\":\"Complete SPECIAL-Groovy without landing past the +// first ? box. No +// Yoshi\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1625960453,\"Created\":1489073036,\"BadgeName\":\"46665\",\"Flags\":3,\"Type\":null,\"Rarity\":3.75,\"RarityHardcore\":3.15,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46665.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46665_lock.png\"},{\"ID\":46005,\"MemAddr\":\"R:0xH0013bf!=73_0xH000f31>=3_T:0xH001dff=12_T:0xH001411=1ST:0xH001dff=12ST:0xH001dff=128\",\"Title\":\"With +// More Than You Started\",\"Description\":\"Complete SPECIAL-Funky with 300 or +// more on the time +// clock\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1625960456,\"Created\":1489073065,\"BadgeName\":\"46669\",\"Flags\":3,\"Type\":null,\"Rarity\":3.41,\"RarityHardcore\":2.86,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46669.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46669_lock.png\"},{\"ID\":2274,\"MemAddr\":\"0xH0013bf=49_0xH0013f9=3_0xH001f2e=11_0xH0013ef=1\",\"Title\":\"Shortest +// Route\",\"Description\":\"Clear the fewest stages possible and beat the +// game\",\"Points\":25,\"Author\":\"Jaarl\",\"Modified\":1570010401,\"Created\":1376742799,\"BadgeName\":\"46575\",\"Flags\":3,\"Type\":null,\"Rarity\":6.08,\"RarityHardcore\":5.05,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46575.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46575_lock.png\"},{\"ID\":2297,\"MemAddr\":\"N:0xH0013ce=0_N:0xH0013cf=0_0xH001f2e=0.1._T:0xH001f2e=11.1._T:0xH0013f9=3_R:0xH000dbe1\",\"Title\":\"Starman +// Challenge\",\"Description\":\"Clear the game without dying (one +// session)\",\"Points\":25,\"Author\":\"Jaarl\",\"Modified\":1625960458,\"Created\":1376918015,\"BadgeName\":\"46579\",\"Flags\":3,\"Type\":null,\"Rarity\":5.76,\"RarityHardcore\":4.67,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46579.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46579_lock.png\"},{\"ID\":383338,\"MemAddr\":\"N:0xH0013bf=21_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=5_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=7_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=46_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=14_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=71_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=28_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=58_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=55_N:0xH000071=5_C:0xW0000ce=508910.1._M:0!=0.9.\",\"Title\":\"Gambling +// with Life\",\"Description\":\"Enter all 1-Up Chambers in a single +// Session.\",\"Points\":5,\"Author\":\"SporyTike\",\"Modified\":1703596675,\"Created\":1703596675,\"BadgeName\":\"431977\",\"Flags\":3,\"Type\":null,\"Rarity\":1.45,\"RarityHardcore\":1.3,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431977.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431977_lock.png\"},{\"ID\":175188,\"MemAddr\":\"0xH0013bf>=49_0xH0013bf<=50_0x +// 0000ce=0_d0xH0013de=0_0xH0013de=3\",\"Title\":\"Do The +// Mario!\",\"Description\":\"Make Mario dance during the +// credits\",\"Points\":0,\"Author\":\"stfN1337\",\"Modified\":1691443564,\"Created\":1632935877,\"BadgeName\":\"195401\",\"Flags\":3,\"Type\":null,\"Rarity\":3.44,\"RarityHardcore\":2.86,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/195401.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/195401_lock.png\"},{\"ID\":45856,\"MemAddr\":\"R:0xH000100<=10_R:0xH000db3=1_R:0xH001f27>0.1._R:0xH001f28>0_R:0xH001f29>0_R:0xH001f2a>0_R:0xH00187a>0_N:0xH0013bf=42_0xH000100=17.1._R:0xH000019>0_R:0xH001490>0SN:0xH0013bf=42_Q:0xH000100=17.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=60_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=60_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=43_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=60_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=43_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=46_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=60_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=43_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=46_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=61_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=60_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=43_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=46_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=61_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=64_N:d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.16.ST:0!=0\",\"Title\":\"Mini-Mario +// vs the World I\",\"Description\":\"From Yoshi's Island 2 reach and beat #3 +// Lemmy's Castle in one Session without using Power-Ups, without using Yoshi +// and without active Switch Palace +// Blocks.\",\"Points\":25,\"Author\":\"kdecks\",\"Modified\":1687365195,\"Created\":1488840058,\"BadgeName\":\"46755\",\"Flags\":3,\"Type\":\"missable\",\"Rarity\":3.03,\"RarityHardcore\":2.54,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46755.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46755_lock.png\"},{\"ID\":45994,\"MemAddr\":\"R:0xH000100<=10_R:0xH000db3=1_R:0xH001f27>0_R:0xH001f28>0_R:0xH001f29>0_R:0xH001f2a>0_R:0xH00187a>0_N:0xH0013bf=15_0xH000100=17.1._R:0xH001490>0SN:0xH0013bf=15_Q:0xH000100=17.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=31_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=32_N:d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.12.SN:0xH0013bf=15_Q:0xH000100=17.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=32_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=31_N:d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.12.SN:0xH0013bf=15_Q:0xH000100=17.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=32_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=31_N:d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.12.ST:0!=0_N:0xH0013bf!=71_R:0xH000019>0\",\"Title\":\"Mini-Mario +// vs the World II\",\"Description\":\"From Cheese Bridge Area reach and beat #5 +// Roy's Castle and Forest Fortress in one Session without using Power-Ups +// except the P-Balloon and in Forest of Illusion 3, without using Yoshi and +// without active Switch Palace +// Blocks.\",\"Points\":25,\"Author\":\"kdecks\",\"Modified\":1687365208,\"Created\":1489073010,\"BadgeName\":\"46756\",\"Flags\":3,\"Type\":\"missable\",\"Rarity\":2.29,\"RarityHardcore\":1.95,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46756.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46756_lock.png\"},{\"ID\":45995,\"MemAddr\":\"R:0xH000100<=10_R:0xH000db3=1_R:0xH001f27>0_R:0xH001f28>0_R:0xH001f29>0_R:0xH001f2a>0_R:0xH00187a>0_N:0xH0013bf=34_0xH000100=17.1._R:0xH000019>0SN:0xH0013bf=34_Q:0xH000100=17.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=56_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=56_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=55_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=56_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=55_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=51_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=56_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=55_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=51_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=52_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=56_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=55_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=51_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=52_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=49_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=53_N:d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.15.ST:0!=0_N:0xH0013bf!=24_R:0xH001490>0\",\"Title\":\"Mini-Mario +// vs the World III\",\"Description\":\"From Chocolate Island 1, unlock all +// Level Entrances in Valley of Bowser and beat the Front Door in one Session +// without using Power-Ups except Sunken Ghost Ships' Super Star, without using +// Yoshi and without active Switch Palace +// Blocks.\",\"Points\":25,\"Author\":\"kdecks\",\"Modified\":1687365228,\"Created\":1489073014,\"BadgeName\":\"46757\",\"Flags\":3,\"Type\":\"missable\",\"Rarity\":2.04,\"RarityHardcore\":1.72,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46757.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46757_lock.png\"},{\"ID\":45996,\"MemAddr\":\"R:0xH000100<=10_R:0xH000db3=1_R:0xH001f27>0_R:0xH001f28>0_R:0xH001f29>0_R:0xH001f2a>0_R:0xH00187a>0_N:0xH0013bf=78_0xH000100=17.1._R:0xH000019>0_R:0xH001490>0SN:0xH0013bf=78_Q:0xH000100=17.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=81_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=81_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=76_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=81_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=76_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=75_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=81_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=76_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=75_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=74_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=81_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=76_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=75_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=74_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=73_N:d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.8.ST:0!=0\",\"Title\":\"Mini-Mario +// vs the World SPECIAL\",\"Description\":\"From Gnarly reach and beat Funky in +// one Session without using Power-Ups except the P-Balloon, without using Yoshi +// and without active Switch Palace +// Blocks.\",\"Points\":50,\"Author\":\"kdecks\",\"Modified\":1687365239,\"Created\":1489073019,\"BadgeName\":\"46758\",\"Flags\":3,\"Type\":\"missable\",\"Rarity\":1.97,\"RarityHardcore\":1.65,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46758.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46758_lock.png\"}],\"Leaderboards\":[{\"ID\":1998,\"Mem\":\"STA:0xh13bf=h44_0xh1411=1_0xhf31=3_0xRda4=1::CAN:0xh77>0::SUB:0xh1dff=hc::VAL:0xhf30_0xhf33*100_0xhf32*1000_0xhf31*10000\",\"Format\":\"VALUE\",\"LowerIsBetter\":0,\"Title\":\"Everything +// Is Lava Achievement\",\"Description\":\"Wall touch alert and best time (does +// not track enemy +// contact)\",\"Hidden\":true},{\"ID\":53203,\"Mem\":\"STA:1=0_0xH0013bf=49_0xH000071=10_0xH000019=0_0xH000dc2=0::CAN:S0xH0013bf!=49S0xH000071=10_O:0xH000019!=0_0xH000dc2!=0::SUB:S0xH0013bf=49_0xH0013f9=3_0xH0013ef=1S0xH000906>d0xH000906::VAL:M:0=0\",\"Format\":\"TIME\",\"LowerIsBetter\":1,\"Title\":\"RAOlympics +// - Bowser Bashing\",\"Description\":\"Defeat Bowser the fastest starting as +// small mario with no backup powers (Front +// Door)\",\"Hidden\":true},{\"ID\":1991,\"Mem\":\"STA:d0x100=h90a_0x100=h90b_0xRda4=1_0xQda4=1_0xh1f2e=0_0xhdb2=0::CAN:0xh100=0::SUB:0xH001f2e<=96S0xH000dbf>0Sd0xH001f2e=95_0xH001f2e=96::VAL:0xh1f2e\",\"Format\":\"VALUE\",\"LowerIsBetter\":0,\"Title\":\"Penniless +// Plumbers\",\"Description\":\"Clear as many exits as possible without getting +// a +// coin\",\"Hidden\":false},{\"ID\":1992,\"Mem\":\"STA:d0x100=h90a_0x100=h90b_0xRda4=1_0xNda2=1_0xh1f2e=0_0xhdb2=0::CAN:0xh100=0::SUB:0xH001f2e<=96S0xH000dbed0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Yoshi's +// Island 1 Speedrun\",\"Description\":\"Complete Yoshi's Island 1 with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104611,\"Mem\":\"STA:0xH0013bf=20_d0xH001493=0_0xH001493=8_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Yellow +// Switch Palace Speedrun\",\"Description\":\"Complete the Yellow Switch Palace +// with the most time +// remaining\",\"Hidden\":false},{\"ID\":104612,\"Mem\":\"STA:0xH0013bf=42_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Yoshi's +// Island 2 Speedrun\",\"Description\":\"Complete Yoshi's Island 2 with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104613,\"Mem\":\"STA:0xH0013bf=39_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Yoshi's +// Island 3 Speedrun\",\"Description\":\"Complete Yoshi's Island 3 with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104614,\"Mem\":\"STA:0xH0013bf=38_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Yoshi's +// Island 4 Speedrun\",\"Description\":\"Complete Yoshi's Island 4 with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104615,\"Mem\":\"STA:0xH0013bf=37_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Iggy's +// Castle Speedrun\",\"Description\":\"Complete Iggy's Castle with the most time +// remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104721,\"Mem\":\"STA:0xH0013bf=21_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut +// Plains 1 Speedrun\",\"Description\":\"Complete Donut Plains 1 with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104722,\"Mem\":\"STA:0xH0013bf=9_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut +// Plains 2 Speedrun\",\"Description\":\"Complete Donut Plains 2 with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104723,\"Mem\":\"STA:0xH0013bf=8_d0xH001493=0_0xH001493=8_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Green +// Switch Palace Speedrun\",\"Description\":\"Complete the Green Switch Palace +// with the most time +// remaining\",\"Hidden\":false},{\"ID\":104724,\"Mem\":\"STA:0xH0013bf=4_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut +// Ghost House Speedrun\",\"Description\":\"Complete the Donut Ghost House with +// the most time +// remaining\",\"Hidden\":false},{\"ID\":104727,\"Mem\":\"STA:0xH0013bf=10_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut +// Secret 1 Speedrun\",\"Description\":\"Complete Donut Secret 1 with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104728,\"Mem\":\"STA:0xH0013bf=19_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut +// Secret House Speedrun\",\"Description\":\"Complete Donut Secret House with +// the most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104729,\"Mem\":\"STA:0xH0013bf=47_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut +// Secret 2 Speedrun\",\"Description\":\"Complete Donut Secret 2 with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104725,\"Mem\":\"STA:0xH0013bf=5_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut +// Plains 3 Speedrun\",\"Description\":\"Complete Donut Plains 3 with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104726,\"Mem\":\"STA:0xH0013bf=6_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut +// Plains 4 Speedrun\",\"Description\":\"Complete Donut Plains 4 with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104730,\"Mem\":\"STA:0xH0013bf=7_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Morton's +// Castle Speedrun\",\"Description\":\"Complete Morton's Castle with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104865,\"Mem\":\"STA:0xH0013bf=62_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla +// Dome 1 Speedrun\",\"Description\":\"Complete Vanilla Dome 1 with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104866,\"Mem\":\"STA:0xH0013bf=60_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla +// Dome 2 Speedrun\",\"Description\":\"Complete Vanilla Dome 2 with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104867,\"Mem\":\"STA:0xH0013bf=63_d0xH001493=0_0xH001493=8_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Red +// Switch Palace Speedrun\",\"Description\":\"Complete the Red Switch Palace +// with the most time +// remaining\",\"Hidden\":false},{\"ID\":104868,\"Mem\":\"STA:0xH0013bf=43_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla +// Ghost House Speedrun\",\"Description\":\"Complete Vanilla Ghost House with +// the most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104869,\"Mem\":\"STA:0xH0013bf=46_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla +// Dome 3 Speedrun\",\"Description\":\"Complete Vanilla Dome 3 with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104870,\"Mem\":\"STA:0xH0013bf=61_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla +// Dome 4 Speedrun\",\"Description\":\"Complete Vanilla Dome 4 with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104871,\"Mem\":\"STA:0xH0013bf=45_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla +// Secret 1 Speedrun\",\"Description\":\"Complete Vanilla Secret 1 with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104872,\"Mem\":\"STA:0xH0013bf=1_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla +// Secret 2 Speedrun\",\"Description\":\"Complete Vanilla Secret 2 with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104873,\"Mem\":\"STA:0xH0013bf=2_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla +// Secret 3 Speedrun\",\"Description\":\"Complete Vanilla Secret 3 with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104874,\"Mem\":\"STA:0xH0013bf=11_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla +// Fortress Speedrun\",\"Description\":\"Complete Vanilla Fortress with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104875,\"Mem\":\"STA:0xH0013bf=64_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Lemmy's +// Castle Speedrun\",\"Description\":\"Complete Lemmy's Castle with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104876,\"Mem\":\"STA:0xH0013bf=15_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Cheese +// Bridge Area Speedrun\",\"Description\":\"Complete Cheese Bridge Area with the +// most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104877,\"Mem\":\"STA:0xH0013bf=16_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Cookie +// Mountain Speedrun\",\"Description\":\"Complete Cookie Mountain with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104878,\"Mem\":\"STA:0xH0013bf=12_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Butter +// Bridge 1 Speedrun\",\"Description\":\"Complete Butter Bridge 1 with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104879,\"Mem\":\"STA:0xH0013bf=13_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Butter +// Bridge 2 Speedrun\",\"Description\":\"Complete Butter Bridge 2 with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104880,\"Mem\":\"STA:0xH0013bf=17_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Soda +// Lake Speedrun\",\"Description\":\"Complete Soda Lake with the most time +// remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104881,\"Mem\":\"STA:0xH0013bf=14_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Ludwig's +// Castle Speedrun\",\"Description\":\"Complete Ludwig's Castle with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104882,\"Mem\":\"STA:0xH0013bf=66_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest +// of Illusion 1 Speedrun\",\"Description\":\"Complete Forest of Illusion 1 with +// the most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104883,\"Mem\":\"STA:0xH0013bf=68_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest +// of Illusion 2 Speedrun\",\"Description\":\"Complete Forest of Illusion 2 with +// the most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104884,\"Mem\":\"STA:0xH0013bf=69_d0xH001493=0_0xH001493=8_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Blue +// Switch Palace Speedrun\",\"Description\":\"Complete the Blue Switch Palace +// with the most time +// remaining\",\"Hidden\":false},{\"ID\":104885,\"Mem\":\"STA:0xH0013bf=71_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest +// of Illusion 3 Speedrun\",\"Description\":\"Complete Forest of Illusion 3 with +// the most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104886,\"Mem\":\"STA:0xH0013bf=65_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest +// Ghost House Speedrun\",\"Description\":\"Complete Forest Ghost House with the +// most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104887,\"Mem\":\"STA:0xH0013bf=67_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest +// of Illusion 4 Speedrun\",\"Description\":\"Complete Forest of Illusion 4 with +// the most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104888,\"Mem\":\"STA:0xH0013bf=70_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest +// Secret Area Speedrun\",\"Description\":\"Complete Forest Secret Area with the +// most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104889,\"Mem\":\"STA:0xH0013bf=31_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest +// Fortress Speedrun\",\"Description\":\"Complete Forest Fortress with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104890,\"Mem\":\"STA:0xH0013bf=32_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Roy's +// Castle Speedrun\",\"Description\":\"Complete Roy's Castle with the most time +// remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104913,\"Mem\":\"STA:N:0x +// 0000d1=16_N:0x +// 0000d3=352_0xH000f31=3.1._0xH0013bf=34_0xM000906>d0xM000906_0xS0013cf=0_O:0xH000dc2=20_P:0xH000dc2=215.1.SR:0xH000100=14::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate +// Island 1 Speedrun\",\"Description\":\"Complete Chocolate Island 1 with the +// most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104914,\"Mem\":\"STA:0xH0013bf=33_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Choco-Ghost +// House Speedrun\",\"Description\":\"Complete Choco-Ghost House with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104915,\"Mem\":\"STA:0xH0013bf=36_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate +// Island 2 Speedrun\",\"Description\":\"Complete Chocolate Island 2 with the +// most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104916,\"Mem\":\"STA:0xH0013bf=35_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate +// Island 3 Speedrun\",\"Description\":\"Complete Chocolate Island 3 with the +// most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104917,\"Mem\":\"STA:0xH0013bf=27_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate +// Fortress Speedrun\",\"Description\":\"Complete Chocolate Fortress with the +// most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104918,\"Mem\":\"STA:0xH0013bf=29_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate +// Island 4 Speedrun\",\"Description\":\"Complete Chocolate Island 4 with the +// most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104919,\"Mem\":\"STA:0xH0013bf=28_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate +// Island 5 Speedrun\",\"Description\":\"Complete Chocolate Island 5 with the +// most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104920,\"Mem\":\"STA:0xH0013bf=59_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate +// Secret Speedrun\",\"Description\":\"Complete Chocolate Secret with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104921,\"Mem\":\"STA:0xH0013bf=26_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Wendy's +// Castle Speedrun\",\"Description\":\"Complete Wendy's Castle with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104922,\"Mem\":\"STA:0xH0013bf=24_0xS0013cf=0_d0xH001493=0_0xH001493=255_0x +// 000096>=6144_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Sunken +// Ghost Ship Speedrun\",\"Description\":\"Complete Sunken Ghost Ship with the +// most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104931,\"Mem\":\"STA:0xH0013bf=58_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley +// of Bowser 1 Speedrun\",\"Description\":\"Complete Valley of Bowser 1 with the +// most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104932,\"Mem\":\"STA:0xH0013bf=57_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley +// of Bowser 2 Speedrun\",\"Description\":\"Complete Valley of Bowser 2 with the +// most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104933,\"Mem\":\"STA:0xH0013bf=56_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley +// Ghost House Speedrun\",\"Description\":\"Complete Valley Ghost House with the +// most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104934,\"Mem\":\"STA:0xH0013bf=55_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley +// of Bowser 3 Speedrun\",\"Description\":\"Complete Valley of Bowser 3 with the +// most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104935,\"Mem\":\"STA:0xH0013bf=51_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley +// of Bowser 4 Speedrun\",\"Description\":\"Complete Valley of Bowser 4 with the +// most time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104936,\"Mem\":\"STA:0xH0013bf=53_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley +// Fortress Speedrun\",\"Description\":\"Complete Valley Fortress with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104937,\"Mem\":\"STA:0xH0013bf=52_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Larry's +// Castle Speedrun\",\"Description\":\"Complete Larry's Castle with the most +// time remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104938,\"Mem\":\"STA:0xH0013bf=88_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Star +// World 1 Speedrun\",\"Description\":\"Complete Star World 1 with the most time +// remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104939,\"Mem\":\"STA:0xH0013bf=84_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Star +// World 2 Speedrun\",\"Description\":\"Complete Star World 2 with the most time +// remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104940,\"Mem\":\"STA:0xH0013bf=86_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Star +// World 3 Speedrun\",\"Description\":\"Complete Star World 3 with the most time +// remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104941,\"Mem\":\"STA:0xH0013bf=89_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Star +// World 4 Speedrun\",\"Description\":\"Complete Star World 4 with the most time +// remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104942,\"Mem\":\"STA:0xH0013bf=90_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Star +// World 5 Speedrun\",\"Description\":\"Complete Star World 5 with the most time +// remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104943,\"Mem\":\"STA:0xH0013bf=78_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Gnarly +// Speedrun\",\"Description\":\"Complete Gnarly with the most time remaining +// without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104944,\"Mem\":\"STA:0xH0013bf=79_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Tubular +// Speedrun\",\"Description\":\"Complete Tubular with the most time remaining +// without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104945,\"Mem\":\"STA:0xH0013bf=80_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Way +// Cool Speedrun\",\"Description\":\"Complete Way Cool with the most time +// remaining without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104946,\"Mem\":\"STA:0xH0013bf=81_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Awesome +// Speedrun\",\"Description\":\"Complete Awesome with the most time remaining +// without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104947,\"Mem\":\"STA:0xH0013bf=76_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Groovy +// Speedrun\",\"Description\":\"Complete Groovy with the most time remaining +// without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104948,\"Mem\":\"STA:0xH0013bf=75_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Mondo +// Speedrun\",\"Description\":\"Complete Mondo with the most time remaining +// without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104949,\"Mem\":\"STA:0xH0013bf=74_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Outrageous +// Speedrun\",\"Description\":\"Complete Outrageous with the most time remaining +// without using a +// checkpoint\",\"Hidden\":false},{\"ID\":104950,\"Mem\":\"STA:0xH0013bf=73_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Funky +// Speedrun\",\"Description\":\"Complete Funky with the most time remaining +// without using a checkpoint\",\"Hidden\":false}]}}" diff --git a/src/app/rcheevos/ra_client.cpp b/src/app/rcheevos/ra_client.cpp index e022fc0f..345e7001 100644 --- a/src/app/rcheevos/ra_client.cpp +++ b/src/app/rcheevos/ra_client.cpp @@ -443,8 +443,8 @@ std::optional RAClient::getCurrentUser() const { User user; user.username = data->username; user.token = data->token; - user.points = data->score_softcore; - user.hardcore_points = data->score; + user.softcoreScore = data->score_softcore; + user.score = data->score; return {user}; } diff --git a/src/app/rcheevos/user.h b/src/app/rcheevos/user.h index 881701d9..9c502130 100644 --- a/src/app/rcheevos/user.h +++ b/src/app/rcheevos/user.h @@ -4,10 +4,11 @@ namespace firelight::achievements { class User { - public: - std::string username; - std::string token; - int points; - int hardcore_points; +public: + std::string username; + std::string avatarUrl; + std::string token; + int softcoreScore; + int score; }; -} \ No newline at end of file +} // namespace firelight::achievements \ No newline at end of file diff --git a/tests/app/achievements/achievement_service_test.cpp b/tests/app/achievements/achievement_service_test.cpp index fb89f5df..23710197 100644 --- a/tests/app/achievements/achievement_service_test.cpp +++ b/tests/app/achievements/achievement_service_test.cpp @@ -112,8 +112,8 @@ class AchievementServiceTest : public testing::Test { User user; user.username = username; user.token = token; - user.points = points; - user.hardcore_points = hardcorePoints; + user.softcoreScore = points; + user.score = hardcorePoints; return user; } }; @@ -138,8 +138,8 @@ TEST_F(AchievementServiceTest, CreateUser_Success) { ASSERT_TRUE(savedUser.has_value()); EXPECT_EQ(savedUser->username, "testuser"); EXPECT_EQ(savedUser->token, "test_token_123"); - EXPECT_EQ(savedUser->points, 1500); - EXPECT_EQ(savedUser->hardcore_points, 750); + EXPECT_EQ(savedUser->softcoreScore, 1500); + EXPECT_EQ(savedUser->score, 750); } TEST_F(AchievementServiceTest, UpdateUser_Success) { @@ -150,8 +150,8 @@ TEST_F(AchievementServiceTest, UpdateUser_Success) { // Update user data user.token = "updated_token_456"; - user.points = 2000; - user.hardcore_points = 1000; + user.softcoreScore = 2000; + user.score = 1000; bool result = service->createOrUpdateUser(user); @@ -162,8 +162,8 @@ TEST_F(AchievementServiceTest, UpdateUser_Success) { ASSERT_TRUE(updatedUser.has_value()); EXPECT_EQ(updatedUser->username, "testuser"); EXPECT_EQ(updatedUser->token, "updated_token_456"); - EXPECT_EQ(updatedUser->points, 2000); - EXPECT_EQ(updatedUser->hardcore_points, 1000); + EXPECT_EQ(updatedUser->softcoreScore, 2000); + EXPECT_EQ(updatedUser->score, 1000); } TEST_F(AchievementServiceTest, GetUser_ExistingUser) { @@ -175,8 +175,8 @@ TEST_F(AchievementServiceTest, GetUser_ExistingUser) { ASSERT_TRUE(result.has_value()); EXPECT_EQ(result->username, "testuser"); EXPECT_EQ(result->token, "test_token_123"); - EXPECT_EQ(result->points, 1500); - EXPECT_EQ(result->hardcore_points, 750); + EXPECT_EQ(result->softcoreScore, 1500); + EXPECT_EQ(result->score, 750); } TEST_F(AchievementServiceTest, GetUser_NonExistentUser) { @@ -201,13 +201,13 @@ TEST_F(AchievementServiceTest, CreateOrUpdateUser_MultipleUsers) { EXPECT_EQ(retrievedUser1->username, "user1"); EXPECT_EQ(retrievedUser1->token, "token1"); - EXPECT_EQ(retrievedUser1->points, 1000); - EXPECT_EQ(retrievedUser1->hardcore_points, 500); + EXPECT_EQ(retrievedUser1->softcoreScore, 1000); + EXPECT_EQ(retrievedUser1->score, 500); EXPECT_EQ(retrievedUser2->username, "user2"); EXPECT_EQ(retrievedUser2->token, "token2"); - EXPECT_EQ(retrievedUser2->points, 2000); - EXPECT_EQ(retrievedUser2->hardcore_points, 1500); + EXPECT_EQ(retrievedUser2->softcoreScore, 2000); + EXPECT_EQ(retrievedUser2->score, 1500); } TEST_F(AchievementServiceTest, CreateOrUpdateUser_ZeroPoints) { @@ -219,8 +219,8 @@ TEST_F(AchievementServiceTest, CreateOrUpdateUser_ZeroPoints) { auto savedUser = service->getUser("beginner"); ASSERT_TRUE(savedUser.has_value()); - EXPECT_EQ(savedUser->points, 0); - EXPECT_EQ(savedUser->hardcore_points, 0); + EXPECT_EQ(savedUser->softcoreScore, 0); + EXPECT_EQ(savedUser->score, 0); } TEST_F(AchievementServiceTest, CreateOrUpdateUser_EmptyToken) { diff --git a/tests/app/achievements/sqlite_achievement_repository_test.cpp b/tests/app/achievements/sqlite_achievement_repository_test.cpp index 23ad789c..b6c422bb 100644 --- a/tests/app/achievements/sqlite_achievement_repository_test.cpp +++ b/tests/app/achievements/sqlite_achievement_repository_test.cpp @@ -24,8 +24,10 @@ class SqliteAchievementRepositoryTest : public testing::Test { AchievementSet createTestAchievementSet() { AchievementSet achievementSet; achievementSet.id = 1; - achievementSet.name = "Test Game Achievements"; - achievementSet.iconUrl = "https://example.com/icon.png"; + achievementSet.title = "Test Game Achievements"; + achievementSet.type = "core"; + achievementSet.gameId = 1; + achievementSet.imageIconUrl = "https://example.com/icon.png"; achievementSet.numAchievements = 3; achievementSet.totalPoints = 150; return achievementSet; @@ -34,14 +36,22 @@ class SqliteAchievementRepositoryTest : public testing::Test { Achievement createTestAchievement(unsigned id = 100, unsigned setId = 1) { Achievement achievement; achievement.id = id; - achievement.name = "Test Achievement"; + achievement.achievementSetId = setId; + achievement.memAddr = ""; + achievement.title = "Test Achievement"; achievement.description = "Complete a test task"; - achievement.imageUrl = "https://example.com/achievement.png"; achievement.points = 50; + achievement.author = ""; + achievement.modified = 0; + achievement.created = 0; + achievement.badgeName = "test_badge"; + achievement.flags = 3; // Active achievement achievement.type = "progression"; + achievement.rarity = 0.0; + achievement.rarityHardcore = 0.0; + achievement.badgeUrl = "https://example.com/achievement.png"; + achievement.badgeLockedUrl = "https://example.com/achievement_locked.png"; achievement.displayOrder = 1; - achievement.gameId = setId; - achievement.flags = 3; // Active achievement return achievement; } @@ -75,8 +85,8 @@ class SqliteAchievementRepositoryTest : public testing::Test { User user; user.username = username; user.token = token; - user.points = points; - user.hardcore_points = hardcorePoints; + user.softcoreScore = points; + user.score = hardcorePoints; return user; } }; @@ -99,8 +109,8 @@ TEST_F(SqliteAchievementRepositoryTest, CreateUser_UpsertBehavior) { // Modify and update (should upsert) user.token = "updated_token_456"; - user.points = 2000; - user.hardcore_points = 1000; + user.softcoreScore = 2000; + user.score = 1000; bool result = repository->createOrUpdateUser(user); EXPECT_TRUE(result); @@ -110,8 +120,8 @@ TEST_F(SqliteAchievementRepositoryTest, CreateUser_UpsertBehavior) { ASSERT_TRUE(retrieved.has_value()); EXPECT_EQ(retrieved->username, "testuser"); EXPECT_EQ(retrieved->token, "updated_token_456"); - EXPECT_EQ(retrieved->points, 2000); - EXPECT_EQ(retrieved->hardcore_points, 1000); + EXPECT_EQ(retrieved->softcoreScore, 2000); + EXPECT_EQ(retrieved->score, 1000); } TEST_F(SqliteAchievementRepositoryTest, GetUser_ExistingUser) { @@ -123,8 +133,8 @@ TEST_F(SqliteAchievementRepositoryTest, GetUser_ExistingUser) { ASSERT_TRUE(result.has_value()); EXPECT_EQ(result->username, "testuser"); EXPECT_EQ(result->token, "test_token_123"); - EXPECT_EQ(result->points, 1500); - EXPECT_EQ(result->hardcore_points, 750); + EXPECT_EQ(result->softcoreScore, 1500); + EXPECT_EQ(result->score, 750); } TEST_F(SqliteAchievementRepositoryTest, GetUser_NonExistentUser) { @@ -149,13 +159,13 @@ TEST_F(SqliteAchievementRepositoryTest, CreateUser_MultipleUsers) { EXPECT_EQ(retrievedUser1->username, "user1"); EXPECT_EQ(retrievedUser1->token, "token1"); - EXPECT_EQ(retrievedUser1->points, 1000); - EXPECT_EQ(retrievedUser1->hardcore_points, 500); + EXPECT_EQ(retrievedUser1->softcoreScore, 1000); + EXPECT_EQ(retrievedUser1->score, 500); EXPECT_EQ(retrievedUser2->username, "user2"); EXPECT_EQ(retrievedUser2->token, "token2"); - EXPECT_EQ(retrievedUser2->points, 2000); - EXPECT_EQ(retrievedUser2->hardcore_points, 1500); + EXPECT_EQ(retrievedUser2->softcoreScore, 2000); + EXPECT_EQ(retrievedUser2->score, 1500); } TEST_F(SqliteAchievementRepositoryTest, CreateUser_ZeroPoints) { @@ -167,8 +177,8 @@ TEST_F(SqliteAchievementRepositoryTest, CreateUser_ZeroPoints) { auto savedUser = repository->getUser("beginner"); ASSERT_TRUE(savedUser.has_value()); - EXPECT_EQ(savedUser->points, 0); - EXPECT_EQ(savedUser->hardcore_points, 0); + EXPECT_EQ(savedUser->softcoreScore, 0); + EXPECT_EQ(savedUser->score, 0); } TEST_F(SqliteAchievementRepositoryTest, CreateUser_EmptyToken) { @@ -200,8 +210,8 @@ TEST_F(SqliteAchievementRepositoryTest, ListUsers_SingleUser) { ASSERT_EQ(result.size(), 1); EXPECT_EQ(result[0].username, "testuser"); EXPECT_EQ(result[0].token, "test_token"); - EXPECT_EQ(result[0].points, 1500); - EXPECT_EQ(result[0].hardcore_points, 750); + EXPECT_EQ(result[0].softcoreScore, 1500); + EXPECT_EQ(result[0].score, 750); } TEST_F(SqliteAchievementRepositoryTest, ListUsers_MultipleUsers) { @@ -221,18 +231,18 @@ TEST_F(SqliteAchievementRepositoryTest, ListUsers_MultipleUsers) { // Results should be ordered by username (alice, bob, charlie) EXPECT_EQ(result[0].username, "alice"); EXPECT_EQ(result[0].token, "token_alice"); - EXPECT_EQ(result[0].points, 2000); - EXPECT_EQ(result[0].hardcore_points, 1800); + EXPECT_EQ(result[0].softcoreScore, 2000); + EXPECT_EQ(result[0].score, 1800); EXPECT_EQ(result[1].username, "bob"); EXPECT_EQ(result[1].token, "token_bob"); - EXPECT_EQ(result[1].points, 1200); - EXPECT_EQ(result[1].hardcore_points, 900); + EXPECT_EQ(result[1].softcoreScore, 1200); + EXPECT_EQ(result[1].score, 900); EXPECT_EQ(result[2].username, "charlie"); EXPECT_EQ(result[2].token, "token_charlie"); - EXPECT_EQ(result[2].points, 3500); - EXPECT_EQ(result[2].hardcore_points, 2100); + EXPECT_EQ(result[2].softcoreScore, 3500); + EXPECT_EQ(result[2].score, 2100); } TEST_F(SqliteAchievementRepositoryTest, ListUsers_OrderedByUsername) { @@ -271,16 +281,16 @@ TEST_F(SqliteAchievementRepositoryTest, ListUsers_WithZeroPoints) { return u.username == "newbie"; }); ASSERT_NE(newbieIt, result.end()); - EXPECT_EQ(newbieIt->points, 0); - EXPECT_EQ(newbieIt->hardcore_points, 0); + EXPECT_EQ(newbieIt->softcoreScore, 0); + EXPECT_EQ(newbieIt->score, 0); // Find the veteran user auto veteranIt = std::find_if(result.begin(), result.end(), [](const User &u) { return u.username == "veteran"; }); ASSERT_NE(veteranIt, result.end()); - EXPECT_EQ(veteranIt->points, 5000); - EXPECT_EQ(veteranIt->hardcore_points, 3000); + EXPECT_EQ(veteranIt->softcoreScore, 5000); + EXPECT_EQ(veteranIt->score, 3000); } TEST_F(SqliteAchievementRepositoryTest, ListUsers_WithEmptyTokens) { @@ -315,8 +325,8 @@ TEST_F(SqliteAchievementRepositoryTest, ListUsers_AfterUserUpdate) { // Update the user user.token = "new_token"; - user.points = 2000; - user.hardcore_points = 1500; + user.softcoreScore = 2000; + user.score = 1500; repository->createOrUpdateUser(user); auto result = repository->listUsers(); @@ -324,8 +334,8 @@ TEST_F(SqliteAchievementRepositoryTest, ListUsers_AfterUserUpdate) { ASSERT_EQ(result.size(), 1); EXPECT_EQ(result[0].username, "updatable"); EXPECT_EQ(result[0].token, "new_token"); - EXPECT_EQ(result[0].points, 2000); - EXPECT_EQ(result[0].hardcore_points, 1500); + EXPECT_EQ(result[0].softcoreScore, 2000); + EXPECT_EQ(result[0].score, 1500); } TEST_F(SqliteAchievementRepositoryTest, ListUsers_LargeDataset) { @@ -356,15 +366,15 @@ TEST_F(SqliteAchievementRepositoryTest, ListUsers_LargeDataset) { return u.username == "user0"; }); ASSERT_NE(user0It, result.end()); - EXPECT_EQ(user0It->points, 0); - EXPECT_EQ(user0It->hardcore_points, 0); + EXPECT_EQ(user0It->softcoreScore, 0); + EXPECT_EQ(user0It->score, 0); auto user25It = std::find_if(result.begin(), result.end(), [](const User &u) { return u.username == "user25"; }); ASSERT_NE(user25It, result.end()); - EXPECT_EQ(user25It->points, 2500); - EXPECT_EQ(user25It->hardcore_points, 1250); + EXPECT_EQ(user25It->softcoreScore, 2500); + EXPECT_EQ(user25It->score, 1250); } // Achievement Set CRUD Tests @@ -384,64 +394,67 @@ TEST_F(SqliteAchievementRepositoryTest, CreateAchievementSet_UpsertBehavior) { EXPECT_TRUE(repository->create(achievementSet)); // Modify and createOrUpdate again (should update) - achievementSet.name = "Updated Game Achievements"; + achievementSet.title = "Updated Game Achievements"; achievementSet.totalPoints = 200; EXPECT_TRUE(repository->create(achievementSet)); // Verify the update took effect - auto retrieved = repository->getAchievementSet(achievementSet.id); - ASSERT_TRUE(retrieved.has_value()); - EXPECT_EQ(retrieved->name, "Updated Game Achievements"); - EXPECT_EQ(retrieved->totalPoints, 200); + auto retrieved = + repository->getAchievementSetsByGameId(achievementSet.gameId); + ASSERT_TRUE(retrieved.size() == 1); + EXPECT_EQ(retrieved[0].title, "Updated Game Achievements"); + EXPECT_EQ(retrieved[0].totalPoints, 200); } TEST_F(SqliteAchievementRepositoryTest, GetAchievementSet_ExistingSet) { AchievementSet achievementSet = createTestAchievementSet(); repository->create(achievementSet); - auto result = repository->getAchievementSet(achievementSet.id); + auto result = repository->getAchievementSetsByGameId(achievementSet.gameId); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->id, achievementSet.id); - EXPECT_EQ(result->name, achievementSet.name); - EXPECT_EQ(result->iconUrl, achievementSet.iconUrl); - EXPECT_EQ(result->numAchievements, achievementSet.numAchievements); - EXPECT_EQ(result->totalPoints, achievementSet.totalPoints); - EXPECT_TRUE(result->achievements.empty()); // No achievements added yet + ASSERT_TRUE(result.size() == 1); + EXPECT_EQ(result[0].id, achievementSet.id); + EXPECT_EQ(result[0].title, achievementSet.title); + EXPECT_EQ(result[0].imageIconUrl, achievementSet.imageIconUrl); + EXPECT_EQ(result[0].numAchievements, achievementSet.numAchievements); + EXPECT_EQ(result[0].totalPoints, achievementSet.totalPoints); + EXPECT_TRUE(result[0].achievements.empty()); // No achievements added yet } TEST_F(SqliteAchievementRepositoryTest, GetAchievementSet_NonExistentSet) { - auto result = repository->getAchievementSet(999); + auto result = repository->getAchievementSetsByGameId(999); - EXPECT_FALSE(result.has_value()); + EXPECT_TRUE(result.empty()); } TEST_F(SqliteAchievementRepositoryTest, UpdateAchievementSet_Success) { AchievementSet achievementSet = createTestAchievementSet(); repository->create(achievementSet); - achievementSet.name = "Updated Achievement Set"; + achievementSet.title = "Updated Achievement Set"; achievementSet.totalPoints = 300; - achievementSet.iconUrl = "https://example.com/new-icon.png"; + achievementSet.imageIconUrl = "https://example.com/new-icon.png"; - bool result = repository->update(achievementSet); + bool result = repository->create(achievementSet); EXPECT_TRUE(result); - auto retrieved = repository->getAchievementSet(achievementSet.id); - ASSERT_TRUE(retrieved.has_value()); - EXPECT_EQ(retrieved->name, "Updated Achievement Set"); - EXPECT_EQ(retrieved->totalPoints, 300); - EXPECT_EQ(retrieved->iconUrl, "https://example.com/new-icon.png"); + auto retrieved = + repository->getAchievementSetsByGameId(achievementSet.gameId); + ASSERT_TRUE(retrieved.size() == 1); + EXPECT_EQ(retrieved[0].title, "Updated Achievement Set"); + EXPECT_EQ(retrieved[0].totalPoints, 300); + EXPECT_EQ(retrieved[0].imageIconUrl, "https://example.com/new-icon.png"); } TEST_F(SqliteAchievementRepositoryTest, UpdateAchievementSet_NonExistentSet) { AchievementSet achievementSet = createTestAchievementSet(); achievementSet.id = 999; + achievementSet.gameId = 999; - bool result = repository->update(achievementSet); + bool result = repository->create(achievementSet); - EXPECT_FALSE(result); // Should return false for non-existent set + EXPECT_TRUE(result); // Should return true (upsert creates new entry) } // Achievement CRUD Tests @@ -451,7 +464,7 @@ TEST_F(SqliteAchievementRepositoryTest, GetAchievement_ExistingAchievement) { repository->create(achievementSet); Achievement achievement = createTestAchievement(101, 1); - achievement.name = "Test Achievement"; + achievement.title = "Test Achievement"; achievement.description = "Complete a test task"; achievement.points = 50; achievement.type = "progression"; @@ -463,14 +476,14 @@ TEST_F(SqliteAchievementRepositoryTest, GetAchievement_ExistingAchievement) { ASSERT_TRUE(result.has_value()); EXPECT_EQ(result->id, 101); - EXPECT_EQ(result->name, "Test Achievement"); + EXPECT_EQ(result->title, "Test Achievement"); EXPECT_EQ(result->description, "Complete a test task"); EXPECT_EQ(result->points, 50); EXPECT_EQ(result->type, "progression"); EXPECT_EQ(result->flags, 3); EXPECT_EQ(result->displayOrder, 1); - EXPECT_EQ(result->gameId, 1); - EXPECT_EQ(result->imageUrl, "https://example.com/achievement.png"); + EXPECT_EQ(result->achievementSetId, 1); + EXPECT_EQ(result->badgeUrl, "https://example.com/achievement.png"); } TEST_F(SqliteAchievementRepositoryTest, GetAchievement_NonExistentAchievement) { @@ -484,9 +497,9 @@ TEST_F(SqliteAchievementRepositoryTest, GetAchievement_VerifyAllFields) { repository->create(achievementSet); Achievement achievement = createTestAchievement(202, 1); - achievement.name = "Detailed Achievement"; + achievement.title = "Detailed Achievement"; achievement.description = "A comprehensive test achievement"; - achievement.imageUrl = "https://example.com/detailed.png"; + achievement.badgeUrl = "https://example.com/detailed.png"; achievement.points = 75; achievement.type = "challenge"; achievement.displayOrder = 5; @@ -497,13 +510,13 @@ TEST_F(SqliteAchievementRepositoryTest, GetAchievement_VerifyAllFields) { ASSERT_TRUE(result.has_value()); EXPECT_EQ(result->id, 202); - EXPECT_EQ(result->name, "Detailed Achievement"); + EXPECT_EQ(result->title, "Detailed Achievement"); EXPECT_EQ(result->description, "A comprehensive test achievement"); - EXPECT_EQ(result->imageUrl, "https://example.com/detailed.png"); + EXPECT_EQ(result->badgeUrl, "https://example.com/detailed.png"); EXPECT_EQ(result->points, 75); EXPECT_EQ(result->type, "challenge"); EXPECT_EQ(result->displayOrder, 5); - EXPECT_EQ(result->gameId, 1); + EXPECT_EQ(result->achievementSetId, 1); EXPECT_EQ(result->flags, 3); } @@ -512,12 +525,12 @@ TEST_F(SqliteAchievementRepositoryTest, GetAchievement_AfterUpdate) { repository->create(achievementSet); Achievement achievement = createTestAchievement(303, 1); - achievement.name = "Original Achievement"; + achievement.title = "Original Achievement"; achievement.points = 25; repository->create(achievement); // Update the achievement - achievement.name = "Updated Achievement"; + achievement.title = "Updated Achievement"; achievement.points = 50; achievement.description = "Updated description"; EXPECT_TRUE(repository->create(achievement)); // Should upsert @@ -526,10 +539,10 @@ TEST_F(SqliteAchievementRepositoryTest, GetAchievement_AfterUpdate) { ASSERT_TRUE(result.has_value()); EXPECT_EQ(result->id, 303); - EXPECT_EQ(result->name, "Updated Achievement"); + EXPECT_EQ(result->title, "Updated Achievement"); EXPECT_EQ(result->points, 50); EXPECT_EQ(result->description, "Updated description"); - EXPECT_EQ(result->gameId, 1); + EXPECT_EQ(result->achievementSetId, 1); } TEST_F(SqliteAchievementRepositoryTest, GetAchievement_MultipleAchievements) { @@ -538,17 +551,17 @@ TEST_F(SqliteAchievementRepositoryTest, GetAchievement_MultipleAchievements) { // Create multiple achievements Achievement achievement1 = createTestAchievement(401, 1); - achievement1.name = "First Achievement"; + achievement1.title = "First Achievement"; achievement1.points = 10; repository->create(achievement1); Achievement achievement2 = createTestAchievement(402, 1); - achievement2.name = "Second Achievement"; + achievement2.title = "Second Achievement"; achievement2.points = 20; repository->create(achievement2); Achievement achievement3 = createTestAchievement(403, 1); - achievement3.name = "Third Achievement"; + achievement3.title = "Third Achievement"; achievement3.points = 30; repository->create(achievement3); @@ -561,13 +574,13 @@ TEST_F(SqliteAchievementRepositoryTest, GetAchievement_MultipleAchievements) { ASSERT_TRUE(result2.has_value()); ASSERT_TRUE(result3.has_value()); - EXPECT_EQ(result1->name, "First Achievement"); + EXPECT_EQ(result1->title, "First Achievement"); EXPECT_EQ(result1->points, 10); - EXPECT_EQ(result2->name, "Second Achievement"); + EXPECT_EQ(result2->title, "Second Achievement"); EXPECT_EQ(result2->points, 20); - EXPECT_EQ(result3->name, "Third Achievement"); + EXPECT_EQ(result3->title, "Third Achievement"); EXPECT_EQ(result3->points, 30); } @@ -577,12 +590,12 @@ TEST_F(SqliteAchievementRepositoryTest, GetAchievement_DifferentFlags) { // Create achievements with different flag values Achievement activeAchievement = createTestAchievement(501, 1); - activeAchievement.name = "Active Achievement"; + activeAchievement.title = "Active Achievement"; activeAchievement.flags = 3; // Active repository->create(activeAchievement); Achievement inactiveAchievement = createTestAchievement(502, 1); - inactiveAchievement.name = "Inactive Achievement"; + inactiveAchievement.title = "Inactive Achievement"; inactiveAchievement.flags = 5; // Inactive/prototype repository->create(inactiveAchievement); @@ -593,10 +606,10 @@ TEST_F(SqliteAchievementRepositoryTest, GetAchievement_DifferentFlags) { ASSERT_TRUE(activeResult.has_value()); ASSERT_TRUE(inactiveResult.has_value()); - EXPECT_EQ(activeResult->name, "Active Achievement"); + EXPECT_EQ(activeResult->title, "Active Achievement"); EXPECT_EQ(activeResult->flags, 3); - EXPECT_EQ(inactiveResult->name, "Inactive Achievement"); + EXPECT_EQ(inactiveResult->title, "Inactive Achievement"); EXPECT_EQ(inactiveResult->flags, 5); } @@ -605,19 +618,19 @@ TEST_F(SqliteAchievementRepositoryTest, GetAchievement_EmptyStringFields) { repository->create(achievementSet); Achievement achievement = createTestAchievement(601, 1); - achievement.name = "Achievement with Empty Fields"; + achievement.title = "Achievement with Empty Fields"; achievement.description = ""; achievement.type = ""; - achievement.imageUrl = ""; + achievement.badgeUrl = ""; repository->create(achievement); auto result = repository->getAchievement(601); ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->name, "Achievement with Empty Fields"); + EXPECT_EQ(result->title, "Achievement with Empty Fields"); EXPECT_EQ(result->description, ""); EXPECT_EQ(result->type, ""); - EXPECT_EQ(result->imageUrl, ""); + EXPECT_EQ(result->badgeUrl, ""); } TEST_F(SqliteAchievementRepositoryTest, CreateAchievement_Success) { @@ -642,18 +655,19 @@ TEST_F(SqliteAchievementRepositoryTest, CreateAchievement_UpsertBehavior) { EXPECT_TRUE(repository->create(achievement)); // Modify and createOrUpdate again (should update) - achievement.name = "Updated Achievement"; + achievement.title = "Updated Achievement"; achievement.points = 75; achievement.type = "challenge"; EXPECT_TRUE(repository->create(achievement)); // Verify by retrieving the achievement set - auto retrievedSet = repository->getAchievementSet(achievementSet.id); - ASSERT_TRUE(retrievedSet.has_value()); - ASSERT_EQ(retrievedSet->achievements.size(), 1); - EXPECT_EQ(retrievedSet->achievements[0].name, "Updated Achievement"); - EXPECT_EQ(retrievedSet->achievements[0].points, 75); - EXPECT_EQ(retrievedSet->achievements[0].type, "challenge"); + auto retrievedSet = + repository->getAchievementSetsByGameId(achievementSet.gameId); + ASSERT_TRUE(retrievedSet.size() == 1); + ASSERT_EQ(retrievedSet[0].achievements.size(), 1); + EXPECT_EQ(retrievedSet[0].achievements[0].title, "Updated Achievement"); + EXPECT_EQ(retrievedSet[0].achievements[0].points, 75); + EXPECT_EQ(retrievedSet[0].achievements[0].type, "challenge"); } TEST_F(SqliteAchievementRepositoryTest, GetAchievementSet_WithAchievements) { @@ -661,42 +675,42 @@ TEST_F(SqliteAchievementRepositoryTest, GetAchievementSet_WithAchievements) { repository->create(achievementSet); // Add multiple achievements with different display orders - Achievement achievement1 = createTestAchievement(101, 1); - achievement1.name = "First Achievement"; + Achievement achievement1 = createTestAchievement(101, achievementSet.id); + achievement1.title = "First Achievement"; achievement1.displayOrder = 2; repository->create(achievement1); - Achievement achievement2 = createTestAchievement(102, 1); - achievement2.name = "Second Achievement"; + Achievement achievement2 = createTestAchievement(102, achievementSet.id); + achievement2.title = "Second Achievement"; achievement2.displayOrder = 1; repository->create(achievement2); - Achievement achievement3 = createTestAchievement(103, 1); - achievement3.name = "Third Achievement"; + Achievement achievement3 = createTestAchievement(103, achievementSet.id); + achievement3.title = "Third Achievement"; achievement3.displayOrder = 3; repository->create(achievement3); - auto result = repository->getAchievementSet(achievementSet.id); + auto result = repository->getAchievementSetsByGameId(achievementSet.gameId); - ASSERT_TRUE(result.has_value()); - ASSERT_EQ(result->achievements.size(), 3); + ASSERT_TRUE(result.size() == 1); + ASSERT_EQ(result[0].achievements.size(), 3); // Verify achievements are ordered by display_order - EXPECT_EQ(result->achievements[0].name, "Second Achievement"); - EXPECT_EQ(result->achievements[0].displayOrder, 1); - EXPECT_EQ(result->achievements[1].name, "First Achievement"); - EXPECT_EQ(result->achievements[1].displayOrder, 2); - EXPECT_EQ(result->achievements[2].name, "Third Achievement"); - EXPECT_EQ(result->achievements[2].displayOrder, 3); + EXPECT_EQ(result[0].achievements[0].title, "Second Achievement"); + EXPECT_EQ(result[0].achievements[0].displayOrder, 1); + EXPECT_EQ(result[0].achievements[1].title, "First Achievement"); + EXPECT_EQ(result[0].achievements[1].displayOrder, 2); + EXPECT_EQ(result[0].achievements[2].title, "Third Achievement"); + EXPECT_EQ(result[0].achievements[2].displayOrder, 3); // Verify all achievement fields are populated - const auto &firstAchievement = result->achievements[0]; + const auto &firstAchievement = result[0].achievements[0]; EXPECT_EQ(firstAchievement.id, 102); EXPECT_EQ(firstAchievement.description, "Complete a test task"); - EXPECT_EQ(firstAchievement.imageUrl, "https://example.com/achievement.png"); + EXPECT_EQ(firstAchievement.badgeUrl, "https://example.com/achievement.png"); EXPECT_EQ(firstAchievement.points, 50); EXPECT_EQ(firstAchievement.type, "progression"); - EXPECT_EQ(firstAchievement.gameId, 1); + EXPECT_EQ(firstAchievement.achievementSetId, 1); EXPECT_EQ(firstAchievement.flags, 3); } @@ -895,17 +909,17 @@ TEST_F(SqliteAchievementRepositoryTest, GetAllUserUnlocks_WithUnlocks) { // Create multiple achievements in the set Achievement achievement1 = createTestAchievement(101, 1); - achievement1.name = "First Achievement"; + achievement1.title = "First Achievement"; achievement1.displayOrder = 1; repository->create(achievement1); Achievement achievement2 = createTestAchievement(102, 1); - achievement2.name = "Second Achievement"; + achievement2.title = "Second Achievement"; achievement2.displayOrder = 2; repository->create(achievement2); Achievement achievement3 = createTestAchievement(103, 1); - achievement3.name = "Third Achievement"; + achievement3.title = "Third Achievement"; achievement3.displayOrder = 3; repository->create(achievement3); @@ -970,7 +984,8 @@ TEST_F(SqliteAchievementRepositoryTest, GetAllUserUnlocks_FiltersBySet) { AchievementSet achievementSet2 = createTestAchievementSet(); achievementSet2.id = 2; - achievementSet2.name = "Second Set"; + achievementSet2.gameId = 2; + achievementSet2.title = "Second Set"; repository->create(achievementSet2); // Create achievements in different sets @@ -1030,7 +1045,8 @@ TEST_F(SqliteAchievementRepositoryTest, SetGameHash_UpsertBehavior) { AchievementSet achievementSet2 = createTestAchievementSet(); achievementSet2.id = 2; - achievementSet2.name = "Second Achievement Set"; + achievementSet2.gameId = 2; + achievementSet2.title = "Second Achievement Set"; repository->create(achievementSet2); const std::string contentHash = "abc123def456"; @@ -1045,7 +1061,7 @@ TEST_F(SqliteAchievementRepositoryTest, SetGameHash_UpsertBehavior) { auto result = repository->getAchievementSetByContentHash(contentHash); ASSERT_TRUE(result.has_value()); EXPECT_EQ(result->id, achievementSet2.id); - EXPECT_EQ(result->name, "Second Achievement Set"); + EXPECT_EQ(result->title, "Second Achievement Set"); } TEST_F(SqliteAchievementRepositoryTest, @@ -1064,7 +1080,7 @@ TEST_F(SqliteAchievementRepositoryTest, ASSERT_TRUE(result.has_value()); EXPECT_EQ(result->id, achievementSet.id); - EXPECT_EQ(result->name, achievementSet.name); + EXPECT_EQ(result->title, achievementSet.title); EXPECT_EQ(result->numAchievements, achievementSet.numAchievements); EXPECT_EQ(result->achievements.size(), 1); // Only achievements with flags == 3 are included @@ -1469,13 +1485,13 @@ TEST_F(SqliteAchievementRepositoryTest, CompleteWorkflow_CreateRetrieveUpdate) { EXPECT_TRUE(repository->create(achievementSet)); // Add achievements - Achievement achievement1 = createTestAchievement(101, 1); - achievement1.name = "First Achievement"; + Achievement achievement1 = createTestAchievement(101, achievementSet.id); + achievement1.title = "First Achievement"; achievement1.displayOrder = 1; EXPECT_TRUE(repository->create(achievement1)); - Achievement achievement2 = createTestAchievement(102, 1); - achievement2.name = "Second Achievement"; + Achievement achievement2 = createTestAchievement(102, achievementSet.id); + achievement2.title = "Second Achievement"; achievement2.displayOrder = 2; EXPECT_TRUE(repository->create(achievement2)); @@ -1489,10 +1505,11 @@ TEST_F(SqliteAchievementRepositoryTest, CompleteWorkflow_CreateRetrieveUpdate) { EXPECT_TRUE(repository->create(progress1)); EXPECT_TRUE(repository->create(progress2)); - // Retrieve by ID - auto retrievedById = repository->getAchievementSet(achievementSet.id); - ASSERT_TRUE(retrievedById.has_value()); - EXPECT_EQ(retrievedById->achievements.size(), 2); + // Retrieve by game ID + auto retrievedById = + repository->getAchievementSetsByGameId(achievementSet.gameId); + ASSERT_TRUE(retrievedById.size() == 1); + EXPECT_EQ(retrievedById[0].achievements.size(), 2); // Retrieve by hash auto retrievedByHash = @@ -1502,12 +1519,13 @@ TEST_F(SqliteAchievementRepositoryTest, CompleteWorkflow_CreateRetrieveUpdate) { EXPECT_EQ(retrievedByHash->achievements.size(), 2); // Update achievement set - achievementSet.name = "Updated Achievement Set"; - EXPECT_TRUE(repository->update(achievementSet)); + achievementSet.title = "Updated Achievement Set"; + EXPECT_TRUE(repository->create(achievementSet)); - auto updatedSet = repository->getAchievementSet(achievementSet.id); - ASSERT_TRUE(updatedSet.has_value()); - EXPECT_EQ(updatedSet->name, "Updated Achievement Set"); + auto updatedSet = + repository->getAchievementSetsByGameId(achievementSet.gameId); + ASSERT_TRUE(updatedSet.size() == 1); + EXPECT_EQ(updatedSet[0].title, "Updated Achievement Set"); } // GetAllUnsyncedUserUnlocks Tests diff --git a/tests/app/rcheevos/rcheevos_offline_client_test.cpp b/tests/app/rcheevos/rcheevos_offline_client_test.cpp index 8f2891e9..b82b0f10 100644 --- a/tests/app/rcheevos/rcheevos_offline_client_test.cpp +++ b/tests/app/rcheevos/rcheevos_offline_client_test.cpp @@ -2594,8 +2594,8 @@ TEST_F(RetroAchievementsOfflineClientTest, LoginGetsScoreCorrectly) { service.createOrUpdateUser(User{.username = "testuser", .token = "testtoken", - .points = 1000, - .hardcore_points = 2000}); + .softcoreScore = 1000, + .score = 2000}); const auto response = offlineClient.handleRequest( "https://retroachievements.com/dorequest.php", @@ -2774,8 +2774,8 @@ TEST_F(RetroAchievementsOfflineClientTest, service.createOrUpdateUser(User{.username = "testuser", .token = "testtoken", - .points = 80, - .hardcore_points = 102}); + .softcoreScore = 80, + .score = 102}); // Do unlock achievement request hardcore auto response = offlineClient.handleRequest( @@ -2899,8 +2899,8 @@ TEST_F(RetroAchievementsOfflineClientTest, // Set up user and achievement User user{.username = "testuser", .token = "token", - .points = 100, - .hardcore_points = 50}; + .softcoreScore = 100, + .score = 50}; service.createOrUpdateUser(user); Achievement achievement{.id = 1, @@ -2924,8 +2924,8 @@ TEST_F(RetroAchievementsOfflineClientTest, // Verify points were added to non-hardcore auto updatedUser = service.getUser("testuser"); ASSERT_TRUE(updatedUser.has_value()); - EXPECT_EQ(updatedUser->points, 125); // 100 + 25 - EXPECT_EQ(updatedUser->hardcore_points, 50); // Unchanged + EXPECT_EQ(updatedUser->softcoreScore, 125); // 100 + 25 + EXPECT_EQ(updatedUser->score, 50); // Unchanged } TEST_F(RetroAchievementsOfflineClientTest, AwardAchievementFirstTimeHardcore) { @@ -2937,8 +2937,8 @@ TEST_F(RetroAchievementsOfflineClientTest, AwardAchievementFirstTimeHardcore) { // Set up user and achievement User user{.username = "testuser", .token = "token", - .points = 100, - .hardcore_points = 50}; + .softcoreScore = 100, + .score = 50}; service.createOrUpdateUser(user); Achievement achievement{.id = 1, @@ -2962,8 +2962,8 @@ TEST_F(RetroAchievementsOfflineClientTest, AwardAchievementFirstTimeHardcore) { // Verify points were added to hardcore auto updatedUser = service.getUser("testuser"); ASSERT_TRUE(updatedUser.has_value()); - EXPECT_EQ(updatedUser->points, 100); // Unchanged - EXPECT_EQ(updatedUser->hardcore_points, 75); // 50 + 25 + EXPECT_EQ(updatedUser->softcoreScore, 100); // Unchanged + EXPECT_EQ(updatedUser->score, 75); // 50 + 25 } TEST_F(RetroAchievementsOfflineClientTest, @@ -2976,8 +2976,8 @@ TEST_F(RetroAchievementsOfflineClientTest, // Set up user and achievement User user{.username = "testuser", .token = "token", - .points = 100, - .hardcore_points = 50}; + .softcoreScore = 100, + .score = 50}; service.createOrUpdateUser(user); Achievement achievement{.id = 1, @@ -3000,8 +3000,8 @@ TEST_F(RetroAchievementsOfflineClientTest, // Verify initial state auto userAfterFirst = service.getUser("testuser"); ASSERT_TRUE(userAfterFirst.has_value()); - EXPECT_EQ(userAfterFirst->points, 125); // 100 + 25 - EXPECT_EQ(userAfterFirst->hardcore_points, 50); // Unchanged + EXPECT_EQ(userAfterFirst->softcoreScore, 125); // 100 + 25 + EXPECT_EQ(userAfterFirst->score, 50); // Unchanged // Then, award in hardcore mode (should move points) auto response2 = offlineClient.handleRequest( @@ -3012,8 +3012,8 @@ TEST_F(RetroAchievementsOfflineClientTest, // Verify points moved from non-hardcore to hardcore auto finalUser = service.getUser("testuser"); ASSERT_TRUE(finalUser.has_value()); - EXPECT_EQ(finalUser->points, 100); // 125 - 25 (moved to hardcore) - EXPECT_EQ(finalUser->hardcore_points, + EXPECT_EQ(finalUser->softcoreScore, 100); // 125 - 25 (moved to hardcore) + EXPECT_EQ(finalUser->score, 75); // 50 + 25 (moved from non-hardcore) // Verify unlock state @@ -3033,8 +3033,8 @@ TEST_F(RetroAchievementsOfflineClientTest, // Set up user and achievement User user{.username = "testuser", .token = "token", - .points = 100, - .hardcore_points = 50}; + .softcoreScore = 100, + .score = 50}; service.createOrUpdateUser(user); Achievement achievement{.id = 1, @@ -3057,8 +3057,8 @@ TEST_F(RetroAchievementsOfflineClientTest, // Verify initial state auto userAfterFirst = service.getUser("testuser"); ASSERT_TRUE(userAfterFirst.has_value()); - EXPECT_EQ(userAfterFirst->points, 100); // Unchanged - EXPECT_EQ(userAfterFirst->hardcore_points, 75); // 50 + 25 + EXPECT_EQ(userAfterFirst->softcoreScore, 100); // Unchanged + EXPECT_EQ(userAfterFirst->score, 75); // 50 + 25 // Then, try to award in non-hardcore mode (should be ignored - no points // change) @@ -3070,8 +3070,8 @@ TEST_F(RetroAchievementsOfflineClientTest, // Verify points unchanged (hardcore takes precedence) auto finalUser = service.getUser("testuser"); ASSERT_TRUE(finalUser.has_value()); - EXPECT_EQ(finalUser->points, 100); // Unchanged - EXPECT_EQ(finalUser->hardcore_points, 75); // Unchanged + EXPECT_EQ(finalUser->softcoreScore, 100); // Unchanged + EXPECT_EQ(finalUser->score, 75); // Unchanged // Verify unlock state (both should be true) auto unlock = service.getUserUnlock("testuser", 1); @@ -3090,8 +3090,8 @@ TEST_F(RetroAchievementsOfflineClientTest, // Set up user and achievement User user{.username = "testuser", .token = "token", - .points = 100, - .hardcore_points = 50}; + .softcoreScore = 100, + .score = 50}; service.createOrUpdateUser(user); Achievement achievement{.id = 1, @@ -3119,8 +3119,8 @@ TEST_F(RetroAchievementsOfflineClientTest, // Verify points only added once auto finalUser = service.getUser("testuser"); ASSERT_TRUE(finalUser.has_value()); - EXPECT_EQ(finalUser->points, 125); // 100 + 25 (only once) - EXPECT_EQ(finalUser->hardcore_points, 50); // Unchanged + EXPECT_EQ(finalUser->softcoreScore, 125); // 100 + 25 (only once) + EXPECT_EQ(finalUser->score, 50); // Unchanged } /** From 31f4c4c47275ba553210936bc3a85e24ecc6653f Mon Sep 17 00:00:00 2001 From: biscuitcakes Date: Fri, 23 Jan 2026 15:55:23 -0600 Subject: [PATCH 3/5] Oh you know lots of stuff --- libs/rcheevos | 2 +- .../achievements/achievement_repository.hpp | 31 +- src/app/achievements/achievement_service.cpp | 98 +- src/app/achievements/achievement_service.hpp | 14 +- src/app/achievements/models/achievement.hpp | 79 + .../achievements/models/achievement_set.hpp | 50 +- src/app/achievements/models/leaderboard.hpp | 56 + .../sqlite_achievement_repository.cpp | 197 +- .../sqlite_achievement_repository.hpp | 32 +- src/app/rcheevos/achievement_set_response.hpp | 70 + .../rcheevos/award_achievement_response.hpp | 22 +- src/app/rcheevos/login2_response.hpp | 27 +- .../offline/rcheevos_offline_client.cpp | 157 +- .../offline/rcheevos_offline_client.hpp | 15 +- src/app/rcheevos/user.h | 49 +- src/main.cpp | 2 +- .../achievements/achievement_service_test.cpp | 726 ++-- .../rcheevos/rcheevos_offline_client_test.cpp | 2986 +---------------- 18 files changed, 1019 insertions(+), 3594 deletions(-) create mode 100644 src/app/achievements/models/leaderboard.hpp create mode 100644 src/app/rcheevos/achievement_set_response.hpp diff --git a/libs/rcheevos b/libs/rcheevos index 21b87cf8..40d916de 160000 --- a/libs/rcheevos +++ b/libs/rcheevos @@ -1 +1 @@ -Subproject commit 21b87cf86c5bf35f19788292b86019d2716e40d1 +Subproject commit 40d916de00fe757bab40fb4db41a7912193a48e3 diff --git a/src/app/achievements/achievement_repository.hpp b/src/app/achievements/achievement_repository.hpp index 65d47665..079c1658 100644 --- a/src/app/achievements/achievement_repository.hpp +++ b/src/app/achievements/achievement_repository.hpp @@ -130,6 +130,8 @@ class IAchievementRepository { virtual bool create(Game game) = 0; + virtual bool create(const Leaderboard &leaderboard) = 0; + /** * @brief Retrieves the achievement set ID associated with a content hash * @@ -237,6 +239,9 @@ class IAchievementRepository { */ virtual bool setGameId(const std::string &contentHash, int gameId) = 0; + virtual bool setAchievementSetHash(unsigned achievementSetId, + const std::string &contentHash) = 0; + // User Unlock Operations /** @@ -302,32 +307,6 @@ class IAchievementRepository { */ [[nodiscard]] virtual std::vector getAllUnsyncedUserUnlocks(const std::string &username) const = 0; - - // Patch Response Caching Operations - - /** - * @brief Caches a patch response from RetroAchievements API - * - * Stores the complete patch response data for offline use, enabling - * achievement functionality when not connected to the RetroAchievements - * service. - * - * @param patchResponse The patch response data to cache - * @return true if caching succeeded, false otherwise - */ - virtual bool create(PatchResponse patchResponse) = 0; - - /** - * @brief Retrieves cached patch response data for a game - * - * Fetches previously cached patch response data, allowing offline achievement - * functionality without requiring a network connection. - * - * @param gameId The game ID to retrieve patch data for - * @return The cached patch response if found, std::nullopt otherwise - */ - [[nodiscard]] virtual std::optional - getPatchResponse(int gameId) const = 0; }; } // namespace firelight::achievements \ No newline at end of file diff --git a/src/app/achievements/achievement_service.cpp b/src/app/achievements/achievement_service.cpp index 5c64cba0..0af922bd 100644 --- a/src/app/achievements/achievement_service.cpp +++ b/src/app/achievements/achievement_service.cpp @@ -17,7 +17,7 @@ std::optional AchievementService::getUser(const std::string &username) const { return m_repository.getUser(username); } -bool AchievementService::createOrUpdateUser(const User &user) { +bool AchievementService::create(const User &user) { return m_repository.createOrUpdateUser(user); } @@ -31,12 +31,29 @@ bool AchievementService::setGameId(const std::string &contentHash, const int gameId) { return m_repository.setGameId(contentHash, gameId); } +bool AchievementService::setAchievementSetHash(const unsigned achievementSetId, + const std::string &contentHash) { + return m_repository.setAchievementSetHash(achievementSetId, contentHash); +} std::optional -AchievementService::getAchievement(unsigned achievementId) const { +AchievementService::getAchievement(const unsigned achievementId) const { return m_repository.getAchievement(achievementId); } +bool AchievementService::create(const Game &game) { + return m_repository.create(game); +} +bool AchievementService::create(const AchievementSet &achievementSet) { + return m_repository.create(achievementSet); +} +bool AchievementService::create(const Achievement &achievement) { + return m_repository.create(achievement); +} +bool AchievementService::create(const Leaderboard &leaderboard) { + return m_repository.create(leaderboard); +} + std::optional AchievementService::getGameId(const std::string &contentHash) const { return m_repository.getGameId(contentHash); @@ -65,83 +82,6 @@ AchievementService::getAllUserUnlocks(const std::string &username, return m_repository.getAllUserUnlocks(username, gameId); } -std::optional -AchievementService::getPatchResponse(const int gameId) const { - return m_repository.getPatchResponse(gameId); -} - -bool AchievementService::processPatchResponse(const std::string &username, - const PatchResponse &response) { - m_repository.create(response); - - std::vector achievements; - unsigned totalPoints = 0; - auto i = 0; - for (const auto &a : response.PatchData.Achievements) { - auto achievement = Achievement{.id = a.ID, - .achievementSetId = response.PatchData.ID, - .memAddr = a.MemAddr, - .title = a.Title, - .description = a.Description, - .points = a.Points, - .author = a.Author, - .modified = a.Modified, - .created = a.Created, - .flags = a.Flags, - .type = a.Type, - .rarity = a.Rarity, - .rarityHardcore = a.RarityHardcore, - .badgeUrl = a.BadgeURL, - .badgeLockedUrl = a.BadgeLockedURL, - .displayOrder = i++}; - - if (a.Flags == 5) { - continue; - } - - achievements.emplace_back(achievement); - totalPoints += a.Points; - } - - auto set = AchievementSet{.id = response.PatchData.ID, - .title = response.PatchData.Title, - .type = "core", - .gameId = response.PatchData.ID, - .imageIconUrl = response.PatchData.ImageIconURL, - .numAchievements = - static_cast(achievements.size()), - .totalPoints = totalPoints}; - - if (!m_repository.create(set)) { - spdlog::error("Failed to createOrUpdate achievement set: {}", set.id); - return false; - } - - for (const auto &achievement : achievements) { - if (!m_repository.create(achievement)) { - spdlog::error("Failed to createOrUpdate achievement: {}", achievement.id); - return false; - } - - auto unlock = m_repository.getUserUnlock(username, achievement.id); - if (!unlock.has_value()) { - auto newUnlock = UserUnlock{.username = username, - .achievementId = achievement.id, - .earned = false, - .earnedHardcore = false, - .unlockTimestamp = 0, - .unlockTimestampHardcore = 0, - .synced = true}; - - if (!m_repository.createOrUpdate(newUnlock)) { - spdlog::error("Failed to createOrUpdate user unlock: {} for user {}", - achievement.id, username); - return false; - } - } - } - return true; -} bool AchievementService::processStartSessionResponse( const std::string &username, const unsigned gameId, const StartSessionResponse &startSessionResponse) { diff --git a/src/app/achievements/achievement_service.hpp b/src/app/achievements/achievement_service.hpp index ea15d005..58089d45 100644 --- a/src/app/achievements/achievement_service.hpp +++ b/src/app/achievements/achievement_service.hpp @@ -50,16 +50,24 @@ class AchievementService { * @param user The user data to store or update * @return true if the operation succeeded, false otherwise */ - bool createOrUpdateUser(const User &user); + bool create(const User &user); [[nodiscard]] std::optional getAchievementSetByContentHash(const std::string &contentHash) const; bool setGameId(const std::string &contentHash, int gameId); + bool setAchievementSetHash(unsigned achievementSetId, + const std::string &contentHash); + [[nodiscard]] std::optional getAchievement(unsigned achievementId) const; + bool create(const Game &game); + bool create(const AchievementSet &achievementSet); + bool create(const Achievement &achievement); + bool create(const Leaderboard &leaderboard); + /** * @brief Retrieves the achievement set ID associated with a content hash * @@ -84,10 +92,6 @@ class AchievementService { [[nodiscard]] std::vector getAllUserUnlocks(const std::string &username, unsigned gameId) const; - [[nodiscard]] std::optional getPatchResponse(int gameId) const; - bool processPatchResponse(const std::string &username, - const PatchResponse &response); - bool processStartSessionResponse(const std::string &username, unsigned gameId, const StartSessionResponse &startSessionResponse); diff --git a/src/app/achievements/models/achievement.hpp b/src/app/achievements/models/achievement.hpp index 01cb0ad1..18656767 100644 --- a/src/app/achievements/models/achievement.hpp +++ b/src/app/achievements/models/achievement.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include namespace firelight::achievements { @@ -22,4 +23,82 @@ struct Achievement { std::string badgeLockedUrl; int displayOrder; }; + +inline void from_json(const nlohmann::json &j, Achievement &a) { + j.at("ID").get_to(a.id); + + if (j.contains("MemAddr") && !j.at("MemAddr").is_null()) { + j.at("MemAddr").get_to(a.memAddr); + } + + if (j.contains("Title") && !j.at("Title").is_null()) { + j.at("Title").get_to(a.title); + } + + if (j.contains("Description") && !j.at("Description").is_null()) { + j.at("Description").get_to(a.description); + } + + if (j.contains("Points") && !j.at("Points").is_null()) { + j.at("Points").get_to(a.points); + } + + if (j.contains("Author") && !j.at("Author").is_null()) { + j.at("Author").get_to(a.author); + } + + if (j.contains("Modified") && !j.at("Modified").is_null()) { + j.at("Modified").get_to(a.modified); + } + + if (j.contains("Created") && !j.at("Created").is_null()) { + j.at("Created").get_to(a.created); + } + + if (j.contains("BadgeName") && !j.at("BadgeName").is_null()) { + j.at("BadgeName").get_to(a.badgeName); + } + + if (j.contains("Flags") && !j.at("Flags").is_null()) { + j.at("Flags").get_to(a.flags); + } + + if (j.contains("Type") && !j.at("Type").is_null()) { + j.at("Type").get_to(a.type); + } + + if (j.contains("Rarity") && !j.at("Rarity").is_null()) { + j.at("Rarity").get_to(a.rarity); + } + + if (j.contains("RarityHardcore") && !j.at("RarityHardcore").is_null()) { + j.at("RarityHardcore").get_to(a.rarityHardcore); + } + + if (j.contains("BadgeURL") && !j.at("BadgeURL").is_null()) { + j.at("BadgeURL").get_to(a.badgeUrl); + } + + if (j.contains("BadgeLockedURL") && !j.at("BadgeLockedURL").is_null()) { + j.at("BadgeLockedURL").get_to(a.badgeLockedUrl); + } +} + +inline void to_json(nlohmann::json &j, const Achievement &a) { + j = nlohmann::json{{"ID", a.id}, + {"MemAddr", a.memAddr}, + {"Title", a.title}, + {"Description", a.description}, + {"Points", a.points}, + {"Author", a.author}, + {"Modified", a.modified}, + {"Created", a.created}, + {"BadgeName", a.badgeName}, + {"Flags", a.flags}, + {"Type", a.type}, + {"Rarity", a.rarity}, + {"RarityHardcore", a.rarityHardcore}, + {"BadgeURL", a.badgeUrl}, + {"BadgeLockedURL", a.badgeLockedUrl}}; +} } // namespace firelight::achievements diff --git a/src/app/achievements/models/achievement_set.hpp b/src/app/achievements/models/achievement_set.hpp index e55d5721..18b89c0d 100644 --- a/src/app/achievements/models/achievement_set.hpp +++ b/src/app/achievements/models/achievement_set.hpp @@ -1,21 +1,55 @@ #pragma once #include "achievement.hpp" +#include "leaderboard.hpp" +#include #include #include namespace firelight::achievements { struct AchievementSet { - unsigned id; - std::string title; - std::string type; - unsigned gameId; - std::string imageIconUrl; + unsigned id{}; + unsigned gameId{}; + std::string title{}; + std::string type{}; + std::string imageIconUrl{}; - unsigned numAchievements; - unsigned totalPoints; + // Calculated fields + unsigned numAchievements{}; + unsigned totalPoints{}; // Read-only, populated from database - std::vector achievements; + std::vector achievements{}; + std::vector leaderboards{}; }; + +inline void from_json(const nlohmann::json &j, AchievementSet &a) { + j.at("AchievementSetId").get_to(a.id); + j.at("GameId").get_to(a.gameId); + + if (j.contains("Title") && !j.at("Title").is_null()) { + j.at("Title").get_to(a.title); + } + + if (j.contains("Type") && !j.at("Type").is_null()) { + j.at("Type").get_to(a.type); + } + + if (j.contains("ImageIconUrl") && !j.at("ImageIconUrl").is_null()) { + j.at("ImageIconUrl").get_to(a.imageIconUrl); + } + + j.at("Achievements").get_to(a.achievements); + j.at("Leaderboards").get_to(a.leaderboards); +} + +inline void to_json(nlohmann::json &j, const AchievementSet &a) { + j = nlohmann::json{{"AchievementSetId", a.id}, + {"GameId", a.gameId}, + {"Title", a.title}, + {"Type", a.type}, + {"ImageIconUrl", a.imageIconUrl}, + {"Achievements", a.achievements}, + {"Leaderboards", a.leaderboards}}; +} } // namespace firelight::achievements \ No newline at end of file diff --git a/src/app/achievements/models/leaderboard.hpp b/src/app/achievements/models/leaderboard.hpp new file mode 100644 index 00000000..27496f5a --- /dev/null +++ b/src/app/achievements/models/leaderboard.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +namespace firelight::achievements { +struct Leaderboard { + unsigned id; + unsigned achievementSetId; + std::string memAddr; + std::string format; + bool lowerIsBetter; + std::string title; + std::string description; + bool hidden; + int displayOrder; +}; + +inline void from_json(const nlohmann::json &j, Leaderboard &l) { + j.at("ID").get_to(l.id); + + if (j.contains("Mem") && !j.at("Mem").is_null()) { + j.at("Mem").get_to(l.memAddr); + } + + if (j.contains("Format") && !j.at("Format").is_null()) { + j.at("Format").get_to(l.format); + } + + if (j.contains("LowerIsBetter") && !j.at("LowerIsBetter").is_null()) { + j.at("LowerIsBetter").get_to(l.lowerIsBetter); + } + + if (j.contains("Title") && !j.at("Title").is_null()) { + j.at("Title").get_to(l.title); + } + + if (j.contains("Description") && !j.at("Description").is_null()) { + j.at("Description").get_to(l.description); + } + + if (j.contains("Hidden") && !j.at("Hidden").is_null()) { + j.at("Hidden").get_to(l.hidden); + } +} + +inline void to_json(nlohmann::json &j, const Leaderboard &l) { + j = nlohmann::json{{"ID", l.id}, + {"Mem", l.memAddr}, + {"Format", l.format}, + {"LowerIsBetter", l.lowerIsBetter}, + {"Title", l.title}, + {"Description", l.description}, + {"Hidden", l.hidden}}; +} +} // namespace firelight::achievements diff --git a/src/app/achievements/sqlite_achievement_repository.cpp b/src/app/achievements/sqlite_achievement_repository.cpp index c4566f9f..04ce68a9 100644 --- a/src/app/achievements/sqlite_achievement_repository.cpp +++ b/src/app/achievements/sqlite_achievement_repository.cpp @@ -42,9 +42,8 @@ SqliteAchievementRepository::SqliteAchievementRepository(std::string dbPath) const std::string setupQueryString = R"( -- Content hash to game mapping for automatic loading CREATE TABLE IF NOT EXISTS game_hashes ( - hash TEXT NOT NULL, -- Game content hash - game_id INTEGER NOT NULL, -- Associated set - PRIMARY KEY (hash, game_id) + hash TEXT NOT NULL PRIMARY KEY, -- Game content hash + game_id INTEGER NOT NULL ); -- Content hash to achievement set mapping for automatic loading @@ -57,6 +56,7 @@ SqliteAchievementRepository::SqliteAchievementRepository(std::string dbPath) -- User account information CREATE TABLE IF NOT EXISTS users ( username TEXT PRIMARY KEY NOT NULL, + display_name TEXT NOT NULL DEFAULT '', -- User display name avatar_url TEXT NOT NULL DEFAULT '', -- User avatar URL token TEXT NOT NULL, -- Authentication token score INTEGER NOT NULL, -- Total score earned @@ -110,6 +110,19 @@ SqliteAchievementRepository::SqliteAchievementRepository(std::string dbPath) FOREIGN KEY (achievement_set_id) REFERENCES achievement_sets(id) ); + -- Achievement set metadata + CREATE TABLE IF NOT EXISTS leaderboards ( + id INTEGER NOT NULL PRIMARY KEY, + achievement_set_id INTEGER NOT NULL, -- Associated game ID + title TEXT NOT NULL, -- Achievement set name + description TEXT NOT NULL, -- Achievement set type + mem_address TEXT NOT NULL, -- Achievement set type + lower_is_better INTEGER NOT NULL, -- Achievement set type + format TEXT NOT NULL, -- Achievement set type + hidden INTEGER NOT NULL DEFAULT 0, -- Achievement set type + FOREIGN KEY (achievement_set_id) REFERENCES achievement_sets(id) + ); + -- User progress tracking for incremental achievements CREATE TABLE IF NOT EXISTS achievement_progress ( achievement_id INTEGER NOT NULL, -- Achievement being tracked @@ -153,18 +166,25 @@ SqliteAchievementRepository::SqliteAchievementRepository(std::string dbPath) std::optional SqliteAchievementRepository::getUser(const std::string &username) const { try { - SQLite::Statement query(*m_database, - "SELECT username, token, softcoreScore, score " - "FROM users " - "WHERE username = :username"); + SQLite::Statement query( + *m_database, + "SELECT username, display_name, token, softcore_score, score, " + "avatar_url, messages, permissions, account_type " + "FROM users " + "WHERE username = :username"); query.bind(":username", username); if (query.executeStep()) { User user; user.username = query.getColumn(0).getString(); - user.token = query.getColumn(1).getString(); - user.softcoreScore = query.getColumn(2); - user.score = query.getColumn(3); + user.displayName = query.getColumn(1).getString(); + user.token = query.getColumn(2).getString(); + user.softcoreScore = query.getColumn(3); + user.score = query.getColumn(4); + user.avatarUrl = query.getColumn(5).getString(); + user.messages = query.getColumn(6); + user.permissions = query.getColumn(7); + user.accountType = query.getColumn(8).getString(); return user; } @@ -178,17 +198,24 @@ std::vector SqliteAchievementRepository::listUsers() const { std::vector users; try { - SQLite::Statement query(*m_database, - "SELECT username, token, softcoreScore, score " - "FROM users " - "ORDER BY username"); + SQLite::Statement query( + *m_database, + "SELECT username, display_name, token, softcore_score, score, " + "avatar_url, messages, permissions, account_type " + "FROM users " + "ORDER BY username"); while (query.executeStep()) { User user; user.username = query.getColumn(0).getString(); - user.token = query.getColumn(1).getString(); - user.softcoreScore = query.getColumn(2); - user.score = query.getColumn(3); + user.displayName = query.getColumn(1).getString(); + user.token = query.getColumn(2).getString(); + user.softcoreScore = query.getColumn(3); + user.score = query.getColumn(4); + user.avatarUrl = query.getColumn(5).getString(); + user.messages = query.getColumn(6); + user.permissions = query.getColumn(7); + user.accountType = query.getColumn(8).getString(); users.emplace_back(user); } } catch (const std::exception &e) { @@ -210,19 +237,34 @@ std::vector SqliteAchievementRepository::listUsers() const { */ bool SqliteAchievementRepository::createOrUpdateUser(const User &user) { try { - SQLite::Statement query( - *m_database, - "INSERT INTO users " - "(username, token, softcoreScore, score) " - "VALUES (:username, :token, :softcoreScore, :hardcorePoints) " - "ON CONFLICT(username) DO UPDATE SET " - "token = excluded.token, " - "softcoreScore = excluded.softcoreScore, " - "score = excluded.score"); + SQLite::Statement query(*m_database, + "INSERT INTO users " + "(username, display_name, token, softcore_score, " + "score, avatar_url, messages, " + "permissions, account_type) " + "VALUES (:username, :displayName, :token, " + ":softcoreScore, :score, :avatarUrl, " + ":messages, :permissions, :accountType) " + "ON CONFLICT(username) DO UPDATE SET " + "display_name = excluded.display_name, " + "token = excluded.token, " + "softcore_score = excluded.softcore_score, " + "score = excluded.score, " + "avatar_url = excluded.avatar_url, " + "messages = excluded.messages, " + "permissions = excluded.permissions, " + "account_type = excluded.account_type"); + query.bind(":username", user.username); + // Intentionally set display_name to username on creation/update + query.bind(":displayName", user.username); query.bind(":token", user.token); query.bind(":softcoreScore", user.softcoreScore); - query.bind(":hardcorePoints", user.score); + query.bind(":score", user.score); + query.bind(":avatarUrl", user.avatarUrl); + query.bind(":messages", user.messages); + query.bind(":permissions", user.permissions); + query.bind(":accountType", user.accountType); query.exec(); return true; @@ -542,6 +584,25 @@ bool SqliteAchievementRepository::setGameId(const std::string &contentHash, } } +bool SqliteAchievementRepository::setAchievementSetHash( + const unsigned achievementSetId, const std::string &contentHash) { + try { + SQLite::Statement query(*m_database, + "INSERT INTO achievement_set_hashes " + "(hash, achievement_set_id) " + "VALUES (:contentHash, :achievementSetId) " + "ON CONFLICT(hash, achievement_set_id) DO NOTHING"); + query.bind(":contentHash", contentHash); + query.bind(":achievementSetId", achievementSetId); + + query.exec(); + return true; + } catch (const std::exception &e) { + spdlog::error("Failed to set achievement set hash: {}", e.what()); + return false; + } +} + std::optional SqliteAchievementRepository::getAchievementSetByContentHash( const std::string &contentHash) const { @@ -628,7 +689,7 @@ std::optional SqliteAchievementRepository::getGameId(const std::string &contentHash) const { try { SQLite::Statement query(*m_database, - "SELECT achievement_set_id FROM game_hashes " + "SELECT game_id FROM game_hashes " "WHERE hash = :contentHash AND game_id != 0"); query.bind(":contentHash", contentHash); @@ -659,6 +720,39 @@ SqliteAchievementRepository::getGameHash(const unsigned gameId) const { return std::nullopt; } } +bool SqliteAchievementRepository::create(const Leaderboard &leaderboard) { + try { + SQLite::Statement query( + *m_database, + "INSERT INTO leaderboards " + "(id, achievement_set_id, title, description, mem_address, " + "lower_is_better, format, hidden) " + "VALUES (:id, :achievementSetId, :title, :description, :memAddress, " + ":lowerIsBetter, :format, :hidden) " + "ON CONFLICT(id) DO UPDATE SET " + "achievement_set_id = excluded.achievement_set_id, " + "title = excluded.title, " + "description = excluded.description, " + "mem_address = excluded.mem_address, " + "lower_is_better = excluded.lower_is_better, " + "format = excluded.format, " + "hidden = excluded.hidden"); + query.bind(":id", leaderboard.id); + query.bind(":achievementSetId", leaderboard.achievementSetId); + query.bind(":title", leaderboard.title); + query.bind(":description", leaderboard.description); + query.bind(":memAddress", leaderboard.memAddr); + query.bind(":lowerIsBetter", leaderboard.lowerIsBetter); + query.bind(":format", leaderboard.format); + query.bind(":hidden", leaderboard.hidden); + + query.exec(); + return true; + } catch (const std::exception &e) { + spdlog::error("Failed to create leaderboard: {}", e.what()); + return false; + } +} std::optional SqliteAchievementRepository::getUserUnlock(const std::string &username, @@ -811,51 +905,4 @@ std::vector SqliteAchievementRepository::getAllUnsyncedUserUnlocks( return unlocks; } - -std::optional -SqliteAchievementRepository::getPatchResponse(const int gameId) const { - try { - SQLite::Statement query(*m_database, - "SELECT cached_value FROM patch_response_cache " - "WHERE game_id = :gameId"); - query.bind(":gameId", gameId); - - if (query.executeStep()) { - const void *blobData = query.getColumn(0).getBlob(); - int blobSize = query.getColumn(0).getBytes(); - - std::string jsonString(static_cast(blobData), blobSize); - auto jsonObj = nlohmann::json::parse(jsonString); - - PatchResponse response; - from_json(jsonObj, response); - return response; - } - - return std::nullopt; - } catch (const std::exception &e) { - spdlog::error("Failed to get patch response: {}", e.what()); - return std::nullopt; - } -} -bool SqliteAchievementRepository::create(const PatchResponse patchResponse) { - try { - nlohmann::json jsonObj; - to_json(jsonObj, patchResponse); - const std::string jsonString = jsonObj.dump(); - - SQLite::Statement query(*m_database, - "INSERT OR REPLACE INTO patch_response_cache " - "(game_id, cached_value) " - "VALUES (:gameId, :cachedValue)"); - query.bind(":gameId", patchResponse.PatchData.ID); - query.bind(":cachedValue", jsonString.c_str(), jsonString.size()); - - query.exec(); - return true; - } catch (const std::exception &e) { - spdlog::error("Failed to createOrUpdate patch response: {}", e.what()); - return false; - } -} } // namespace firelight::achievements \ No newline at end of file diff --git a/src/app/achievements/sqlite_achievement_repository.hpp b/src/app/achievements/sqlite_achievement_repository.hpp index 08eb46a2..efc8af0f 100644 --- a/src/app/achievements/sqlite_achievement_repository.hpp +++ b/src/app/achievements/sqlite_achievement_repository.hpp @@ -161,9 +161,11 @@ class SqliteAchievementRepository final : public IAchievementRepository { * @see setGameId() to create or update hash <-> set ID mappings * @see getAchievementSetByContentHash() to retrieve sets directly by hash */ - std::optional getGameHash(unsigned gameId) const override; + [[nodiscard]] std::optional + getGameHash(unsigned gameId) const override; // Individual Achievement Operations + bool create(const Leaderboard &leaderboard) override; /** * @brief Creates or updates an individual achievement @@ -210,6 +212,9 @@ class SqliteAchievementRepository final : public IAchievementRepository { */ bool setGameId(const std::string &contentHash, int gameId) override; + bool setAchievementSetHash(unsigned achievementSetId, + const std::string &contentHash) override; + // User Unlock Operations /** @@ -288,31 +293,6 @@ class SqliteAchievementRepository final : public IAchievementRepository { std::vector getAllUnsyncedUserUnlocks(const std::string &username) const override; - // Patch Response Caching Operations - - /** - * @brief Retrieves cached patch response data for offline use - * - * Fetches previously stored RetroAchievements API patch response data, - * enabling achievement functionality without network connectivity. - * - * @param gameId The game ID to retrieve cached data for - * @return The cached patch response if available, std::nullopt otherwise - */ - std::optional getPatchResponse(int gameId) const override; - - /** - * @brief Caches RetroAchievements patch response data - * - * Stores complete patch response JSON data in binary format for later - * offline retrieval. Enables achievement functionality without requiring - * constant network connectivity. - * - * @param patchResponse The patch response data to cache - * @return true if caching succeeded, false on database error - */ - bool create(PatchResponse patchResponse) override; - private: /** @brief Path to the SQLite database file */ std::string m_databaseFile; diff --git a/src/app/rcheevos/achievement_set_response.hpp b/src/app/rcheevos/achievement_set_response.hpp new file mode 100644 index 00000000..34855a2c --- /dev/null +++ b/src/app/rcheevos/achievement_set_response.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include + +#include "achievements/models/achievement_set.hpp" + +namespace firelight::achievements { + struct AchievementSetResponse { + bool Success{}; + unsigned GameId{}; + std::string Title{}; + std::string ImageIconUrl{}; + unsigned RichPresenceGameId{}; + std::string RichPresencePatch{}; + unsigned ConsoleId{}; + + std::vector Sets{}; + }; + + inline void from_json(const nlohmann::json &j, AchievementSetResponse &p) { + j.at("Success").get_to(p.Success); + + if (j.contains("GameId") && !j.at("GameId").is_null()) { + j.at("GameId").get_to(p.GameId); + } + if (j.contains("Title") && !j.at("Title").is_null()) { + j.at("Title").get_to(p.Title); + } + if (j.contains("ImageIconUrl") && !j.at("ImageIconUrl").is_null()) { + j.at("ImageIconUrl").get_to(p.ImageIconUrl); + } + + if (j.contains("RichPresenceGameId") && !j.at("RichPresenceGameId").is_null()) { + j.at("RichPresenceGameId").get_to(p.RichPresenceGameId); + } + + if (j.contains("RichPresencePatch") && !j.at("RichPresencePatch").is_null()) { + j.at("RichPresencePatch").get_to(p.RichPresencePatch); + } + + if (j.contains("ConsoleId") && !j.at("ConsoleId").is_null()) { + j.at("ConsoleId").get_to(p.ConsoleId); + } + + if (j.contains("Sets") && !j.at("Sets").is_null()) { + j.at("Sets").get_to(p.Sets); + } + } + + inline void to_json(nlohmann::json &j, const AchievementSetResponse &a) { + j = nlohmann::json{ + {"Success", a.Success}, + {"GameId", a.GameId}, + {"Title", a.Title}, + {"ImageIconUrl", a.ImageIconUrl}, + {"RichPresenceGameId", a.RichPresenceGameId}, + {"RichPresencePatch", a.RichPresencePatch}, + {"ConsoleId", a.ConsoleId}, + {"Sets", a.Sets} + }; + } + + // NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(PatchAchievement, ID, MemAddr, Title, Description, Points, Author, Modified, Created, + // BadgeName, Flags, Type, Rarity, RarityHardcore, BadgeURL, BadgeLockedURL) + // NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(PatchDataStruct, ID, Title, ImageIcon, RichPresencePatch, ConsoleID, + // ImageIconURL, Achievements) +} + +// "{\"Success\":true,\"PatchData\":{\"ID\":228,\"Title\":\"Super Mario World\",\"ImageIcon\":\"\\/Images\\/066393.png\",\"RichPresencePatch\":\"\",\"ConsoleID\":3,\"ImageIconURL\":\"https:\\/\\/media.retroachievements.org\\/Images\\/066393.png\",\"Achievements\":[{\"ID\":4934,\"MemAddr\":\"R:0xH0013c7=4\",\"Title\":\"Yellow Yoshi (old prototype)\",\"Description\":\"old abandoned prototype\",\"Points\":0,\"Author\":\"UNHchabo\",\"Modified\":1504474480,\"Created\":1392011140,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":4935,\"MemAddr\":\"R:0xH0013c7=6\",\"Title\":\"Blue Yoshi (old prototype)\",\"Description\":\"old abandoned prototype\",\"Points\":0,\"Author\":\"UNHchabo\",\"Modified\":1504474488,\"Created\":1392011155,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":4936,\"MemAddr\":\"R:0xH0013c7=8\",\"Title\":\"Red Yoshi (old prototype)\",\"Description\":\"old abandoned prototype\",\"Points\":0,\"Author\":\"UNHchabo\",\"Modified\":1504474493,\"Created\":1392011190,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":4937,\"MemAddr\":\"R:0xH000dbe>=98_R:0xH000019=1\",\"Title\":\"Super 99 (old prototype)\",\"Description\":\"Get 99 Lives and be not small\",\"Points\":0,\"Author\":\"UNHchabo\",\"Modified\":1504474496,\"Created\":1392018537,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":5756,\"MemAddr\":\"R:0xH0013c5>0\",\"Title\":\"Moon (old prototype)\",\"Description\":\"Collect a 3-up Moon\",\"Points\":0,\"Author\":\"UNHchabo\",\"Modified\":1504474501,\"Created\":1393710657,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":45815,\"MemAddr\":\"0xT001ecc>d0xT001ecc.1._R:0xH000db5=0_R:0xH000db3=1_0xT001ec9>d0xT001ec9.1._0xT001ec8>d0xT001ec8.1._0xT001ec7>d0xT001ec7.1._R:0xH000019>0_R:0xH001490>0_R:0xH001f28>0_R:0xH001f29>0_R:0x 001f2a>0_0xH001ecc=0.1._R:0xH00187a>0_0xT001eb7>d0xT001eb7.1._0xT001eab>d0xT001eab.1._0xT001ea6>d0xT001ea6.1._0xT001ea8>d0xT001ea8.1._0xT001ea9>d0xT001ea9.1._0xT001ee0>d0xT001ee0.1._0xT001ede>d0xT001ede.1._0xT001ed0>d0xT001ed0.1._0xT001edf>d0xT001edf.1._0xT001ee2>d0xT001ee2.1._0xT001ea7>d0xT001ea7.1.\",\"Title\":\"Mini-Mario vs the World 1 (duplicate)\",\"Description\":\"not an achievement is a duplicate.\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474529,\"Created\":1488837144,\"BadgeName\":\"46672\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46672.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46672_lock.png\"},{\"ID\":45857,\"MemAddr\":\"R:0xH000100<=10_R:0xH000db3=1_R:0xH000dbed0xT001ecb.1._0xT001ecc>d0xT001ecc.1.\",\"Title\":\"Deathless Two Stages (prototype)\",\"Description\":\"prototype - not an achievement\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474132,\"Created\":1488840902,\"BadgeName\":\"00000\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00000.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00000_lock.png\"},{\"ID\":45998,\"MemAddr\":\"R:0xH0013ef=1_R:0xH0013bf!=1_0xH000dbe>d0xH000dbe.20._0xH001411=1\",\"Title\":\"I could do this all day (unofficial)\",\"Description\":\"Get 20 1-ups on Vanilla Secret 2 without touching the ground\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474504,\"Created\":1489073027,\"BadgeName\":\"46617\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46617.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46617_lock.png\"},{\"ID\":46009,\"MemAddr\":\"R:0xH0013bf!=42_d0xH0014c9=8_d0xH0014ca=8_d0xH0014cb=8_d0xH0014cc=8_d0xH0014cd=8_d0xH0014ce=8_d0xH0014cf=8_d0xH0014d0=8_0xH0014c9=9_0xH0014ca=9_0xH0014cb=9_0xH0014cc=9_0xH0014cd=9_0xH0014ce=9_0xH0014cf=9_0xH0014d0=9_0xH0013ef=1_0x 001408=51201\",\"Title\":\"A Mighty Quake (duplicate)\",\"Description\":\"(duplicate)\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474509,\"Created\":1489073081,\"BadgeName\":\"46678\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46678.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46678_lock.png\"},{\"ID\":46011,\"MemAddr\":\"R:0xH0013bf!=76_0xH001dff=12_R:0xH0013ef=1_0x 000094<464.1._R:0xH00187a>0\",\"Title\":\"A Groovy Flight (duplicate)\",\"Description\":\"Pass SPECIAL-Groovy flying. No landing past the first ? box. No Yoshi\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474513,\"Created\":1489086171,\"BadgeName\":\"46671\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46671.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46671_lock.png\"},{\"ID\":46062,\"MemAddr\":\"0xH0013f3=1.20._0xH001411=1.20._R:0xH001411=0_0xH000019!=0_R:0xH000019=0\",\"Title\":\"Another Kind of Flying (legacy)\",\"Description\":\"Collect a P-Balloon\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474516,\"Created\":1489171212,\"BadgeName\":\"46564\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46564.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46564_lock.png\"},{\"ID\":46119,\"MemAddr\":\"P:0xH0013bf!=49_0xH000065=6.1._P:0xH000100!=15_0xH000065=100.1._0xH000065=1.1._0xH000065=62.1._0xH000065=96.1._0xH000065=21.1._0xH000065=118.1._0xH000065=168.1.\",\"Title\":\"Bowser Castle Explorer (dup)\",\"Description\":\"Complete all 8 numbered rooms in one game session\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474465,\"Created\":1489285071,\"BadgeName\":\"46769\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46769.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46769_lock.png\"},{\"ID\":46330,\"MemAddr\":\"R:0xH0013bf!=46_R:0x 000094<3926_R:0x 000094>4512_R:0xH0014d0=2_R:0xH0014d0=3_R:0xH0014d0=4_0x 000094>4369_0xH0014d0=8\",\"Title\":\"Suicide Prevention (duplicate)\",\"Description\":\"O n Vanilla Dome 3 Stop the koopa by the midflag from getting killed\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474531,\"Created\":1489701495,\"BadgeName\":\"00000\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00000.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00000_lock.png\"},{\"ID\":59018,\"MemAddr\":\"B:0xH0dbf=0_d0xH0dbf=4294967295_0xH0dbf=5\",\"Title\":\"bug\",\"Description\":\"buggy\",\"Points\":1,\"Author\":\"April\",\"Modified\":1549934619,\"Created\":1522626529,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":85062,\"MemAddr\":\"0xH000071=9\",\"Title\":\"I failed you, my princess\",\"Description\":\"die for the first time\",\"Points\":0,\"Author\":\"matisteve\",\"Modified\":1570325049,\"Created\":1570325049,\"BadgeName\":\"92297\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/92297.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/92297_lock.png\"},{\"ID\":85977,\"MemAddr\":\"0xH000071=9\",\"Title\":\"I failed you, my princess\",\"Description\":\"die for the first time\",\"Points\":0,\"Author\":\"matisteveTWO\",\"Modified\":1571010788,\"Created\":1571010788,\"BadgeName\":\"92297\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/92297.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/92297_lock.png\"},{\"ID\":102935,\"MemAddr\":\"0xH000dbf=99\",\"Title\":\"A Man's Worth of Coins\",\"Description\":\"Have 99 coins. Simple enough.\",\"Points\":2,\"Author\":\"ApplemunchRA\",\"Modified\":1584212198,\"Created\":1584212198,\"BadgeName\":\"00000\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00000.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00000_lock.png\"},{\"ID\":113670,\"MemAddr\":\"0xH000065=51_Q:0xH0013bf=7\",\"Title\":\"Hey, look There's a shortcut here!\",\"Description\":\"Find the secret room on #2 Castle\",\"Points\":5,\"Author\":\"Koakuma\",\"Modified\":1591759773,\"Created\":1591759773,\"BadgeName\":\"124010\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/124010.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/124010_lock.png\"},{\"ID\":128311,\"MemAddr\":\"0xH000f2c>=252\",\"Title\":\"Wellcome\",\"Description\":\"Open Super Mario Word\",\"Points\":1,\"Author\":\"freeman016\",\"Modified\":1602001511,\"Created\":1602001511,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/00080_lock.png\"},{\"ID\":128312,\"MemAddr\":\"0xH000f2c>=252\",\"Title\":\"Wellcome\",\"Description\":\"Open the game\",\"Points\":1,\"Author\":\"freeman016\",\"Modified\":1602004389,\"Created\":1602004389,\"BadgeName\":\"141067\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/141067.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/141067_lock.png\"},{\"ID\":342,\"MemAddr\":\"0xH000dc1=1_0xH0013bf>0\",\"Title\":\"Giddy Up!\",\"Description\":\"Catch a ride with a friend\",\"Points\":1,\"Author\":\"Scott\",\"Modified\":1561707447,\"Created\":1367266931,\"BadgeName\":\"46580\",\"Flags\":3,\"Type\":null,\"Rarity\":94.83,\"RarityHardcore\":46.63,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46580.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46580_lock.png\"},{\"ID\":341,\"MemAddr\":\"0x 001420=5\",\"Title\":\"Unleash The Dragon\",\"Description\":\"Collect 5 Dragon Coins in a level\",\"Points\":2,\"Author\":\"Scott\",\"Modified\":1561707451,\"Created\":1367266583,\"BadgeName\":\"46591\",\"Flags\":3,\"Type\":null,\"Rarity\":84.33,\"RarityHardcore\":42.96,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46591.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46591_lock.png\"},{\"ID\":340,\"MemAddr\":\"0xH000dbf=99\",\"Title\":\"Rich Mario (demoted)\",\"Description\":\"Collect 99 coins\",\"Points\":0,\"Author\":\"Scott\",\"Modified\":1504474537,\"Created\":1367254976,\"BadgeName\":\"46582\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46582.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46582_lock.png\"},{\"ID\":4874,\"MemAddr\":\"0xH000019=2\",\"Title\":\"I Believe I Can Fly\",\"Description\":\"Collect a feather\",\"Points\":1,\"Author\":\"UNHchabo\",\"Modified\":1564447174,\"Created\":1391908064,\"BadgeName\":\"46577\",\"Flags\":3,\"Type\":null,\"Rarity\":60.99,\"RarityHardcore\":32.97,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46577.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46577_lock.png\"},{\"ID\":2251,\"MemAddr\":\"0xH0013f3=1_0xH000dda!=0\",\"Title\":\"Another Kind of Flying\",\"Description\":\"Collect a P-Balloon\",\"Points\":2,\"Author\":\"Jaarl\",\"Modified\":1564448705,\"Created\":1376615078,\"BadgeName\":\"47083\",\"Flags\":3,\"Type\":null,\"Rarity\":34.48,\"RarityHardcore\":20.7,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47083.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47083_lock.png\"},{\"ID\":4933,\"MemAddr\":\"0xH0018c2=1\",\"Title\":\"Floating Through The Clouds\",\"Description\":\"Hijack a Lakitu's cloud\",\"Points\":2,\"Author\":\"UNHchabo\",\"Modified\":1564450921,\"Created\":1392010935,\"BadgeName\":\"46571\",\"Flags\":3,\"Type\":null,\"Rarity\":22.91,\"RarityHardcore\":14.7,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46571.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46571_lock.png\"},{\"ID\":1706,\"MemAddr\":\"0xH001900=80\",\"Title\":\"Maximum Finish\",\"Description\":\"Cross the finish line at the end of the stage and collect the max 50 stars\",\"Points\":5,\"Author\":\"jackolantern\",\"Modified\":1561707461,\"Created\":1372674230,\"BadgeName\":\"47084\",\"Flags\":3,\"Type\":null,\"Rarity\":13.06,\"RarityHardcore\":8.48,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47084.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47084_lock.png\"},{\"ID\":2246,\"MemAddr\":\"0xH000f31=0.20._R:0xH000f31!=0_0xH000f32=0.20._R:0xH000f32!=0_0xH000f33=0.20._R:0xH000f33!=0_0xH000dbe>d0xH000dbe.8._0xH001411=1.20._R:0xH001411=0\",\"Title\":\"Perfect Bonus Stage\",\"Description\":\"Score 8 extra lives in the 'Bonus Game'\",\"Points\":10,\"Author\":\"Jaarl\",\"Modified\":1561707465,\"Created\":1376582613,\"BadgeName\":\"47085\",\"Flags\":3,\"Type\":null,\"Rarity\":12.36,\"RarityHardcore\":7.14,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47085.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47085_lock.png\"},{\"ID\":46003,\"MemAddr\":\"0xH0018da=106_0xH0018e3=10_0xH001411=1S0xH0016e1=13S0xH0016e2=13S0xH0016e3=13S0xH0016e4=13S0xH0016e5=13S0xH0016e6=13\",\"Title\":\"Yoshi's a Mommy?\",\"Description\":\"Help Yoshi lay a cloud egg and get 10 happy coins to summon a 1up\",\"Points\":5,\"Author\":\"kdecks\",\"Modified\":1603575778,\"Created\":1489073051,\"BadgeName\":\"46668\",\"Flags\":3,\"Type\":null,\"Rarity\":8.86,\"RarityHardcore\":6.66,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46668.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46668_lock.png\"},{\"ID\":2253,\"MemAddr\":\"0xH0013bf=37_0xH000dd5=1\",\"Title\":\"I is for Icky Iggy\",\"Description\":\"Defeat Iggy Koopa of Castle #1\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1564806766,\"Created\":1376616356,\"BadgeName\":\"46598\",\"Flags\":3,\"Type\":\"progression\",\"Rarity\":62.79,\"RarityHardcore\":34.54,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46598.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46598_lock.png\"},{\"ID\":347,\"MemAddr\":\"0xH0013bf=7_0xH000dd5=1\",\"Title\":\"Morton Enough\",\"Description\":\"Defeat Morton Koopa Jr. of Castle #2\",\"Points\":5,\"Author\":\"Scott\",\"Modified\":1561707476,\"Created\":1367322700,\"BadgeName\":\"46599\",\"Flags\":3,\"Type\":null,\"Rarity\":36.4,\"RarityHardcore\":20.97,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46599.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46599_lock.png\"},{\"ID\":2261,\"MemAddr\":\"0xH0013bf=64_0xH000dd5=1\",\"Title\":\"Lemmy Down Slowly\",\"Description\":\"Defeat Lemmy Koopa of Castle #3\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1561707478,\"Created\":1376652522,\"BadgeName\":\"46600\",\"Flags\":3,\"Type\":null,\"Rarity\":24.01,\"RarityHardcore\":14.45,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46600.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46600_lock.png\"},{\"ID\":2262,\"MemAddr\":\"0xH0013bf=14_0xH000dd5=1\",\"Title\":\"Ludwig's Last Symphony\",\"Description\":\"Defeat Ludwig von Koopa of Castle #4\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1561707482,\"Created\":1376653163,\"BadgeName\":\"46601\",\"Flags\":3,\"Type\":null,\"Rarity\":22.74,\"RarityHardcore\":13.62,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46601.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46601_lock.png\"},{\"ID\":2306,\"MemAddr\":\"0xH0013bf=32_0xH000dd5=1\",\"Title\":\"Roy's Ploy Destroy-ed\",\"Description\":\"Defeat Roy Koopa of Castle #5\",\"Points\":10,\"Author\":\"Jaarl\",\"Modified\":1561707484,\"Created\":1376938808,\"BadgeName\":\"46602\",\"Flags\":3,\"Type\":null,\"Rarity\":17.77,\"RarityHardcore\":10.93,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46602.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46602_lock.png\"},{\"ID\":2307,\"MemAddr\":\"0xH0013bf=31.20._0xH000906=1.20._R:0xH001411!=1\",\"Title\":\"Reznor Again? (demoted)\",\"Description\":\"Defeat the Reznor in the clearing of the Forest of Illusion\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474541,\"Created\":1376938811,\"BadgeName\":\"46597\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597_lock.png\"},{\"ID\":2309,\"MemAddr\":\"0xH000906=1.400._R:0xH0013bf!=26\",\"Title\":\"Wendy Chips Are Down\",\"Description\":\"Defeat Wendy O. Koopa of Castle #6\",\"Points\":10,\"Author\":\"Jaarl\",\"Modified\":1561707487,\"Created\":1376939582,\"BadgeName\":\"46603\",\"Flags\":3,\"Type\":null,\"Rarity\":15.19,\"RarityHardcore\":9.48,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46603.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46603_lock.png\"},{\"ID\":2308,\"MemAddr\":\"0xH0013bf=27.20._0xH000906=1.20._R:0xH001411!=1\",\"Title\":\"Reznor, Do You Ever Give Up? (dmt)\",\"Description\":\"(demoted) Defeat the Reznor at the center of Chocolate Island\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474546,\"Created\":1376938815,\"BadgeName\":\"46597\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597_lock.png\"},{\"ID\":2342,\"MemAddr\":\"0xH0013bf=52_0xH000dd5=1\",\"Title\":\"Larry in the Airy\",\"Description\":\"Defeat Larry Koopa of Castle #7\",\"Points\":10,\"Author\":\"Jaarl\",\"Modified\":1561707489,\"Created\":1376970283,\"BadgeName\":\"46604\",\"Flags\":3,\"Type\":null,\"Rarity\":13,\"RarityHardcore\":8.27,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46604.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46604_lock.png\"},{\"ID\":2275,\"MemAddr\":\"0xH0013f9=3_0xH0013ef=1_O:0xH0013bf=50_0xH0013bf=49\",\"Title\":\"Bowser Disposer\",\"Description\":\"Beat Bowser and save the princess\",\"Points\":25,\"Author\":\"Jaarl\",\"Modified\":1571582041,\"Created\":1376742802,\"BadgeName\":\"46760\",\"Flags\":3,\"Type\":\"win_condition\",\"Rarity\":16.14,\"RarityHardcore\":10.13,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46760.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46760_lock.png\"},{\"ID\":2338,\"MemAddr\":\"0xH0013bf=53.20._0xH000906=1.20._R:0xH001411!=1\",\"Title\":\"Reznor... (demoted)\",\"Description\":\"Defeat the Reznor in the Valley of Bowser\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474549,\"Created\":1376969412,\"BadgeName\":\"46597\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597_lock.png\"},{\"ID\":2299,\"MemAddr\":\"0xH001f11=0_0xH0013bf=19_0xH000dd5=2_0xH000dda=0\",\"Title\":\"Shoo! Shoo! Big Boo\",\"Description\":\"Find and defeat the hidden Big Boo in Donut Secret House\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1564459621,\"Created\":1376918022,\"BadgeName\":\"46596\",\"Flags\":3,\"Type\":null,\"Rarity\":24.52,\"RarityHardcore\":16.54,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46596.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46596_lock.png\"},{\"ID\":2250,\"MemAddr\":\"0xH0013bf=11_0xH000dd5=1\",\"Title\":\"Reznor - Flaming Wheel of Death\",\"Description\":\"Defeat the Reznor atop Vanilla Dome\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1575640223,\"Created\":1376615073,\"BadgeName\":\"46597\",\"Flags\":3,\"Type\":null,\"Rarity\":19.72,\"RarityHardcore\":12.66,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46597_lock.png\"},{\"ID\":2276,\"MemAddr\":\"0xH0013bf=49_R:0xH0013bf!=49_0xH0013f9=3_R:0xH0013f9!=3_0xH0013ef=1_R:0xH0013ef!=1_0xH000019=0_R:0xH000019!=0\",\"Title\":\"Baby's First Kiss (demoted)\",\"Description\":\"Get the princess kiss as Little Mario (Front Door!)\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474554,\"Created\":1376742805,\"BadgeName\":\"46592\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46592.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46592_lock.png\"},{\"ID\":2277,\"MemAddr\":\"T:0xH0013f9=3_T:0xH0013ef=1_0xH000019=3_T:0xH0003da=131_0xH0013bf=49\",\"Title\":\"Burning Bowser\",\"Description\":\"Get the princess kiss as Fire Mario (Front Door!)\",\"Points\":10,\"Author\":\"Jaarl\",\"Modified\":1625960412,\"Created\":1376742808,\"BadgeName\":\"46593\",\"Flags\":3,\"Type\":null,\"Rarity\":6.07,\"RarityHardcore\":4.85,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46593.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46593_lock.png\"},{\"ID\":2278,\"MemAddr\":\"0xH0013bf=49_R:0xH0013bf!=49_0xH0013f9=3_R:0xH0013f9!=3_0xH0013ef=1_R:0xH0013ef!=1_0xH000019=2_R:0xH000019!=2\",\"Title\":\"Flying Finish (demoted)\",\"Description\":\"Get the princess kiss as Cape Mario (Front Door!)\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474556,\"Created\":1376742811,\"BadgeName\":\"46594\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46594.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46594_lock.png\"},{\"ID\":2345,\"MemAddr\":\"0xH0013ef=1_0xH0013f9=3_0xH0013bf=50\",\"Title\":\"Backdooring Bowser (demoted)\",\"Description\":\"Beat Bowser through the Back Door\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474560,\"Created\":1376973534,\"BadgeName\":\"47082\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47082.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47082_lock.png\"},{\"ID\":2199,\"MemAddr\":\"A:d0xH001f28_A:d0xH001f27_A:d0xH001f29_d0xH001f2a=3_A:0xH001f28_A:0xH001f27_A:0xH001f29_0xH001f2a=4_C:0xH001f27=1.1._C:0xH001f28=1.1._C:0xH001f29=1.1._C:0xH001f2a=1.1._M:0!=0.4._R:0x 001f28112_0xH000007<128_0xH000008>112_0xH000008<128\",\"Title\":\"I Could've Sworn... (demoted)\",\"Description\":\"Get lost in the Forest of Illusion\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474567,\"Created\":1376657732,\"BadgeName\":\"47070\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47070.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47070_lock.png\"},{\"ID\":2305,\"MemAddr\":\"0xH00001e=169_0xH000020=28_0xH001f11=0_0xH000dda=0_0xH0013d4=0\",\"Title\":\"Chocolate Donut\",\"Description\":\"Walk in a circle on Chocolate Island\",\"Points\":1,\"Author\":\"Jaarl\",\"Modified\":1571622934,\"Created\":1376938805,\"BadgeName\":\"47071\",\"Flags\":3,\"Type\":null,\"Rarity\":15.14,\"RarityHardcore\":9.61,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47071.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47071_lock.png\"},{\"ID\":2298,\"MemAddr\":\"0xH0013c3=6\",\"Title\":\"To the Stars!\",\"Description\":\"Reach the Star Road\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1504475594,\"Created\":1376918019,\"BadgeName\":\"47072\",\"Flags\":3,\"Type\":null,\"Rarity\":28.61,\"RarityHardcore\":18.04,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47072.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47072_lock.png\"},{\"ID\":2300,\"MemAddr\":\"0xH0013c3=5\",\"Title\":\"Mario's Special Place\",\"Description\":\"Get to the challenging Special Zone\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1504475595,\"Created\":1376918026,\"BadgeName\":\"47073\",\"Flags\":3,\"Type\":null,\"Rarity\":16.61,\"RarityHardcore\":11.08,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47073.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47073_lock.png\"},{\"ID\":2302,\"MemAddr\":\"N:d0xM001eea=0_0xM001eea=1.1._d0xH001f11=5_N:0xH001eec!=d0xH001eec_N:0xH001eed!=d0xH001eed_R:0xH001eee!=d0xH001eeeS0xH001f11=1S0xH001f11=6\",\"Title\":\"Change of Scenery\",\"Description\":\"Clear the Special Zone and change the seasons in Dinosaur Land\",\"Points\":25,\"Author\":\"Jaarl\",\"Modified\":1599744063,\"Created\":1376929303,\"BadgeName\":\"47074\",\"Flags\":3,\"Type\":null,\"Rarity\":10.71,\"RarityHardcore\":7.15,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47074.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47074_lock.png\"},{\"ID\":2304,\"MemAddr\":\"R:0xH001f2e=1.1._C:0xH001f2e>=2.1._C:0xH001f2e>=3.1._C:0xH001f2e>=4.1._C:0xH001f2e>=5.1._C:0xH001f2e>=6.1._C:0xH001f2e>=7.1._C:0xH001f2e>=8.1._C:0xH001f2e>=9.1._C:0xH001f2e>=10.1._C:0xH001f2e>=11.1._C:0xH001f2e>=12.1._C:0xH001f2e>=13.1._C:0xH001f2e>=14.1._C:0xH001f2e>=15.1._C:0xH001f2e>=16.1._C:0xH001f2e>=17.1._C:0xH001f2e>=18.1._C:0xH001f2e>=19.1._C:0xH001f2e>=20.1._C:0xH001f2e>=21.1._C:0xH001f2e>=22.1._C:0xH001f2e>=23.1._C:0xH001f2e>=24.1._C:0xH001f2e>=25.1._C:0xH001f2e>=26.1._C:0xH001f2e>=27.1._C:0xH001f2e>=28.1._C:0xH001f2e>=29.1._C:0xH001f2e>=30.1._C:0xH001f2e>=31.1._C:0xH001f2e>=32.1._C:0xH001f2e>=33.1._C:0xH001f2e>=34.1._C:0xH001f2e>=35.1._C:0xH001f2e>=36.1._C:0xH001f2e>=37.1._C:0xH001f2e>=38.1._C:0xH001f2e>=39.1._C:0xH001f2e>=40.1._C:0xH001f2e>=41.1._C:0xH001f2e>=42.1._C:0xH001f2e>=43.1._C:0xH001f2e>=44.1._C:0xH001f2e>=45.1._C:0xH001f2e>=46.1._C:0xH001f2e>=47.1._C:0xH001f2e>=48.1._C:0xH001f2e>=49.1._C:0xH001f2e>=50.1._C:0xH001f2e>=51.1._C:0xH001f2e>=52.1._C:0xH001f2e>=53.1._C:0xH001f2e>=54.1._C:0xH001f2e>=55.1._C:0xH001f2e>=56.1._C:0xH001f2e>=57.1._C:0xH001f2e>=58.1._C:0xH001f2e>=59.1._C:0xH001f2e>=60.1._C:0xH001f2e>=61.1._C:0xH001f2e>=62.1._C:0xH001f2e>=63.1._C:0xH001f2e>=64.1._C:0xH001f2e>=65.1._C:0xH001f2e>=66.1._C:0xH001f2e>=67.1._C:0xH001f2e>=68.1._C:0xH001f2e>=69.1._C:0xH001f2e>=70.1._C:0xH001f2e>=71.1._C:0xH001f2e>=72.1._C:0xH001f2e>=73.1._C:0xH001f2e>=74.1._C:0xH001f2e>=75.1._C:0xH001f2e>=76.1._C:0xH001f2e>=77.1._C:0xH001f2e>=78.1._C:0xH001f2e>=79.1._C:0xH001f2e>=80.1._C:0xH001f2e>=81.1._C:0xH001f2e>=82.1._C:0xH001f2e>=83.1._C:0xH001f2e>=84.1._C:0xH001f2e>=85.1._C:0xH001f2e>=86.1._C:0xH001f2e>=87.1._C:0xH001f2e>=88.1._C:0xH001f2e>=89.1._C:0xH001f2e>=90.1._C:0xH001f2e>=91.1._C:0xH001f2e>=92.1._C:0xH001f2e>=93.1._C:0xH001f2e>=94.1._C:0xH001f2e>=95.1._C:0xH001f2e=96.1._M:0!=0.96.\",\"Title\":\"All Exits\",\"Description\":\"100% clear the game\",\"Points\":50,\"Author\":\"Jaarl\",\"Modified\":1625960414,\"Created\":1376929308,\"BadgeName\":\"46761\",\"Flags\":3,\"Type\":null,\"Rarity\":7.49,\"RarityHardcore\":5.74,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46761.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46761_lock.png\"},{\"ID\":2985,\"MemAddr\":\"0x 0018f2=3598_0xH0013bf=31\",\"Title\":\"The Investigator\",\"Description\":\"Access a secret area in the Forest Fortress\",\"Points\":5,\"Author\":\"mrvsonic87\",\"Modified\":1504475601,\"Created\":1379656738,\"BadgeName\":\"47075\",\"Flags\":3,\"Type\":null,\"Rarity\":9.14,\"RarityHardcore\":6.24,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47075.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47075_lock.png\"},{\"ID\":2303,\"MemAddr\":\"0xH001f11=5.7200._R:0xH001f11!=5_R:0xH000100<13_R:0xH000100>14\",\"Title\":\"That Oh-So-Familiar Tune\",\"Description\":\"Find the secret in the Special Zone\",\"Points\":2,\"Author\":\"Jaarl\",\"Modified\":1626262445,\"Created\":1376929305,\"BadgeName\":\"46563\",\"Flags\":3,\"Type\":null,\"Rarity\":9.1,\"RarityHardcore\":6.95,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46563.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46563_lock.png\"},{\"ID\":383324,\"MemAddr\":\"N:d0xH000100=19_N:0xH000100=20_O:0xH0013cf=64_0xH000071=10S0xH0013bf=37_0xT001ec7=1S0xH0013bf=7_0xT001ea9=1S0xH0013bf=64_0xT001ee2=1S0xH0013bf=14_0xT001eb0=1S0xH0013bf=32_0xT001ec2=1S0xH0013bf=26_0xT001ebc=1S0xH0013bf=52_0xT001ed6=1S0xH0013bf=11_0xT001ead=1S0xH0013bf=31_0xT001ec1=1S0xH0013bf=27_0xT001ebd=1S0xH0013bf=53_0xT001ed7=1\",\"Title\":\"Entering the Castle Ruins\",\"Description\":\"Reenter a Castle after it was destroyed by pressing L + R.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1717162497,\"Created\":1703596667,\"BadgeName\":\"431957\",\"Flags\":3,\"Type\":null,\"Rarity\":4.45,\"RarityHardcore\":3.51,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431957.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431957_lock.png\"},{\"ID\":45901,\"MemAddr\":\"R:0xH0013bf!=39_R:0xH000dc0!=0_R:0x 00001e<2032_R:0x 00001e>2128Sd0xH0016e6!=13_0xH0016e6=13Sd0xH0016e5!=13_0xH0016e5=13Sd0xH0016e4!=13_0xH0016e4=13Sd0xH0016e3!=13_0xH0016e3=13Sd0xH0016e2!=13_0xH0016e2=13Sd0xH0016e1!=13_0xH0016e1=13\",\"Title\":\"Bonus on the Island\",\"Description\":\"Obtain 1-up from Bonus Block in Yoshi's Island 3\",\"Points\":5,\"Author\":\"Salsa\",\"Modified\":1576437920,\"Created\":1488942160,\"BadgeName\":\"46551\",\"Flags\":3,\"Type\":null,\"Rarity\":13.13,\"RarityHardcore\":9.13,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46551.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46551_lock.png\"},{\"ID\":45902,\"MemAddr\":\"R:0xH0013bf!=5_R:0xH000dc0!=0_R:0x 00001e<2224_R:0x 00001e>2352Sd0xH0016e6!=13_0xH0016e6=13Sd0xH0016e5!=13_0xH0016e5=13Sd0xH0016e4!=13_0xH0016e4=13Sd0xH0016e3!=13_0xH0016e3=13Sd0xH0016e2!=13_0xH0016e2=13Sd0xH0016e1!=13_0xH0016e1=13\",\"Title\":\"Bonus of the Donuts\",\"Description\":\"Obtain 1-up from Bonus Block in Donut Plains 3\",\"Points\":5,\"Author\":\"Salsa\",\"Modified\":1576437924,\"Created\":1488942165,\"BadgeName\":\"46552\",\"Flags\":3,\"Type\":null,\"Rarity\":13.46,\"RarityHardcore\":9.32,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46552.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46552_lock.png\"},{\"ID\":45903,\"MemAddr\":\"R:0xH0013bf!=12_R:0xH000dc0!=0_R:0x 00001e<1404_R:0x 00001e>1404_R:0xH000094<192_R:0xH00009c!=22Sd0xH0016e6!=13_0xH0016e6=13Sd0xH0016e5!=13_0xH0016e5=13Sd0xH0016e4!=13_0xH0016e4=13Sd0xH0016e3!=13_0xH0016e3=13Sd0xH0016e2!=13_0xH0016e2=13Sd0xH0016e1!=13_0xH0016e1=13\",\"Title\":\"Bonus on the Butter\",\"Description\":\"Obtain 1-up from Bonus Block in Butter Bridge 1\",\"Points\":5,\"Author\":\"Salsa\",\"Modified\":1576437928,\"Created\":1488942170,\"BadgeName\":\"46553\",\"Flags\":3,\"Type\":null,\"Rarity\":5.48,\"RarityHardcore\":4.56,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46553.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46553_lock.png\"},{\"ID\":45904,\"MemAddr\":\"R:0xH0013bf!=35_R:0xH000dc0!=0_R:0x 00001e<2160_R:0x 00001e>2304Sd0xH0016e6!=13_0xH0016e6=13Sd0xH0016e5!=13_0xH0016e5=13Sd0xH0016e4!=13_0xH0016e4=13Sd0xH0016e3!=13_0xH0016e3=13Sd0xH0016e2!=13_0xH0016e2=13Sd0xH0016e1!=13_0xH0016e1=13\",\"Title\":\"Bonus of the Chocolate\",\"Description\":\"Obtain 1-up from Bonus Block in Chocolate Island 3\",\"Points\":5,\"Author\":\"Salsa\",\"Modified\":1576437931,\"Created\":1488942175,\"BadgeName\":\"46554\",\"Flags\":3,\"Type\":null,\"Rarity\":11.29,\"RarityHardcore\":7.4,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46554.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46554_lock.png\"},{\"ID\":383328,\"MemAddr\":\"Q:0xH0013bf=38_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=509428\",\"Title\":\"Secrets of the Cheep Cheeps\",\"Description\":\"Trigger the hidden 1-Up in Yoshi's Island 4.\",\"Points\":3,\"Author\":\"SporyTike\",\"Modified\":1703596668,\"Created\":1703596668,\"BadgeName\":\"431961\",\"Flags\":3,\"Type\":null,\"Rarity\":2.18,\"RarityHardcore\":1.87,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431961.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431961_lock.png\"},{\"ID\":383325,\"MemAddr\":\"Q:0xH0013bf=21_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=509653\",\"Title\":\"Secrets of the Chucks\",\"Description\":\"Trigger the hidden 1-Up in Donut Plains 1.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596667,\"Created\":1703596667,\"BadgeName\":\"431958\",\"Flags\":3,\"Type\":null,\"Rarity\":2.2,\"RarityHardcore\":1.89,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431958.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431958_lock.png\"},{\"ID\":383326,\"MemAddr\":\"Q:0xH0013bf=6_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=510020\",\"Title\":\"Secrets of the Galoombas\",\"Description\":\"Trigger the hidden 1-Up in Donut Plains 4.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596667,\"Created\":1703596667,\"BadgeName\":\"431959\",\"Flags\":3,\"Type\":null,\"Rarity\":2.07,\"RarityHardcore\":1.79,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431959.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431959_lock.png\"},{\"ID\":383332,\"MemAddr\":\"Q:0xH0013bf=7_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=510246\",\"Title\":\"Secrets of Morton\",\"Description\":\"Trigger the hidden 1-Up in #2 Morton's Castle.\",\"Points\":1,\"Author\":\"SporyTike\",\"Modified\":1703596668,\"Created\":1703596668,\"BadgeName\":\"431965\",\"Flags\":3,\"Type\":null,\"Rarity\":2.5,\"RarityHardcore\":2.06,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431965.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431965_lock.png\"},{\"ID\":383333,\"MemAddr\":\"Q:0xH0013bf=43_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=510940\",\"Title\":\"Secrets of the Boos\",\"Description\":\"Trigger the hidden 1-Up in Vanilla Ghost House.\",\"Points\":3,\"Author\":\"SporyTike\",\"Modified\":1703596669,\"Created\":1703596669,\"BadgeName\":\"431966\",\"Flags\":3,\"Type\":null,\"Rarity\":2.04,\"RarityHardcore\":1.77,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431966.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431966_lock.png\"},{\"ID\":383335,\"MemAddr\":\"Q:0xH0013bf=61_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=511188\",\"Title\":\"Secrets of the Bullet Bills\",\"Description\":\"Trigger the hidden 1-Up in Vanilla Dome 4.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596675,\"Created\":1703596675,\"BadgeName\":\"431974\",\"Flags\":3,\"Type\":null,\"Rarity\":2.03,\"RarityHardcore\":1.74,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431974.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431974_lock.png\"},{\"ID\":383331,\"MemAddr\":\"Q:0xH0013bf=11_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=511821\",\"Title\":\"Secrets of the Fishbones\",\"Description\":\"Trigger the hidden 1-Up in Vanilla Fortress.\",\"Points\":3,\"Author\":\"SporyTike\",\"Modified\":1703596668,\"Created\":1703596668,\"BadgeName\":\"431964\",\"Flags\":3,\"Type\":null,\"Rarity\":1.86,\"RarityHardcore\":1.63,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431964.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431964_lock.png\"},{\"ID\":383327,\"MemAddr\":\"Q:0xH0013bf=16_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=512067\",\"Title\":\"Secrets of the Sumo Bros\",\"Description\":\"Trigger the hidden 1-Up in Cookie Mountain.\",\"Points\":1,\"Author\":\"SporyTike\",\"Modified\":1703596668,\"Created\":1703596668,\"BadgeName\":\"431960\",\"Flags\":3,\"Type\":null,\"Rarity\":4.05,\"RarityHardcore\":2.84,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431960.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431960_lock.png\"},{\"ID\":383336,\"MemAddr\":\"Q:0xH0013bf=71_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=513093\",\"Title\":\"Secrets of the Bubbles\",\"Description\":\"Trigger the hidden 1-Up in Forest of Illusion 3.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596675,\"Created\":1703596675,\"BadgeName\":\"431975\",\"Flags\":3,\"Type\":null,\"Rarity\":1.89,\"RarityHardcore\":1.65,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431975.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431975_lock.png\"},{\"ID\":46332,\"MemAddr\":\"R:0xH0013bf!=46_R:0x 000094<4194_R:0x 000094>4306_0x 000096>=276_0xH000013>d0xH000013.240._P:0xH0013d4=1S0xH0000a6=2_P:0xH0014d0!=8S0xH0000a5=2_P:0xH0014d1!=8\",\"Title\":\"Suicide Prevention (moved to bonus)\",\"Description\":\"On 3-3 prevent kicking koopa by checkpoint from dying. Stay by him.\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1505703949,\"Created\":1489701538,\"BadgeName\":\"47077\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47077.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47077_lock.png\"},{\"ID\":383330,\"MemAddr\":\"Q:0xH0013bf=36_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=514026\",\"Title\":\"Secrets of Chocolate\",\"Description\":\"Trigger the hidden 1-Up in Chocolate Island 2.\",\"Points\":3,\"Author\":\"SporyTike\",\"Modified\":1703596668,\"Created\":1703596668,\"BadgeName\":\"431963\",\"Flags\":3,\"Type\":null,\"Rarity\":1.75,\"RarityHardcore\":1.56,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431963.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431963_lock.png\"},{\"ID\":46061,\"MemAddr\":\"R:0xH0013bf!=1_R:0xH001411=0_R:0xH000f31=1_0xH000dbe>d0xH000dbe.50.\",\"Title\":\"Silver Worth More than Gold (moved to bonus)\",\"Description\":\"On Vanilla Secret 2 Get 50 1ups with 200 or more on the clock\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474573,\"Created\":1489170843,\"BadgeName\":\"46762\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46762.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46762_lock.png\"},{\"ID\":383329,\"MemAddr\":\"Q:0xH0013bf=26_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=515003\",\"Title\":\"Secrets of Wendy\",\"Description\":\"Trigger the hidden 1-Up in #6 Wendy's Castle.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596668,\"Created\":1703596668,\"BadgeName\":\"431962\",\"Flags\":3,\"Type\":null,\"Rarity\":2.22,\"RarityHardcore\":1.8,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431962.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431962_lock.png\"},{\"ID\":46008,\"MemAddr\":\"R:0xH0013bf!=15_R:0xH000019!=3_0xH000095=1.1._R:0xH001411=0_0x 000094>=2288_0x 000096>=341_R:0xH00187a=1_R:0xO000077=1\",\"Title\":\"Inferno Tornado (moved to bonus)\",\"Description\":\"As Fire Mario in Cheese Bridge cross 1st gap with only spin jump. No platforms\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474576,\"Created\":1489073077,\"BadgeName\":\"46606\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46606.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46606_lock.png\"},{\"ID\":383340,\"MemAddr\":\"Q:0xH0013bf=57_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=515507_Q:0x 000094<2048\",\"Title\":\"Secrets of the Moving Walls\",\"Description\":\"Trigger the first hidden 1-Up in Valley of Bowser 2.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596683,\"Created\":1703596683,\"BadgeName\":\"431979\",\"Flags\":3,\"Type\":null,\"Rarity\":1.68,\"RarityHardcore\":1.48,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431979.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431979_lock.png\"},{\"ID\":46002,\"MemAddr\":\"R:0xH0013bf!=14_0xH00c685>=204_0xH00c685<=205_0xH0017c3=2.11._P:d0xH0017c3=2_R:0xH00c680=152_R:0xH000065!=59\",\"Title\":\"Surfing the Shell (moved to bonus)\",\"Description\":\"In #4 Ludwig's Castle bouce on Ludwig's shell 11 times before he leaps\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1516573766,\"Created\":1489073044,\"BadgeName\":\"46667\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46667.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46667_lock.png\"},{\"ID\":383337,\"MemAddr\":\"Q:0xH0013bf=57_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=515507_Q:0x 000094>2048\",\"Title\":\"Secrets of the Moving Walls II\",\"Description\":\"Trigger the second hidden 1-Up in Valley of Bowser 2.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596675,\"Created\":1703596675,\"BadgeName\":\"431976\",\"Flags\":3,\"Type\":null,\"Rarity\":1.58,\"RarityHardcore\":1.4,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431976.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431976_lock.png\"},{\"ID\":2263,\"MemAddr\":\"0xH001697=12_0xH0013bf=66_R:0xH001697=0\",\"Title\":\"Bother The Wigglers (moved to bonus)\",\"Description\":\"Jump on yellow Wigglers in Forest of Illusion 12 times in a row for a surprise!\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474582,\"Created\":1376655155,\"BadgeName\":\"46574\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46574.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46574_lock.png\"},{\"ID\":383339,\"MemAddr\":\"Q:0xH0013bf=52_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=516017\",\"Title\":\"Secrets of Larry\",\"Description\":\"Trigger the hidden 1-Up in #7 Larry's Castle.\",\"Points\":3,\"Author\":\"SporyTike\",\"Modified\":1703596679,\"Created\":1703596679,\"BadgeName\":\"431978\",\"Flags\":3,\"Type\":null,\"Rarity\":1.64,\"RarityHardcore\":1.45,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431978.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431978_lock.png\"},{\"ID\":383334,\"MemAddr\":\"Q:0xH0013bf=78_0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=517084\",\"Title\":\"Secrets of the Vines\",\"Description\":\"Trigger the hidden 1-Up in Gnarly.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596669,\"Created\":1703596669,\"BadgeName\":\"431970\",\"Flags\":3,\"Type\":null,\"Rarity\":1.87,\"RarityHardcore\":1.62,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431970.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431970_lock.png\"},{\"ID\":46171,\"MemAddr\":\"R:0xH0018a7=50_R:0xH00009c=13_R:0xH00005a!=254_0xH0000d2=0.10._0xH0000d2=6_0xH0000d1>=213_0xH0000d1<=242\",\"Title\":\"These Platforms are so Slow... (demoted)\",\"Description\":\"Arrive at the First Door of Larry's Castle without Activating the Platform\",\"Points\":0,\"Author\":\"GalacticSpear\",\"Modified\":1504474585,\"Created\":1489319085,\"BadgeName\":\"46770\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46770.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46770_lock.png\"},{\"ID\":46006,\"MemAddr\":\"0xH000dba=4_0xH000dc1>=1_0xH000f2d!=d0xH000f2d.10._R:0xH0018ac=0_R:0xH0013bf!=28_R:0xH001411=0_R:0x 000094>1680\",\"Title\":\"A Trip to Pound Town (dmt, fals pos)\",\"Description\":\"Pound 10* spinys hard on 6-5 with Y. Yoshi. Don't spit or swallow til the end\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474587,\"Created\":1489073069,\"BadgeName\":\"46670\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46670.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46670_lock.png\"},{\"ID\":45909,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>90_P:0xH000dd5<1_P:0xH000dd5>2_R:d0x 000f34>0x 000f34_0xH0013bf=42.1._0xH0013bf=41.1._0xH0013bf=39.1._0xH0013bf=38.1._0xH0013bf=37.1.\",\"Title\":\"Mario vs. the Score I (Yoshi's Island) (demoted)\",\"Description\":\"Score 900 points or less playing through all stages in Yoshi Island\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474594,\"Created\":1488942350,\"BadgeName\":\"46713\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46713.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46713_lock.png\"},{\"ID\":45910,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>100_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x 000f34_0xH0013bf=21.1._0xH0013bf=9.1._0xH0013bf=5.1._0xH0013bf=6.1._0xH0013bf=10.1._0xH0013bf=47.1._0xH0013bf=19.1._0xH0013bf=4.1._0xH0013bf=7.1.\",\"Title\":\"Mario vs. the Score II (Donut Plains) (demoted)\",\"Description\":\"Score 1000 points or less playing through all stages in Donut Plains\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474597,\"Created\":1488942354,\"BadgeName\":\"46714\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46714.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46714_lock.png\"},{\"ID\":45911,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>301_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x 000f34_0xH0013bf=62.1._0xH0013bf=60.1._0xH0013bf=61.1._0xH0013bf=43.1._0xH0013bf=64.1._0xH0013bf=1.1._0xH0013bf=2.1._0xH0013bf=11.1._0xH0013bf=45.1._0xH0013bf=46.1.\",\"Title\":\"Mario vs. the Score III (Vanilla Dome) (demoted)\",\"Description\":\"Score 3010 points or less playing through all stages in Vanilla Dome\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474603,\"Created\":1488942359,\"BadgeName\":\"46715\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46715.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46715_lock.png\"},{\"ID\":45912,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>110_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x 000f34_0xH0013bf=12.1._0xH0013bf=13.1._0xH0013bf=15.1._0xH0013bf=17.1._0xH0013bf=16.1._0xH0013bf=14.1.\",\"Title\":\"Mario vs. the Score IV (Twin Bridges) (demoted)\",\"Description\":\"Score 1100 points or less playing through all stages in Twin Bridges\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474609,\"Created\":1488942364,\"BadgeName\":\"46716\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46716.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46716_lock.png\"},{\"ID\":45913,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>95_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x 000f34_0xH0013bf=66.1._0xH0013bf=68.1._0xH0013bf=71.1._0xH0013bf=67.1._0xH0013bf=65.1._0xH0013bf=70.1._0xH0013bf=31.1._0xH0013bf=32.1.\",\"Title\":\"Mario vs. the Score V (Forest of Illusion) (demoted)\",\"Description\":\"Score 950 points or less playing through all stages in Forest of Illusion\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474613,\"Created\":1488942370,\"BadgeName\":\"46717\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46717.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46717_lock.png\"},{\"ID\":46304,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>0_P:0xH000dd5<1_P:0xH000dd5>2_R:d0x 000f34>0x 000f34_0xH0013bf=21.1._0xH0013bf=9.1._0xH0013bf=10.1._0xH0013bf=62.1._0xH0013bf=60.1._0xH0013bf=15.1.\",\"Title\":\"Loopholes by Keyholes - Donut Vanilla Butter (moved to bonus)\",\"Description\":\"Don't score any points reaching keyhole exits on stages in Worlds 2, 3 and 4\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474617,\"Created\":1489616634,\"BadgeName\":\"47079\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47079.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47079_lock.png\"},{\"ID\":45914,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>285_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x 000f34_0xH0013bf=34.1._0xH0013bf=36.1._0xH0013bf=35.1._0xH0013bf=29.1._0xH0013bf=28.1._0xH0013bf=59.1._0xH0013bf=33.1._0xH0013bf=27.1._0xH0013bf=26.1._0xH0013bf=24.1.\",\"Title\":\"Mario vs. the Score VI (Chocolate Island) (demoted)\",\"Description\":\"Score 2850 points or less playing through all stages in Chocolate Island\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474620,\"Created\":1488942396,\"BadgeName\":\"46718\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46718.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46718_lock.png\"},{\"ID\":46305,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>0_P:0xH000dd5<1_P:0xH000dd5>2_R:d0x 000f34>0x 000f34_0xH0013bf=66.1._0xH0013bf=68.1._0xH0013bf=67.1._0xH0013bf=36.1._0xH0013bf=57.1._0xH0013bf=56.1._0xH0013bf=51.1.\",\"Title\":\"Loopholes by Keyholes - Chocolate Forest Bowser (bonus)\",\"Description\":\"Don't score any points reaching keyhole exits on stages in Worlds 4, 6 and 7\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474637,\"Created\":1489616639,\"BadgeName\":\"47080\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47080.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47080_lock.png\"},{\"ID\":45915,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>95_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x 000f34_0xH0013bf=58.1._0xH0013bf=57.1._0xH0013bf=55.1._0xH0013bf=51.1._0xH0013bf=56.1._0xH0013bf=52.1._0xH0013bf=53.1._0xH0013bf<=50_0xH0013bf>=49_0xH0013ef=1_0xH0013f9=3\",\"Title\":\"Mario vs. the Score VII (Valley of Bowser) (demoted)\",\"Description\":\"Score 950 points or less playing through all stages in Valley of Bowser\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474640,\"Created\":1488942402,\"BadgeName\":\"46719\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46719.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46719_lock.png\"},{\"ID\":45916,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>110_P:0xH000dd5>2_R:d0x 000f34>0x 000f34_0xH0013bf=88.1._0xH0013bf=84.1._0xH0013bf=86.1._0xH0013bf=89.1._0xH0013bf=90.1._P:0xH000dd5<1\",\"Title\":\"Loopholes by Keyholes - Star World (moved to bonus)\",\"Description\":\"Score 1100 points or less playing through all stages in Star World\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474645,\"Created\":1488942407,\"BadgeName\":\"47081\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47081.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47081_lock.png\"},{\"ID\":46307,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=42.1._0xH0013bf=41.1._0xH0013bf=39.1._0xH0013bf=38.1._0xH0013bf=37_0x 0000ce=50896_R:0xH000db3=1_R:0xH000dd5=128_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0\",\"Title\":\"Super Pacifist Mario feat. Hungry Yoshi I (Yoshi Island) (bon)\",\"Description\":\"See achievement comments for details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474058,\"Created\":1489623727,\"BadgeName\":\"46908\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46908.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46908_lock.png\"},{\"ID\":45917,\"MemAddr\":\"R:0xH000db3=1_R:0x 000f34>100_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x 000f34_0xH0013bf=78.1._0xH0013bf=79.1._0xH0013bf=80.1._0xH0013bf=81.1._0xH0013bf=76.1._0xH0013bf=75.1._0xH0013bf=74.1._0xH0013bf=73.1.\",\"Title\":\"Mario vs. the Score IX (Special World) (demoted)\",\"Description\":\"Score 1000 points or less playing through all stages in Special World\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474649,\"Created\":1488942411,\"BadgeName\":\"46721\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46721.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46721_lock.png\"},{\"ID\":46308,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=21.1._0xH0013bf=9.1._0xH0013bf=5.1._0xH0013bf=6.1._0xH0013bf=10.1._0xH0013bf=47.1._0xH0013bf=19.1._0xH0013bf=4.1._0xH0013bf=7.1._0x 0000ce=51523_R:0xH000db3=1_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0\",\"Title\":\"Super Pacifist Marioi II (Donut Plains) (bonus)\",\"Description\":\"See achievement comments for details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474682,\"Created\":1489635441,\"BadgeName\":\"46909\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46909.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46909_lock.png\"},{\"ID\":46309,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=62.1._0xH0013bf=60.1._0xH0013bf=61.1._0xH0013bf=43.1._0xH0013bf=64_0xH0013bf=1.1._0xH0013bf=2.1._0xH0013bf=45.1._0x 0000ce=52672_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_R:0xH000db3=1_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0_P:d0xH0000a6=2\",\"Title\":\"Super Pacifist Mario III (Vanilla Dome) (bonus)\",\"Description\":\"See achievement comments for details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474749,\"Created\":1489635447,\"BadgeName\":\"46910\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46910.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46910_lock.png\"},{\"ID\":46310,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=12.1._0xH0013bf=15.1._0xH0013bf=17.1._0xH0013bf=16.1._0xH0013bf=14_0x 0000ce=53586_R:0xH000db3=1_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0_0xH0013bf=13.1.\",\"Title\":\"Super Pacifist Mario IV (Twin Bridges) (bonus)\",\"Description\":\"See achievement comments for details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474894,\"Created\":1489635455,\"BadgeName\":\"46911\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46911.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46911_lock.png\"},{\"ID\":46311,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=66.1._0xH0013bf=68.1._0xH0013bf=71.1._0xH0013bf=67.1._0xH0013bf=65.1._0xH0013bf=70.1._0xH0013bf=32_0x 0000ce=54557_R:0xH000db3=1_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0_P:0x 0000a2=30840\",\"Title\":\"Super Pacifist Mario V (Forest of Illusion) (moved to bonus)\",\"Description\":\"See achievement comments for details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474914,\"Created\":1489635462,\"BadgeName\":\"46912\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46912.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46912_lock.png\"},{\"ID\":46312,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=34.1._0xH0013bf=36.1._0xH0013bf=35.1._0xH0013bf=29.1._0xH0013bf=28.1._0xH0013bf=59.1._0xH0013bf=33.1._0xH0013bf=26_0xH0013bf=24.1._0x 0000ce=56354_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_R:0xH000db3=1_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0\",\"Title\":\"Super Pacifist Mario VI (Chocolate Island) (moved to bonus)\",\"Description\":\"See achievement comments for details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474928,\"Created\":1489635469,\"BadgeName\":\"46913\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46913.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46913_lock.png\"},{\"ID\":46306,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=57.1._0xH0013bf=55.1._0xH0013bf=51.1._0xH0013bf=56.1._0xH0013bf=49.1._0xH0013bf=58.1._0x 0000ce=57792_R:0xH000db3=1_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0\",\"Title\":\"Super Pacifist Mario VII (Valley of Bowser) (moved to bonus)\",\"Description\":\"See achievement comments for details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474937,\"Created\":1489623613,\"BadgeName\":\"46916\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46916.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46916_lock.png\"},{\"ID\":46313,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=88.1._0xH0013bf=84.1._0xH0013bf=86.1._0xH0013bf=89.1._0xH0013bf=90.1._0xH000dd5>0_R:0xH000db3=1_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_P:d0xH000dd5=1_P:d0xH000dd5=2_R:0xH0013cf=64_R:0xH000100=0\",\"Title\":\"Super Pacifist Mario VIII (Star World) (moved to bonus)\",\"Description\":\"See achievement comments for details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474946,\"Created\":1489635474,\"BadgeName\":\"46914\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46914.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46914_lock.png\"},{\"ID\":46314,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=78.1._0xH0013bf=79.1._0xH0013bf=80.1._0xH0013bf=76.1._0xH0013bf=75.1._0xH0013bf=74.1._0xH0013bf=73.1._0xH001dff=12_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_R:0xH000db3=1_R:0xH0013cf=64_R:0xH000100=0_0xH0013bf=81.1._P:d0xH000dd5=1_P:d0xH000dd5=2\",\"Title\":\"Super Pacifist Mario IX (Special World) (moved to bonus)\",\"Description\":\"See achievement comments for details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474957,\"Created\":1489635478,\"BadgeName\":\"46915\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46915.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46915_lock.png\"},{\"ID\":29653,\"MemAddr\":\"C:0xS001f34=1.1._C:0xR001f34=1.1._C:0xM001f33=1.1._C:0xN001f33=1.1._M:0!=0.4._0xS001f34=1_0xR001f34=1_0xM001f33=1_0xN001f33=1_0xH000008!=85_R:0xH001f33d0xH000dbe.8._R:0xH001411=0\",\"Title\":\"Fence Offense\",\"Description\":\"Get 8 1-ups at #1 Iggy's Castle with 230 or more on the time clock\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1625960433,\"Created\":1489073056,\"BadgeName\":\"46608\",\"Flags\":3,\"Type\":null,\"Rarity\":4.9,\"RarityHardcore\":4.24,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46608.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46608_lock.png\"},{\"ID\":46170,\"MemAddr\":\"N:0xH0013bf=4_N:d0xH000dda=0_0xH000dda=7.1._R:0xH0013e0=21_R:0xH000019=2_T:0xH000dd5=2_R:0xH0013bf!=4_R:0xH000dd5=128\",\"Title\":\"Who Needs Vines?\",\"Description\":\"Get the Normal Exit in Donut Ghost House without a Feather or Climbing on a Vine\",\"Points\":10,\"Author\":\"GalacticSpear\",\"Modified\":1625960437,\"Created\":1489319082,\"BadgeName\":\"46771\",\"Flags\":3,\"Type\":null,\"Rarity\":6.03,\"RarityHardcore\":5.08,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46771.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46771_lock.png\"},{\"ID\":46331,\"MemAddr\":\"0xH0000aa=64_0xH0014c8=2_R:0xH0013bf!=19_R:0xH00005a!=237\",\"Title\":\"Spooking Big Boo\",\"Description\":\"In the first room of Donut Secret House kill Big Boo\",\"Points\":5,\"Author\":\"kdecks\",\"Modified\":1571057401,\"Created\":1489701500,\"BadgeName\":\"47076\",\"Flags\":3,\"Type\":null,\"Rarity\":8.75,\"RarityHardcore\":7.06,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47076.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47076_lock.png\"},{\"ID\":46001,\"MemAddr\":\"R:0xH0013bf!=68_R:0xH000077>0_R:0xH000071=1_0xH000f31=3.1._R:0xH000071=9_T:0xH001dff=12ST:0xH001dff=12ST:0xH001dff=128\",\"Title\":\"Everything is Lava\",\"Description\":\"Get to the normal exit of Forest of Illusion 2 without touching any walls, enemies, blue blocks\",\"Points\":25,\"Author\":\"kdecks\",\"Modified\":1625960440,\"Created\":1489073039,\"BadgeName\":\"46666\",\"Flags\":3,\"Type\":null,\"Rarity\":3.44,\"RarityHardcore\":2.9,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46666.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46666_lock.png\"},{\"ID\":45997,\"MemAddr\":\"0xH001523=16_0xH001411=1_0xH000f31=3.1._T:0x 000094>=2448_R:0xH000019=2_R:0xH001410=2_R:0xH0018e0>0_R:0xH001523=0_R:0xH0013bf!=67\",\"Title\":\"Didn't Take the Bait\",\"Description\":\"On Forest of Illusion 4, get past Lakitu without killing him or taking the 1-up. No cape, wings.\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1671592204,\"Created\":1489073023,\"BadgeName\":\"46615\",\"Flags\":3,\"Type\":null,\"Rarity\":6.14,\"RarityHardcore\":5.15,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46615.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46615_lock.png\"},{\"ID\":46120,\"MemAddr\":\"P:0xH0013bf!=49_P:0xH000100!=15_C:0xH0000ce=103.1._C:0xH0000ce=141.1._C:0xH0000ce=197.1._C:0xH0000ce=232.1._C:0xH0000ce=20.1._C:0xH0000ce=49.1._C:0xH0000ce=96.1._C:0xH0000ce=131.1._M:0!=0.8.\",\"Title\":\"Bowser's Castle Explorer\",\"Description\":\"Complete all 8 numbered rooms in one game session\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1625960448,\"Created\":1489285260,\"BadgeName\":\"47078\",\"Flags\":3,\"Type\":null,\"Rarity\":4.64,\"RarityHardcore\":4.01,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47078.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/47078_lock.png\"},{\"ID\":46007,\"MemAddr\":\"N:d0xH0000a5=44_0xH0000a5=45.1._P:0xH00187a!=0.1._N:d0xH0013fb=0_P:0xH0013fb!=0.1._P:0xH000dda=255.5._T:0x 0014cf=12_O:0xH001dff=12_T:0xH001dff=128_N:p0xH0000a5=45_T:0xH0000a5=120SR:0xH0013bf!=89_R:0xH000d9b=2_N:0xH0000a5!=45_R:0xH0000a5!=120\",\"Title\":\"I Starved Yoshi and All I Got Was...\",\"Description\":\"On Star world 4 bring Baby Yoshi to the end flag without feeding it to adulthood\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1669165514,\"Created\":1489073073,\"BadgeName\":\"46607\",\"Flags\":3,\"Type\":null,\"Rarity\":7,\"RarityHardcore\":5.76,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46607.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46607_lock.png\"},{\"ID\":45999,\"MemAddr\":\"R:0xH0013bf!=79_R:0xH000dba=6_R:0xH0013f3>0_T:0xH001dff=12_0xH000f31=3.1.\",\"Title\":\"Who Needs Helium?\",\"Description\":\"Complete SPECIAL-Tubular without Blue Yoshi, or Balloon Mario\",\"Points\":25,\"Author\":\"kdecks\",\"Modified\":1625960450,\"Created\":1489073032,\"BadgeName\":\"46677\",\"Flags\":3,\"Type\":null,\"Rarity\":4.05,\"RarityHardcore\":3.19,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46677.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46677_lock.png\"},{\"ID\":46000,\"MemAddr\":\"R:0xH0013bf!=76_T:0xH001dff=12_R:0xH0013ef=1_0x 000094<464.1._R:0xH00187a>0\",\"Title\":\"A Groovy Flight\",\"Description\":\"Complete SPECIAL-Groovy without landing past the first ? box. No Yoshi\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1625960453,\"Created\":1489073036,\"BadgeName\":\"46665\",\"Flags\":3,\"Type\":null,\"Rarity\":3.75,\"RarityHardcore\":3.15,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46665.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46665_lock.png\"},{\"ID\":46005,\"MemAddr\":\"R:0xH0013bf!=73_0xH000f31>=3_T:0xH001dff=12_T:0xH001411=1ST:0xH001dff=12ST:0xH001dff=128\",\"Title\":\"With More Than You Started\",\"Description\":\"Complete SPECIAL-Funky with 300 or more on the time clock\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1625960456,\"Created\":1489073065,\"BadgeName\":\"46669\",\"Flags\":3,\"Type\":null,\"Rarity\":3.41,\"RarityHardcore\":2.86,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46669.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46669_lock.png\"},{\"ID\":2274,\"MemAddr\":\"0xH0013bf=49_0xH0013f9=3_0xH001f2e=11_0xH0013ef=1\",\"Title\":\"Shortest Route\",\"Description\":\"Clear the fewest stages possible and beat the game\",\"Points\":25,\"Author\":\"Jaarl\",\"Modified\":1570010401,\"Created\":1376742799,\"BadgeName\":\"46575\",\"Flags\":3,\"Type\":null,\"Rarity\":6.08,\"RarityHardcore\":5.05,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46575.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46575_lock.png\"},{\"ID\":2297,\"MemAddr\":\"N:0xH0013ce=0_N:0xH0013cf=0_0xH001f2e=0.1._T:0xH001f2e=11.1._T:0xH0013f9=3_R:0xH000dbe1\",\"Title\":\"Starman Challenge\",\"Description\":\"Clear the game without dying (one session)\",\"Points\":25,\"Author\":\"Jaarl\",\"Modified\":1625960458,\"Created\":1376918015,\"BadgeName\":\"46579\",\"Flags\":3,\"Type\":null,\"Rarity\":5.76,\"RarityHardcore\":4.67,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46579.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46579_lock.png\"},{\"ID\":383338,\"MemAddr\":\"N:0xH0013bf=21_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=5_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=7_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=46_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=14_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=71_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=28_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=58_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=55_N:0xH000071=5_C:0xW0000ce=508910.1._M:0!=0.9.\",\"Title\":\"Gambling with Life\",\"Description\":\"Enter all 1-Up Chambers in a single Session.\",\"Points\":5,\"Author\":\"SporyTike\",\"Modified\":1703596675,\"Created\":1703596675,\"BadgeName\":\"431977\",\"Flags\":3,\"Type\":null,\"Rarity\":1.45,\"RarityHardcore\":1.3,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431977.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/431977_lock.png\"},{\"ID\":175188,\"MemAddr\":\"0xH0013bf>=49_0xH0013bf<=50_0x 0000ce=0_d0xH0013de=0_0xH0013de=3\",\"Title\":\"Do The Mario!\",\"Description\":\"Make Mario dance during the credits\",\"Points\":0,\"Author\":\"stfN1337\",\"Modified\":1691443564,\"Created\":1632935877,\"BadgeName\":\"195401\",\"Flags\":3,\"Type\":null,\"Rarity\":3.44,\"RarityHardcore\":2.86,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/195401.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/195401_lock.png\"},{\"ID\":45856,\"MemAddr\":\"R:0xH000100<=10_R:0xH000db3=1_R:0xH001f27>0.1._R:0xH001f28>0_R:0xH001f29>0_R:0xH001f2a>0_R:0xH00187a>0_N:0xH0013bf=42_0xH000100=17.1._R:0xH000019>0_R:0xH001490>0SN:0xH0013bf=42_Q:0xH000100=17.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=60_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=60_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=43_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=60_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=43_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=46_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=60_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=43_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=46_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=61_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=60_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=43_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=46_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=61_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=64_N:d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.16.ST:0!=0\",\"Title\":\"Mini-Mario vs the World I\",\"Description\":\"From Yoshi's Island 2 reach and beat #3 Lemmy's Castle in one Session without using Power-Ups, without using Yoshi and without active Switch Palace Blocks.\",\"Points\":25,\"Author\":\"kdecks\",\"Modified\":1687365195,\"Created\":1488840058,\"BadgeName\":\"46755\",\"Flags\":3,\"Type\":\"missable\",\"Rarity\":3.03,\"RarityHardcore\":2.54,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46755.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46755_lock.png\"},{\"ID\":45994,\"MemAddr\":\"R:0xH000100<=10_R:0xH000db3=1_R:0xH001f27>0_R:0xH001f28>0_R:0xH001f29>0_R:0xH001f2a>0_R:0xH00187a>0_N:0xH0013bf=15_0xH000100=17.1._R:0xH001490>0SN:0xH0013bf=15_Q:0xH000100=17.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=31_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=32_N:d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.12.SN:0xH0013bf=15_Q:0xH000100=17.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=32_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=31_N:d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.12.SN:0xH0013bf=15_Q:0xH000100=17.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=32_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=31_N:d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.12.ST:0!=0_N:0xH0013bf!=71_R:0xH000019>0\",\"Title\":\"Mini-Mario vs the World II\",\"Description\":\"From Cheese Bridge Area reach and beat #5 Roy's Castle and Forest Fortress in one Session without using Power-Ups except the P-Balloon and in Forest of Illusion 3, without using Yoshi and without active Switch Palace Blocks.\",\"Points\":25,\"Author\":\"kdecks\",\"Modified\":1687365208,\"Created\":1489073010,\"BadgeName\":\"46756\",\"Flags\":3,\"Type\":\"missable\",\"Rarity\":2.29,\"RarityHardcore\":1.95,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46756.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46756_lock.png\"},{\"ID\":45995,\"MemAddr\":\"R:0xH000100<=10_R:0xH000db3=1_R:0xH001f27>0_R:0xH001f28>0_R:0xH001f29>0_R:0xH001f2a>0_R:0xH00187a>0_N:0xH0013bf=34_0xH000100=17.1._R:0xH000019>0SN:0xH0013bf=34_Q:0xH000100=17.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=56_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=56_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=55_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=56_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=55_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=51_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=56_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=55_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=51_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=52_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=56_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=55_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=51_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=52_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=49_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=53_N:d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.15.ST:0!=0_N:0xH0013bf!=24_R:0xH001490>0\",\"Title\":\"Mini-Mario vs the World III\",\"Description\":\"From Chocolate Island 1, unlock all Level Entrances in Valley of Bowser and beat the Front Door in one Session without using Power-Ups except Sunken Ghost Ships' Super Star, without using Yoshi and without active Switch Palace Blocks.\",\"Points\":25,\"Author\":\"kdecks\",\"Modified\":1687365228,\"Created\":1489073014,\"BadgeName\":\"46757\",\"Flags\":3,\"Type\":\"missable\",\"Rarity\":2.04,\"RarityHardcore\":1.72,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46757.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46757_lock.png\"},{\"ID\":45996,\"MemAddr\":\"R:0xH000100<=10_R:0xH000db3=1_R:0xH001f27>0_R:0xH001f28>0_R:0xH001f29>0_R:0xH001f2a>0_R:0xH00187a>0_N:0xH0013bf=78_0xH000100=17.1._R:0xH000019>0_R:0xH001490>0SN:0xH0013bf=78_Q:0xH000100=17.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=81_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=81_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=76_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=81_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=76_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=75_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=81_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=76_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=75_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=74_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=81_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=76_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=75_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=74_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=73_N:d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.8.ST:0!=0\",\"Title\":\"Mini-Mario vs the World SPECIAL\",\"Description\":\"From Gnarly reach and beat Funky in one Session without using Power-Ups except the P-Balloon, without using Yoshi and without active Switch Palace Blocks.\",\"Points\":50,\"Author\":\"kdecks\",\"Modified\":1687365239,\"Created\":1489073019,\"BadgeName\":\"46758\",\"Flags\":3,\"Type\":\"missable\",\"Rarity\":1.97,\"RarityHardcore\":1.65,\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46758.png\",\"BadgeLockedURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/46758_lock.png\"}],\"Leaderboards\":[{\"ID\":1998,\"Mem\":\"STA:0xh13bf=h44_0xh1411=1_0xhf31=3_0xRda4=1::CAN:0xh77>0::SUB:0xh1dff=hc::VAL:0xhf30_0xhf33*100_0xhf32*1000_0xhf31*10000\",\"Format\":\"VALUE\",\"LowerIsBetter\":0,\"Title\":\"Everything Is Lava Achievement\",\"Description\":\"Wall touch alert and best time (does not track enemy contact)\",\"Hidden\":true},{\"ID\":53203,\"Mem\":\"STA:1=0_0xH0013bf=49_0xH000071=10_0xH000019=0_0xH000dc2=0::CAN:S0xH0013bf!=49S0xH000071=10_O:0xH000019!=0_0xH000dc2!=0::SUB:S0xH0013bf=49_0xH0013f9=3_0xH0013ef=1S0xH000906>d0xH000906::VAL:M:0=0\",\"Format\":\"TIME\",\"LowerIsBetter\":1,\"Title\":\"RAOlympics - Bowser Bashing\",\"Description\":\"Defeat Bowser the fastest starting as small mario with no backup powers (Front Door)\",\"Hidden\":true},{\"ID\":1991,\"Mem\":\"STA:d0x100=h90a_0x100=h90b_0xRda4=1_0xQda4=1_0xh1f2e=0_0xhdb2=0::CAN:0xh100=0::SUB:0xH001f2e<=96S0xH000dbf>0Sd0xH001f2e=95_0xH001f2e=96::VAL:0xh1f2e\",\"Format\":\"VALUE\",\"LowerIsBetter\":0,\"Title\":\"Penniless Plumbers\",\"Description\":\"Clear as many exits as possible without getting a coin\",\"Hidden\":false},{\"ID\":1992,\"Mem\":\"STA:d0x100=h90a_0x100=h90b_0xRda4=1_0xNda2=1_0xh1f2e=0_0xhdb2=0::CAN:0xh100=0::SUB:0xH001f2e<=96S0xH000dbed0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Yoshi's Island 1 Speedrun\",\"Description\":\"Complete Yoshi's Island 1 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104611,\"Mem\":\"STA:0xH0013bf=20_d0xH001493=0_0xH001493=8_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Yellow Switch Palace Speedrun\",\"Description\":\"Complete the Yellow Switch Palace with the most time remaining\",\"Hidden\":false},{\"ID\":104612,\"Mem\":\"STA:0xH0013bf=42_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Yoshi's Island 2 Speedrun\",\"Description\":\"Complete Yoshi's Island 2 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104613,\"Mem\":\"STA:0xH0013bf=39_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Yoshi's Island 3 Speedrun\",\"Description\":\"Complete Yoshi's Island 3 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104614,\"Mem\":\"STA:0xH0013bf=38_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Yoshi's Island 4 Speedrun\",\"Description\":\"Complete Yoshi's Island 4 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104615,\"Mem\":\"STA:0xH0013bf=37_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Iggy's Castle Speedrun\",\"Description\":\"Complete Iggy's Castle with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104721,\"Mem\":\"STA:0xH0013bf=21_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Plains 1 Speedrun\",\"Description\":\"Complete Donut Plains 1 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104722,\"Mem\":\"STA:0xH0013bf=9_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Plains 2 Speedrun\",\"Description\":\"Complete Donut Plains 2 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104723,\"Mem\":\"STA:0xH0013bf=8_d0xH001493=0_0xH001493=8_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Green Switch Palace Speedrun\",\"Description\":\"Complete the Green Switch Palace with the most time remaining\",\"Hidden\":false},{\"ID\":104724,\"Mem\":\"STA:0xH0013bf=4_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Ghost House Speedrun\",\"Description\":\"Complete the Donut Ghost House with the most time remaining\",\"Hidden\":false},{\"ID\":104727,\"Mem\":\"STA:0xH0013bf=10_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Secret 1 Speedrun\",\"Description\":\"Complete Donut Secret 1 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104728,\"Mem\":\"STA:0xH0013bf=19_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Secret House Speedrun\",\"Description\":\"Complete Donut Secret House with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104729,\"Mem\":\"STA:0xH0013bf=47_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Secret 2 Speedrun\",\"Description\":\"Complete Donut Secret 2 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104725,\"Mem\":\"STA:0xH0013bf=5_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Plains 3 Speedrun\",\"Description\":\"Complete Donut Plains 3 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104726,\"Mem\":\"STA:0xH0013bf=6_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Plains 4 Speedrun\",\"Description\":\"Complete Donut Plains 4 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104730,\"Mem\":\"STA:0xH0013bf=7_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Morton's Castle Speedrun\",\"Description\":\"Complete Morton's Castle with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104865,\"Mem\":\"STA:0xH0013bf=62_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Dome 1 Speedrun\",\"Description\":\"Complete Vanilla Dome 1 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104866,\"Mem\":\"STA:0xH0013bf=60_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Dome 2 Speedrun\",\"Description\":\"Complete Vanilla Dome 2 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104867,\"Mem\":\"STA:0xH0013bf=63_d0xH001493=0_0xH001493=8_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Red Switch Palace Speedrun\",\"Description\":\"Complete the Red Switch Palace with the most time remaining\",\"Hidden\":false},{\"ID\":104868,\"Mem\":\"STA:0xH0013bf=43_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Ghost House Speedrun\",\"Description\":\"Complete Vanilla Ghost House with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104869,\"Mem\":\"STA:0xH0013bf=46_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Dome 3 Speedrun\",\"Description\":\"Complete Vanilla Dome 3 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104870,\"Mem\":\"STA:0xH0013bf=61_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Dome 4 Speedrun\",\"Description\":\"Complete Vanilla Dome 4 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104871,\"Mem\":\"STA:0xH0013bf=45_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Secret 1 Speedrun\",\"Description\":\"Complete Vanilla Secret 1 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104872,\"Mem\":\"STA:0xH0013bf=1_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Secret 2 Speedrun\",\"Description\":\"Complete Vanilla Secret 2 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104873,\"Mem\":\"STA:0xH0013bf=2_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Secret 3 Speedrun\",\"Description\":\"Complete Vanilla Secret 3 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104874,\"Mem\":\"STA:0xH0013bf=11_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Fortress Speedrun\",\"Description\":\"Complete Vanilla Fortress with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104875,\"Mem\":\"STA:0xH0013bf=64_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Lemmy's Castle Speedrun\",\"Description\":\"Complete Lemmy's Castle with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104876,\"Mem\":\"STA:0xH0013bf=15_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Cheese Bridge Area Speedrun\",\"Description\":\"Complete Cheese Bridge Area with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104877,\"Mem\":\"STA:0xH0013bf=16_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Cookie Mountain Speedrun\",\"Description\":\"Complete Cookie Mountain with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104878,\"Mem\":\"STA:0xH0013bf=12_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Butter Bridge 1 Speedrun\",\"Description\":\"Complete Butter Bridge 1 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104879,\"Mem\":\"STA:0xH0013bf=13_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Butter Bridge 2 Speedrun\",\"Description\":\"Complete Butter Bridge 2 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104880,\"Mem\":\"STA:0xH0013bf=17_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Soda Lake Speedrun\",\"Description\":\"Complete Soda Lake with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104881,\"Mem\":\"STA:0xH0013bf=14_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Ludwig's Castle Speedrun\",\"Description\":\"Complete Ludwig's Castle with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104882,\"Mem\":\"STA:0xH0013bf=66_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest of Illusion 1 Speedrun\",\"Description\":\"Complete Forest of Illusion 1 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104883,\"Mem\":\"STA:0xH0013bf=68_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest of Illusion 2 Speedrun\",\"Description\":\"Complete Forest of Illusion 2 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104884,\"Mem\":\"STA:0xH0013bf=69_d0xH001493=0_0xH001493=8_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Blue Switch Palace Speedrun\",\"Description\":\"Complete the Blue Switch Palace with the most time remaining\",\"Hidden\":false},{\"ID\":104885,\"Mem\":\"STA:0xH0013bf=71_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest of Illusion 3 Speedrun\",\"Description\":\"Complete Forest of Illusion 3 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104886,\"Mem\":\"STA:0xH0013bf=65_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest Ghost House Speedrun\",\"Description\":\"Complete Forest Ghost House with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104887,\"Mem\":\"STA:0xH0013bf=67_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest of Illusion 4 Speedrun\",\"Description\":\"Complete Forest of Illusion 4 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104888,\"Mem\":\"STA:0xH0013bf=70_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest Secret Area Speedrun\",\"Description\":\"Complete Forest Secret Area with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104889,\"Mem\":\"STA:0xH0013bf=31_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest Fortress Speedrun\",\"Description\":\"Complete Forest Fortress with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104890,\"Mem\":\"STA:0xH0013bf=32_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Roy's Castle Speedrun\",\"Description\":\"Complete Roy's Castle with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104913,\"Mem\":\"STA:N:0x 0000d1=16_N:0x 0000d3=352_0xH000f31=3.1._0xH0013bf=34_0xM000906>d0xM000906_0xS0013cf=0_O:0xH000dc2=20_P:0xH000dc2=215.1.SR:0xH000100=14::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate Island 1 Speedrun\",\"Description\":\"Complete Chocolate Island 1 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104914,\"Mem\":\"STA:0xH0013bf=33_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Choco-Ghost House Speedrun\",\"Description\":\"Complete Choco-Ghost House with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104915,\"Mem\":\"STA:0xH0013bf=36_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate Island 2 Speedrun\",\"Description\":\"Complete Chocolate Island 2 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104916,\"Mem\":\"STA:0xH0013bf=35_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate Island 3 Speedrun\",\"Description\":\"Complete Chocolate Island 3 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104917,\"Mem\":\"STA:0xH0013bf=27_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate Fortress Speedrun\",\"Description\":\"Complete Chocolate Fortress with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104918,\"Mem\":\"STA:0xH0013bf=29_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate Island 4 Speedrun\",\"Description\":\"Complete Chocolate Island 4 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104919,\"Mem\":\"STA:0xH0013bf=28_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate Island 5 Speedrun\",\"Description\":\"Complete Chocolate Island 5 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104920,\"Mem\":\"STA:0xH0013bf=59_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate Secret Speedrun\",\"Description\":\"Complete Chocolate Secret with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104921,\"Mem\":\"STA:0xH0013bf=26_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Wendy's Castle Speedrun\",\"Description\":\"Complete Wendy's Castle with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104922,\"Mem\":\"STA:0xH0013bf=24_0xS0013cf=0_d0xH001493=0_0xH001493=255_0x 000096>=6144_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Sunken Ghost Ship Speedrun\",\"Description\":\"Complete Sunken Ghost Ship with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104931,\"Mem\":\"STA:0xH0013bf=58_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley of Bowser 1 Speedrun\",\"Description\":\"Complete Valley of Bowser 1 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104932,\"Mem\":\"STA:0xH0013bf=57_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley of Bowser 2 Speedrun\",\"Description\":\"Complete Valley of Bowser 2 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104933,\"Mem\":\"STA:0xH0013bf=56_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley Ghost House Speedrun\",\"Description\":\"Complete Valley Ghost House with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104934,\"Mem\":\"STA:0xH0013bf=55_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley of Bowser 3 Speedrun\",\"Description\":\"Complete Valley of Bowser 3 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104935,\"Mem\":\"STA:0xH0013bf=51_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley of Bowser 4 Speedrun\",\"Description\":\"Complete Valley of Bowser 4 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104936,\"Mem\":\"STA:0xH0013bf=53_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley Fortress Speedrun\",\"Description\":\"Complete Valley Fortress with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104937,\"Mem\":\"STA:0xH0013bf=52_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Larry's Castle Speedrun\",\"Description\":\"Complete Larry's Castle with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104938,\"Mem\":\"STA:0xH0013bf=88_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Star World 1 Speedrun\",\"Description\":\"Complete Star World 1 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104939,\"Mem\":\"STA:0xH0013bf=84_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Star World 2 Speedrun\",\"Description\":\"Complete Star World 2 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104940,\"Mem\":\"STA:0xH0013bf=86_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Star World 3 Speedrun\",\"Description\":\"Complete Star World 3 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104941,\"Mem\":\"STA:0xH0013bf=89_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Star World 4 Speedrun\",\"Description\":\"Complete Star World 4 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104942,\"Mem\":\"STA:0xH0013bf=90_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Star World 5 Speedrun\",\"Description\":\"Complete Star World 5 with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104943,\"Mem\":\"STA:0xH0013bf=78_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Gnarly Speedrun\",\"Description\":\"Complete Gnarly with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104944,\"Mem\":\"STA:0xH0013bf=79_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Tubular Speedrun\",\"Description\":\"Complete Tubular with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104945,\"Mem\":\"STA:0xH0013bf=80_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Way Cool Speedrun\",\"Description\":\"Complete Way Cool with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104946,\"Mem\":\"STA:0xH0013bf=81_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Awesome Speedrun\",\"Description\":\"Complete Awesome with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104947,\"Mem\":\"STA:0xH0013bf=76_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Groovy Speedrun\",\"Description\":\"Complete Groovy with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104948,\"Mem\":\"STA:0xH0013bf=75_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Mondo Speedrun\",\"Description\":\"Complete Mondo with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104949,\"Mem\":\"STA:0xH0013bf=74_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Outrageous Speedrun\",\"Description\":\"Complete Outrageous with the most time remaining without using a checkpoint\",\"Hidden\":false},{\"ID\":104950,\"Mem\":\"STA:0xH0013bf=73_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Funky Speedrun\",\"Description\":\"Complete Funky with the most time remaining without using a checkpoint\",\"Hidden\":false}]}}" diff --git a/src/app/rcheevos/award_achievement_response.hpp b/src/app/rcheevos/award_achievement_response.hpp index de7efddc..7de17453 100644 --- a/src/app/rcheevos/award_achievement_response.hpp +++ b/src/app/rcheevos/award_achievement_response.hpp @@ -3,15 +3,17 @@ #include namespace firelight::achievements { - struct AwardAchievementResponse { - bool Success; - int Score; - int SoftcoreScore; - unsigned AchievementID; - int AchievementsRemaining; - // const char* server_response = "{\"Success\":true,\"Score\":119102,\"SoftcoreScore\":777,\"AchievementID\":56481,\"AchievementsRemaining\":11}"; - }; +struct AwardAchievementResponse { + bool Success; + unsigned Score; + unsigned SoftcoreScore; + unsigned AchievementID; + unsigned AchievementsRemaining; + // const char* server_response = + // "{\"Success\":true,\"Score\":119102,\"SoftcoreScore\":777,\"AchievementID\":56481,\"AchievementsRemaining\":11}"; +}; - NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(AwardAchievementResponse, Success, Score, SoftcoreScore, AchievementID, - AchievementsRemaining) +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(AwardAchievementResponse, Success, Score, + SoftcoreScore, AchievementID, + AchievementsRemaining) } // namespace firelight::achievements diff --git a/src/app/rcheevos/login2_response.hpp b/src/app/rcheevos/login2_response.hpp index 14d40ce2..3d494d83 100644 --- a/src/app/rcheevos/login2_response.hpp +++ b/src/app/rcheevos/login2_response.hpp @@ -4,17 +4,18 @@ #include namespace firelight::achievements { - struct Login2Response { - std::string AccountType{}; - int Messages{}; - int Permissions{}; - int Score{}; - int SoftcoreScore{}; - bool Success{}; - std::string Token{}; - std::string User{}; - }; +struct Login2Response { + std::string AccountType{}; + unsigned Messages{}; + unsigned Permissions{}; + unsigned Score{}; + unsigned SoftcoreScore{}; + bool Success{}; + std::string Token{}; + std::string User{}; +}; - NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Login2Response, AccountType, Messages, Permissions, Score, SoftcoreScore, - Success, Token, User) -} +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Login2Response, AccountType, Messages, + Permissions, Score, SoftcoreScore, Success, + Token, User) +} // namespace firelight::achievements diff --git a/src/app/rcheevos/offline/rcheevos_offline_client.cpp b/src/app/rcheevos/offline/rcheevos_offline_client.cpp index 80e6ea0e..4b0da86e 100644 --- a/src/app/rcheevos/offline/rcheevos_offline_client.cpp +++ b/src/app/rcheevos/offline/rcheevos_offline_client.cpp @@ -1,9 +1,9 @@ #include "rcheevos_offline_client.hpp" +#include "../achievement_set_response.hpp" #include "../award_achievement_response.hpp" #include "../gameid_response.hpp" #include "../login2_response.hpp" -#include "../patch_response.hpp" #include "../ra_constants.h" #include "../startsession_response.hpp" @@ -19,7 +19,7 @@ namespace firelight::achievements { static const std::string GAMEID = "gameid"; -static const std::string PATCH = "patch"; +static const std::string ACHIEVEMENT_SETS = "achievementsets"; static const std::string START_SESSION = "startsession"; static const std::string AWARD_ACHIEVEMENT = "awardachievement"; static const std::string LOGIN2 = "login2"; @@ -58,12 +58,8 @@ RetroAchievementsOfflineClient::handleRequest(const std::string &url, // const auto nowEpochMillis = // std::chrono::duration_cast(duration).count(); - if (params["r"] == GAMEID) { - return handleGameIdRequest(params["m"]); - } - - if (params["r"] == PATCH) { - return handlePatchRequest(stoi(params["g"])); + if (params["r"] == ACHIEVEMENT_SETS) { + return handleAchievementSetsRequest(params["u"], params["t"], params["m"]); } if (params["r"] == START_SESSION) { @@ -103,10 +99,8 @@ void RetroAchievementsOfflineClient::processResponse( auto params = parseQueryParams(request); if (params["r"] == "login2") { processLogin2Response(params["u"], params["t"], response); - } else if (params["r"] == "gameid") { - processGameIdResponse(params["m"], response); - } else if (params["r"] == "patch") { - processPatchResponse(params["u"], stoi(params["g"]), response); + } else if (params["r"] == "achievementsets") { + processAchievementSetsResponse(params["u"], params["m"], response); } else if (params["r"] == "startsession") { processStartSessionResponse(params["u"], stoi(params["g"]), response); } else if (params["r"] == "awardachievement") { @@ -122,38 +116,11 @@ void RetroAchievementsOfflineClient::startOnlineHardcoreSession() { m_inHardcoreSession = true; } -rc_api_server_response_t RetroAchievementsOfflineClient::handleGameIdRequest( - const std::string &hash) const { - const auto cached = m_achievementService.getGameId(hash); - if (!cached.has_value()) { - return GENERIC_SERVER_ERROR; - } - - GameIdResponse gameIdResponse{.Success = true, .GameID = cached.value()}; - - const auto json = nlohmann::json(gameIdResponse).dump(); - - rc_api_server_response_t rcResponse; - rcResponse.http_status_code = 200; - rcResponse.body = strdup(json.c_str()); - rcResponse.body_length = strlen(rcResponse.body); - return rcResponse; -} - rc_api_server_response_t -RetroAchievementsOfflineClient::handlePatchRequest(const int gameId) const { - auto cached = m_achievementService.getPatchResponse(gameId); - if (!cached.has_value()) { - return GENERIC_SERVER_ERROR; - } - - const auto json = nlohmann::json(cached.value()).dump(); - - rc_api_server_response_t rcResponse; - rcResponse.http_status_code = 200; - rcResponse.body = strdup(json.c_str()); - rcResponse.body_length = strlen(rcResponse.body); - return rcResponse; +RetroAchievementsOfflineClient::handleAchievementSetsRequest( + const std::string &username, const std::string &token, + const std::string &hash) const { + return GENERIC_SUCCESS; } rc_api_server_response_t @@ -233,7 +200,7 @@ RetroAchievementsOfflineClient::handleAwardAchievementRequest( } m_achievementService.createOrUpdate(newUnlock); - m_achievementService.createOrUpdateUser(*user); + m_achievementService.create(*user); } else { if (hardcore && unlock->earnedHardcore) { spdlog::info("User {} already has achievement {} in hardcore mode", @@ -277,13 +244,13 @@ RetroAchievementsOfflineClient::handleAwardAchievementRequest( } m_achievementService.createOrUpdate(*unlock); - m_achievementService.createOrUpdateUser(*user); + m_achievementService.create(*user); } auto unlocks = m_achievementService.getAllUserUnlocks( username, achievement->achievementSetId); - auto numLocked = 0; + unsigned numLocked = 0; for (const auto &u : unlocks) { if ((hardcore && !u.earnedHardcore) || (!hardcore && !u.earned)) { numLocked++; @@ -318,7 +285,7 @@ rc_api_server_response_t RetroAchievementsOfflineClient::handleLogin2Request( .username = username, .token = token, .softcoreScore = 0, .score = 0}; auto userOpt = m_achievementService.getUser(username); if (!userOpt.has_value()) { - m_achievementService.createOrUpdateUser(user); + m_achievementService.create(user); } else { user = userOpt.value(); } @@ -353,42 +320,88 @@ RetroAchievementsOfflineClient::handleSubmitLbEntryRequest() { void RetroAchievementsOfflineClient::processLogin2Response( const std::string &username, const std::string &token, const std::string &response) const { - auto json = nlohmann::json::parse(response); - auto user = User{.username = username, .token = token}; - if (!json.contains("Success") || !json["Success"].is_boolean() || !json["Success"].get()) { spdlog::error("Login response was not success"); return; } - if (json.contains("Score") && json["Score"].is_number()) { - user.score = json["Score"]; - } - - if (json.contains("SoftcoreScore") && json["SoftcoreScore"].is_number()) { - user.softcoreScore = json["SoftcoreScore"]; - } - - m_achievementService.createOrUpdateUser(user); + const auto user = json.get(); + m_achievementService.create(user); } -void RetroAchievementsOfflineClient::processGameIdResponse( - const std::string &hash, const std::string &response) const { - const auto json = nlohmann::json::parse(response); - const auto gameidResponse = json.get(); - m_achievementService.setGameId(hash, gameidResponse.GameID); -} - -void RetroAchievementsOfflineClient::processPatchResponse( - const std::string &username, const int gameId, +void RetroAchievementsOfflineClient::processAchievementSetsResponse( + const std::string &username, const std::string &hash, const std::string &response) const { - const auto json = nlohmann::json::parse(response); - const auto patchResponse = json.get(); + auto json = nlohmann::json::parse(response); + if (!json.contains("Success") || !json["Success"].is_boolean() || + !json["Success"].get()) { + spdlog::error("Achievement sets response was not success"); + return; + } + + const auto setResponse = json.get(); + Game game{.id = setResponse.GameId, + .title = setResponse.Title, + .imageIconUrl = setResponse.ImageIconUrl, + .richPresenceGameId = setResponse.RichPresenceGameId, + .richPresencePatch = setResponse.RichPresencePatch, + .consoleId = setResponse.ConsoleId}; + + m_achievementService.create(game); + m_achievementService.setGameId(hash, game.id); + + auto i = 0; + for (const auto &set : setResponse.Sets) { + AchievementSet achievementSet{.id = set.id, + .title = set.title, + .gameId = game.id, + .type = set.type, + .imageIconUrl = set.imageIconUrl}; + + m_achievementService.create(achievementSet); + m_achievementService.setAchievementSetHash(achievementSet.id, hash); + + for (const auto &achieve : set.achievements) { + Achievement achievement{.id = achieve.id, + .achievementSetId = set.id, + .memAddr = achieve.memAddr, + .title = achieve.title, + .description = achieve.description, + .points = achieve.points, + .author = achieve.author, + .modified = achieve.modified, + .created = achieve.created, + .flags = achieve.flags, + .type = achieve.type, + .rarity = achieve.rarity, + .rarityHardcore = achieve.rarityHardcore, + .badgeUrl = achieve.badgeUrl, + .badgeLockedUrl = achieve.badgeLockedUrl, + .displayOrder = i++}; + + m_achievementService.create(achievement); + } - m_achievementService.processPatchResponse(username, patchResponse); + // TODO: Calculate total points and num achievements for set? + + i = 0; + for (const auto &leaderboard : set.leaderboards) { + Leaderboard newLeaderboard{.id = leaderboard.id, + .achievementSetId = set.id, + .memAddr = leaderboard.memAddr, + .format = leaderboard.format, + .lowerIsBetter = leaderboard.lowerIsBetter, + .title = leaderboard.title, + .description = leaderboard.description, + .hidden = leaderboard.hidden, + .displayOrder = i++}; + + m_achievementService.create(newLeaderboard); + } + } } void RetroAchievementsOfflineClient::processStartSessionResponse( @@ -435,7 +448,7 @@ void RetroAchievementsOfflineClient::processAwardAchievementResponse( user.softcoreScore = json["SoftcoreScore"]; } - m_achievementService.createOrUpdateUser(user); + m_achievementService.create(user); if (json.contains("AchievementID") && json["AchievementID"].is_number()) { if (auto id = json["AchievementID"]; diff --git a/src/app/rcheevos/offline/rcheevos_offline_client.hpp b/src/app/rcheevos/offline/rcheevos_offline_client.hpp index 838ed2e0..cb36c5f5 100644 --- a/src/app/rcheevos/offline/rcheevos_offline_client.hpp +++ b/src/app/rcheevos/offline/rcheevos_offline_client.hpp @@ -24,9 +24,10 @@ class RetroAchievementsOfflineClient final { void startOnlineHardcoreSession(); private: - rc_api_server_response_t handleGameIdRequest(const std::string &hash) const; - - rc_api_server_response_t handlePatchRequest(int gameId) const; + rc_api_server_response_t + handleAchievementSetsRequest(const std::string &username, + const std::string &token, + const std::string &hash) const; rc_api_server_response_t handleStartSessionRequest(const std::string &username, int gameId, @@ -49,11 +50,9 @@ class RetroAchievementsOfflineClient final { const std::string &token, const std::string &response) const; - void processGameIdResponse(const std::string &hash, - const std::string &response) const; - - void processPatchResponse(const std::string &username, int gameId, - const std::string &response) const; + void processAchievementSetsResponse(const std::string &username, + const std::string &hash, + const std::string &response) const; void processStartSessionResponse(const std::string &username, int gameId, const std::string &response) const; diff --git a/src/app/rcheevos/user.h b/src/app/rcheevos/user.h index 9c502130..357422c8 100644 --- a/src/app/rcheevos/user.h +++ b/src/app/rcheevos/user.h @@ -1,14 +1,59 @@ #pragma once +#include #include namespace firelight::achievements { class User { public: std::string username; + std::string displayName; std::string avatarUrl; std::string token; - int softcoreScore; - int score; + unsigned score; + unsigned softcoreScore; + unsigned messages; + unsigned permissions; + std::string accountType; }; + +inline void from_json(const nlohmann::json &j, User &u) { + j.at("User").get_to(u.username); + j.at("Token").get_to(u.token); + + if (j.contains("AvatarUrl") && !j.at("AvatarUrl").is_null()) { + j.at("AvatarUrl").get_to(u.avatarUrl); + } + + if (j.contains("Score") && !j.at("Score").is_null()) { + j.at("Score").get_to(u.score); + } + + if (j.contains("SoftcoreScore") && !j.at("SoftcoreScore").is_null()) { + j.at("SoftcoreScore").get_to(u.softcoreScore); + } + + if (j.contains("Messages") && !j.at("Messages").is_null()) { + j.at("Messages").get_to(u.messages); + } + + if (j.contains("Permissions") && !j.at("Permissions").is_null()) { + j.at("Permissions").get_to(u.permissions); + } + + if (j.contains("AccountType") && !j.at("AccountType").is_null()) { + j.at("AccountType").get_to(u.accountType); + } +} + +inline void to_json(nlohmann::json &j, const User &u) { + j = nlohmann::json{{"User", u.username}, + {"Token", u.token}, + {"AvatarUrl", u.avatarUrl}, + {"Score", u.score}, + {"SoftcoreScore", u.softcoreScore}, + {"Messages", u.messages}, + {"Permissions", u.permissions}, + {"AccountType", u.accountType}}; +} } // namespace firelight::achievements \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 0000e012..c88bb276 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -225,7 +225,7 @@ int main(int argc, char *argv[]) { }); firelight::achievements::SqliteAchievementRepository achievementRepo( - (defaultAppDataPathString + "/rcheevos2.db").toStdString()); + (defaultAppDataPathString + "/rcheevos3.db").toStdString()); firelight::achievements::AchievementService achievementService( achievementRepo); diff --git a/tests/app/achievements/achievement_service_test.cpp b/tests/app/achievements/achievement_service_test.cpp index 23710197..127592ba 100644 --- a/tests/app/achievements/achievement_service_test.cpp +++ b/tests/app/achievements/achievement_service_test.cpp @@ -129,7 +129,7 @@ TEST_F(AchievementServiceTest, Constructor_Success) { TEST_F(AchievementServiceTest, CreateUser_Success) { auto user = createTestUser("testuser", "test_token_123", 1500, 750); - bool result = service->createOrUpdateUser(user); + bool result = service->create(user); EXPECT_TRUE(result); @@ -146,14 +146,14 @@ TEST_F(AchievementServiceTest, UpdateUser_Success) { auto user = createTestUser("testuser", "test_token_123", 1500, 750); // Create user - EXPECT_TRUE(service->createOrUpdateUser(user)); + EXPECT_TRUE(service->create(user)); // Update user data user.token = "updated_token_456"; user.softcoreScore = 2000; user.score = 1000; - bool result = service->createOrUpdateUser(user); + bool result = service->create(user); EXPECT_TRUE(result); @@ -168,7 +168,7 @@ TEST_F(AchievementServiceTest, UpdateUser_Success) { TEST_F(AchievementServiceTest, GetUser_ExistingUser) { auto user = createTestUser("testuser", "test_token_123", 1500, 750); - EXPECT_TRUE(service->createOrUpdateUser(user)); + EXPECT_TRUE(service->create(user)); auto result = service->getUser("testuser"); @@ -189,8 +189,8 @@ TEST_F(AchievementServiceTest, CreateOrUpdateUser_MultipleUsers) { auto user1 = createTestUser("user1", "token1", 1000, 500); auto user2 = createTestUser("user2", "token2", 2000, 1500); - EXPECT_TRUE(service->createOrUpdateUser(user1)); - EXPECT_TRUE(service->createOrUpdateUser(user2)); + EXPECT_TRUE(service->create(user1)); + EXPECT_TRUE(service->create(user2)); // Verify both users exist independently auto retrievedUser1 = service->getUser("user1"); @@ -213,7 +213,7 @@ TEST_F(AchievementServiceTest, CreateOrUpdateUser_MultipleUsers) { TEST_F(AchievementServiceTest, CreateOrUpdateUser_ZeroPoints) { auto user = createTestUser("beginner", "token", 0, 0); - bool result = service->createOrUpdateUser(user); + bool result = service->create(user); EXPECT_TRUE(result); @@ -226,7 +226,7 @@ TEST_F(AchievementServiceTest, CreateOrUpdateUser_ZeroPoints) { TEST_F(AchievementServiceTest, CreateOrUpdateUser_EmptyToken) { auto user = createTestUser("test", "", 100, 50); - bool result = service->createOrUpdateUser(user); + bool result = service->create(user); EXPECT_TRUE(result); @@ -297,28 +297,28 @@ TEST_F(AchievementServiceTest, GetGameId_MultipleMappings) { EXPECT_EQ(result2.value(), setId2); } -TEST_F(AchievementServiceTest, GetGameId_IntegrationWithSetGameId) { - // This test verifies the complete round-trip functionality - const std::string contentHash = "integration_test_hash"; - const int setId = 777; - - // First create some achievement data - auto patchResponse = createTestPatchResponse(setId); - EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); - - // Set the game ID mapping - EXPECT_TRUE(service->setGameId(contentHash, setId)); - - // Verify we can get the game ID back - auto retrievedGameId = service->getGameId(contentHash); - ASSERT_TRUE(retrievedGameId.has_value()); - EXPECT_EQ(retrievedGameId.value(), setId); - - // Verify we can also get the achievement set by content hash - auto achievementSet = service->getAchievementSetByContentHash(contentHash); - ASSERT_TRUE(achievementSet.has_value()); - EXPECT_EQ(achievementSet->id, setId); -} +// TEST_F(AchievementServiceTest, GetGameId_IntegrationWithSetGameId) { +// // This test verifies the complete round-trip functionality +// const std::string contentHash = "integration_test_hash"; +// const int setId = 777; +// +// // First create some achievement data +// auto patchResponse = createTestPatchResponse(setId); +// EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); +// +// // Set the game ID mapping +// EXPECT_TRUE(service->setGameId(contentHash, setId)); +// +// // Verify we can get the game ID back +// auto retrievedGameId = service->getGameId(contentHash); +// ASSERT_TRUE(retrievedGameId.has_value()); +// EXPECT_EQ(retrievedGameId.value(), setId); +// +// // Verify we can also get the achievement set by content hash +// auto achievementSet = service->getAchievementSetByContentHash(contentHash); +// ASSERT_TRUE(achievementSet.has_value()); +// EXPECT_EQ(achievementSet->id, setId); +// } TEST_F(AchievementServiceTest, GetGameId_EmptyContentHash) { const std::string emptyHash = ""; @@ -333,20 +333,20 @@ TEST_F(AchievementServiceTest, GetGameId_EmptyContentHash) { EXPECT_EQ(result.value(), setId); } -TEST_F(AchievementServiceTest, GetAchievementSetByContentHash_Delegates) { - const std::string contentHash = "test_hash"; - - // First set up some data in the repository - auto patchResponse = createTestPatchResponse(1); - EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); - EXPECT_TRUE(service->setGameId(contentHash, 1)); - - auto result = service->getAchievementSetByContentHash(contentHash); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->id, 1); - EXPECT_EQ(result->name, "Test Game 1"); -} +// TEST_F(AchievementServiceTest, GetAchievementSetByContentHash_Delegates) { +// const std::string contentHash = "test_hash"; +// +// // First set up some data in the repository +// auto patchResponse = createTestPatchResponse(1); +// EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); +// EXPECT_TRUE(service->setGameId(contentHash, 1)); +// +// auto result = service->getAchievementSetByContentHash(contentHash); +// +// ASSERT_TRUE(result.has_value()); +// EXPECT_EQ(result->id, 1); +// EXPECT_EQ(result->name, "Test Game 1"); +// } TEST_F(AchievementServiceTest, GetAchievementSetByContentHash_NotFound) { auto result = service->getAchievementSetByContentHash("nonexistent_hash"); @@ -371,18 +371,18 @@ TEST_F(AchievementServiceTest, UpdateAchievementProgress_Success) { EXPECT_TRUE(result); } -TEST_F(AchievementServiceTest, GetUserUnlock_ExistingUnlock) { - // Set up achievement data first - auto patchResponse = createTestPatchResponse(1); - EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); - - auto result = service->getUserUnlock("testuser", 1); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->username, "testuser"); - EXPECT_EQ(result->achievementId, 1); - EXPECT_FALSE(result->earned); // Should be false initially -} +// TEST_F(AchievementServiceTest, GetUserUnlock_ExistingUnlock) { +// // Set up achievement data first +// auto patchResponse = createTestPatchResponse(1); +// EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); +// +// auto result = service->getUserUnlock("testuser", 1); +// +// ASSERT_TRUE(result.has_value()); +// EXPECT_EQ(result->username, "testuser"); +// EXPECT_EQ(result->achievementId, 1); +// EXPECT_FALSE(result->earned); // Should be false initially +// } TEST_F(AchievementServiceTest, GetUserUnlock_NotFound) { auto result = service->getUserUnlock("nonexistent", 999); @@ -390,200 +390,48 @@ TEST_F(AchievementServiceTest, GetUserUnlock_NotFound) { EXPECT_FALSE(result.has_value()); } -TEST_F(AchievementServiceTest, GetPatchResponse_ExistingResponse) { - auto patchResponse = createTestPatchResponse(1); - EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); - - auto result = service->getPatchResponse(1); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->PatchData.ID, 1); - EXPECT_EQ(result->PatchData.Title, "Test Game 1"); -} - -TEST_F(AchievementServiceTest, GetPatchResponse_NotFound) { - auto result = service->getPatchResponse(999); - - EXPECT_FALSE(result.has_value()); -} - -// ProcessPatchResponse Tests - -TEST_F(AchievementServiceTest, ProcessPatchResponse_Success) { - auto patchResponse = createTestPatchResponse(1); - - bool result = service->processPatchResponse("testuser", patchResponse); - - EXPECT_TRUE(result); - - // Verify the achievement set was created - auto savedResponse = service->getPatchResponse(1); - ASSERT_TRUE(savedResponse.has_value()); - EXPECT_EQ(savedResponse->PatchData.Title, "Test Game 1"); -} - -TEST_F(AchievementServiceTest, ProcessPatchResponse_CreatesUserUnlocks) { - auto patchResponse = createTestPatchResponse(1); - - bool result = service->processPatchResponse("testuser", patchResponse); - - EXPECT_TRUE(result); - - // Verify user unlocks were created for both achievements - auto unlock1 = service->getUserUnlock("testuser", 1); - auto unlock2 = service->getUserUnlock("testuser", 2); - - ASSERT_TRUE(unlock1.has_value()); - ASSERT_TRUE(unlock2.has_value()); - - EXPECT_FALSE(unlock1->earned); - EXPECT_FALSE(unlock1->earnedHardcore); - EXPECT_TRUE(unlock1->synced); - - EXPECT_FALSE(unlock2->earned); - EXPECT_FALSE(unlock2->earnedHardcore); - EXPECT_TRUE(unlock2->synced); -} - -TEST_F(AchievementServiceTest, ProcessPatchResponse_SkipsInactiveAchievements) { - std::vector achievements = { - createTestPatchAchievement(1, 3, 50), // Active (flags = 3) - createTestPatchAchievement( - 2, 5, 25), // Inactive (flags = 5) - should be skipped - createTestPatchAchievement(3, 3, 75) // Active (flags = 3) - }; - - auto patchResponse = createTestPatchResponse(1, achievements); - - bool result = service->processPatchResponse("testuser", patchResponse); - - EXPECT_TRUE(result); - - // Verify only active achievements have unlocks created - auto unlock1 = service->getUserUnlock("testuser", 1); - auto unlock2 = service->getUserUnlock("testuser", 2); - auto unlock3 = service->getUserUnlock("testuser", 3); - - EXPECT_TRUE(unlock1.has_value()); // Active - EXPECT_FALSE(unlock2.has_value()); // Inactive - no unlock created - EXPECT_TRUE(unlock3.has_value()); // Active -} - -TEST_F(AchievementServiceTest, ProcessPatchResponse_CalculatesCorrectPoints) { - std::vector achievements = { - createTestPatchAchievement(1, 3, 50), // Active - 50 points - createTestPatchAchievement( - 2, 5, 25), // Inactive - 25 points (should be excluded) - createTestPatchAchievement(3, 3, 75) // Active - 75 points - }; - - auto patchResponse = createTestPatchResponse(1, achievements); - - bool result = service->processPatchResponse("testuser", patchResponse); - - EXPECT_TRUE(result); - - // Get the created achievement set via hash mapping - EXPECT_TRUE(service->setGameId("test_hash", 1)); - auto achievementSet = service->getAchievementSetByContentHash("test_hash"); - - ASSERT_TRUE(achievementSet.has_value()); - EXPECT_EQ(achievementSet->totalPoints, 125); // 50 + 75 (excluding inactive) - EXPECT_EQ(achievementSet->numAchievements, 2); // Only active achievements -} - -TEST_F(AchievementServiceTest, - ProcessPatchResponse_DoesNotOverwriteExistingUnlocks) { - auto patchResponse = createTestPatchResponse(1); - - // Process once - EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); - - // Manually update an unlock - auto unlock = service->getUserUnlock("testuser", 1); - ASSERT_TRUE(unlock.has_value()); - unlock->earned = true; - unlock->unlockTimestamp = 1609545600; - EXPECT_TRUE(repository->createOrUpdate(*unlock)); - - // Process again - should not overwrite existing unlock - EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); - - auto updatedUnlock = service->getUserUnlock("testuser", 1); - ASSERT_TRUE(updatedUnlock.has_value()); - EXPECT_TRUE(updatedUnlock->earned); // Should still be earned - EXPECT_EQ(updatedUnlock->unlockTimestamp, - 1609545600); // Should keep timestamp -} - -TEST_F(AchievementServiceTest, ProcessPatchResponse_EmptyAchievements) { - std::vector emptyAchievements; - auto patchResponse = createTestPatchResponse(99, emptyAchievements); - - bool result = service->processPatchResponse("testuser", patchResponse); - - EXPECT_TRUE(result); - - // Verify achievement set was still created but with no achievements - EXPECT_TRUE(service->setGameId("test_hash_empty", 99)); - auto achievementSet = - service->getAchievementSetByContentHash("test_hash_empty"); - - ASSERT_TRUE(achievementSet.has_value()); - EXPECT_EQ(achievementSet->numAchievements, 0); - EXPECT_EQ(achievementSet->totalPoints, 0); -} - -TEST_F(AchievementServiceTest, ProcessPatchResponse_MultipleUsers) { - auto patchResponse = createTestPatchResponse(1); - - bool result1 = service->processPatchResponse("user1", patchResponse); - bool result2 = service->processPatchResponse("user2", patchResponse); - - EXPECT_TRUE(result1); - EXPECT_TRUE(result2); - - // Verify both users have separate unlocks - auto unlock1_user1 = service->getUserUnlock("user1", 1); - auto unlock1_user2 = service->getUserUnlock("user2", 1); - - ASSERT_TRUE(unlock1_user1.has_value()); - ASSERT_TRUE(unlock1_user2.has_value()); - - EXPECT_EQ(unlock1_user1->username, "user1"); - EXPECT_EQ(unlock1_user2->username, "user2"); -} +// TEST_F(AchievementServiceTest, GetPatchResponse_ExistingResponse) { +// auto patchResponse = createTestPatchResponse(1); +// EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); +// +// auto result = service->getPatchResponse(1); +// +// ASSERT_TRUE(result.has_value()); +// EXPECT_EQ(result->PatchData.ID, 1); +// EXPECT_EQ(result->PatchData.Title, "Test Game 1"); +// } // ProcessStartSessionResponse Tests -TEST_F(AchievementServiceTest, ProcessStartSessionResponse_Success) { - // Set up achievements first - auto patchResponse = createTestPatchResponse(1); - EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); - - std::vector unlocks = {createTestUnlock(1, 1609459200)}; - std::vector hardcoreUnlocks = {createTestUnlock(2, 1609545600)}; - auto startSessionResponse = - createTestStartSessionResponse(unlocks, hardcoreUnlocks); - - bool result = - service->processStartSessionResponse("testuser", 1, startSessionResponse); - - EXPECT_TRUE(result); - - // Verify normal unlock - auto unlock1 = service->getUserUnlock("testuser", 1); - ASSERT_TRUE(unlock1.has_value()); - EXPECT_TRUE(unlock1->earned); - EXPECT_EQ(unlock1->unlockTimestamp, 1609459200); - EXPECT_TRUE(unlock1->synced); - - // Verify hardcore unlock - auto unlock2 = service->getUserUnlock("testuser", 2); - ASSERT_TRUE(unlock2.has_value()); - EXPECT_TRUE(unlock2->earnedHardcore); - EXPECT_EQ(unlock2->unlockTimestampHardcore, 1609545600); -} +// TEST_F(AchievementServiceTest, ProcessStartSessionResponse_Success) { +// // Set up achievements first +// auto patchResponse = createTestPatchResponse(1); +// EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); +// +// std::vector unlocks = {createTestUnlock(1, 1609459200)}; +// std::vector hardcoreUnlocks = {createTestUnlock(2, 1609545600)}; +// auto startSessionResponse = +// createTestStartSessionResponse(unlocks, hardcoreUnlocks); +// +// bool result = +// service->processStartSessionResponse("testuser", 1, +// startSessionResponse); +// +// EXPECT_TRUE(result); +// +// // Verify normal unlock +// auto unlock1 = service->getUserUnlock("testuser", 1); +// ASSERT_TRUE(unlock1.has_value()); +// EXPECT_TRUE(unlock1->earned); +// EXPECT_EQ(unlock1->unlockTimestamp, 1609459200); +// EXPECT_TRUE(unlock1->synced); +// +// // Verify hardcore unlock +// auto unlock2 = service->getUserUnlock("testuser", 2); +// ASSERT_TRUE(unlock2.has_value()); +// EXPECT_TRUE(unlock2->earnedHardcore); +// EXPECT_EQ(unlock2->unlockTimestampHardcore, 1609545600); +// } TEST_F(AchievementServiceTest, ProcessStartSessionResponse_CreatesNewUnlocks) { std::vector unlocks = {createTestUnlock(999, 1609459200)}; @@ -645,86 +493,88 @@ TEST_F(AchievementServiceTest, EXPECT_TRUE(unsupportedUnlock->synced); } -TEST_F(AchievementServiceTest, - ProcessStartSessionResponse_RelocksRemovedAchievements) { - // Set up achievements first - auto patchResponse = createTestPatchResponse(1); - EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); - - // Manually set achievements as earned - auto unlock1 = service->getUserUnlock("testuser", 1); - auto unlock2 = service->getUserUnlock("testuser", 2); - ASSERT_TRUE(unlock1.has_value() && unlock2.has_value()); - - unlock1->earned = true; - unlock1->unlockTimestamp = 1609459200; - unlock2->earned = true; - unlock2->unlockTimestamp = 1609459200; - - EXPECT_TRUE(repository->createOrUpdate(*unlock1)); - EXPECT_TRUE(repository->createOrUpdate(*unlock2)); - - // Process start session with only achievement 1 unlocked - std::vector unlocks = {createTestUnlock(1, 1609459200)}; - auto startSessionResponse = createTestStartSessionResponse(unlocks, {}); - - bool result = - service->processStartSessionResponse("testuser", 1, startSessionResponse); - - EXPECT_TRUE(result); - - // Achievement 1 should remain earned - auto updatedUnlock1 = service->getUserUnlock("testuser", 1); - ASSERT_TRUE(updatedUnlock1.has_value()); - EXPECT_TRUE(updatedUnlock1->earned); - - // Achievement 2 should be re-locked - auto updatedUnlock2 = service->getUserUnlock("testuser", 2); - ASSERT_TRUE(updatedUnlock2.has_value()); - EXPECT_FALSE(updatedUnlock2->earned); - EXPECT_EQ(updatedUnlock2->unlockTimestamp, 0); -} - -TEST_F(AchievementServiceTest, - ProcessStartSessionResponse_RelocksRemovedHardcoreAchievements) { - // Set up achievements first - auto patchResponse = createTestPatchResponse(1); - EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); - - // Manually set achievements as hardcore earned - auto unlock1 = service->getUserUnlock("testuser", 1); - auto unlock2 = service->getUserUnlock("testuser", 2); - ASSERT_TRUE(unlock1.has_value() && unlock2.has_value()); - - unlock1->earnedHardcore = true; - unlock1->unlockTimestampHardcore = 1609459200; - unlock2->earnedHardcore = true; - unlock2->unlockTimestampHardcore = 1609459200; - - EXPECT_TRUE(repository->createOrUpdate(*unlock1)); - EXPECT_TRUE(repository->createOrUpdate(*unlock2)); - - // Process start session with only achievement 1 hardcore unlocked - std::vector hardcoreUnlocks = {createTestUnlock(1, 1609459200)}; - auto startSessionResponse = - createTestStartSessionResponse({}, hardcoreUnlocks); - - bool result = - service->processStartSessionResponse("testuser", 1, startSessionResponse); - - EXPECT_TRUE(result); - - // Achievement 1 hardcore should remain earned - auto updatedUnlock1 = service->getUserUnlock("testuser", 1); - ASSERT_TRUE(updatedUnlock1.has_value()); - EXPECT_TRUE(updatedUnlock1->earnedHardcore); - - // Achievement 2 hardcore should be re-locked - auto updatedUnlock2 = service->getUserUnlock("testuser", 2); - ASSERT_TRUE(updatedUnlock2.has_value()); - EXPECT_FALSE(updatedUnlock2->earnedHardcore); - EXPECT_EQ(updatedUnlock2->unlockTimestampHardcore, 0); -} +// TEST_F(AchievementServiceTest, +// ProcessStartSessionResponse_RelocksRemovedAchievements) { +// // Set up achievements first +// auto patchResponse = createTestPatchResponse(1); +// EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); +// +// // Manually set achievements as earned +// auto unlock1 = service->getUserUnlock("testuser", 1); +// auto unlock2 = service->getUserUnlock("testuser", 2); +// ASSERT_TRUE(unlock1.has_value() && unlock2.has_value()); +// +// unlock1->earned = true; +// unlock1->unlockTimestamp = 1609459200; +// unlock2->earned = true; +// unlock2->unlockTimestamp = 1609459200; +// +// EXPECT_TRUE(repository->createOrUpdate(*unlock1)); +// EXPECT_TRUE(repository->createOrUpdate(*unlock2)); +// +// // Process start session with only achievement 1 unlocked +// std::vector unlocks = {createTestUnlock(1, 1609459200)}; +// auto startSessionResponse = createTestStartSessionResponse(unlocks, {}); +// +// bool result = +// service->processStartSessionResponse("testuser", 1, +// startSessionResponse); +// +// EXPECT_TRUE(result); +// +// // Achievement 1 should remain earned +// auto updatedUnlock1 = service->getUserUnlock("testuser", 1); +// ASSERT_TRUE(updatedUnlock1.has_value()); +// EXPECT_TRUE(updatedUnlock1->earned); +// +// // Achievement 2 should be re-locked +// auto updatedUnlock2 = service->getUserUnlock("testuser", 2); +// ASSERT_TRUE(updatedUnlock2.has_value()); +// EXPECT_FALSE(updatedUnlock2->earned); +// EXPECT_EQ(updatedUnlock2->unlockTimestamp, 0); +// } + +// TEST_F(AchievementServiceTest, +// ProcessStartSessionResponse_RelocksRemovedHardcoreAchievements) { +// // Set up achievements first +// auto patchResponse = createTestPatchResponse(1); +// EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); +// +// // Manually set achievements as hardcore earned +// auto unlock1 = service->getUserUnlock("testuser", 1); +// auto unlock2 = service->getUserUnlock("testuser", 2); +// ASSERT_TRUE(unlock1.has_value() && unlock2.has_value()); +// +// unlock1->earnedHardcore = true; +// unlock1->unlockTimestampHardcore = 1609459200; +// unlock2->earnedHardcore = true; +// unlock2->unlockTimestampHardcore = 1609459200; +// +// EXPECT_TRUE(repository->createOrUpdate(*unlock1)); +// EXPECT_TRUE(repository->createOrUpdate(*unlock2)); +// +// // Process start session with only achievement 1 hardcore unlocked +// std::vector hardcoreUnlocks = {createTestUnlock(1, 1609459200)}; +// auto startSessionResponse = +// createTestStartSessionResponse({}, hardcoreUnlocks); +// +// bool result = +// service->processStartSessionResponse("testuser", 1, +// startSessionResponse); +// +// EXPECT_TRUE(result); +// +// // Achievement 1 hardcore should remain earned +// auto updatedUnlock1 = service->getUserUnlock("testuser", 1); +// ASSERT_TRUE(updatedUnlock1.has_value()); +// EXPECT_TRUE(updatedUnlock1->earnedHardcore); +// +// // Achievement 2 hardcore should be re-locked +// auto updatedUnlock2 = service->getUserUnlock("testuser", 2); +// ASSERT_TRUE(updatedUnlock2.has_value()); +// EXPECT_FALSE(updatedUnlock2->earnedHardcore); +// EXPECT_EQ(updatedUnlock2->unlockTimestampHardcore, 0); +// } TEST_F(AchievementServiceTest, ProcessStartSessionResponse_EmptyResponse) { auto startSessionResponse = createTestStartSessionResponse({}, {}); @@ -741,106 +591,109 @@ TEST_F(AchievementServiceTest, ProcessStartSessionResponse_EmptyResponse) { EXPECT_FALSE(unsupportedUnlock->earned); } -TEST_F(AchievementServiceTest, - ProcessStartSessionResponse_MixedNormalAndHardcore) { - // Set up achievements first - auto patchResponse = createTestPatchResponse(1); - EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); - - // Different achievements unlocked in each mode - std::vector unlocks = {createTestUnlock(1, 1609459200)}; - std::vector hardcoreUnlocks = {createTestUnlock(2, 1609545600)}; - auto startSessionResponse = - createTestStartSessionResponse(unlocks, hardcoreUnlocks); - - bool result = - service->processStartSessionResponse("testuser", 1, startSessionResponse); - - EXPECT_TRUE(result); - - // Verify normal unlock - auto unlock1 = service->getUserUnlock("testuser", 1); - ASSERT_TRUE(unlock1.has_value()); - EXPECT_TRUE(unlock1->earned); - EXPECT_FALSE(unlock1->earnedHardcore); - EXPECT_EQ(unlock1->unlockTimestamp, 1609459200); - EXPECT_EQ(unlock1->unlockTimestampHardcore, 0); - EXPECT_TRUE(unlock1->synced); - - // Verify hardcore unlock - auto unlock2 = service->getUserUnlock("testuser", 2); - ASSERT_TRUE(unlock2.has_value()); - EXPECT_TRUE(unlock2->earned); - EXPECT_TRUE(unlock2->earnedHardcore); - EXPECT_EQ(unlock2->unlockTimestamp, 1609545600); - EXPECT_EQ(unlock2->unlockTimestampHardcore, 1609545600); - EXPECT_TRUE(unlock2->synced); -} - -TEST_F(AchievementServiceTest, - ProcessStartSessionResponse_HardcoreUnlockSetsEarnedTrue) { - // Set up achievements first - auto patchResponse = createTestPatchResponse(1); - EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); - - // Achievement unlocked ONLY in hardcore mode (no non-hardcore unlock) - std::vector unlocks = {}; - std::vector hardcoreUnlocks = {createTestUnlock(1, 1609545600)}; - auto startSessionResponse = - createTestStartSessionResponse(unlocks, hardcoreUnlocks); - - bool result = - service->processStartSessionResponse("testuser", 1, startSessionResponse); - - EXPECT_TRUE(result); - - auto unlock = service->getUserUnlock("testuser", 1); - ASSERT_TRUE(unlock.has_value()); - - // Hardcore unlock should set BOTH earned and earnedHardcore to true - EXPECT_TRUE(unlock->earned); - EXPECT_TRUE(unlock->earnedHardcore); - EXPECT_EQ(unlock->unlockTimestamp, - 1609545600); // Should use hardcore timestamp - EXPECT_EQ(unlock->unlockTimestampHardcore, 1609545600); - EXPECT_TRUE(unlock->synced); -} - -TEST_F(AchievementServiceTest, - ProcessStartSessionResponse_HardcoreWithExistingNonHardcore) { - // Set up achievements first - auto patchResponse = createTestPatchResponse(1); - EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); - - // Different achievements unlocked in different modes - std::vector unlocks = {createTestUnlock(1, 1609459200)}; - std::vector hardcoreUnlocks = {createTestUnlock(2, 1609545600)}; - auto startSessionResponse = - createTestStartSessionResponse(unlocks, hardcoreUnlocks); - - bool result = - service->processStartSessionResponse("testuser", 1, startSessionResponse); - - EXPECT_TRUE(result); - - // Verify normal unlock - auto unlock1 = service->getUserUnlock("testuser", 1); - ASSERT_TRUE(unlock1.has_value()); - EXPECT_TRUE(unlock1->earned); - EXPECT_FALSE(unlock1->earnedHardcore); - EXPECT_EQ(unlock1->unlockTimestamp, 1609459200); - EXPECT_EQ(unlock1->unlockTimestampHardcore, 0); - EXPECT_TRUE(unlock1->synced); - - // Verify hardcore unlock (which sets both earned flags) - auto unlock2 = service->getUserUnlock("testuser", 2); - ASSERT_TRUE(unlock2.has_value()); - EXPECT_TRUE(unlock2->earned); - EXPECT_TRUE(unlock2->earnedHardcore); - EXPECT_EQ(unlock2->unlockTimestamp, 1609545600); - EXPECT_EQ(unlock2->unlockTimestampHardcore, 1609545600); - EXPECT_TRUE(unlock2->synced); -} +// TEST_F(AchievementServiceTest, +// ProcessStartSessionResponse_MixedNormalAndHardcore) { +// // Set up achievements first +// auto patchResponse = createTestPatchResponse(1); +// EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); +// +// // Different achievements unlocked in each mode +// std::vector unlocks = {createTestUnlock(1, 1609459200)}; +// std::vector hardcoreUnlocks = {createTestUnlock(2, 1609545600)}; +// auto startSessionResponse = +// createTestStartSessionResponse(unlocks, hardcoreUnlocks); +// +// bool result = +// service->processStartSessionResponse("testuser", 1, +// startSessionResponse); +// +// EXPECT_TRUE(result); +// +// // Verify normal unlock +// auto unlock1 = service->getUserUnlock("testuser", 1); +// ASSERT_TRUE(unlock1.has_value()); +// EXPECT_TRUE(unlock1->earned); +// EXPECT_FALSE(unlock1->earnedHardcore); +// EXPECT_EQ(unlock1->unlockTimestamp, 1609459200); +// EXPECT_EQ(unlock1->unlockTimestampHardcore, 0); +// EXPECT_TRUE(unlock1->synced); +// +// // Verify hardcore unlock +// auto unlock2 = service->getUserUnlock("testuser", 2); +// ASSERT_TRUE(unlock2.has_value()); +// EXPECT_TRUE(unlock2->earned); +// EXPECT_TRUE(unlock2->earnedHardcore); +// EXPECT_EQ(unlock2->unlockTimestamp, 1609545600); +// EXPECT_EQ(unlock2->unlockTimestampHardcore, 1609545600); +// EXPECT_TRUE(unlock2->synced); +// } + +// TEST_F(AchievementServiceTest, +// ProcessStartSessionResponse_HardcoreUnlockSetsEarnedTrue) { +// // Set up achievements first +// auto patchResponse = createTestPatchResponse(1); +// EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); +// +// // Achievement unlocked ONLY in hardcore mode (no non-hardcore unlock) +// std::vector unlocks = {}; +// std::vector hardcoreUnlocks = {createTestUnlock(1, 1609545600)}; +// auto startSessionResponse = +// createTestStartSessionResponse(unlocks, hardcoreUnlocks); +// +// bool result = +// service->processStartSessionResponse("testuser", 1, +// startSessionResponse); +// +// EXPECT_TRUE(result); +// +// auto unlock = service->getUserUnlock("testuser", 1); +// ASSERT_TRUE(unlock.has_value()); +// +// // Hardcore unlock should set BOTH earned and earnedHardcore to true +// EXPECT_TRUE(unlock->earned); +// EXPECT_TRUE(unlock->earnedHardcore); +// EXPECT_EQ(unlock->unlockTimestamp, +// 1609545600); // Should use hardcore timestamp +// EXPECT_EQ(unlock->unlockTimestampHardcore, 1609545600); +// EXPECT_TRUE(unlock->synced); +// } + +// TEST_F(AchievementServiceTest, +// ProcessStartSessionResponse_HardcoreWithExistingNonHardcore) { +// // Set up achievements first +// auto patchResponse = createTestPatchResponse(1); +// EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); +// +// // Different achievements unlocked in different modes +// std::vector unlocks = {createTestUnlock(1, 1609459200)}; +// std::vector hardcoreUnlocks = {createTestUnlock(2, 1609545600)}; +// auto startSessionResponse = +// createTestStartSessionResponse(unlocks, hardcoreUnlocks); +// +// bool result = +// service->processStartSessionResponse("testuser", 1, +// startSessionResponse); +// +// EXPECT_TRUE(result); +// +// // Verify normal unlock +// auto unlock1 = service->getUserUnlock("testuser", 1); +// ASSERT_TRUE(unlock1.has_value()); +// EXPECT_TRUE(unlock1->earned); +// EXPECT_FALSE(unlock1->earnedHardcore); +// EXPECT_EQ(unlock1->unlockTimestamp, 1609459200); +// EXPECT_EQ(unlock1->unlockTimestampHardcore, 0); +// EXPECT_TRUE(unlock1->synced); +// +// // Verify hardcore unlock (which sets both earned flags) +// auto unlock2 = service->getUserUnlock("testuser", 2); +// ASSERT_TRUE(unlock2.has_value()); +// EXPECT_TRUE(unlock2->earned); +// EXPECT_TRUE(unlock2->earnedHardcore); +// EXPECT_EQ(unlock2->unlockTimestamp, 1609545600); +// EXPECT_EQ(unlock2->unlockTimestampHardcore, 1609545600); +// EXPECT_TRUE(unlock2->synced); +// } // Edge Cases and Error Handling @@ -861,25 +714,6 @@ TEST_F(AchievementServiceTest, ProcessStartSessionResponse_NonExistentSetId) { EXPECT_TRUE(unlock->earned); } -TEST_F(AchievementServiceTest, ProcessPatchResponse_DuplicateProcessing) { - auto patchResponse = createTestPatchResponse(1); - - // Process the same response twice - EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); - EXPECT_TRUE(service->processPatchResponse("testuser", patchResponse)); - - // Should not create duplicate data - auto unlock1 = service->getUserUnlock("testuser", 1); - auto unlock2 = service->getUserUnlock("testuser", 2); - - EXPECT_TRUE(unlock1.has_value()); - EXPECT_TRUE(unlock2.has_value()); - - // Verify the cached response exists - auto cachedResponse = service->getPatchResponse(1); - EXPECT_TRUE(cachedResponse.has_value()); -} - TEST_F(AchievementServiceTest, ProcessStartSessionResponse_ZeroTimestamps) { std::vector unlocks = {createTestUnlock(1, 0)}; auto startSessionResponse = createTestStartSessionResponse(unlocks, {}); diff --git a/tests/app/rcheevos/rcheevos_offline_client_test.cpp b/tests/app/rcheevos/rcheevos_offline_client_test.cpp index b82b0f10..b74325a8 100644 --- a/tests/app/rcheevos/rcheevos_offline_client_test.cpp +++ b/tests/app/rcheevos/rcheevos_offline_client_test.cpp @@ -9,2476 +9,7 @@ namespace firelight::achievements { -class RetroAchievementsOfflineClientTest : public testing::Test { -protected: - std::string patchData = - "{\"Success\":true,\"PatchData\":{\"ID\":228,\"Title\":\"Super Mario " - "World\",\"ImageIcon\":\"\\/Images\\/" - "066393.png\",\"RichPresencePatch\":\"\",\"ConsoleID\":3," - "\"ImageIconURL\":\"https:\\/\\/media.retroachievements.org\\/Images\\/" - "066393.png\",\"Achievements\":[{\"ID\":4934,\"MemAddr\":\"R:0xH0013c7=" - "4\",\"Title\":\"Yellow Yoshi (old prototype)\",\"Description\":\"old " - "abandoned " - "prototype\",\"Points\":0,\"Author\":\"UNHchabo\",\"Modified\":" - "1504474480,\"Created\":1392011140,\"BadgeName\":\"00080\",\"Flags\":5," - "\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "00080.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "00080_lock.png\"},{\"ID\":4935,\"MemAddr\":\"R:0xH0013c7=6\",\"Title\":" - "\"Blue Yoshi (old prototype)\",\"Description\":\"old abandoned " - "prototype\",\"Points\":0,\"Author\":\"UNHchabo\",\"Modified\":" - "1504474488,\"Created\":1392011155,\"BadgeName\":\"00080\",\"Flags\":5," - "\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "00080.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "00080_lock.png\"},{\"ID\":4936,\"MemAddr\":\"R:0xH0013c7=8\",\"Title\":" - "\"Red Yoshi (old prototype)\",\"Description\":\"old abandoned " - "prototype\",\"Points\":0,\"Author\":\"UNHchabo\",\"Modified\":" - "1504474493,\"Created\":1392011190,\"BadgeName\":\"00080\",\"Flags\":5," - "\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "00080.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "00080_lock.png\"},{\"ID\":4937,\"MemAddr\":\"R:0xH000dbe>=98_R:" - "0xH000019=1\",\"Title\":\"Super 99 (old " - "prototype)\",\"Description\":\"Get 99 Lives and be not " - "small\",\"Points\":0,\"Author\":\"UNHchabo\",\"Modified\":1504474496," - "\"Created\":1392018537,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "00080.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "00080_lock.png\"},{\"ID\":5756,\"MemAddr\":\"R:0xH0013c5>0\",\"Title\":" - "\"Moon (old prototype)\",\"Description\":\"Collect a 3-up " - "Moon\",\"Points\":0,\"Author\":\"UNHchabo\",\"Modified\":1504474501," - "\"Created\":1393710657,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "00080.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "00080_lock.png\"},{\"ID\":45815,\"MemAddr\":\"0xT001ecc>d0xT001ecc.1._R:" - "0xH000db5=0_R:0xH000db3=1_0xT001ec9>d0xT001ec9.1._0xT001ec8>d0xT001ec8." - "1._0xT001ec7>d0xT001ec7.1._R:0xH000019>0_R:0xH001490>0_R:0xH001f28>0_R:" - "0xH001f29>0_R:0x " - "001f2a>0_0xH001ecc=0.1._R:0xH00187a>0_0xT001eb7>d0xT001eb7.1._0xT001eab>" - "d0xT001eab.1._0xT001ea6>d0xT001ea6.1._0xT001ea8>d0xT001ea8.1._0xT001ea9>" - "d0xT001ea9.1._0xT001ee0>d0xT001ee0.1._0xT001ede>d0xT001ede.1._0xT001ed0>" - "d0xT001ed0.1._0xT001edf>d0xT001edf.1._0xT001ee2>d0xT001ee2.1._0xT001ea7>" - "d0xT001ea7.1.\",\"Title\":\"Mini-Mario vs the World 1 " - "(duplicate)\",\"Description\":\"not an achievement is a " - "duplicate.\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474529," - "\"Created\":1488837144,\"BadgeName\":\"46672\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46672.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46672_lock.png\"},{\"ID\":45857,\"MemAddr\":\"R:0xH000100<=10_R:" - "0xH000db3=1_R:0xH000dbed0xT001ecb.1._0xT001ecc>" - "d0xT001ecc.1.\",\"Title\":\"Deathless Two Stages " - "(prototype)\",\"Description\":\"prototype - not an " - "achievement\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":" - "1504474132,\"Created\":1488840902,\"BadgeName\":\"00000\",\"Flags\":5," - "\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "00000.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "00000_lock.png\"},{\"ID\":45998,\"MemAddr\":\"R:0xH0013ef=1_R:0xH0013bf!" - "=1_0xH000dbe>d0xH000dbe.20._0xH001411=1\",\"Title\":\"I could do this " - "all day (unofficial)\",\"Description\":\"Get 20 1-ups on Vanilla Secret " - "2 without touching the " - "ground\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474504," - "\"Created\":1489073027,\"BadgeName\":\"46617\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46617.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46617_lock.png\"},{\"ID\":46009,\"MemAddr\":\"R:0xH0013bf!=42_" - "d0xH0014c9=8_d0xH0014ca=8_d0xH0014cb=8_d0xH0014cc=8_d0xH0014cd=8_" - "d0xH0014ce=8_d0xH0014cf=8_d0xH0014d0=8_0xH0014c9=9_0xH0014ca=9_" - "0xH0014cb=9_0xH0014cc=9_0xH0014cd=9_0xH0014ce=9_0xH0014cf=9_0xH0014d0=9_" - "0xH0013ef=1_0x 001408=51201\",\"Title\":\"A Mighty Quake " - "(duplicate)\",\"Description\":\"(duplicate)\",\"Points\":0,\"Author\":" - "\"kdecks\",\"Modified\":1504474509,\"Created\":1489073081,\"BadgeName\":" - "\"46678\",\"Flags\":5,\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0," - "\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/" - "46678.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46678_lock.png\"},{\"ID\":46011,\"MemAddr\":\"R:0xH0013bf!=76_0xH001dff=" - "12_R:0xH0013ef=1_0x 000094<464.1._R:0xH00187a>0\",\"Title\":\"A Groovy " - "Flight (duplicate)\",\"Description\":\"Pass SPECIAL-Groovy flying. No " - "landing past the first ? box. No " - "Yoshi\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474513," - "\"Created\":1489086171,\"BadgeName\":\"46671\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46671.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46671_lock.png\"},{\"ID\":46062,\"MemAddr\":\"0xH0013f3=1.20._0xH001411=" - "1.20._R:0xH001411=0_0xH000019!=0_R:0xH000019=0\",\"Title\":\"Another " - "Kind of Flying (legacy)\",\"Description\":\"Collect a " - "P-Balloon\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474516," - "\"Created\":1489171212,\"BadgeName\":\"46564\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46564.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46564_lock.png\"},{\"ID\":46119,\"MemAddr\":\"P:0xH0013bf!=49_0xH000065=" - "6.1._P:0xH000100!=15_0xH000065=100.1._0xH000065=1.1._0xH000065=62.1._" - "0xH000065=96.1._0xH000065=21.1._0xH000065=118.1._0xH000065=168.1.\"," - "\"Title\":\"Bowser Castle Explorer (dup)\",\"Description\":\"Complete " - "all 8 numbered rooms in one game " - "session\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474465," - "\"Created\":1489285071,\"BadgeName\":\"46769\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46769.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46769_lock.png\"},{\"ID\":46330,\"MemAddr\":\"R:0xH0013bf!=46_R:0x " - "000094<3926_R:0x " - "000094>4512_R:0xH0014d0=2_R:0xH0014d0=3_R:0xH0014d0=4_0x " - "000094>4369_0xH0014d0=8\",\"Title\":\"Suicide Prevention " - "(duplicate)\",\"Description\":\"O n Vanilla Dome 3 Stop the koopa by " - "the midflag from getting " - "killed\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474531," - "\"Created\":1489701495,\"BadgeName\":\"00000\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "00000.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "00000_lock.png\"},{\"ID\":59018,\"MemAddr\":\"B:0xH0dbf=0_d0xH0dbf=" - "4294967295_0xH0dbf=5\",\"Title\":\"bug\",\"Description\":\"buggy\"," - "\"Points\":1,\"Author\":\"April\",\"Modified\":1549934619,\"Created\":" - "1522626529,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":null,\"Rarity\":" - "0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "00080.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "00080_lock.png\"},{\"ID\":85062,\"MemAddr\":\"0xH000071=9\",\"Title\":" - "\"I failed you, my princess\",\"Description\":\"die for the first " - "time\",\"Points\":0,\"Author\":\"matisteve\",\"Modified\":1570325049," - "\"Created\":1570325049,\"BadgeName\":\"92297\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "92297.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "92297_lock.png\"},{\"ID\":85977,\"MemAddr\":\"0xH000071=9\",\"Title\":" - "\"I failed you, my princess\",\"Description\":\"die for the first " - "time\",\"Points\":0,\"Author\":\"matisteveTWO\",\"Modified\":1571010788," - "\"Created\":1571010788,\"BadgeName\":\"92297\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "92297.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "92297_lock.png\"},{\"ID\":102935,\"MemAddr\":\"0xH000dbf=99\",\"Title\":" - "\"A Man's Worth of Coins\",\"Description\":\"Have 99 coins. Simple " - "enough.\",\"Points\":2,\"Author\":\"ApplemunchRA\",\"Modified\":" - "1584212198,\"Created\":1584212198,\"BadgeName\":\"00000\",\"Flags\":5," - "\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "00000.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "00000_lock.png\"},{\"ID\":113670,\"MemAddr\":\"0xH000065=51_Q:0xH0013bf=" - "7\",\"Title\":\"Hey, look There's a shortcut " - "here!\",\"Description\":\"Find the secret room on #2 " - "Castle\",\"Points\":5,\"Author\":\"Koakuma\",\"Modified\":1591759773," - "\"Created\":1591759773,\"BadgeName\":\"124010\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "124010.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "124010_lock.png\"},{\"ID\":128311,\"MemAddr\":\"0xH000f2c>=252\"," - "\"Title\":\"Wellcome\",\"Description\":\"Open Super Mario " - "Word\",\"Points\":1,\"Author\":\"freeman016\",\"Modified\":1602001511," - "\"Created\":1602001511,\"BadgeName\":\"00080\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "00080.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "00080_lock.png\"},{\"ID\":128312,\"MemAddr\":\"0xH000f2c>=252\"," - "\"Title\":\"Wellcome\",\"Description\":\"Open the " - "game\",\"Points\":1,\"Author\":\"freeman016\",\"Modified\":1602004389," - "\"Created\":1602004389,\"BadgeName\":\"141067\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "141067.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "141067_lock.png\"},{\"ID\":342,\"MemAddr\":\"0xH000dc1=1_0xH0013bf>0\"," - "\"Title\":\"Giddy Up!\",\"Description\":\"Catch a ride with a " - "friend\",\"Points\":1,\"Author\":\"Scott\",\"Modified\":1561707447," - "\"Created\":1367266931,\"BadgeName\":\"46580\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":94.83,\"RarityHardcore\":46.63,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "46580.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46580_lock.png\"},{\"ID\":341,\"MemAddr\":\"0x " - "001420=5\",\"Title\":\"Unleash The Dragon\",\"Description\":\"Collect 5 " - "Dragon Coins in a " - "level\",\"Points\":2,\"Author\":\"Scott\",\"Modified\":1561707451," - "\"Created\":1367266583,\"BadgeName\":\"46591\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":84.33,\"RarityHardcore\":42.96,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "46591.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46591_lock.png\"},{\"ID\":340,\"MemAddr\":\"0xH000dbf=99\",\"Title\":" - "\"Rich Mario (demoted)\",\"Description\":\"Collect 99 " - "coins\",\"Points\":0,\"Author\":\"Scott\",\"Modified\":1504474537," - "\"Created\":1367254976,\"BadgeName\":\"46582\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46582.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46582_lock.png\"},{\"ID\":4874,\"MemAddr\":\"0xH000019=2\",\"Title\":" - "\"I Believe I Can Fly\",\"Description\":\"Collect a " - "feather\",\"Points\":1,\"Author\":\"UNHchabo\",\"Modified\":1564447174," - "\"Created\":1391908064,\"BadgeName\":\"46577\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":60.99,\"RarityHardcore\":32.97,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "46577.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46577_lock.png\"},{\"ID\":2251,\"MemAddr\":\"0xH0013f3=1_0xH000dda!=0\"," - "\"Title\":\"Another Kind of Flying\",\"Description\":\"Collect a " - "P-Balloon\",\"Points\":2,\"Author\":\"Jaarl\",\"Modified\":1564448705," - "\"Created\":1376615078,\"BadgeName\":\"47083\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":34.48,\"RarityHardcore\":20.7,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "47083.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47083_lock.png\"},{\"ID\":4933,\"MemAddr\":\"0xH0018c2=1\",\"Title\":" - "\"Floating Through The Clouds\",\"Description\":\"Hijack a Lakitu's " - "cloud\",\"Points\":2,\"Author\":\"UNHchabo\",\"Modified\":1564450921," - "\"Created\":1392010935,\"BadgeName\":\"46571\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":22.91,\"RarityHardcore\":14.7,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "46571.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46571_lock.png\"},{\"ID\":1706,\"MemAddr\":\"0xH001900=80\",\"Title\":" - "\"Maximum Finish\",\"Description\":\"Cross the finish line at the end " - "of the stage and collect the max 50 " - "stars\",\"Points\":5,\"Author\":\"jackolantern\",\"Modified\":" - "1561707461,\"Created\":1372674230,\"BadgeName\":\"47084\",\"Flags\":3," - "\"Type\":null,\"Rarity\":13.06,\"RarityHardcore\":8.48,\"BadgeURL\":" - "\"https:\\/\\/media.retroachievements.org\\/Badge\\/" - "47084.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47084_lock.png\"},{\"ID\":2246,\"MemAddr\":\"0xH000f31=0.20._R:" - "0xH000f31!=0_0xH000f32=0.20._R:0xH000f32!=0_0xH000f33=0.20._R:0xH000f33!" - "=0_0xH000dbe>d0xH000dbe.8._0xH001411=1.20._R:0xH001411=0\",\"Title\":" - "\"Perfect Bonus Stage\",\"Description\":\"Score 8 extra lives in the " - "'Bonus " - "Game'\",\"Points\":10,\"Author\":\"Jaarl\",\"Modified\":1561707465," - "\"Created\":1376582613,\"BadgeName\":\"47085\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":12.36,\"RarityHardcore\":7.14,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "47085.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47085_lock.png\"},{\"ID\":46003,\"MemAddr\":\"0xH0018da=106_0xH0018e3=" - "10_0xH001411=1S0xH0016e1=13S0xH0016e2=13S0xH0016e3=13S0xH0016e4=" - "13S0xH0016e5=13S0xH0016e6=13\",\"Title\":\"Yoshi's a " - "Mommy?\",\"Description\":\"Help Yoshi lay a cloud egg and get 10 happy " - "coins to summon a " - "1up\",\"Points\":5,\"Author\":\"kdecks\",\"Modified\":1603575778," - "\"Created\":1489073051,\"BadgeName\":\"46668\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":8.86,\"RarityHardcore\":6.66,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46668.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46668_lock.png\"},{\"ID\":2253,\"MemAddr\":\"0xH0013bf=37_0xH000dd5=1\"," - "\"Title\":\"I is for Icky Iggy\",\"Description\":\"Defeat Iggy Koopa of " - "Castle " - "#1\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1564806766," - "\"Created\":1376616356,\"BadgeName\":\"46598\",\"Flags\":3,\"Type\":" - "\"progression\",\"Rarity\":62.79,\"RarityHardcore\":34.54,\"BadgeURL\":" - "\"https:\\/\\/media.retroachievements.org\\/Badge\\/" - "46598.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46598_lock.png\"},{\"ID\":347,\"MemAddr\":\"0xH0013bf=7_0xH000dd5=1\"," - "\"Title\":\"Morton Enough\",\"Description\":\"Defeat Morton Koopa Jr. " - "of Castle " - "#2\",\"Points\":5,\"Author\":\"Scott\",\"Modified\":1561707476," - "\"Created\":1367322700,\"BadgeName\":\"46599\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":36.4,\"RarityHardcore\":20.97,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "46599.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46599_lock.png\"},{\"ID\":2261,\"MemAddr\":\"0xH0013bf=64_0xH000dd5=1\"," - "\"Title\":\"Lemmy Down Slowly\",\"Description\":\"Defeat Lemmy Koopa of " - "Castle " - "#3\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1561707478," - "\"Created\":1376652522,\"BadgeName\":\"46600\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":24.01,\"RarityHardcore\":14.45,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "46600.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46600_lock.png\"},{\"ID\":2262,\"MemAddr\":\"0xH0013bf=14_0xH000dd5=1\"," - "\"Title\":\"Ludwig's Last Symphony\",\"Description\":\"Defeat Ludwig " - "von Koopa of Castle " - "#4\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1561707482," - "\"Created\":1376653163,\"BadgeName\":\"46601\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":22.74,\"RarityHardcore\":13.62,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "46601.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46601_lock.png\"},{\"ID\":2306,\"MemAddr\":\"0xH0013bf=32_0xH000dd5=1\"," - "\"Title\":\"Roy's Ploy Destroy-ed\",\"Description\":\"Defeat Roy Koopa " - "of Castle " - "#5\",\"Points\":10,\"Author\":\"Jaarl\",\"Modified\":1561707484," - "\"Created\":1376938808,\"BadgeName\":\"46602\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":17.77,\"RarityHardcore\":10.93,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "46602.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46602_lock.png\"},{\"ID\":2307,\"MemAddr\":\"0xH0013bf=31.20._0xH000906=" - "1.20._R:0xH001411!=1\",\"Title\":\"Reznor Again? " - "(demoted)\",\"Description\":\"Defeat the Reznor in the clearing of the " - "Forest of " - "Illusion\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474541," - "\"Created\":1376938811,\"BadgeName\":\"46597\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46597.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46597_lock.png\"},{\"ID\":2309,\"MemAddr\":\"0xH000906=1.400._R:" - "0xH0013bf!=26\",\"Title\":\"Wendy Chips Are " - "Down\",\"Description\":\"Defeat Wendy O. Koopa of Castle " - "#6\",\"Points\":10,\"Author\":\"Jaarl\",\"Modified\":1561707487," - "\"Created\":1376939582,\"BadgeName\":\"46603\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":15.19,\"RarityHardcore\":9.48,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "46603.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46603_lock.png\"},{\"ID\":2308,\"MemAddr\":\"0xH0013bf=27.20._0xH000906=" - "1.20._R:0xH001411!=1\",\"Title\":\"Reznor, Do You Ever Give Up? " - "(dmt)\",\"Description\":\"(demoted) Defeat the Reznor at the center of " - "Chocolate " - "Island\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474546," - "\"Created\":1376938815,\"BadgeName\":\"46597\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46597.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46597_lock.png\"},{\"ID\":2342,\"MemAddr\":\"0xH0013bf=52_0xH000dd5=1\"," - "\"Title\":\"Larry in the Airy\",\"Description\":\"Defeat Larry Koopa of " - "Castle " - "#7\",\"Points\":10,\"Author\":\"Jaarl\",\"Modified\":1561707489," - "\"Created\":1376970283,\"BadgeName\":\"46604\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":13,\"RarityHardcore\":8.27,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46604.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46604_lock.png\"},{\"ID\":2275,\"MemAddr\":\"0xH0013f9=3_0xH0013ef=1_O:" - "0xH0013bf=50_0xH0013bf=49\",\"Title\":\"Bowser " - "Disposer\",\"Description\":\"Beat Bowser and save the " - "princess\",\"Points\":25,\"Author\":\"Jaarl\",\"Modified\":1571582041," - "\"Created\":1376742802,\"BadgeName\":\"46760\",\"Flags\":3,\"Type\":" - "\"win_condition\",\"Rarity\":16.14,\"RarityHardcore\":10.13," - "\"BadgeURL\":\"https:\\/\\/media.retroachievements.org\\/Badge\\/" - "46760.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46760_lock.png\"},{\"ID\":2338,\"MemAddr\":\"0xH0013bf=53.20._0xH000906=" - "1.20._R:0xH001411!=1\",\"Title\":\"Reznor... " - "(demoted)\",\"Description\":\"Defeat the Reznor in the Valley of " - "Bowser\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474549," - "\"Created\":1376969412,\"BadgeName\":\"46597\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46597.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46597_lock.png\"},{\"ID\":2299,\"MemAddr\":\"0xH001f11=0_0xH0013bf=19_" - "0xH000dd5=2_0xH000dda=0\",\"Title\":\"Shoo! Shoo! Big " - "Boo\",\"Description\":\"Find and defeat the hidden Big Boo in Donut " - "Secret " - "House\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1564459621," - "\"Created\":1376918022,\"BadgeName\":\"46596\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":24.52,\"RarityHardcore\":16.54,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "46596.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46596_lock.png\"},{\"ID\":2250,\"MemAddr\":\"0xH0013bf=11_0xH000dd5=1\"," - "\"Title\":\"Reznor - Flaming Wheel of Death\",\"Description\":\"Defeat " - "the Reznor atop Vanilla " - "Dome\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1575640223," - "\"Created\":1376615073,\"BadgeName\":\"46597\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":19.72,\"RarityHardcore\":12.66,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "46597.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46597_lock.png\"},{\"ID\":2276,\"MemAddr\":\"0xH0013bf=49_R:0xH0013bf!=" - "49_0xH0013f9=3_R:0xH0013f9!=3_0xH0013ef=1_R:0xH0013ef!=1_0xH000019=0_R:" - "0xH000019!=0\",\"Title\":\"Baby's First Kiss " - "(demoted)\",\"Description\":\"Get the princess kiss as Little Mario " - "(Front " - "Door!)\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474554," - "\"Created\":1376742805,\"BadgeName\":\"46592\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46592.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46592_lock.png\"},{\"ID\":2277,\"MemAddr\":\"T:0xH0013f9=3_T:0xH0013ef=" - "1_0xH000019=3_T:0xH0003da=131_0xH0013bf=49\",\"Title\":\"Burning " - "Bowser\",\"Description\":\"Get the princess kiss as Fire Mario (Front " - "Door!)\",\"Points\":10,\"Author\":\"Jaarl\",\"Modified\":1625960412," - "\"Created\":1376742808,\"BadgeName\":\"46593\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":6.07,\"RarityHardcore\":4.85,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46593.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46593_lock.png\"},{\"ID\":2278,\"MemAddr\":\"0xH0013bf=49_R:0xH0013bf!=" - "49_0xH0013f9=3_R:0xH0013f9!=3_0xH0013ef=1_R:0xH0013ef!=1_0xH000019=2_R:" - "0xH000019!=2\",\"Title\":\"Flying Finish " - "(demoted)\",\"Description\":\"Get the princess kiss as Cape Mario " - "(Front " - "Door!)\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474556," - "\"Created\":1376742811,\"BadgeName\":\"46594\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46594.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46594_lock.png\"},{\"ID\":2345,\"MemAddr\":\"0xH0013ef=1_0xH0013f9=3_" - "0xH0013bf=50\",\"Title\":\"Backdooring Bowser " - "(demoted)\",\"Description\":\"Beat Bowser through the Back " - "Door\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474560," - "\"Created\":1376973534,\"BadgeName\":\"47082\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47082.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47082_lock.png\"},{\"ID\":2199,\"MemAddr\":\"A:d0xH001f28_A:d0xH001f27_" - "A:d0xH001f29_d0xH001f2a=3_A:0xH001f28_A:0xH001f27_A:0xH001f29_0xH001f2a=" - "4_C:0xH001f27=1.1._C:0xH001f28=1.1._C:0xH001f29=1.1._C:0xH001f2a=1.1._M:" - "0!=0.4._R:0x 001f28112_0xH000007<128_0xH000008>112_0xH000008<128\"," - "\"Title\":\"I Could've Sworn... (demoted)\",\"Description\":\"Get lost " - "in the Forest of " - "Illusion\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474567," - "\"Created\":1376657732,\"BadgeName\":\"47070\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47070.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47070_lock.png\"},{\"ID\":2305,\"MemAddr\":\"0xH00001e=169_0xH000020=28_" - "0xH001f11=0_0xH000dda=0_0xH0013d4=0\",\"Title\":\"Chocolate " - "Donut\",\"Description\":\"Walk in a circle on Chocolate " - "Island\",\"Points\":1,\"Author\":\"Jaarl\",\"Modified\":1571622934," - "\"Created\":1376938805,\"BadgeName\":\"47071\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":15.14,\"RarityHardcore\":9.61,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "47071.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47071_lock.png\"},{\"ID\":2298,\"MemAddr\":\"0xH0013c3=6\",\"Title\":" - "\"To the Stars!\",\"Description\":\"Reach the Star " - "Road\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1504475594," - "\"Created\":1376918019,\"BadgeName\":\"47072\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":28.61,\"RarityHardcore\":18.04,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "47072.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47072_lock.png\"},{\"ID\":2300,\"MemAddr\":\"0xH0013c3=5\",\"Title\":" - "\"Mario's Special Place\",\"Description\":\"Get to the challenging " - "Special " - "Zone\",\"Points\":5,\"Author\":\"Jaarl\",\"Modified\":1504475595," - "\"Created\":1376918026,\"BadgeName\":\"47073\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":16.61,\"RarityHardcore\":11.08,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "47073.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47073_lock.png\"},{\"ID\":2302,\"MemAddr\":\"N:d0xM001eea=0_0xM001eea=1." - "1._d0xH001f11=5_N:0xH001eec!=d0xH001eec_N:0xH001eed!=d0xH001eed_R:" - "0xH001eee!=d0xH001eeeS0xH001f11=1S0xH001f11=6\",\"Title\":\"Change of " - "Scenery\",\"Description\":\"Clear the Special Zone and change the " - "seasons in Dinosaur " - "Land\",\"Points\":25,\"Author\":\"Jaarl\",\"Modified\":1599744063," - "\"Created\":1376929303,\"BadgeName\":\"47074\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":10.71,\"RarityHardcore\":7.15,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "47074.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47074_lock.png\"},{\"ID\":2304,\"MemAddr\":\"R:0xH001f2e=1.1._C:0xH001f2e>=2.1._C:" - "0xH001f2e>=3.1._C:0xH001f2e>=4.1._C:0xH001f2e>=5.1._C:0xH001f2e>=6.1._C:" - "0xH001f2e>=7.1._C:0xH001f2e>=8.1._C:0xH001f2e>=9.1._C:0xH001f2e>=10.1._" - "C:0xH001f2e>=11.1._C:0xH001f2e>=12.1._C:0xH001f2e>=13.1._C:0xH001f2e>=" - "14.1._C:0xH001f2e>=15.1._C:0xH001f2e>=16.1._C:0xH001f2e>=17.1._C:" - "0xH001f2e>=18.1._C:0xH001f2e>=19.1._C:0xH001f2e>=20.1._C:0xH001f2e>=21." - "1._C:0xH001f2e>=22.1._C:0xH001f2e>=23.1._C:0xH001f2e>=24.1._C:0xH001f2e>" - "=25.1._C:0xH001f2e>=26.1._C:0xH001f2e>=27.1._C:0xH001f2e>=28.1._C:" - "0xH001f2e>=29.1._C:0xH001f2e>=30.1._C:0xH001f2e>=31.1._C:0xH001f2e>=32." - "1._C:0xH001f2e>=33.1._C:0xH001f2e>=34.1._C:0xH001f2e>=35.1._C:0xH001f2e>" - "=36.1._C:0xH001f2e>=37.1._C:0xH001f2e>=38.1._C:0xH001f2e>=39.1._C:" - "0xH001f2e>=40.1._C:0xH001f2e>=41.1._C:0xH001f2e>=42.1._C:0xH001f2e>=43." - "1._C:0xH001f2e>=44.1._C:0xH001f2e>=45.1._C:0xH001f2e>=46.1._C:0xH001f2e>" - "=47.1._C:0xH001f2e>=48.1._C:0xH001f2e>=49.1._C:0xH001f2e>=50.1._C:" - "0xH001f2e>=51.1._C:0xH001f2e>=52.1._C:0xH001f2e>=53.1._C:0xH001f2e>=54." - "1._C:0xH001f2e>=55.1._C:0xH001f2e>=56.1._C:0xH001f2e>=57.1._C:0xH001f2e>" - "=58.1._C:0xH001f2e>=59.1._C:0xH001f2e>=60.1._C:0xH001f2e>=61.1._C:" - "0xH001f2e>=62.1._C:0xH001f2e>=63.1._C:0xH001f2e>=64.1._C:0xH001f2e>=65." - "1._C:0xH001f2e>=66.1._C:0xH001f2e>=67.1._C:0xH001f2e>=68.1._C:0xH001f2e>" - "=69.1._C:0xH001f2e>=70.1._C:0xH001f2e>=71.1._C:0xH001f2e>=72.1._C:" - "0xH001f2e>=73.1._C:0xH001f2e>=74.1._C:0xH001f2e>=75.1._C:0xH001f2e>=76." - "1._C:0xH001f2e>=77.1._C:0xH001f2e>=78.1._C:0xH001f2e>=79.1._C:0xH001f2e>" - "=80.1._C:0xH001f2e>=81.1._C:0xH001f2e>=82.1._C:0xH001f2e>=83.1._C:" - "0xH001f2e>=84.1._C:0xH001f2e>=85.1._C:0xH001f2e>=86.1._C:0xH001f2e>=87." - "1._C:0xH001f2e>=88.1._C:0xH001f2e>=89.1._C:0xH001f2e>=90.1._C:0xH001f2e>" - "=91.1._C:0xH001f2e>=92.1._C:0xH001f2e>=93.1._C:0xH001f2e>=94.1._C:" - "0xH001f2e>=95.1._C:0xH001f2e=96.1._M:0!=0.96.\",\"Title\":\"All " - "Exits\",\"Description\":\"100% clear the " - "game\",\"Points\":50,\"Author\":\"Jaarl\",\"Modified\":1625960414," - "\"Created\":1376929308,\"BadgeName\":\"46761\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":7.49,\"RarityHardcore\":5.74,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46761.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46761_lock.png\"},{\"ID\":2985,\"MemAddr\":\"0x " - "0018f2=3598_0xH0013bf=31\",\"Title\":\"The " - "Investigator\",\"Description\":\"Access a secret area in the Forest " - "Fortress\",\"Points\":5,\"Author\":\"mrvsonic87\",\"Modified\":" - "1504475601,\"Created\":1379656738,\"BadgeName\":\"47075\",\"Flags\":3," - "\"Type\":null,\"Rarity\":9.14,\"RarityHardcore\":6.24,\"BadgeURL\":" - "\"https:\\/\\/media.retroachievements.org\\/Badge\\/" - "47075.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47075_lock.png\"},{\"ID\":2303,\"MemAddr\":\"0xH001f11=5.7200._R:" - "0xH001f11!=5_R:0xH000100<13_R:0xH000100>14\",\"Title\":\"That " - "Oh-So-Familiar Tune\",\"Description\":\"Find the secret in the Special " - "Zone\",\"Points\":2,\"Author\":\"Jaarl\",\"Modified\":1626262445," - "\"Created\":1376929305,\"BadgeName\":\"46563\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":9.1,\"RarityHardcore\":6.95,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46563.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46563_lock.png\"},{\"ID\":383324,\"MemAddr\":\"N:d0xH000100=19_N:" - "0xH000100=20_O:0xH0013cf=64_0xH000071=10S0xH0013bf=37_0xT001ec7=" - "1S0xH0013bf=7_0xT001ea9=1S0xH0013bf=64_0xT001ee2=1S0xH0013bf=14_" - "0xT001eb0=1S0xH0013bf=32_0xT001ec2=1S0xH0013bf=26_0xT001ebc=1S0xH0013bf=" - "52_0xT001ed6=1S0xH0013bf=11_0xT001ead=1S0xH0013bf=31_0xT001ec1=" - "1S0xH0013bf=27_0xT001ebd=1S0xH0013bf=53_0xT001ed7=1\",\"Title\":" - "\"Entering the Castle Ruins\",\"Description\":\"Reenter a Castle after " - "it was destroyed by pressing L + " - "R.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1717162497," - "\"Created\":1703596667,\"BadgeName\":\"431957\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":4.45,\"RarityHardcore\":3.51,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431957.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431957_lock.png\"},{\"ID\":45901,\"MemAddr\":\"R:0xH0013bf!=39_R:" - "0xH000dc0!=0_R:0x 00001e<2032_R:0x " - "00001e>2128Sd0xH0016e6!=13_0xH0016e6=13Sd0xH0016e5!=13_0xH0016e5=" - "13Sd0xH0016e4!=13_0xH0016e4=13Sd0xH0016e3!=13_0xH0016e3=13Sd0xH0016e2!=" - "13_0xH0016e2=13Sd0xH0016e1!=13_0xH0016e1=13\",\"Title\":\"Bonus on the " - "Island\",\"Description\":\"Obtain 1-up from Bonus Block in Yoshi's " - "Island " - "3\",\"Points\":5,\"Author\":\"Salsa\",\"Modified\":1576437920," - "\"Created\":1488942160,\"BadgeName\":\"46551\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":13.13,\"RarityHardcore\":9.13,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "46551.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46551_lock.png\"},{\"ID\":45902,\"MemAddr\":\"R:0xH0013bf!=5_R:" - "0xH000dc0!=0_R:0x 00001e<2224_R:0x " - "00001e>2352Sd0xH0016e6!=13_0xH0016e6=13Sd0xH0016e5!=13_0xH0016e5=" - "13Sd0xH0016e4!=13_0xH0016e4=13Sd0xH0016e3!=13_0xH0016e3=13Sd0xH0016e2!=" - "13_0xH0016e2=13Sd0xH0016e1!=13_0xH0016e1=13\",\"Title\":\"Bonus of the " - "Donuts\",\"Description\":\"Obtain 1-up from Bonus Block in Donut Plains " - "3\",\"Points\":5,\"Author\":\"Salsa\",\"Modified\":1576437924," - "\"Created\":1488942165,\"BadgeName\":\"46552\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":13.46,\"RarityHardcore\":9.32,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "46552.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46552_lock.png\"},{\"ID\":45903,\"MemAddr\":\"R:0xH0013bf!=12_R:" - "0xH000dc0!=0_R:0x 00001e<1404_R:0x " - "00001e>1404_R:0xH000094<192_R:0xH00009c!=22Sd0xH0016e6!=13_0xH0016e6=" - "13Sd0xH0016e5!=13_0xH0016e5=13Sd0xH0016e4!=13_0xH0016e4=13Sd0xH0016e3!=" - "13_0xH0016e3=13Sd0xH0016e2!=13_0xH0016e2=13Sd0xH0016e1!=13_0xH0016e1=" - "13\",\"Title\":\"Bonus on the Butter\",\"Description\":\"Obtain 1-up " - "from Bonus Block in Butter Bridge " - "1\",\"Points\":5,\"Author\":\"Salsa\",\"Modified\":1576437928," - "\"Created\":1488942170,\"BadgeName\":\"46553\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":5.48,\"RarityHardcore\":4.56,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46553.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46553_lock.png\"},{\"ID\":45904,\"MemAddr\":\"R:0xH0013bf!=35_R:" - "0xH000dc0!=0_R:0x 00001e<2160_R:0x " - "00001e>2304Sd0xH0016e6!=13_0xH0016e6=13Sd0xH0016e5!=13_0xH0016e5=" - "13Sd0xH0016e4!=13_0xH0016e4=13Sd0xH0016e3!=13_0xH0016e3=13Sd0xH0016e2!=" - "13_0xH0016e2=13Sd0xH0016e1!=13_0xH0016e1=13\",\"Title\":\"Bonus of the " - "Chocolate\",\"Description\":\"Obtain 1-up from Bonus Block in Chocolate " - "Island " - "3\",\"Points\":5,\"Author\":\"Salsa\",\"Modified\":1576437931," - "\"Created\":1488942175,\"BadgeName\":\"46554\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":11.29,\"RarityHardcore\":7.4,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46554.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46554_lock.png\"},{\"ID\":383328,\"MemAddr\":\"Q:0xH0013bf=38_0xH001421>" - "d0xH001421_M:0xH001421=4_Q:0xW0000ce=509428\",\"Title\":\"Secrets of " - "the Cheep Cheeps\",\"Description\":\"Trigger the hidden 1-Up in Yoshi's " - "Island " - "4.\",\"Points\":3,\"Author\":\"SporyTike\",\"Modified\":1703596668," - "\"Created\":1703596668,\"BadgeName\":\"431961\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":2.18,\"RarityHardcore\":1.87,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431961.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431961_lock.png\"},{\"ID\":383325,\"MemAddr\":\"Q:0xH0013bf=21_" - "0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=509653\",\"Title\":" - "\"Secrets of the Chucks\",\"Description\":\"Trigger the hidden 1-Up in " - "Donut Plains " - "1.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596667," - "\"Created\":1703596667,\"BadgeName\":\"431958\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":2.2,\"RarityHardcore\":1.89,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431958.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431958_lock.png\"},{\"ID\":383326,\"MemAddr\":\"Q:0xH0013bf=6_0xH001421>" - "d0xH001421_M:0xH001421=4_Q:0xW0000ce=510020\",\"Title\":\"Secrets of " - "the Galoombas\",\"Description\":\"Trigger the hidden 1-Up in Donut " - "Plains " - "4.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596667," - "\"Created\":1703596667,\"BadgeName\":\"431959\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":2.07,\"RarityHardcore\":1.79,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431959.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431959_lock.png\"},{\"ID\":383332,\"MemAddr\":\"Q:0xH0013bf=7_0xH001421>" - "d0xH001421_M:0xH001421=4_Q:0xW0000ce=510246\",\"Title\":\"Secrets of " - "Morton\",\"Description\":\"Trigger the hidden 1-Up in #2 Morton's " - "Castle.\",\"Points\":1,\"Author\":\"SporyTike\",\"Modified\":1703596668," - "\"Created\":1703596668,\"BadgeName\":\"431965\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":2.5,\"RarityHardcore\":2.06,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431965.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431965_lock.png\"},{\"ID\":383333,\"MemAddr\":\"Q:0xH0013bf=43_" - "0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=510940\",\"Title\":" - "\"Secrets of the Boos\",\"Description\":\"Trigger the hidden 1-Up in " - "Vanilla Ghost " - "House.\",\"Points\":3,\"Author\":\"SporyTike\",\"Modified\":1703596669," - "\"Created\":1703596669,\"BadgeName\":\"431966\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":2.04,\"RarityHardcore\":1.77,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431966.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431966_lock.png\"},{\"ID\":383335,\"MemAddr\":\"Q:0xH0013bf=61_" - "0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=511188\",\"Title\":" - "\"Secrets of the Bullet Bills\",\"Description\":\"Trigger the hidden " - "1-Up in Vanilla Dome " - "4.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596675," - "\"Created\":1703596675,\"BadgeName\":\"431974\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":2.03,\"RarityHardcore\":1.74,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431974.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431974_lock.png\"},{\"ID\":383331,\"MemAddr\":\"Q:0xH0013bf=11_" - "0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=511821\",\"Title\":" - "\"Secrets of the Fishbones\",\"Description\":\"Trigger the hidden 1-Up " - "in Vanilla " - "Fortress.\",\"Points\":3,\"Author\":\"SporyTike\",\"Modified\":" - "1703596668,\"Created\":1703596668,\"BadgeName\":\"431964\",\"Flags\":3," - "\"Type\":null,\"Rarity\":1.86,\"RarityHardcore\":1.63,\"BadgeURL\":" - "\"https:\\/\\/media.retroachievements.org\\/Badge\\/" - "431964.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431964_lock.png\"},{\"ID\":383327,\"MemAddr\":\"Q:0xH0013bf=16_" - "0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=512067\",\"Title\":" - "\"Secrets of the Sumo Bros\",\"Description\":\"Trigger the hidden 1-Up " - "in Cookie " - "Mountain.\",\"Points\":1,\"Author\":\"SporyTike\",\"Modified\":" - "1703596668,\"Created\":1703596668,\"BadgeName\":\"431960\",\"Flags\":3," - "\"Type\":null,\"Rarity\":4.05,\"RarityHardcore\":2.84,\"BadgeURL\":" - "\"https:\\/\\/media.retroachievements.org\\/Badge\\/" - "431960.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431960_lock.png\"},{\"ID\":383336,\"MemAddr\":\"Q:0xH0013bf=71_" - "0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=513093\",\"Title\":" - "\"Secrets of the Bubbles\",\"Description\":\"Trigger the hidden 1-Up in " - "Forest of Illusion " - "3.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596675," - "\"Created\":1703596675,\"BadgeName\":\"431975\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":1.89,\"RarityHardcore\":1.65,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431975.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431975_lock.png\"},{\"ID\":46332,\"MemAddr\":\"R:0xH0013bf!=46_R:0x " - "000094<4194_R:0x 000094>4306_0x " - "000096>=276_0xH000013>d0xH000013.240._P:0xH0013d4=1S0xH0000a6=2_P:" - "0xH0014d0!=8S0xH0000a5=2_P:0xH0014d1!=8\",\"Title\":\"Suicide " - "Prevention (moved to bonus)\",\"Description\":\"On 3-3 prevent kicking " - "koopa by checkpoint from dying. Stay by " - "him.\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1505703949," - "\"Created\":1489701538,\"BadgeName\":\"47077\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47077.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47077_lock.png\"},{\"ID\":383330,\"MemAddr\":\"Q:0xH0013bf=36_0xH001421>" - "d0xH001421_M:0xH001421=4_Q:0xW0000ce=514026\",\"Title\":\"Secrets of " - "Chocolate\",\"Description\":\"Trigger the hidden 1-Up in Chocolate " - "Island " - "2.\",\"Points\":3,\"Author\":\"SporyTike\",\"Modified\":1703596668," - "\"Created\":1703596668,\"BadgeName\":\"431963\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":1.75,\"RarityHardcore\":1.56,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431963.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431963_lock.png\"},{\"ID\":46061,\"MemAddr\":\"R:0xH0013bf!=1_R:" - "0xH001411=0_R:0xH000f31=1_0xH000dbe>d0xH000dbe.50.\",\"Title\":\"Silver " - "Worth More than Gold (moved to bonus)\",\"Description\":\"On Vanilla " - "Secret 2 Get 50 1ups with 200 or more on the " - "clock\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474573," - "\"Created\":1489170843,\"BadgeName\":\"46762\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46762.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46762_lock.png\"},{\"ID\":383329,\"MemAddr\":\"Q:0xH0013bf=26_0xH001421>" - "d0xH001421_M:0xH001421=4_Q:0xW0000ce=515003\",\"Title\":\"Secrets of " - "Wendy\",\"Description\":\"Trigger the hidden 1-Up in #6 Wendy's " - "Castle.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596668," - "\"Created\":1703596668,\"BadgeName\":\"431962\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":2.22,\"RarityHardcore\":1.8,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431962.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431962_lock.png\"},{\"ID\":46008,\"MemAddr\":\"R:0xH0013bf!=15_R:" - "0xH000019!=3_0xH000095=1.1._R:0xH001411=0_0x 000094>=2288_0x " - "000096>=341_R:0xH00187a=1_R:0xO000077=1\",\"Title\":\"Inferno Tornado " - "(moved to bonus)\",\"Description\":\"As Fire Mario in Cheese Bridge " - "cross 1st gap with only spin jump. No " - "platforms\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474576," - "\"Created\":1489073077,\"BadgeName\":\"46606\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46606.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46606_lock.png\"},{\"ID\":383340,\"MemAddr\":\"Q:0xH0013bf=57_0xH001421>" - "d0xH001421_M:0xH001421=4_Q:0xW0000ce=515507_Q:0x " - "000094<2048\",\"Title\":\"Secrets of the Moving " - "Walls\",\"Description\":\"Trigger the first hidden 1-Up in Valley of " - "Bowser " - "2.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596683," - "\"Created\":1703596683,\"BadgeName\":\"431979\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":1.68,\"RarityHardcore\":1.48,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431979.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431979_lock.png\"},{\"ID\":46002,\"MemAddr\":\"R:0xH0013bf!=14_" - "0xH00c685>=204_0xH00c685<=205_0xH0017c3=2.11._P:d0xH0017c3=2_R:" - "0xH00c680=152_R:0xH000065!=59\",\"Title\":\"Surfing the Shell (moved to " - "bonus)\",\"Description\":\"In #4 Ludwig's Castle bouce on Ludwig's " - "shell 11 times before he " - "leaps\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1516573766," - "\"Created\":1489073044,\"BadgeName\":\"46667\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46667.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46667_lock.png\"},{\"ID\":383337,\"MemAddr\":\"Q:0xH0013bf=57_0xH001421>" - "d0xH001421_M:0xH001421=4_Q:0xW0000ce=515507_Q:0x " - "000094>2048\",\"Title\":\"Secrets of the Moving Walls " - "II\",\"Description\":\"Trigger the second hidden 1-Up in Valley of " - "Bowser " - "2.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596675," - "\"Created\":1703596675,\"BadgeName\":\"431976\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":1.58,\"RarityHardcore\":1.4,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431976.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431976_lock.png\"},{\"ID\":2263,\"MemAddr\":\"0xH001697=12_0xH0013bf=66_" - "R:0xH001697=0\",\"Title\":\"Bother The Wigglers (moved to " - "bonus)\",\"Description\":\"Jump on yellow Wigglers in Forest of " - "Illusion 12 times in a row for a " - "surprise!\",\"Points\":0,\"Author\":\"Jaarl\",\"Modified\":1504474582," - "\"Created\":1376655155,\"BadgeName\":\"46574\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46574.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46574_lock.png\"},{\"ID\":383339,\"MemAddr\":\"Q:0xH0013bf=52_0xH001421>" - "d0xH001421_M:0xH001421=4_Q:0xW0000ce=516017\",\"Title\":\"Secrets of " - "Larry\",\"Description\":\"Trigger the hidden 1-Up in #7 Larry's " - "Castle.\",\"Points\":3,\"Author\":\"SporyTike\",\"Modified\":1703596679," - "\"Created\":1703596679,\"BadgeName\":\"431978\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":1.64,\"RarityHardcore\":1.45,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431978.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431978_lock.png\"},{\"ID\":383334,\"MemAddr\":\"Q:0xH0013bf=78_" - "0xH001421>d0xH001421_M:0xH001421=4_Q:0xW0000ce=517084\",\"Title\":" - "\"Secrets of the Vines\",\"Description\":\"Trigger the hidden 1-Up in " - "Gnarly.\",\"Points\":2,\"Author\":\"SporyTike\",\"Modified\":1703596669," - "\"Created\":1703596669,\"BadgeName\":\"431970\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":1.87,\"RarityHardcore\":1.62,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431970.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431970_lock.png\"},{\"ID\":46171,\"MemAddr\":\"R:0xH0018a7=50_R:" - "0xH00009c=13_R:0xH00005a!=254_0xH0000d2=0.10._0xH0000d2=6_0xH0000d1>=" - "213_0xH0000d1<=242\",\"Title\":\"These Platforms are so Slow... " - "(demoted)\",\"Description\":\"Arrive at the First Door of Larry's " - "Castle without Activating the " - "Platform\",\"Points\":0,\"Author\":\"GalacticSpear\",\"Modified\":" - "1504474585,\"Created\":1489319085,\"BadgeName\":\"46770\",\"Flags\":5," - "\"Type\":null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/" - "\\/media.retroachievements.org\\/Badge\\/" - "46770.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46770_lock.png\"},{\"ID\":46006,\"MemAddr\":\"0xH000dba=4_0xH000dc1>=1_" - "0xH000f2d!=d0xH000f2d.10._R:0xH0018ac=0_R:0xH0013bf!=28_R:0xH001411=0_R:" - "0x 000094>1680\",\"Title\":\"A Trip to Pound Town (dmt, fals " - "pos)\",\"Description\":\"Pound 10* spinys hard on 6-5 with Y. Yoshi. " - "Don't spit or swallow til the " - "end\",\"Points\":0,\"Author\":\"kdecks\",\"Modified\":1504474587," - "\"Created\":1489073069,\"BadgeName\":\"46670\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46670.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46670_lock.png\"},{\"ID\":45909,\"MemAddr\":\"R:0xH000db3=1_R:0x " - "000f34>90_P:0xH000dd5<1_P:0xH000dd5>2_R:d0x 000f34>0x " - "000f34_0xH0013bf=42.1._0xH0013bf=41.1._0xH0013bf=39.1._0xH0013bf=38.1._" - "0xH0013bf=37.1.\",\"Title\":\"Mario vs. the Score I (Yoshi's Island) " - "(demoted)\",\"Description\":\"Score 900 points or less playing through " - "all stages in Yoshi " - "Island\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474594," - "\"Created\":1488942350,\"BadgeName\":\"46713\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46713.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46713_lock.png\"},{\"ID\":45910,\"MemAddr\":\"R:0xH000db3=1_R:0x " - "000f34>100_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x " - "000f34_0xH0013bf=21.1._0xH0013bf=9.1._0xH0013bf=5.1._0xH0013bf=6.1._" - "0xH0013bf=10.1._0xH0013bf=47.1._0xH0013bf=19.1._0xH0013bf=4.1._" - "0xH0013bf=7.1.\",\"Title\":\"Mario vs. the Score II (Donut Plains) " - "(demoted)\",\"Description\":\"Score 1000 points or less playing through " - "all stages in Donut " - "Plains\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474597," - "\"Created\":1488942354,\"BadgeName\":\"46714\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46714.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46714_lock.png\"},{\"ID\":45911,\"MemAddr\":\"R:0xH000db3=1_R:0x " - "000f34>301_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x " - "000f34_0xH0013bf=62.1._0xH0013bf=60.1._0xH0013bf=61.1._0xH0013bf=43.1._" - "0xH0013bf=64.1._0xH0013bf=1.1._0xH0013bf=2.1._0xH0013bf=11.1._0xH0013bf=" - "45.1._0xH0013bf=46.1.\",\"Title\":\"Mario vs. the Score III (Vanilla " - "Dome) (demoted)\",\"Description\":\"Score 3010 points or less playing " - "through all stages in Vanilla " - "Dome\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474603," - "\"Created\":1488942359,\"BadgeName\":\"46715\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46715.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46715_lock.png\"},{\"ID\":45912,\"MemAddr\":\"R:0xH000db3=1_R:0x " - "000f34>110_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x " - "000f34_0xH0013bf=12.1._0xH0013bf=13.1._0xH0013bf=15.1._0xH0013bf=17.1._" - "0xH0013bf=16.1._0xH0013bf=14.1.\",\"Title\":\"Mario vs. the Score IV " - "(Twin Bridges) (demoted)\",\"Description\":\"Score 1100 points or less " - "playing through all stages in Twin " - "Bridges\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474609," - "\"Created\":1488942364,\"BadgeName\":\"46716\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46716.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46716_lock.png\"},{\"ID\":45913,\"MemAddr\":\"R:0xH000db3=1_R:0x " - "000f34>95_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x " - "000f34_0xH0013bf=66.1._0xH0013bf=68.1._0xH0013bf=71.1._0xH0013bf=67.1._" - "0xH0013bf=65.1._0xH0013bf=70.1._0xH0013bf=31.1._0xH0013bf=32.1.\"," - "\"Title\":\"Mario vs. the Score V (Forest of Illusion) " - "(demoted)\",\"Description\":\"Score 950 points or less playing through " - "all stages in Forest of " - "Illusion\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474613," - "\"Created\":1488942370,\"BadgeName\":\"46717\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46717.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46717_lock.png\"},{\"ID\":46304,\"MemAddr\":\"R:0xH000db3=1_R:0x " - "000f34>0_P:0xH000dd5<1_P:0xH000dd5>2_R:d0x 000f34>0x " - "000f34_0xH0013bf=21.1._0xH0013bf=9.1._0xH0013bf=10.1._0xH0013bf=62.1._" - "0xH0013bf=60.1._0xH0013bf=15.1.\",\"Title\":\"Loopholes by Keyholes - " - "Donut Vanilla Butter (moved to bonus)\",\"Description\":\"Don't score " - "any points reaching keyhole exits on stages in Worlds 2, 3 and " - "4\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474617," - "\"Created\":1489616634,\"BadgeName\":\"47079\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47079.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47079_lock.png\"},{\"ID\":45914,\"MemAddr\":\"R:0xH000db3=1_R:0x " - "000f34>285_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x " - "000f34_0xH0013bf=34.1._0xH0013bf=36.1._0xH0013bf=35.1._0xH0013bf=29.1._" - "0xH0013bf=28.1._0xH0013bf=59.1._0xH0013bf=33.1._0xH0013bf=27.1._" - "0xH0013bf=26.1._0xH0013bf=24.1.\",\"Title\":\"Mario vs. the Score VI " - "(Chocolate Island) (demoted)\",\"Description\":\"Score 2850 points or " - "less playing through all stages in Chocolate " - "Island\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474620," - "\"Created\":1488942396,\"BadgeName\":\"46718\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46718.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46718_lock.png\"},{\"ID\":46305,\"MemAddr\":\"R:0xH000db3=1_R:0x " - "000f34>0_P:0xH000dd5<1_P:0xH000dd5>2_R:d0x 000f34>0x " - "000f34_0xH0013bf=66.1._0xH0013bf=68.1._0xH0013bf=67.1._0xH0013bf=36.1._" - "0xH0013bf=57.1._0xH0013bf=56.1._0xH0013bf=51.1.\",\"Title\":\"Loopholes " - "by Keyholes - Chocolate Forest Bowser (bonus)\",\"Description\":\"Don't " - "score any points reaching keyhole exits on stages in Worlds 4, 6 and " - "7\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474637," - "\"Created\":1489616639,\"BadgeName\":\"47080\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47080.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47080_lock.png\"},{\"ID\":45915,\"MemAddr\":\"R:0xH000db3=1_R:0x " - "000f34>95_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x " - "000f34_0xH0013bf=58.1._0xH0013bf=57.1._0xH0013bf=55.1._0xH0013bf=51.1._" - "0xH0013bf=56.1._0xH0013bf=52.1._0xH0013bf=53.1._0xH0013bf<=50_0xH0013bf>" - "=49_0xH0013ef=1_0xH0013f9=3\",\"Title\":\"Mario vs. the Score VII " - "(Valley of Bowser) (demoted)\",\"Description\":\"Score 950 points or " - "less playing through all stages in Valley of " - "Bowser\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474640," - "\"Created\":1488942402,\"BadgeName\":\"46719\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46719.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46719_lock.png\"},{\"ID\":45916,\"MemAddr\":\"R:0xH000db3=1_R:0x " - "000f34>110_P:0xH000dd5>2_R:d0x 000f34>0x " - "000f34_0xH0013bf=88.1._0xH0013bf=84.1._0xH0013bf=86.1._0xH0013bf=89.1._" - "0xH0013bf=90.1._P:0xH000dd5<1\",\"Title\":\"Loopholes by Keyholes - " - "Star World (moved to bonus)\",\"Description\":\"Score 1100 points or " - "less playing through all stages in Star " - "World\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474645," - "\"Created\":1488942407,\"BadgeName\":\"47081\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47081.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47081_lock.png\"},{\"ID\":46307,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=" - "42.1._0xH0013bf=41.1._0xH0013bf=39.1._0xH0013bf=38.1._0xH0013bf=37_0x " - "0000ce=50896_R:0xH000db3=1_R:0xH000dd5=128_R:0xH0016e6=5_R:0xH0016e6=6_" - "R:0xH0016e6=8_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=" - "33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:" - "0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_" - "R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:" - "0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:" - "0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_P:d0xH000dd5=1_P:d0xH000dd5=2_" - "R:0xH0013cf=64_R:0xH000100=0\",\"Title\":\"Super Pacifist Mario feat. " - "Hungry Yoshi I (Yoshi Island) (bon)\",\"Description\":\"See achievement " - "comments for " - "details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474058," - "\"Created\":1489623727,\"BadgeName\":\"46908\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46908.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46908_lock.png\"},{\"ID\":45917,\"MemAddr\":\"R:0xH000db3=1_R:0x " - "000f34>100_P:0xH000dd5<1_P:0xH000dd5>2_R:0x 000f34>0x " - "000f34_0xH0013bf=78.1._0xH0013bf=79.1._0xH0013bf=80.1._0xH0013bf=81.1._" - "0xH0013bf=76.1._0xH0013bf=75.1._0xH0013bf=74.1._0xH0013bf=73.1.\"," - "\"Title\":\"Mario vs. the Score IX (Special World) " - "(demoted)\",\"Description\":\"Score 1000 points or less playing through " - "all stages in Special " - "World\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474649," - "\"Created\":1488942411,\"BadgeName\":\"46721\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46721.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46721_lock.png\"},{\"ID\":46308,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=" - "21.1._0xH0013bf=9.1._0xH0013bf=5.1._0xH0013bf=6.1._0xH0013bf=10.1._" - "0xH0013bf=47.1._0xH0013bf=19.1._0xH0013bf=4.1._0xH0013bf=7.1._0x " - "0000ce=51523_R:0xH000db3=1_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=" - "33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:" - "0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=" - "33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=" - "2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_" - "R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_" - "R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_P:d0xH000dd5=1_P:d0xH000dd5=2_" - "R:0xH0013cf=64_R:0xH000100=0\",\"Title\":\"Super Pacifist Marioi II " - "(Donut Plains) (bonus)\",\"Description\":\"See achievement comments for " - "details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474682," - "\"Created\":1489635441,\"BadgeName\":\"46909\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46909.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46909_lock.png\"},{\"ID\":46309,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=" - "62.1._0xH0013bf=60.1._0xH0013bf=61.1._0xH0013bf=43.1._0xH0013bf=64_" - "0xH0013bf=1.1._0xH0013bf=2.1._0xH0013bf=45.1._0x " - "0000ce=52672_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=" - "33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:" - "0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=" - "33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=" - "2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_" - "R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_" - "R:0xH0016e6=6_R:0xH0016e6=8_R:0xH000db3=1_P:d0xH000dd5=1_P:d0xH000dd5=2_" - "R:0xH0013cf=64_R:0xH000100=0_P:d0xH0000a6=2\",\"Title\":\"Super " - "Pacifist Mario III (Vanilla Dome) (bonus)\",\"Description\":\"See " - "achievement comments for " - "details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474749," - "\"Created\":1489635447,\"BadgeName\":\"46910\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46910.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46910_lock.png\"},{\"ID\":46310,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=" - "12.1._0xH0013bf=15.1._0xH0013bf=17.1._0xH0013bf=16.1._0xH0013bf=14_0x " - "0000ce=53586_R:0xH000db3=1_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=" - "33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:" - "0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=" - "33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=" - "2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_" - "R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_" - "R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_P:d0xH000dd5=1_P:d0xH000dd5=2_" - "R:0xH0013cf=64_R:0xH000100=0_0xH0013bf=13.1.\",\"Title\":\"Super " - "Pacifist Mario IV (Twin Bridges) (bonus)\",\"Description\":\"See " - "achievement comments for " - "details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474894," - "\"Created\":1489635455,\"BadgeName\":\"46911\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46911.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46911_lock.png\"},{\"ID\":46311,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=" - "66.1._0xH0013bf=68.1._0xH0013bf=71.1._0xH0013bf=67.1._0xH0013bf=65.1._" - "0xH0013bf=70.1._0xH0013bf=32_0x " - "0000ce=54557_R:0xH000db3=1_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=" - "33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:" - "0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=" - "33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=" - "2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_" - "R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_" - "R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_P:d0xH000dd5=1_P:d0xH000dd5=2_" - "R:0xH0013cf=64_R:0xH000100=0_P:0x 0000a2=30840\",\"Title\":\"Super " - "Pacifist Mario V (Forest of Illusion) (moved to " - "bonus)\",\"Description\":\"See achievement comments for " - "details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474914," - "\"Created\":1489635462,\"BadgeName\":\"46912\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46912.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46912_lock.png\"},{\"ID\":46312,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=" - "34.1._0xH0013bf=36.1._0xH0013bf=35.1._0xH0013bf=29.1._0xH0013bf=28.1._" - "0xH0013bf=59.1._0xH0013bf=33.1._0xH0013bf=26_0xH0013bf=24.1._0x " - "0000ce=56354_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=" - "33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=33_R:" - "0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=" - "33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=" - "2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_" - "R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_" - "R:0xH0016e6=6_R:0xH0016e6=8_R:0xH000db3=1_P:d0xH000dd5=1_P:d0xH000dd5=2_" - "R:0xH0013cf=64_R:0xH000100=0\",\"Title\":\"Super Pacifist Mario VI " - "(Chocolate Island) (moved to bonus)\",\"Description\":\"See achievement " - "comments for " - "details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474928," - "\"Created\":1489635469,\"BadgeName\":\"46913\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46913.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46913_lock.png\"},{\"ID\":46306,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=" - "57.1._0xH0013bf=55.1._0xH0013bf=51.1._0xH0013bf=56.1._0xH0013bf=49.1._" - "0xH0013bf=58.1._0x " - "0000ce=57792_R:0xH000db3=1_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=" - "33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:" - "0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=" - "33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=" - "2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_" - "R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_" - "R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_P:d0xH000dd5=1_P:d0xH000dd5=2_" - "R:0xH0013cf=64_R:0xH000100=0\",\"Title\":\"Super Pacifist Mario VII " - "(Valley of Bowser) (moved to bonus)\",\"Description\":\"See achievement " - "comments for " - "details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474937," - "\"Created\":1489623613,\"BadgeName\":\"46916\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46916.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46916_lock.png\"},{\"ID\":46313,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=" - "88.1._0xH0013bf=84.1._0xH0013bf=86.1._0xH0013bf=89.1._0xH0013bf=90.1._" - "0xH000dd5>0_R:0xH000db3=1_R:0xH000dd5=128_R:0xH00009e=33_R:0xH00009f=33_" - "R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=33_R:0xH0000a3=33_R:0xH0000a4=" - "33_R:0xH0000a5=33_R:0xH0000a6=33_R:0xH0000a7=33_R:0xH0000a8=33_R:" - "0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_R:0xH0014ca=2_R:0xH0014cb=2_R:" - "0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:0xH0014cf=2_R:0xH0014d0=2_R:" - "0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:0xH0018ac>0_R:0xH000302=63_R:" - "0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_P:d0xH000dd5=1_P:d0xH000dd5=2_R:" - "0xH0013cf=64_R:0xH000100=0\",\"Title\":\"Super Pacifist Mario VIII " - "(Star World) (moved to bonus)\",\"Description\":\"See achievement " - "comments for " - "details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474946," - "\"Created\":1489635474,\"BadgeName\":\"46914\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46914.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46914_lock.png\"},{\"ID\":46314,\"MemAddr\":\"0xH000100=10.1._0xH0013bf=" - "78.1._0xH0013bf=79.1._0xH0013bf=80.1._0xH0013bf=76.1._0xH0013bf=75.1._" - "0xH0013bf=74.1._0xH0013bf=73.1._0xH001dff=12_R:0xH000dd5=128_R:" - "0xH00009e=33_R:0xH00009f=33_R:0xH0000a0=33_R:0xH0000a1=33_R:0xH0000a2=" - "33_R:0xH0000a3=33_R:0xH0000a4=33_R:0xH0000a5=33_R:0xH0000a6=33_R:" - "0xH0000a7=33_R:0xH0000a8=33_R:0xH0000a9=33_R:0xH0014c8=2_R:0xH0014c9=2_" - "R:0xH0014ca=2_R:0xH0014cb=2_R:0xH0014cc=2_R:0xH0014cd=2_R:0xH0014ce=2_R:" - "0xH0014cf=2_R:0xH0014d0=2_R:0xH0014d1=2_R:0xH0014d2=2_R:0xH0014d3=2_R:" - "0xH0018ac>0_R:0xH000302=63_R:0xH0016e6=5_R:0xH0016e6=6_R:0xH0016e6=8_R:" - "0xH000db3=1_R:0xH0013cf=64_R:0xH000100=0_0xH0013bf=81.1._P:d0xH000dd5=1_" - "P:d0xH000dd5=2\",\"Title\":\"Super Pacifist Mario IX (Special World) " - "(moved to bonus)\",\"Description\":\"See achievement comments for " - "details\",\"Points\":0,\"Author\":\"Salsa\",\"Modified\":1504474957," - "\"Created\":1489635478,\"BadgeName\":\"46915\",\"Flags\":5,\"Type\":" - "null,\"Rarity\":0,\"RarityHardcore\":0,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46915.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46915_lock.png\"},{\"ID\":29653,\"MemAddr\":\"C:0xS001f34=1.1._C:" - "0xR001f34=1.1._C:0xM001f33=1.1._C:0xN001f33=1.1._M:0!=0.4._0xS001f34=1_" - "0xR001f34=1_0xM001f33=1_0xN001f33=1_0xH000008!=85_R:0xH001f33<" - "d0xH001f33_R:0xH001f34d0xH000dbe.8._R:0xH001411=0\"," - "\"Title\":\"Fence Offense\",\"Description\":\"Get 8 1-ups at #1 Iggy's " - "Castle with 230 or more on the time " - "clock\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1625960433," - "\"Created\":1489073056,\"BadgeName\":\"46608\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":4.9,\"RarityHardcore\":4.24,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46608.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46608_lock.png\"},{\"ID\":46170,\"MemAddr\":\"N:0xH0013bf=4_N:" - "d0xH000dda=0_0xH000dda=7.1._R:0xH0013e0=21_R:0xH000019=2_T:0xH000dd5=2_" - "R:0xH0013bf!=4_R:0xH000dd5=128\",\"Title\":\"Who Needs " - "Vines?\",\"Description\":\"Get the Normal Exit in Donut Ghost House " - "without a Feather or Climbing on a " - "Vine\",\"Points\":10,\"Author\":\"GalacticSpear\",\"Modified\":" - "1625960437,\"Created\":1489319082,\"BadgeName\":\"46771\",\"Flags\":3," - "\"Type\":null,\"Rarity\":6.03,\"RarityHardcore\":5.08,\"BadgeURL\":" - "\"https:\\/\\/media.retroachievements.org\\/Badge\\/" - "46771.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46771_lock.png\"},{\"ID\":46331,\"MemAddr\":\"0xH0000aa=64_0xH0014c8=2_" - "R:0xH0013bf!=19_R:0xH00005a!=237\",\"Title\":\"Spooking Big " - "Boo\",\"Description\":\"In the first room of Donut Secret House kill " - "Big " - "Boo\",\"Points\":5,\"Author\":\"kdecks\",\"Modified\":1571057401," - "\"Created\":1489701500,\"BadgeName\":\"47076\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":8.75,\"RarityHardcore\":7.06,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47076.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47076_lock.png\"},{\"ID\":46001,\"MemAddr\":\"R:0xH0013bf!=68_R:" - "0xH000077>0_R:0xH000071=1_0xH000f31=3.1._R:0xH000071=9_T:0xH001dff=12ST:" - "0xH001dff=12ST:0xH001dff=128\",\"Title\":\"Everything is " - "Lava\",\"Description\":\"Get to the normal exit of Forest of Illusion 2 " - "without touching any walls, enemies, blue " - "blocks\",\"Points\":25,\"Author\":\"kdecks\",\"Modified\":1625960440," - "\"Created\":1489073039,\"BadgeName\":\"46666\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":3.44,\"RarityHardcore\":2.9,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46666.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46666_lock.png\"},{\"ID\":45997,\"MemAddr\":\"0xH001523=16_0xH001411=1_" - "0xH000f31=3.1._T:0x " - "000094>=2448_R:0xH000019=2_R:0xH001410=2_R:0xH0018e0>0_R:0xH001523=0_R:" - "0xH0013bf!=67\",\"Title\":\"Didn't Take the Bait\",\"Description\":\"On " - "Forest of Illusion 4, get past Lakitu without killing him or taking the " - "1-up. No cape, " - "wings.\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1671592204," - "\"Created\":1489073023,\"BadgeName\":\"46615\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":6.14,\"RarityHardcore\":5.15,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46615.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46615_lock.png\"},{\"ID\":46120,\"MemAddr\":\"P:0xH0013bf!=49_P:" - "0xH000100!=15_C:0xH0000ce=103.1._C:0xH0000ce=141.1._C:0xH0000ce=197.1._" - "C:0xH0000ce=232.1._C:0xH0000ce=20.1._C:0xH0000ce=49.1._C:0xH0000ce=96.1." - "_C:0xH0000ce=131.1._M:0!=0.8.\",\"Title\":\"Bowser's Castle " - "Explorer\",\"Description\":\"Complete all 8 numbered rooms in one game " - "session\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1625960448," - "\"Created\":1489285260,\"BadgeName\":\"47078\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":4.64,\"RarityHardcore\":4.01,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47078.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "47078_lock.png\"},{\"ID\":46007,\"MemAddr\":\"N:d0xH0000a5=44_0xH0000a5=" - "45.1._P:0xH00187a!=0.1._N:d0xH0013fb=0_P:0xH0013fb!=0.1._P:0xH000dda=" - "255.5._T:0x " - "0014cf=12_O:0xH001dff=12_T:0xH001dff=128_N:p0xH0000a5=45_T:0xH0000a5=" - "120SR:0xH0013bf!=89_R:0xH000d9b=2_N:0xH0000a5!=45_R:0xH0000a5!=120\"," - "\"Title\":\"I Starved Yoshi and All I Got Was...\",\"Description\":\"On " - "Star world 4 bring Baby Yoshi to the end flag without feeding it to " - "adulthood\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1669165514," - "\"Created\":1489073073,\"BadgeName\":\"46607\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":7,\"RarityHardcore\":5.76,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46607.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46607_lock.png\"},{\"ID\":45999,\"MemAddr\":\"R:0xH0013bf!=79_R:" - "0xH000dba=6_R:0xH0013f3>0_T:0xH001dff=12_0xH000f31=3.1.\",\"Title\":" - "\"Who Needs Helium?\",\"Description\":\"Complete SPECIAL-Tubular " - "without Blue Yoshi, or Balloon " - "Mario\",\"Points\":25,\"Author\":\"kdecks\",\"Modified\":1625960450," - "\"Created\":1489073032,\"BadgeName\":\"46677\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":4.05,\"RarityHardcore\":3.19,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46677.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46677_lock.png\"},{\"ID\":46000,\"MemAddr\":\"R:0xH0013bf!=76_T:" - "0xH001dff=12_R:0xH0013ef=1_0x " - "000094<464.1._R:0xH00187a>0\",\"Title\":\"A Groovy " - "Flight\",\"Description\":\"Complete SPECIAL-Groovy without landing past " - "the first ? box. No " - "Yoshi\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1625960453," - "\"Created\":1489073036,\"BadgeName\":\"46665\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":3.75,\"RarityHardcore\":3.15,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46665.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46665_lock.png\"},{\"ID\":46005,\"MemAddr\":\"R:0xH0013bf!=73_0xH000f31>" - "=3_T:0xH001dff=12_T:0xH001411=1ST:0xH001dff=12ST:0xH001dff=128\"," - "\"Title\":\"With More Than You Started\",\"Description\":\"Complete " - "SPECIAL-Funky with 300 or more on the time " - "clock\",\"Points\":10,\"Author\":\"kdecks\",\"Modified\":1625960456," - "\"Created\":1489073065,\"BadgeName\":\"46669\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":3.41,\"RarityHardcore\":2.86,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46669.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46669_lock.png\"},{\"ID\":2274,\"MemAddr\":\"0xH0013bf=49_0xH0013f9=3_" - "0xH001f2e=11_0xH0013ef=1\",\"Title\":\"Shortest " - "Route\",\"Description\":\"Clear the fewest stages possible and beat the " - "game\",\"Points\":25,\"Author\":\"Jaarl\",\"Modified\":1570010401," - "\"Created\":1376742799,\"BadgeName\":\"46575\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":6.08,\"RarityHardcore\":5.05,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46575.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46575_lock.png\"},{\"ID\":2297,\"MemAddr\":\"N:0xH0013ce=0_N:0xH0013cf=" - "0_0xH001f2e=0.1._T:0xH001f2e=11.1._T:0xH0013f9=3_R:0xH000dbe1\",\"Title\":\"Starman " - "Challenge\",\"Description\":\"Clear the game without dying (one " - "session)\",\"Points\":25,\"Author\":\"Jaarl\",\"Modified\":1625960458," - "\"Created\":1376918015,\"BadgeName\":\"46579\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":5.76,\"RarityHardcore\":4.67,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46579.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46579_lock.png\"},{\"ID\":383338,\"MemAddr\":\"N:0xH0013bf=21_N:" - "0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=5_N:0xH000071=5_C:" - "0xW0000ce=508910.1._N:0xH0013bf=7_N:0xH000071=5_C:0xW0000ce=508910.1._N:" - "0xH0013bf=46_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=14_N:" - "0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=71_N:0xH000071=5_C:" - "0xW0000ce=508910.1._N:0xH0013bf=28_N:0xH000071=5_C:0xW0000ce=508910.1._" - "N:0xH0013bf=58_N:0xH000071=5_C:0xW0000ce=508910.1._N:0xH0013bf=55_N:" - "0xH000071=5_C:0xW0000ce=508910.1._M:0!=0.9.\",\"Title\":\"Gambling with " - "Life\",\"Description\":\"Enter all 1-Up Chambers in a single " - "Session.\",\"Points\":5,\"Author\":\"SporyTike\",\"Modified\":" - "1703596675,\"Created\":1703596675,\"BadgeName\":\"431977\",\"Flags\":3," - "\"Type\":null,\"Rarity\":1.45,\"RarityHardcore\":1.3,\"BadgeURL\":" - "\"https:\\/\\/media.retroachievements.org\\/Badge\\/" - "431977.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "431977_lock.png\"},{\"ID\":175188,\"MemAddr\":\"0xH0013bf>=49_0xH0013bf<" - "=50_0x 0000ce=0_d0xH0013de=0_0xH0013de=3\",\"Title\":\"Do The " - "Mario!\",\"Description\":\"Make Mario dance during the " - "credits\",\"Points\":0,\"Author\":\"stfN1337\",\"Modified\":1691443564," - "\"Created\":1632935877,\"BadgeName\":\"195401\",\"Flags\":3,\"Type\":" - "null,\"Rarity\":3.44,\"RarityHardcore\":2.86,\"BadgeURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "195401.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "195401_lock.png\"},{\"ID\":45856,\"MemAddr\":\"R:0xH000100<=10_R:" - "0xH000db3=1_R:0xH001f27>0.1._R:0xH001f28>0_R:0xH001f29>0_R:0xH001f2a>0_" - "R:0xH00187a>0_N:0xH0013bf=42_0xH000100=17.1._R:0xH000019>0_R:0xH001490>" - "0SN:0xH0013bf=42_Q:0xH000100=17.1._N:0xH0013bf=42_N:0xH000100=17.1._N:" - "0xH0013bf=42_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=" - "17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:" - "0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_C:0xH000dd5=" - "1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:" - "0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:" - "0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_C:0xH000dd5=" - "1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:" - "d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=42_N:0xH000100=17.1._N:" - "0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:" - "0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_C:0xH000dd5=" - "1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:" - "d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:" - "0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:" - "0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=60_N:d0xH000dd5=0_C:0xH000dd5=" - "1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:" - "d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=60_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=43_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:" - "0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=60_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=43_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=46_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:" - "0xH0013bf=42_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=39_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=37_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=4_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=7_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=60_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=43_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=46_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=61_N:d0xH000dd5=0_C:0xH000dd5=" - "1.1._N:0xH0013bf=42_N:0xH000100=17.1._N:0xH0013bf=42_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=39_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=38_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=37_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=21_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=9_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=4_N:" - "d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=5_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=6_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=7_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=62_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=60_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=43_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=46_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=61_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=64_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.16.ST:0!=0\",\"Title\":\"Mini-" - "Mario vs the World I\",\"Description\":\"From Yoshi's Island 2 reach " - "and beat #3 Lemmy's Castle in one Session without using Power-Ups, " - "without using Yoshi and without active Switch Palace " - "Blocks.\",\"Points\":25,\"Author\":\"kdecks\",\"Modified\":1687365195," - "\"Created\":1488840058,\"BadgeName\":\"46755\",\"Flags\":3,\"Type\":" - "\"missable\",\"Rarity\":3.03,\"RarityHardcore\":2.54,\"BadgeURL\":" - "\"https:\\/\\/media.retroachievements.org\\/Badge\\/" - "46755.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46755_lock.png\"},{\"ID\":45994,\"MemAddr\":\"R:0xH000100<=10_R:" - "0xH000db3=1_R:0xH001f27>0_R:0xH001f28>0_R:0xH001f29>0_R:0xH001f2a>0_R:" - "0xH00187a>0_N:0xH0013bf=15_0xH000100=17.1._R:0xH001490>0SN:0xH0013bf=15_" - "Q:0xH000100=17.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:" - "0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:" - "0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_C:0xH000dd5=" - "1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:" - "0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:" - "0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_C:0xH000dd5=" - "2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:" - "d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:" - "0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=" - "2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:" - "d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:" - "0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=" - "2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:" - "d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:d0xH000dd5=0_C:0xH000dd5=" - "1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:" - "d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=" - "2.1._N:0xH0013bf=70_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=31_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:" - "0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_C:0xH000dd5=" - "1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=32_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.12.SN:0xH0013bf=15_Q:0xH000100=17." - "1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_C:" - "0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_C:0xH000dd5=" - "1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=14_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=" - "17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=66_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:" - "0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=68_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:" - "0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:" - "0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=32_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:" - "0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_C:0xH000dd5=" - "2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:" - "d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_C:0xH000dd5=" - "2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:" - "d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=" - "2.1._N:0xH0013bf=67_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:" - "0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=" - "2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:" - "d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:d0xH000dd5=0_C:0xH000dd5=" - "1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:" - "d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=" - "2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=31_N:d0xH000dd5=0_C:0xH000dd5=" - "1.1._M:0!=0.12.SN:0xH0013bf=15_Q:0xH000100=17.1._N:0xH0013bf=15_N:" - "0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:" - "0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:" - "0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=14_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=" - "17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=66_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:" - "0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=" - "2.1._N:0xH0013bf=65_N:d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=15_N:" - "0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=" - "2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:" - "0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:" - "d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=68_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:" - "0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=" - "2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=71_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:" - "0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=" - "2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=68_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=71_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=32_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:" - "0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:" - "d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_C:0xH000dd5=" - "2.1._N:0xH0013bf=15_N:0xH000100=17.1._N:0xH0013bf=15_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=16_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=66_N:" - "d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:d0xH000dd5=0_N:0xH000dd5=" - "2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=70_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=15_N:0xH000100=17.1._N:" - "0xH0013bf=15_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=16_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=14_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=66_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=65_N:" - "d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=67_N:d0xH000dd5=0_N:0xH000dd5=" - "2.1._N:0xH0013bf=70_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=31_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.12.ST:0!=0_N:0xH0013bf!=71_R:" - "0xH000019>0\",\"Title\":\"Mini-Mario vs the World " - "II\",\"Description\":\"From Cheese Bridge Area reach and beat #5 Roy's " - "Castle and Forest Fortress in one Session without using Power-Ups " - "except the P-Balloon and in Forest of Illusion 3, without using Yoshi " - "and without active Switch Palace " - "Blocks.\",\"Points\":25,\"Author\":\"kdecks\",\"Modified\":1687365208," - "\"Created\":1489073010,\"BadgeName\":\"46756\",\"Flags\":3,\"Type\":" - "\"missable\",\"Rarity\":2.29,\"RarityHardcore\":1.95,\"BadgeURL\":" - "\"https:\\/\\/media.retroachievements.org\\/Badge\\/" - "46756.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46756_lock.png\"},{\"ID\":45995,\"MemAddr\":\"R:0xH000100<=10_R:" - "0xH000db3=1_R:0xH001f27>0_R:0xH001f28>0_R:0xH001f29>0_R:0xH001f2a>0_R:" - "0xH00187a>0_N:0xH0013bf=34_0xH000100=17.1._R:0xH000019>0SN:0xH0013bf=34_" - "Q:0xH000100=17.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:" - "0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:" - "0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_C:0xH000dd5=" - "2.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:" - "0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=" - "2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:" - "0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=" - "2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_C:0xH000dd5=" - "1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:" - "0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=" - "2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:" - "0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=" - "2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=56_N:d0xH000dd5=0_C:0xH000dd5=" - "1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=56_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=55_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:" - "0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=" - "2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=56_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=55_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=51_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:" - "0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=" - "2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=56_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=55_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=51_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=52_N:d0xH000dd5=0_C:0xH000dd5=" - "1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:0xH0013bf=34_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=33_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=59_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=58_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=56_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=55_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=51_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=52_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=49_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=34_N:0xH000100=17.1._N:" - "0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=" - "2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:" - "d0xH000dd5=0_C:0xH000dd5=2.1._N:0xH0013bf=34_N:0xH000100=17.1._N:" - "0xH0013bf=34_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=33_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=36_N:d0xH000dd5=0_N:0xH000dd5=" - "2.1._N:0xH0013bf=59_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=26_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=24_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=58_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=57_N:" - "d0xH000dd5=0_N:0xH000dd5=2.1._N:0xH0013bf=53_N:d0xH000dd5=0_C:0xH000dd5=" - "1.1._M:0!=0.15.ST:0!=0_N:0xH0013bf!=24_R:0xH001490>0\",\"Title\":\"Mini-" - "Mario vs the World III\",\"Description\":\"From Chocolate Island 1, " - "unlock all Level Entrances in Valley of Bowser and beat the Front Door " - "in one Session without using Power-Ups except Sunken Ghost Ships' Super " - "Star, without using Yoshi and without active Switch Palace " - "Blocks.\",\"Points\":25,\"Author\":\"kdecks\",\"Modified\":1687365228," - "\"Created\":1489073014,\"BadgeName\":\"46757\",\"Flags\":3,\"Type\":" - "\"missable\",\"Rarity\":2.04,\"RarityHardcore\":1.72,\"BadgeURL\":" - "\"https:\\/\\/media.retroachievements.org\\/Badge\\/" - "46757.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46757_lock.png\"},{\"ID\":45996,\"MemAddr\":\"R:0xH000100<=10_R:" - "0xH000db3=1_R:0xH001f27>0_R:0xH001f28>0_R:0xH001f29>0_R:0xH001f2a>0_R:" - "0xH00187a>0_N:0xH0013bf=78_0xH000100=17.1._R:0xH000019>0_R:0xH001490>" - "0SN:0xH0013bf=78_Q:0xH000100=17.1._N:0xH0013bf=78_N:0xH000100=17.1._N:" - "0xH0013bf=78_N:d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=" - "17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:" - "0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_C:0xH000dd5=" - "1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=80_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=81_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:" - "0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=81_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=76_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:" - "0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=81_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=76_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=75_N:d0xH000dd5=0_C:0xH000dd5=" - "1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:0xH0013bf=78_N:d0xH000dd5=0_N:" - "0xH000dd5=1.1._N:0xH0013bf=79_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:" - "0xH0013bf=80_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=81_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=76_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=75_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=74_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._N:0xH0013bf=78_N:0xH000100=17.1._N:" - "0xH0013bf=78_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=79_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=80_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=81_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=76_N:" - "d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=75_N:d0xH000dd5=0_N:0xH000dd5=" - "1.1._N:0xH0013bf=74_N:d0xH000dd5=0_N:0xH000dd5=1.1._N:0xH0013bf=73_N:" - "d0xH000dd5=0_C:0xH000dd5=1.1._M:0!=0.8.ST:0!=0\",\"Title\":\"Mini-Mario " - "vs the World SPECIAL\",\"Description\":\"From Gnarly reach and beat " - "Funky in one Session without using Power-Ups except the P-Balloon, " - "without using Yoshi and without active Switch Palace " - "Blocks.\",\"Points\":50,\"Author\":\"kdecks\",\"Modified\":1687365239," - "\"Created\":1489073019,\"BadgeName\":\"46758\",\"Flags\":3,\"Type\":" - "\"missable\",\"Rarity\":1.97,\"RarityHardcore\":1.65,\"BadgeURL\":" - "\"https:\\/\\/media.retroachievements.org\\/Badge\\/" - "46758.png\",\"BadgeLockedURL\":\"https:\\/\\/" - "media.retroachievements.org\\/Badge\\/" - "46758_lock.png\"}],\"Leaderboards\":[{\"ID\":1998,\"Mem\":\"STA:0xh13bf=" - "h44_0xh1411=1_0xhf31=3_0xRda4=1::CAN:0xh77>0::SUB:0xh1dff=hc::VAL:" - "0xhf30_0xhf33*100_0xhf32*1000_0xhf31*10000\",\"Format\":\"VALUE\"," - "\"LowerIsBetter\":0,\"Title\":\"Everything Is Lava " - "Achievement\",\"Description\":\"Wall touch alert and best time (does " - "not track enemy " - "contact)\",\"Hidden\":true},{\"ID\":53203,\"Mem\":\"STA:1=0_0xH0013bf=" - "49_0xH000071=10_0xH000019=0_0xH000dc2=0::CAN:S0xH0013bf!=49S0xH000071=" - "10_O:0xH000019!=0_0xH000dc2!=0::SUB:S0xH0013bf=49_0xH0013f9=3_0xH0013ef=" - "1S0xH000906>d0xH000906::VAL:M:0=0\",\"Format\":\"TIME\"," - "\"LowerIsBetter\":1,\"Title\":\"RAOlympics - Bowser " - "Bashing\",\"Description\":\"Defeat Bowser the fastest starting as small " - "mario with no backup powers (Front " - "Door)\",\"Hidden\":true},{\"ID\":1991,\"Mem\":\"STA:d0x100=h90a_0x100=" - "h90b_0xRda4=1_0xQda4=1_0xh1f2e=0_0xhdb2=0::CAN:0xh100=0::SUB:0xH001f2e<=" - "96S0xH000dbf>0Sd0xH001f2e=95_0xH001f2e=96::VAL:0xh1f2e\",\"Format\":" - "\"VALUE\",\"LowerIsBetter\":0,\"Title\":\"Penniless " - "Plumbers\",\"Description\":\"Clear as many exits as possible without " - "getting a " - "coin\",\"Hidden\":false},{\"ID\":1992,\"Mem\":\"STA:d0x100=h90a_0x100=" - "h90b_0xRda4=1_0xNda2=1_0xh1f2e=0_0xhdb2=0::CAN:0xh100=0::SUB:0xH001f2e<=" - "96S0xH000dbed0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Yoshi's Island 1 " - "Speedrun\",\"Description\":\"Complete Yoshi's Island 1 with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104611,\"Mem\":\"STA:0xH0013bf=" - "20_d0xH001493=0_0xH001493=8_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=" - "215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:" - "0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\"," - "\"LowerIsBetter\":0,\"Title\":\"Yellow Switch Palace " - "Speedrun\",\"Description\":\"Complete the Yellow Switch Palace with the " - "most time " - "remaining\",\"Hidden\":false},{\"ID\":104612,\"Mem\":\"STA:0xH0013bf=42_" - "0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Yoshi's Island 2 " - "Speedrun\",\"Description\":\"Complete Yoshi's Island 2 with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104613,\"Mem\":\"STA:0xH0013bf=" - "39_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Yoshi's Island 3 " - "Speedrun\",\"Description\":\"Complete Yoshi's Island 3 with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104614,\"Mem\":\"STA:0xH0013bf=" - "38_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Yoshi's Island 4 " - "Speedrun\",\"Description\":\"Complete Yoshi's Island 4 with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104615,\"Mem\":\"STA:0xH0013bf=" - "37_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Iggy's Castle " - "Speedrun\",\"Description\":\"Complete Iggy's Castle with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104721,\"Mem\":\"STA:0xH0013bf=" - "21_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1." - "S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:" - "B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_" - "M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut " - "Plains 1 Speedrun\",\"Description\":\"Complete Donut Plains 1 with the " - "most time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104722,\"Mem\":\"STA:0xH0013bf=9_" - "0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>" - "d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:" - "0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\"," - "\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Plains 2 " - "Speedrun\",\"Description\":\"Complete Donut Plains 2 with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104723,\"Mem\":\"STA:0xH0013bf=8_" - "d0xH001493=0_0xH001493=8_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215." - "1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:" - "0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\"," - "\"LowerIsBetter\":0,\"Title\":\"Green Switch Palace " - "Speedrun\",\"Description\":\"Complete the Green Switch Palace with the " - "most time " - "remaining\",\"Hidden\":false},{\"ID\":104724,\"Mem\":\"STA:0xH0013bf=4_" - "0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::" - "CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:" - "0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\"," - "\"LowerIsBetter\":0,\"Title\":\"Donut Ghost House " - "Speedrun\",\"Description\":\"Complete the Donut Ghost House with the " - "most time " - "remaining\",\"Hidden\":false},{\"ID\":104727,\"Mem\":\"STA:0xH0013bf=10_" - "0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>" - "d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:" - "0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\"," - "\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Secret 1 " - "Speedrun\",\"Description\":\"Complete Donut Secret 1 with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104728,\"Mem\":\"STA:0xH0013bf=" - "19_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1." - "::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:" - "0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\"," - "\"LowerIsBetter\":0,\"Title\":\"Donut Secret House " - "Speedrun\",\"Description\":\"Complete Donut Secret House with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104729,\"Mem\":\"STA:0xH0013bf=" - "47_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Secret 2 " - "Speedrun\",\"Description\":\"Complete Donut Secret 2 with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104725,\"Mem\":\"STA:0xH0013bf=5_" - "0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Plains 3 " - "Speedrun\",\"Description\":\"Complete Donut Plains 3 with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104726,\"Mem\":\"STA:0xH0013bf=6_" - "0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Donut Plains 4 " - "Speedrun\",\"Description\":\"Complete Donut Plains 4 with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104730,\"Mem\":\"STA:0xH0013bf=7_" - "0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Morton's Castle " - "Speedrun\",\"Description\":\"Complete Morton's Castle with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104865,\"Mem\":\"STA:0xH0013bf=" - "62_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1." - "S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:" - "B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_" - "M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla " - "Dome 1 Speedrun\",\"Description\":\"Complete Vanilla Dome 1 with the " - "most time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104866,\"Mem\":\"STA:0xH0013bf=" - "60_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1." - "S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:" - "B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_" - "M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla " - "Dome 2 Speedrun\",\"Description\":\"Complete Vanilla Dome 2 with the " - "most time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104867,\"Mem\":\"STA:0xH0013bf=" - "63_d0xH001493=0_0xH001493=8_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=" - "215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:" - "0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\"," - "\"LowerIsBetter\":0,\"Title\":\"Red Switch Palace " - "Speedrun\",\"Description\":\"Complete the Red Switch Palace with the " - "most time " - "remaining\",\"Hidden\":false},{\"ID\":104868,\"Mem\":\"STA:0xH0013bf=43_" - "0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::" - "CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:" - "0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\"," - "\"LowerIsBetter\":0,\"Title\":\"Vanilla Ghost House " - "Speedrun\",\"Description\":\"Complete Vanilla Ghost House with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104869,\"Mem\":\"STA:0xH0013bf=" - "46_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Dome 3 " - "Speedrun\",\"Description\":\"Complete Vanilla Dome 3 with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104870,\"Mem\":\"STA:0xH0013bf=" - "61_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Dome 4 " - "Speedrun\",\"Description\":\"Complete Vanilla Dome 4 with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104871,\"Mem\":\"STA:0xH0013bf=" - "45_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Secret 1 " - "Speedrun\",\"Description\":\"Complete Vanilla Secret 1 with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104872,\"Mem\":\"STA:0xH0013bf=1_" - "0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Secret 2 " - "Speedrun\",\"Description\":\"Complete Vanilla Secret 2 with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104873,\"Mem\":\"STA:0xH0013bf=2_" - "0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Secret 3 " - "Speedrun\",\"Description\":\"Complete Vanilla Secret 3 with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104874,\"Mem\":\"STA:0xH0013bf=" - "11_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Vanilla Fortress " - "Speedrun\",\"Description\":\"Complete Vanilla Fortress with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104875,\"Mem\":\"STA:0xH0013bf=" - "64_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Lemmy's Castle " - "Speedrun\",\"Description\":\"Complete Lemmy's Castle with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104876,\"Mem\":\"STA:0xH0013bf=" - "15_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Cheese Bridge Area " - "Speedrun\",\"Description\":\"Complete Cheese Bridge Area with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104877,\"Mem\":\"STA:0xH0013bf=" - "16_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Cookie Mountain " - "Speedrun\",\"Description\":\"Complete Cookie Mountain with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104878,\"Mem\":\"STA:0xH0013bf=" - "12_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Butter Bridge 1 " - "Speedrun\",\"Description\":\"Complete Butter Bridge 1 with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104879,\"Mem\":\"STA:0xH0013bf=" - "13_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Butter Bridge 2 " - "Speedrun\",\"Description\":\"Complete Butter Bridge 2 with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104880,\"Mem\":\"STA:0xH0013bf=" - "17_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Soda Lake " - "Speedrun\",\"Description\":\"Complete Soda Lake with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104881,\"Mem\":\"STA:0xH0013bf=" - "14_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Ludwig's Castle " - "Speedrun\",\"Description\":\"Complete Ludwig's Castle with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104882,\"Mem\":\"STA:0xH0013bf=" - "66_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1." - "S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:" - "B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_" - "M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest of " - "Illusion 1 Speedrun\",\"Description\":\"Complete Forest of Illusion 1 " - "with the most time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104883,\"Mem\":\"STA:0xH0013bf=" - "68_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1." - "S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:" - "B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_" - "M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest of " - "Illusion 2 Speedrun\",\"Description\":\"Complete Forest of Illusion 2 " - "with the most time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104884,\"Mem\":\"STA:0xH0013bf=" - "69_d0xH001493=0_0xH001493=8_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=" - "215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:" - "0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\"," - "\"LowerIsBetter\":0,\"Title\":\"Blue Switch Palace " - "Speedrun\",\"Description\":\"Complete the Blue Switch Palace with the " - "most time " - "remaining\",\"Hidden\":false},{\"ID\":104885,\"Mem\":\"STA:0xH0013bf=71_" - "0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.S0xM000906>" - "d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:B:1_A:" - "0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\"," - "\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest of " - "Illusion 3 Speedrun\",\"Description\":\"Complete Forest of Illusion 3 " - "with the most time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104886,\"Mem\":\"STA:0xH0013bf=" - "65_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest Ghost House " - "Speedrun\",\"Description\":\"Complete Forest Ghost House with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104887,\"Mem\":\"STA:0xH0013bf=" - "67_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1." - "S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:" - "B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_" - "M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest of " - "Illusion 4 Speedrun\",\"Description\":\"Complete Forest of Illusion 4 " - "with the most time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104888,\"Mem\":\"STA:0xH0013bf=" - "70_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest Secret Area " - "Speedrun\",\"Description\":\"Complete Forest Secret Area with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104889,\"Mem\":\"STA:0xH0013bf=" - "31_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Forest Fortress " - "Speedrun\",\"Description\":\"Complete Forest Fortress with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104890,\"Mem\":\"STA:0xH0013bf=" - "32_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Roy's Castle " - "Speedrun\",\"Description\":\"Complete Roy's Castle with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104913,\"Mem\":\"STA:N:0x " - "0000d1=16_N:0x " - "0000d3=352_0xH000f31=3.1._0xH0013bf=34_0xM000906>d0xM000906_0xS0013cf=0_" - "O:0xH000dc2=20_P:0xH000dc2=215.1.SR:0xH000100=14::CAN:0=1::SUB:0=0::VAL:" - "B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_" - "M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate " - "Island 1 Speedrun\",\"Description\":\"Complete Chocolate Island 1 with " - "the most time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104914,\"Mem\":\"STA:0xH0013bf=" - "33_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Choco-Ghost House " - "Speedrun\",\"Description\":\"Complete Choco-Ghost House with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104915,\"Mem\":\"STA:0xH0013bf=" - "36_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1." - "S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:" - "B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_" - "M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate " - "Island 2 Speedrun\",\"Description\":\"Complete Chocolate Island 2 with " - "the most time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104916,\"Mem\":\"STA:0xH0013bf=" - "35_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate Island 3 " - "Speedrun\",\"Description\":\"Complete Chocolate Island 3 with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104917,\"Mem\":\"STA:0xH0013bf=" - "27_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate Fortress " - "Speedrun\",\"Description\":\"Complete Chocolate Fortress with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104918,\"Mem\":\"STA:0xH0013bf=" - "29_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate Island 4 " - "Speedrun\",\"Description\":\"Complete Chocolate Island 4 with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104919,\"Mem\":\"STA:0xH0013bf=" - "28_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate Island 5 " - "Speedrun\",\"Description\":\"Complete Chocolate Island 5 with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104920,\"Mem\":\"STA:0xH0013bf=" - "59_0xS0013cf=0_0xM000906>d0xM000906_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Chocolate Secret " - "Speedrun\",\"Description\":\"Complete Chocolate Secret with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104921,\"Mem\":\"STA:0xH0013bf=" - "26_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Wendy's Castle " - "Speedrun\",\"Description\":\"Complete Wendy's Castle with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104922,\"Mem\":\"STA:0xH0013bf=" - "24_0xS0013cf=0_d0xH001493=0_0xH001493=255_0x " - "000096>=6144_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1.::CAN:0=1::" - "SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:" - "0xH000f30*f2.5_M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0," - "\"Title\":\"Sunken Ghost Ship Speedrun\",\"Description\":\"Complete " - "Sunken Ghost Ship with the most time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104931,\"Mem\":\"STA:0xH0013bf=" - "58_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley of Bowser 1 " - "Speedrun\",\"Description\":\"Complete Valley of Bowser 1 with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104932,\"Mem\":\"STA:0xH0013bf=" - "57_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1." - "S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:" - "B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_" - "M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley of " - "Bowser 2 Speedrun\",\"Description\":\"Complete Valley of Bowser 2 with " - "the most time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104933,\"Mem\":\"STA:0xH0013bf=" - "56_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1." - "S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:" - "B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_" - "M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley " - "Ghost House Speedrun\",\"Description\":\"Complete Valley Ghost House " - "with the most time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104934,\"Mem\":\"STA:0xH0013bf=" - "55_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley of Bowser 3 " - "Speedrun\",\"Description\":\"Complete Valley of Bowser 3 with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104935,\"Mem\":\"STA:0xH0013bf=" - "51_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1." - "S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:" - "B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_" - "M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley of " - "Bowser 4 Speedrun\",\"Description\":\"Complete Valley of Bowser 4 with " - "the most time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104936,\"Mem\":\"STA:0xH0013bf=" - "53_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Valley Fortress " - "Speedrun\",\"Description\":\"Complete Valley Fortress with the most " - "time remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104937,\"Mem\":\"STA:0xH0013bf=" - "52_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Larry's Castle " - "Speedrun\",\"Description\":\"Complete Larry's Castle with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104938,\"Mem\":\"STA:0xH0013bf=" - "88_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1." - "S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:" - "B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_" - "M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Star World " - "1 Speedrun\",\"Description\":\"Complete Star World 1 with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104939,\"Mem\":\"STA:0xH0013bf=" - "84_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1." - "S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:" - "B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_" - "M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Star World " - "2 Speedrun\",\"Description\":\"Complete Star World 2 with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104940,\"Mem\":\"STA:0xH0013bf=" - "86_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1." - "S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:" - "B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_" - "M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Star World " - "3 Speedrun\",\"Description\":\"Complete Star World 3 with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104941,\"Mem\":\"STA:0xH0013bf=" - "89_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1." - "S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:" - "B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_" - "M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Star World " - "4 Speedrun\",\"Description\":\"Complete Star World 4 with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104942,\"Mem\":\"STA:0xH0013bf=" - "90_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:0xH000dc2=215.1." - "S0xM000906>d0xM000906Sd0xH001434=0_0xH001434=48::CAN:0=1::SUB:0=0::VAL:" - "B:1_A:0xH000f31*10000_A:0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_" - "M:0\",\"Format\":\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Star World " - "5 Speedrun\",\"Description\":\"Complete Star World 5 with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104943,\"Mem\":\"STA:0xH0013bf=" - "78_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Gnarly " - "Speedrun\",\"Description\":\"Complete Gnarly with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104944,\"Mem\":\"STA:0xH0013bf=" - "79_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Tubular " - "Speedrun\",\"Description\":\"Complete Tubular with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104945,\"Mem\":\"STA:0xH0013bf=" - "80_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Way Cool " - "Speedrun\",\"Description\":\"Complete Way Cool with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104946,\"Mem\":\"STA:0xH0013bf=" - "81_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Awesome " - "Speedrun\",\"Description\":\"Complete Awesome with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104947,\"Mem\":\"STA:0xH0013bf=" - "76_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Groovy " - "Speedrun\",\"Description\":\"Complete Groovy with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104948,\"Mem\":\"STA:0xH0013bf=" - "75_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Mondo " - "Speedrun\",\"Description\":\"Complete Mondo with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104949,\"Mem\":\"STA:0xH0013bf=" - "74_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Outrageous " - "Speedrun\",\"Description\":\"Complete Outrageous with the most time " - "remaining without using a " - "checkpoint\",\"Hidden\":false},{\"ID\":104950,\"Mem\":\"STA:0xH0013bf=" - "73_0xM000906>d0xM000906_0xS0013cf=0_Z:0xH000100=14_O:0xH000dc2=20_P:" - "0xH000dc2=215.1.::CAN:0=1::SUB:0=0::VAL:B:1_A:0xH000f31*10000_A:" - "0xH000f32*1000_A:0xH000f33*100_A:0xH000f30*f2.5_M:0\",\"Format\":" - "\"FIXED2\",\"LowerIsBetter\":0,\"Title\":\"Funky " - "Speedrun\",\"Description\":\"Complete Funky with the most time " - "remaining without using a checkpoint\",\"Hidden\":false}]}}"; -}; +class RetroAchievementsOfflineClientTest : public testing::Test {}; TEST_F(RetroAchievementsOfflineClientTest, PingCheckReturnsSuccess) { auto repo = SqliteAchievementRepository(":memory:"); @@ -2500,49 +31,6 @@ TEST_F(RetroAchievementsOfflineClientTest, PingCheckReturnsSuccess) { // Test awarding achievement in mode we dont have when we have the other mode // Test awarding achievement we dont have -// test handle game id request -TEST_F(RetroAchievementsOfflineClientTest, GameIdWithNoRecordReturnsFailure) { - auto repo = SqliteAchievementRepository(":memory:"); - auto service = AchievementService(repo); - - RetroAchievementsOfflineClient offlineClient(service); - - const auto response = - offlineClient.handleRequest("https://retroachievements.com/dorequest.php", - "r=gameid&g=228", "application/json"); - - ASSERT_EQ(response.body_length, 0); - ASSERT_EQ(response.http_status_code, 500); - ASSERT_NE(nullptr, response.body); - ASSERT_EQ("", std::string(response.body)); -} - -TEST_F(RetroAchievementsOfflineClientTest, GameIdReturnsSuccess) { - auto repo = SqliteAchievementRepository(":memory:"); - auto service = AchievementService(repo); - - RetroAchievementsOfflineClient offlineClient(service); - - // Create game id response - auto gameIdResponse = nlohmann::json::object(); - gameIdResponse["Success"] = true; - gameIdResponse["GameID"] = 100; - - offlineClient.processResponse("r=gameid&m=testhash", gameIdResponse.dump()); - - const auto response = - offlineClient.handleRequest("https://retroachievements.com/dorequest.php", - "r=gameid&m=testhash", "application/json"); - - ASSERT_EQ(response.http_status_code, 200); - ASSERT_NE(nullptr, response.body); - auto body = std::string(response.body); - - const auto parsedResponse = nlohmann::json::parse(body).get(); - ASSERT_EQ(parsedResponse.Success, true); - ASSERT_EQ(parsedResponse.GameID, 100); -} - TEST_F(RetroAchievementsOfflineClientTest, LoginWithPasswordOffline) { auto repo = SqliteAchievementRepository(":memory:"); auto service = AchievementService(repo); @@ -2592,10 +80,10 @@ TEST_F(RetroAchievementsOfflineClientTest, LoginGetsScoreCorrectly) { RetroAchievementsOfflineClient offlineClient(service); - service.createOrUpdateUser(User{.username = "testuser", - .token = "testtoken", - .softcoreScore = 1000, - .score = 2000}); + service.create(User{.username = "testuser", + .token = "testtoken", + .softcoreScore = 1000, + .score = 2000}); const auto response = offlineClient.handleRequest( "https://retroachievements.com/dorequest.php", @@ -2618,276 +106,133 @@ TEST_F(RetroAchievementsOfflineClientTest, LoginGetsScoreCorrectly) { ASSERT_EQ(parsedResponse.SoftcoreScore, 1000); } -TEST_F(RetroAchievementsOfflineClientTest, NoPatchDataReturnsError) { - auto repo = SqliteAchievementRepository(":memory:"); - auto service = AchievementService(repo); - - RetroAchievementsOfflineClient offlineClient(service); - - const auto response = - offlineClient.handleRequest("https://retroachievements.com/dorequest.php", - "r=patch&g=228", "application/json"); - - ASSERT_EQ(response.body_length, 0); - ASSERT_EQ(response.http_status_code, 500); - ASSERT_NE(nullptr, response.body); - ASSERT_EQ("", std::string(response.body)); -} - -TEST_F(RetroAchievementsOfflineClientTest, PatchDataCachedCorrectly) { - auto repo = SqliteAchievementRepository(":memory:"); - auto service = AchievementService(repo); - - RetroAchievementsOfflineClient offlineClient(service); - - offlineClient.processResponse("r=patch&g=228", patchData); - - auto response = - offlineClient.handleRequest("https://retroachievements.com/dorequest.php", - "r=patch&g=228", "application/json"); - - ASSERT_NE(nullptr, response.body); - ASSERT_TRUE(response.body_length > 0); - ASSERT_EQ(response.http_status_code, 200); - - const auto json = nlohmann::json::parse(patchData); - const auto patchResponse = json.get(); - - const auto cachedResponse = - nlohmann::json::parse(response.body).get(); - - ASSERT_EQ(cachedResponse.PatchData.ID, patchResponse.PatchData.ID); - ASSERT_EQ(cachedResponse.PatchData.Title, patchResponse.PatchData.Title); - ASSERT_EQ(cachedResponse.PatchData.ImageIcon, - patchResponse.PatchData.ImageIcon); - ASSERT_EQ(cachedResponse.PatchData.ConsoleID, - patchResponse.PatchData.ConsoleID); - ASSERT_EQ(cachedResponse.PatchData.RichPresencePatch, - patchResponse.PatchData.RichPresencePatch); - ASSERT_EQ(cachedResponse.PatchData.ImageIconURL, - patchResponse.PatchData.ImageIconURL); - - ASSERT_EQ(patchResponse.PatchData.Achievements.size(), - cachedResponse.PatchData.Achievements.size()); - - ASSERT_EQ(patchResponse.PatchData.Leaderboards.size(), - cachedResponse.PatchData.Leaderboards.size()); - - for (const auto &achieve : patchResponse.PatchData.Achievements) { - bool found = false; - for (const auto &cachedAchieve : cachedResponse.PatchData.Achievements) { - if (achieve.ID == cachedAchieve.ID) { - ASSERT_EQ(cachedAchieve.ID, achieve.ID); - ASSERT_EQ(cachedAchieve.Author, achieve.Author); - ASSERT_EQ(cachedAchieve.Title, achieve.Title); - ASSERT_EQ(cachedAchieve.Description, achieve.Description); - ASSERT_EQ(cachedAchieve.Points, achieve.Points); - ASSERT_EQ(cachedAchieve.Flags, achieve.Flags); - ASSERT_EQ(cachedAchieve.Modified, achieve.Modified); - ASSERT_EQ(cachedAchieve.Created, achieve.Created); - ASSERT_EQ(cachedAchieve.Rarity, achieve.Rarity); - ASSERT_EQ(cachedAchieve.Type, achieve.Type); - ASSERT_EQ(cachedAchieve.BadgeName, achieve.BadgeName); - ASSERT_EQ(cachedAchieve.MemAddr, achieve.MemAddr); - ASSERT_EQ(cachedAchieve.RarityHardcore, achieve.RarityHardcore); - ASSERT_EQ(cachedAchieve.BadgeURL, achieve.BadgeURL); - ASSERT_EQ(cachedAchieve.BadgeLockedURL, achieve.BadgeLockedURL); - - found = true; - break; - } - } - - ASSERT_TRUE(found); - } - - for (const auto &leaderboard : patchResponse.PatchData.Leaderboards) { - bool found = false; - for (const auto &cachedLeaderboard : - cachedResponse.PatchData.Leaderboards) { - if (leaderboard.ID == cachedLeaderboard.ID) { - ASSERT_EQ(cachedLeaderboard.ID, leaderboard.ID); - ASSERT_EQ(cachedLeaderboard.Title, leaderboard.Title); - ASSERT_EQ(cachedLeaderboard.Description, leaderboard.Description); - ASSERT_EQ(cachedLeaderboard.LowerIsBetter, leaderboard.LowerIsBetter); - ASSERT_EQ(cachedLeaderboard.Hidden, leaderboard.Hidden); - ASSERT_EQ(cachedLeaderboard.Mem, leaderboard.Mem); - ASSERT_EQ(cachedLeaderboard.LowerIsBetter, leaderboard.LowerIsBetter); - - found = true; - break; - } - } - - ASSERT_TRUE(found); - } -} - -TEST_F(RetroAchievementsOfflineClientTest, - PatchResponsePopulatesAchievementsAndUnlocks) { - auto repo = SqliteAchievementRepository(":memory:"); - auto service = AchievementService(repo); - - RetroAchievementsOfflineClient offlineClient(service); - - offlineClient.processResponse("r=patch&g=228&u=testuser", patchData); - - auto response = - offlineClient.handleRequest("https://retroachievements.com/dorequest.php", - "r=patch&g=228", "application/json"); - - ASSERT_NE(nullptr, response.body); - ASSERT_TRUE(response.body_length > 0); - ASSERT_EQ(response.http_status_code, 200); - - const auto json = nlohmann::json::parse(patchData); - const auto patchResponse = json.get(); - - for (const auto &achieve : patchResponse.PatchData.Achievements) { - auto achievement = service.getAchievement(achieve.ID); - if (achieve.Flags == 5) { - ASSERT_FALSE(achievement.has_value()); - continue; - } - - ASSERT_TRUE(achievement.has_value()); - ASSERT_EQ(achievement->id, achieve.ID); - ASSERT_EQ(achievement->name, achieve.Title); - ASSERT_EQ(achievement->description, achieve.Description); - ASSERT_EQ(achievement->points, achieve.Points); - ASSERT_EQ(achievement->flags, achieve.Flags); - ASSERT_EQ(achievement->gameId, 228); - - auto unlock = service.getUserUnlock("testuser", achieve.ID); - ASSERT_TRUE(unlock.has_value()); - } -} - -TEST_F(RetroAchievementsOfflineClientTest, - StartSessionWorksAfterOfflineAchievementUnlocks) { - auto repo = SqliteAchievementRepository(":memory:"); - auto service = AchievementService(repo); - - RetroAchievementsOfflineClient offlineClient(service); - - offlineClient.processResponse("r=patch&g=228&u=testuser", patchData); - - service.createOrUpdateUser(User{.username = "testuser", - .token = "testtoken", - .softcoreScore = 80, - .score = 102}); - - // Do unlock achievement request hardcore - auto response = offlineClient.handleRequest( - "https://retroachievements.com/dorequest.php", - "r=awardachievement&u=testuser&t=testtoken&a=2251&h=1", - "application/json"); - - ASSERT_NE(nullptr, response.body); - ASSERT_TRUE(response.body_length > 0); - ASSERT_EQ(response.http_status_code, 200); - - const auto json = nlohmann::json::parse(response.body); - const auto awardAchievementResponse = json.get(); - - ASSERT_EQ(2251, awardAchievementResponse.AchievementID); - ASSERT_EQ(84, awardAchievementResponse.AchievementsRemaining); - ASSERT_EQ(104, awardAchievementResponse.Score); - ASSERT_EQ(80, awardAchievementResponse.SoftcoreScore); - ASSERT_TRUE(awardAchievementResponse.Success); - - auto response2 = offlineClient.handleRequest( - "https://retroachievements.com/dorequest.php", - "r=awardachievement&u=testuser&t=testtoken&a=45994&h=1", - "application/json"); - - ASSERT_NE(nullptr, response2.body); - ASSERT_TRUE(response2.body_length > 0); - ASSERT_EQ(response2.http_status_code, 200); - - const auto json2 = nlohmann::json::parse(response2.body); - const auto awardAchievementResponse2 = json2.get(); - - ASSERT_EQ(45994, awardAchievementResponse2.AchievementID); - ASSERT_EQ(83, awardAchievementResponse2.AchievementsRemaining); - ASSERT_EQ(129, awardAchievementResponse2.Score); - ASSERT_EQ(80, awardAchievementResponse2.SoftcoreScore); - ASSERT_TRUE(awardAchievementResponse2.Success); - - auto response3 = offlineClient.handleRequest( - "https://retroachievements.com/dorequest.php", - "r=awardachievement&u=testuser&t=testtoken&a=2305&h=0", - "application/json"); - - ASSERT_NE(nullptr, response3.body); - ASSERT_TRUE(response3.body_length > 0); - ASSERT_EQ(response3.http_status_code, 200); - - const auto json3 = nlohmann::json::parse(response3.body); - const auto awardAchievementResponse3 = json3.get(); - - ASSERT_EQ(2305, awardAchievementResponse3.AchievementID); - ASSERT_EQ(82, awardAchievementResponse3.AchievementsRemaining); - ASSERT_EQ(129, awardAchievementResponse3.Score); - ASSERT_EQ(81, awardAchievementResponse3.SoftcoreScore); - ASSERT_TRUE(awardAchievementResponse3.Success); - - auto startSessionResponse = offlineClient.handleRequest( - "https://retroachievements.com/dorequest.php", - "r=startsession&u=testuser&t=testtoken&g=228&h=1", "application/json"); - - const auto json4 = nlohmann::json::parse(startSessionResponse.body); - const auto startSession = json4.get(); - - ASSERT_TRUE(startSession.Success); - ASSERT_EQ(2, startSession.HardcoreUnlocks.size()); - ASSERT_EQ(1, startSession.Unlocks.size()); - ASSERT_TRUE(startSession.ServerNow > 0); - - auto seen2251 = false; - auto seen45994 = false; - for (auto unlock : startSession.HardcoreUnlocks) { - if (unlock.ID == 2251) { - seen2251 = true; - } else if (unlock.ID == 45994) { - seen45994 = true; - } - } - - ASSERT_TRUE(seen2251 && seen45994); - - auto seen2305 = false; - for (auto unlock : startSession.Unlocks) { - if (unlock.ID == 2305) { - seen2305 = true; - } - } - - ASSERT_TRUE(seen2305); - - // TODO: VERIFY THAT SYNCING WORKS -} - -TEST_F(RetroAchievementsOfflineClientTest, - StartSessionWhenNoAchievementsUnlocked) { - auto repo = SqliteAchievementRepository(":memory:"); - auto service = AchievementService(repo); - - RetroAchievementsOfflineClient offlineClient(service); - - offlineClient.processResponse("r=patch&g=228&u=testuser", patchData); - auto startSessionResponse = offlineClient.handleRequest( - "https://retroachievements.com/dorequest.php", - "r=startsession&u=testuser&t=testtoken&g=228&h=1", "application/json"); - - const auto json = nlohmann::json::parse(startSessionResponse.body); - const auto startSession = json.get(); - - ASSERT_TRUE(startSession.Success); - ASSERT_EQ(0, startSession.HardcoreUnlocks.size()); - ASSERT_EQ(0, startSession.Unlocks.size()); - ASSERT_TRUE(startSession.ServerNow > 0); -} +// TEST_F(RetroAchievementsOfflineClientTest, +// StartSessionWorksAfterOfflineAchievementUnlocks) { +// auto repo = SqliteAchievementRepository(":memory:"); +// auto service = AchievementService(repo); +// +// RetroAchievementsOfflineClient offlineClient(service); +// +// offlineClient.processResponse("r=patch&g=228&u=testuser", patchData); +// +// service.create(User{.username = "testuser", +// .token = "testtoken", +// .softcoreScore = 80, +// .score = 102}); +// +// // Do unlock achievement request hardcore +// auto response = offlineClient.handleRequest( +// "https://retroachievements.com/dorequest.php", +// "r=awardachievement&u=testuser&t=testtoken&a=2251&h=1", +// "application/json"); +// +// ASSERT_NE(nullptr, response.body); +// ASSERT_TRUE(response.body_length > 0); +// ASSERT_EQ(response.http_status_code, 200); +// +// const auto json = nlohmann::json::parse(response.body); +// const auto awardAchievementResponse = json.get(); +// +// ASSERT_EQ(2251, awardAchievementResponse.AchievementID); +// ASSERT_EQ(84, awardAchievementResponse.AchievementsRemaining); +// ASSERT_EQ(104, awardAchievementResponse.Score); +// ASSERT_EQ(80, awardAchievementResponse.SoftcoreScore); +// ASSERT_TRUE(awardAchievementResponse.Success); +// +// auto response2 = offlineClient.handleRequest( +// "https://retroachievements.com/dorequest.php", +// "r=awardachievement&u=testuser&t=testtoken&a=45994&h=1", +// "application/json"); +// +// ASSERT_NE(nullptr, response2.body); +// ASSERT_TRUE(response2.body_length > 0); +// ASSERT_EQ(response2.http_status_code, 200); +// +// const auto json2 = nlohmann::json::parse(response2.body); +// const auto awardAchievementResponse2 = +// json2.get(); +// +// ASSERT_EQ(45994, awardAchievementResponse2.AchievementID); +// ASSERT_EQ(83, awardAchievementResponse2.AchievementsRemaining); +// ASSERT_EQ(129, awardAchievementResponse2.Score); +// ASSERT_EQ(80, awardAchievementResponse2.SoftcoreScore); +// ASSERT_TRUE(awardAchievementResponse2.Success); +// +// auto response3 = offlineClient.handleRequest( +// "https://retroachievements.com/dorequest.php", +// "r=awardachievement&u=testuser&t=testtoken&a=2305&h=0", +// "application/json"); +// +// ASSERT_NE(nullptr, response3.body); +// ASSERT_TRUE(response3.body_length > 0); +// ASSERT_EQ(response3.http_status_code, 200); +// +// const auto json3 = nlohmann::json::parse(response3.body); +// const auto awardAchievementResponse3 = +// json3.get(); +// +// ASSERT_EQ(2305, awardAchievementResponse3.AchievementID); +// ASSERT_EQ(82, awardAchievementResponse3.AchievementsRemaining); +// ASSERT_EQ(129, awardAchievementResponse3.Score); +// ASSERT_EQ(81, awardAchievementResponse3.SoftcoreScore); +// ASSERT_TRUE(awardAchievementResponse3.Success); +// +// auto startSessionResponse = offlineClient.handleRequest( +// "https://retroachievements.com/dorequest.php", +// "r=startsession&u=testuser&t=testtoken&g=228&h=1", "application/json"); +// +// const auto json4 = nlohmann::json::parse(startSessionResponse.body); +// const auto startSession = json4.get(); +// +// ASSERT_TRUE(startSession.Success); +// ASSERT_EQ(2, startSession.HardcoreUnlocks.size()); +// ASSERT_EQ(1, startSession.Unlocks.size()); +// ASSERT_TRUE(startSession.ServerNow > 0); +// +// auto seen2251 = false; +// auto seen45994 = false; +// for (auto unlock : startSession.HardcoreUnlocks) { +// if (unlock.ID == 2251) { +// seen2251 = true; +// } else if (unlock.ID == 45994) { +// seen45994 = true; +// } +// } +// +// ASSERT_TRUE(seen2251 && seen45994); +// +// auto seen2305 = false; +// for (auto unlock : startSession.Unlocks) { +// if (unlock.ID == 2305) { +// seen2305 = true; +// } +// } +// +// ASSERT_TRUE(seen2305); +// +// // TODO: VERIFY THAT SYNCING WORKS +// } + +// TEST_F(RetroAchievementsOfflineClientTest, +// StartSessionWhenNoAchievementsUnlocked) { +// auto repo = SqliteAchievementRepository(":memory:"); +// auto service = AchievementService(repo); +// +// RetroAchievementsOfflineClient offlineClient(service); +// +// offlineClient.processResponse("r=patch&g=228&u=testuser", patchData); +// auto startSessionResponse = offlineClient.handleRequest( +// "https://retroachievements.com/dorequest.php", +// "r=startsession&u=testuser&t=testtoken&g=228&h=1", "application/json"); +// +// const auto json = nlohmann::json::parse(startSessionResponse.body); +// const auto startSession = json.get(); +// +// ASSERT_TRUE(startSession.Success); +// ASSERT_EQ(0, startSession.HardcoreUnlocks.size()); +// ASSERT_EQ(0, startSession.Unlocks.size()); +// ASSERT_TRUE(startSession.ServerNow > 0); +// } TEST_F(RetroAchievementsOfflineClientTest, AwardAchievementFirstTimeNonHardcore) { @@ -2901,16 +246,16 @@ TEST_F(RetroAchievementsOfflineClientTest, .token = "token", .softcoreScore = 100, .score = 50}; - service.createOrUpdateUser(user); + service.create(user); Achievement achievement{.id = 1, - .name = "Test Achievement", + .title = "Test Achievement", .description = "Test", - .imageUrl = "", + .badgeUrl = "", .points = 25, .type = "progression", .displayOrder = 0, - .gameId = 1, + .achievementSetId = 1, .flags = 3}; repo.create(achievement); @@ -2924,8 +269,8 @@ TEST_F(RetroAchievementsOfflineClientTest, // Verify points were added to non-hardcore auto updatedUser = service.getUser("testuser"); ASSERT_TRUE(updatedUser.has_value()); - EXPECT_EQ(updatedUser->softcoreScore, 125); // 100 + 25 - EXPECT_EQ(updatedUser->score, 50); // Unchanged + EXPECT_EQ(updatedUser->softcoreScore, 125); // 100 + 25 + EXPECT_EQ(updatedUser->score, 50); // Unchanged } TEST_F(RetroAchievementsOfflineClientTest, AwardAchievementFirstTimeHardcore) { @@ -2939,16 +284,16 @@ TEST_F(RetroAchievementsOfflineClientTest, AwardAchievementFirstTimeHardcore) { .token = "token", .softcoreScore = 100, .score = 50}; - service.createOrUpdateUser(user); + service.create(user); Achievement achievement{.id = 1, - .name = "Test Achievement", + .title = "Test Achievement", .description = "Test", - .imageUrl = "", + .badgeUrl = "", .points = 25, .type = "progression", .displayOrder = 0, - .gameId = 1, + .achievementSetId = 1, .flags = 3}; repo.create(achievement); @@ -2962,8 +307,8 @@ TEST_F(RetroAchievementsOfflineClientTest, AwardAchievementFirstTimeHardcore) { // Verify points were added to hardcore auto updatedUser = service.getUser("testuser"); ASSERT_TRUE(updatedUser.has_value()); - EXPECT_EQ(updatedUser->softcoreScore, 100); // Unchanged - EXPECT_EQ(updatedUser->score, 75); // 50 + 25 + EXPECT_EQ(updatedUser->softcoreScore, 100); // Unchanged + EXPECT_EQ(updatedUser->score, 75); // 50 + 25 } TEST_F(RetroAchievementsOfflineClientTest, @@ -2978,16 +323,16 @@ TEST_F(RetroAchievementsOfflineClientTest, .token = "token", .softcoreScore = 100, .score = 50}; - service.createOrUpdateUser(user); + service.create(user); Achievement achievement{.id = 1, - .name = "Test Achievement", + .title = "Test Achievement", .description = "Test", - .imageUrl = "", + .badgeUrl = "", .points = 25, .type = "progression", .displayOrder = 0, - .gameId = 1, + .achievementSetId = 1, .flags = 3}; repo.create(achievement); @@ -3000,8 +345,8 @@ TEST_F(RetroAchievementsOfflineClientTest, // Verify initial state auto userAfterFirst = service.getUser("testuser"); ASSERT_TRUE(userAfterFirst.has_value()); - EXPECT_EQ(userAfterFirst->softcoreScore, 125); // 100 + 25 - EXPECT_EQ(userAfterFirst->score, 50); // Unchanged + EXPECT_EQ(userAfterFirst->softcoreScore, 125); // 100 + 25 + EXPECT_EQ(userAfterFirst->score, 50); // Unchanged // Then, award in hardcore mode (should move points) auto response2 = offlineClient.handleRequest( @@ -3035,16 +380,16 @@ TEST_F(RetroAchievementsOfflineClientTest, .token = "token", .softcoreScore = 100, .score = 50}; - service.createOrUpdateUser(user); + service.create(user); Achievement achievement{.id = 1, - .name = "Test Achievement", + .title = "Test Achievement", .description = "Test", - .imageUrl = "", + .badgeUrl = "", .points = 25, .type = "progression", .displayOrder = 0, - .gameId = 1, + .achievementSetId = 1, .flags = 3}; repo.create(achievement); @@ -3057,8 +402,8 @@ TEST_F(RetroAchievementsOfflineClientTest, // Verify initial state auto userAfterFirst = service.getUser("testuser"); ASSERT_TRUE(userAfterFirst.has_value()); - EXPECT_EQ(userAfterFirst->softcoreScore, 100); // Unchanged - EXPECT_EQ(userAfterFirst->score, 75); // 50 + 25 + EXPECT_EQ(userAfterFirst->softcoreScore, 100); // Unchanged + EXPECT_EQ(userAfterFirst->score, 75); // 50 + 25 // Then, try to award in non-hardcore mode (should be ignored - no points // change) @@ -3070,8 +415,8 @@ TEST_F(RetroAchievementsOfflineClientTest, // Verify points unchanged (hardcore takes precedence) auto finalUser = service.getUser("testuser"); ASSERT_TRUE(finalUser.has_value()); - EXPECT_EQ(finalUser->softcoreScore, 100); // Unchanged - EXPECT_EQ(finalUser->score, 75); // Unchanged + EXPECT_EQ(finalUser->softcoreScore, 100); // Unchanged + EXPECT_EQ(finalUser->score, 75); // Unchanged // Verify unlock state (both should be true) auto unlock = service.getUserUnlock("testuser", 1); @@ -3092,16 +437,16 @@ TEST_F(RetroAchievementsOfflineClientTest, .token = "token", .softcoreScore = 100, .score = 50}; - service.createOrUpdateUser(user); + service.create(user); Achievement achievement{.id = 1, - .name = "Test Achievement", + .title = "Test Achievement", .description = "Test", - .imageUrl = "", + .badgeUrl = "", .points = 25, .type = "progression", .displayOrder = 0, - .gameId = 1, + .achievementSetId = 1, .flags = 3}; repo.create(achievement); @@ -3119,8 +464,8 @@ TEST_F(RetroAchievementsOfflineClientTest, // Verify points only added once auto finalUser = service.getUser("testuser"); ASSERT_TRUE(finalUser.has_value()); - EXPECT_EQ(finalUser->softcoreScore, 125); // 100 + 25 (only once) - EXPECT_EQ(finalUser->score, 50); // Unchanged + EXPECT_EQ(finalUser->softcoreScore, 125); // 100 + 25 (only once) + EXPECT_EQ(finalUser->score, 50); // Unchanged } /** @@ -3152,9 +497,6 @@ processLogin2Response. You should test that a successful login response correctly adds a user and updates their scores in the cache, and that a failed response does nothing. -gameid: No tests for processGameIdResponse. A test should -confirm that the game ID is correctly cached. - startsession: processStartSessionResponse is untested. You should test: Correctly marking achievements as unlocked from the response. From e963be6275bacf0f057e8f43a0e60dcf4786d033 Mon Sep 17 00:00:00 2001 From: biscuitcakes Date: Sun, 1 Feb 2026 15:00:13 -0600 Subject: [PATCH 4/5] ok --- CMakeLists.txt | 2 + qml/components/QuickMenu.qml | 1010 +++++++++++++---- .../achievements/AchievementListButton.qml | 1 + .../achievements/achievement_repository.hpp | 2 +- src/app/achievements/achievement_service.cpp | 30 +- src/app/achievements/achievement_service.hpp | 6 +- .../achievements/gui/AchievementSetItem.cpp | 106 +- .../achievements/gui/AchievementSetItem.hpp | 40 +- .../gui/retro_achievements_game_item.cpp | 58 + .../gui/retro_achievements_game_item.hpp | 60 + .../sqlite_achievement_repository.cpp | 48 +- .../sqlite_achievement_repository.hpp | 3 +- src/app/emulator_item_renderer.cpp | 6 +- .../offline/rcheevos_offline_client.cpp | 15 +- src/app/rcheevos/ra_client.cpp | 2 +- src/main.cpp | 11 +- .../achievements/achievement_service_test.cpp | 10 +- .../app/emulation/emulator_instance_test.cpp | 2 +- 18 files changed, 1023 insertions(+), 389 deletions(-) create mode 100644 src/app/achievements/gui/retro_achievements_game_item.cpp create mode 100644 src/app/achievements/gui/retro_achievements_game_item.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 474a6992..de533370 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -207,6 +207,8 @@ add_library(firelight_lib src/app/achievements/models/achievement_progress.hpp src/gui/qt_achievement_service_proxy.cpp src/gui/qt_achievement_service_proxy.hpp + src/app/achievements/gui/retro_achievements_game_item.cpp + src/app/achievements/gui/retro_achievements_game_item.hpp ) target_include_directories(firelight_lib PUBLIC ${SDL2_INCLUDE_DIRS} ${FFMPEG_INCLUDE_DIRS} include ${CMAKE_SOURCE_DIR}/src/app) diff --git a/qml/components/QuickMenu.qml b/qml/components/QuickMenu.qml index 1df9431f..cb2c17fe 100644 --- a/qml/components/QuickMenu.qml +++ b/qml/components/QuickMenu.qml @@ -17,6 +17,12 @@ Pane { entryId: EmulationService.currentEntryId } + RetroAchievementsGame { + id: rcheevosGame + contentHash: EmulationService.currentContentHash + hardcore: AchievementService.inHardcoreSession + } + horizontalPadding: 16 verticalPadding: 0 background: Item {} @@ -135,7 +141,7 @@ Pane { replaceEnter: Transition { NumberAnimation { - duration: 200 + duration: 100 from: 0.0 property: "opacity" to: 1.0 @@ -150,7 +156,7 @@ Pane { } replaceExit: Transition { NumberAnimation { - duration: 20 + duration: 100 from: 1.0 property: "opacity" to: 0.0 @@ -195,267 +201,771 @@ Pane { Component { id: achievementsView FocusScope { - AchievementSet { - id: achievementSet - contentHash: EmulationService.currentContentHash - hardcore: AchievementService.inHardcoreSession - // Component.onCompleted: { - // console.log("Achievements view loaded for content hash: " + EmulationService.currentContentHash) - // console.log("Has achievements: " + hasAchievements) - // console.log("Set ID: " + setId) - // console.log("Icon URL: " + iconUrl) - // console.log("Set name: " + name) - // console.log("Num achievements: " + numAchievements) - // console.log("Num achievements earned: " + numEarnedHardcore) - // console.log("Num points: " + totalNumPoints) - // } - } - - Text { - visible: AchievementService.loggedIn && !achievementSet.hasAchievements - anchors.centerIn: parent - text: "No achievements found for this game." - color: ColorPalette.neutral100 - font.family: Constants.mainFontFamily - font.pixelSize: 18 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - - Text { - visible: !AchievementService.loggedIn - anchors.centerIn: parent - text: "Not logged in to RetroAchievements. You can login from the Settings menu.\n\nAfter you log in, restart the game to load achievements." - color: ColorPalette.neutral100 - font.family: Constants.mainFontFamily - font.pixelSize: 18 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - - Column { - visible: AchievementService.loggedIn && achievementSet.hasAchievements - anchors.right: mainArea.left - anchors.rightMargin: 16 - anchors.topMargin: 24 + Pane { + id: setListContainer anchors.top: parent.top - - RadioIconButton { - icon.source: "qrc:/icons/sort" - icon.width: 60 - icon.height: 60 - KeyNavigation.right: mainArea - model: [ - { label: "Default order", value: "default" }, - { label: "A-Z", value: "a-z" }, - { label: "Z-A", value: "z-a" }, - { label: "Points (most first)", value: "points_most" }, - { label: "Points (least first)", value: "points_least" }, - { label: "Earned date", value: "earned_date" }, - { label: "Type", value: "type" } - ] - - onSelectionChanged: function(index, value, text) { - achievementSet.achievements.sortMethod = value + anchors.bottom: parent.bottom + anchors.left: parent.left + width: (parent.width - 700) > 280 ? 280 : 108 + // width: Math.max(80, Math.min(300, parent.width - 1000)) + // Behavior on width { + // NumberAnimation { + // duration: 50 + // easing.type: Easing.InOutQuad + // } + // } + background: Item {} + contentItem: ListView { + id: actualList + Layout.fillWidth: true + Layout.fillHeight: true + model: rcheevosGame.achievementSets + spacing: 4 + delegate: Button { + id: control + width: setListContainer.width > 108 ? actualList.width : implicitWidth + TapHandler { + onTapped: { + actualList.currentIndex = index + } + } + padding: 8 + background: Rectangle { + color: "white" + opacity: control.hovered ? 0.20 : actualList.currentIndex === index ? 0.15 : 0 + radius: 6 + } + contentItem: RowLayout { + spacing: 12 + Image { + id: gameIcon + source: model.iconUrl + sourceSize.width: 64 + fillMode: Image.PreserveAspectFit + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + smooth: true + } + + ColumnLayout { + Layout.fillWidth: true + Layout.fillHeight: true + visible: setListContainer.width > 108 + + RowLayout { + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + Layout.fillWidth: true + spacing: 16 + + Text { + text: model.name + color: ColorPalette.neutral100 + font.family: Constants.mainFontFamily + font.pixelSize: 16 + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + } + + + + RowLayout { + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + Layout.fillWidth: true + + Text { + text: (AchievementService.inHardcoreSession ? model.numEarnedHardcore : model.numEarned) + "/" + model.numAchievements + " earned" + color: ColorPalette.neutral400 + font.family: Constants.mainFontFamily + font.pixelSize: 14 + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + + Rectangle { + color: "#1e1e1e" + implicitHeight: 14 + Layout.fillWidth: true + radius: 4 + + Rectangle { + color: "#FFD700" + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + width: parent.width * ((AchievementService.inHardcoreSession ? model.numEarnedHardcore : model.numEarned) / model.numAchievements) + radius: 4 + } + } + } + } } - - } + // delegate: Row { + // TapHandler { + // onTapped: { + // actualList.currentIndex = index + // } + // } + // Image { + // source: model.iconUrl + // sourceSize.width: 60 + // sourceSize.height: 60 + // fillMode: Image.PreserveAspectFit + // smooth: true + // anchors.verticalCenter: parent.verticalCenter + // } + // Text { + // text: model.name + // color: "white" + // font.family: Constants.mainFontFamily + // font.pixelSize: 16 + // horizontalAlignment: Text.AlignLeft + // verticalAlignment: Text.AlignVCenter + // anchors.verticalCenter: parent.verticalCenter + // padding: 8 + // } + // } + } } - - Pane { - id: mainArea - visible: AchievementService.loggedIn && achievementSet.hasAchievements anchors.top: parent.top - width: Math.min(parent.width, 1000) - anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom + anchors.left: setListContainer.right + anchors.right: parent.right clip: true - focus: true - leftPadding: 16 - rightPadding: 16 - topPadding: 16 - bottomPadding: 32 - background: Item{} - contentItem: ListView { - id: achievementList - model: achievementSet.achievements - highlightMoveDuration: 80 - highlightMoveVelocity: -1 - highlightRangeMode: InputMethodManager.usingMouse ? ListView.NoHighlightRange : ListView.ApplyRange - preferredHighlightBegin: 200 - preferredHighlightEnd: height - 200 - boundsBehavior: Flickable.StopAtBounds - contentY: 0 - focus: true - cacheBuffer: 1000 - - ScrollBar.vertical: ScrollBar { } - - header: Pane { - width: Math.min(ListView.view.width, 1000) - anchors.horizontalCenter: parent.horizontalCenter - background: Item {} - leftPadding: 0 - rightPadding: 0 - topPadding: 8 - bottomPadding: 24 - contentItem: ColumnLayout { - spacing: 16 - // Pane { - // Layout.fillWidth: true - // background: Rectangle { - // color: "transparent" - // border.color: "#FFD700" - // radius: 6 - // } - // contentItem: Text { - // text: "Hardcore mode is active" - // color: "white" - // font.family: Constants.mainFontFamily - // font.pixelSize: 16 - // horizontalAlignment: Text.AlignHCenter - // verticalAlignment: Text.AlignVCenter - // } - // } - RowLayout { - Layout.fillWidth: true - spacing: 24 - Image { - id: gameIcon - source: achievementSet.iconUrl - sourceSize.width: 80 - fillMode: Image.PreserveAspectFit - Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft - smooth: true - } - - ColumnLayout { - Layout.fillWidth: true - Layout.fillHeight: true - - RowLayout { - Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft - Layout.fillWidth: true - spacing: 16 - - Text { - text: achievementSet.name - color: ColorPalette.neutral100 - font.family: Constants.mainFontFamily - font.pixelSize: 18 - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - } - - Pane { - visible: AchievementService.inHardcoreSession - verticalPadding: 4 - background: Rectangle { - color: "transparent" - border.color: "#FFD700" - radius: 6 - } - contentItem: Text { - text: "Hardcore mode active" - color: "white" - font.family: Constants.mainFontFamily - font.pixelSize: 16 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - } - - Item { - Layout.fillWidth: true - Layout.fillHeight: true - } - } - - - - RowLayout { - Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft - Layout.fillWidth: true - - Text { - text: achievementSet.platformName - color: ColorPalette.neutral300 - font.family: Constants.mainFontFamily - font.pixelSize: 17 - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - } - - Item { - Layout.fillWidth: true - Layout.fillHeight: true - } - - Text { - text: (AchievementService.inHardcoreSession ? achievementSet.numEarnedHardcore : achievementSet.numEarned) + "/" + achievementSet.numAchievements + " earned" - color: ColorPalette.neutral300 - font.family: Constants.mainFontFamily - font.pixelSize: 16 - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - } - } - Item { - Layout.fillWidth: true - Layout.fillHeight: true - } + background: Item {} + contentItem: ColumnLayout { + spacing: 16 + // Text { + // text: "Achievements" + // color: ColorPalette.neutral200 + // font.family: Constants.mainFontFamily + // font.pixelSize: 16 + // Layout.alignment: Qt.AlignLeft | Qt.AlignTop + // } + ListView { + id: achievementList + Layout.fillWidth: true + Layout.fillHeight: true + model: rcheevosGame.achievementSets[actualList.currentIndex].achievements + highlightMoveDuration: 80 + highlightMoveVelocity: -1 + highlightRangeMode: InputMethodManager.usingMouse ? ListView.NoHighlightRange : ListView.ApplyRange + preferredHighlightBegin: 200 + preferredHighlightEnd: height - 200 + boundsBehavior: Flickable.StopAtBounds + contentY: 0 + focus: true + cacheBuffer: 1000 + + ScrollBar.vertical: ScrollBar { + // parent: achievementList.parent + anchors.top: achievementList.top + anchors.left: achievementList.right + anchors.leftMargin: 4 + anchors.bottom: achievementList.bottom + } - Rectangle { - color: "#1e1e1e" - implicitHeight: 14 - Layout.fillWidth: true - Layout.alignment: Qt.AlignBottom | Qt.AlignLeft - Layout.bottomMargin: 4 - radius: 4 - - Rectangle { - color: "#FFD700" - anchors.left: parent.left - anchors.top: parent.top - anchors.bottom: parent.bottom - width: parent.width * ((AchievementService.inHardcoreSession ? achievementSet.numEarnedHardcore : achievementSet.numEarned) / achievementSet.numAchievements) - radius: 4 + header: Pane { + background: Item {} + width: ListView.view.width + padding: 0 + bottomPadding: 16 + height: 64 + contentItem: RowLayout { + RadioIconButton { + Layout.fillHeight: true + Layout.preferredWidth: height + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + icon.source: "qrc:/icons/sort" + // icon.height: 42 + // icon.width: 42 + // KeyNavigation.right: mainArea + model: [ + { label: "Default order", value: "default" }, + { label: "A-Z", value: "a-z" }, + { label: "Z-A", value: "z-a" }, + { label: "Points (most first)", value: "points_most" }, + { label: "Points (least first)", value: "points_least" }, + { label: "Earned date", value: "earned_date" }, + { label: "Type", value: "type" } + ] + + onSelectionChanged: function(index, value, text) { + rcheevosGame.achievementSets[actualList.currentIndex].achievements.sortMethod = value } - } - } - } - } - } - - spacing: 12 - delegate: AchievementListButton { - required property var model - required property var index + } + } + } - name: model.name - description: model.description - type: model.type - points: model.points - iconUrl: model.iconUrl - earned: model.earned - achievementId: model.achievementId - } + // header: Pane { + // width: Math.min(ListView.view.width, 1000) + // anchors.horizontalCenter: parent.horizontalCenter + // background: Item {} + // leftPadding: 0 + // rightPadding: 0 + // topPadding: 8 + // bottomPadding: 24 + // contentItem: ColumnLayout { + // spacing: 16 + // // Pane { + // // Layout.fillWidth: true + // // background: Rectangle { + // // color: "transparent" + // // border.color: "#FFD700" + // // radius: 6 + // // } + // // contentItem: Text { + // // text: "Hardcore mode is active" + // // color: "white" + // // font.family: Constants.mainFontFamily + // // font.pixelSize: 16 + // // horizontalAlignment: Text.AlignHCenter + // // verticalAlignment: Text.AlignVCenter + // // } + // // } + // RowLayout { + // Layout.fillWidth: true + // spacing: 24 + // Image { + // id: gameIcon + // source: rcheevosGame.achievementSets[actualList.currentIndex].iconUrl + // sourceSize.width: 80 + // fillMode: Image.PreserveAspectFit + // Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + // smooth: true + // } + // + // ColumnLayout { + // Layout.fillWidth: true + // Layout.fillHeight: true + // + // RowLayout { + // Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + // Layout.fillWidth: true + // spacing: 16 + // + // Text { + // text: rcheevosGame.achievementSets[actualList.currentIndex].name + // color: ColorPalette.neutral100 + // font.family: Constants.mainFontFamily + // font.pixelSize: 18 + // horizontalAlignment: Text.AlignLeft + // verticalAlignment: Text.AlignVCenter + // } + // + // Pane { + // visible: AchievementService.inHardcoreSession + // verticalPadding: 4 + // background: Rectangle { + // color: "transparent" + // border.color: "#FFD700" + // radius: 6 + // } + // contentItem: Text { + // text: "Hardcore mode active" + // color: "white" + // font.family: Constants.mainFontFamily + // font.pixelSize: 16 + // horizontalAlignment: Text.AlignHCenter + // verticalAlignment: Text.AlignVCenter + // } + // } + // + // Item { + // Layout.fillWidth: true + // Layout.fillHeight: true + // } + // } + // + // + // + // RowLayout { + // Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + // Layout.fillWidth: true + // + // Text { + // text: rcheevosGame.achievementSets[actualList.currentIndex].platformName + // color: ColorPalette.neutral300 + // font.family: Constants.mainFontFamily + // font.pixelSize: 17 + // horizontalAlignment: Text.AlignLeft + // verticalAlignment: Text.AlignVCenter + // } + // + // Item { + // Layout.fillWidth: true + // Layout.fillHeight: true + // } + // + // Text { + // text: (AchievementService.inHardcoreSession ? rcheevosGame.achievementSets[actualList.currentIndex].numEarnedHardcore : rcheevosGame.achievementSets[actualList.currentIndex].numEarned) + "/" + rcheevosGame.achievementSets[actualList.currentIndex].numAchievements + " earned" + // color: ColorPalette.neutral300 + // font.family: Constants.mainFontFamily + // font.pixelSize: 16 + // horizontalAlignment: Text.AlignLeft + // verticalAlignment: Text.AlignVCenter + // } + // } + // Item { + // Layout.fillWidth: true + // Layout.fillHeight: true + // } + // + // Rectangle { + // color: "#1e1e1e" + // implicitHeight: 14 + // Layout.fillWidth: true + // Layout.alignment: Qt.AlignBottom | Qt.AlignLeft + // Layout.bottomMargin: 4 + // radius: 4 + // + // Rectangle { + // color: "#FFD700" + // anchors.left: parent.left + // anchors.top: parent.top + // anchors.bottom: parent.bottom + // width: parent.width * ((AchievementService.inHardcoreSession ? rcheevosGame.achievementSets[actualList.currentIndex].numEarnedHardcore : rcheevosGame.achievementSets[actualList.currentIndex].numEarned) / rcheevosGame.achievementSets[actualList.currentIndex].numAchievements) + // radius: 4 + // } + // } + // } + // } + // } + // } + + spacing: 12 + + delegate: AchievementListButton { + required property var model + required property var index + + name: model.name + description: model.description + type: model.type + points: model.points + iconUrl: model.iconUrl + earned: model.earned + achievementId: model.achievementId + } - // delegate: Text { - // required property var model - // required property var index - // text: model.name - // color: "white" - // font.family: Constants.mainFontFamily - // font.pixelSize: 16 - // horizontalAlignment: Text.AlignLeft - // verticalAlignment: Text.AlignVCenter - // padding: 8 - // } - } + // delegate: Text { + // required property var model + // required property var index + // text: model.name + // color: "white" + // font.family: Constants.mainFontFamily + // font.pixelSize: 16 + // horizontalAlignment: Text.AlignLeft + // verticalAlignment: Text.AlignVCenter + // padding: 8 + // } + } + } } + + // Pane { + // id: setList + // anchors.left: parent.left + // anchors.top: parent.top + // anchors.bottom: parent.bottom + // width: 400 + // background: Item {} + // contentItem: ListView { + // id: actualList + // model: rcheevosGame.achievementSets + // spacing: 16 + // delegate: Pane { + // TapHandler { + // onTapped: { + // actualList.currentIndex = index + // } + // } + // padding: 0 + // background: Item {} + // contentItem: ColumnLayout { + // spacing: 16 + // // Pane { + // // Layout.fillWidth: true + // // background: Rectangle { + // // color: "transparent" + // // border.color: "#FFD700" + // // radius: 6 + // // } + // // contentItem: Text { + // // text: "Hardcore mode is active" + // // color: "white" + // // font.family: Constants.mainFontFamily + // // font.pixelSize: 16 + // // horizontalAlignment: Text.AlignHCenter + // // verticalAlignment: Text.AlignVCenter + // // } + // // } + // RowLayout { + // Layout.fillWidth: true + // spacing: 12 + // Image { + // id: gameIcon + // source: model.iconUrl + // sourceSize.width: 64 + // fillMode: Image.PreserveAspectFit + // Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + // smooth: true + // } + // + // ColumnLayout { + // Layout.fillWidth: true + // Layout.fillHeight: true + // + // RowLayout { + // Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + // Layout.fillWidth: true + // spacing: 16 + // + // Text { + // text: model.name + // color: ColorPalette.neutral100 + // font.family: Constants.mainFontFamily + // font.pixelSize: 16 + // horizontalAlignment: Text.AlignLeft + // verticalAlignment: Text.AlignVCenter + // } + // + // Item { + // Layout.fillWidth: true + // Layout.fillHeight: true + // } + // } + // + // + // + // RowLayout { + // Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + // Layout.fillWidth: true + // + // Text { + // text: (AchievementService.inHardcoreSession ? model.numEarnedHardcore : model.numEarned) + "/" + model.numAchievements + " earned" + // color: ColorPalette.neutral400 + // font.family: Constants.mainFontFamily + // font.pixelSize: 14 + // horizontalAlignment: Text.AlignLeft + // verticalAlignment: Text.AlignVCenter + // } + // } + // + // Item { + // Layout.fillWidth: true + // Layout.fillHeight: true + // } + // + // Rectangle { + // color: "#1e1e1e" + // implicitHeight: 14 + // Layout.fillWidth: true + // radius: 4 + // + // Rectangle { + // color: "#FFD700" + // anchors.left: parent.left + // anchors.top: parent.top + // anchors.bottom: parent.bottom + // width: parent.width * ((AchievementService.inHardcoreSession ? model.numEarnedHardcore : model.numEarned) / model.numAchievements) + // radius: 4 + // } + // } + // } + // } + // } + // } + // // delegate: Row { + // // TapHandler { + // // onTapped: { + // // actualList.currentIndex = index + // // } + // // } + // // Image { + // // source: model.iconUrl + // // sourceSize.width: 60 + // // sourceSize.height: 60 + // // fillMode: Image.PreserveAspectFit + // // smooth: true + // // anchors.verticalCenter: parent.verticalCenter + // // } + // // Text { + // // text: model.name + // // color: "white" + // // font.family: Constants.mainFontFamily + // // font.pixelSize: 16 + // // horizontalAlignment: Text.AlignLeft + // // verticalAlignment: Text.AlignVCenter + // // anchors.verticalCenter: parent.verticalCenter + // // padding: 8 + // // } + // // } + // } + // } + // + // + // + // // Text { + // // visible: AchievementService.loggedIn && !rcheevosGame.achievementSets[0].hasAchievements + // // anchors.centerIn: parent + // // text: "No achievements found for this game." + // // color: ColorPalette.neutral100 + // // font.family: Constants.mainFontFamily + // // font.pixelSize: 18 + // // horizontalAlignment: Text.AlignHCenter + // // verticalAlignment: Text.AlignVCenter + // // } + // // + // // Text { + // // visible: !AchievementService.loggedIn + // // anchors.centerIn: parent + // // text: "Not logged in to RetroAchievements. You can login from the Settings menu.\n\nAfter you log in, restart the game to load achievements." + // // color: ColorPalette.neutral100 + // // font.family: Constants.mainFontFamily + // // font.pixelSize: 18 + // // horizontalAlignment: Text.AlignHCenter + // // verticalAlignment: Text.AlignVCenter + // // } + // // + // // Column { + // // visible: AchievementService.loggedIn && rcheevosGame.achievementSets[0].hasAchievements + // // anchors.right: mainArea.left + // // anchors.rightMargin: 16 + // // anchors.topMargin: 24 + // // anchors.top: parent.top + // // + // // RadioIconButton { + // // icon.source: "qrc:/icons/sort" + // // icon.width: 60 + // // icon.height: 60 + // // KeyNavigation.right: mainArea + // // model: [ + // // { label: "Default order", value: "default" }, + // // { label: "A-Z", value: "a-z" }, + // // { label: "Z-A", value: "z-a" }, + // // { label: "Points (most first)", value: "points_most" }, + // // { label: "Points (least first)", value: "points_least" }, + // // { label: "Earned date", value: "earned_date" }, + // // { label: "Type", value: "type" } + // // ] + // // + // // onSelectionChanged: function(index, value, text) { + // // rcheevosGame.achievementSets[0].achievements.sortMethod = value + // // } + // // + // // } + // // } + // // + // // + // Pane { + // id: mainArea + // visible: AchievementService.loggedIn && rcheevosGame.achievementSets[actualList.currentIndex].hasAchievements + // anchors.top: parent.top + // anchors.left: setList.right + // anchors.right: parent.right + // // anchors.bottom: parent.bottom + // // width: Math.min(parent.width, 1000) + // // anchors.horizontalCenter: parent.horizontalCenter + // anchors.bottom: parent.bottom + // clip: true + // focus: true + // leftPadding: 16 + // rightPadding: 16 + // topPadding: 16 + // bottomPadding: 32 + // background: Item{} + // contentItem: ListView { + // id: achievementList + // model: rcheevosGame.achievementSets[actualList.currentIndex].achievements + // highlightMoveDuration: 80 + // highlightMoveVelocity: -1 + // highlightRangeMode: InputMethodManager.usingMouse ? ListView.NoHighlightRange : ListView.ApplyRange + // preferredHighlightBegin: 200 + // preferredHighlightEnd: height - 200 + // boundsBehavior: Flickable.StopAtBounds + // contentY: 0 + // focus: true + // cacheBuffer: 1000 + // + // ScrollBar.vertical: ScrollBar { } + // + // // header: Pane { + // // width: Math.min(ListView.view.width, 1000) + // // anchors.horizontalCenter: parent.horizontalCenter + // // background: Item {} + // // leftPadding: 0 + // // rightPadding: 0 + // // topPadding: 8 + // // bottomPadding: 24 + // // contentItem: ColumnLayout { + // // spacing: 16 + // // // Pane { + // // // Layout.fillWidth: true + // // // background: Rectangle { + // // // color: "transparent" + // // // border.color: "#FFD700" + // // // radius: 6 + // // // } + // // // contentItem: Text { + // // // text: "Hardcore mode is active" + // // // color: "white" + // // // font.family: Constants.mainFontFamily + // // // font.pixelSize: 16 + // // // horizontalAlignment: Text.AlignHCenter + // // // verticalAlignment: Text.AlignVCenter + // // // } + // // // } + // // RowLayout { + // // Layout.fillWidth: true + // // spacing: 24 + // // Image { + // // id: gameIcon + // // source: rcheevosGame.achievementSets[actualList.currentIndex].iconUrl + // // sourceSize.width: 80 + // // fillMode: Image.PreserveAspectFit + // // Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + // // smooth: true + // // } + // // + // // ColumnLayout { + // // Layout.fillWidth: true + // // Layout.fillHeight: true + // // + // // RowLayout { + // // Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + // // Layout.fillWidth: true + // // spacing: 16 + // // + // // Text { + // // text: rcheevosGame.achievementSets[actualList.currentIndex].name + // // color: ColorPalette.neutral100 + // // font.family: Constants.mainFontFamily + // // font.pixelSize: 18 + // // horizontalAlignment: Text.AlignLeft + // // verticalAlignment: Text.AlignVCenter + // // } + // // + // // Pane { + // // visible: AchievementService.inHardcoreSession + // // verticalPadding: 4 + // // background: Rectangle { + // // color: "transparent" + // // border.color: "#FFD700" + // // radius: 6 + // // } + // // contentItem: Text { + // // text: "Hardcore mode active" + // // color: "white" + // // font.family: Constants.mainFontFamily + // // font.pixelSize: 16 + // // horizontalAlignment: Text.AlignHCenter + // // verticalAlignment: Text.AlignVCenter + // // } + // // } + // // + // // Item { + // // Layout.fillWidth: true + // // Layout.fillHeight: true + // // } + // // } + // // + // // + // // + // // RowLayout { + // // Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + // // Layout.fillWidth: true + // // + // // Text { + // // text: rcheevosGame.achievementSets[actualList.currentIndex].platformName + // // color: ColorPalette.neutral300 + // // font.family: Constants.mainFontFamily + // // font.pixelSize: 17 + // // horizontalAlignment: Text.AlignLeft + // // verticalAlignment: Text.AlignVCenter + // // } + // // + // // Item { + // // Layout.fillWidth: true + // // Layout.fillHeight: true + // // } + // // + // // Text { + // // text: (AchievementService.inHardcoreSession ? rcheevosGame.achievementSets[actualList.currentIndex].numEarnedHardcore : rcheevosGame.achievementSets[actualList.currentIndex].numEarned) + "/" + rcheevosGame.achievementSets[actualList.currentIndex].numAchievements + " earned" + // // color: ColorPalette.neutral300 + // // font.family: Constants.mainFontFamily + // // font.pixelSize: 16 + // // horizontalAlignment: Text.AlignLeft + // // verticalAlignment: Text.AlignVCenter + // // } + // // } + // // Item { + // // Layout.fillWidth: true + // // Layout.fillHeight: true + // // } + // // + // // Rectangle { + // // color: "#1e1e1e" + // // implicitHeight: 14 + // // Layout.fillWidth: true + // // Layout.alignment: Qt.AlignBottom | Qt.AlignLeft + // // Layout.bottomMargin: 4 + // // radius: 4 + // // + // // Rectangle { + // // color: "#FFD700" + // // anchors.left: parent.left + // // anchors.top: parent.top + // // anchors.bottom: parent.bottom + // // width: parent.width * ((AchievementService.inHardcoreSession ? rcheevosGame.achievementSets[actualList.currentIndex].numEarnedHardcore : rcheevosGame.achievementSets[actualList.currentIndex].numEarned) / rcheevosGame.achievementSets[actualList.currentIndex].numAchievements) + // // radius: 4 + // // } + // // } + // // } + // // } + // // } + // // } + // + // spacing: 12 + // + // delegate: AchievementListButton { + // required property var model + // required property var index + // + // name: model.name + // description: model.description + // type: model.type + // points: model.points + // iconUrl: model.iconUrl + // earned: model.earned + // achievementId: model.achievementId + // } + // + // // delegate: Text { + // // required property var model + // // required property var index + // // text: model.name + // // color: "white" + // // font.family: Constants.mainFontFamily + // // font.pixelSize: 16 + // // horizontalAlignment: Text.AlignLeft + // // verticalAlignment: Text.AlignVCenter + // // padding: 8 + // // } + // } + // } } } diff --git a/qml/components/achievements/AchievementListButton.qml b/qml/components/achievements/AchievementListButton.qml index a922dcee..648d36cb 100644 --- a/qml/components/achievements/AchievementListButton.qml +++ b/qml/components/achievements/AchievementListButton.qml @@ -18,6 +18,7 @@ Button { width: ListView.view.width property bool showGlobalCursor: true focus: true + padding: 8 hoverEnabled: true diff --git a/src/app/achievements/achievement_repository.hpp b/src/app/achievements/achievement_repository.hpp index 079c1658..ee88e5dd 100644 --- a/src/app/achievements/achievement_repository.hpp +++ b/src/app/achievements/achievement_repository.hpp @@ -256,7 +256,7 @@ class IAchievementRepository { * @return The unlock data if found, std::nullopt otherwise */ [[nodiscard]] virtual std::optional - getUserUnlock(const std::string &username, unsigned achievementId) const = 0; + getUserUnlock(const std::string &username, unsigned achievementId) = 0; /** * @brief Creates or updates user unlock status for an achievement diff --git a/src/app/achievements/achievement_service.cpp b/src/app/achievements/achievement_service.cpp index 0af922bd..99ce28d0 100644 --- a/src/app/achievements/achievement_service.cpp +++ b/src/app/achievements/achievement_service.cpp @@ -59,8 +59,17 @@ AchievementService::getGameId(const std::string &contentHash) const { return m_repository.getGameId(contentHash); } -bool AchievementService::updateAchievementProgress( - const AchievementProgress &progress) { +std::optional +AchievementService::getGameForHash(const std::string &contentHash) const { + const auto id = m_repository.getGameId(contentHash); + if (!id.has_value()) { + return std::nullopt; + } + + return m_repository.getGameById(*id); +} + +bool AchievementService::create(const AchievementProgress &progress) { return m_repository.create(progress); } @@ -69,7 +78,7 @@ AchievementService::getUserUnlock(const std::string &username, const unsigned achievementId) const { return m_repository.getUserUnlock(username, achievementId); } -bool AchievementService::createOrUpdate(const UserUnlock &unlock) { +bool AchievementService::create(const UserUnlock &unlock) { if (m_currentSessionHardcore) { m_currentSessionHardcoreUnlocks.emplace_back(unlock.achievementId); } @@ -104,8 +113,8 @@ bool AchievementService::processStartSessionResponse( .unlockTimestampHardcore = 0, .synced = true}; if (!m_repository.createOrUpdate(newUnlock)) { - spdlog::error("Failed to createOrUpdate user unlock: {} for user {}", - a.ID, username); + spdlog::error("Failed to create user unlock: {} for user {}", a.ID, + username); return false; } } @@ -128,8 +137,8 @@ bool AchievementService::processStartSessionResponse( .unlockTimestampHardcore = a.When, .synced = true}; if (!m_repository.createOrUpdate(newUnlock)) { - spdlog::error("Failed to createOrUpdate user unlock: {} for user {}", - a.ID, username); + spdlog::error("Failed to create user unlock: {} for user {}", a.ID, + username); return false; } } @@ -193,7 +202,7 @@ bool AchievementService::processStartSessionResponse( .synced = true}; if (!m_repository.createOrUpdate(newUnlock)) { - spdlog::error("Failed to createOrUpdate unsupported achievement user " + spdlog::error("Failed to create unsupported achievement user " "unlock: {} for user {}", UNSUPPORTED_EMULATOR_ACHIEVEMENT_ID, username); } @@ -202,6 +211,11 @@ bool AchievementService::processStartSessionResponse( } void AchievementService::syncOfflineAchievements() { + // TODO: + // Need to probably add rate limiting + // Need to store the hash with the user unlocks to get accurate one when + // sending offline award request + const auto headers = cpr::Header{{"User-Agent", OFFLINE_USER_AGENT}, {"Content-Type", "application/x-www-form-urlencoded"}}; diff --git a/src/app/achievements/achievement_service.hpp b/src/app/achievements/achievement_service.hpp index 58089d45..5039f929 100644 --- a/src/app/achievements/achievement_service.hpp +++ b/src/app/achievements/achievement_service.hpp @@ -67,6 +67,8 @@ class AchievementService { bool create(const AchievementSet &achievementSet); bool create(const Achievement &achievement); bool create(const Leaderboard &leaderboard); + bool create(const AchievementProgress &progress); + bool create(const UserUnlock &unlock); /** * @brief Retrieves the achievement set ID associated with a content hash @@ -82,13 +84,11 @@ class AchievementService { [[nodiscard]] std::optional getGameId(const std::string &contentHash) const; - bool updateAchievementProgress(const AchievementProgress &progress); + std::optional getGameForHash(const std::string &contentHash) const; [[nodiscard]] std::optional getUserUnlock(const std::string &username, unsigned achievementId) const; - bool createOrUpdate(const UserUnlock &unlock); - [[nodiscard]] std::vector getAllUserUnlocks(const std::string &username, unsigned gameId) const; diff --git a/src/app/achievements/gui/AchievementSetItem.cpp b/src/app/achievements/gui/AchievementSetItem.cpp index 7c000b9f..b55829c9 100644 --- a/src/app/achievements/gui/AchievementSetItem.cpp +++ b/src/app/achievements/gui/AchievementSetItem.cpp @@ -1,80 +1,56 @@ #include "AchievementSetItem.hpp" -#include -#include -#include -#include +#include "achievements/achievement_service.hpp" + #include namespace firelight::achievements { -QString AchievementSetItem::getContentHash() const { return m_contentHash; } - -void AchievementSetItem::setContentHash(const QString &contentHash) { - if (m_contentHash == contentHash) { - return; - } - - auto entry = getLibraryService()->getEntryWithContentHash(contentHash); - if (!entry) { - m_hasAchievements = false; - return; - } - - auto platform = getPlatformService()->getPlatform(entry->platformId); - if (!platform) { - m_hasAchievements = false; - return; - } - - m_platform = *platform; - m_platformName = QString::fromStdString(m_platform.name); - - const auto set = getAchievementService()->getAchievementSetByContentHash( - contentHash.toStdString()); - if (!set) { - m_hasAchievements = false; +AchievementSetItem::AchievementSetItem(const AchievementSet &set, + QObject *parent) + : QObject(parent) { + m_setId = set.id; + if (set.title.empty()) { + m_setName = "Core Achievements"; } else { - m_setId = set->id; - m_setName = QString::fromStdString(set->title); - m_iconUrl = QString::fromStdString(set->imageIconUrl); - m_numAchievements = set->numAchievements; - m_totalNumPoints = set->totalPoints; - m_hasAchievements = m_numAchievements > 0; - - m_numEarned = 0; - m_numEarnedHardcore = 0; - QVector items; - for (const auto &achieve : set->achievements) { - auto unlock = getAchievementService()->getUserUnlock( - getAchievementService()->getLoggedInUsername(), achieve.id); - if (!unlock) { - continue; - } + m_setName = QString::fromStdString(set.title); + } + m_iconUrl = QString::fromStdString(set.imageIconUrl); + m_numAchievements = set.numAchievements; + m_totalNumPoints = set.totalPoints; + m_hasAchievements = m_numAchievements > 0; + + m_numEarned = 0; + m_numEarnedHardcore = 0; + QVector items; + for (const auto &achieve : set.achievements) { + auto unlock = getAchievementService()->getUserUnlock( + getAchievementService()->getLoggedInUsername(), achieve.id); + if (!unlock) { + continue; + } - items.emplace_back(gui::AchievementListModel::Item{ - .achievement = achieve, .unlockState = *unlock}); + items.emplace_back(gui::AchievementListModel::Item{.achievement = achieve, + .unlockState = *unlock}); - if (unlock->earnedHardcore) { - m_numEarnedHardcore++; - m_numEarned++; - } else if (unlock->earned) { - m_numEarned++; - } + if (unlock->earnedHardcore) { + m_numEarnedHardcore++; + m_numEarned++; + } else if (unlock->earned) { + m_numEarned++; } - - m_achievementListModel = - std::make_unique(items, this); - m_achievementListModel->setHardcore(m_hardcore); - m_sortFilterModel = - std::make_unique(this); - m_sortFilterModel->setSourceModel(m_achievementListModel.get()); - m_sortFilterModel->setSortMethod("default"); - m_sortFilterModel->sort(0); } - m_contentHash = contentHash; - emit contentHashChanged(); + m_achievementListModel = + std::make_unique(items, this); + m_achievementListModel->setHardcore(m_hardcore); + m_sortFilterModel = + std::make_unique(this); + m_sortFilterModel->setSourceModel(m_achievementListModel.get()); + m_sortFilterModel->setSortMethod("default"); + m_sortFilterModel->sort(0); + + emit setIdChanged(); } gui::AchievementListSortFilterModel * diff --git a/src/app/achievements/gui/AchievementSetItem.hpp b/src/app/achievements/gui/AchievementSetItem.hpp index 306fc904..be043247 100644 --- a/src/app/achievements/gui/AchievementSetItem.hpp +++ b/src/app/achievements/gui/AchievementSetItem.hpp @@ -1,39 +1,31 @@ #pragma once #include "achievement_list_model.hpp" +#include "achievement_list_sort_filter_model.hpp" +#include "achievements/models/achievement_set.hpp" #include "service_accessor.hpp" #include -#include namespace firelight::achievements { class AchievementSetItem : public QObject, public ServiceAccessor { Q_OBJECT - Q_PROPERTY(int setId MEMBER m_setId NOTIFY contentHashChanged) - Q_PROPERTY(QString contentHash READ getContentHash WRITE setContentHash NOTIFY - contentHashChanged) + Q_PROPERTY(int setId MEMBER m_setId NOTIFY setIdChanged) Q_PROPERTY( bool hardcore READ isHardcore WRITE setHardcore NOTIFY hardcoreChanged) - Q_PROPERTY(QString iconUrl MEMBER m_iconUrl NOTIFY contentHashChanged) + Q_PROPERTY(QString iconUrl MEMBER m_iconUrl NOTIFY setIdChanged) + Q_PROPERTY(bool hasAchievements MEMBER m_hasAchievements NOTIFY setIdChanged) + Q_PROPERTY(QString name MEMBER m_setName NOTIFY setIdChanged) + Q_PROPERTY(int numEarned MEMBER m_numEarned NOTIFY setIdChanged) Q_PROPERTY( - bool hasAchievements MEMBER m_hasAchievements NOTIFY contentHashChanged) - Q_PROPERTY(QString name MEMBER m_setName NOTIFY contentHashChanged) - Q_PROPERTY(int numEarned MEMBER m_numEarned NOTIFY contentHashChanged) - Q_PROPERTY(int numEarnedHardcore MEMBER m_numEarnedHardcore NOTIFY - contentHashChanged) - Q_PROPERTY( - int numAchievements MEMBER m_numAchievements NOTIFY contentHashChanged) - Q_PROPERTY( - int totalNumPoints MEMBER m_totalNumPoints NOTIFY contentHashChanged) - Q_PROPERTY( - QString platformName MEMBER m_platformName NOTIFY contentHashChanged) + int numEarnedHardcore MEMBER m_numEarnedHardcore NOTIFY setIdChanged) + Q_PROPERTY(int numAchievements MEMBER m_numAchievements NOTIFY setIdChanged) + Q_PROPERTY(int totalNumPoints MEMBER m_totalNumPoints NOTIFY setIdChanged) Q_PROPERTY(QSortFilterProxyModel *achievements READ getAchievements NOTIFY - contentHashChanged) + setIdChanged) public: - explicit AchievementSetItem(QObject *parent = nullptr) : QObject(parent) {} - - [[nodiscard]] QString getContentHash() const; - void setContentHash(const QString &contentHash); + explicit AchievementSetItem(const AchievementSet &set, + QObject *parent = nullptr); bool isHardcore() const { return m_hardcore; } void setHardcore(const bool hardcore) { @@ -52,22 +44,18 @@ class AchievementSetItem : public QObject, public ServiceAccessor { [[nodiscard]] gui::AchievementListSortFilterModel *getAchievements() const; signals: - void contentHashChanged(); + void setIdChanged(); void hardcoreChanged(); private: bool m_hasAchievements = false; unsigned m_setId = 0; - QString m_contentHash; QString m_setName = "lol"; QString m_iconUrl; unsigned m_numEarned = 0; unsigned m_numEarnedHardcore = 0; unsigned m_numAchievements = 0; unsigned m_totalNumPoints = 0; - QString m_platformName; - - platforms::Platform m_platform; bool m_hardcore = false; diff --git a/src/app/achievements/gui/retro_achievements_game_item.cpp b/src/app/achievements/gui/retro_achievements_game_item.cpp new file mode 100644 index 00000000..54694e90 --- /dev/null +++ b/src/app/achievements/gui/retro_achievements_game_item.cpp @@ -0,0 +1,58 @@ +#include "retro_achievements_game_item.hpp" + +#include "achievements/achievement_service.hpp" + +#include +#include + +namespace firelight::achievements { +RetroAchievementsGameItem::RetroAchievementsGameItem(QObject *parent) {} + +QString RetroAchievementsGameItem::getContentHash() const { + return m_contentHash; +} + +void RetroAchievementsGameItem::setContentHash(const QString &contentHash) { + if (m_contentHash == contentHash) { + return; + } + + const auto entry = getLibraryService()->getEntryWithContentHash(contentHash); + if (!entry) { + m_achievementSets.clear(); + return; + } + + const auto platform = getPlatformService()->getPlatform(entry->platformId); + if (!platform) { + m_achievementSets.clear(); + return; + } + + m_platform = *platform; + m_platformName = QString::fromStdString(m_platform.name); + + auto game = + getAchievementService()->getGameForHash(contentHash.toStdString()); + if (!game) { + m_achievementSets.clear(); + return; + } + + m_title = QString::fromStdString(game->title); + m_iconUrl = QString::fromStdString(game->imageIconUrl); + + m_achievementSets.clear(); + for (const auto &set : game->achievementSets) { + m_achievementSets.emplace_back(new AchievementSetItem(set, this)); + } + + m_contentHash = contentHash; + emit contentHashChanged(); +} + +QList +RetroAchievementsGameItem::getAchievementSets() const { + return m_achievementSets; +} +} // namespace firelight::achievements \ No newline at end of file diff --git a/src/app/achievements/gui/retro_achievements_game_item.hpp b/src/app/achievements/gui/retro_achievements_game_item.hpp new file mode 100644 index 00000000..71a47d1a --- /dev/null +++ b/src/app/achievements/gui/retro_achievements_game_item.hpp @@ -0,0 +1,60 @@ +#pragma once +#include "AchievementSetItem.hpp" + +#include +#include + +namespace firelight::achievements { + +class RetroAchievementsGameItem : public QObject, public ServiceAccessor { + Q_OBJECT + Q_PROPERTY(QString contentHash READ getContentHash WRITE setContentHash NOTIFY + contentHashChanged) + Q_PROPERTY(QString title MEMBER m_title NOTIFY contentHashChanged) + Q_PROPERTY(QString iconUrl MEMBER m_iconUrl NOTIFY contentHashChanged) + Q_PROPERTY( + bool hardcore READ isHardcore WRITE setHardcore NOTIFY hardcoreChanged) + Q_PROPERTY( + QString platformName MEMBER m_platformName NOTIFY contentHashChanged) + + Q_PROPERTY(QList achievementSets READ getAchievementSets + NOTIFY contentHashChanged) + +public: + explicit RetroAchievementsGameItem(QObject *parent = nullptr); + [[nodiscard]] QString getContentHash() const; + void setContentHash(const QString &contentHash); + + [[nodiscard]] bool isHardcore() const { return m_hardcore; } + void setHardcore(const bool hardcore) { + if (m_hardcore == hardcore) { + return; + } + m_hardcore = hardcore; + + for (const auto &setItem : m_achievementSets) { + setItem->setHardcore(m_hardcore); + } + + emit hardcoreChanged(); + } + + [[nodiscard]] QList getAchievementSets() const; + +signals: + void contentHashChanged(); + void hardcoreChanged(); + +private: + QString m_contentHash; + QString m_title; + QString m_iconUrl; + QString m_platformName; + bool m_hardcore = false; + + platforms::Platform m_platform; + + QList m_achievementSets; +}; + +} // namespace firelight::achievements diff --git a/src/app/achievements/sqlite_achievement_repository.cpp b/src/app/achievements/sqlite_achievement_repository.cpp index 04ce68a9..050ec71f 100644 --- a/src/app/achievements/sqlite_achievement_repository.cpp +++ b/src/app/achievements/sqlite_achievement_repository.cpp @@ -309,8 +309,7 @@ std::vector SqliteAchievementRepository::getAchievementSetsByGameId(unsigned gameId) const { try { SQLite::Statement query(*m_database, - "SELECT id, title, type, game_id, image_icon_url, " - "num_achievements, num_points " + "SELECT id, title, type, game_id, image_icon_url " "FROM achievement_sets " "WHERE game_id = :gameId"); query.bind(":gameId", gameId); @@ -323,8 +322,6 @@ SqliteAchievementRepository::getAchievementSetsByGameId(unsigned gameId) const { achievementSet.type = query.getColumn(2).getString(); achievementSet.gameId = query.getColumn(3); achievementSet.imageIconUrl = query.getColumn(4).getString(); - achievementSet.numAchievements = query.getColumn(5); - achievementSet.totalPoints = query.getColumn(6); // Load achievements for this set SQLite::Statement achievementQuery( @@ -338,6 +335,8 @@ SqliteAchievementRepository::getAchievementSetsByGameId(unsigned gameId) const { "ORDER BY display_order"); achievementQuery.bind(":setId", achievementSet.id); + auto totalPoints = 0; + auto numAchievements = 0; while (achievementQuery.executeStep()) { Achievement achievement; achievement.id = achievementQuery.getColumn(0); @@ -358,9 +357,17 @@ SqliteAchievementRepository::getAchievementSetsByGameId(unsigned gameId) const { achievement.badgeLockedUrl = achievementQuery.getColumn(15).getString(); achievement.displayOrder = achievementQuery.getColumn(16); + if (achievement.flags != 3) { + continue; // Only include active achievements + } + + numAchievements++; + totalPoints += achievement.points; achievementSet.achievements.emplace_back(achievement); } + achievementSet.totalPoints = totalPoints; + achievementSet.numAchievements = numAchievements; sets.emplace_back(achievementSet); } @@ -608,11 +615,12 @@ SqliteAchievementRepository::getAchievementSetByContentHash( const std::string &contentHash) const { try { SQLite::Statement query( - *m_database, "SELECT s.id, s.title, s.type, s.game_id, " - "s.image_icon_url, s.num_achievements, s.num_points " - "FROM achievement_sets s " - "JOIN game_hashes h ON s.id = h.game_id " - "WHERE h.hash = :contentHash"); + *m_database, + "SELECT s.id, s.title, s.type, s.game_id, " + "s.image_icon_url, s.num_achievements, s.num_points " + "FROM achievement_sets s " + "JOIN achievement_set_hashes h ON s.id = h.achievement_set_id " + "WHERE h.hash = :contentHash"); query.bind(":contentHash", contentHash); if (query.executeStep()) { @@ -637,8 +645,11 @@ SqliteAchievementRepository::getAchievementSetByContentHash( "ORDER BY display_order"); achievementQuery.bind(":gameId", achievementSet.id); + unsigned numAchievements = 0; unsigned totalPoints = 0; while (achievementQuery.executeStep()) { + numAchievements++; + Achievement achievement; achievement.id = achievementQuery.getColumn(0); achievement.achievementSetId = achievementQuery.getColumn(1); @@ -664,6 +675,7 @@ SqliteAchievementRepository::getAchievementSetByContentHash( } } + achievementSet.numAchievements = numAchievements; achievementSet.totalPoints = totalPoints; return achievementSet; } @@ -756,7 +768,7 @@ bool SqliteAchievementRepository::create(const Leaderboard &leaderboard) { std::optional SqliteAchievementRepository::getUserUnlock(const std::string &username, - const unsigned achievementId) const { + const unsigned achievementId) { try { SQLite::Statement query( *m_database, @@ -781,7 +793,18 @@ SqliteAchievementRepository::getUserUnlock(const std::string &username, return unlock; } - return std::nullopt; + UserUnlock newUnlock{ + .username = username, + .achievementId = achievementId, + .earned = false, + .earnedHardcore = false, + .unlockTimestamp = 0, + .unlockTimestampHardcore = 0, + .synced = false, + }; + createOrUpdate(newUnlock); + + return {newUnlock}; } catch (const std::exception &e) { return std::nullopt; } @@ -844,7 +867,8 @@ SqliteAchievementRepository::getAllUserUnlocks(const std::string &username, "u.\"when\", u.when_hardcore, u.synced " "FROM user_unlocks u " "JOIN achievements a ON u.achievement_id = a.id " - "WHERE u.username = :username AND a.achievement_set_id = :gameId " + "JOIN achievement_sets s ON a.achievement_set_id = s.id " + "WHERE u.username = :username AND s.game_id = :gameId " "ORDER BY a.display_order"); query.bind(":username", username); diff --git a/src/app/achievements/sqlite_achievement_repository.hpp b/src/app/achievements/sqlite_achievement_repository.hpp index efc8af0f..1dc904fd 100644 --- a/src/app/achievements/sqlite_achievement_repository.hpp +++ b/src/app/achievements/sqlite_achievement_repository.hpp @@ -228,8 +228,7 @@ class SqliteAchievementRepository final : public IAchievementRepository { * @return The unlock data if found, std::nullopt if no unlock record exists */ [[nodiscard]] std::optional - getUserUnlock(const std::string &username, - unsigned achievementId) const override; + getUserUnlock(const std::string &username, unsigned achievementId) override; /** * @brief Creates or updates user unlock status for an achievement diff --git a/src/app/emulator_item_renderer.cpp b/src/app/emulator_item_renderer.cpp index b86ad9c7..30b304c4 100644 --- a/src/app/emulator_item_renderer.cpp +++ b/src/app/emulator_item_renderer.cpp @@ -96,7 +96,7 @@ void EmulatorItemRenderer::receive(const void *data, unsigned width, // // .object = (quint64)m_vulkanImage.create_info.image})) { // // spdlog::error("woah there"); // // } - // texture->createOrUpdate(); + // texture->create(); // // QRhiResourceUpdateBatch *batch = rhi()->nextResourceUpdateBatch(); // QImage image(m_coreMaxWidth, m_coreMaxHeight, QImage::Format_RGBA8888); // image.fill(Qt::green); @@ -220,7 +220,7 @@ void EmulatorItemRenderer::setHwRenderContextNegotiationInterface( PFN_vkGetInstanceProcAddr get_instance_proc_addr, retro_vulkan_create_device_wrapper_t create_device_wrapper, void *opaque) { - printf("Calling createOrUpdate device 2***************\n"); + printf("Calling create device 2***************\n"); // auto vulkanHandles = reinterpret_cast(rhi()->nativeHandles()); // return vulkanHandles-; @@ -233,7 +233,7 @@ void EmulatorItemRenderer::setHwRenderContextNegotiationInterface( retro_vulkan_create_instance_wrapper_t create_instance_wrapper, void *opaque) { spdlog::info("Calling create_instance"); - printf("Calling createOrUpdate instance***************\n"); + printf("Calling create instance***************\n"); auto vulkanHandles = reinterpret_cast( globalRenderer->rhi()->nativeHandles()); diff --git a/src/app/rcheevos/offline/rcheevos_offline_client.cpp b/src/app/rcheevos/offline/rcheevos_offline_client.cpp index 4b0da86e..c4e2283b 100644 --- a/src/app/rcheevos/offline/rcheevos_offline_client.cpp +++ b/src/app/rcheevos/offline/rcheevos_offline_client.cpp @@ -18,7 +18,6 @@ #include namespace firelight::achievements { -static const std::string GAMEID = "gameid"; static const std::string ACHIEVEMENT_SETS = "achievementsets"; static const std::string START_SESSION = "startsession"; static const std::string AWARD_ACHIEVEMENT = "awardachievement"; @@ -26,10 +25,10 @@ static const std::string LOGIN2 = "login2"; static const std::string PING = "ping"; static const std::string SUBMIT_LB_ENTRY = "submitlbentry"; -static const rc_api_server_response_t GENERIC_SERVER_ERROR = {"", 0, 500}; +static constexpr rc_api_server_response_t GENERIC_SERVER_ERROR = {"", 0, 500}; -static const rc_api_server_response_t GENERIC_SUCCESS = {"{\"Success\":true}", - 16, 200}; +static constexpr rc_api_server_response_t GENERIC_SUCCESS = { + "{\"Success\":true}", 16, 200}; std::unordered_map parseQueryParams(const std::string &query) { @@ -199,7 +198,7 @@ RetroAchievementsOfflineClient::handleAwardAchievementRequest( user->softcoreScore += achievement->points; } - m_achievementService.createOrUpdate(newUnlock); + m_achievementService.create(newUnlock); m_achievementService.create(*user); } else { if (hardcore && unlock->earnedHardcore) { @@ -243,7 +242,7 @@ RetroAchievementsOfflineClient::handleAwardAchievementRequest( unlock->synced = false; } - m_achievementService.createOrUpdate(*unlock); + m_achievementService.create(*unlock); m_achievementService.create(*user); } @@ -465,7 +464,7 @@ void RetroAchievementsOfflineClient::processAwardAchievementResponse( hardcore ? static_cast(epochSeconds) : 0, .synced = true}; - m_achievementService.createOrUpdate(newUnlock); + m_achievementService.create(newUnlock); return; } @@ -482,7 +481,7 @@ void RetroAchievementsOfflineClient::processAwardAchievementResponse( } unlock->synced = true; - m_achievementService.createOrUpdate(*unlock); + m_achievementService.create(*unlock); } } } diff --git a/src/app/rcheevos/ra_client.cpp b/src/app/rcheevos/ra_client.cpp index 345e7001..3704db0e 100644 --- a/src/app/rcheevos/ra_client.cpp +++ b/src/app/rcheevos/ra_client.cpp @@ -80,7 +80,7 @@ static void eventHandler(const rc_client_event_t *event, rc_client_t *client) { event->achievement->title, event->achievement->description, std::stoi(current), std::stoi(desired)); - raClient->m_service.updateAchievementProgress(AchievementProgress{ + raClient->m_service.create(AchievementProgress{ .username = raClient->getCurrentUser()->username, .achievementId = event->achievement->id, .numerator = static_cast(std::stoi(current)), diff --git a/src/main.cpp b/src/main.cpp index c88bb276..1c47a734 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,6 +17,7 @@ #include #include "achievements/achievement_service.hpp" +#include "achievements/gui/retro_achievements_game_item.hpp" #include "achievements/sqlite_achievement_repository.hpp" #include "activity/gui/game_activity_item.hpp" #include "app/audio/SfxPlayer.hpp" @@ -150,7 +151,7 @@ int main(int argc, char *argv[]) { QDir baseDir(defaultAppDataPathString); if (!baseDir.mkpath("core-system")) { - spdlog::warn("Unable to createOrUpdate core-system directory"); + spdlog::warn("Unable to create core-system directory"); } firelight::ManagerAccessor::setCoreSystemDirectory( @@ -289,8 +290,10 @@ int main(int argc, char *argv[]) { qmlRegisterType("Firelight", 1, 0, "GamepadProfile"); qmlRegisterType("Firelight", 1, 0, "ModInfo"); - qmlRegisterType( - "Firelight", 1, 0, "AchievementSet"); + + qmlRegisterType( + "Firelight", 1, 0, "RetroAchievementsGame"); + qmlRegisterType("Firelight", 1, 0, "LibraryEntry"); qmlRegisterType("Firelight", 1, 0, @@ -330,7 +333,7 @@ int main(int argc, char *argv[]) { } }); - // QQmlNetworkAccessManagerFactory::createOrUpdate(); + // QQmlNetworkAccessManagerFactory::create(); // QNetworkAccessManager *manager = new QNetworkAccessManager(); // QNetworkDiskCache *diskCache = new QNetworkDiskCache(); // QString directory = diff --git a/tests/app/achievements/achievement_service_test.cpp b/tests/app/achievements/achievement_service_test.cpp index 127592ba..cb69e6be 100644 --- a/tests/app/achievements/achievement_service_test.cpp +++ b/tests/app/achievements/achievement_service_test.cpp @@ -366,7 +366,7 @@ TEST_F(AchievementServiceTest, SetGameId_Success) { TEST_F(AchievementServiceTest, UpdateAchievementProgress_Success) { auto progress = createTestProgress(); - bool result = service->updateAchievementProgress(progress); + bool result = service->create(progress); EXPECT_TRUE(result); } @@ -509,8 +509,8 @@ TEST_F(AchievementServiceTest, // unlock2->earned = true; // unlock2->unlockTimestamp = 1609459200; // -// EXPECT_TRUE(repository->createOrUpdate(*unlock1)); -// EXPECT_TRUE(repository->createOrUpdate(*unlock2)); +// EXPECT_TRUE(repository->create(*unlock1)); +// EXPECT_TRUE(repository->create(*unlock2)); // // // Process start session with only achievement 1 unlocked // std::vector unlocks = {createTestUnlock(1, 1609459200)}; @@ -550,8 +550,8 @@ TEST_F(AchievementServiceTest, // unlock2->earnedHardcore = true; // unlock2->unlockTimestampHardcore = 1609459200; // -// EXPECT_TRUE(repository->createOrUpdate(*unlock1)); -// EXPECT_TRUE(repository->createOrUpdate(*unlock2)); +// EXPECT_TRUE(repository->create(*unlock1)); +// EXPECT_TRUE(repository->create(*unlock2)); // // // Process start session with only achievement 1 hardcore unlocked // std::vector hardcoreUnlocks = {createTestUnlock(1, 1609459200)}; diff --git a/tests/app/emulation/emulator_instance_test.cpp b/tests/app/emulation/emulator_instance_test.cpp index bd65fe48..db22968b 100644 --- a/tests/app/emulation/emulator_instance_test.cpp +++ b/tests/app/emulation/emulator_instance_test.cpp @@ -136,7 +136,7 @@ TEST_F(EmulatorInstanceTest, PlatformSettingChangeUpdatesInstance) { // .m_platformId = 3, // .m_fileSizeBytes = 16777216}; // -// m_library->createOrUpdate(info); +// m_library->create(info); // ASSERT_NE(info.m_id, -1); // // auto entry = From 9f5a9cdc473a0a94ad375cffcf4990f185fb8020 Mon Sep 17 00:00:00 2001 From: biscuitcakes Date: Sun, 22 Feb 2026 16:20:49 -0600 Subject: [PATCH 5/5] k --- qml/components/FLFocusHighlight.qml | 94 +++++++++++++----- qml/components/QuickMenu.qml | 99 +++++++++++-------- .../navigation/LeftNavigationBar.qml | 2 +- .../sqlite_achievement_repository.cpp | 2 +- .../offline/rcheevos_offline_client.cpp | 3 - .../offline/rcheevos_offline_client.hpp | 10 +- 6 files changed, 134 insertions(+), 76 deletions(-) diff --git a/qml/components/FLFocusHighlight.qml b/qml/components/FLFocusHighlight.qml index 871a22cc..ff392168 100644 --- a/qml/components/FLFocusHighlight.qml +++ b/qml/components/FLFocusHighlight.qml @@ -2,64 +2,108 @@ import QtQuick import QtQuick.Controls import QtMultimedia -Rectangle { - id: activeFocusHighlight +Item { + id: root property color borderColor: "#ff9f38" property real borderWidth: 2 property real bounceAmplitude: 16 property real defaultAnchorMargins: -4 + property int animationDuration: 85 z: 1000 - // property MediaPlayer bumpSfx: undefined required property Item target required property bool usingMouse - visible: !usingMouse && parent !== null + visible: !usingMouse + // Track which highlight is currently "active" + property bool useHighlightA: true - anchors.fill: parent - anchors.margins: -4 - border.color: borderColor - border.width: borderWidth - color: "transparent" - - radius: { - if (activeFocusHighlight.parent === null || activeFocusHighlight.parent === undefined) { - return Math.abs(anchors.margins) + function calculateRadius(item: Item): real { + if (item === null) { + return Math.abs(root.defaultAnchorMargins) } - if (activeFocusHighlight.parent.hasOwnProperty('background')) { - return parent.background.radius + (Math.abs(anchors.margins)) - } else if (activeFocusHighlight.parent.hasOwnProperty('radius')) { - return parent.radius + (Math.abs(anchors.margins)) + if (item.hasOwnProperty('background') && item.background && item.background.hasOwnProperty('radius')) { + return item.background.radius + Math.abs(root.defaultAnchorMargins) + } else if (item.hasOwnProperty('radius')) { + return item.radius + Math.abs(root.defaultAnchorMargins) } else { - return Math.abs(anchors.margins) + return Math.abs(root.defaultAnchorMargins) } } + function getMargins(item: Item): real { + if (item !== null && item.hasOwnProperty('globalCursorSpacing')) { + return -item.globalCursorSpacing + } + return root.defaultAnchorMargins + } + + // Two highlights that crossfade via re-parenting + Rectangle { + id: highlightA + opacity: 0 + visible: !root.usingMouse + anchors.fill: parent + anchors.margins: parent ? root.getMargins(parent) : root.defaultAnchorMargins + border.color: root.borderColor + border.width: root.borderWidth + color: "transparent" + radius: root.calculateRadius(parent) + z: 1000 + + Behavior on opacity { NumberAnimation { duration: root.animationDuration; easing.type: Easing.OutQuad } } + } + + Rectangle { + id: highlightB + opacity: 0 + visible: !root.usingMouse + anchors.fill: parent + anchors.margins: parent ? root.getMargins(parent) : root.defaultAnchorMargins + border.color: root.borderColor + border.width: root.borderWidth + color: "transparent" + radius: root.calculateRadius(parent) + z: 1000 + + Behavior on opacity { NumberAnimation { duration: root.animationDuration; easing.type: Easing.OutQuad } } + } + onTargetChanged: { if (target === null) { - activeFocusHighlight.parent = null + highlightA.opacity = 0 + highlightB.opacity = 0 return } if (target.hasOwnProperty('showGlobalCursor') && target.showGlobalCursor) { + var newParent if (target.hasOwnProperty("globalCursorProxy") && target.globalCursorProxy) { - activeFocusHighlight.parent = target.globalCursorProxy + newParent = target.globalCursorProxy } else { - activeFocusHighlight.parent = target + newParent = target } - if (activeFocusHighlight.parent.hasOwnProperty('globalCursorSpacing')) { - anchors.margins = -activeFocusHighlight.parent.globalCursorSpacing + // Crossfade: fade out old highlight (stays with old parent), fade in new one + if (useHighlightA) { + highlightA.parent = newParent + highlightA.opacity = 1 + highlightB.opacity = 0 } else { - anchors.margins = activeFocusHighlight.defaultAnchorMargins + highlightB.parent = newParent + highlightB.opacity = 1 + highlightA.opacity = 0 } + useHighlightA = !useHighlightA return } - activeFocusHighlight.parent = null + // Target doesn't have showGlobalCursor + highlightA.opacity = 0 + highlightB.opacity = 0 } } diff --git a/qml/components/QuickMenu.qml b/qml/components/QuickMenu.qml index cb2c17fe..b795cc5d 100644 --- a/qml/components/QuickMenu.qml +++ b/qml/components/QuickMenu.qml @@ -17,12 +17,6 @@ Pane { entryId: EmulationService.currentEntryId } - RetroAchievementsGame { - id: rcheevosGame - contentHash: EmulationService.currentContentHash - hardcore: AchievementService.inHardcoreSession - } - horizontalPadding: 16 verticalPadding: 0 background: Item {} @@ -48,6 +42,7 @@ Pane { background: Item {} Repeater { + focus: true model: ["Quick Menu", "Achievements", "Settings"] delegate: TabButton { HoverHandler { @@ -56,6 +51,7 @@ Pane { } anchors.verticalCenter: parent.verticalCenter padding: 10 + property bool showGlobalCursor: true contentItem: Text { text: modelData font.family: Constants.mainFontFamily @@ -206,6 +202,7 @@ Pane { anchors.top: parent.top anchors.bottom: parent.bottom anchors.left: parent.left + KeyNavigation.right: achievementListContainer width: (parent.width - 700) > 280 ? 280 : 108 // width: Math.max(80, Math.min(300, parent.width - 1000)) // Behavior on width { @@ -214,13 +211,26 @@ Pane { // easing.type: Easing.InOutQuad // } // } + RetroAchievementsGame { + id: rcheevosGame + contentHash: EmulationService.currentContentHash + hardcore: AchievementService.inHardcoreSession + } background: Item {} contentItem: ListView { id: actualList Layout.fillWidth: true Layout.fillHeight: true model: rcheevosGame.achievementSets - spacing: 4 + spacing: 6 + highlightMoveDuration: 80 + highlightMoveVelocity: -1 + highlightRangeMode: InputMethodManager.usingMouse ? ListView.NoHighlightRange : ListView.ApplyRange + preferredHighlightBegin: 200 + preferredHighlightEnd: height - 200 + boundsBehavior: Flickable.StopAtBounds + keyNavigationEnabled: true + focus: true delegate: Button { id: control width: setListContainer.width > 108 ? actualList.width : implicitWidth @@ -229,6 +239,7 @@ Pane { actualList.currentIndex = index } } + property bool showGlobalCursor: true padding: 8 background: Rectangle { color: "white" @@ -338,11 +349,14 @@ Pane { } } Pane { + id: achievementListContainer anchors.top: parent.top anchors.bottom: parent.bottom anchors.left: setListContainer.right - anchors.right: parent.right + anchors.right: sortBar.left + KeyNavigation.right: sortBar clip: true + focus: true background: Item {} contentItem: ColumnLayout { spacing: 16 @@ -364,6 +378,7 @@ Pane { preferredHighlightBegin: 200 preferredHighlightEnd: height - 200 boundsBehavior: Flickable.StopAtBounds + keyNavigationEnabled: true contentY: 0 focus: true cacheBuffer: 1000 @@ -376,39 +391,6 @@ Pane { anchors.bottom: achievementList.bottom } - header: Pane { - background: Item {} - width: ListView.view.width - padding: 0 - bottomPadding: 16 - height: 64 - contentItem: RowLayout { - RadioIconButton { - Layout.fillHeight: true - Layout.preferredWidth: height - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - icon.source: "qrc:/icons/sort" - // icon.height: 42 - // icon.width: 42 - // KeyNavigation.right: mainArea - model: [ - { label: "Default order", value: "default" }, - { label: "A-Z", value: "a-z" }, - { label: "Z-A", value: "z-a" }, - { label: "Points (most first)", value: "points_most" }, - { label: "Points (least first)", value: "points_least" }, - { label: "Earned date", value: "earned_date" }, - { label: "Type", value: "type" } - ] - - onSelectionChanged: function(index, value, text) { - rcheevosGame.achievementSets[actualList.currentIndex].achievements.sortMethod = value - } - - } - } - } - // header: Pane { // width: Math.min(ListView.view.width, 1000) // anchors.horizontalCenter: parent.horizontalCenter @@ -575,6 +557,41 @@ Pane { } } + Pane { + id: sortBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + width: 64 + background: Item {} + contentItem: ColumnLayout { + RadioIconButton { + Layout.preferredWidth: 42 + Layout.preferredHeight: 42 + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + focus: true + icon.source: "qrc:/icons/sort" + // icon.height: 42 + // icon.width: 42 + // KeyNavigation.right: mainArea + model: [ + { label: "Default order", value: "default" }, + { label: "A-Z", value: "a-z" }, + { label: "Z-A", value: "z-a" }, + { label: "Points (most first)", value: "points_most" }, + { label: "Points (least first)", value: "points_least" }, + { label: "Earned date", value: "earned_date" }, + { label: "Type", value: "type" } + ] + + onSelectionChanged: function(index, value, text) { + rcheevosGame.achievementSets[actualList.currentIndex].achievements.sortMethod = value + } + + } + } + } + // Pane { // id: setList // anchors.left: parent.left diff --git a/qml/components/navigation/LeftNavigationBar.qml b/qml/components/navigation/LeftNavigationBar.qml index 9e7220e7..a41b07bd 100644 --- a/qml/components/navigation/LeftNavigationBar.qml +++ b/qml/components/navigation/LeftNavigationBar.qml @@ -42,7 +42,7 @@ Pane { } background: Rectangle { - color: "#19181d" + color: "#18181c" } contentItem: ColumnLayout { diff --git a/src/app/achievements/sqlite_achievement_repository.cpp b/src/app/achievements/sqlite_achievement_repository.cpp index 050ec71f..3b460ad6 100644 --- a/src/app/achievements/sqlite_achievement_repository.cpp +++ b/src/app/achievements/sqlite_achievement_repository.cpp @@ -800,7 +800,7 @@ SqliteAchievementRepository::getUserUnlock(const std::string &username, .earnedHardcore = false, .unlockTimestamp = 0, .unlockTimestampHardcore = 0, - .synced = false, + .synced = true, }; createOrUpdate(newUnlock); diff --git a/src/app/rcheevos/offline/rcheevos_offline_client.cpp b/src/app/rcheevos/offline/rcheevos_offline_client.cpp index c4e2283b..c457ecc6 100644 --- a/src/app/rcheevos/offline/rcheevos_offline_client.cpp +++ b/src/app/rcheevos/offline/rcheevos_offline_client.cpp @@ -2,15 +2,12 @@ #include "../achievement_set_response.hpp" #include "../award_achievement_response.hpp" -#include "../gameid_response.hpp" #include "../login2_response.hpp" #include "../ra_constants.h" #include "../startsession_response.hpp" #include #include -#include -#include #include #include #include diff --git a/src/app/rcheevos/offline/rcheevos_offline_client.hpp b/src/app/rcheevos/offline/rcheevos_offline_client.hpp index cb36c5f5..918c3c05 100644 --- a/src/app/rcheevos/offline/rcheevos_offline_client.hpp +++ b/src/app/rcheevos/offline/rcheevos_offline_client.hpp @@ -24,12 +24,12 @@ class RetroAchievementsOfflineClient final { void startOnlineHardcoreSession(); private: - rc_api_server_response_t + [[nodiscard]] rc_api_server_response_t handleAchievementSetsRequest(const std::string &username, const std::string &token, const std::string &hash) const; - rc_api_server_response_t + [[nodiscard]] rc_api_server_response_t handleStartSessionRequest(const std::string &username, int gameId, bool hardcore) const; @@ -38,9 +38,9 @@ class RetroAchievementsOfflineClient final { const std::string &token, unsigned achievementId, bool hardcore); - rc_api_server_response_t handleLogin2Request(const std::string &username, - const std::string &password, - const std::string &token) const; + [[nodiscard]] rc_api_server_response_t + handleLogin2Request(const std::string &username, const std::string &password, + const std::string &token) const; rc_api_server_response_t handlePingRequest();