手搓 UEFI BIOS 隐藏选项工具:变量修改、鼠标滚轮与界面美化

发布时间:2026/9/17 11:56:28
手搓 UEFI BIOS 隐藏选项工具:变量修改、鼠标滚轮与界面美化 “BIOS 里的隐藏选项”这句话不同人理解差别挺大。有人是想把来电自启打开有人是想把虚拟化开关从灰的弄成可点有人只是想给自己那台老笔记本的 DVMT 显存调大一点。我手上这套“改 BIOS 隐藏选项的工具”最早就是个跑在 UEFI Shell 里的命令行小程序能用但很难用方向键一个个挪改完还得背下偏移。后来我干脆把鼠标驱动自己写进去滚轮也能用了界面又重新画了一遍现在拿起来更像一个正常的设置面板而不是固件里的老古董。这篇就把这套东西从头拆一遍隐藏选项到底存在哪、为什么非得跑在 UEFI 环境里、鼠标驱动怎么把滚轮补上、界面美化在固件里要踩哪些坑以及真正动手改值时的定位、计算和回滚。正在折腾 BIOS 变量、或者手上有台机器某些开关被锁住的人都能从里面拿到能直接抄的做法。1. 先搞清楚要改的是什么隐藏选项藏在哪个角落动手之前先分清对象这一步省下来的时间比后面所有优化都值钱。很多人一上来就找工具结果改了半个月才发现自己盯着的那一项根本不是变量工具再怎么升级也动不了它。1.1 Setup 表、IFR 与 VarStore 三件套主板固件里的设置界面本质是一套 HII 表单。你在 BIOS 里看到的每一个勾选框、下拉菜单都是表单包里的一个 question而 question 的答案并不存在某个配置文件里而是存在 NVRAM 的一段变量里。这段变量通常叫 SetupGUID 由厂商定义属性一般是 NV BS RT也就是掉电保存、启动期可读写、运行时也能读。从表单到变量的中间桥梁是 IFR也就是固件里那段描述界面结构的中间表示。IFR 里会出现 VARSTORE 记录告诉你“这个表单的数据存在哪个变量、从第几个字节开始”紧接着的 ONE_OF、CHECKBOX、NUMERIC 这类操作码里带着 VarOffset 和宽度。厂商想“隐藏”某个选项做法往往不是删掉它而是要么在表单里加一段 SuppressIf 表达式让它不显示要么干脆不给它做界面只留变量里的那一位。两种情况都意味着——界面看不到但变量里的偏移依然存在也依然可写。这就是这类工具能生效的根。典型工作流是这样先用 UEFITool 把固件镜像里的 Setup 模块AMI 一般是 SetupInsyde 常见 SetupUtility翻出来找到模块里的 IFR 段导出成文本再用 IFR 解析工具把那些操作码摊平你会得到一张带 VarStore 编号、VarOffset、宽度、可选值的清单。这张清单就是你后面所有操作的“地图”。注意不同厂商的 Setup 变量 GUID 完全不同同一台机器不同 BIOS 版本之间偏移也可能漂移。升级过 BIOS 之后老的偏移一律当失效处理重新提一遍 IFR。1.2 哪些“隐藏项”根本不是变量工具再改也白搭这是我最想提前说清楚的一段因为坑太深。变量型的隐藏项只是其中一类还有一大票东西躺在固件的模块里或者是别的区域在管跟 Setup 变量一点关系没有。你想改的东西实际存在形式本工具能处理吗来电自启、虚拟化开关、Above 4G、CFG LockSetup 变量里的 1 字节或几位能定位偏移直接写MIPI 屏时序、核显输出配置显卡模块里的 VBT 表不能得替换或重打包模块开机 Logo、默认分辨率固件模块里的资源文件不能网卡白名单、部分传感器校准各模块自己的数据区不能微码、管理引擎相关区域独立固件区域不能且建议别碰EC 管的电源与风扇逻辑嵌入式控制器固件不能拿“把 MIPI 时序导进 VBT”这件事举例VBT 是核显的视频 BIOS 表嵌在显示相关的驱动模块里开机非常早的阶段就被读取跟 Setup 变量是两条平行线。你就算在变量里找到一个名字看起来很像的项写了也不会改变实际时序。判断方法很简单IFR 清单里找不到的一律当成模块级改动别抱幻想。还有一种情况也值得提传统 BIOS 或者 CSM 兼容模式下的老机器压根没有这套 UEFI 变量机制你需要的可能是别的思路。虚拟机上练手也是同理虚拟固件往往做了精简能改的项寥寥无几但用来验证工具本身能不能跑、UI 正不正常非常合适。2. 工具整体设计为什么非要跑在 UEFI 环境里选型这一步我前后试过三条路最后落在“UEFI 应用 内建驱动”这个组合上理由挺实在的不是图新鲜。2.1 为什么不做一个系统下运行的小工具最省事的方案当然是在系统里跑个程序直接读写变量。问题在于系统下能访问的变量是受限制的属性里没有 RT 的变量在系统里根本拿不到句柄就算拿到了很多 Setup 变量在启动早期就已经被各个模块读走并生效你在系统里改完当前这次启动的硬件状态不会跟着变只能等下次冷启动容易让人误判成“没写进去”。更麻烦的是系统下访问底层寄存器通常要内核驱动驱动签名、加载权限又是一堆事。在 UEFI 环境里跑就没有这些绕弯的地方gRT-GetVariable和gRT-SetVariable直接可用变量属性一目了然开机前改完立刻重启就是新值。工具本身只是一个 EFI 应用放在 U 盘的 ESP 分区里Shell 里敲一下就起来干净、可移动、不污染系统。提示这套玩法只适合你自己的设备而且你要清楚自己在承担什么。写错关键偏移导致不开机是完全可能的后面第 5 章会专门讲备份和回滚。2.2 一个应用 自带驱动 分层 UI 的架构拆解模块划分我最后定成四层好处是每一层都能单独测变量层封装 GetVariable / SetVariable按 GUID 加偏移加宽度读写带位域掩码运算和原值缓存。解析层读 IFR 导出文件工具内置一份精简解析或者直接接受预先整理好的偏移表把 VarStore 编号映射到实际 GUID。输入层自建鼠标驱动产出“位移 左键 滚轮增量”这样一份统一事件。表现层GOP 上画界面双缓冲一帧一次 Blt。编译上它是一个标准 UEFI 应用模块类型 UEFI_APPLICATION入口 UefiMain.inf 里声明依赖 ProtocolGOP、Simple Pointer、USB IO 视需要用 EDK2 的构建脚本出一条build -a X64 -t VS2019 -p xxx.dsc就能出 .efi。有个细节值得提醒固件环境里没有 C 运行库没有 malloc、没有 printf、没有浮点运算字符串处理要自己写或者用 MdePkg 里的 BaseMemoryLib 和 BaseLib。浮点尤其要注意别在动画插值里写x * 0.8f老老实实用整数位移。3. 自带鼠标驱动滚轮是怎么一点点补上去的这是这次改造里最花时间的部分也是最容易被人忽略的部分。很多人以为“UEFI 协议里没有滚轮”其实不完全对坑比这句话更细。3.1 官方指针协议为什么“看起来”没有滚轮UEFI 规范里的 EFI_SIMPLE_POINTER_MODE 结构其实是有 RelativeMovementZ 这个字段的字面上就是第三维的位移。问题出在实现固件里那个 USB 鼠标驱动在处理 HID 报告时一般只解析 X、Y 和左右键Z 字段填 0PS/2 触摸板那条路径更简单压根不处理滚轮扩展包。所以从应用角度看你拿到的就是“两轴 两个键”滚轮信息在源头上就被丢掉了。也就是说想让滚轮可用不能指望现成协议把数据喂给你得自己在更底层把报告抢过来解析。这就是“干脆自带鼠标驱动”这句话的真实含义——不是重写一遍指针协议而是补上官方驱动丢掉的那几个字节。3.2 USB 鼠标读 HID 报告描述符把 Wheel 那一位捞出来USB 这条路径相对规范。思路是通过 EFI_USB_IO_PROTOCOL 拿到接口先取 HID 报告描述符把描述符解析成一张“字段表”从中找出 Usage Page 0x01、Usage 0x38Wheel那一项记下它的 ReportID、ReportSize、ReportCount 和它在报告里的比特偏移然后对中断 IN 端点发起异步中断传输在回调里按字段表切比特把滚轮增量取出来。/* 伪代码报告描述符解析后的字段定位 */ typedef struct { UINT8 ReportId; UINT8 BitOffset; UINT8 BitSize; BOOLEAN IsWheel; /* Usage Page 0x01 Usage 0x38 */ BOOLEAN IsHorizontal; /* Usage 0x238横向滚轮 */ } HID_FIELD; /* 回调里按字段切比特注意有符号扩展 */ INT8 WheelDelta(const UINT8 *report, const HID_FIELD *f) { UINT32 raw 0; UINT8 i, byteOff f-BitOffset 3; for (i 0; i f-BitSize; i) { UINT8 bit (report[byteOff ((f-BitOffset i) 3)] ((f-BitOffset i) 7)) 1; raw | (UINT32)bit i; } if (f-BitSize 8 (raw (1u (f-BitSize - 1)))) raw | ~((1u f-BitSize) - 1); return (INT8)raw; }这里有两个特别容易翻车的点。一是报告描述符里的偏移是连续比特计算的不是字节对齐你按字节去切十有八九读到的是别的字段二是滚轮字段是有符号的必须做符号扩展否则往前滚和往后滚会变成同一个值。我第一版就是漏了符号扩展界面里表现成“只能往下滚”查了半天。3.3 PS/2 与触摸板老接口的滚轮要“握手”才出现PS/2 这条路径更像考古。标准 PS/2 鼠标默认只发三个字节的包第四个字节里的滚轮信息需要先跟设备“谈”一下依次改采样率到 200、100、80再读设备 ID如果回读是 0x03说明对方支持扩展包从此它会发四个字节。第四字节的低四位是滚轮和按键 4、5 的符号位高四位丢掉。# 概念流程8082 端口侧 0xA8 # 启用辅助设备 0xF3 200 # 采样率 200 0xF3 100 # 采样率 100 0xF3 80 # 采样率 80 0xF2 # 读设备 ID期望 0x03 0xF4 # 开启数据上报触摸板要更麻烦一些不同厂商的初始化序列完全不一样有的要先进厂商模式再退出来。我在这块没有死磕做成的策略是能识别到扩展包就用识别不到就退化成两轴别让一个触摸板把整个工具卡住。实测下来外接 USB 鼠标的用户体验是最稳的触摸板当成加分项就好。3.4 事件回调与自定义协议把数据交给 UI 层数据拿到之后怎么送到 UI 层也有讲究。我走的是“自定义协议 事件”的路子驱动在 DXE 阶段注册一个自己的 GUID 协议把“读一次鼠标状态”这个方法暴露出去应用侧在合适的 TPL 下轮询或者挂事件通知。/* 应用侧创建一个周期事件在回调里取增量并交给 UI */ gBS-CreateEvent(EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_CALLBACK, OnMouseTick, NULL, gMouseTickEvent); gBS-SetTimer(gMouseTickEvent, TimerPeriodic, 100000); /* 10ms */这里有三个实操要点。第一回调运行在 TPL_CALLBACK不要在回调里做重绘把增量记进队列等主循环空闲时再画第二中断传输回调的上下文和主循环不是一个世界共享数据要用简单的标志位或者环形缓冲别指望锁第三采样周期 10ms 是个比较舒服的值太快了 CPU 空转太慢了拖影明显。用 100000 这个 100ns 单位计时别写成毫秒。4. 界面美化在 800x600 的帧缓冲上做出“不像固件”的观感固件里的界面很难看很大一部分原因不是画不出来而是大家默认“能跑就行”。这次我把 UI 重画了一遍总结下来就是三件事少闪烁、别糊、有反馈。4.1 帧缓冲、Blt 与双缓冲先把闪烁干掉UEFI 下画图靠 GOP。最基本的做法是把像素直接写进线性帧缓冲但你要先拿到正确的行跨度。很多人下意识用“水平分辨率 × 4”算一行字节数结果画面是斜的、错位的原因就在这里——行跨度要用Mode-Info-PixelsPerScanLine它在显存里对齐后可能大于水平分辨率。EFI_GRAPHICS_OUTPUT_PROTOCOL *gop; EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info; gop-QueryMode(gop, gop-Mode-Mode, SizeOfInfo, info); UINTN stride info-PixelsPerScanLine; /* 不要用 HorizontalResolution */ UINTN strideBytes stride * 4; /* BGRx32bpp */ UINT32 *frame (UINT32 *)gop-Mode-FrameBufferBase;双缓冲的做法是在内存里自己开一块同尺寸的缓冲所有绘制都往这块写一帧画完用一次 Blt 把整块拷到显存。Blt 次数是性能关键不要每个控件单独 Blt 一次那样在低端机上肉眼可见地撕裂。另外内存分配记得用gBS-AllocatePool(EfiBootServicesData, ...)用完 FreePool别把自己塞进运行时内存里。4.2 字体、圆角、渐变都是手搓出来的固件里没有 CSS所有“好看”都是算出来的。字体这块有两条路用标准的 HII 字体协议渲染字符串省事但受限于固件自带字库的样式或者自己嵌点阵字库ASCII 用 8x16中文用 16x16好处是风格统一、颜色可控代价是体积。中文点阵全字库相当大我的做法是只嵌界面文案里出现过的那些字几百个字符就够几百 KB 以内可以接受。圆角和渐变都是逐像素算的。圆角用距离判断在角部区域内判断像素到圆心的距离是否超过半径超过就不画。渐变用整数插值/* 竖直渐变y 从 0 到 h颜色从 c0 线性插到 c1 */ UINT32 Lerp(UINT32 c0, UINT32 c1, UINT32 y, UINT32 h) { UINT32 w0 ((h - y) * 256) / h; UINT32 w1 256 - w0; UINT32 r ((((c0 16) 0xFF) * w0 ((c1 16) 0xFF) * w1) 8); UINT32 g ((((c0 8) 0xFF) * w0 ((c1 8) 0xFF) * w1) 8); UINT32 b ((( c0 0xFF) * w0 ( c1 0xFF) * w1) 8); return (r 16) | (g 8) | b; }全是整数运算不用浮点也不用除法以外的数学库。想再讲究一点可以在圆角边缘做一层 2x2 或 4x4 的超采样边缘就不会有锯齿。代价是每次重绘的计算量上去一点只在控件首次绘制时算一次、结果缓存起来就没问题了。4.3 交互细节指针、焦点、滚动条的“手感”光标绘制最忌讳全屏重绘。我的做法是脏矩形先把指针即将覆盖的那块背景存下来移动时把旧的贴回去再在新位置画指针。这样一帧只动两个小矩形比整屏重绘快一个数量级。指针本身可以做成带一点阴影的箭头边缘柔化一下观感立刻就上去了。焦点和滚动的“手感”靠三件事堆起来焦点框要有明显但克制的对比别用大红大绿列表滚动的滚轮增量要按比例放大我用的是一格滚轮对应三行滑起来不费劲滚动条要有“轨道 滑块”滑块长度跟内容比例对应让人一眼知道自己在哪。这些都不是必须的但做过之后用户就分不清这是固件还是应用了。注意别把动画做得太重。固件环境下没有 GPU全屏渐变动画在低端机上会明显掉帧。做个三帧的淡入淡出就够了。5. 实操把工具跑起来把想改的那一项改掉前面讲的都是“怎么造工具”这一章讲“怎么用工具把事办成”。流程我跑过很多遍按这个顺序基本不会出岔子。5.1 准备 U 盘与 ESP先把风险讲透准备一个 U 盘格式化成 FAT32建一个EFI/BOOT目录把 Shell 和工具放进去再写一个startup.nsh让它开机自动跑# startup.nsh fs0: UefiSetupTool.efi -ifr fs0:\Setup_IFR.txt如果你不想每次敲路径这几行就够了。启动时通过一次性启动菜单选 U 盘即可。顺手提醒一句动手前把原始变量值导出存一份用dmpstore -guid 你的 Setup GUID -s fs0:\setup.bak之类的方式具体参数看你手上的 Shell 版本dmpstore -?先看一眼帮助。另外把当前 BIOS 版本的官方固件文件也下载一份放好万一需要重新刷写你手边得有东西。5.2 从固件里提 IFR定位你要改的那一项先在固件镜像里找出 Setup 模块导出它的 IFR 段成文本然后用解析工具摊平你会得到类似这样的条目VarStore: 0x1 Name: CpuSetup GUID: {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} Question: CFG Lock VarOffset: 0x10F Size: 1 Byte Type: OneOf Option: Disabled 0x00 Option: Enabled 0x01这就是你要的全部信息变量 GUID、偏移、宽度、可选值。把它抄进工具的偏移表或者直接让工具去解析这份 IFR 文本。同一份 IFR 里往往会同时暴露好几个你想要的项一次性都记下来省得来回重启。5.3 计算偏移与写入值包括位域那点事整字节的项最简单读到旧值确认一下写新值再读回来验证。真正需要小心的是一个字节里塞了好几个开关的情况比如某几位控制超线程、某几位控制电源状态。这时候不能直接写整字节得先算掩码/* 把 old 的第 shift 位起的 width 位替换成 val其余位保持不动 */ UINT8 PatchBits(UINT8 old, UINT8 val, UINT8 shift, UINT8 width) { UINT8 mask (UINT8)(((1u width) - 1) shift); return (UINT8)((old ~mask) | ((val shift) mask)); }写的时候还有两条经验写入前一定先 GetVariable 把原值存下来写入后立刻再读一次确认别相信“调用没报错就是成功了”有些变量带 Attribute 校验或者有别的模块会在启动早期重写如果发现重启后值又变回去了说明它被别的地方管控不是你写错偏移这种情况就别硬刚了。5.4 验证、生效与回滚写完值之后有个特别容易被忽略的点不是所有项都靠热重启生效。有些开关在非常早的阶段就被读取改完必须彻底断电一次等几秒再上电也就是冷启动。我习惯的做法是改完先记下来然后关机、拔电源笔记本的话把电池相关都断掉、再开机看效果。如果有项改了没反应先怀疑“没冷启动”再怀疑偏移写错。回滚路线要提前想好按严重程度排值写错但还能进系统的工具里改回去就行改完不开机的清 CMOS、扣主板电池、短接触点或者用双 BIOS、恢复按钮这类机制最坏的情况你得动用编程器那就是另一个话题了。所以还是那句老话动之前先备份只改你能说清楚后果的项。6. 踩坑记录那些文档里不会写的东西工具从命令行版到现在的版本我踩过的坑基本都集中在这几类整理成表方便对症下药。现象更可能的原因处理方式工具里改了值重启又变回去偏移对应的变量不对或有其他模块每次启动重写核对 VarStore GUID 与偏移换一个变量试若被管控就放弃改完黑屏、提示 CMOS 校验错误写坏了关键项清 CMOS、断电、恢复默认用备份值写回滚轮一点反应没有报告描述符里没有 Wheel 字段或比特偏移算错打印字段表核对 Usage Page 0x01 / Usage 0x38检查符号扩展画面撕裂、鼠标拖动带残影直接写帧缓冲、没双缓冲、Blt 次数过多改成内存缓冲一次 Blt指针用脏矩形恢复界面中文显示成方块字库编码与字符串编码不一致或缺字统一用 UTF-16 或 GBK确认字库覆盖率鼠标完全不动事件没触发、TPL 不对、驱动没拿到设备检查事件通知类型与周期确认 USB 接口已被绑定工具直接加载失败安全启动策略不允许未签名模块这属于设备安全设置范畴自行评估是否调整风险自负选项在界面里看不到但能写被 SuppressIf 隐藏属于正常现象不用纠结界面直接按偏移写几条不在表里的心得我觉得比表本身更值钱。第一先在虚拟机里把工具跑通虚拟固件的项虽然少但 UI、鼠标、事件这套链路能不能跑是能验证的省得在真机上反复插拔 U 盘。第二偏移表要版本化管理每台机器、每个 BIOS 版本一份标注日期和来源升级 BIOS 后第一时间重新提 IFR 更新不然哪天你会对着两套数不清谁是谁的偏移发呆。第三改值前后各截一张工具界面的图存下来出问题时这就是最直接的证据链。最后再分享一个我自己觉得最实用的扩展方向现在这套界面已经能跑鼠标和滚轮了下一步我想把“配置档”做进去把某台机器需要改的那几项存成一个小文件插上 U 盘工具自动读出来逐项比对、显示差异、一键应用。这样折腾两台以上同型号机器的时候就不用再对着偏移表一个个敲了。真做出来之后再回来聊。