RetroArch libretro 核心选项 API 实战指南:从 v0/v1 升级到 v2 分类与多语言支持
2026/9/15 1:15:57 网站建设 项目流程

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中定义了三条相关环境调用,构成三代接口:

版本环境调用宏值特点
v0RETRO_ENVIRONMENT_SET_VARIABLES16最古老的接口,把每个选项序列化为desc; default\|val2\|val3形式的字符串数组(struct retro_variable),已标记 deprecated
v1RETRO_ENVIRONMENT_SET_CORE_OPTIONS/_INTL53结构化定义struct retro_core_option_definition,支持子标签(sublabel)与国际化
v2RETRO_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/:

  1. example_default/libretro_core_options.h复制到libretro.c/.cpp所在目录;
  2. example_default/libretro_core_options_intl.h复制到同一目录;
  3. libretro.c/.cpp中加入#include "libretro_core_options.h"
  4. 把原有的RETRO_ENVIRONMENT_SET_VARIABLES调用替换为libretro_set_core_options(retro_environment_t environ_cb)
  5. 打开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_usstruct retro_core_option_definition数组(结构定义见 libretro.h),包含keydescinfovalues[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 与 labelvalue是实际写入配置的字符串,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)为范例:

  1. libretro_core_options.hoption_defs_us的内容复制到libretro_core_options_intl.h,构成带对应语言后缀的新结构数组(如option_defs_fr);
  2. 翻译所有人类可读字符串(desc、sublabel、value_label),key 与 value 必须与英文表保持一致
  3. 把新数组挂到libretro_core_options.hoption_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/空则选项保持无分类;
  • 双描述机制descinfo用于无分类前端(可用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):

  1. 初始化*categories_supported = false
  2. 查询版本;version >= 2时通过RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL(或_V2)注册,并把环境调用返回值写入categories_supported——core 可据此决定是否选择性隐藏/重排选项;
  3. 否则降级:把 v2 定义数组逐字段复制struct retro_core_option_definition(v1 数组),values 需逐个拷贝("Values must be copied individually..."),再走SET_CORE_OPTIONS_INTL
  4. 连 v1 也不支持(version < 1)时,降级到与 v1 模板相同的 v0 字符串拼接路径;
  5. 所有临时分配在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.ymlcrowdin_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.pyv1_to_v2_converter.pycore_option_translation.pycrowdin_*.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_valueretro_core_option_definitionretro_core_options_intlretro_core_option_v2_categoryretro_core_option_v2_definitionretro_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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询