news 2026/9/13 16:24:40

Flipper Zero 上的 Wii 扩展控制器协议分析仪:从接线、识别到校准的完整实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flipper Zero 上的 Wii 扩展控制器协议分析仪:从接线、识别到校准的完整实战指南

Flipper Zero 上的 Wii 扩展控制器协议分析仪:从接线、识别到校准的完整实战指南

【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper

本篇文章基于 Flipper 仓库 Applications/Official/source-OLDER/grnch/wii_ec_anal 目录下的 Wii Extension Controller Protocol Analyser(Wii 扩展控制器协议分析仪)项目文档,结合其完整 C 源码展开。你将掌握:如何在 Flipper Zero 上通过 I2C 连接 Wii 外设(Nunchuck、Classic Controller 等)、协议分析仪的每个屏幕与按键功能、模拟量校准机制,以及底层 I2C 寄存器级通信原理。

项目是什么

Wii Extension Controller Protocol Analyser 是一个跑在 Flipper Zero 上的协议分析仪插件,提供了一套完整的Test(测试)与 Calibrate(校准)系统,用于连接和调试 Wii 扩展控制器(Wii Extension Controller)。它属于本项目仓库作者 "stuff I make or modify for the Flipper Zero" 的收集内容之一,位于Applications/Official/source-OLDER目录(即官方来源的早期版本归档区)。

项目在 application.fam 中注册为外部应用:

appid="Wii_EC_Analyser" name="Wii EC Analyser" apptype=FlipperAppType.EXTERNAL entry_point="wii_ec_anal" requires=["gui"] stack_size=2 * 1024 sources=["wii_*.c", "gfx/*.c"] fap_category="Misc"

插件入口函数为wii_ec_anal(),实现在 wii_anal.c,它创建一个 20 条消息的事件队列、一个 GUI ViewPort、一个周期性定时器,然后进入主事件循环。启动后会先显示 SPLASH 屏,3.5 秒超时或任意按键短按后进入 WAIT 屏并开始扫描 I2C 总线。

免责声明:原文档明确强调,使用本插件、尤其是将扩展控制器连接到 Flipper Zero 的操作完全由使用者自担风险。请务必在确认接线正确后再上电。

硬件接线:WAIT 屏与引脚映射

插件启动并进入 WAIT 屏后,屏幕上会直接绘制出需要连接的引脚示意图。接线前请先确认所有连接都已断开电源

从扩展控制器插头的露出面观察(凹槽朝下),各引脚定义如下表:

EC 引脚号EC 位置EC 引脚 ID功能FZ GPIO 引脚名FZ GPIO 引脚号
1左上+3v3电源3v39
2左下SCLI2C 时钟C016
3上中EN疑似检测脚(未验证)
4下中-x-无连接
5右上SDAI2C 数据C115
6右下Gnd电源地Gnd18

也就是说,实际只需连接4 根线3v3 → 3v3C0 → SCLC1 → SDAGnd → Gnd。顶部居中的 "EN" 脚作者认为是"存在检测(presence detect)"功能,但尚未验证;本插件不需要该引脚,设备检测完全依靠 I2C 握手完成。

WAIT 屏的按键:

  • Left(左键):回到 SPLASH 屏
  • Back(返回键):退出插件

WAIT 屏的实际绘制代码在 wii_anal.c 的SCENE_WAIT分支中,它把 3v3、C1/SDA、Gnd、C0/SCL 的图形和文字标注逐像素画到屏幕上。

适配器选择与接线警告

最省事的连接方案是使用现成的WiiChuckNunchucky适配器(原文给出了两者购买渠道,此处不赘述外链)。但必须注意:

警告:WiiChuck 与 Nunchucky 都没有防反接的极性机构。如果插反,你会把电压以错误的方向加到控制器上!作者表示无法确认这是否会永久损坏控制器——没人愿意去试。

对常见的 WiiChuck 适配器,作者观察到:

  • WiiChuck 一侧有3 个连接器,另一侧有2 个连接器
  • 有 2 个连接器的一侧,应对准控制器插头上带大凹槽的一侧。

即便如此,强烈建议在插入前核对适配器各引脚的实际定义。

加密与"加密绕过"策略

Wii 扩展控制器支持 I2C 加密通信,但本插件中加密相关的代码虽然存在,却是未使用、未测试的,并且已知部分不工作

当前插件只支持实现"加密绕过(encryption-bypass)"策略的扩展控制器,即上电初始化时执行:

i2c_write(0xf0, 0x55); i2c_write(0xfb, 0x00);

这在源码 wii_i2c.c 中对应:

static const uint8_t cmdInit1[] = {regInit1, 0x55}; // regInit1 = 0xF0 static const uint8_t cmdInit2[] = {regInit2, 0x00}; // regInit2 = 0xFB

即先向寄存器0xF0写入0x55,再向0xFB写入0x00,让控制器进入不加密模式。若你需要加密支持,作者建议到上游仓库提交 Issue,或者更好——提交 Pull Request。

wii_i2c.c中还保留了一个decrypt()函数(见 wii_i2c.c),它实现decrypted_byte = (encrypted_byte XOR encKey[1][addr%8]) + encKey[2][addr%8]的标准解密算法,但由于加密链路本身未打通,该函数实际上不会被可靠调用。

设备识别:I2C 握手与 PID 表

当设备连接后,插件会立即识别。如果识别失败,可能的原因有二:

  • 控制器没有正确连接——也许只是一根线断了;
  • 控制器板卡本身故障——修复超出了本文档范围。

识别流程在ecInit()(wii_i2c.c)中实现:先检查设备是否在线(furi_hal_i2c_is_device_ready),发送初始化命令,然后从寄存器0xFA..0xFF读取6 字节的外设 ID(PID),最后在已知设备表中逐条memcmp匹配,匹配失败则归为PID_UNKNOWN

运行./info.sh脚本(即仓库内的 info.sh)可以查看已知控制器列表。作者写文档时返回如下(注意info.sh第一行就自嘲地输出MARKED AS TODO,说明项目仍处于开发中状态):

[PID_UNKNOWN ] = { {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, "Unknown Perhipheral", SCENE_DUMP, [PID_NUNCHUCK ] = { {0x00, 0x00, 0xA4, 0x20, 0x00, 0x00}, "Nunchuck", SCENE_NUNCHUCK, [PID_CLASSIC ] = { {0x00, 0x00, 0xA4, 0x20, 0x01, 0x01}, "Classic Controller", SCENE_CLASSIC, [PID_BALANCE ] = { {0x00, 0x00, 0xA4, 0x20, 0x04, 0x02}, "Balance Board", SCENE_DUMP, [PID_GH_GUITAR ] = { {0x00, 0x00, 0xA4, 0x20, 0x01, 0x03}, "Guitar Hero Guitar", SCENE_DUMP, [PID_GH_DRUMS ] = { {0x01, 0x00, 0xA4, 0x20, 0x01, 0x03}, "Guitar Hero World Tour Drums", SCENE_DUMP, [PID_TURNTABLE ] = { {0x03, 0x00, 0xA4, 0x20, 0x01, 0x03}, "DJ Hero Turntable", SCENE_DUMP, [PID_TAIKO_DRUMS] = { {0x00, 0x00, 0xA4, 0x20, 0x01, 0x11}, "Taiko Drum Controller)", SCENE_DUMP,

可见共8 个已知设备:1 个是未知设备的默认项;7 个设备按名字识别;其中 2 个(Nunchuck 与 Classic Controller)拥有定制的"场景(scene)"(即SCENE_NUNCHUCKSCENE_CLASSIC)。其余设备(Balance Board、Guitar Hero 吉他、鼓、DJ Hero 转盘、太鼓)虽然能被 PID 识别,但只使用通用的 DUMP 屏。

这一 PID 表在源码中对应 wii_ec.c 的ecId[PID_CNT]全局数组。每个条目是一个ecId_t结构(定义见 wii_ec.h),包含:

  • id[6]:6 字节 PID 字符串
  • name:友好名称
  • scene:默认场景
  • init:额外的初始化回调(如 uDraw 平板需要)
  • decode:解码函数
  • check:检查(动作)函数
  • calib:软件校准函数
  • show:绘制场景函数
  • keys:按键解释函数

Nunchuck 条目挂接的是nunchuck_decode / nunchuck_msg / nunchuck_calib / nunchuck_show / nunchuck_key,Classic Controller 条目挂接的是classic_decode / classic_msg / classic_calib / classic_show / classic_key——这种"函数指针表"设计让新增设备支持变得非常简单。此外,ecId表中还能看到两个 README 中未列出的条目:PID_NUNCHUCK_R2{0xFF, 0x00, 0xA4, 0x20, 0x00, 0x00},"Nunchuck (rev2)")与PID_CLASSIC_PRO{0x01, 0x00, 0xA4, 0x20, 0x01, 0x01},"Classic Controller Pro"),说明源码较 README 又前进了一步。

屏幕详解

插件基于"场景(scene)"驱动 UI,场景枚举定义在 wii_anal.h:SCENE_SPLASH / SCENE_RIP / SCENE_WAIT / SCENE_DEBUG / SCENE_DUMP / SCENE_CLASSIC / SCENE_CLASSIC_N / SCENE_NUNCHUCK / SCENE_NUNCHUCK_ACC

SPLASH 屏

插件启动时显示 SPLASH 屏。按任意键可立即清除,否则3.5 秒后自动清除并进入扫描状态(对应 wii_anal.c 中的tmo = 3.5 * 1000超时逻辑)。

NUNCHUCK 主屏

连接 Nunchuck 后,主屏显示:

  • 加速度计Accelerometer {X, Y, Z}数值
  • 摇杆Joystick {X, Y}数值
  • 摇杆位置图形
  • 按键Button {C, Z}状态

按键:

  • Left:前往 DUMP 屏
  • Right:前往加速度计屏
  • Up / Down / OK:见下文"Peak Meters(峰值表)"
  • 短按 Back:复位控制器
  • 长按 Back:退出插件

NUNCHUCK 加速度计屏

加速度计屏以波形图方式实时绘制三轴读数。轴的移动方向语义如下表:

移动低值高值
X左 / 右
Y前 / 后
Z下 / 上

要点:

  • 沿某轴平移,会改变该轴的读数;
  • 绕某轴扭转/倾斜,会改变另外两轴的读数;
  • 例如:向左平移(沿 X 轴)影响 X;向左转身(绕 Y 轴旋转)影响 X 和 Z。

源码 wii_ec_nunchuck.c 的nunchuck_showAcc()中实现了带"死区"(dead = 1 << 5)的三段式归一化:中值附近归为中间区,上下极限归为端点,其余按剩余区间均分为 6 档映射到屏幕上的绘图高度。注释里还提到存在滚动显示的代码,但"LCD 刷新率太低,看起来糟透了",因此实际没有启用滚动。

按键:

  • Left:返回 NUNCHUCK 主屏
  • Up:三态切换——
    • Auto-Pause 已禁用 → 启用 Auto-Pause
    • 在一页末尾暂停 → 重新开始扫描
    • Auto-Pause 启用中运行 → 禁用 Auto-Pause
  • Nunchuck-Z:切换暂停
  • Nunchuck-C:切换自动暂停
  • 长按 OK:进入软件校准模式(见下文"校准");在加速度计屏上进入校准只会校准加速度计
  • 短按 OK:退出软件校准模式,并校准 CENTRE(中心)位置
  • 短按 Back:复位控制器
  • 长按 Back:退出插件

CLASSIC 屏

连接 Classic Controller [Pro] 后,屏幕上会绘制一个经典手柄图形:

  • 手柄会跟随控制器事件实时动画(按键按下、摇杆移动、扳机深浅都会反映在图形上);
  • 扫描率设为30fps,但实际 LCD 有一定延迟,所以实际帧率"因人而异(YMMV)"。

按键:

  • Left:前往 DUMP 屏
  • Right:显示模拟量读数(再按一次 Left 隐藏)
  • Up / Down / OK:见"Peak Meters"
  • 短按 Back:复位控制器
  • 长按 Back:退出插件

Classic Controller 的字节解码逻辑在 wii_ec_classic.c 的classic_decode()中。从 6 字节原始数据中拆出 2 个 6 位摇杆、2 个 5 位摇杆、2 个 5 位扳机(trgZL/trgZR)、方向键、A/B/X/YL/R+/-/Home等全部输入。作者注释特别指出:左摇杆比右摇杆多 1 位精度;且trgZL/trgZR在对应数字按键(btnZL/btnZR)触发后仍会继续增长。绘制部分使用了一整套手柄素材图(gfx/images.h 中如img_cc_Mainimg_cc_trg_L1..L4img_cc_pad_UD1img_cc_btn_X1等),根据当前解码值选择性地叠印按键/摇杆/扳机图层。

DUMP 屏

DUMP 屏显示设备原始读数。任何没有专属_decode()等函数的设备,连接后都只会看到这个屏幕。屏幕内容:

  • SID:String ID——来自info表的可读名称
  • PID:Peripheral ID——标识设备的 6 个字节
  • Cal:校准数据——16 字节
  • 底部的六字节十六进制为控制器数据,每个十六进制数字下方是对应的二进制位表示
    • 例:连接 Nunchuck 时按 Z 键,观察最右侧的那一位会翻转

按键:

  • Right:返回控制器专属屏幕(如果有)
  • 短按 Back:复位控制器
  • 长按 Back:退出插件

DUMP 屏绘制函数ec_show()在 wii_ec.c,它把 SID、PID、Cal 与 6 字节读数逐位(bit-by-bit)画出来;ec_key()(同文件 L286-L297)则处理 Right 键返回上一场景的逻辑。这也从源码角度印证了 README 的说法:未知设备(PID_UNKNOWN)的默认场景就是SCENE_DUMP,其show函数同样复用ec_show

Peak Meters(峰值表 / 校准值显示)

在任何带有 Peak/Trough 菜单的控制器专属屏幕上:

  • Up:切换为只显示峰值(peak)
  • Down:切换为只显示谷值(trough)
  • 长按 OK:进入软件校准模式(见下文"校准")
  • 短按 OK:退出软件校准模式 / 校准 CENTRE 位置

状态变量state->hold(见 wii_anal.h)负责记录显示模式:-1表示谷值保持、0表示实时值、+1表示峰值保持。

校准(Calibration)

这是本项目最有价值的部分。原文特别强调:

本项目处理模拟量控制的校准,但目前对加速度计数值没有任何"理解"(yet)。

要点:

  • 数字按键不需要校准
  • 部分校准数据由出厂时计算好并存储在控制器内存(疑似 OTP)中;
  • 每种设备的校准数据解读方式不同——例如 Nunchuck 有 1 个摇杆 + 加速度计,而 Classic Controller 有 2 个摇杆 + 2 个模拟扳机;
  • 作者实测发现出厂校准数据往往不准(猜测控制器常年漂移所致)。若出厂值"限制"了行程,很容易通过在线扩展解决;但如果出厂数据给出的极限比摇杆实际可达范围更远,就需要对控制器做完全重新校准

推荐的校准方法:

  1. 在控制器**静止且水平(at rest)**时采集若干读数;
  2. 将控制器移动到所有极限位置,记录极限 {peak/trough} 值。

据称任天堂会在控制器刚连接后立即取一次"静止"读数,并且同时按住 {A, B, +, -} 至少 3 秒即可随时重新校准(作者没有掌握该操作的具体内部细节)。

本工具的校准流程

  • 控制器首次被识别时:用出厂校准数据决定每个模拟控制的中心/中间位置与极限值(如最左、最右)。
  • 长按 OK(Flipper Zero 上):进入软件校准模式。按下期间不要触碰任何模拟控制器!
    1. 校准图标开始闪烁;
    2. 取当前读数为中心位置
    3. 将范围极限设为"无范围";
    4. 现在你需要把控制器在各个极限之间来回移动,让代码计算出新的校准/范围/peak+trough 值;
    5. 完成后按短按 OK退出软件校准模式。
  • 短按 OK(Flipper Zero 上)按下期间不要触碰任何模拟控制器!
    • 停止校准图标闪烁;
    • 校准所有模拟控制的中心位置(加速度计暂不支持)。

校准的底层实现在每个设备的*_calib()函数中,核心是 wii_ec.h 中定义的一组校准策略位掩码

typedef enum ecCalib { CAL_FACTORY = 0x01, // (re)set to factory defaults CAL_TRACK = 0x02, // track maximum and minimum values seen CAL_RESET = 0x04, // initialise ready for software calibration CAL_RANGE = 0x08, // perform software calibration step CAL_CENTRE = 0x10, // reset centre point of joystick CAL_NOTJOY = 0x20, // do NOT calibrate the joystick } ecCalib_t;

以 Nunchuck 为例(wii_ec_nunchuck.c):

  • CAL_RESET:把 LO 预置为最大值(以便被调低)、HI 预置为(以便被调高),对摇杆(8 位)与加速度计(10 位)分别初始化;
  • CAL_FACTORY:从 16 字节出厂校准数据pec->calF[]恢复各轴中心与 0G/1G 参考点,摇杆则取calF[8..13]中的 X/Y 低-中-高值(Nunchuck 出厂摇杆数据格式为{maxX, minX, midX, maxY, minY, midY},即代码中的FACTORY_HI(joyX, calF[8])FACTORY_LO(joyX, calF[9])FACTORY_MID(joyX, calF[10])等对应关系);加速度计的 0G/1G 参考点则由calF[0..7]按位拼装为 10 位值;
  • CAL_TRACK:持续跟踪实际见到的最大/最小值;
  • CAL_RANGE:在软件校准模式下按用户操作重算范围;
  • CAL_CENTRE:把当前读数写回中心点。

Classic Controller 的校准(wii_ec_classic.c)与之类似,但摇杆量程不同:左摇杆是 6 位(出厂数据>>2对齐),右摇杆是 5 位(出厂数据>>3对齐);扳机trgZL/trgZR出厂校准策略"目前未知",代码里用经验值0x03(下限)与0x1B(中点)兜底。

DEBUG 屏与日志调试

在任何屏幕(SPLASH 除外)按长按 Down即可进入 Debug 模式。

进入 DEBUG 屏后,实时扫描器会停止,此时可用按键:

  • Up:尝试初始化已连接的控制器
  • OK:对控制器采一次读数
  • 长按 Down:重启实时扫描器并返回 WAIT 屏

你可以在任意时刻通过 USB 连接 Flipper Zero 的串口控制台minicomputty等),开启log功能查看调试消息。

日志输出量可在编译期(参见info.sh,它通过grep LOG_LEVEL *.h列出可调整的日志级别宏)或运行期(Flipper 菜单Settings -> System -> LogLevel)进行限制。原文特别注明:这可能在 FAP 支持引入后已经过时——如果遇到内存问题,编译期限制日志可以让插件体积更小;但限制越多,发给日志系统的调试信息就越少。

底层 I2C 协议与已知缺陷

wii_i2c.c头部注释完整记录了 Wii 扩展控制器的寄存器布局(总线地址0x52,读操作后寄存器自动递增):

0x00..0x05 ( 6 bytes) ... [r] 控制器数据 (Controller Data) 0x20..0x2F (16 bytes) ... [r] 校准数据 (Calibration Data) 0x30..0x3F (16 bytes) ... [r] 校准数据副本 0x40..0x4F (16 bytes) ... [w] 加密密钥 0xFA..0xFF ( 6 bytes) ... [r] 外设 ID (Peripheral ID)

对应的常量定义在 wii_i2c.c:regJoy = 0x00regCal = 0x20regEnc = 0x40regPid = 0xFA

值得一提的实现细节(可作扩展控制器开发参考):

  • 轮询模型:主循环通过定时器消息触发ecPoll()(wii_ec.c),它维护一个有限状态机:未初始化则尝试ecInit(),成功发WIIEC_CONN事件;已初始化则ecRead(),设备消失返回 2 发WIIEC_DISCONN事件,读失败返回 3 则暂时忽略(可能只是瞬时问题);
  • 事件驱动ecPoll()产生的WIIEC_CONN / DISCONN / PRESS / RELEASE / ANALOG / ACCEL事件(枚举见 wii_ec.h)与按键事件、定时器事件统一走消息队列,由主循环分发;
  • i2c_workaround:原文档 TODO 提到"写文档时 FZ 的 i2c 函数存在问题"。对应的修复思路记录在 i2c_workaround.h 中:它用宏把furi_hal_i2c_*系列调用替换为furi_hal_Wi2c_*包装函数,每次操作前后显式acquire/release总线;同时提供furi_hal_i2c_trxd()——在"设地址"与"读数据"之间插入furi_delay_us(us)延迟,因为"有些设备需要一点时间才能响应读请求"。相关上游问题为 flipperzero-firmware issue #1670(原文注释提及,此处不展开外链)。

结语

Wii Extension Controller Protocol Analyser 是一份结构清晰、开箱即用的 Flipper Zero 外设协议分析示例:它完整覆盖了"接线 → I2C 识别 → 解码 → 屏幕可视化 → 软件校准 → 串口调试"的整条链路。得益于ecId_t函数指针表的架构(wii_ec.h),扩展新控制器只需在表中新增条目并实现decode/msg/calib/show/keys五个回调。如果你正打算在 Flipper Zero 上折腾 Wii 外设,或想研究裸 I2C 外设的插件开发范式,这个项目的源码(wii_i2c.c、wii_ec.c、wii_ec_nunchuck.c、wii_ec_classic.c)与配套图片资源(_images)都是绝佳的参考资料。需要提醒的是,加密支持、加速度计校准仍是 TODO,接线上电前务必再读一遍接线警告。

【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 16:23:46

斜拉桥主梁横梁计算全攻略:midas Civil建模到验算实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 16:23:05

gs-quant 滚动窗口回归实战:用 RollingLinearRegression 监控参数漂移

gs-quant 滚动窗口回归实战&#xff1a;用 RollingLinearRegression 监控参数漂移 【免费下载链接】gs-quant Python toolkit for quantitative finance 项目地址: https://gitcode.com/GitHub_Trending/gs/gs-quant gs-quant 是 Goldman Sachs 开源的量化金融工具包。其…

作者头像 李华
网站建设 2026/9/13 16:22:28

SSM+JSP+MySQL社保系统实战:参保登记与账户查询全流程

简介&#xff1a;这是一套基于SSM框架与JSP技术实现的、可直接运行的社会保险管理系统源码&#xff0c;适用于计算机专业本科生课程设计与毕业设计实践。系统覆盖参保档案管理、保险缴纳审核、社保金发放、多维信息查询、补缴信息发布及后台系统维护六大核心模块&#xff0c;完…

作者头像 李华
网站建设 2026/9/13 16:21:15

AI Agent跨会话记忆系统实战:从持久化到人格化交互

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 16:21:08

易语言调用Python3全攻略:管道、HTTP与嵌入式方案详解

简介&#xff1a;面向易语言开发者&#xff0c;这份压缩包提供了一套易语言与Python3无缝调用的完整方案&#xff0c;适合需要在现有易语言项目中嵌入爬虫、数据处理或AI功能的场景。包内包含易语言扩展模块与示例工程&#xff0c;配合Python服务端脚本和依赖库&#xff0c;实现…

作者头像 李华
网站建设 2026/9/13 16:19:17

SpringBoot+Vue构建农产品直卖平台技术解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华