Skip to content

Instantly share code, notes, and snippets.

@unixzii
Created April 21, 2026 11:07
Show Gist options
  • Select an option

  • Save unixzii/bd34798f73d14204410cd38c74575107 to your computer and use it in GitHub Desktop.

Select an option

Save unixzii/bd34798f73d14204410cd38c74575107 to your computer and use it in GitHub Desktop.
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.
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

Extract CAR Images

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.

Outcome

Build a small macOS command-line tool or utility that:

  • opens a target Assets.car file
  • 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

Key Insight

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

Platform Constraints

  • 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.

Private APIs And Runtime Surface

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 .car file and constructs the in-memory CoreUI catalog.
  • CUICatalog -> enumerateNamedLookupsUsingBlock: Enumerates each named lookup object (CUINamed***) contained in the catalog.
  • named lookup object -> name Returns the logical asset name used as the default output stem.
  • CUINamedImage -> image Materializes a raster asset as an image object that can be bridged to CGImageRef and exported with ImageIO.
  • CUINamedData -> _rendition Returns the underlying rendition object, primarily needed when the lookup is data-backed rather than directly image-backed.
  • _CUIThemeSVGRendition -> svgDocument Returns the SVG document handle for SVG-backed renditions.
  • CoreSVG framework private function CGSVGDocumentWriteToData Serializes 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.

Core Flow

  1. Validate that the input path exists and points to a .car file.
  2. Create a file URL for the catalog path.
  3. Call CUICatalog -> initWithURL:error: to load the catalog.
  4. Create or clear the output directory.
  5. Call CUICatalog -> enumerateNamedLookupsUsingBlock: to enumerate named lookups.
  6. For each lookup:
  • call lookup -> name
  • inspect the runtime class name
  • branch by resource kind
  1. If the object is a named raster image, then use the raster export strategy to export it.
  2. If the object is a named data, then use the SVG export strategy to export it.
  3. For unsupported resource kinds, then log and continue.
  4. Return a nonzero exit status only for catalog-open failure or unrecoverable filesystem failure.

Raster Export Strategy

For bitmap assets, the practical route is:

  • call CUINamedImage -> image (which returns a CGImageRef)
  • use ImageIO CGImageDestinationCreateWithURL + CGImageDestinationAddImage + CGImageDestinationFinalize to 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.

SVG Export Strategy

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.

CGSVGDocumentWriteToData In Practice

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:

  • NSMutableData is 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 NSMutableData instance already contains the full XML payload and can be written directly to disk

Guardrails

  • If svgDocument is NULL, treat the asset as failed or unsupported and continue.
  • If data.length == 0 after serialization, log it as a failed SVG export instead of writing an empty file.
  • Do not assume every CUINamedData rendition is SVG; keep the _CUIThemeSVGRendition runtime check.
  • Prefer per-asset buffers and per-asset error logging so one malformed rendition does not stop the full export.

Naming And Directory Layout

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.

Runtime Robustness

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.

What To Avoid

  • Do not parse the .car binary 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.

Suggested Implementation Tactics

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 id for 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.

Validation Checklist

Before considering the extractor complete, verify that it can:

  • open an arbitrary .car file 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment