VectorDesigner: The Resurrection
The Problem: VectorDesigner Dead
A few years ago I bought a vector drawing app called VectorDesigner and then I used it to make a bunch of stuff. Beautiful. Stuff. Then VectorDesigner and it’s developer kinda disappeared from the Internet. Time moved on, MacOS 10.7 and 10.8 came out, and the old VectorDesigner app that I still have on my Mac stopped working. It would just fail to launch, bouncing in the dock for a few moments then sagging into an apparent coma without ever showing a window or menubar. This new behavior meant that the many works of beautiful stuff that I lovingly crafted in VectorDesigner were completely inaccessible to me.
A GDB-based Resurrection
I needed a way to regain access to my VectorDesigner files. If I could get the app working again, I could export the files to EPS. EPS import support in the more recent drawing apps that I own (like Sketch and Artboard) is horrible, but having something semi-usable is better than having a bunch of unreadable files. So I started to investigate how to get VectorDesigner to work again.
My first attempt at diagnosis was to take a look at VectorDesigner under the GNU debugger, GDB.
$ cd /Applications/VectorDesigner.app/Contents/MacOS/
$ gdb VectorDesigner
GNU gdb 6.3.50-20050815 (Apple version gdb-1820) (Sat Jun 16 02:40:11 UTC 2012)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "x86_64-apple-darwin"...Reading symbols for shared libraries ..................... done
(gdb) run
Starting program: /Applications/VectorDesigner.app/Contents/MacOS/VectorDesigner
Reading symbols for shared libraries +++++++++++++++++++..................................................... done
At this point the app did the aforementioned bouncing, sagging nothing. I interrupted it (with Control-C) to take a look at the backtrace. The backtrace shows where VectorDesigner was executing (or hanging) and what functions/methods/messages got it there.
^C
Program received signal SIGINT, Interrupt.
0x936f091a in __psynch_mutexwait ()
(gdb) bt
#0 0x936f091a in __psynch_mutexwait ()
#1 0x97ce313b in pthread_mutex_lock ()
#2 0x91bb468e in dyldGlobalLockAcquire ()
#3 0x8fe0a3cd in __dyld_dlopen ()
#4 0x91bb5dbe in dlopen ()
#5 0x94f5dd8d in __CFLookupCoreServicesInternalFunction ()
#6 0x94f5dd44 in __CFURLBeginResourcePropertyCacheAccess ()
#7 0x94f5dca1 in -[NSURL getResourceValue:forKey:error:] ()
#8 0x98764ca1 in +[NSImageRep _imageRepsWithContentsOfURL:expandImageContentNow:giveUpOnNetworkURLsWithoutGoodExtensions:] ()
#9 0x98807840 in +[NSImageRep _imageRepsWithContentsOfURL:expandImageContentNow:] ()
#10 0x98807802 in +[NSImageRep imageRepsWithContentsOfFile:] ()
#11 0x98807712 in -[NSImage initWithContentsOfFile:] ()
#12 0xcf12388f in +[iMBMusicFolder initialize] ()
#13 0x92abf600 in _class_initialize ()
#14 0x92abf4c8 in prepareForMethodLookup ()
#15 0x92abf337 in lookUpMethod ()
#16 0x92abf2e1 in _class_lookupMethodAndLoadCache3 ()
#17 0x92abeac1 in objc_msgSend ()
#18 0x92abe4fa in call_load_methods ()
#19 0x92abe228 in load_images ()
#20 0x8fe01c32 in __dyld__ZN4dyldL12notifySingleE17dyld_image_statesPK11ImageLoader ()
#21 0x8fe10252 in __dyld__ZN11ImageLoader23recursiveInitializationERKNS_11LinkContextEjRNS_21InitializerTimingListE ()
#22 0x8fe101cc in __dyld__ZN11ImageLoader23recursiveInitializationERKNS_11LinkContextEjRNS_21InitializerTimingListE ()
#23 0x8fe100ba in __dyld__ZN11ImageLoader15runInitializersERKNS_11LinkContextERNS_21InitializerTimingListE ()
#24 0x8fe01e05 in __dyld__ZN4dyld24initializeMainExecutableEv ()
#25 0x8fe05adb in __dyld__ZN4dyld5_mainEPK12macho_headermiPPKcS5_S5_Pm ()
#26 0x8fe01376 in __dyld__ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm ()
#27 0x8fe01077 in __dyld__dyld_start ()
To read the backtrace chronologically, start at the bottom and work your way up. Those first bits look like OS plumbing and various loadings of various libraries, that is until about frame #12.
This frame looked a bit odd to me because VectorDesigner has nothing to do with music. Some Googling showed this bug report for an unrelated software product which uses the phrase, “the problem stems from the use of very old 3rd party code which I haven’t yet updated in the 2.6 beta, namely, the media browser as it tries to read the iTunes library.” It sounded like it might also apply to my search, because VectorDesigner has just such a media browser.
I decided to intercept the app’s attempt to access my music folder and make the access attempt fail. I set a breakpoint on the Objective-C class method in frame 12.
(gdb) break +[iMBMusicFolder initialize]
Breakpoint 1 at 0xcf1237b6
(gdb)
I also got some more info about this problematic class method:
(gdb) info symbol 0xcf1237b6
+[iMBMusicFolder initialize] + 24 in section LC_SEGMENT.__TEXT.__text of /Applications/VectorDesigner.app/Contents/Frameworks/iMediaBrowser.framework/Versions/A/iMediaBrowser
Googling “iMediaBrowser” revealed that VectorDesigner was leveraging The Karelia iMedia Browser framework. That was good to know in the event that I wanted to look at the iMedia framework’s source code. I tried running VectorDesigner with the new breakpoint:
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /Applications/VectorDesigner.app/Contents/MacOS/VectorDesigner
Reading symbols for shared libraries . done
Breakpoint 1, 0xcf1237b6 in +[iMBMusicFolder initialize] ()
(gdb)
GDB stopped at the breakpoint before the method’s code was going to be run. I used the “return” command to make it exit from the method immediately without running any of its code.
(gdb) return
Make selected stack frame return now? (y or n) y
#0 0x92abf600 in _class_initialize ()
(gdb) c
Continuing.
Reading symbols for shared libraries . done
Reading symbols for shared libraries . done
Reading symbols for shared libraries . done
Reading symbols for shared libraries . done
Reading symbols for shared libraries ...... done
Suddenly VectorDesigner sprang back to life. That was easy. Then next goal was to make this fix into a patch, so that I don’t need gdb to run VectorDesigner. Additionally, others might want to run VectorDesigner a few more times for the same reason that I do, and it would be easier to share a patch.
Patching VectorDesigner
To make the fix permanent-ish I needed to change the broken executable code so that it always does what I had just done with gdb, except without all the gdb stuff. This essentially meant that I’d be hex editing. I’ve been using Hex Fiend for a few years and I decided to stick with it and other tools included with OS X.
I needed to do a bit of research, and after some googling I came to the conclusion that there are primarily two types of people who patch binary executables:
- software vendors who are distributing updates
- crackers who are trying to break copy-protection, subvert DRM, get free DLC, post something to a high-score or achievement system, or give your computer a virus
During my short research I found useful information only from members of the latter camp. They have posted plenty of helpful articles, for example this article on cracking Cocoa apps, this article on mac hacking, and even this old article about PPC cracking. That last one documents many general concepts and tools that are still relevant on x86 architecture.
Incidentally, during this research I encountered f-script first time. It could have been helpful with this VectorDesigner issue, and I intend to learn more about it in the future.
Back in GDB I took a look at the disassembled code for the problem method, +[iMBMusicFolder initialize]
.
(gdb) disass
Dump of assembler code for function +[iMBMusicFolder initialize]:
0xcf12379e <+[iMBMusicFolder initialize]+0>: push %ebp
0xcf12379f <+[iMBMusicFolder initialize]+1>: mov %esp,%ebp
0xcf1237a1 <+[iMBMusicFolder initialize]+3>: sub $0x38,%esp
0xcf1237a4 <+[iMBMusicFolder initialize]+6>: mov %ebx,-0xc(%ebp)
0xcf1237a7 <+[iMBMusicFolder initialize]+9>: call 0xcf1237ac <+[iMBMusicFolder initialize]+14>
0xcf1237ac <+[iMBMusicFolder initialize]+14>: pop %ebx
0xcf1237ad <+[iMBMusicFolder initialize]+15>: mov %esi,-0x8(%ebp)
... yadda yadda ...
0xcf123912 <+[iMBMusicFolder initialize]+372>: mov -0x4(%ebp),%edi
0xcf123915 <+[iMBMusicFolder initialize]+375>: leave
0xcf123916 <+[iMBMusicFolder initialize]+376>: ret
End of assembler dump.
(gdb)
I noted the starting address 0xcf12379e
and the ending address 0xcf123916
, which work out to a total method code size of 377 bytes. The output of the “info symbol” command above gave me a path to the relevant binary file, which I tried to inspect with otool.
$ cd /Applications/VectorDesigner.app/Contents/Frameworks/iMediaBrowser.framework/Versions/A/
$ otool -vf iMediaBrowser
Fat headers
fat_magic FAT_MAGIC
nfat_arch 2
architecture ppc
cputype CPU_TYPE_POWERPC
cpusubtype CPU_SUBTYPE_POWERPC_ALL
capabilities 0x0
offset 4096
size 382288
align 2^12 (4096)
architecture i386
cputype CPU_TYPE_I386
cpusubtype CPU_SUBTYPE_I386_ALL
capabilities 0x0
offset 389120
size 401148
align 2^12 (4096)
$ otool -oV iMediaBrowser | grep iMBMusicFolder
name 0xcf1349ac iMBMusicFolder
method_imp 0xcf120874 -[iMBMusicFolder parseDatabase]
method_imp 0xcf120744 -[iMBMusicFolder populateLibraryNode:]
method_imp 0xcf1200f4 -[iMBMusicFolder recursivelyParse:withNode:movieTypes:]
method_imp 0xcf120064 -[iMBMusicFolder dealloc]
method_imp 0xcf12000c -[iMBMusicFolder init]
method_imp 0xcf11ff28 -[iMBMusicFolder initWithContentsOfFile:]
method_imp 0xcf11fe88 -[iMBMusicFolder initWithContentsOfFile:musicFolderName:unknownArtistName:iconName:parseMetadata:]
name 0xcf1349ac iMBMusicFolder
method_imp 0xcf11fdf4 +[iMBMusicFolder load]
method_imp 0xcf11fcac +[iMBMusicFolder initialize]
name 0xcf13c2e0 iMBMusicFolder
method_imp 0xcf12448d -[iMBMusicFolder parseDatabase]
method_imp 0xcf124337 -[iMBMusicFolder populateLibraryNode:]
method_imp 0xcf123c3c -[iMBMusicFolder recursivelyParse:withNode:movieTypes:]
method_imp 0xcf123baf -[iMBMusicFolder dealloc]
method_imp 0xcf123b62 -[iMBMusicFolder init]
method_imp 0xcf123a55 -[iMBMusicFolder initWithContentsOfFile:]
method_imp 0xcf1239b2 -[iMBMusicFolder initWithContentsOfFile:musicFolderName:unknownArtistName:iconName:parseMetadata:]
name 0xcf13c2e0 iMBMusicFolder
method_imp 0xcf123917 +[iMBMusicFolder load]
method_imp 0xcf12379e +[iMBMusicFolder initialize]
This fat binary library has both PowerPC and i386 code, so everything appears to appear twice. I just ignored the PPC code because PPC users are still running older versions of OS X and they therefore likely still have a functional app. The aforementioned research told me that I should just replace the entire method +[iMBMusicFolder initialize]
with no-op instructions (and one return instruction). In x86 assembly the instruction is “nop” and it translates to 0x90 in machine code; the return instruction is “ret” and it translates to 0xC3, which I verified thusly:
# Create a program that just contains "nop" and assemble it
$ echo "nop" >test.a
$ echo "ret" >>test.a
$ as test.a
# Now disassemble it and see what's there
$ otool -tV a.out
a.out:
(__TEXT,__text) section
0000000000000000 nop
0000000000000001 ret
# Now take a look at the machine code
$ otool -t a.out
a.out:
(__TEXT,__text) section
0000000000000000 90 c3
I opened the iMediaBrowser library file in Hex Fiend to make the edit.
But I ran into a problem: the address that I have for my target method +[iMBMusicFolder initialize]
is 0xcf12379e
. This doesn’t correspond to Hex Fiend’s addresses, which are file byte offsets. The crackers referenced above solve this by getting some of the bytes of machine code in the library file at the target method’s address, then they use those bytes in their hex editor’s search tool to find the relevant library file byte offset. Something like this:
$ otool -t iMediaBrowser | grep cf12379
cf12379c 80e100b4 7fc8f378 7fdaf378 4bfeff03
cf123798 d0 5b 5e 5f c9 c3 55 89 e5 83 ec 38 89 5d f4 e8
# the first result is the PPC machine code, so we ignore it
# our method begins at 0xcf12379e, which is the 0x55 byte in the middle
They would just use a hex editor to search for 55 89 e5 83 ec 38 89 5d f4 e8
in the library file to find the target method. Realistically you’d probably need to use more search bytes than that. This strategy works, but I don’t like it. Instead I wrote a little python program to report the library file byte offset for every method in a library. This code isn’t sufficient to deal with all the changes to OS X internals and mac processor architecture that have happened since this library was compiled, but it works on this file under 10.8, which is what I needed.
#!/usr/bin/python
import sys
import subprocess
import re
import pprint
# fixme: So this doesn't work in modern stuff. But maybe it would be worth
# trying to modernize it
# This works on old:
# otool -oV iMediaBrowser | grep method_imp
# This kinda works but will require lots more work on new:
# otool -oV /Applications/iTunes.app/Contents/MacOS/iTunes | egrep 'name 0x\w+ '
arch_reg = re.compile("architecture (\w+)")
cmd_reg = re.compile("^cmd (\w+)$")
segname_reg = re.compile("^segname (\w+)$")
offset_reg = re.compile("^offset (\d+)$")
vmaddr_reg = re.compile("^vmaddr (\w+)$")
method_imp_reg = re.compile("^method_imp (0x\w+) (.+)$")
for target_file in sys.argv:
architecture_offsets = {}
starting_addresses = {}
method_info = {}
# Find out the fat architecture offsets
fat_header_info = subprocess.check_output(["otool", "-fv", target_file])
current_arch = None
for line_u in fat_header_info.splitlines(False):
line = line_u.strip()
arch_test = arch_reg.match(line)
if arch_test:
current_arch = arch_test.group(1)
if current_arch is not None:
offset_test = offset_reg.match(line)
if offset_test:
architecture_offsets[current_arch] = int(offset_test.group(1))
# And now the __TEXT segment starting addresses
segment_info = subprocess.check_output(["otool", "-lv", target_file])
current_arch=None
current_cmd=None
current_segname = None
for line_u in segment_info.splitlines(False):
line = line_u.strip()
arch_test = arch_reg.search(line)
if arch_test:
current_arch = arch_test.group(1)
if current_arch is not None:
cmd_test = cmd_reg.match(line)
if cmd_test:
current_cmd = cmd_test.group(1)
if current_cmd == "LC_SEGMENT":
segname_test = segname_reg.match(line)
if segname_test:
current_segname = segname_test.group(1)
if current_segname == "__TEXT":
vmaddr_test = vmaddr_reg.match(line)
if vmaddr_test:
starting_addresses[current_arch] = int(vmaddr_test.group(1), 16)
# Find the objective-c methods
objc_info = subprocess.check_output(["otool", "-oV", target_file])
current_arch=None
for line_u in objc_info.splitlines(False):
line = line_u.strip()
arch_test = arch_reg.search(line)
if arch_test:
current_arch = arch_test.group(1)
if current_arch not in method_info:
method_info[current_arch] = {}
if current_arch is not None:
method_imp_test = method_imp_reg.match(line)
if method_imp_test:
method_info[current_arch][method_imp_test.group(2)] = int(method_imp_test.group(1), 16)
#pp = pprint.PrettyPrinter(indent=4)
#print "arch offsets", architecture_offsets
#print "starting addr", starting_addresses
#print "method info"
#pp.pprint(method_info)
for arch in architecture_offsets:
for method in method_info[arch]:
print "\t".join([
arch,
method,
"%s" % (method_info[arch][method]
- starting_addresses[arch]
+ architecture_offsets[arch])])
It works like this:
$ ./print_method_offsets.py iMediaBrowser | grep "iMBMusicFolder initialize"
ppc +[iMBMusicFolder initialize] 134316
i386 +[iMBMusicFolder initialize] 534430
Armed with this info, I selected 377 bytes of machine code starting at +534430 bytes.
My selection ended with a C3
byte, which is the “ret” instruction that I wanted to keep. I replaced all the other bytes with 90
. To accomplish this I put Hex Fiend in “overwrite mode” and I copied and pasted my selection into a text editor, changed the bytes there (using a regex) and pasted them back into Hex Fiend.
I saved my changes and opened VectorDesigner from the Dock. Success.
Distributing The VectorDesigner Patch
Now that I had fixed my copy of VectorDesigner, I went about determining how to distribute the fix to other people. For legal reasons, I wasn’t about to just zip my copy of the app and put it on Dropbox. Legally I’d probably be OK sending out the binary of the modified framework file because that framework itself is OSS. But that presented ease-of-use challenges.
I decided that the best solution would be to fix the file in place. As far as I know, xxd is the only binary patching tool that is included with OS X. This particular fix is so small that it can be accomplished with this command-line: perl -e 'print "90" x 376, "\n";' | xxd -s 534430 -p -r - iMediaBrowser
(from the proper working directory). I decided to try to leverage the power of AppleScript Editor URLs to distribute this patch to other Mac users.
So I created this AppleScript Editor link which patches VectorDesigner. It won’t do anything until you both give gatekeeper the OK to open it and click the run button. It will patch VectorDesigner. You should make a backup of VectorDesigner first. If this helps you, please let me know.
Update! : I recently searched some third-party twitter archives for old Marco Pifferi tweets. I found this link to the undocumented VectorDesigner 1.8.0 (2591): http://www.aroundmeapp.com/VectorDesigner.zip. Use it!
Next Time
VectorDesigner works again and can export to EPS. Some apps I have are better at SVG than EPS. I guess I want to convert EPS to SVG. But how?
Prelims:
-
http://superuser.com/questions/198460/converting-between-eps-and-svg-format
-
http://sk1project.org/modules.php?name=Products&product=uniconvertor - There is no mac package. Maybe get via homebrew? No homebrew. Why? Not sure. But someone looks like they’ve done it. See: https://github.com/shamrin/homebrew/blob/master/Library/Formula/uniconvertor.rb https://github.com/shamrin/homebrew/blob/master/Library/Formula/sk1libs.rb