A couple of years ago when I was leading development efforts over at the now defunct T-Cats, I’ve used Panic’s StatusBoard - An iPad dashboard app designed to be used on a large TV - as our office dashboard, displaying the number of waiting pull requests, our team calendar and some server response time graphs among other things.

The shape of the room made it so I couldn’t really see the board from my desk. THAT SUCKED. I had to come up with a solution.

I wasn’t going to move the monitor or my desk, and I wasn’t going get another iPad, Nope. My plan was much more fun! Use StatusBoard’s export feature, re-implement StatusBoard as a Mac app and load the exported board on my MacBook. Sounds simple enough.

Surprisingly, It was.

First thing first, I had to figure out the structure of the panicBoard file. I made the educated guess of it being a plist, since it’s the easiest way to serialize objects when working with Cocoa, and Indeed it was. As confirmed by the file command:

# jorge at Jorges-MacBook-Pro.local in ~/Development/BoardViewer on git:master ✖︎ [18:46:09]
→ file StatusBoard.panicboard
StatusBoard.panicboard: Apple binary property list

So, I changed the extension to plist and opened it with Xcode and… Looks like this was created using NSKeyedArchiver panicBoard in Xcode

How the hell am I going to read this?! Google search cocoa - How to parse the contents of a foreign file created with NSKeyedArchiver - Stack Overflow

Oh, Thanks StackOverflow.

Basically this consisted of unarchiving the file, setting my class as a delegate of NSKeyedUnarchiver

NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
unarchiver.delegate = self;
id graph = [unarchiver decodeObjectForKey:@"root"];

And in the delegate implementing -unarchiver:cannotDecodeObjectOfClassName:originalClasses: which gets called whenever the unarchiver tries to decode a missing class.

- (Class)unarchiver:(NSKeyedUnarchiver *)unarchiver cannotDecodeObjectOfClassName:(NSString *)name originalClasses:(NSArray *)classNames
{
    NSLog(@"wants: %@", name);
    return nil;
}

I started getting class names printed to the console! I knew which classes had to be re-implemented!

At this point, to make things easier I took StatusBoard’s executable and did two things:

  1. Used class-dump to get header files for all of the classes used by StatusBoard
  2. Used Hopper to get some idea on how everything was implemented.

Whenever NSKeyedUnarchiver complained about a missing class, I used the header file from class-dump to create a stub class. After I had all of the classes I needed to unarchive the board file I started to go over the disassembled executable, filling in my stubs whenever I realized how something worked.

Working iteratively I first implemented the portions of code necessary to position widgets on the board. Obviously non of the widgets did anything, I used a plain yellow view as a placeholder: Initial view

Quickly after getting proper layout I started implementing widget logic: Widgets Functioning board

Eventually I had something that was 80% complete in about two days of work, I never got around to implementing all of the widgets StatusBoard offered and I’m pretty sure my BoardViewer wasn’t the most stable, as I’ve only tested it with one board file - But the experience was fun. I really liked StatusBoard, too bad Panic discontinued the product.