122 lines
10 KiB
Markdown
122 lines
10 KiB
Markdown
|
```
|
||
|
title = "Clarus Returns Home"
|
||
|
tags = ["misc"]
|
||
|
date = "2022-06-14 10:11:42 -0400"
|
||
|
short_desc = "Did you know Clarus the Dogcow is hiding in the macOS Ventura beta?"
|
||
|
slug = "clarus"
|
||
|
card_image_path = "/2022/clarus/clarus-smooth.png"
|
||
|
```
|
||
|
|
||
|
<figure>
|
||
|
<div style="display: flex; flex-direction: row; align-items: center; background-color: white;">
|
||
|
<img src="/2022/clarus/clarus-kare.png" alt="Susan Kare's pixel art dogcow icon" style="width: 50%; image-rendering: pixelated;">
|
||
|
<img src="/2022/clarus/clarus-smooth.png" alt="The high resolution dogcow icon that ships with macOS Ventura" style="width: 50%;">
|
||
|
</div>
|
||
|
<figcaption>How it started / How it's going</figcaption>
|
||
|
</figure>
|
||
|
|
||
|
Did you know that with macOS Ventura, Clarus the Dogcow has at long last returned home? Recently, while doing something else, I accidentally hit Cmd+Shift+P which opened the Page Setup dialog. I was greeted, surprisingly, with a new high-resolution version of the classic Clarus icon that I'd never seen before. I looked at it briefly, and then closed the dialog and went back to whatever I was doing before. I had assumed that because I'd been in a 3rd-party app at the time, that the Clarus icon was just some easter egg the developer had left. But a little while later, I got to thinking. What were the chances that someone went to the trouble of customizing the Page Setup dialog, of all things, just for an easter egg? Zero, it turns out. That dialog shows Clarus on the page preview in every app.
|
||
|
|
||
|
<!-- excerpt-end -->
|
||
|
|
||
|
<img src="/2022/clarus/page-setup.png" alt="The Page Setup dialog. The page preview on the left shows the high-resolution Clarus icon.">
|
||
|
|
||
|
I don't have a Monterey machine to test it at the moment (I, er, [accidentally](https://social.shadowfacts.net/notice/AKGSrBOxnVDVO0ueem) updated my laptop to the beta), but I _believe_ this is a new change with Ventura.
|
||
|
|
||
|
**Update:** I installed Monterey in a virtual machine to check, and, indeed, the Page Setup dialog there bears no sign of Clarus.
|
||
|
|
||
|
The next step, then—having been thoroughly nerd-sniped by this—was to figure out where the icon was coming from and if I could pull it out of whatever nook it was hidden in.
|
||
|
|
||
|
The first stop was [`NSPageLayout`](https://developer.apple.com/documentation/appkit/nspagelayout), the panel object that is responsible for displaying the panel. It was unlikely that the class would actually contain the implementation of the panel, but it was at least a starting point.
|
||
|
|
||
|
In order to actually look at the disassembled implementation of this AppKit class, I needed the actual AppKit framework binary. Since macOS Big Sur, all system framework binaries are stored merged in the `dyld` shared cache, rather than in separate files. But, I need them as separate files in order to actually inspect them.
|
||
|
|
||
|
Since the [last time](/2021/scrollswitcher/) I wrote about this, a couple things have changed. Before, I built the Apple `dyld_shared_cache_util` from one of the periodic `dyld` source dumps. This is annoying because you have to make a bunch of changes to the source code to get it to compile outside of an Apple-internal environment. It also may break whenever there's an OS update. So, I've switched to using [this utility](https://github.com/keith/dyld-shared-cache-extractor) which uses the `dyld_extractor.bundle` that ships with Xcode. The other difference since before is a minor one: the dyld shared cache has moved. Wheras before it was in `/System/Library/dyld/`, in the Ventura beta it's moved to `/System/Cryptexes/OS/System/Library/dyld/` (the Cryptex seems to be part of the [Rapid Security Response](https://threedots.ovh/blog/2022/06/a-quick-look-at-macos-rapid-security-response/) feature Apple announced).
|
||
|
|
||
|
With the shared cache extracted, I could load the AppKit binary into Hopper (I had to disable the Objective-C analysis, otherwise the app crashed when trying to load the binary) and start poking around. I searched for the `NSPageLayout` class that I'm interested in, and looked at the `runModalWithPrintInfo:` method, since that sounded like a good candidate for something that would lead to the bulk of the implementation. And, indeed, it was. The method appears to be a fairly simple wrapper around the `PMPrepare...` function that sounds like it lives in a separate private framework.
|
||
|
|
||
|
<div class="article-content-wide">
|
||
|
<img src="/2022/clarus/appkit.png" alt="Hopper window with AppKit showing the runModalWithPrintInfo: method">
|
||
|
</div>
|
||
|
|
||
|
<aside class="inline">
|
||
|
|
||
|
Curious about what those `_objc_msgSend$...` calls are? I would be to, if I haddn't watched the fascinating [Improve app size and runtime performance](https://developer.apple.com/wwdc22/110363) session from WWDC this year.
|
||
|
|
||
|
</aside>
|
||
|
|
||
|
The next step was figuring out where that prepare function is actually implemented. Running `otool -L` on the AppKit binary doesn't reveal anything obviously useful, but in the PrivateFrameworks directory extracted from the dyld shared cache, there's something called `PrintingPrivate.framework`, which sounds promising. Opening it up in Hopper, I saw that this is indeed the framework I was looking for.
|
||
|
|
||
|
<div class="article-content-wide">
|
||
|
<img src="/2022/clarus/printingprivate.png" alt="PrintingPrivate in Hopper showing the _PMPrepareAppKitPageSetupDialogWithPrintInfoPrivate function">
|
||
|
</div>
|
||
|
|
||
|
Looking at the implementation of the prepare function, what immediately jumps out is the call to `_LoadAndGetPrintingUIBundle`. This seems to be yet another layer of indirection with the actual thing implemented in a different bundle. There's also a call in the else branch to the similarly-named `_LoadAndGetPrintCocoaUIBundle`, but let's start with the first one in hopes that it's more common.
|
||
|
|
||
|
The implementation of that function goes through another helper function and it ends up loading a `PrintingUI.bundle` plugin from inside the PrintingPrivate framework bundle. This one isn't part of the dyld shared cache, so I can just open it right up in Hopper without any fuss.
|
||
|
|
||
|
If you look for the function PrintingPrivate calls, it turns out it winds up in a method on `PMPageSetupController`. This sounds promising, let's see what else that class can do.
|
||
|
|
||
|
What's this? A method called `updateClarus`? Could it be? Have we finally reached it?
|
||
|
|
||
|
<div class="article-content-wide">
|
||
|
<img src="/2022/clarus/printingui.png" alt="The PrintingUI binary in Hopper with the search panel showing a bunch of methods with 'clarus' in the name">
|
||
|
</div>
|
||
|
|
||
|
Yes! Clarus, I'm coming! One method that sounds particularly encouraging is `-[PMPageSetupController setClarusImageView:]`. If I can find out what's setting the image view, maybe that'll lead to where it's being configured with the image.
|
||
|
|
||
|
Unfortunately, the setter for that property isn't referenced anywhere in the PrintingUI binary. Nor is the getter. I was stuck here for a while, until I realized that the setter not being called anywhere was probably a sign that the UI was defined in a Nib and that an outlet was added from Interface Builder, even though it was never used.
|
||
|
|
||
|
Sure enough, in the plugin bundle's resources, there is a `PMPageSetup.nib`. And if the page setup UI is defined in a Nib, and Clarus is being shown in a image view, the image itself is probably located in the asset catalog.
|
||
|
|
||
|
Using the system `assetutil` program, one can list all of the files in a compiled asset catalog. And sure enough, there she is:
|
||
|
|
||
|
```shell
|
||
|
$ assetutil --info /System/Library/PrivateFrameworks/PrintingPrivate.framework/Versions/A/Plugins/PrintingUI.bundle/Contents/Resources/Assets.car | grep -i clarus
|
||
|
"Name" : "Clarus",
|
||
|
"RenditionName" : "ClarusSmooth2.pdf",
|
||
|
"Name" : "Clarus",
|
||
|
"RenditionName" : "ClarusSmooth2.pdf",
|
||
|
"Name" : "Clarus",
|
||
|
"RenditionName" : "ClarusSmooth2.pdf",
|
||
|
```
|
||
|
|
||
|
To actually extract the image from the asset catalog, I needed to use a third-party tool. [acextract](https://github.com/bartoszj/acextract) worked perfectly on the first try, though it did need couple of additional `@import`s to compile on Ventura since [Foundation no-longer re-exports CoreGraphics](https://twitter.com/illian/status/1534014772848365568).
|
||
|
|
||
|
<aside>
|
||
|
|
||
|
The `assetutil` manpage does contain a reference to a dump option (`-d`/`--dump`) which is curiously not present in the manpage nor is it recognized by the program. Perhaps an Apple-internal feature that is excluded from compilation in public builds?
|
||
|
|
||
|
</aside>
|
||
|
|
||
|
And with that, I finally gazed upon the 512 × 512px beauty that is Smooth Clarus:
|
||
|
|
||
|
<figure>
|
||
|
<img src="/2022/clarus/clarus-smooth.png" alt="Smooth Clarus">
|
||
|
</figure>
|
||
|
|
||
|
The version shown here I've added a white background to, so it's not invisible in dark mode. The <a href="/2022/clarus/Clarus256x256@2x.png" data-link="/2022/clarus/Clarus256x256@2x.png">original image</a> has a transparent background.
|
||
|
|
||
|
<aside class="inline">
|
||
|
|
||
|
The keen-eyed among you may notice that although, it had a `.pdf` extension in `assetutil` info, I've given it here as a PNG. I was confused by this too, but upon closer inspection I believe the PNG is what ships with the OS. Although the `.car` format is not documented, you can still open it up in a hex viewer and learn a bit about what it contains. Looking through it, there appears to be some metadata for each file, followed by the image data itself.
|
||
|
|
||
|
As `assetutil` showed, there are multiple entries for `ClarusSmooth2.pdf`—and one of them is followed by data that starts with the PDF file format header (`%PDF`). But, unfortunately, extracting that data into a separate file seems to result in a blank PDF. And I don't know enough about the format to figure out whether there is any vector data in it, or if it truly is empty.
|
||
|
|
||
|
**Update:** [Reid Ellis](https://mastodon.social/users/clith/statuses/108482914539340482) managed to extract the actual PDF data, so here's Clarus in all of the <a href="/2022/clarus/clarus.pdf" data-link="/2022/clarus/clarus.pdf">infinite smoothness</a>.
|
||
|
|
||
|
</aside>
|
||
|
|
||
|
Lastly, if you're writing a Mac app and would like to hide Clarus somewhere, you can load the bundle yourself and then pull the image out like so:
|
||
|
|
||
|
```swift
|
||
|
let bundle = Bundle(path: "/System/Library/PrivateFrameworks/PrintingPrivate.framework/Versions/A/Plugins/PrintingUI.bundle")!
|
||
|
try! bundle.loadAndReturnError()
|
||
|
let image = bundle.image(forResource: "Clarus")
|
||
|
```
|
||
|
|
||
|
I'm not sure if the Mac App Store would consider that using private SPI, so use it at your own risk.
|
||
|
|
||
|
It would be very cool to see Clarus return as an SF Symbol some day. If hundreds of icons for various Apple products can go in, so too can everyone's favorite dogcow.
|
||
|
|