Dynamic disks (type 3) and differencing disks (type 4) store only the blocks that have been written. They start with a copy of the footer, whose data offset points to the dynamic disk header; the header points to the block allocation table, which says where each block of the disk is in the file. All numbers are big-endian. Checked against libvhdi’s documentation (Joachim Metz); Microsoft’s specification was not read for this page.
Dynamic disk header
1,024 bytes, normally at offset 512.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
0 | 8 | text | Cookie | cxsparse. |
8 | 8 | uint64 | Data offset | Unused: 0xFFFFFFFFFFFFFFFF. |
16 | 8 | uint64 | Table offset | Where the block allocation table starts, from the beginning of the file. |
24 | 4 | uint32 | Header version | 0x00010000. |
28 | 4 | uint32 | Max table entries | The number of entries in the table: the disk size divided by the block size. |
32 | 4 | uint32 | Block size | Bytes of disk per block, a power of two times 512; 2 MiB (0x00200000) by default. It does not count the sector bitmap. |
36 | 4 | uint32 | Checksum | Ones’ complement of the sum of the header’s bytes, as for the footer. |
40 | 16 | UUID | Parent unique ID | The parent disk’s unique ID (differencing disks). |
56 | 4 | uint32 | Parent time stamp | The parent’s modification time, seconds since 1 January 2000 UTC. |
60 | 4 | – | Reserved | 0. |
64 | 512 | UTF-16BE | Parent unicode name | The parent’s file name, or a full path with \ separators (as in .avhd files); not strict UTF-16, as unpaired surrogates occur. |
576 | 192 | entries | Parent locator entries | Eight entries of 24 bytes. |
768 | 256 | – | Reserved | Zeros. |
Parent locators
A differencing disk finds its parent through these entries, each pointing to the parent’s path stored elsewhere in the file. Unused entries are zero.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
0 | 4 | text | Platform code | How the path is written; see below. |
4 | 4 | uint32 | Platform data space | 512-byte sectors set aside in the file for the path. |
8 | 4 | uint32 | Platform data length | Bytes of path. |
12 | 4 | – | Reserved | 0. |
16 | 8 | uint64 | Platform data offset | Where the path is, from the beginning of the file. |
| Code | Value | Path |
|---|---|---|
| none | 0x00000000 | Unused entry |
Wi2r | 0x57693272 | Relative Windows path (deprecated) |
Wi2k | 0x5769326B | Absolute Windows path (deprecated) |
W2ru | 0x57327275 | Windows path relative to the differencing disk, in UTF-16 |
W2ku | 0x57326B75 | Absolute Windows path, in UTF-16 |
Mac | 0x4D616320 | Mac OS alias, stored as a blob (libvhdi’s table spells the code Max ; unconfirmed which is right) |
MacX | 0x4D616358 | A file:// URL in UTF-8 (RFC 2396) |
libvhdi gives UTF-16 big-endian as the format’s string encoding, and notes that the path strings are not strict UTF-16 either.
Block allocation table
One big-endian 32-bit entry per block of the disk, max table entries in all.
An entry is the sector number (in 512-byte
sectors from the start of the file) where that block is stored.
0xFFFFFFFF means the block is not in the file: in a dynamic disk it
reads as zeros; in a differencing disk it is read from the parent.
A stored block is a sector bitmap followed by the block’s data. The bitmap has one bit per 512-byte sector of the block, most significant bit first, padded to a multiple of 512 bytes; for a 2 MiB block it is 4,096 bits, 512 bytes. In a dynamic disk a set bit means the sector holds data; in a differencing disk it means the sector is in this file rather than in the parent.
To read byte n of the disk: block = n ÷ block size; take that
table entry; if it is not 0xFFFFFFFF, the data is at entry × 512 + bitmap
size + (n mod block size).