RetroArch libretro 核心选项 API 实战指南:从 v0/v1 升级到 v2 分类与多语言支持
【免费下载链接】RetroArchCross-platform, sophisticated frontend for the libretro API. Licensed GPLv3.项目地址: https://gitcode.com/GitHub_Trending/re/RetroArch
本篇指南以libretro-common/samples/core_options/README.md为核心,系统讲解如何为 libretro core 接入"增强版核心选项"(core options v1)与带分类功能的 v2 接口。你将掌握模板文件的复制与改造、libretro_set_core_options()的注册时机、多语言翻译的组织方式、在不支持新 API 的前端上隐藏选项,以及如何通过 v2 分类机制整理高级设置;同时结合仓库中的示例源码与libretro.h定义,理解其底层协商与降级逻辑。
背景:核心选项的三代接口
在 libretro 生态中,"核心选项"(core options)是 core 向前端(如 RetroArch)暴露可配置参数的标准途径。libretro-common/include/libretro.h中定义了三条相关环境调用,构成三代接口:
| 版本 | 环境调用 | 宏值 | 特点 |
|---|---|---|---|
| v0 | RETRO_ENVIRONMENT_SET_VARIABLES | 16 | 最古老的接口,把每个选项序列化为desc; default\|val2\|val3形式的字符串数组(struct retro_variable),已标记 deprecated |
| v1 | RETRO_ENVIRONMENT_SET_CORE_OPTIONS/_INTL | 53 | 结构化定义struct retro_core_option_definition,支持子标签(sublabel)与国际化 |
| v2 | RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2/_INTL | — | 在 v1 基础上新增选项分类(categories),官方推荐新代码使用 |
libretro.h明确建议:"Prefer usingRETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2for new code, as it offers more features such as categories and translation",但核心也应在旧前端上回退到 v1、v0。本文介绍的正是一套完整的、自包含的降级方案模板。
一、为 core 添加增强版核心选项(v1)
libretro-common/samples/core_options/README.md给出了接入 v1 接口的五个基本步骤,模板位于 example_default/:
- 将
example_default/libretro_core_options.h复制到libretro.c/.cpp所在目录; - 将
example_default/libretro_core_options_intl.h复制到同一目录; - 在
libretro.c/.cpp中加入#include "libretro_core_options.h"; - 把原有的
RETRO_ENVIRONMENT_SET_VARIABLES调用替换为libretro_set_core_options(retro_environment_t environ_cb); - 打开
libretro_core_options.h,用实际需要的全部核心选项填充option_defs_us结构数组。
关于注册时机,README 特别强调:libretro_set_core_options()应尽早调用——理想位置是retro_set_environment()内部,最迟不得晚于retro_load_game()。这与模板源码注释"Should be called as early as possible - ideally insideretro_set_environment(), and no later thanretro_load_game()"完全一致。
1.1 option_defs_us 结构字段详解
模板中option_defs_us是struct retro_core_option_definition数组(结构定义见 libretro.h),包含key、desc、info、values[RETRO_NUM_CORE_OPTION_VALUES_MAX]、default_value五个字段,数组以全零哨兵项{ NULL, NULL, NULL, {{0}}, NULL }结尾。模板给出了三种典型写法:
struct retro_core_option_definition option_defs_us[] = { { "mycore_region", /* key(选项名,序列化用) */ "Console Region", /* description(显示标签) */ "Specify which region the system is from.", /* sublabel(info,补充说明) */ { { "auto", "Auto" }, /* value_1, value_1_label */ { "ntsc-j", "Japan" }, /* value_2, value_2_label */ { "ntsc-u", "America" }, /* value_3, value_3_label */ { "pal", "Europe" }, /* value_4, value_4_label */ { NULL, NULL }, /* 哨兵,结束 values */ }, "auto" /* default_value,必须与某个 value 一致 */ }, { "mycore_video_scale", "Video Scale", "Set internal video scale factor.", { { "1x", NULL }, /* 值本身可读(如数字)时,value_label 置 NULL */ { "2x", NULL }, { "3x", NULL }, { "4x", NULL }, { NULL, NULL }, }, "3x" }, { "mycore_overclock", "Reduce Slowdown", "Enable CPU overclock (unsafe).", { { "enabled", NULL }, /* 值为 'enabled'/'disabled' 时,value_label 置 NULL */ { "disabled", NULL }, { NULL, NULL }, }, "disabled" }, { NULL, NULL, NULL, {{0}}, NULL }, /* 数组结束哨兵 */ };从 libretro.h 的注释可以提炼出如下约束:
- key:选项唯一标识,前端序列化、
RETRO_ENVIRONMENT_GET_VARIABLE查询均以它为准,建议形如mycore_xxx; - value 与 label:
value是实际写入配置的字符串,label是显示文本。当 value 本身具备可读性(数字、"enabled"/"disabled" 等)时,label 应置NULL; - default_value:必须等于
values数组中的某个 value,否则libretro.h声明该选项"will be ignored"; - 上限:单个选项最多
RETRO_NUM_CORE_OPTION_VALUES_MAX(值为 128)个取值; - 哨兵:定义数组与 values 数组都必须以 NULL 结尾,前端据此确定边界。
二、模板底层实现剖析:版本协商与三级降级
libretro_set_core_options()的实现被有意放在头文件内(static INLINE),注释说明这是为了"避免额外增加 .c 文件,让 core 开发者接入成本尽可能低"。其核心逻辑在 example_default/libretro_core_options.h 中,可分为三段:
2.1 探测前端支持的核心选项版本
unsigned version = 0; if (!environ_cb) return; if (environ_cb(RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION, &version) && (version >= 1))通过RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION(宏值 52,见 libretro.h)查询前端版本。模板历史注释表明:v1.2 起判断条件从== 1放宽为>= 1,以兼容未来版本。
2.2 v1 路径:优先使用国际化接口
struct retro_core_options_intl core_options_intl; unsigned language = 0; core_options_intl.us = option_defs_us; /* 英文(默认)定义 */ core_options_intl.local = NULL; /* 本地化定义,默认无 */ if (environ_cb(RETRO_ENVIRONMENT_GET_LANGUAGE, &language) && (language < RETRO_LANGUAGE_LAST) && (language != RETRO_LANGUAGE_ENGLISH)) core_options_intl.local = option_defs_intl[language]; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL, &core_options_intl);这段代码的关键设计(也是 README"默认语言"注释的落地实现):
option_defs_us作为兜底:当前端语言不是英文,且option_defs_intl[language]对应的语言表存在时,才把local指向该语言定义;- 若前端语言不可用、或某语言表缺失,前端会自动回退到
us表; - 编译期定义了
HAVE_NO_LANGEXTRA时,则直接走无翻译路径RETRO_ENVIRONMENT_SET_CORE_OPTIONS。
2.3 v0 回退:动态拼接字符串
当version < 1(旧前端),模板会动态构造struct retro_variable数组,其字符串格式为:
<desc>; <default_value>|<value2>|<value3>...实现要点(对应模板第 185-266 行):
- 先扫描
option_defs_us统计选项数量并分配内存; - 对每个选项,先定位
default_value在 values 中的下标(strcmp匹配),strcat时默认值排在最前,其后用|分隔其余值; - 所有分配均带
goto error释放路径,保证失败时无内存泄漏。
这正是 v0 接口的限制:它没有 label、sublabel、分类等概念,因此模板在降级时只能尽力保留"选项名 = 描述; 默认值|可选值"的信息。理解这段代码有助于排查"为什么旧前端上选项显示格式奇怪"的问题。
三、添加核心选项翻译
README 给出了添加翻译的三个步骤,以example_translation/中的法语翻译(option_defs_fr)为范例:
- 把
libretro_core_options.h中option_defs_us的内容复制到libretro_core_options_intl.h,构成带对应语言后缀的新结构数组(如option_defs_fr); - 翻译所有人类可读字符串(desc、sublabel、value_label),key 与 value 必须与英文表保持一致;
- 把新数组挂到
libretro_core_options.h的option_defs_intl[RETRO_LANGUAGE_LAST]数组中对应语言索引位置。
option_defs_intl数组下标与RETRO_LANGUAGE_*枚举严格一一对应(英文、日文、法文、西文、德文……共 30+ 项),未提供的语言保持NULL。默认语言表(英文)承担双重兜底职责:既在前端语言不可用时使用,也用于填补某语言表中缺失的条目。
3.1 法语翻译示例的关键规则
以 example_translation/libretro_core_options_intl.h 为例:
struct retro_core_option_definition option_defs_fr[] = { { "mycore_region", /* key 必须与 option_defs_us 一致 */ "Région de la console", /* 已翻译的 description */ "Spécifiez la région d'origine du système.", /* 已翻译的 sublabel */ { { "auto", "Auto" }, /* value 必须与英文表一致 */ { "ntsc-j", "Japon" }, /* 只翻译 value_label */ { "ntsc-u", "Amérique" }, { "pal", "L'Europe" }, { NULL, NULL }, }, NULL /* default_value 可为 NULL, * 由前端从英文表继承 */ }, { NULL, NULL, NULL, {{0}}, NULL }, };要点归纳:
- key 与 value 不翻译:它们是序列化/持久化契约,翻译 value 会导致配置读写错乱;
- default_value 可省略(置 NULL),前端会回退到英文表的默认值;
- 数字类取值(如
1x/2x)无需翻译时,可写{ NULL, NULL }占位,从英文表继承(见模板中mycore_video_scale的法语条目)。
3.2 BOM、c89 与 HAVE_NO_LANGEXTRA 的限制
README 特别给出了一条硬性约束:翻译文件使用 UTF-8 字符并必须携带 BOM 标记,而 BOM 与 c89 构建不兼容。因此:
- 进行 c89 构建时,必须定义
HAVE_NO_LANGEXTRA(如-DHAVE_NO_LANGEXTRA),这会禁用全部翻译; libretro_core_options_intl.h头部还针对 MSVC 2010-2013 做了兼容处理(#pragma execution_character_set("utf-8")与关闭 4566 警告);- 模板版本注释显示该机制自 1.3 引入,目的是"fix for MSVC 2010-2013"。
四、在不支持新 API 的前端上隐藏选项
v1 接口允许 core 动态显示/隐藏选项,但对旧前端(仅 v0)没有对应能力。一个常见场景是:创建"显示高级设置"类开关选项,其本身依赖 v1 才能工作,在旧前端上应整体隐藏。方案是改造libretro_set_core_options(),在构造 v0 变量数组时跳过特定 key。
example_hide_option/ 演示了该做法:option_defs_us中新增mycore_show_speedhacks开关,并在 v0 降级路径的循环中加入:
/* Skip options that are irrelevant when using the * old style core options interface */ if (strcmp(key, "mycore_show_speedhacks") == 0) continue;注释明确说明"每需要省略一个选项,就追加一个strcmp()比较"。需要注意配套细节:
- v1/v2 路径不受影响——该选项会在新前端上正常显示并发挥"显示/隐藏高级项"的作用;
- 模板注释提醒:虽然数组按全部选项分配了空间,但跳过项对应的
values_buf[i]保持NULL,且变量数组通过独立的option_index游标紧凑填充,避免空洞。
任何需要此能力的 core,应直接以example_hide_option/libretro_core_options.h为模板,替代example_default版本。
五、v2 分类:给选项分组收纳
核心选项 v2 为选项引入了分类(category)机制:支持分类的前端会把同分类选项显示在主选项菜单的子菜单/分区中,从而减少视觉杂乱,或在无需"显示开关"的前提下收纳高级设置。模板见 example_categories/。
5.1 新增的数据结构
v2 使用struct retro_core_option_v2_definition(字段比 v1 多了"categorised"描述与 category key)与分类表struct retro_core_option_v2_category:
struct retro_core_option_v2_category option_cats_us[] = { { "video", /* key(分类名,须被选项引用) */ "Video", /* 分类显示名 */ "Configure display options." /* 分类子标签 */ }, { "hacks", "Advanced", "Options affecting low-level emulation performance and accuracy." }, { NULL, NULL, NULL }, /* 结束哨兵 */ }; struct retro_core_option_v2_definition option_defs_us[] = { { "mycore_video_scale", "Video > Scale", /* description:无分类支持时用 'Video >' 前缀模拟层级 */ "Scale", /* 'categorised' description:有分类支持时在 'Video' 子菜单内显示 */ "Set internal video scale factor.", NULL, /* 'categorised' sublabel(可为 NULL,回退普通 sublabel) */ "video", /* category key:必须匹配 option_cats_us 中的某个 key */ { { "1x", NULL }, { "2x", NULL }, { "3x", NULL }, { "4x", NULL }, { NULL, NULL }, }, "3x" }, { NULL, NULL, NULL, NULL, NULL, NULL, {{0}}, NULL }, };设计要点:
- category key 双向约束:选项的
category_key必须能在option_cats_us中找到;为NULL/空则选项保持无分类; - 双描述机制:
desc与info用于无分类前端(可用Video > Scale前缀模拟层级),desc_categorized/info_categorized用于有分类前端(显示为子菜单内的简洁文案);后者为 NULL 时回退前者; - 分类表与定义表最后都以全 NULL 哨兵结束。
5.2 categories_supported 输出参数与三级降级
v2 模板的libretro_set_core_options()签名多了一个输出参数:
static INLINE void libretro_set_core_options( retro_environment_t environ_cb, bool *categories_supported)核心逻辑(见 example_categories/libretro_core_options.h):
- 初始化
*categories_supported = false; - 查询版本;
version >= 2时通过RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL(或_V2)注册,并把环境调用返回值写入categories_supported——core 可据此决定是否选择性隐藏/重排选项; - 否则降级:把 v2 定义数组逐字段复制为
struct retro_core_option_definition(v1 数组),values 需逐个拷贝("Values must be copied individually..."),再走SET_CORE_OPTIONS_INTL; - 连 v1 也不支持(
version < 1)时,降级到与 v1 模板相同的 v0 字符串拼接路径; - 所有临时分配在
error:标签处统一释放。
由此,一份 v2 定义即可自动覆盖 v2 / v1 / v0 三代前端,这与libretro.h关于"core 应同时支持版本 2、1 和 0"的要求相吻合。
六、翻译工作流与 Crowdin 自动化
example_translation/还附带一套与 intl/ 配合的翻译工程化方案,其使用说明见 instructions.txt:
前置条件
- core 须 libretro 合规,
libretro_core_options.h(英文文本)与libretro_core_options_intl.h(已有翻译)同目录存在; - 无任何翻译时
libretro_core_options_intl.h允许为空文件; - 脚本不支持宏展开或运行时填充的文本——这些文本不会被纳入可翻译范围;
- 确认
libretro_core_options.h中#ifdef HAVE_LANGEXTRA/#ifndef HAVE_NO_LANGEXTRA预处理指令存在且正确,用于在受限平台(如内存紧张)上去掉多余语言引用; - 检查
options_intl(或 v2 的options_intl)是否正确引用了 intl 选项,否则翻译不会生效。
Crowdin 同步接入
- 将
intl目录与.github工作流(crowdin_prep.yml、crowdin_translate.yml)放入仓库根目录; - 可运行
intl/activate.py自动定位libretro_core_options.h并识别 core 名称填充占位符,但必须人工复核结果; crowdin_prep.yml需替换libretro_core_options.h的完整路径(2 处)与<CORE_NAME>,并确认监听分支正确——只有该分支上的文件变更才会触发上传;crowdin_translate.yml需替换定时同步的分钟/小时(<0-59> <0-23>,脚本会生成随机时间避免同时刻请求过载)、<CORE_NAME>与libretro_core_options_intl.h的完整路径(2 处);- 通过 Pull Request 向 Crowdin 项目管理申请 API 密钥,并在 GitHub 仓库创建名为
CROWDIN_API_KEY的 Actions secret; - 手动运行一次 "Crowdin Translations Initial Setup" 上传源文本与既有翻译(切勿重复运行,可能污染尚未合入仓库的最新翻译);之后建议手动执行一次 "Crowdin Translation Sync" 验证流程(遇
Permission to <repository> denied时需配置 GITHUB_TOKEN 权限); - 对 Crowdin 项目经理:为每个 core 单独创建 access token(Projects 读;Source files & strings 读写;Translations 读写;可选 Translation status 读),私密交付给 core 开发者,配置完成后删除,不得公开或明文长期保存。
intl/目录还提供了core_option_regex.py、v1_to_v2_converter.py、core_option_translation.py、crowdin_*.py等脚本,可用于正则校验选项定义、v1 到 v2 的机械转换以及 Crowdin 上传/下载流水线,可直接复用。
七、模板速查与仓库证据
| 场景 | 使用模板 | 关键位置 |
|---|---|---|
| 基础 v1 接入 | example_default/ | libretro_core_options.h |
| 旧前端隐藏选项 | example_hide_option/ | strcmp(key, "mycore_show_speedhacks")跳过逻辑 |
| 法语翻译演示 | example_translation/ | libretro_core_options_intl.h |
| v2 分类 + 三级降级 | example_categories/ | libretro_core_options.h |
| Crowdin 自动化 | example_translation/intl/ | instructions.txt |
底层契约均定义于 libretro.h:retro_core_option_value、retro_core_option_definition、retro_core_options_intl、retro_core_option_v2_category、retro_core_option_v2_definition、retro_core_options_v2(_intl)等结构体,以及RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION(52)、RETRO_ENVIRONMENT_SET_CORE_OPTIONS(53)等宏。各模板头部注释中的版本历史(1.0 → 1.1 → 1.2 → 1.3 → 2.0)完整记录了接口演进轨迹,是理解兼容性策略的第一手资料。
结语
从 v0 的字符串拼接,到 v1 的结构化定义与国际化,再到 v2 的分类收纳,核心选项接口的每一次演进都旨在降低 core 开发者负担、改善前端展示效果。以上四套模板覆盖了绝大多数 core 的需求:默认场景用example_default,需要隐藏选项用example_hide_option,需要分类用example_categories(其内置的三级降级已兼容全部前端),需要多语言则配合intl/脚本与 Crowdin 实现全自动翻译流水线。接入时牢记两条铁律:libretro_set_core_options()尽早注册(retro_set_environment()内最佳),翻译文件中 key 与 value 永不翻译。
【免费下载链接】RetroArchCross-platform, sophisticated frontend for the libretro API. Licensed GPLv3.项目地址: https://gitcode.com/GitHub_Trending/re/RetroArch
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考