也玩

我朝着光明走,却走向深渊。

希望这首歌是你喜欢的歌,希望这句话是你喜欢的话,希望一些都如你所愿


机械革命开机动画固件制作

之前折腾机械革命新版开机动画时,发现一个比较有意思的问题:

部分机械革命官方 BIOS 里面其实已经包含新版开机动画,但正常运行官方 BIOS 更新程序后,ROM Hole 并没有被刷新,所以机器还是显示旧版动画。

后来通过分析 AMI AFU 的 UCP 封装,发现可以在完全不修改官方 BIOS @ROM 内容的情况下,只修改更新程序携带的刷写参数,让 AFU 单独执行:

/L

也就是:

Program all ROM Holes

这样就可以生成对应的:

ROMHOLES_DIRECT.EXE

本文记录一下具体怎么手动制作。

本文以 XxRPxxxxN135MRO58_CAP.EXE 为例。

不同 BIOS 的 Offset、模块大小、原始命令可能不同,不能机械照抄本文中的十六进制地址。

真正通用的是 UCP 的结构和修改思路。


先说原理

机械革命这类 AMI Aptio BIOS 更新 EXE,本质上可以看成:

Windows PE 程序
+
AMI UCP 数据

UCP 部分通常类似:

@UAF
├── @UII
├── @W64
├── @CMD
└── @ROM

其中最重要的是两个模块:

@CMD

存放 AFU 启动时自动执行的参数。

以及:

@ROM

存放真正要刷入的官方 BIOS 固件。

比如这个官方更新包里的 @CMD 是:

/p /b /n /r /x /capsule

而我们想做的 ROM Hole 更新程序,只需要:

/L

所以整个修改过程的核心其实就是:

官方 CAP.EXE
        ↓
保留 @ROM 完全不变
        ↓
把 @CMD 改成 /L
        ↓
重新计算 UCP checksum
        ↓
生成 ROMHOLES_DIRECT.EXE

这样不会修改 BIOS 固件本身,只改变 AFU 对官方固件的刷写方式。


为什么不能直接修改 BIOS?

如果 BIOS 使用了 AMI Secure Flash,那么修改 @ROM 中的 BIOS 内容可能会导致数字签名失效。

之前测试过修改 BIOS 内部动画资源的固件,结构检查全部能够通过:

FFS Checksums ................. Pass
Check ROMLayout ............... Pass
ROM File Size checking ........ Pass
ROM ID Checking ............... Pass
ROM File Verification Status .. Pass

但真正刷写时仍然可能出现:

18 - Error: Secure Flash Rom Verify fail.

所以,如果官方 BIOS 本身已经包含新版动画,最理想的办法就是:

完全不要修改 @ROM

只修改 AFU 的 /L 参数。


一、需要准备的软件

推荐准备:

HxD

或者:

010 Editor

用于查看和修改十六进制文件。

还建议准备:

Python 3

后面计算 checksum 会方便很多。

本文示例文件:

XxRPxxxxN135MRO58_CAP.EXE

二、先确认官方 BIOS 中确实有新版动画

这一点非常重要。

不是所有 BIOS 都已经放入新版动画。

我们之前确认过,机械革命新版动画对应的 FFS GUID 是:

1FB553B5-73E8-4290-9B95-B75D01D4CC9A

新版资源为:

800 × 600
GIF
60 帧
20 ms / 帧
总时长约 1.2 秒

如果官方 BIOS 里面这个位置只有:

800 × 600
1 帧 GIF

那即使执行 /L,最终也只会刷入静态 Logo。

所以:

官方 BIOS 本身有新版动画
→ 可以考虑做 ROMHOLES_DIRECT.EXE

官方 BIOS 本身没有新版动画
→ 单纯改 /L 没有意义

本文这个:

XxRPxxxxN135MRO58_CAP.EXE

里面已经确认存在新版 60 帧动画。


三、找到真正的 @UAF

用 HxD 打开:

XxRPxxxxN135MRO58_CAP.EXE

搜索 ASCII:

@UAF

需要注意:

文件内部可能出现不止一次类似字符串。

真正的 UCP overlay 一般位于 EXE 后部,并且后面能够连续解析出:

@UII
@W64
@CMD
@ROM

在本文这个文件中:

@UAF Offset:
0x119600

模块分布:

@UAF   0x119600
@UII   0x119610
@W64   0x119670
@CMD   0x11EF90
@ROM   0x11EFC0

可以理解成:

0x119600  @UAF
             │
             ├── @UII
             ├── @W64
             ├── @CMD
             └── @ROM

四、找到 @CMD

搜索:

@CMD

本文示例真正需要修改的位置是:

0x11EF90

原始十六进制:

40 43 4D 44 30 00 00 00 A7 11 00 00 00 00 00 00
17 00 00 00 17 00 00 00 2F 70 20 2F 62 20 2F 6E
20 2F 72 20 2F 78 20 2F 63 61 70 73 75 6C 65 00

其中:

40 43 4D 44

转换成 ASCII 就是:

@CMD

后面的:

2F 70 20 2F 62 20 2F 6E 20 2F 72 20 2F 78 20 2F
63 61 70 73 75 6C 65

就是:

/p /b /n /r /x /capsule

五、理解 @CMD 模块结构

这个模块大致可以拆成:

Offset +00   4 bytes   "@CMD"
Offset +04   4 bytes   Module Size
Offset +08   2 bytes   Checksum
Offset +0A   2 bytes   Reserved
Offset +0C   4 bytes   Reserved

Offset +10   4 bytes   Command Length
Offset +14   4 bytes   Command Length
Offset +18   ...       Command String

原来的命令:

/p /b /n /r /x /capsule

长度为:

23 bytes

也就是:

0x17

所以这里可以看到:

17 00 00 00
17 00 00 00

@CMD 模块总大小:

0x30

即:

48 bytes

六、把 @CMD 改成 /L

我们最终需要 AFU 执行:

/L

对应 ASCII:

2F 4C

长度:

2 bytes

所以两个 Command Length 都要变成:

02 00 00 00

字符串部分:

2F 4C 00

其中最后:

00

是字符串结束符。

再按照 16-byte alignment 补零。

新的完整 @CMD

40 43 4D 44 20 00 00 00 20 2C 00 00 00 00 00 00
02 00 00 00 02 00 00 00 2F 4C 00 00 00 00 00 00

解释一下:

40 43 4D 44

即:

@CMD

模块大小:

20 00 00 00

也就是:

0x20

新的 checksum:

20 2C

也就是:

0x2C20

两个命令长度:

02 00 00 00
02 00 00 00

最后:

2F 4C

就是:

/L

七、不能只把原字符串覆盖成 /L

这是整个过程中最容易出错的地方。

原来的 @CMD

0x30 bytes

新的:

0x20 bytes

少了:

0x10

也就是:

16 bytes

所以不能简单地:

/p /b /n /r /x /capsule

直接改成:

/L

然后让后面的数据留在那里。

正确做法是:

重新构造整个 @CMD module。

因此原始位置:

@CMD  0x11EF90
@ROM  0x11EFC0

修改之后:

@CMD  0x11EF90
@ROM  0x11EFB0

也就是说:

@ROM

整体向前移动:

0x10

字节。

注意:

Offset 变化不代表 @ROM 内容发生变化。

只是因为前面的 @CMD 变短了。


八、修改 @UAF 总大小

因为整个 UCP 少了:

0x10

字节,所以最外层:

@UAF

记录的 Size 也必须修改。

原始 @UAF Header:

40 55 41 46 D0 20 92 00 0F 43 10 00 FF FF FF FF

其中:

40 55 41 46

就是:

@UAF

原始 Size:

D0 20 92 00

按照 Little Endian:

0x9220D0

因为我们少了:

0x10

所以新的 Size 是:

0x9220C0

写入文件:

C0 20 92 00

九、重新计算 @CMD Checksum

AMI UCP 这里使用的是:

16-bit Little Endian Additive Checksum

要求整个模块所有 16-bit word 相加后满足:

sum & 0xFFFF == 0

计算时先把 checksum 字段清零:

@CMD + 0x08

设成:

00 00

然后按 Little Endian WORD 对整个 module 求和。

最终:

checksum = (-sum) & 0xFFFF

本文新的 /L 模块得到:

0x2C20

所以写入文件:

20 2C

十、重新计算 @UAF Checksum

最外层:

@UAF

也有自己的 16-bit checksum。

位置:

@UAF + 0x08

本文原始:

0F 43 10 00

修改后:

1F 43 10 00

也就是 checksum:

0x430F

变成:

0x431F

注意:

10 00

属于后面的字段,不要一起修改。

因此新的 @UAF Header:

40 55 41 46 C0 20 92 00 1F 43 10 00 FF FF FF FF

十一、最重要的一步:确认 @ROM 一个字节都没变

制作完成以后,必须检查:

@ROM

的 SHA-256。

原始文件:

@ROM Offset:
0x11EFC0

@ROM Size:
0x91C710

修改以后:

@ROM Offset:
0x11EFB0

@ROM Size:
0x91C710

虽然位置向前移动了:

0x10

但是 @ROM 内容必须完全一样。

本文这个官方 @ROM 的 SHA-256:

514cce8bceb61fb57b8d3fdb15ef55fb41dd55555e3b72e9a3e2dba51dc2e16d

所以生成前:

514cce8bceb61fb57b8d3fdb15ef55fb41dd55555e3b72e9a3e2dba51dc2e16d

生成后也必须是:

514cce8bceb61fb57b8d3fdb15ef55fb41dd55555e3b72e9a3e2dba51dc2e16d

如果两者不同:

不要运行生成的 EXE。


十二、检查最终文件大小

原始官方文件:

10,729,168 bytes

修改后的文件:

10,729,152 bytes

刚好减少:

16 bytes

也就是:

0x10

这与:

@CMD
0x30
↓
0x20

完全对应。


十三、最终得到的结构

修改前:

@UAF
├── @UII
├── @W64
├── @CMD
│   └── /p /b /n /r /x /capsule
└── @ROM
    └── 官方 BIOS

修改后:

@UAF
├── @UII
├── @W64
├── @CMD
│   └── /L
└── @ROM
    └── 官方 BIOS,完全不变

所以这个:

ROMHOLES_DIRECT.EXE

实际上并不是“修改版 BIOS”。

更准确地说,它是:

使用官方原始 BIOS,但改变 AFU 刷写区域选择的更新程序。


十四、为什么 /L 可以更新开机动画?

AMI AFU 对 /L 的定义是:

Program all ROM Holes

也就是刷新 BIOS 中的 ROM Hole。

机械革命新版开机动画就在对应 ROM Hole 内。

我们之前确认的动画 GUID:

1FB553B5-73E8-4290-9B95-B75D01D4CC9A

如果官方 BIOS 已经包含新版:

800 × 600
60 帧 GIF

但正常刷 BIOS 以后机器还是旧动画,那么很可能就是:

正常 BIOS Region 已更新
        ↓
ROM Hole 没刷新
        ↓
Flash 中仍然保留旧动画

执行:

/L

之后:

官方 BIOS 的 ROM Hole
        ↓
写入 SPI Flash
        ↓
新版开机动画

十五、哪些情况下不能这样做?

这个方法不是万能的。

至少要满足:

1. 官方 BIOS 使用 AMI UCP 封装

2. EXE 中存在 @UAF / @CMD / @ROM

3. 官方 @ROM 本身已经包含新版动画

4. AFU 支持 /L

5. ROM Hole 与当前机器平台匹配

如果官方 BIOS 本身只有:

1 帧静态 Logo

那么:

/L

只会把那个静态 Logo 刷进去。

不会自动变成新版动画。

另外,不同平台不能混用:

X6AR45xU
XxMT4xxx
XxRPxxxx
X6AR5xxx

虽然 BIOS 的某些结构可能很像,但并不代表 ROM 可以跨平台刷。


十六、可以用 Python 自动制作

如果不想每次都用 HxD 手工修改,可以直接用下面这个 Python 脚本自动完成。

脚本会做这些事情:

读取原厂 EXE
↓
寻找真正的 @UAF
↓
解析 @UII / @W64 / @CMD / @ROM
↓
读取原始 @CMD
↓
重建成 /L
↓
重新计算 @CMD checksum
↓
调整 @UAF size
↓
重新计算 @UAF checksum
↓
确认 @ROM 完全没有变化
↓
生成 ROMHOLES_DIRECT.EXE

其中最重要的一项保护是:

如果脚本发现生成后的 @ROM 和原始 @ROM 有任何一个字节不同,就会直接报错,并拒绝生成输出文件。

脚本如下:

#!/usr/bin/env python3
from __future__ import annotations

import argparse
import hashlib
import struct
from pathlib import Path

ALIGN = 0x10


def u32(b: bytes | bytearray, off: int) -> int:
    return struct.unpack_from('<I', b, off)[0]


def p32(b: bytearray, off: int, v: int) -> None:
    struct.pack_into('<I', b, off, v)


def p16(b: bytearray, off: int, v: int) -> None:
    struct.pack_into('<H', b, off, v)


def sum16le(data: bytes | bytearray) -> int:
    if len(data) & 1:
        data = bytes(data) + b'\x00'

    return sum(
        struct.unpack(
            '<' + 'H' * (len(data) // 2),
            data
        )
    ) & 0xFFFF


def fix_low16_checksum(blob: bytearray, checksum_off: int) -> None:
    # UCP 的 +8 位置低 16 bit 使用 16-bit additive checksum。
    # 保留 DWORD 的高 16 bit。
    p16(blob, checksum_off, 0)

    p16(
        blob,
        checksum_off,
        (-sum16le(blob)) & 0xFFFF
    )

    if sum16le(blob) != 0:
        raise RuntimeError('checksum calculation failed')


def find_uaf(data: bytes) -> int:
    # 寻找 size 字段刚好延伸到文件结尾的 @UAF。
    # 这样可以避免误匹配 PE 主体中的 "@UAF" 字符串。

    pos = 0
    candidates = []

    while True:
        pos = data.find(b'@UAF', pos)

        if pos < 0:
            break

        if pos + 16 <= len(data):
            size = u32(data, pos + 4)

            if size >= 16 and pos + size == len(data):
                candidates.append(pos)

        pos += 1

    if len(candidates) != 1:
        raise RuntimeError(
            f'expected exactly one terminal @UAF, '
            f'found {len(candidates)}: {candidates}'
        )

    return candidates[0]


def parse_modules(data: bytes | bytearray, uaf_off: int):
    uaf_size = u32(data, uaf_off + 4)

    p = uaf_off + 16
    end = uaf_off + uaf_size

    mods = []

    while p < end:

        if p + 16 > end:
            raise RuntimeError('truncated module header')

        tag = bytes(data[p:p + 4])
        size = u32(data, p + 4)

        if (
            size < 16
            or size % ALIGN
            or p + size > end
        ):
            raise RuntimeError(
                f'bad module at 0x{p:X}: '
                f'tag={tag!r}, size=0x{size:X}'
            )

        mods.append((tag, p, size))

        p += size

    if p != end:
        raise RuntimeError(
            'module parsing did not end at UAF boundary'
        )

    return mods


def sha256(x: bytes | bytearray) -> str:
    return hashlib.sha256(x).hexdigest()


def build_cmd_module(old: bytes, command: str) -> bytes:

    cmd = command.encode('ascii')

    if b'\x00' in cmd:
        raise ValueError(
            'command may not contain NUL'
        )

    # 结构:
    #
    # 16-byte module header
    # +
    # 两个 DWORD command length
    # +
    # NUL 结尾的命令字符串
    # +
    # 16-byte alignment padding

    raw_len = 24 + len(cmd) + 1

    new_size = (
        raw_len + ALIGN - 1
    ) & ~(ALIGN - 1)

    m = bytearray(new_size)

    # 保留原始 module header
    m[:16] = old[:16]

    m[0:4] = b'@CMD'

    # 更新 module size
    p32(m, 4, new_size)

    # +8 的低 WORD 是 checksum。
    # +0xA 的高 WORD / reserved 保持原值。
    p16(m, 8, 0)

    # 两个 command length
    p32(m, 16, len(cmd))
    p32(m, 20, len(cmd))

    # command
    m[24:24 + len(cmd)] = cmd

    # NUL terminator
    m[24 + len(cmd)] = 0

    # 修复 @CMD checksum
    fix_low16_checksum(m, 8)

    return bytes(m)


def main() -> None:

    ap = argparse.ArgumentParser(
        description=(
            'Convert an AMI UCP CAP EXE to a /L '
            'ROM-Holes-only variant without modifying @ROM.'
        )
    )

    ap.add_argument(
        'input',
        type=Path
    )

    ap.add_argument(
        'output',
        type=Path
    )

    ap.add_argument(
        '--command',
        default='/L',
        help='replacement @CMD string (default: /L)'
    )

    args = ap.parse_args()

    # 读取原始 EXE
    original = args.input.read_bytes()

    # 找到真正的 @UAF
    uaf = find_uaf(original)

    # 解析 UCP modules
    mods = parse_modules(
        original,
        uaf
    )

    by_tag = {
        tag: (off, size)
        for tag, off, size in mods
    }

    if (
        b'@CMD' not in by_tag
        or b'@ROM' not in by_tag
    ):
        raise RuntimeError(
            'required @CMD/@ROM modules not found'
        )

    cmd_off, cmd_size = by_tag[b'@CMD']
    rom_off, rom_size = by_tag[b'@ROM']

    old_cmd = original[
        cmd_off:
        cmd_off + cmd_size
    ]

    old_rom = original[
        rom_off:
        rom_off + rom_size
    ]

    # 检查原始 module checksum
    if sum16le(old_cmd) != 0:
        raise RuntimeError(
            'original @CMD checksum is not valid'
        )

    if sum16le(old_rom) != 0:
        raise RuntimeError(
            'original @ROM checksum is not valid'
        )

    # 读取原始命令字符串
    old_len1 = u32(old_cmd, 16)
    old_len2 = u32(old_cmd, 20)

    old_text = old_cmd[
        24:
        24 + min(old_len1, old_len2)
    ].decode(
        'ascii',
        'replace'
    )

    # 构建新的 /L @CMD
    new_cmd = build_cmd_module(
        old_cmd,
        args.command
    )

    # 替换整个 @CMD module。
    #
    # 允许 @CMD 变短或变长,
    # 所以后面的 @ROM Offset 会自动移动。
    out = bytearray(
        original[:cmd_off]
        + new_cmd
        + original[cmd_off + cmd_size:]
    )

    delta = len(new_cmd) - cmd_size

    # @UAF 位于 @CMD 前面,
    # 所以 @UAF 自己的 Offset 不变。
    old_uaf_size = u32(
        original,
        uaf + 4
    )

    p32(
        out,
        uaf + 4,
        old_uaf_size + delta
    )

    # 重新计算 @UAF 的低 16-bit checksum,
    # 保留高 16-bit metadata。
    uaf_blob = bytearray(
        out[uaf:]
    )

    fix_low16_checksum(
        uaf_blob,
        8
    )

    out[uaf:] = uaf_blob

    # ==============================
    # Validation
    # ==============================

    # @UAF Size 必须刚好到 EOF
    if (
        uaf + u32(out, uaf + 4)
        != len(out)
    ):
        raise RuntimeError(
            'new @UAF size does not reach EOF'
        )

    # 重新解析修改后的 modules
    new_mods = parse_modules(
        out,
        uaf
    )

    # 检查每个 module checksum
    for tag, off, size in new_mods:

        if sum16le(
            out[off:off + size]
        ) != 0:

            raise RuntimeError(
                f'bad checksum after rebuild: {tag!r}'
            )

    # 检查整个 @UAF checksum
    if sum16le(out[uaf:]) != 0:
        raise RuntimeError(
            'bad @UAF checksum after rebuild'
        )

    # 重新寻找 @ROM
    new_rom_entry = next(
        (
            x
            for x in new_mods
            if x[0] == b'@ROM'
        ),
        None
    )

    if not new_rom_entry:
        raise RuntimeError(
            '@ROM disappeared after rebuild'
        )

    _, new_rom_off, new_rom_size = (
        new_rom_entry
    )

    new_rom = out[
        new_rom_off:
        new_rom_off + new_rom_size
    ]

    # 最重要的保险:
    # @ROM 必须逐字节完全不变。
    if new_rom != old_rom:
        raise RuntimeError(
            '@ROM changed; refusing to write output'
        )

    # 所有验证通过后才写入输出文件
    args.output.write_bytes(out)

    print(
        f'Input:          {args.input}'
    )

    print(
        f'Output:         {args.output}'
    )

    print(
        f'@UAF offset:    0x{uaf:X}'
    )

    print(
        f'Old @CMD:       {old_text!r}'
    )

    print(
        f'New @CMD:       {args.command!r}'
    )

    print(
        f'@CMD size:      '
        f'0x{cmd_size:X} -> '
        f'0x{len(new_cmd):X}'
    )

    print(
        f'File size:      '
        f'{len(original)} -> '
        f'{len(out)} '
        f'({delta:+d})'
    )

    print(
        f'@ROM SHA-256:   '
        f'{sha256(old_rom)} '
        f'(unchanged)'
    )

    print(
        f'Output SHA-256: '
        f'{sha256(out)}'
    )

    print(
        'Validation:     PASS'
    )


if __name__ == '__main__':
    main()

使用方法

把上面的代码保存成:

make_romholes_direct.py

然后把 Python 脚本和原厂 BIOS EXE 放在同一个目录。

例如:

BIOS\
├── make_romholes_direct.py
└── XxRPxxxxN135MRO58_CAP.EXE

在这个目录打开 CMD 或 PowerShell。

执行:

python make_romholes_direct.py XxRPxxxxN135MRO58_CAP.EXE XxRPxxxxN135MRO58_ROMHOLES_DIRECT.EXE

脚本默认把 @CMD 修改成:

/L

也可以显式指定:

python make_romholes_direct.py XxRPxxxxN135MRO58_CAP.EXE XxRPxxxxN135MRO58_ROMHOLES_DIRECT.EXE --command "/L"

如果一切正常,最后应该看到类似:

Input:          XxRPxxxxN135MRO58_CAP.EXE
Output:         XxRPxxxxN135MRO58_ROMHOLES_DIRECT.EXE
@UAF offset:    0x119600

Old @CMD:
'/p /b /n /r /x /capsule'

New @CMD:
'/L'

@CMD size:
0x30 -> 0x20

File size:
10729168 -> 10729152 (-16)

@ROM SHA-256:
514cce8bceb61fb57b8d3fdb15ef55fb41dd55555e3b72e9a3e2dba51dc2e16d
(unchanged)

Output SHA-256:
5c24e34d98b37461037926ad1f0e8c3ac881bd11d2a0cc032b3d89cda0da7221

Validation:
PASS

其中最重要的是下面两项:

@ROM SHA-256 ... (unchanged)

Validation: PASS

只有看到:

unchanged

以及:

PASS

才说明:

@ROM 没有被修改
UCP module checksum 正常
@UAF checksum 正常
模块结构正常

如果脚本出现任何:

RuntimeError

或者没有显示:

Validation: PASS

不要运行生成出来的 EXE。

脚本做了什么?

整个自动处理过程相当于:

原厂 CAP.EXE
        ↓
自动寻找 @UAF
        ↓
解析所有 UCP Module
        ↓
找到 @CMD
        ↓
读取原始命令
        ↓
重新构建 @CMD = /L
        ↓
重新计算 @CMD checksum
        ↓
自动调整 @UAF size
        ↓
重新计算 @UAF checksum
        ↓
重新寻找 @ROM
        ↓
逐字节比较原始 @ROM 和新 @ROM
        ↓
完全一致
        ↓
输出 ROMHOLES_DIRECT.EXE

因此它比手工用 HxD 修改更适合批量处理不同机型的官方 BIOS。

不过这个脚本只能解决:

怎么把官方 AFU 参数改成 /L

它不会自动判断官方 BIOS 里有没有新版开机动画。

所以在制作之前仍然需要先确认:

官方 @ROM
        ↓
对应的 ROM Hole
        ↓
1FB553B5-73E8-4290-9B95-B75D01D4CC9A
        ↓
确实包含新版 800×600 / 60帧 GIF

如果官方 BIOS 本身没有新版动画,那么即使成功生成:

ROMHOLES_DIRECT.EXE

刷进去的也仍然是官方 BIOS 自带的旧动画或静态 Logo。

十七、最终检查清单

在真正运行自己制作的 ROMHOLES_DIRECT.EXE 之前,至少检查:

[ ] 平台型号正确

[ ] BIOS版本对应

[ ] @ROM 中确实存在新版动画

[ ] 原始 @ROM SHA-256 与修改后完全一致

[ ] @CMD 已变成 /L

[ ] @CMD checksum 正确

[ ] @UAF size 正确

[ ] @UAF checksum 正确

[ ] 文件结构可以正常解析

其中最重要的是:

@ROM 必须完全没变

如果修改 @CMD 的过程中连 @ROM 都变化了,说明文件重建过程有问题,不要拿去刷。


总结

手动制作 ROMHOLES_DIRECT.EXE 的核心其实只有一句话:

保留官方 BIOS
只把 AFU 参数改成 /L

完整流程:

官方 CAP.EXE
↓
找到 @UAF
↓
找到 @CMD
↓
读取原始 AFU 参数
↓
重建 @CMD 为 /L
↓
重新计算 checksum
↓
调整 @UAF
↓
确认 @ROM 完全没变
↓
输出 ROMHOLES_DIRECT.EXE

这样做的好处是:

不用修改 BIOS 内部固件,也不用重新签名,只是让官方 AFU 把原本没有刷新的 ROM Hole 写进去。

如果官方 BIOS 中本来就包含新版机械革命开机动画,这种方式通常比直接修改 BIOS 本身干净得多。


风险提示

BIOS 属于底层固件。

即使只是修改 AFU 的刷写参数,也仍然属于固件刷写操作。

本文中的 Offset:

0x119600
0x11EF90
0x11EFC0

以及:

@CMD Size
@UAF Size
Checksum

都是针对本文示例:

XxRPxxxxN135MRO58_CAP.EXE

得到的结果。

其他 BIOS 不要直接照抄这些地址。

正确做法应该是重新解析自己手里的官方 EXE,再根据实际结构修改。

操作前请确认机器型号、平台和 BIOS 版本,并自行承担刷写固件产生的风险。

最近的文章

本文是2025版机械革命耀世15 Pro(X6AR45xU)升级 N.1.15MRO10 BIOS,以及解决升级 BIOS 后仍然显示旧版开机动画的方法。 BIOS 刷写存在风险,操作前请确认自己&…

继续阅读