Introduction
A virtual machine made by Parallels Desktop
keeps each virtual disk in a folder called a .hdd bundle. The folder holds a
DiskDescriptor.xml, which says how big the disk is and which image files make it up,
and the image files themselves, with the extension .hds. Older versions of Parallels used
.hdd for the image file itself, in the same format.
An image is one of two kinds, named by the Type element of the descriptor. A
Compressed image is an expanding one: a 64-byte header, a table that says where each
cluster of the virtual disk is stored, and only the clusters that were written, so the file grows with
use. A Plain image is the virtual disk itself, byte for byte, with no header. A
snapshot adds a further expanding image that holds only the clusters changed since the snapshot it
sits on.
Parallels does not publish the format. What is known comes from QEMU, which reads and writes it
(the header and the format extension are described in QEMU’s docs/interop/parallels.rst,
the descriptor in prl-xml.rst), and from OpenVZ’s open-source ploop library, which
Parallels’ server products share code with. These pages follow those sources, and say where a fact was
only seen in real files.
Structure
All numbers in an expanding image are little-endian. The header says which of two signatures it
is: WithoutFreeSpace, where the BAT counts sectors and the disk size has four
bytes, or WithouFreSpacExt, where the BAT counts clusters, the size has eight
bytes and a format extension may follow. Both are described on
Header, BAT and extension.
0Header signature, version, geometry, cluster size, BAT size, disk size, in-use word, data offset, flags, extension offset64 bytes0x40Block allocation table (BAT) one 32-bit entry for each cluster of the virtual disk: 0 is not allocated4 × entries…Padding to the data areavariesdata offsetData area the clusters the BAT points to, and the format extension cluster if there is oneto the end
To read the virtual disk, cut the byte offset into a cluster number (offset ÷ cluster size) and a position inside the cluster; the BAT entry for that cluster says where the cluster is in the file, and 0 says it is not stored and reads as zeros. The details and the rules a valid image obeys are on the header page; the descriptor, the snapshot chain and the bundle folder are on the descriptor page.
In the explorer
Drop a .hds or .hdd file that starts WithoutFreeSpace or
WithouFreSpacExt. The structure tree shows the header (every field, decoded), the block
allocation table (the first 200 entries as nodes; the Block allocation table list pages through every
one, with its virtual offset and where its cluster is), the padding, the format extension (its magic,
MD5 checked, every feature, and for the dirty bitmap its size, granularity and L1 table) and the data
area, which is labelled rather than drawn cluster by cluster because a disk is tens of gigabytes. Every
BAT entry is checked (inside the file, not in the header or BAT, unique, aligned to the cluster size).
Only the pages the parser asks for are read, so a 56 GB image opens at once.
The Virtual disk table offers the disk the guest sees as a file of its own, read
through the BAT in windows of about 1 MiB, so a partition table, a GPT or a file system inside it can be
opened without reading the image whole. A Plain image has no header, so it opens as bytes (it is the raw
disk: the real Plain image read here has a GPT header at its second sector). Drop a whole
.hdd folder to see its descriptor and images read together.
Not described
Written from QEMU’s and OpenVZ’s sources (read on 2026-11-14) and checked against two real expanding images and one Plain image made by Parallels Desktop, never booted or changed.
Known, not yet covered
- Viewer: a snapshot chain is not followed. A snapshot overlay is read as it is stored, so its unallocated clusters show as zeros, where a reader would take them from the parent image that DiskDescriptor.xml names.
- Viewer: the dirty bitmap’s L1 table is read and each cluster address checked, but the rule that its size equals the number of clusters the serialised bitmap needs (QEMU checks it) is not recomputed, and the bitmap bits themselves are not shown.
- Viewer: the BAT is checked entry by entry for the first 16,777,216 entries (a 16 TiB disk of 1 MiB clusters); the list shows every entry.
Unknown
- Reference: the in-use word of the real images is
pd17(0x37316470), which neither QEMU’s page (0x746F6E59 or 0x312E3276) nor ploop lists; what it means is not published. - Reference: Parallels’ own description of the format, and what Parallels Desktop and Cloud Server do with the “unspecified nodes” QEMU mentions in DiskDescriptor.xml, are not public. Padding 1, split images (more than one Storage) and the encryption fields are not covered by any source read.
- Reference: the
.drhfile beside an expanding image (see the bundle) has no source.
Links
Software
- Parallels Desktop – makes and runs the disks (macOS)
- QEMU and qemu-img – read, write and convert Parallels images (Windows, macOS, Linux)
Developers
- QEMU block/parallels.c – the reader and writer, with the dirty bitmap extension in parallels-ext.c (C)
- OpenVZ ploop – the library that defines the header as C structures (C)
Reference
- Parallels Expandable Image File Format – QEMU’s description of the header, BAT and format extension
- Parallels Disk Format – QEMU’s description of DiskDescriptor.xml
- ploop1_image.h – OpenVZ’s C definitions of the header and the extension