PCSX2 成就系统底层:rcheevos 库架构、API 与集成实践

发布时间:2026/9/14 11:32:04
PCSX2 成就系统底层:rcheevos 库架构、API 与集成实践 PCSX2 成就系统底层rcheevos 库架构、API 与集成实践【免费下载链接】pcsx2PCSX2 - The Playstation 2 Emulator项目地址: https://gitcode.com/GitHub_Trending/pc/pcsx2rcheevos 是一套以纯 C 实现的成就处理库为模拟器提供 RetroAchievements 平台数据的解析、成就/排行榜判定与游戏哈希识别能力是 PCSX2 内建成就系统的核心依赖随仓库以3rdparty/rcheevos形式携带。本文将基于该库在仓库中的实际代码与 PCSX2 的集成源码梳理其模块划分、错误处理、控制台标识、rc_client_t运行时、rapi服务端通信、rhash游戏识别与自定义文件处理等核心机制帮助读者理解成就系统从读取内存到解锁成就的完整链路并掌握将其集成到自家模拟器中的关键 API 用法。rcheevos 的设计定位只做处理不做网络rcheevos 的定位是一套让模拟器更容易处理 RetroAchievements 数据的 C 代码库为玩家提供成就achievements与排行榜leaderboards支持。阅读其源码结构3rdparty/rcheevos/src/可以清楚地看到这一边界src/rcheevos/成就逻辑核心包括条件condition.c、条件组condset.c、触发器trigger.c、内存引用memref.c、操作数operand.c、排行榜lboard.c、富文本状态显示richpresence.c与运行时runtime.c、runtime_progress.c等src/rapi/面向 RetroAchievements Web 服务的 URL 构造与响应解析rc_api_user.c、rc_api_runtime.c、rc_api_info.c、rc_api_editor.c、rc_api_common.csrc/rhash/游戏文件哈希识别hash.c、cdreader.c、md5.csrc/rc_client.c与src/rc_client_external.c高层客户端封装。rcheevos 本身不提供任何 HTTP 网络连接。客户端必须自行从 RetroAchievements 获取数据再把响应交给 rcheevos 处理同样rapi 也从不发起 HTTP 请求它只负责把参数编码成合法 URL以及把服务端响应解析成结构体。这一设计把网络层完全留给宿主程序PCSX2 正是通过自己的HTTPDownloader见 pcsx2/Achievements.cpp来承担实际请求。另外需要注意rcheevos 中并非所有结构都能通过公开 API 创建但结构体本身是暴露的以便 UI 代码在创建、销毁和测试之外进行更深入的交互例如成就编辑器的展示逻辑。模块总览rcheevos / rapi / rhash / rc_clientrcheevos 的公开头文件集中在3rdparty/rcheevos/include/与源码目录一一对应模块头文件职责成就运行时rcheevos.h、rc_runtime.h、rc_runtime_types.h解析成就/排行榜定义按帧评估触发条件高层客户端rc_client.h、rc_client_raintegration.h管理登录、游戏识别/加载、成就与排行榜列表统一调度 rapi 与 rhash服务端通信rc_api_user.h、rc_api_runtime.h、rc_api_common.h、rc_api_info.h、rc_api_editor.h、rc_api_request.h构造 API 请求 URL 与解析响应游戏识别rc_hash.h为指定游戏生成 RetroAchievements 哈希公共支撑rc_error.h、rc_consoles.h、rc_util.h、rc_export.h错误码、控制台标识、通用工具、导出宏构建层面3rdparty/rcheevos/CMakeLists.txt将上述源码编译为一个rcheevos静态库并通过target_include_directories把include/以 INTERFACE 方式暴露给使用者同时定义了RC_NO_THREADS1、RC_HASH_NO_DISC、RC_HASH_NO_ENCRYPTED、RC_HASH_NO_ROM、RC_HASH_NO_ZIP等编译宏详见 CMakeLists.txt表明该构建裁剪掉了光盘读取、加密内容、ROM 与 ZIP 相关的可选能力。返回值与错误处理RC_OK与rc_error_strrcheevos 中任何返回成功指示的函数都会返回RC_OK或rc_error.h中定义的常量之一。完整的错误码枚举定义在 rc_error.henum { 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, /* 无效的 RequiredHits */ RC_DUPLICATED_START -8, /* 重复的 Start 定义 */ RC_DUPLICATED_CANCEL -9, /* 重复的 Cancel 定义 */ RC_DUPLICATED_SUBMIT -10, /* 重复的 Submit 定义 */ RC_DUPLICATED_VALUE -11, /* 重复的 Value 定义 */ RC_DUPLICATED_PROGRESS -12, /* 重复的 Progress 定义 */ RC_MISSING_START -13, /* 缺少 Start 定义 */ RC_MISSING_CANCEL -14, /* 缺少 Cancel 定义 */ RC_MISSING_SUBMIT -15, /* 缺少 Submit 定义 */ RC_MISSING_VALUE -16, /* 缺少 Value 定义 */ 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, /* 缺少 Measured 值 */ RC_MULTIPLE_MEASURED -22, /* 多个 Measured */ RC_INVALID_MEASURED_TARGET -23, /* 无效的 Measured 目标 */ RC_INVALID_COMPARISON -24, /* 无效的比较 */ RC_INVALID_STATE -25, /* 无效的状态 */ RC_INVALID_JSON -26, /* 无效的 JSON */ RC_API_FAILURE -27, /* API 调用失败 */ 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, /* Token 过期 */ RC_INSUFFICIENT_BUFFER -36, /* 缓冲区不足 */ RC_INVALID_VARIABLE_NAME -37, /* 无效的变量名 */ RC_UNKNOWN_VARIABLE_NAME -38, /* 未知的变量名 */ RC_NOT_FOUND -39, /* 未找到 */ RC_INVALID_VALUE -40 /* 无效的值 */ };从枚举分布可以看出负值区间覆盖了成就定义解析类错误-1 至 -26与运行时/服务端类错误-27 至 -40两大类后者与rc_client的异步回调场景直接相关。要把返回码转成人类可读字符串直接调用const char* rc_error_str(int ret);该函数同样声明于 rc_error.h是排查集成问题时的第一入口。控制台标识rc_consoles.h与内存区域RetroAchievements 平台支持的主机平台在 rc_consoles.h 中枚举例如RC_CONSOLE_PLAYSTATION 12、RC_CONSOLE_PLAYSTATION_2 21、RC_CONSOLE_NINTENDO_64 2、RC_CONSOLE_GAMECUBE 16、RC_CONSOLE_SATURN 39、RC_CONSOLE_PSP 41等一直到RC_CONSOLE_FAMICOM_DISK_SYSTEM 81此外还有RC_CONSOLE_HUBS 100、RC_CONSOLE_EVENTS 101、RC_CONSOLE_STANDALONE 102这类非实体平台标识。需要留意的是枚举中的部分控制台尚未被完全支持——可能缺少内存映射或缺少唯一识别游戏的手段。这一点对集成方很重要在接入新平台前应确认该平台是否具备可用的内存映射memory map。与识别相关的还有**内存区域memory region**描述机制。同一头文件定义了rc_memory_region_t与rc_memory_regions_t用于描述RetroAchievements 查询地址到真实地址的映射关系typedef struct rc_memory_region_t { uint32_t start_address; /* 该块在 RetroAchievements 侧的起始地址 */ uint32_t end_address; /* 该块的结束地址 */ uint32_t real_address; /* 起始地址对应的真实内存地址 */ uint8_t type; /* RC_MEMORY_TYPE_ 枚举之一 */ const char* description; /* 块的简短描述 */ } rc_memory_region_t;地址块类型包括RC_MEMORY_TYPE_SYSTEM_RAM系统内存、RC_MEMORY_TYPE_SAVE_RAM持久化存档内存、RC_MEMORY_TYPE_VIDEO_RAM显存、RC_MEMORY_TYPE_READONLY只读数据、RC_MEMORY_TYPE_HARDWARE_CONTROLLER硬件控制器交互、RC_MEMORY_TYPE_VIRTUAL_RAM映射到系统 RAM 的二次地址空间与RC_MEMORY_TYPE_UNUSED。集成方可通过rc_console_memory_regions(console_id)查询某平台的内存区域表再结合rc_client的读内存回调把成就逻辑中的虚拟地址翻译为真实地址。运行时支持rc_client_t一站式集成如果要为模拟器添加成就支持首选方案是使用rc_client_t系列函数。它封装了日常集成所需的公共逻辑管理用户信息、识别并加载游戏、为 UI 构建激活/未激活的成就列表同时通过若干回调函数把 UI 与 HTTP 等依赖功能交给客户端实现。rc_client还统一调度了 rhash识别游戏与 rapi与服务器通信因此是 README 明确推荐的集成入口。创建与销毁核心对象rc_client_t的实现被抽象在rc_client_internal.h中公开头文件仅暴露不透明指针。创建与销毁对应rc_client_t* rc_client_create(rc_client_read_memory_func_t read_memory_function, rc_client_server_call_t server_call_function); void rc_client_destroy(rc_client_t* client);构造时就必须提供两个关键回调读内存回调rc_client_read_memory_func_t从指定地址读取num_bytes字节到缓冲区返回实际读取字节数返回 0 表示地址无效服务器调用回调rc_client_server_call_t向服务器发起一次请求并在响应到达后回调rc_client_server_callback_t。模式开关rc_client.h提供了一组运行模式开关见 rc_client.hrc_client_set_hardcore_enabled(client, enabled)硬核模式默认开启在已加载游戏时开启会触发RC_CLIENT_EVENT_RESET事件处理将暂停直到调用rc_client_resetrc_client_set_encore_mode_enabled(client, enabled)安可模式默认关闭仅在加载游戏时生效rc_client_set_unofficial_enabled(client, enabled)是否加载非官方成就默认关闭加载游戏时生效rc_client_set_spectator_mode_enabled(client, enabled)旁观模式默认关闭开启后仍会抛出解锁/提交事件但不会真正向服务器执行解锁/提交。此外还有rc_client_set_host指定服务端主机名、rc_client_set_get_time_millisecs_function提供单调递增的毫秒时钟、rc_client_set_userdata附加客户端私有数据以及rc_client_enable_logging日志级别从RC_CLIENT_LOG_LEVEL_NONE到RC_CLIENT_LOG_LEVEL_VERBOSE共五档等辅助接口。登录登录支持两种凭据方式均为异步调用并返回可中止的rc_client_async_handle_t*rc_client_async_handle_t* rc_client_begin_login_with_password(rc_client_t* client, const char* username, const char* password, rc_client_callback_t callback, void* callback_userdata); rc_client_async_handle_t* rc_client_begin_login_with_token(rc_client_t* client, const char* username, const char* token, rc_client_callback_t callback, void* callback_userdata);登录成功后可通过rc_client_get_user_info获取rc_client_user_t包含username、token、score、score_softcore、num_unread_messages、avatar_url等字段通过rc_client_get_user_game_summary获取已解锁 X / 共 Y 个成就的汇总数据rc_client_user_game_summary_t用于开局提示。游戏识别与加载在启用哈希能力RC_CLIENT_SUPPORTS_HASH时可一步完成识别 加载rc_client_async_handle_t* 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);若已持有现成的哈希字符串也可直接走rc_client_begin_load_game(client, hash, callback, ...)。加载过程是一个明确的状态机rc_client_get_load_game_state状态依次为等待登录RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN→ 识别游戏RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME→ 启动会话RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION→ 完成RC_CLIENT_LOAD_GAME_STATE_DONE。多光盘游戏可通过rc_client_begin_identify_and_change_media/rc_client_begin_change_media切换当前光盘。成就与排行榜列表面向 UI 的场景rc_client提供按分桶bucket组织的列表接口成就rc_client_create_achievement_list(client, category, grouping)其中 category 区分核心RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE与非官方RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL分桶包括已解锁、未解锁、最近解锁、进行中的挑战RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE、差一点RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE等 9 类列表用完须调用rc_client_destroy_achievement_list释放排行榜rc_client_create_leaderboard_list(client, grouping)支持按追踪状态分组条目含title、description、tracker_value当前追踪值、format时间/分数/数值与lower_is_better排行榜排名rc_client_begin_fetch_leaderboard_entries与rc_client_begin_fetch_leaderboard_entries_around_user异步拉取榜单条目全集进度rc_client_begin_fetch_all_user_progress查询某平台全部游戏的解锁汇总哈希库rc_client_begin_fetch_hash_library拉取哈希 → 游戏 ID的映射一个游戏可对应多个哈希多光盘或兼容变体。PCSX2 中的rc_client落地实例PCSX2 的成就模块在 Achievements.cpp 中完整实践了上述流程。其CreateClient函数约第 501–537 行展示了典型初始化顺序rc_client_t* new_client rc_client_create(ClientReadMemory, ClientServerCall); // ... rc_client_enable_logging(new_client, RC_CLIENT_LOG_LEVEL_VERBOSE, ClientMessageCallback); // DEV 版 rc_client_set_userdata(new_client, http-get()); // 支持自定义服务器主机 rc_client_set_host(new_client, custom_host.c_str());其中ClientServerCall把rc_api_request_t转交给 PCSX2 自己的HTTPDownloader超时与并发数分别由SERVER_CALL_TIMEOUT、MAX_CONCURRENT_SERVER_CALLS控制ClientReadMemory则把 rcheevos 的虚拟地址映射到 PS2 内存。登录走rc_client_begin_login_with_token第 483 行游戏加载完成后通过rc_client_create_achievement_list第 2432 行与rc_client_create_leaderboard_list第 3049 行构建全屏 UI 的列表。这是 README 所述回调函数由客户端实现 UI 与 HTTP 依赖的教科书式应用。服务端通信rapi 的请求-响应流程rapi的作用是构建访问 RetroAchievements 各项 Web 服务的 URL其核心价值在于帮开发者省去手动 URL 编码与拼装合法 URL 的负担。rapi 不做 HTTP 请求网络层由宿主负责。rapi 相关的头文件为rc_api_user.h、rc_api_runtime.h与rc_api_common.h仓库中另含rc_api_info.h、rc_api_editor.h、rc_api_request.h覆盖信息查询与成就编辑器等更多接口。一个典型 rapi 调用的完整流程是初始化一个 params 对象如rc_api_login_request_t调用对应函数把它转换成rc_api_request_t内含 URL由宿主把请求发给服务器把服务器响应交给对应的解析函数转换成 response 对象处理 response 中的字段用完调用对应的 destroy 函数释放。以登录请求为例rc_api_user.htypedef struct rc_api_login_request_t { const char* username; /* 玩家用户名 */ const char* api_token; /* 上次登录得到的 API Token */ const char* password; /* 玩家密码 */ } rc_api_login_request_t; int rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params); int rc_api_process_login_server_response(rc_api_login_response_t* response, const rc_api_server_response_t* server_response); void rc_api_destroy_login_response(rc_api_login_response_t* response);注意如果同时提供了 password 和 api_tokenapi_token 会被忽略。登录响应rc_api_login_response_t会回填大小写校正后的用户名、用于后续所有请求的api_token、硬核/软核分数、未读消息数、显示名与头像信息并内嵌公共的rc_api_response_t携带服务端返回码与错误信息。会话启动请求rc_api_start_session_request_t则要求提供username、api_token、game_id并推荐带上game_hash此时hardcore标志才会被采纳。rapi 与 rc_client 的关系README 明确指出rc_client是与服务器交互的首选方式——它内部已封装 rapi 的调用与重试逻辑宿主只需实现server_call回调。只有在需要脱离 rc_client 单独实现自定义网络层时才需要直接操作 rapi 系列函数。游戏识别rhash 与迭代器 APIrhash负责为给定游戏生成 RetroAchievements 哈希。API 提供两种用法传入文件名让 rhash 自行打开并处理文件或把已读入内存的文件缓冲传给 rhash。核心函数全部位于 rc_hash.hvoid rc_hash_initialize_iterator(rc_hash_iterator_t* iterator, const char* path, const uint8_t* buffer, size_t buffer_size); int rc_hash_generate(char hash[33], uint32_t console_id, const rc_hash_iterator_t* iterator); int rc_hash_iterate(char hash[33], rc_hash_iterator_t* iterator); void rc_hash_destroy_iterator(rc_hash_iterator_t* iterator);使用要点哈希输出为33 字节32 个十六进制字符 结尾\0的字符串rc_hash_initialize_iterator要求提供 path若同时提供 buffer 与 buffer_sizepath 仅作为文件名使用例如从 ZIP 中解出的文件rc_hash_generate为指定console_id生成哈希返回非零表示成功rc_hash_iterate依次生成迭代器中的下一个哈希例如多光盘游戏返回 0 表示没有更多哈希可生成rc_hash_destroy_iterator负责释放迭代器相关资源。rc_hash_iterator_t内部维护了候选控制台列表consoles[12]、当前索引、路径/缓冲以及一组可自定义的回调rc_hash_callbacks_t因此同一个文件可以对多个平台依次尝试哈希。PCSX2 的 PS2 哈希实现PCSX2 没有直接走 rhash 的迭代器路径而是按See rcheevos hash.c - rc_hash_ps2()的规则自行实现见 Achievements.cpp 的GetGameHash取光盘内 ELF 的规范化文件名连同 ELF 内容的前64 MiBMAX_HASH_SIZE 64 * 1024 * 1024一起送入 MD5最终输出 32 位十六进制哈希字符串。这一实现从侧面印证了 rhash 哈希算法的要点PS2 游戏哈希由文件名 前 64MiB 内容的 MD5 构成且对同一游戏的不同镜像版本保持兼容。自定义文件处理为 ZIP / CHD 等格式接入读取器rhash以及依赖它的rc_client支持自定义的文件打开/读取处理器使客户端能把文件读取重定向到自定义文件格式如 ZIP 或 CHD无需预先解包。回调结构定义如下rc_hash.htypedef struct rc_hash_filereader { rc_hash_filereader_open_file_handler open; /* 打开文件返回句柄 */ rc_hash_filereader_seek_handler seek; /* 移动文件指针fseek 语义 */ rc_hash_filereader_tell_handler tell; /* 获取文件指针位置 */ rc_hash_filereader_read_handler read; /* 读取字节返回实际读取数 */ rc_hash_filereader_close_file_handler close; /* 关闭句柄 */ } rc_hash_filereader_t;在支持光盘读取的构建未定义RC_HASH_NO_DISC中还提供rc_hash_cdreader_t包含打开音轨open_track、按绝对扇区读取read_sector、获取首扇区索引first_track_sector等回调并预置了RC_HASH_CDTRACK_FIRST_DATA、RC_HASH_CDTRACK_LAST、RC_HASH_CDTRACK_LARGEST、RC_HASH_CDTRACK_FIRST_OF_SECOND_SESSION等特殊音轨选择常量用于跳过音轨、取最后一条数据/音轨或第二会话首轨等场景。加密内容支持如 3DS CIA/NCCH 解密密钥回调则在未定义RC_HASH_NO_ENCRYPTED时可用。回调的注册方式有两种较新的方式是填充rc_hash_callbacks_t并通过rc_client_set_hash_callbacks挂到rc_client上旧版则使用rc_hash_init_custom_filereader/rc_hash_init_custom_cdreader等全局注册函数头文件中已标注[deprecated]建议优先使用前者。版本与分支策略rcheevos 的日常开发发生在develop分支GitHub 上默认为该分支新 PR 默认合并到 develop而master 分支对应最近一次官方发布版本。README 给出的集成建议是将 rcheevos 集成到自有项目时使用 master 分支以最大限度降低因引入发布后新增 bug 的风险。仓库内随附的 CHANGELOG.md 记录了各版本演进如rc_client.h中多处标注 minimum version: 11.1 / 12.0 / 12.4 的字段即不同版本引入的能力可作为评估依赖版本时的参考。集成要点小结综合 README 与仓库源码把 rcheevos 接入模拟器时可遵循以下路径引入构建通过 CMake 添加3rdparty/rcheevos子目录链接rcheevos目标并按需裁剪编译宏参考 CMakeLists.txt 中RC_HASH_NO_*与RC_NO_THREADS的用法初始化运行时用rc_client_create传入读内存回调与服务端调用回调配置日志、主机与硬核等模式实现网络层在server_call回调中接入自己的 HTTP 下载器PCSX2 使用HTTPDownloader见 pcsx2/Achievements.cpp完成登录与游戏数据拉取识别游戏优先使用rc_client_begin_identify_and_load_game一步完成哈希生成与游戏加载需要时通过rc_client_set_hash_callbacks注入自定义文件读取器驱动判定每帧调用运行时更新接口由rc_client内部管理通过事件回调处理解锁、排行榜追踪等 UI 反馈构建 UI用rc_client_create_achievement_list/rc_client_create_leaderboard_list获取分桶列表展示进度并响应用户操作。rcheevos 通过纯处理、零网络的职责划分把成就判定、服务端协议与文件哈希这三块复杂逻辑与网络实现解耦使得 PCSX2 等宿主只需聚焦于内存访问、HTTP 下载与 UI 呈现即可快速获得一套完整、可维护的成就与排行榜能力。【免费下载链接】pcsx2PCSX2 - The Playstation 2 Emulator项目地址: https://gitcode.com/GitHub_Trending/pc/pcsx2创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考