| name | extract-car-images |
|---|---|
| description | Use this skill when you need to build or explain a macOS-only tool that extracts image assets from an Apple asset catalog `.car` file. |
| version | 1.0.0 |
Use this skill when the user needs the core capability of exporting all image resources from an Apple .car asset catalog. The intended environment is macOS with Objective-C tooling available. This skill is about reproducing the capability, not reproducing any specific project structure.
Build a small macOS command-line tool or utility that:
- opens a target
Assets.carfile - asks CoreUI to enumerate named resources
- identifies image-bearing entries
- exports raster assets as PNG or another chosen bitmap format
- exports SVG-backed assets by serializing the vector document
Do not start by reverse-engineering the .car file format. The higher-leverage path is to let Apple’s private CoreUI implementation decode the catalog for you, then interact with the in-memory resource objects through Objective-C runtime calls, private selectors, and KVC where needed.
This is the main technique to preserve:
- the catalog file is opened by a CoreUI catalog object
- named lookups are enumerated from that catalog
- each named lookup is inspected by runtime class name
- image-bearing objects are exported according to their rendition type
- This is macOS-only.
- It depends on private Apple APIs and undocumented selectors.
- It is suitable for internal tooling, research, migration, or debugging.
- It is not suitable for App Store distribution.
- Behavior may drift across macOS releases, so runtime checks matter.
The implementation relies on CoreUI concepts that are normally not exposed in public SDK headers. In practice, agents should bridge only the minimum surface they need and prefer loose coupling over full private header reconstruction.
Critical class and method mapping:
CUICatalog->initWithURL:error:Opens the target.carfile and constructs the in-memory CoreUI catalog.CUICatalog->enumerateNamedLookupsUsingBlock:Enumerates each named lookup object (CUINamed***) contained in the catalog.- named lookup object ->
nameReturns the logical asset name used as the default output stem. CUINamedImage->imageMaterializes a raster asset as an image object that can be bridged toCGImageRefand exported with ImageIO.CUINamedData->_renditionReturns the underlying rendition object, primarily needed when the lookup is data-backed rather than directly image-backed._CUIThemeSVGRendition->svgDocumentReturns the SVG document handle for SVG-backed renditions.- CoreSVG framework private function
CGSVGDocumentWriteToDataSerializes the SVG document handle into raw bytes for disk output.
Treat these as runtime observations, not stable contracts. Avoid overfitting to more private classes than necessary.
- Validate that the input path exists and points to a
.carfile. - Create a file URL for the catalog path.
- Call
CUICatalog->initWithURL:error:to load the catalog. - Create or clear the output directory.
- Call
CUICatalog->enumerateNamedLookupsUsingBlock:to enumerate named lookups. - For each lookup:
- call lookup ->
name - inspect the runtime class name
- branch by resource kind
- If the object is a named raster image, then use the raster export strategy to export it.
- If the object is a named data, then use the SVG export strategy to export it.
- For unsupported resource kinds, then log and continue.
- Return a nonzero exit status only for catalog-open failure or unrecoverable filesystem failure.
For bitmap assets, the practical route is:
- call
CUINamedImage->image(which returns aCGImageRef) - use ImageIO
CGImageDestinationCreateWithURL+CGImageDestinationAddImage+CGImageDestinationFinalizeto write a PNG file
Why PNG:
- simple and broadly supported
- preserves alpha
- avoids guessing the original compressed representation hidden in the catalog
Do not assume every named image yields a raster image. Some named image entries may represent vector-backed assets or configurations that do not materialize as CGImage through the chosen selector. Those should be treated as “not exportable through the raster path” and retried only if you add vector-aware handling.
Some image resources are stored as data renditions rather than as readily rendered bitmaps. A notable case is SVG-backed assets.
Reliable flow:
- call
CUINamedData->_rendition - detect the SVG rendition class by runtime name
- call
_CUIThemeSVGRendition->svgDocument - serialize it through
CGSVGDocumentWriteToData - persist the bytes to disk
Use the rendition’s raw asset name or extension metadata if available to preserve the correct output suffix. If that metadata is missing, default to .svg.
The SVG serialization bridge should be declared like this:
void CGSVGDocumentWriteToData(void *document, CFDataRef data, CFDictionaryRef options);That declaration is intentionally minimal because the function is private. The practical interpretation is:
- argument 1: the SVG document handle returned by
_CUIThemeSVGRendition->svgDocument. Treat it as opaque. Do not cast it to a richer type unless you have a version-specific private header and a strong reason to depend on it. - argument 2: a mutable data buffer that receives the serialized SVG bytes. Use a fresh mutable buffer per asset.
NSMutableData *data = [NSMutableData data];is enough. Reusing one buffer across assets is possible but only if you clear it between writes and keep the export loop strictly serialized. - argument 3: optional serialization options, usually passed as
NULL.
Even though the local declaration uses CFDataRef, the call site should pass a mutable buffer.
Why this works:
NSMutableDatais toll-free bridged with Core Foundation data types- Core Graphics appends the SVG output into that mutable buffer
- after the call returns, the Objective-C
NSMutableDatainstance already contains the full XML payload and can be written directly to disk
- If
svgDocumentisNULL, treat the asset as failed or unsupported and continue. - If
data.length == 0after serialization, log it as a failed SVG export instead of writing an empty file. - Do not assume every
CUINamedDatarendition is SVG; keep the_CUIThemeSVGRenditionruntime check. - Prefer per-asset buffers and per-asset error logging so one malformed rendition does not stop the full export.
Use the asset’s logical name as the primary identifier, but do not assume it is globally unique in all dimensions. Safer exporters include enough variant information to avoid collisions.
Recommended naming policy:
- base stem: logical asset name
- optional suffixes: idiom, scale, appearance, memory class, size class, subtype, state
- extension: derived from export format
If the lookup name contains path separators, either:
- preserve them intentionally to mirror namespace-like grouping, or
- sanitize them deterministically if a flat output layout is required
Always ensure the parent directories exist before writing.
Because the APIs are private, the implementation should be defensive:
- check selectors with
respondsToSelector:before calling them when practical - treat class-name checks as string comparisons rather than compile-time type assumptions
- use KVC only for fields that are demonstrably needed
- keep failures per-asset, not global
- log unknown classes and rendition classes for future extension
Prefer minimal private surface area. For example, if a field or object is not necessary for extraction, do not depend on it just because it exists.
- Do not parse the
.carbinary directly unless the user explicitly wants a format parser. - Do not require a full AppKit GUI lifecycle if a command-line tool is enough.
- Do not assume that all images are raster or all data renditions are SVG.
- Do not silently overwrite colliding outputs without either disambiguation or logging.
- Do not rely on one macOS version’s private class graph as immutable truth.
Prefer a plain command-line macOS tool instead of a Cocoa app lifecycle. Use Objective-C with Foundation, AppKit or CoreGraphics bridging as needed, and ImageIO for bitmap encoding.
A practical strategy for missing headers:
- declare only the classes, selectors, and properties you need
- use forward declarations and
idfor private object types - use runtime class-name checks instead of full private type modeling
This keeps the code resilient and minimizes breakage from undocumented details.
Before considering the extractor complete, verify that it can:
- open an arbitrary
.carfile supplied at runtime - enumerate named lookups without crashing
- export ordinary bitmap assets successfully
- export SVG-backed assets successfully
- create missing output directories
- skill unknown resource types