In a previous article, I reverse-engineered the .car file format used to store the compiled assets of an Asset Catalog. I also demonstrated how to create a tool to manually parse such files. While this tool can extract a lot of information, it is cumbersome to use if you want to quickly see all the assets contained in a car file.

In this article, I explain how to leverage the private CoreUI.framework to build a QuickLook plugin allowing us to inspect the content of car files:

The QLCARFiles QuickLook plugin can be downloaded at the end of this article, as well as the complete source code. A command line tool is also available to dump the assets to disk.

Table of contents:

Purpose

In order to analyze iOS and macOS apps, I needed a tool to quickly view the content of car files: a QuickLook plugin was exactly what I needed. In fact I wrote this QuickLook plugin several years ago and kept improving it with each macOS and iOS release. If you read some old articles on this blog, you might have seen screenshots of this QuickLook plugin. For example when I analyzed the Facebook.app for iOS [v. 87.0].

https://blog.timac.org/2017/0410-analysis-of-the-facebook-app-for-ios-v-87-0/

With this tool, I also discovered that Slack for iOS was containing several pictures of animals… Slack quickly exterminated all these puppies in their next release:

https://twitter.com/timacfr/status/841756620531093505

Recap about Asset Catalogs and car files

An Asset Catalog is an important piece of any iOS, tvOS, watchOS and macOS application. It lets you organize and manage the different assets used by an app, such as images, sprites, textures, ARKit resources, colors and data.

When a developer builds the app using Xcode, the asset catalogs containing the various assets (images, icons, textures, …) are not simply copied to the app bundle but they are compiled as car files. The resulting car file contains the asset variations (also called renditions):

  • @1x, @2x, @3x resolutions
  • Dark Mode or Light Mode

If you are interested about all the details of the car file format, you can read: Reverse engineering the .car file format (compiled Asset Catalogs)

Getting the asset variations

The CoreUI.framework has a simple API to load and extract the assets from a car file:

@interface CUICatalog : NSObject

- (id)initWithURL:(NSURL *)url error:(NSError **)error;
- (void)enumerateNamedLookupsUsingBlock:(void (^)(CUINamedLookup *namedLookup))block;

@end

We can create a CUICatalog object using -[CUICatalog initWithURL:error:] and then use -[CUICatalog enumerateNamedLookupsUsingBlock:] to execute a given block for each asset found. Thus the core of an application to dump all the assets consists of just 2 lines of code:

CUICatalog *catalog = [[CUICatalog alloc] initWithURL:[NSURL fileURLWithPath:inCarPath] error:nil];
[catalog enumerateNamedLookupsUsingBlock:^(CUINamedLookup *namedLookup)
{
	// Dump the namedLookup
}];

Et voilĂ ! But as you can imagine, there is a little more to do:

  • in the block, we get a CUINamedLookup object representing the asset. We need to access the actual data (CGImageRef, CGColor, NSData, …).

  • the QuickLook plugin needs some code to generate the preview/thumbnail of the assets.

  • for the command line tool to dump the assets to disk, we need to find a proper name for the asset… which is not as easy as it sounds.

The CUINamedLookup base class

The -[CUICatalog enumerateNamedLookupsUsingBlock:] method executes a given block for each asset found. It has a single parameter, a CUINamedLookup object, which contains a lot of interesting properties:

@interface CUINamedLookup : NSObject

@property(readonly) NSString *name;
@property(readonly) NSString *renditionName;

@property(readonly) kCoreThemeIdiom idiom;
@property(readonly) uint64_t subtype;
@property(readonly) kCoreThemeUISizeClass sizeClassHorizontal;
@property(readonly) kCoreThemeUISizeClass sizeClassVertical;
@property(readonly) kCoreThemeFeatureSetMetalFamily graphicsClass;
@property(readonly) kCoreThemeMemoryClass memoryClass;
@property(readonly) kCoreThemeDisplayGamut displayGamut;
@property(readonly) NSString *appearance;

@end

Let’s analyze the different properties. They correspond to the attributes you can set in Xcode.

name and renditionName

The name corresponds to the name of the asset as it appears in Xcode. The renditionName is the filename of the original file dragged into Xcode… but not always.

For example a @1x asset generally looks like:

name: MyPNG
renditionName: Timac.png

and a @2x asset is:

name: MyPNG
renditionName: Timac@2x.png

However for PDFs stored as pdf, the renditionName uses the hardcoded string CoreStructuredImage:

name: MyPDF
renditionName: CoreStructuredImage

For images created from a PDF with the Preserve Vector Data flag, the renditionName is set to a random unique identifier:

name: MyPNG
renditionName: 52A94C15-AF59-4DBB-8EE9-EE1A8FDF1FDB@1x.pdf

Due to these details, I decided not to rely on the renditionName but to generate an appropriate filename from the name.

idiom

The idiom corresponds to the device family and is an enum similar to the well-known UIUserInterfaceIdiom:

typedef NS_ENUM(NSInteger, kCoreThemeIdiom)
{
	kCoreThemeIdiomUniversal = 0,
	kCoreThemeIdiomPhone,
	kCoreThemeIdiomPad,
	kCoreThemeIdiomTV,
	kCoreThemeIdiomCar,
	kCoreThemeIdiomWatch,
	kCoreThemeIdiomMarketing,
	
	kCoreThemeIdiomMax
};

static const char* const kCoreThemeIdiomNames[kCoreThemeIdiomMax] = { "", "phone", "pad", "tv", "car", "watch", "marketing" };

Don’t ask me what is kCoreThemeIdiomMarketing… It seems that the Apple engineers working on assetutil are not sure neither: "Marketing idiom? What were you thinking?"

subtype

The subtype is mainly used to differentiate the Apple Watch models. It contains heights like 320, 340, 384 or 390.

sizeClassHorizontal and sizeClassVertical

The size class for the image width and height is similar to UIUserInterfaceSizeClass:

typedef NS_ENUM(NSInteger, kCoreThemeUISizeClass)
{
	kCoreThemeUISizeClassUnspecified = 0,
	kCoreThemeUISizeClassCompact,
	kCoreThemeUISizeClassRegular,
	
	kCoreThemeUISizeClassMax
};

static const char* const kCoreThemeUISizeClassNames[kCoreThemeUISizeClassMax] = { "", "compact", "regular" };

graphicsClass

This property corresponds to the Metal features:

typedef NS_ENUM(NSInteger, kCoreThemeFeatureSetMetalFamily)
{
    kCoreThemeFeatureSetMetalFamilyDefault  = 0,
    kCoreThemeFeatureSetMetalFamily1v2,
    kCoreThemeFeatureSetMetalFamily2v2,
    kCoreThemeFeatureSetMetalFamily3v1,
    kCoreThemeFeatureSetMetalFamily3v2,
    kCoreThemeFeatureSetMetalFamily4v1,
    kCoreThemeFeatureSetMetalFamily5v1,
	
    kCoreThemeFeatureSetMetalFamilyMax
};

static const char* const kCoreThemeFeatureSetMetalFamilyNames[kCoreThemeFeatureSetMetalFamilyMax] = { "", "MTL1,2", "MTL2,2", "MTL3,1", "MTL3,2", "MTL4,1", "MTL5,1" };

memoryClass

This property indicates the minimum device memory configuration required:

typedef NS_ENUM(NSInteger, kCoreThemeMemoryClass)
{
    kCoreThemeMemoryClassLow = 0,
    kCoreThemeMemoryClass1GB,
    kCoreThemeMemoryClass2GB,
    kCoreThemeMemoryClass4GB,
    kCoreThemeMemoryClass3GB,
    kCoreThemeMemoryClass6GB,
	
    kCoreThemeMemoryClassMax
};

static const char* const kCoreThemeMemoryClassNames[kCoreThemeMemoryClassMax] = { "", "1GB", "2GB", "4GB", "3GB", "6GB" };

displayGamut

This property indicates the color gamut:

typedef NS_ENUM(NSInteger, kCoreThemeDisplayGamut)
{
    kCoreThemeDisplayGamutSRGB = 0,
    kCoreThemeDisplayGamutP3,
	
    kCoreThemeDisplayGamutMax
};

static const char* const kCoreThemeDisplayGamutNames[kCoreThemeDisplayGamutMax] = { "sRGB", "P3" };

appearance

The appareance corresponds to the appearance names to support for example Dark Mode on macOS Mojave. Some example of values are:

  • NSAppearanceNameSystem
  • NSAppearanceNameDarkAqua
  • NSAppearanceNameAccessibilitySystem
  • NSAppearanceNameAccessibilityDarkAqua

The CUINamedLookup subclasses

For each type of rendition, there is a corresponding CUINamedLookup subclass:

  • CUINamedImage
  • CUINamedData
  • CUINamedLayerStack
  • CUINamedImageAtlas
  • CUINamedExternalLink
  • CUINamedTexture
  • CUINamedColor
  • CUINamedModel
  • CUINamedRecognitionImage
  • CUINamedRecognitionGroup
  • CUINamedRecognitionObject
  • CUINamedVectorImage
  • CUINamedMultisizeImageSet

CUINamedImage has also 2 subclasses:

  • CUINamedMultisizeImage
  • CUINamedLayerImage

Let’s look at 3 of these subclasses.

CUINamedImage

The CUINamedImage class is used for images like PNGs and has an API to retrieve the CGImageRef:

@interface CUINamedImage : CUINamedLookup

@property(readonly) CGSize size;
@property(readonly) CGFloat scale;

-(CGImageRef)image;

@end

CUINamedColor

The CUINamedColor class is used for named color and has an API to retrieve the CGColorRef:

@interface CUINamedColor : CUINamedLookup

@property (readonly, nonatomic) CGColorRef cgColor;
@property (readonly, nonatomic) NSString *systemColorName;

@end

To view the color in the QuickLook plugin, I create a small image filled with the CGColorRef.

CUINamedData

The CUINamedData class is used for raw data like pdf, video, gif, text files, … It has an API to retrieve the NSData and UTI:

@interface CUINamedData : CUINamedLookup

@property (readonly, copy, nonatomic) NSString * utiType;
@property (readonly, copy, nonatomic) NSData * data;

@end

Depending on the UTI, the QuickLook plugin displays:

  • an image (png, pdf, svg, …)
  • an animated gif
  • the first frame of the video
  • a placeholder image for other unsupported data

The QLCARFiles QuickLook plugin

The QLCARFiles QuickLook plugin lets you visualize the content of a car file. It extracts the assets using the CoreUI.framework, generates a webpage with all the images, and displays this webpage in the QuickLook window. The use of a webpage has several advantages, one of them being the possibility to render multiple images in a scrolling view.

Features

  • Display images and their dimensions and file sizes
  • Support for png, pdf, gif, svg, video, …
  • Display named colors and their hex values
  • Support for Light and Dark mode
  • Generate the previews as HTML data to allow scrolling
  • Display the total number of assets in the window’s title
  • Add a light gray background to images that are too white or transparent
  • For thumbnails, render the best asset as icon and the number of assets
  • The command line tool has a -r option to dump all the car files found in a folder

Known limitations

  • No support for special car files used by pro applications like Final Cut Pro.
  • Assets of the following type are not supported: CUINamedExternalLink, CUINamedTexture, CUINamedModel, CUINamedRecognitionImage, CUINamedRecognitionGroup, CUINamedRecognitionObject, CUINamedVectorImage, CUINamedMultisizeImage, CUINamedLayerImage. So far I never encountered these types of renditions.

Downloads

macOS 10.14 or later is required. Please note that the precompiled binaries are not code signed. To install the precompiled QuickLook plugin, you should:

  1. Download and unzip QLCARFiles.qlgenerator.zip
  2. Remove the quarantine flag by running in the Terminal xattr -c -r QLCARFiles.qlgenerator
  3. Move QLCARFiles.qlgenerator to ~/Library/QuickLook/QLCARFiles.qlgenerator
  4. Execute in the Terminal qlmanage -r to make sure the QLCARFiles plugin is loaded

Alternatively you can sign the precompiled binaries or recompile the application yourself.