John-MustangGT/ford-workshop-manual-tools

GitHub: John-MustangGT/ford-workshop-manual-tools

福特维修手册Arc文件提取器,简化维修手册使用。

Stars: 0 | Forks: 0

# 福特维修手册 Arc 提取器 用于从福特维修手册 `.arc` 容器文件中提取和本地服务内容,无需使用福特专有软件的工具。处理 `BAY POD` 和 `POD BAY` 容器格式,并解压缩所有 IDICOMP 压缩条目。 ## 重要 **此存储库不包含手册数据。** 您必须提供自己的福特维修手册 CD-ROM 复制件。这些工具是在针对 **2012-2014 福特维修手册**(主要是 S197Mustang)开发和测试的,并且应该适用于同一光盘覆盖的其他车辆和年份。其他版本的维修手册可能使用相同的格式且无需修改即可工作,但尚未经过测试。 福特维修手册软件和所有服务内容均为福特汽车公司所有。 ## 使用方法 ``` python3 extract_baypod.py [ ...] [-o ] [-v] [-l] [-e ] ``` **参数:** | 标志 | 描述 | |------|-------------| | `input` | 一个或多个 `.arc` 文件,或包含 `.arc` 文件的目录 | | `-o DIR` | 输出目录(默认:`extracted`) | | `-v` | 详细信息:打印每个提取的文件名 | | `-l` | 列出内容而不提取 | | `-e EXT` | 仅提取具有此扩展名的文件(可重复,例如 `-e .htm -e .svg`) | **示例:** ``` # 从 arc 文件夹中提取所有内容 python3 extract_baypod.py content/useni4 -o extracted # 列出单个 arc 的内容 python3 extract_baypod.py content/useni4/SEB.arc -l # 仅提取 HTML 和 SVG 文件 python3 extract_baypod.py content/useni4 -o extracted -e .htm -e .HTM -e .svg ``` 每个 `.arc` 文件都提取到以 arc 命名的单独子目录中(例如 `extracted/SEB/`)。 ## LLM 训练导出 如果您想要一个按节级的训练语料库而不是 Obsidian 保险库,请使用: ``` python3 -m pip install 'markitdown[pdf]' python3 build_training_data.py --arc SEB ``` 这将遍历提取的节目录,使用 MarkItDown 转换文档文件,并在 `training_export//` 下写入 JSONL 语料库和清单。默认包含 HTML、PDF、WCF、XML 和 TXT 文件;使用 `--extensions` 来缩小或扩大集合。 ## RAG 知识导出 对于针对 `Qwen:latest` 调优的 Open WebUI 知识导入,请使用分块 RAG 导出器: ``` python3 build_rag_knowledge.py --arc SEB ``` 这将写入 `rag_export//chunks/` 下的重叠 markdown 块,并在已知时包含配对的布线 arc。默认块大小为 `900` 个单词,重叠 `120` 个单词,这可以将维修程序和诊断上下文保持在一起,同时不会使检索单元太大。如果需要,请使用 `--target-words` 和 `--overlap-words` 来重新调整块大小。 ## 格式参考 ### BAY POD 容器(`.arc`) 魔数:`BAY POD\x02`(8 字节) ``` Offset Size Description ------ ---- ----------- 0 8 Magic: "BAY POD\x02" 8 1 Padding (0x00) 9 3 Number of directory entries (3-byte LE) 12 1 Padding (0x00) 13 3 Filename table size in bytes (3-byte LE) 16 N*16 Directory entries (16 bytes each) 16+N*16 1 Null separator byte ... ? Filename table (concatenated filenames, no separators) ... ? Data section (IDICOMP-wrapped file data) ``` **目录条目(16 字节):** ``` Byte(s) Description ------- ----------- 0 Padding (0x00) 1–3 Offset into filename table (3-byte LE) 4 Padding (0x00) 5 Filename length (1 byte) 6–8 Padding (0x00 0x00 0x00) 9–12 Absolute data offset in file (4-byte LE) 13–14 Size of IDICOMP-wrapped data block (2-byte LE) 15 Padding (0x00) ``` ### POD BAY 容器(`.arc`) 魔数:`POD BAY\x01`(8 字节) 用于召回/TSB 文档。具有与 BAY POD 相同的标题布局,但不含可用的文件名表——文件名是从数据中嵌入的工作单元 XML 块中推断出来的。数据部分是 IDICOMP 包装的块序列,端到端存储,没有目录;提取器扫描 `\x01IDICOMP\x01` 魔数以定位每个块。 工作单元块包含 `r98s20.htm`,它给出了主要输出文件名。其他块被分配从存档基本名和检测到的内容类型派生的名称。 ## IDICOMP 格式 arc 中的每个数据块(无论是在 BAY POD 还是 POD BAY 中)都包裹在一个 IDICOMP 标题中。`\x01IDICOMP\x01` 魔数出现在数据部分,绝对偏移量由目录条目给出。 **IDICOMP 块布局:** ``` Offset Size Description ------ ---- ----------- 0 9 Magic: "\x01IDICOMP\x01" 9 2 Compressed length of first chunk (2-byte LE) 11 ? First compressed chunk (see below) ... ... Additional chunks (see Multi-chunk blocks) ``` 有效载荷从字节 9 立即开始。没有单独的“类型标志”字段——最初记录为这样的内容实际上是第一个压缩块的前两个字节(第一个 LZSS 控制字)。 **原始(未压缩)文件:** 对于二进制文件(JPEG、GIF、PDF 等),偏移量 9-10 的字节包含实际文件数据的第一个两个字节,而不是有效的 LZSS 长度。解压缩器通过第一个块产生越界回参考(`IndexError`)来检测这一点;在这种情况下,从偏移量 11 开始的原始字节被直接返回。 在偏移量 11 处常见的签名: - `FF D8` → JPEG(从字节 11 存储原始数据) - `47 49` (`GI…`) → GIF(从字节 11 存储原始数据) - `25 50` (`%P…`) → PDF(从字节 11 存储原始数据) - `3B 20` (`;` ` `) → WCF 文本(从字节 11 存储原始数据) ## IDICOMP 多块块 大文件作为单个 IDICOMP 块内独立压缩的块序列存储。每个块解压缩到最多 **16,384 字节**(0x4000)。在 9 字节魔数之后的格式: ``` [2-byte LE chunk_len] [chunk_len bytes of LZSS data] [2-byte LE chunk_len] [chunk_len bytes of LZSS data] ... [0x00 0x00] ← terminator (zero-length chunk) ``` 解压缩器将所有块的输出连接起来以重建完整文件。单块文件(≤16 KB 解压缩)正好有一个块后跟终止符。 ## IDICOMP 解压缩算法 从文件偏移量 `0x35fe9` 的 `TSBASP.dll` 中逆向工程。通过调试符号识别的源项目为 `D:\develop\common\idicomm2\DB.CPP`。 该算法是一个 **位导向 LZSS 变体**,具有 16 位控制字。 ### 控制字处理 ``` mask = 0 ctrl = 0 src = 0 (index into compressed input) out = [] (output buffer) loop: mask >>= 1 if mask == 0: ctrl = read_u16_le() # reload control word mask = 0x8000 if (ctrl & mask) == 0: out.append(read_byte()) # literal else: decode_encoded_op() # back-reference or run-fill ``` 控制字是 16 位小端值。位按 MSB-first(掩码从 `0x8000` 开始并右移)测试。一个 `0` 位表示下一个字节是字面量;一个 `1` 位表示接下来是一个编码操作。 **第一个控制字也是每个块的前两个字节**(第一个块的 IDICOMP 块的字节 11-12,或后续块的第一个两个字节)。 ### 编码操作 当遇到一个 `1` 位时,读取一个字节 `b`: - `t = (b >> 4) & 0xF` — 高位,操作类型 - `m = b & 0xF` — 低位,参数 | 类型 (`t`) | 描述 | |------------|-------------| | 0 | **短运行填充**:读取 `fill_byte`;发出 `(m + 3)` 个副本 | | 1 | **长运行填充**:读取 `nb`,然后 `fill_byte`;发出 `(m + (nb << 4) + 19)` 个副本 | | 2 | **回参考,显式长度**:读取 `nb`,然后 `cl`;从 `out[current - dist]` 复制 `(cl + 16)` 个字节,其中 `dist = m + (nb << 4) + 3` | | 3–15 | **回参考,隐式长度**:读取 `nb`;从 `out[current - dist]` 复制 `t` 个字节,其中 `dist = m + (nb << 4) + 3` | 回参考使用对已解码输出的滑动窗口。重叠的副本(其中 `dist < copy_length`)按字节逐个处理,以正确实现通过回参考的运行长度编码。 ### Python 实现 ``` def _decompress_idicomp(data): src = 0 n = len(data) out = bytearray() mask = 0 ctrl = 0 while src < n: mask = (mask >> 1) & 0xFFFF if mask == 0: if src + 2 > n: break ctrl = data[src] | (data[src + 1] << 8) src += 2 mask = 0x8000 if (ctrl & mask) == 0: if src >= n: break out.append(data[src]) src += 1 else: if src >= n: break b = data[src]; src += 1 t = (b >> 4) & 0xF m = b & 0xF if t == 0: # short run-fill ln = m + 3 fb = data[src]; src += 1 out.extend([fb] * ln) elif t == 1: # long run-fill nb = data[src]; src += 1 ln = m + (nb << 4) + 19 fb = data[src]; src += 1 out.extend([fb] * ln) elif t == 2: # back-ref, explicit copy length nb = data[src]; src += 1 dist = m + (nb << 4) + 3 cl = data[src] + 0x10; src += 1 p = len(out) - dist for i in range(cl): out.append(out[p + i]) else: # back-ref, copy length = type nibble nb = data[src]; src += 1 dist = m + (nb << 4) + 3 p = len(out) - dist for i in range(t): out.append(out[p + i]) return bytes(out) ``` ## 语料库说明(福特维修手册) 从完整安装中提取的典型内容: | 扩展名 | 数量 | 描述 | |-----------|-------|-------------| | `.HTM`/`.htm` | ~36,000 | 服务程序 HTML 页面 | | `.SVG` | ~11,000 | 布线图矢量图形 | | `.JPG` | ~80,000 | 程序步骤照片 | | `.GIF` | ~15,000 | 图表和图标 | | `.XML`/`.xml` | ~18,000 | 结构化数据/零件信息 | | `.PDF` | ~5,000 | 补充和规范表 | | `.wcf` | ~5,600 | 工作单元控制文件(元数据) | 命名 arc 文件对应于车辆系列和年份(例如 `SEB.arc` = S197Mustang,`SE2.arc` = 布线图,`VIE.arc` = 老式参考图像)。 ## 本地查看器服务器 启动本地手册查看器: ``` python3 serve.py ``` 默认 URL:`http://localhost:8080/` 直接 S197 入口: ``` http://localhost:8080/SEB/SEBALPHAINDEX.HTM ``` 服务器还模拟福特提取的 HTML 使用的福特路由: - `/renderers/2colframeset.asp` - `/tpsasps/2colframeset.asp` - `/renderers/wiringsvg/ep_main.asp` ## 第 1 阶段离线移动应用(PWA) 存储库现在包括位于 `webapp/` 的基于浏览器的离线应用外壳。 ### 1) 构建离线目录 从提取的 arc 目录生成 `webapp/offline_catalog.json`。 ``` # 示例:仅 S197 工作坊 + 线路 python3 build_offline_catalog.py --only SEB EEB # 或索引所有提取的 arc python3 build_offline_catalog.py ``` 如果您愿意,可以跳过此单独步骤,并通过 `extract_baypod.py --cache-catalog ...` 直接从提取生成相同的目录。 ### 2) 启动服务器 ``` python3 serve.py ``` ### 3) 打开离线应用 ``` http://localhost:8080/offline_app.html ``` ### 4) 在离线前缓存书籍 在应用中: 1. 选择一个 arc(例如 `SEB`) 2. 点击 **下载 Arc 以供离线使用** 3. 为其他 arc(例如 `EEB`)重复操作 4. 在在线时打开一些重要页面,以便缓存动态渲染器 URL ### 5) 在设备上安装 - **iPad(Safari)**:共享 -> 添加到主屏幕 - **Android(Chrome)**:安装提示或浏览器菜单 -> 安装应用 ### 注意 - 这是一个第 1 阶段 PWA 实现,旨在与您的提取文件进行本地/离线使用。 - iOS 可能会在压力下驱逐缓存的存储;在依赖断开连接的使用之前,请验证关键页面。 ## Obsidian 导出管道 使用以下方法将一个提取的维修手册 arc(例如 `SEB`)转换为 Obsidian 准备好的捆绑包: - 每个`.HTM`页面的 Markdown 笔记 - Obsidian wikilinks 用于内部页面导航 - 复制的 arc 资产(JPG/GIF/PDF/SVG/PNG) - 可选的布线图资产从相关的 EE* arc 中复制(如果引用) - 生成的节索引和字母索引笔记 ### 1) 安装依赖项 推荐(本地虚拟环境): ``` python3 -m venv .venv .venv/bin/python -m pip install beautifulsoup4 markdownify ``` 替代(系统 Python): ``` python3 -m pip install beautifulsoup4 markdownify ``` ### 2) 构建 Obsidian 捆绑包(SEB 示例) ``` .venv/bin/python build_obsidian_section.py --arc SEB ``` 使用显式布线 arc 覆盖: ``` python3 build_obsidian_section.py --arc SEB --wiring-arc EEB ``` 从布线 arc 复制所有资产(可选;默认为仅引用): ``` python3 build_obsidian_section.py --arc SEB --wiring-arc EEB --copy-all-wiring-assets ``` 导出后压缩保险库,以便轻松云/手机传输: ``` python3 build_obsidian_section.py --arc SEB --zip ``` 这将生成 `obsidian_export/SEB_YYYYMMDD_HHMMSS.zip`,与保险库文件夹并列。 默认输出路径: ``` obsidian_export/SEB/ ``` 顶级捆绑包内容: - `index.md`(根导航笔记) - `pages/`(所有转换的程序) - `assets/`(复制的引用图像/文档) - `indexes/Section Index.md` - `indexes/Alphabetical Index.md` - `_meta/manifest.json` ### 3) 导入到 Obsidian 将 `obsidian_export/SEB/` 打开为保险库(或将其内容复制到现有保险库中)。
标签:逆向工具