Jump to content
Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Log in or create an account to edit The Apple Wiki.
(Redirected from Iconconfigpack)
Example icon atlas

Since iOS 13, Apple Maps uses a custom binary format for map icons. There are files with .iconconfigpack, .iconmappack, and .icondatapack extensions, collectively known as "resource packs". These files contain image atlases with multiple icons and variants, and configuration for how to display them.

These files are parsed and rendered by the VectorKit private framework, in C++ classes like grl::codec::IconDataPack.

Example files:

Overview

All icon data is split into independent sets of files for each display scale: default (1x), @2x, and @3x. For each display scale, there is one iconmappack file with global information, and several iconconfigpack and icondatapack files, one pair for each "region".

Icons

Icons are identified by a tuple (iconID, variant, sizeGroup) or (iconID, variant, dataVariant, sizeGroup) (depending on the version), called an "IconKey". Each iconID has an associated name defined in the IconMapPack.

Icons are not just individual bitmaps. IconConfigPacks specify an icon as a list of "layers" of different types. An "image layer" references an image by its imageID. An imageID then corresponds to a part of an image atlas in a IconDataPack. There can be other layers, such as a "path layer" defining a colored circle drawn behind the image.

The sizeGroup part of the IconKey corresponds to:

0: XXXSmall
1: XXSmall
2: XSmall
3: Small
4: Small Medium
5: Medium
6: Large
7: XLarge
8: XXLarge
9: XXXLarge

Regions

"Regions" allow downloading only the icon packs needed for a certain purpose or area of the map, for example, only download Japan-specific public transport icons when you zoom into Japan. There is also a "Default Icons" region used globally.

However, there are also other regions like "Guides Icons Default" which don't correspond directly to "a region of the world", so the term is somewhat a misnomer. A region is really a pair of config+data pack files containing a set of icons.

There are two mechanisms to decide what "regions" are applicable, but the details are unclear (how do the two interact, etc):

  • The geo_manifest that lists available icon packs specifies one or more minX/maxX/minY/maxY/minZ/maxZ tuples for some packs. Presumably this marks an icon pack as only applying to some zoom levels and some rectangular areas of the map.
  • The IconMapPack file assigns a list of regions to each country. This seems to be used both for country-specific icons (like local landmarks), and for icons that look different in different countries. For example, bank POI icons have a $ or € or £ symbol depending on the country. The mappack file says "in Spain, use the Localized Poi INTL Euro Icons region", and the config/data packs for that region contain a bank icon with the € symbol.

Resource pack file format

All resource packs have the same outer format. They have a sequence of "chapters", which are usually zlib-compressed. Chapter type 1 "pack info" has the same format in all three file types. The contents of the other chapters are specific to each file type.

All integers and floating point numbers are stored in little-endian byte order.

The first 0x40 bytes of the file contain the "header name", which may be "ICONCONFIGPACK", "ICONDATAPACK", or "ICONMAPPACK", padded with zeros. Apple's decoder doesn't actually check if the padding has zeros, so it's possible they could use those bytes for extra fields in the future.

Next there is the list of chapters and their locations in the file.

struct ResourcePack {
    char headerName[0x40];
    u16 chapterCount;
    ChapterInfo chapters[chapterCount];
};
struct ChapterInfo {
    u16 chapterType;
    u64 byteStart;
};

It's probably invalid to have duplicate chapterTypes, or to have chapters in an unexpected order. For example, some chapters change their format slightly depending on the version number in the PackInfo chapter, so PackInfo has to be first. However, there is no explicit error checking for this in Apple's decoder.

The byteStart field in ChapterInfo specifies the offset where the chapter starts, from the beginning of the file.

At that offset, you find this structure:

struct Chapter {
    u64 rawSize;
    u64 compressedSize;
    u8 data[];
};

If compressedSize is non-zero, then the following data is of size compressedSize, and contains zlib-compressed data. Decompressing it should result in data of size rawSize.

If compressedSize is 0, then the following data is of size rawSize and contains the uncompressed chapter data directly.

All the rest of the format complexity is in the content of the chapters.

PackInfo chapter

All three resource file types have a PackInfo chapter (chapterType=1). The content is:

struct PackInfoChapter {
    u16 packVersion;
    char region[];
    float contentScale;
};

The region string is variable size and null-terminated; the contentScale float appears immediately after the null terminator of the region string.

The packVersion field affects parsing of other structures. At the moment the only known difference is that dataVariant in iconconfigpack only exists if packVersion >= 3.

IconDataPack format

Data packs are resource pack files that contain image atlases, as embedded PNG images. They use the header name "ICONDATAPACK", and have three chapters:

  • 1: PackInfo
  • 13: ImageInfo
  • 14: Atlases

ImageInfo chapter

The ImageInfo chapter (chapterType=13) has a list of all the "images" in this file, specified as rectangular regions within atlases, and a list of all the atlases.

struct ImageInfoChapter {
    u32 numImages;
    Image images[numImages];
    u16 numIndices;
    AtlasIndex indices[numIndices];
};
struct Image {
    u32 imageID;
    u16 atlasIndex;
    float atlasOffsetX;
    float atlasOffsetY;
    float imageSizeX;
    float imageSizeY;
};
struct AtlasIndex {
    u16 atlasIndex;
    u32 byteStart;
    u32 byteLength;
};

The atlasOffset and imageSize fields are in floating-point format, but they appear to be always positive integer values in practice.

The Image structure specifies that the image identified by imageID is a rectangular region of atlas atlasIndex, starting at top left corner atlasOffset, with size imageSize in pixels.

Each AtlasIndex specifies that the PNG image data for atlas atlasIndex is at offset byteStart and length byteLength within the data of the atlas chapter.

Atlases chapter

The Atlases chapter (chapterType=14) contains the actual image data. It's not compressed at the chapter level; Apple's decoder checks for this and throws an error if compressedSize != 0.

struct AtlasesChapter {
    u32 numAtlases;
    Atlas atlases[numAtlases];
};
struct Atlas {
    u32 length;
    u8 pngData[length];
};

The bytes at pngData contain a PNG image.

In theory, you could find the atlas at index N by skipping over the previous N-1 atlases using their length. However, Apple's decoder uses the AtlasIndex list in the ImageInfo chapter, so the length and numAtlases fields here are not actually used.

IconConfigPack format

Config packs are resource pack files that contain definitions of icons. They use header name "ICONDATAPACK", and have four chapters:

  • 1: PackInfo
  • 11: PropertyInfo
  • 15: IconList
  • 16: IconData

The structure of IconData is quite complex. It contains multiple groups, each has multiple icons, each icon has multiple layers of different types, and each layer has multiple properties.

Layer property values can have different data types. The property types are:

Type ID Length Description
0 1 byte bool (0/1)
1 4 bytes float32
2 4 bytes uint32, often an enum
3 8 bytes 2D point or size, as a pair of float32
4 16 bytes unknown, not seen in use
5 4 bytes color, as 8-bit integer RGBA components
6 variable null-terminated string

PropertyInfo chapter

This chapter (chapterType=11) contains the list of property types used in the file and their lengths. It's used for forward compatibility. If the IconData chapter has a property type that isn't recognized by the decoder, the length in this chapter allows it to skip over the properties of that type correctly. In addition, a "known" property type with an unexpected length will also cause all properties of that type to be skipped and not parsed.

The format is:

struct PropertyInfoChapter {
    u8 numProperties;
    PropertyTypeInfo types;
};
struct PropertyTypeInfo {
    u8 type;
    u8 length;
};

Property type 6 (string) has variable length, since it's null-terminated. It's listed here with length 0.

All known iconconfigpack files have the same content in this chapter, matching the table of property types mentioned above. In hex:

07  00 01  01 04  02 04  03 08  04 10  05 04  06 00

IconList chapter

This chapter (chapterType=15) is an index into the IconData chapter. Some strings in Apple's code call it "Icon Info Chapter" (which seems confusing, since the actual icon information is in IconData), or "IconDataLocations".

It contains a list mapping from "icon location indexes" to byte offsets. A location index consists of (sizeGroup, dataVariant, variant), ie. an IconKey without the IconID.

struct IconListChapter {
    u32 numDataLocations;
    IconDataLocation locations[numDataLocations];
};
struct IconDataLocation {
    u8 sizeGroup;
    #if (packVersion >= 3)
    u16 dataVariant;
    #endif
    u16 variant;
    u32 bytePosition;
};

This means the data for all icons with a given (sizeGroup, dataVariant, variant) is at bytePosition within the IconData chapter content.

The dataVariant field only exists if packVersion >= 3 in the PackInfo chapter. Previous versions did not have a dataVariant at all, meaning IconDataLocation was smaller.

IconData chapter

The IconData chapter (chapterType=16) contains the actual information about the icons. It's not compressed at the chapter level (ie. compressedLength == 0), although it seems Apple's decoder doesn't check for this. Instead it has multiple individually-compressed sections.

The chapter payload is thus a concatenation of:

struct IconDataSection {
    u64 rawSize;
    u64 compressedSize;
    u8 data[]; // zlib-compressed
};

The bytePosition field in the IconDataLocation index points at these sections, relative to the beginning of the IconData chapter content. This means the first IconDataLocation.bytePosition is 0.

Decompressing an IconDataSection payload leads to a deeply nested structure.

struct IconDataSectionContent {
    u32 numIcons;
    IconData icons[numIcons];
}
struct IconKey {
    u32 iconID;
    #if (packVersion >= 3)
    u16 dataVariant;
    #endif
    u16 variant;
    u8 sizeGroup;
};
struct IconData {
    u32 keySize;
    IconKey iconKey; // of size keySize

    u32 dataSize;
    // everything below of size dataSize
    u8 imageCount;
    IconDataImage images[imageCount];
    u8 layerCount;
    Layer layers[layerCount];
};

The dataVariant field only exists if packVersion >= 3 in the PackInfo chapter.

The iconKey field has its size specified by keySize, so in a decoder program you can read keySize bytes and pass that to your IconKey decoder. Perhaps this allows adding new fields to IconKey without breaking compatibility; but dataVariant was already added in an incompatible way...

Similarly, the size of the rest of the structure is given by dataSize, which allows skipping to the next IconData without parsing the contents.

Images

An IconData has one or more image definitions with this format:

struct IconDataImage {
    u16 imageLayerIndex;
    u32 imageID;
    float imageSizeX;
    float imageSizeY;
};

The imageID field identifies an image in the paired icondatapack file. The imageLayerIndex field specifies which of the layers this image is used in.

Unclear why this is a separate structure instead of putting the imageID field in the ImageLayer directly.

Layers

After the images, there is a list of layers.

enum LayerType {
    ColorLayer = 0;
    PathLayer  = 1;
    ImageLayer = 2;
    TextLayer  = 3;
    InfoLayer  = 4;
};
struct Layer {
    u16 layerType;
    u16 layerIndex;
    u32 dataLength;
    LayerData layerData; // of length dataLength
};

The layerIndex appears to be per layer type, because icons seem to have eg. one PathLayer, one ImageLayer, and one InfoLayer, all with layerIndex=0.

The different LayerTypes are handled by different C++ classes in Apple's code, but the binary format in the file is the same for all layer types. At this level, the only difference is what subset of properties they have.

Each layer has a list of key-value properties. They are identified by a numeric "property ID", and have a "property type".

The properties are grouped by property type. For example, an InfoLayer may have these properties:

alternateImageFamily     = uint32 0
calloutShape             = uint32 0
scale                    = float  1.0
calloutFillColor         = color  (248, 149, 64, 255)
labelTextColor           = color  (222, 126, 52, 255)
labelAnnotationIconColor = color  (0, 0, 0, 255)

But for some reason, the file format goes out of its way to avoid repeating the types:

uint32s:
    alternateImageFamily     = 0
    calloutShape             = 0
floats:
    scale                    = 1.0
colors:
    calloutFillColor         = (248, 149, 64, 255)
    labelTextColor           = (222, 126, 52, 255)
    labelAnnotationIconColor = (0, 0, 0, 255)

(This seems pointless, it would save 1 or 2 bytes per property, but the whole section is zlib-compressed so it's likely less than that...)

The binary format for properties is as follows:

struct LayerData {
    u16 numPropertyTypes;
    PropertyGroup types[numPropertyTypes];
};
struct PropertyGroup {
    u16 propertyType;
    u16 propertyCount;
    PropertyEntry val[propertyCount];
};
struct PropertyEntry {
    u16 propertyID;
    switch (propertyType) {
      case 0: u8 value; // 0 or 1 boolean
      case 1: float value;
      case 2: u32 value;
      case 3: float x, y;
      case 5: u8 r, g, b, a;
      case 6: char[] value;
    }
};

(I'm doing some more C-like syntax abuse than usual here, hopefully this is understandable)

This is where the information in the PropertyInfo chapter comes in: If propertyType == 1 and the PropertyInfo chapter says its length is 4, then the values are 4-byte floats and we can parse them. If the PropertyInfo chapter says type 1 has a length different from 4, then we don't know how to parse the property values, but we can use that length to skip over the whole PropertyGroup and move on to the next one.

Similarly, if we see propertyType == 7, we have no idea what that is, but the PropertyInfo chapter says how long the values are, so we can safely skip them.

Property IDs

Prop ID Type Name
00 float (1) scale
01 point (3) fillSize
02 float (1) haloWeight
03 float (1) shadowWeight
04 point (3) shadowOffset
05 color (5) fillColor
06 color (5) haloColor
07 color (5) shadowColor
08 uint32 (2) blendMode
09 uint32 (2) imageStretchType
10 point (3) horizontalPadding
11 point (3) verticalPadding
12 bool (0) changeHaloAndShadowOrder
13 float (1) cornerRadius
14 float (1) tailDirection
15 bool (0) visible
16 color (5) gradientStartColor
17 color (5) gradientEndColor
18 uint32 (2) fillType
19 uint32 (2) alternateImageTextLimit
20 uint32 (2) alternateImageFamily
21 uint32 (2) shapeType
22 float (1) fontSize
23 float (1) nonDigitTextFontSize
24 string (6) fontName
25 string (6) nonDigitTextFontName
26 color (5) nonDigitTextColor
27 color (5) calloutHaloColor
28 string (6) text
29 float (1) nonDigitTextHaloWeight
30 color (5) nonDigitTextHaloColor
31 point (3) textPosition
32 float (1) gradientStartLocation
33 float (1) gradientEndLocation
34 color (5) calloutFillColor
35 uint32 (2) fillColorSource
36 point (3) horizontalStretchPadding
37 uint32 (2) calloutShape
38 color (5) labelTextColor
39 float (1) gradientAngle
40 uint32 (2) gradientType
41 color (5) calloutTextColor
42 point (3) imageOffset
43 bool (0) isClipPath
44 color (5) labelAnnotationIconColor
45 bool (0) useShapeRect
46 float (1) opacity
47 color (5) clusterElementHaloColor
48 uint32 (2) imageDataSource