English · 中文
A small, zero-dependency C parser for WinOLS .ols project files.
Reads everything the file has: the 24-byte header, all project metadata, every Version with its ROM image and segment layout, and every Kennfeld (calibration map) with axes, scaling and cell layout.
Plain C99, kernel-style tabs, no external libraries.
make
Produces one binary: ols_dump.
./ols_dump path/to/file.ols # one line per map
./ols_dump -v path/to/file.ols # each map with axes, scaling, cell info
./ols_dump -q path/to/file.ols # header + metadata + versions only
#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);
}offset size meaning
------- ----- -----------------------------------------------------
0x00 u32 magic length prefix = 0x0000000B (decimal 11)
0x04 11 "WinOLS File"
0x0F 1 NUL
0x10 u32 format_version (gates dozens of optional fields)
0x14 u32 declared_size
0x18 --- payload starts here
format_version is the single most important field. It gates dozens
of optional fields downstream. Modern files (≥ 200) use one wire
layout, older ones another. 439+ dropped NUL terminators from strings
and added interned-token sentinels for common values.
No outer ZIP. The payload is a serialized CArchive blob. In order:
-
Project metadata — 23 CString fields + 1 FILETIME + up to 9 schema-gated fields (make, model, ECU name, HW number, SW number, production number, engine code, checksum, flags, import comment, notes…).
-
Magic anchors — seven 4-byte LE sentinels scanned anywhere in the file:
name value purpose M1 0x42007899 Version-record fence M2 0x11883377 Version-array dispatch M3 0x98728833 Per-Version directory M4 0x08260064 Old-format path M5 0xCD23018A Project-properties extension M6 0x88271283 Optional comment / build fingerprint M7 0x84C0AD36 Optional XOR-scrambled trailer -
Version directory — three u32s immediately preceding M1:
[numVersions - 1][versionDataStart][versionRecordSize]. Version i lives atversionDataStart + 4 + versionRecordSize*i. -
ROM segments (fmtVer ≥ 200) — each real ECU flash segment is anchored by a FADECAFE / CAFEAFFE sentinel pair. The 32-byte descriptor ending at that pair carries
[hash, —, hash2, flashBase|0x80000000, flashEnd|0x80000000, 0xFADECAFE, 0xCAFEAFFE, —]. 18 bytes before each descriptor is the project slot — either 18 ×0xAFor a printable ASCII project tag. The first 76 bytes of each segment are WinOLS framing (index byte + inline header + proj slot + descriptor); strip them and the remainder is real flash.Segments are grouped into Versions whenever
flash_baserestarts at a lower value.Old-format files (fmtVer < 200) have no FADECAFE sentinels; each Version slot is the ROM image and is read directly from the version directory.
-
Kennfeld records (maps) — a long sequence starting around offset 0x180 and ending at the first
0x98638811(TAG_POSTZIP_HDR). Each record, on the wire:CFolderRefcomment (the description shown in the WinOLS tree)- 5 × u32:
kennfeld_type, —,cell_data_type, —,cell_bits CStringmap name (C identifier)- schema-gated u32s / booleans
- 6 × f64 scaling doubles (two used, four discarded)
- 4 × bool flags (inverse, signed, diff-vs-ref, mode)
- 5 × u32 geometry:
a276,a280,a396,a400,a408 CStringaxis label +CStringunit- f64 scale, f64 offset, u32 ROM start, u32 ROM end
- two axis sub-records (rows, then cols) — each a nested
CFolderRef+ unit + scale + offset + ROM address + type enum- cell width + schema-gated tail
- trailer
01 01 01delimits the record
cell_data_type(offset +152) is the authoritative byte-layout enumenum bytes/cell endian ---- ---------- ------ 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 / LEcell_bits(offset +164) is display-only and must not be used to size ROM reads.
Map dimensions come from classify_dims() which picks between
a276/a280 and a396/a400 based on kennfeld_type
(1 = scalar, 2 = value, 3 = curve, 5 = map).
Public domain / 0BSD. Do whatever you want.