Files
opendance-windows/build/check-pe-version.sh
T
zijunchiang ba988f6ca6 fix(packaging): 发布包 exe 的 PE 版本资源必须等于发布版本(v0.2.2)
缺陷(v0.2.1 实测):把 opendance-windows-v0.2.1.zip 解开、在 Windows 上
「右键 → 属性 → 详细信息」,显示的是

    文件版本    0.1.0
    产品版本    0.1.0

而产物名、Git tag、README 全是 v0.2.1 —— 正是文档 §11.2 要禁止的
「不可核对的发布」。

根因:export_presets.cfg 的 application/file_version /
application/product_version 被硬编码在 0.1.0,而它们**不会**跟随
project.godot 的 application/config/version 自动更新;check-version.sh 当时
只比 project.godot ↔ project.yaml(两处一致),所以静态校验全绿、产物却是错的。
漂移发生在没人看的第三个位置 —— 校验位置少一个,等于没校验。

证据:
  strings -a -el OpenDance.exe  ->  FileVersion 0.1.0 / ProductVersion 0.1.0
  VS_FIXEDFILEINFO @0x67b976c   ->  FileVersion 0.1.0.0 / ProductVersion 0.0.0.0
对照组:官方模板 windows_release_x86_64.exe 的 FileVersion = 4.7.2,
说明本机 rcedit 改写是生效的,问题纯粹在预设的值本身。

修复:
  * build/sync-export-version.sh(新增):导出前把版本号写进导出预设,
    幂等、拒绝非 X.Y.Z;
  * build/check-pe-version.sh(新增):直接读 exe 的 PE 版本资源 ——
    VS_FIXEDFILEINFO(签名 0xFEEF04BD 后 16 字节,grep -abo 取偏移约 40 ms)
    与 StringFileInfo(strings -el,资源管理器属性页显示的就是它)。
    两处都查:实测固定信息里的 ProductVersion 恒为 0.0.0.0;
  * build/check-version.sh:新增导出预设两个键的校验;
  * build/export-windows.sh:导出前同步、导出后直接复核产物;
  * build/package-release.sh:打包前校验源 exe、解包后再校验包内 exe;
  * build/verify.sh:新增步骤 1/5「产物 PE 版本资源」(未导出则跳过);
  * tests/test_about.gd:新增 test_export_preset_version_matches_project_version。

反向验证:把预设 file_version 改回 0.1.0 →
  check-version.sh 退出码 1(报出两处不一致);
  无头测试 753 通过 / 1 失败,失败项正是
  「导出预设的 file_version 与版本号唯一来源一致」。

实测 v0.2.2 产物:PE FileVersion 0.2.2.0 / StringFileInfo 0.2.2,
版本号在 project.godot / project.yaml / export_presets.cfg / exe 元数据 /
产物名 / tag 六处一致。verify.sh 全绿(754 通过 / 0 失败 / 5 跳过)。

同时把 LZMA 压缩的取舍写清楚:deflate 包 38,248,818 B 仍超 IIS
30,000,000 B 上限,故 Release 资产仍是 LZMA 整包(26,628,529 B,不拆分卷);
若上限放开或改由 Web /static 分发,用 ZIP_METHOD=deflate 重打包即可得到
资源管理器可直接双击解压的包。
2026-09-17 04:22:57 +08:00

117 lines
5.2 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# ============================================================
# OpenDance Studio —— Windows 可执行文件的 PE 版本资源校验
#
# 背景(真实缺陷):发布包里的 `OpenDance.exe` 是 Godot 用
# `export_presets.cfg` 的 `application/file_version` / `application/product_version`
# 通过 rcedit 写进 PE `VERSIONINFO` 资源的。这两个键**当初被硬编码成 0.1.0 后再没人更新**,
# 于是 v0.2.1 的发布包在 Windows 上「右键 → 属性 → 详细信息」显示的是
# 文件版本 0.1.0 / 产品版本 0.1.0
# —— 产物名与 Gitea tag 是 v0.2.1,二进制元数据却是 0.1.0,正是文档 §11.2
# 要禁止的那种「不可核对的发布」。光校验 project.godot / project.yaml 抓不到它:
# 那两处当时是一致的,漂移发生在**导出预设**里。
#
# 因此本脚本直接读产物本身的两种版本元数据:
# 1. VS_FIXEDFILEINFO(二进制,权威、机器可读)
# FileVersionMS/LS、ProductVersionMS/LS —— 位于签名 0xFEEF04BD 之后 16 字节;
# 2. StringFileInfo(UTF-16LE 文本,资源管理器「详细信息」里显示的就是它)
# FileVersion / ProductVersion 键值对。
#
# 用法:
# bash build/check-pe-version.sh <exe> [期望版本 X.Y.Z]
# 不带期望版本时只打印实际值(排障用),退出码 0;
# 带期望版本时任一不一致即退出码 1。
# ============================================================
set -uo pipefail
EXE="${1:-}"
EXPECTED="${2:-}"
if [ -z "$EXE" ]; then
echo "用法:bash build/check-pe-version.sh <exe> [期望版本 X.Y.Z]" >&2
exit 2
fi
if [ ! -f "$EXE" ]; then
echo "✗ 找不到文件:$EXE" >&2
exit 2
fi
# ---- 1. VS_FIXEDFILEINFO ----
# 签名 0xFEEF04BD(LE 字节序 bd 04 ef fe)在整个文件的 `VERSIONINFO` 资源里唯一。
# 用 grep -abo 直接拿字节偏移(109 MB 文件约 40 ms),再 od 读 4 个 DWORD。
SIG_OFFSETS=$(LC_ALL=C grep -abo "$(printf '\xbd\x04\xef\xfe')" "$EXE" 2>/dev/null | cut -d: -f1)
SIG_COUNT=$(printf '%s\n' "$SIG_OFFSETS" | grep -c . || true)
if [ "$SIG_COUNT" -eq 0 ]; then
echo "✗ $EXE 里没有 VS_FIXEDFILEINFO 签名(0xFEEF04BD),PE 版本资源缺失" >&2
exit 1
fi
if [ "$SIG_COUNT" -gt 1 ]; then
echo "⚠ $EXE 里出现 $SIG_COUNT 处签名,取第一处($SIG_COUNT > 1 时结果可能不准)" >&2
fi
SIG_OFFSET=$(printf '%s\n' "$SIG_OFFSETS" | head -1)
read -r FV_MS FV_LS PV_MS PV_LS <<<"$(od -An -tu4 -j $((SIG_OFFSET + 16)) -N 16 "$EXE" | tr -s ' ')"
fmt_version() { printf '%s.%s.%s.%s' "$(( $1 >> 16 ))" "$(( $1 & 0xFFFF ))" "$(( $2 >> 16 ))" "$(( $2 & 0xFFFF ))"; }
FIXED_FILE_VERSION="$(fmt_version "$FV_MS" "$FV_LS")"
FIXED_PRODUCT_VERSION="$(fmt_version "$PV_MS" "$PV_LS")"
# ---- 2. StringFileInfo(UTF-16LE 文本)----
# `strings -el` 会把 UTF-16LE 字符串按出现顺序逐行列出,键与值相邻:
# FileVersion
# 0.1.0
# 取键所在行的下一行即为值。缺 strings(binutils) 时降级为只查固定信息。
string_value_of() {
local key="$1" text="$2"
printf '%s\n' "$text" | awk -v k="$key" '$0 == k { getline; print; exit }'
}
STRING_DUMP=""
if command -v strings >/dev/null 2>&1; then
STRING_DUMP="$(strings -a -el "$EXE" 2>/dev/null || true)"
fi
STR_FILE_VERSION="$(string_value_of FileVersion "$STRING_DUMP")"
STR_PRODUCT_VERSION="$(string_value_of ProductVersion "$STRING_DUMP")"
STR_PRODUCT_NAME="$(string_value_of ProductName "$STRING_DUMP")"
STR_COMPANY="$(string_value_of CompanyName "$STRING_DUMP")"
echo " PE 版本资源($EXE):"
echo " VS_FIXEDFILEINFO FileVersion = ${FIXED_FILE_VERSION:-(未读到)}"
echo " VS_FIXEDFILEINFO ProductVersion = ${FIXED_PRODUCT_VERSION:-(未读到)}"
if [ -n "$STRING_DUMP" ]; then
echo " StringFileInfo FileVersion = ${STR_FILE_VERSION:-(未找到)}"
echo " StringFileInfo ProductVersion = ${STR_PRODUCT_VERSION:-(未找到)}"
echo " StringFileInfo ProductName = ${STR_PRODUCT_NAME:-(未找到)}"
echo " StringFileInfo CompanyName = ${STR_COMPANY:-(未找到)}"
else
echo " StringFileInfo (本机没有 strings/binutils,跳过文本项)"
fi
if [ -z "$EXPECTED" ]; then
exit 0
fi
# ---- 3. 与期望版本比对 ----
FAILED=0
# 固定信息的第 4 段是 revision,rcedit 一律写 0,因此只比前三段
fixed_prefix="${FIXED_FILE_VERSION%.*}"
if [ "$fixed_prefix" != "$EXPECTED" ]; then
echo " ✗ PE FileVersion 为 ${FIXED_FILE_VERSION:-(空)},期望 ${EXPECTED}.x(Windows 属性页会显示错版本)" >&2
FAILED=1
fi
if [ -n "$STRING_DUMP" ]; then
if [ "$STR_FILE_VERSION" != "$EXPECTED" ]; then
echo " ✗ 资源字符串 FileVersion 为 '${STR_FILE_VERSION:-(空)}',期望 '${EXPECTED}'" >&2
FAILED=1
fi
if [ "$STR_PRODUCT_VERSION" != "$EXPECTED" ]; then
echo " ✗ 资源字符串 ProductVersion 为 '${STR_PRODUCT_VERSION:-(空)}',期望 '${EXPECTED}'" >&2
FAILED=1
fi
fi
if [ "$FAILED" -ne 0 ]; then
echo " ✗ PE 版本资源与期望版本不一致 —— 请先跑 bash build/sync-export-version.sh 并重新导出" >&2
exit 1
fi
echo " ✓ PE 版本资源一致:FileVersion ${FIXED_FILE_VERSION} / 资源字符串 ${EXPECTED}"