mirror of
https://github.com/ARMSX2/ARMSX1.git
synced 2026-08-24 16:53:35 -07:00
140 lines
7.5 KiB
C++
140 lines
7.5 KiB
C++
#ifndef ARMSX_ACHIEVEMENTS_H
|
|
#define ARMSX_ACHIEVEMENTS_H
|
|
|
|
/*
|
|
ARMSX — RetroAchievements (rcheevos) integration.
|
|
|
|
SOFTCORE ONLY. Hardcore is wired shut at every layer: the client is created with
|
|
rc_client_set_hardcore_enabled(client, 0) and nothing in this module ever calls it with a
|
|
non-zero argument, the persisted store has no hardcore key, and the JNI toggle is a no-op
|
|
that logs and returns. Until the emulator has had a lot more testing an unlock earned here
|
|
must not be able to claim hardcore credit on a user's RA account.
|
|
|
|
LAYOUT
|
|
------
|
|
Everything rc_client touches lives behind one mutex (the emulation thread runs
|
|
armsx_ach_frame_update(), the Android UI thread polls armsx_ach_get_json(), and a Java IO
|
|
thread drives armsx_ach_login()). rcheevos is built with RC_NO_THREADS, so serialising here
|
|
is the whole story.
|
|
|
|
HTTP is not implemented in this file. The host installs a blocking transport with
|
|
armsx_ach_set_http_handler() — on Android that is kr.co.iefriends.pcsx2.HttpClient via JNI —
|
|
and this module runs it on its own worker threads, parking the responses until a pump
|
|
(armsx_ach_frame_update, or the login loop) hands them back to rc_client on a thread that
|
|
holds the lock.
|
|
|
|
Memory reads go through the PS1 map RA publishes for RC_CONSOLE_PLAYSTATION:
|
|
0x000000-0x1FFFFF is main RAM, 0x200000-0x2003FF is the scratchpad at 0x1F800000.
|
|
*/
|
|
|
|
#include <cstdint>
|
|
#include <string>
|
|
|
|
struct psx_t;
|
|
|
|
/* One HTTP transaction. The handler blocks until it has an answer and fills this in;
|
|
status_code is the HTTP status, or negative for a transport failure (-1 generic, -2 timeout)
|
|
matching kr.co.iefriends.pcsx2.HttpClient's sentinels. */
|
|
struct armsx_ach_http_response {
|
|
int status_code = 0;
|
|
std::string content_type;
|
|
std::string body;
|
|
};
|
|
|
|
typedef void (*armsx_ach_http_fn)(const char* url, const char* post_data, const char* content_type,
|
|
const char* user_agent, int timeout_ms,
|
|
armsx_ach_http_response* out, void* user);
|
|
|
|
/* Fire-and-forget playback of an unlock sound. Only invoked when the user has picked one
|
|
(armsx_ach_set_unlock_sound); ARMSX1 bundles no RA sounds of its own. */
|
|
typedef void (*armsx_ach_sound_fn)(const char* path, void* user);
|
|
|
|
/* What a notice is about. The host uses this to style the toast (accent colour, fallback icon)
|
|
and nothing else — every kind carries the same fields, and an unknown kind must render as
|
|
ARMSX_ACH_NOTICE_INFO rather than be dropped. */
|
|
enum {
|
|
ARMSX_ACH_NOTICE_INFO = 0, /* connection state, and anything uncategorised */
|
|
ARMSX_ACH_NOTICE_LOGIN = 1, /* signed in — [image_url] is the user's avatar */
|
|
ARMSX_ACH_NOTICE_GAME = 2, /* disc identified — [image_url] is the game's box art */
|
|
ARMSX_ACH_NOTICE_UNLOCK = 3, /* achievement earned — [image_url] is its badge */
|
|
ARMSX_ACH_NOTICE_MASTERY = 4, /* whole set (or a subset) completed — game box art */
|
|
ARMSX_ACH_NOTICE_LEADERBOARD = 5, /* leaderboard attempt started/failed/submitted */
|
|
ARMSX_ACH_NOTICE_ERROR = 6 /* something the user needs to act on */
|
|
};
|
|
|
|
/* One toast for the host's on-screen notification stack: signing in, the game summary at boot,
|
|
an unlock, a leaderboard attempt, a sign-in that expired.
|
|
|
|
[key] identifies the toast for replacement, and is never null or empty. A notice whose key
|
|
matches one already on screen REPLACES it in place rather than stacking — this is what keeps a
|
|
game that starts and submits six leaderboards in the same frame (they exist) from burying an
|
|
unlock under a wall of its own attempt notices. Unlocks carry a per-achievement key so they
|
|
always stack; leaderboards carry a per-leaderboard one so a start/submit pair collapses.
|
|
|
|
[title] is always non-null and non-empty. [detail] is the second line and may be empty, never
|
|
null. [image_url] is an https RetroAchievements image (avatar / box art / badge) and may be
|
|
empty, never null — the host downloads and caches it; it must never block on that download
|
|
before showing the text. [duration_ms] is the user's configured notification duration for this
|
|
category (achievement notifications and leaderboard notifications are configured separately).
|
|
|
|
Called ONLY from the notice pump (armsx_ach_frame_update / the login loop) with no lock held,
|
|
never from an rc_client callback — those run on whichever thread happened to be draining HTTP,
|
|
which on Android is not necessarily one that can talk to Java. The host may therefore assume a
|
|
thread that is safe to make a JNI call from, but must NOT assume the UI thread. */
|
|
typedef void (*armsx_ach_notify_fn)(int kind, const char* key, const char* title,
|
|
const char* detail, const char* image_url, int duration_ms,
|
|
void* user);
|
|
|
|
void armsx_ach_set_http_handler(armsx_ach_http_fn handler, void* user);
|
|
void armsx_ach_set_sound_handler(armsx_ach_sound_fn handler, void* user);
|
|
void armsx_ach_set_notify_handler(armsx_ach_notify_fn handler, void* user);
|
|
|
|
/* The User-Agent every RA request carries. See frontend/ra_ua.h — it is a placeholder until
|
|
the RA team issues ARMSX1 a client. */
|
|
const char* armsx_ach_user_agent(void);
|
|
|
|
/* Create the client and restore a saved login, if any. Safe to call repeatedly; the first call
|
|
wins. Requires the pref path to be resolved (psxe_cfg_set_pref_path on Android). */
|
|
void armsx_ach_startup(void);
|
|
void armsx_ach_shutdown(void);
|
|
|
|
/* Session lifecycle, called from the emulation thread. [psx] is published for the memory reader
|
|
and cleared by armsx_ach_session_ended(); the game hash is computed off the mounted disc on
|
|
the next armsx_ach_frame_update() so a slow CHD open never lands inside a boot. */
|
|
void armsx_ach_session_started(psx_t* psx);
|
|
void armsx_ach_session_ended(void);
|
|
|
|
/* Emulation-thread pump: drains finished HTTP requests, runs rc_client_do_frame() (or
|
|
rc_client_idle() when the session is not stepping) and refreshes rich presence. */
|
|
void armsx_ach_frame_update(bool stepping);
|
|
|
|
/* UI-thread queries. Never return null; the JSON one always returns a parseable object. */
|
|
std::string armsx_ach_get_json(void);
|
|
std::string armsx_ach_get_rich_presence(void);
|
|
|
|
/* Blocking password login. Returns an empty string on success or a human-readable error.
|
|
Runs the HTTP round trip to completion — call off the UI thread. */
|
|
std::string armsx_ach_login(const char* username, const char* password);
|
|
void armsx_ach_logout(void);
|
|
|
|
/* Presentation options, mirrored back out through armsx_ach_get_json(). Keys match the Kotlin
|
|
side: notifications, leaderboardNotifications, overlays, lbOverlays, soundEffects,
|
|
encoreMode, spectatorMode, unofficialTestMode / notificationsDuration, leaderboardsDuration,
|
|
notificationPosition, overlayPosition. */
|
|
void armsx_ach_set_option(const char* key, bool enabled);
|
|
void armsx_ach_set_option_int(const char* key, int value);
|
|
void armsx_ach_set_unlock_sound(const char* path);
|
|
|
|
/* Point the client at a loopback proxy (RA host override) and back. */
|
|
void armsx_ach_set_host_override(const char* host);
|
|
void armsx_ach_clear_host_override(void);
|
|
|
|
/* RA hash for a disc image that is not mounted, for the library's progress lookup. Empty when
|
|
the image cannot be read or has no PS1 executable. Does disc I/O — call off the UI thread. */
|
|
std::string armsx_ach_hash_for_path(const char* image_path);
|
|
|
|
/* Always false. Kept so callers have something to ask instead of assuming. */
|
|
bool armsx_ach_hardcore_active(void);
|
|
|
|
#endif
|