简介:CHM帮助文件格式由微软推出,将大量HTML页面及相关资源压缩为单一文件,便于离线分发;由于编译后的内容对普通用户不可见,若要编辑、翻译或检索信息,必须借助反编译工具还原HTML、图片、样式表等组件。工具包面向开发者、翻译人员及文档维护者,提供完整的反编译方案,可解析CHM结构并导出各组件,支持编辑、重新打包或迁移至其他帮助系统。压缩包共4个文件,包含exe安装程序、dat数据组件、htm操作说明与nfo信息文件,整体约2.19MB;exe用于安装工具,htm与nfo提供使用指引和版本信息,结构精简,无需复杂配置。目前已有216人浏览学习;工具可解析CHM内部结构,导出内容保留原始目录,支持修改文本或样式,也可重新编译为新的CHM文档,操作说明覆盖安装、导入、导出流程,适合具备基础文档操作经验的用户。使用时请尊重知识产权,仅对拥有合法权限的CHM文件进行反编译,避免触犯相关版权法规。
1. CHM反编译:把黑匣子帮助文档拆回 HTML,到底在反什么
很多人第一次接触“CHM反编译”,是因为手里有一个打不开、或者想复用内容的帮助文档。CHM 是 Windows 上最常见的帮助文件格式,本质上是一堆 HTML、图片、CSS 和索引文件被打包压缩后的产物,而不是一种全新的文档格式。所谓“反编译”,并不是破解加密,而是把这个打包好的黑匣子重新拆开,把里面的 HTML 资源还原出来。这个操作能解决的实际问题很具体:旧项目遗留的帮助文档要改版、帮助内容要迁移到线上知识库、或者只是想把某个软件的 CHM 帮助转成普通网页慢慢读。适合读这篇文章的人,是那些已经拿到 CHM 文件、但不知道从哪一步下手的开发者,以及需要批量处理大量帮助文档的文档工程师。读完以后,你应该能自己用命令行拆包、处理乱码、重建索引,并把拆出来的内容整理成可浏览的静态站点。
2. 反编译之前先看懂 CHM 的结构:编译与解包是同一套协议的两面
2.1 一个 CHM 文件里到底装了什么
CHM 的全称是 Compiled HTML Help,本质是微软帮助工作台把一组 HTML 文件、图片、脚本和目录索引统一打包后的结果。这个打包过程经历了两层处理:第一层是文件归档,把一堆零散文件按特定目录结构组织起来;第二层是压缩,内容区使用 LZX 压缩算法处理,索引区则部分采用明文存储。所以反编译工具要做的核心工作,就是先解析文件头,确认这个文件的格式版本和压缩参数,再去解压内容流,最后按文件列表还原出真实的目录结构。
这里需要注意一个关键点:CHM 内容区里“文件名”和“文件路径”是编译时写入的,原始文件叫什么、放在哪个子目录,打包后依然能看到。也就是说,只要解压算法正确,还原出来的资源命名和目录层级基本能保持原样。真正容易出问题的,是后续要讲的 .hhc(目录树)和 .hhk(关键词索引),这两个文件决定了解包后的 HTML 能不能拼成一个可导航的在线文档。
从文件结构上看,一个典型的 CHM 内部包含:
- 头部信息区:文件标识、时间戳、目录偏移量;
- 压缩内容区:存放大部分 HTML、图片、CSS、JS;
- 目录索引区:.hhc 和 .hhk 文件,负责导航树和检索关键词;
- 附加信息区:sitemap 等辅助文件,部分 CHM 会在这里写元数据。
这些部件里,内容区占了体积的绝大部分,而索引区则是反编译后重新组织文档的“地图”。这也是为什么同样叫“反编译”,有些工具拆出来的文件能直接用,有些拆出来一堆散文件却没法浏览——问题往往不是解压失败,而是索引文件没有被正确处理。
2.2 hhc、hhk、hhp、sitemap 这四个文件分别管什么
拆开一个 CHM 之后,你通常会看到 .hhc、.hhk、.sitemap 这几个特殊文件,偶尔还会看到 .hhp。它们在编译前是源代码的一部分,编译后以资源形式存在,反编译时会被还原出来。很多人拿到这些文件后不知道该怎么处理,甚至误以为是垃圾文件直接删掉,结果导致导航结构丢失。
| 文件 | 全称 | 作用 | 反编译后的用途 |
|---|---|---|---|
| .hhc | Html Help Contents | 定义左侧导航树的层级结构 | 用来重建文档目录,可转成 HTML 侧边栏 |
| .hhk | Html Help Keyword Index | 定义关键词与页面的对应关系 | 用来做全文搜索入口和索引页 |
| .hhp | Html Help Project | 编译工程配置文件,记录窗口属性、默认页等 | 反编译时参考,可了解原工程结构 |
| .sitemap | Site Map | 部分新版编译器写入的结构化地图 | 与 .hhc 内容重复度较高,可作对照 |
我一般会先打开 .hhc 看一眼,如果里面的文件路径和实际解压出来的 HTML 对得上,说明这次反编译基本成功。.hhk 则更像一个附加索引层,对普通网页浏览不是必需,但如果要复刻“搜索某个词直接跳到对应页面”的效果,就得保留它。.hhp 比较特殊,它通常只在某些编译器的项目缓存里出现,CHM 内部不一定有,所以看到就留着,没看到也不用慌。
2.3 工具选型:能拆 CHM 的工具那么多,为什么先选这两个
市面上能拆 CHM 的工具可以分成三类:Windows 自带的命令行工具、通用解压缩软件、以及专门的 CHM 反编译图形工具。它们各自的优势和使用场景完全不同,选错工具往往会让你多折腾一晚上。
Windows 自带的 hh.exe 是最“正统”的路子。它本身是 HTML Help 的查看器,但带了一个 -decompile 参数,能把 CHM 拆开。优点是系统自带、不依赖第三方软件、对绝大部分正常编译的 CHM 都有效;缺点是只支持 Windows 平台,且对某些特殊压缩参数的文件会报错。通用解压缩软件比如 7-Zip 也能打开 CHM,因为它把 CHM 当作一个归档格式处理,解压速度快,但它在索引文件的还原上不如 hh.exe 精细,偶尔会出现文件名被重编码的情况。专门的图形化工具体验最好,双击选文件、点导出、完事,但这类工具通常不是免费的就是功能受限,而且对于需要批量处理的人来说,一个个点导出效率太低。
所以我的习惯是:先用 hh.exe 解一遍,如果失败或者结果不完整,再用 7-Zip 兜底,最后才考虑图形化工具。后面两章给出的命令,就是按这个优先级设计的最小可用方案。
3. 两条最小可复现的反编译路径:命令行拆包与压缩包解包
3.1 路径一:Windows 自带的 hh.exe 反编译命令
这是最直接的拆包方式,不需要安装任何额外软件。打开命令行(cmd 或 PowerShell),执行以下命令:
"C:\Windows\hh.exe" -decompile D:\chm_output D:\source\help.chm命令的含义是:调用 Windows 自带的 HTML Help 查看器,把 D:\source\help.chm 这个文件的内容解包到 D:\chm_output 目录。参数顺序不能写反,第一段是 hh.exe 的完整路径,第二段是固定的 -decompile 参数,第三段是输出目录,第四段是输入文件。
注意输出目录必须先存在,hh.exe 不会自动创建不存在的目录,也不会提示你。如果 D:\chm_output 不存在,命令会静默失败,看起来像没反应。另外,路径里如果有空格,整段路径必须用英文双引号包住,否则命令行会把路径拆成两个参数,导致找不到文件。执行完成后,去输出目录里看有没有 .hhc 文件和 HTML 文件,有就说明拆包成功。
3.2 路径二:用 7-Zip 把 CHM 当压缩包直接解
有些 CHM 文件用 hh.exe 解不出来,常见原因是编译时用了比较特殊的压缩参数,或者文件头有轻微损坏。这时候 7-Zip 往往能救场,因为它对 CHM 的解析逻辑更宽容,遇到错误会自动跳过。
7z x D:\source\help.chm -oD:\chm_extracted -y这条命令把 help.chm 解压到 D:\chm_extracted 目录,-y 参数表示遇到确认提示自动选是。7-Zip 会把 CHM 当作类似 ZIP 的归档文件处理,解压出来的东西从文件列表来看和 hh.exe 的结果差不多,但有两个差异要注意。第一,7-Zip 有时会把文件名按 UTF-8 重新编码,导致解出来之后中文文件名变成乱码;第二,7-Zip 不关心 .hhc 和 .hhk 的关联关系,它只负责把文件“倒出来”,后续整理工作得自己做。
所以 7-Zip 更适合作为排查工具,而不是首选方案。当 hh.exe 解出来缺文件、报错,或者你怀疑 CHM 内部有隐藏资源时,用 7-Zip 再解一次做对照,能快速定位问题出在文件本身还是出在解包工具上。
3.3 拆完怎么验证:三步确认这包没白拆
解包完成不等于任务完成,很多人在这一步就直接去开 HTML,结果发现页面全是红叉,然后回头怀疑工具不行。实际上,问题出在拆包后没有做完整性验证。验证只需要三步,每条命令不到十秒。
# 第一步:统计解包出来的文件总数 find D:\chm_output -type f | wc -l # 第二步:确认索引文件存在且非空 find D:\chm_output -name "*.hhc" -exec ls -la {} \; # 第三步:抽查几个 HTML 文件是否包含正常的文档结构 grep -l "<html" D:\chm_output\*.htm | head -5第一步看数量。如果一个帮助文档有几 MB,解出来却只有三五个文件,那肯定不正常,正常情况应该有一批 HTML 和图片资源。第二步看 .hhc 文件的大小,通常至少有几 KB,如果只有几十字节,说明索引可能没解出来。第三步抽查 HTML 内容,确认文件不是空壳,也不是被软件错误重编码过的乱码文件。三步都通过,这个反编译结果才算可以继续往下用。
4. 把拆开的内容拼回去:索引文件解析与中文乱码处理
4.1 hhc 和 hhk 不是摆设:导航树与关键词索引的还原
拆出来的 .hhc 文件,表面上看是一个 HTML 格式的目录文件,里面写着导航树每个节点的名称和对应的目标页面。很多人不知道,这个文件就是文档左侧目录栏的数据源,把它转换一下,就能直接生成静态站点里的侧边栏和面包屑导航。
一个典型的 .hhc 内部片段长这样:
<ul> <li><object type="text/sitemap"> <param name="Name" value="安装指南"> <param name="Local" value="install.htm"> </object> <ul> <li><object type="text/sitemap"> <param name="Name" value="环境要求"> <param name="Local" value="install/env.htm"> </object></li> </ul> </li> </ul>这个结构里,object 标签包住两条关键信息:Name 对应当前节点的显示名称,Local 对应当前节点链接的 HTML 文件路径。嵌套的 ul/li 结构体现了层级关系,每一层缩进就是导航树的一级。解析 .hhc 时只要递归读取这个树形结构,就能重建出完整的文档目录。
.hhk 的格式和 .hhc 类似,但语义不同:它的 param 标签里没有层级关系,只有关键词和页面的映射。比如一个 .hhk 条目可能把“安装”这个关键词指向 install.htm,把“配置”指向 config.htm。如果你的原始 CHM 提供过全文搜索,那么 .hhk 就是重建搜索索引的基础数据。对于静态站点来说,可以把它转成一个 JSON 文件,前端用简单的模糊匹配就能还原接近原版的搜索体验。
4.2 中文乱码:反编译最常见的翻车现场及批量转码
反编译 CHM 的人,十个里有七个会碰到中文乱码。现象很一致:解出来的 HTML 用浏览器打开,标题和正文里的中文全部变成问号或菱形字符,英文和数字正常。这个问题的根源不在解包,而在编码声明。
很多 CHM 在编译时使用 GBK(CP936)编码保存中文字符,但 HTML 文件内部没有声明字符集,或者声明的是 UTF-8。浏览器按照声明的编码解析,自然就乱套了。解决办法是用脚本检测文件的实际编码,统一转成 UTF-8,并补上 meta 声明。下面是常用的批量处理脚本:
import os import chardet import codecs src_dir = r"D:\chm_output" for root, dirs, files in os.walk(src_dir): for name in files: if not name.lower().endswith((".htm", ".html", ".hhc", ".hhk")): continue path = os.path.join(root, name) with open(path, "rb") as f: raw = f.read() # 先检测实际编码,再判断是否需要转码 result = chardet.detect(raw) if result["encoding"] and result["encoding"].lower() not in ("utf-8", "ascii"): text = raw.decode(result["encoding"], errors="replace") with open(path, "w", encoding="utf-8") as f: f.write(text) # 为 HTML 文件补上字符集声明,避免后续再被误判 if name.lower().endswith((".htm", ".html")): with open(path, "r", encoding="utf-8") as f: content = f.read() if "<meta charset=" not in content.lower(): content = content.replace("<head>", "<head><meta charset='utf-8'>", 1) with open(path, "w", encoding="utf-8") as f: f.write(content)脚本的逻辑分两步:先用 chardet 判断文件真实编码,如果是 GBK 或别的非 UTF-8 编码,就解码并重新以 UTF-8 写回;然后遍历 HTML 文件,在 head 开头补上 meta 声明。这里要说明几个坑:第一,chardet 对短文件或者纯中文文本的判断不一定准,如果发现某些文件转完还是乱码,可以手动指定 GBK 再跑一次;第二,errors="replace" 会在无法解码的字节位置放入占位符,如果源文件本身混着损毁内容,转换后会出现替换字符,这是正常现象,不需要特别处理。
4.3 用脚本把拆出来的 HTML 重建成一个能浏览的站点骨架
反编译后的 HTML 文件虽然能用浏览器逐个打开,但体验很割裂,没有导航、没有首页入口。把 .hhc 转换成真正的静态站点导航,是让反编译结果“可用”的最后一公里。不用上什么复杂框架,一个 Python 脚本就够。
import os import re src_dir = r"D:\chm_output" hhc_path = os.path.join(src_dir, "toc.hhc") index_path = os.path.join(src_dir, "index.html") # 解析 hhc,提取导航树节点的名称和链接 nav_items = [] with open(hhc_path, "r", encoding="utf-8", errors="ignore") as f: content = f.read() # 用正则抓取 Name 和 Local 参数对,注意处理多级嵌套 pattern = re.compile(r'<param name="Name" value="(.*?)">.*?<param name="Local" value="(.*?)">', re.S) matches = pattern.findall(content) for name, local in matches: nav_items.append((name.replace("&", "&"), local.replace("\\", "/"))) # 生成一个极简首页,把所有条目输出为列表 html_parts = ["<html><head><meta charset='utf-8'></head><body><h1>文档目录</h1><ul>"] for name, local in nav_items: html_parts.append(f'<li><a href="{local}">{name}</a></li>') html_parts.append("</ul></body></html>") with open(index_path, "w", encoding="utf-8") as f: f.write("\n".join(html_parts)) print(f"已生成 {len(nav_items)} 个导航条目")脚本用正则解析 .hhc,提取每个节点名称和对应文件路径,最后输入一个 index.html 作为入口页。要注意的是,这个正则只处理了平铺列表的情况,如果 .hhc 里有多级嵌套结构,还需要按缩进层级生成多级 ul/li,否则导航层级会丢失。另外,.hhc 里的路径分隔符有时候是反斜杠,浏览器不认,所以这里统一替换成斜杠。做完这一步,整个反编译流程才算真正闭环:有入口、有导航、有正文页面,可以直接挂到任何静态服务器上。
5. CHM 反编译避坑指南:五个高频“玄学”问题及排查路径
5.1 解包成功但文件名全是乱码,HTM 打不开
现象:用 hh.exe 或者 7-Zip 解包后,文件名变成类似“淇℃伅. htm”这样的乱码,双击也只能打开一张空白页。
原因: CHM 编译时文件名使用 GBK 编码,而解包工具按 UTF-8 或者系统默认编码解析了文件名。这种情况在中文版 Windows 上不常见,但在系统区域设置为英文、或者使用第三方解包工具时经常出现。
解决:优先推荐用 7-Zip 配合其“文件名编码”相关选项重新解一遍,或者把系统区域临时切到中文再解包。如果不想改系统设置,可以在 Python 里用 os.listdir 读出文件名,再按 GBK 解码后手动重命名。这个方法偶尔会碰到个别字节无法映射,忽略即可,大部分文件能恢复正常。
5.2 解包后图片、CSS 全丢,页面裸奔
现象:HTML 文件都能正常打开,文字内容也在,但所有图片都是红叉,CSS 样式完全没生效。
原因: CHM 内部的资源路径是编译时记录的,通常指向一个叫 images 或 img 的子目录。如果解包工具没有完整还原目录结构,只是把文件堆到同一层,相对路径就全部失效了。还有一种情况是图片文件本身被压进了特殊数据块,常规解包工具没有导出。
解决:先检查输出目录里有没有 images 之类的文件夹,有的话看看 HTML 里引用的路径和实际目录结构是否一致。如果文件都在但路径不对,写一个简单脚本批量修正 HTML 里的 src 和 href。如果文件根本不存在,换另一种工具解包,或者回到原始工程重新编译。
5.3 解出来一堆文件但全是空壳,页面提示“无法显示”
现象:文件数量看着正常,.hhc 也在,但每个 HTML 打开后都是空白,或者浏览器提示页面无法解析。
原因:最常遇到的是压缩数据块解码不完整。这类 CHM 在编译时使用了非标准的 LZX 压缩参数,通用解包工具无法完全还原,只解出了文件骨架,正文数据全是乱码字节。另外,部分加密型 CHM 也存在类似表现,不是传统意义上的密码加密,而是数据校验类限制。
解决:先试 hh.exe,再试 7-Zip,如果两者结果一致且都是空壳,基本上可以判断是文件本身特殊。此时不要继续折腾解包工具,换个思路:把 CHM 放在服务器环境里,用无头浏览器逐个渲染页面再截图或抓取 DOM。虽然效率低,但这是内容不可替代时的最后办法。
5.4 命令报错“拒绝访问”或目录被占用
现象:执行 hh.exe -decompile 后,命令行没有任何输出,或者直接提示拒绝访问;有时候第二次运行同一命令也会报错。
原因:-decompile 的输出目录如果已经存在且有文件,hh.exe 在某些系统版本上会拒绝覆盖;另外,如果这个 CHM 文件之前被打开过,相关进程还占用着文件句柄,也会导致访问失败。
解决:每次运行前换一个全新的输出目录,或者先手动清空目标目录;同时检查后台是否有进程还开着这个 CHM,关掉再试。这个报错很容易被误判为文件损坏,下次遇到先怀疑“占用”,再怀疑格式。
5.5 Windows 长路径与文件名过长导致解包不完整
现象:解包过程不报错,但输出目录里文件数量明显偏少,或者提示找不到某个路径。
原因:CHM 内部资源路径有可能非常深,再加上输出目录本身路径较长,拼在一起就超过了 Windows 默认的 260 字符路径限制,导致后续文件没有实际落盘。
解决:把输出目录设在根目录下,比如 D:\d,最大限度缩短前缀路径;如果还不够,用 7-Zip 解压,它对长路径的容忍度比 hh.exe 更高。这属于老系统遗留风格的 CHM 才会踩的坑,新生成的帮助文件基本不会出现。
6. 进阶技巧:把反编译做成批处理流水线,并给结果上一道后悔药
单文件反编译跑通后,下一步就是批量。如果是几十个 CHM 文件,一个个敲命令不仅累,还容易出错,我习惯写一个批处理脚本统一跑。
@echo off setlocal enabledelayedexpansion set CHM_DIR=D:\chm_src set OUT_DIR=D:\chm_out for %%f in ("%CHM_DIR%\*.chm") do ( set name=%%~nf mkdir "%OUT_DIR%\!name!" 2>nul "C:\Windows\hh.exe" -decompile "%OUT_DIR%\!name!" "%%f" ) echo 批量反编译完成 pause这个脚本遍历 D:\chm_src 下所有 .chm 文件,按文件名建立独立输出目录,再逐个调用 hh.exe。用 setlocal enabledelayedexpansion 是因为循环内部需要动态取变量值,%%~nf 能拿到不带扩展名的文件名。如果其中某个文件解包失败,不会影响其他文件继续执行,最后在输出目录里把空目录或只有零星文件的目录清理掉就行。
除了批处理,还有一个习惯值得养成:反编译前先把原始 CHM 复制一份备份,并记录文件哈希。原因是 hh.exe 的 -decompile 在某些异常情况下会尝试修改源文件的索引缓存,虽然不是常态,但一旦出现,你手上就没有干净原稿了。我现在每次处理前都会执行一行命令:
certutil -hashfile D:\source\help.chm MD5 > D:\chm_backup\help.md5把哈希值存下来,解完包如果发现格式不对、文件缺失,还能对照哈希确定源文件有没有被动过。这有点像给手术前拍张照片,不算复杂,但能在查问题时省下大量时间。
技术上的坑绕完以后,我想说的是:CHM 反编译这件事本身不难,难的是对索引、编码和文件路径这三样东西的理解。我见过不少人栽在同一个地方——解包成功就以为大功告成,结果乱码文件名和丢失的导航结构花了更多时间收拾。你先用 hh.exe 打底,7-Zip 兜底,闲下来再把我给的 Python 脚本攒成一个自己的工具包,以后再碰到 CHM 也就十分钟的事。希望帮到你。
本文还有配套的精品资源,点击获取