# ols-parser **Repository Path**: liyu2015/ols-parser ## Basic Information - **Project Name**: ols-parser - **Description**: No description available - **Primary Language**: Unknown - **License**: 0BSD - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-25 - **Last Updated**: 2026-04-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ols-parser [English](README.md) · **中文** 一个轻量、零依赖的 C parser,专门啃 WinOLS 的 `.ols` 项目文件。 文件里有啥它都给你抠出来:24 字节 header、整套项目 metadata、每个 Version 的 ROM 镜像和分段布局、每张 Kennfeld(标定 map)连带 axis、 scale、cell 布局全给你整明白。 纯 C99,内核风格硬 Tab,不依赖任何外部库,make 一下就能跑。 ## 编译 ``` make ``` 吐出来一个二进制:`ols_dump`。 ## 怎么用 ``` ./ols_dump path/to/file.ols # 每张 map 一行 ./ols_dump -v path/to/file.ols # 带上 axis、scale、cell 细节 ./ols_dump -q path/to/file.ols # 只打 header + metadata + versions ``` ## 当 library 用 ```c #include "ols.h" struct ols_ctx ctx; if (ols_parse(buf, len, &ctx) == 0) { printf("ECU: %s %s HW %s SW %s\n", ctx.metadata.manufacturer, ctx.metadata.ecu_name, ctx.metadata.hw_number, ctx.metadata.sw_number); for (size_t v = 0; v < ctx.nversions; v++) printf("Version %zu: %zu bytes @ 0x%08x\n", v, ctx.versions[v].rom_size, ctx.versions[v].base_address); for (size_t i = 0; i < ctx.nmaps; i++) { const struct ols_map *m = &ctx.maps[i]; printf("%-5s %3dx%-3d @%08x %s scale=%g offset=%g\n", m->type, m->nx, m->ny, m->raw_address, m->name, m->scale, m->offset); } ols_free(&ctx); } ``` --- ## OLS 格式速成 ### 24 字节外层 header ``` offset size 含义 ------- ----- ----------------------------------------------------- 0x00 u32 magic 长度前缀 = 0x0000000B(就是十进制 11) 0x04 11 "WinOLS File" 0x0F 1 NUL 0x10 u32 format_version(后面一堆可选字段全看它脸色) 0x14 u32 declared_size 0x18 --- payload 从这里开始 ``` `format_version` 是整个文件的命门。后面几十个可选字段存不存在、 咋编码的,版本号说了算。新格式(≥ 200)一套玩法,老格式另一套; 439+ 干脆把字符串尾巴的 NUL 砍了,常见值还整了几个 interned token 的 sentinel 来偷懒。 ### Payload —— Microsoft MFC CArchive 没有外层 ZIP,payload 就是一坨 `CArchive` 序列化数据直接铺开。顺序是: 1. **项目 metadata** —— 23 个 CString 字段 + 1 个 FILETIME + 最多 9 个跟版本挂钩的字段(厂家、车型、ECU 名、HW 号、SW 号、 生产编号、发动机代号、checksum、flags、导入备注、notes……)。 2. **Magic anchor** —— 7 个 4 字节小端哨兵值,整个文件里哪都可能 冒出来: | 名字 | 值 | 干啥的 | |------|------------|-----------------------------------------| | M1 | 0x42007899 | Version record 栅栏 | | M2 | 0x11883377 | Version 数组 dispatch | | M3 | 0x98728833 | 每个 Version 的目录 | | M4 | 0x08260064 | 老格式走的路 | | M5 | 0xCD23018A | 项目属性扩展 | | M6 | 0x88271283 | 可选备注 / build fingerprint | | M7 | 0x84C0AD36 | 可选的 XOR 混淆尾巴 | 3. **Version 目录** —— 紧挨 M1 前面塞了三个 u32: `[numVersions - 1][versionDataStart][versionRecordSize]`。第 *i* 个 Version 的数据就在 `versionDataStart + 4 + versionRecordSize*i`。 4. **ROM 分段(fmtVer ≥ 200)** —— 每一段真实的 ECU flash 都靠 **FADECAFE / CAFEAFFE** 这对哨兵锚定。哨兵对前面 32 字节的 descriptor 长这样: `[hash, —, hash2, flashBase|0x80000000, flashEnd|0x80000000, 0xFADECAFE, 0xCAFEAFFE, —]`。descriptor 再往前 18 字节是 *project slot*——要么是 18 个 `0xAF`,要么是一串可打印 ASCII 项目 tag。每段前 76 字节是 WinOLS 自己的 framing(index byte + 内联 header + proj slot + descriptor),砍掉这 76 字节剩下的才是真正的 flash 内容。 `flash_base` 一旦重新从小值开始,就知道跳进下一个 Version 了。 老格式(fmtVer < 200)根本没 FADECAFE 哨兵,每个 Version 槽 *就是* ROM 镜像,照着 version 目录直接读就完事。 5. **Kennfeld 记录(标定 map)** —— 从 0x180 附近一路排到第一个 `0x98638811`(TAG_POSTZIP_HDR)为止就是 map 区。每条记录字节顺序 如下: - `CFolderRef` 注释(WinOLS 树里那行描述) - 5 个 u32: `kennfeld_type`、—、`cell_data_type`、—、`cell_bits` - `CString` map 名(C 标识符) - 跟版本挂钩的一堆 u32 / bool - 6 个 f64 scaling double(用两个,扔四个) - 4 个 bool flag(inverse、signed、diff-vs-ref、mode) - 5 个 u32 几何: `a276`、`a280`、`a396`、`a400`、`a408` - `CString` axis label + `CString` unit - f64 scale、f64 offset、u32 ROM 起始、u32 ROM 结束 - 两条 axis 子记录(先 rows 后 cols),每条都是一个嵌套的 `CFolderRef` + unit + scale + offset + ROM 地址 + 类型枚举 + cell 宽度 + 跟版本挂钩的尾巴 - 尾巴 `01 01 01` 切分一条记录 `cell_data_type`(偏移 +152)才是真正定字节布局的那个枚举—— ``` 枚举 字节/cell 字节序 ---- ---------- ---------- 1 1 — 2 2 BE 3 2 LE 4/5 4 BE / LE 6/7 4-float BE / LE 10/11 8 BE / LE 12/13 8-double BE / LE ``` `cell_bits`(偏移 +164)纯粹是显示用的,**千万别**拿它决定从 ROM 里读几个字节,不然就等着翻车。 map 的维度看 `classify_dims()`——它按 `kennfeld_type` (`1` = scalar,`2` = value,`3` = curve,`5` = map)在 `a276/a280` 和 `a396/a400` 两组里挑。 --- ## License [0BSD](LICENSE)。随便拿去造。