From byte 69 (0x45) to the end, the file is a ZIP archive as PKWARE’s APPNOTE describes it: the members one after another, each with a local header, then the central directory and the end record. SketchUp writes it with a few particulars, all seen in every file examined, set out below. All numbers are little-endian.
Layout
| Part | Size | Contents |
|---|---|---|
| Local header | 30 + name + 20 | One before each member’s data, with a ZIP64 extra field. |
| Data | compressed size | The member, compressed with Deflate. |
| … | The next member, and so on; model.dat is always last. | |
| Central directory | 46 + name each | One record a member, in the same order. |
| End record | 22 | The member count and where the central directory is. |
Offsets
Every offset in the archive, the local header offset in each central directory record and the directory offset in the end record, counts from the start of the ZIP archive, byte 0x45 of the file, not from the start of the file. To find a member’s local header, add 0x45 (or wherever the archive starts) to its offset.
Tools such as unzip notice the difference, warn of “69 extra bytes at
beginning” and cope. The explorer looks for the central directory at the offset counted
from the archive’s start, then from the file’s, so it also reads a file that another
program has rewritten with offsets from the file’s start.
Local header
| Offset | Size | Type | Name | Seen in SketchUp files |
|---|---|---|---|---|
| 0 | 4 | u32 | Signature | 50 4B 03 04, “PK” 3 4. |
| 4 | 2 | u16 | Version needed | 45 (4.5, the ZIP64 version). |
| 6 | 2 | u16 | Flags | 0 for the two PNGs in meta/; 6 for every other member. Bits 1 and 2 together mean Deflate’s “super fast” option. |
| 8 | 2 | u16 | Method | 8, Deflate, for every member. |
| 10 | 2 | u16 | Time | 0. |
| 12 | 2 | u16 | Date | 0x0020: in DOS form, year 1980, month 1, day 0. No real date is stored. |
| 14 | 4 | u32 | CRC-32 | The CRC-32 of the uncompressed member, filled in. |
| 18 | 4 | u32 | Compressed size | The size of the data, filled in. |
| 22 | 4 | u32 | Size | The size of the member once inflated, filled in. |
| 26 | 2 | u16 | Name length | The length of the name. |
| 28 | 2 | u16 | Extra length | 20. |
| 30 | name length | text | Name | The member’s path, such as meta/meta.dat. |
| 30 + name | 20 | bytes | Extra field | A ZIP64 extra field. |
The data follows straight after the extra field. The sizes are in the header, so there is no data descriptor after it.
ZIP64 extra field
Every local header carries the same 20-byte extra field:
| Offset | Size | Type | Name | Seen |
|---|---|---|---|---|
| 0 | 2 | u16 | ID | 1, the ZIP64 field. |
| 2 | 2 | u16 | Length | 16. |
| 4 | 8 | u64 | Size | 0. |
| 12 | 8 | u64 | Compressed size | 0. |
01 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
The real sizes are in the 32-bit fields of the header. A reader should use the 64-bit
values only when a 32-bit field holds FFFFFFFF, as the ZIP specification
says; the explorer does so.
Central directory
One record for each member, in the same order as the members:
| Offset | Size | Type | Name | Seen in SketchUp files |
|---|---|---|---|---|
| 0 | 4 | u32 | Signature | 50 4B 01 02. |
| 4 | 2 | u16 | Version made by | 0. |
| 6 | 2 | u16 | Version needed | 20 (2.0). |
| 8 | 2 | u16 | Flags | General purpose flags. |
| 10 | 2 | u16 | Method | The compression method. |
| 12 | 2 | u16 | Time | The DOS time. |
| 14 | 2 | u16 | Date | The DOS date. |
| 16 | 4 | u32 | CRC-32 | The CRC-32 of the uncompressed member. |
| 20 | 4 | u32 | Compressed size | The size of the data. |
| 24 | 4 | u32 | Size | The size once inflated. |
| 28 | 2 | u16 | Name length | The length of the name. |
| 30 | 2 | u16 | Extra length | 0: no extra field. |
| 32 | 2 | u16 | Comment length | 0: no comment. |
| 34 | 2 | u16 | Disk | The disk the member starts on. |
| 36 | 2 | u16 | Internal attributes | 0. |
| 38 | 4 | u32 | External attributes | 0. |
| 42 | 4 | u32 | Local header offset | Where the member’s local header is, counted from the start of the archive (see Offsets). |
| 46 | name length | text | Name | The member’s path. |
When a size or the offset is FFFFFFFF, the real value is in a ZIP64 extra
field in the record. None was seen; the explorer reads one if it is there.
End record
The last 22 bytes of the file: the end of central directory record, with no comment.
| Offset | Size | Type | Name | Meaning |
|---|---|---|---|---|
| 0 | 4 | u32 | Signature | 50 4B 05 06. |
| 4 | 2 | u16 | Disk | The number of this disk: 0. |
| 6 | 2 | u16 | Directory disk | The disk the central directory starts on: 0. |
| 8 | 2 | u16 | Entries on this disk | The number of members. |
| 10 | 2 | u16 | Entries | The number of members. |
| 12 | 4 | u32 | Directory size | The size of the central directory in bytes. |
| 16 | 4 | u32 | Directory offset | Where the central directory starts, counted from the start of the archive. |
| 20 | 2 | u16 | Comment length | 0. |
50 4B 05 06 00 00 00 00 41 00 41 00 12 14 00 00 E6 72 3B 00 00 00
To find it, search backwards from the end of the file for 50 4B 05 06; with
no comment it is exactly 22 bytes from the end.
ZIP64 end records
An archive with more than 65,535 members, or a directory beyond 4 GB, needs the
ZIP64 end of central directory record and its locator just before the end record. No
SketchUp file seen has them, not even a 693 MB model with 2,720 members. The explorer
reads them if they are there: the locator (50 4B 06 07, a u32 disk, a u64
offset of the ZIP64 end record, a u32 disk count) sits 20 bytes before the end record,
and the ZIP64 end record (50 4B 06 06) gives 64-bit counts, size and offset
of the directory.
Deflate
Every member is compressed with Deflate (method 8, RFC 1951). The explorer has its own decoder for it, which follows the steps below.
The PNG members: stored blocks
The two PNG pictures in meta/ are “compressed” as Deflate stored blocks:
the PNG sits in the file as it is, behind a 5-byte block header. So the compressed size
is the size plus 5.
01 AA 7C 55 83 89 50 4E 47 …
A stored block holds at most 65,535 bytes, so a bigger PNG needs several. SketchUp splits them smaller than that: one preview picture of 48,967 bytes was stored in 48,977 bytes, two blocks with their 5-byte headers. The explorer shows a PNG that is a single stored block straight from the file, and inflates and decodes the others.
Reading Deflate data
Deflate data is a sequence of blocks, read as a stream of bits, lowest bit of each byte first. Numbers in the stream are read lowest bit first too; Huffman codes are read one bit at a time from their first (highest) bit.
- Read 1 bit: 1 means this is the last block.
- Read 2 bits: the block type. 0 is stored, 1 fixed Huffman codes, 2 dynamic Huffman codes; 3 is an error.
- Stored: skip to the next byte boundary, read LEN (u16) and NLEN (u16), check that NLEN is the complement of LEN, then copy LEN bytes to the output.
- Fixed: use the fixed code lengths: 8 bits for literals 0 to 143, 9 for 144 to 255, 7 for 256 to 279, 8 for 280 to 287, and 5 bits for each of the 30 distance codes.
- Dynamic: read HLIT (5 bits, plus 257 literal/length codes), HDIST (5 bits, plus 1 distance codes) and HCLEN (4 bits, plus 4). Read HCLEN 3-bit lengths for the code-length alphabet, in the order 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15. Build that code, and with it read HLIT + HDIST lengths: symbols 0 to 15 are a length; 16 repeats the previous length 3 to 6 times (2 extra bits); 17 gives 3 to 10 zeros (3 extra bits); 18 gives 11 to 138 zeros (7 extra bits). The first HLIT lengths are the literal/length code, the rest the distance code.
- Build each Huffman code from its lengths: count the codes of each length, give the shortest codes the lowest values, and number the symbols of one length in order.
- Decode symbols until 256. A symbol below 256 is a literal byte. 257 to 285 is a length (table below); read a distance code and its extra bits, then copy that many bytes from that far back in the output.
- Repeat from step 1 until the last block is done.
| Length codes | Extra bits | Lengths |
|---|---|---|
| 257–264 | 0 | 3–10 |
| 265–268 | 1 | 11–18 (bases 11, 13, 15, 17) |
| 269–272 | 2 | 19–34 (bases 19, 23, 27, 31) |
| 273–276 | 3 | 35–66 (bases 35, 43, 51, 59) |
| 277–280 | 4 | 67–130 (bases 67, 83, 99, 115) |
| 281–284 | 5 | 131–257 (bases 131, 163, 195, 227) |
| 285 | 0 | 258 |
| Distance codes | Extra bits | Bases |
|---|---|---|
| 0–3 | 0 | 1, 2, 3, 4 |
| 4–5 | 1 | 5, 7 |
| 6–7 | 2 | 9, 13 |
| 8–9 | 3 | 17, 25 |
| 10–11 | 4 | 33, 49 |
| 12–13 | 5 | 65, 97 |
| 14–15 | 6 | 129, 193 |
| 16–17 | 7 | 257, 385 |
| 18–19 | 8 | 513, 769 |
| 20–21 | 9 | 1025, 1537 |
| 22–23 | 10 | 2049, 3073 |
| 24–25 | 11 | 4097, 6145 |
| 26–27 | 12 | 8193, 12289 |
| 28–29 | 13 | 16385, 24577 |
A length or distance is its base plus the value of its extra bits. The members are
large: model.dat was 17.5 MB to 86.6 MB once inflated in the files seen,
so the explorer inflates at most the first 64 MB of it for its outline.