Resource Madness

You know what's tough to do? I mean really tough? Adding a menu item to a Carbon application that has its menus defined in a resource file, without the aid of a GUI resource map editor.

Anyone who has an idea idea what I'm talking about should at this point be thinking: Why do such a thing!? The answer is simple, if depressing: You have to maintain such a Carbon app, your computer is Intel based, and you don't have available an Intel compatible resource editor capable of adding menu items1.

This insane task can in fact be accomplished via the magic of a pair of tools, Rez and Derez. Derez takes a resource map and writes it out as a structure ascii file, making it marginally more editable. Indeed, the output is practcally that of a hex-editor, as evidenced by this, the Derezzed form of the menu that I sought to edit2:

data 'MENU' (570) {
    $"023A 0000 0000 0000 0000 FFFF FEF7 0445"  /* .:........ˇˇ˛?E */
    $"6469 7404 556E 646F 005A 0000 0452 6564"  /* dit.Undo.Z...Red */
    $"6F00 5900 0001 2D00 0000 0003 4375 7400"  /* o.Y...-.....Cut. */
    $"5800 0004 436F 7079 0043 0000 0550 6173"  /* X...Copy.C...Pas */
    $"7465 0056 0000 0543 6C65 6172 0000 0000"  /* te.V...Clear.... */
    $"012D 0000 0000 0B50 6C61 7920 536F 756E"  /* .-.....Play Soun */
    $"6473 0000 1200 204B 656C 616E 646F 6E73"  /* ds.... Kelandons */
    $"2053 7472 6963 7420 3244 2049 636F 6E20"  /*  Strict 2D Icon  */
    $"4164 6A75 7374 7300 0012 0019 416C 7761"  /* Adjusts.....Alwa */
    $"7973 2053 686F 7720 4865 6967 6874 204C"  /* ys Show Height L */
    $"6162 656C 7300 0012 0000"                 /* abels..... */
};

The only difference is that this does not actually exist in a hex-editor, it's just sitting in a text file. You get to keep the hex and ASCII forms synchronized by hand, and while I think the ASCII form is merely for human consumption, you'll want to have it there to consume. Likewise, you don't get any help matching selections between the two halves, you get to do it yourself by counting characters. It's not impossible, just tedious. The really tricky part is that to add a menu item, you've got to either understand the formatting of the binary data, displayed in hex form, or be able to guess closely enough to fake it.

So how to take it apart? To begin the names of the menu items themselves are pretty obvious. 'Always Show Height Labels' is indeed the name of the last item that was defined for this menu. Since I just want another item identical to it but for a different name, I just need to figure out which bit of the data corresponds to that item and append a duplicate.

For the next step I was lucky to have a bit of experience which made me suspicious. Basically there are two ways to store a string as a sequence of binary data. The C way is to write out the bytes for all of the characters, and then write a zero byte to mark the end. This has the disadvantage that the only way to find out how long the string is is to iterate along it until you find the zero byte. The Pascal way to store strings is the make the first byte store the length of the actual string data, and then subsequently write out said data. It has some nice advantages, and luckily I'd encountered it before when hand editing binary files. If you look closely and know what you're looking for, you'll see that the byte before each of the legible strings in the above block has a value the exactly matches the length of the string. That isn't likely to be a coincidence.3

The next thing to note is that between each pair of legible strings the non-printable characters are nearly all represented by the . character. I needed to know how many of those are part of the end of the menu item whose string precedes them, and how many belong to the item whose string follows them. Looking at the end of the block is no help because the non-printable stretches are of varying lengths, so some of what's at the end might prove to be trailing information of some sort for the menu as a whole. For all we know at this point is could encode 'This is the end of the menu', so we need to know whether to move it or duplicate it. Help comes from looking at the zeroth and first items, 'Edit', which appears as the title of the menu, and 'Undo' which is the first normal item. between them there is only a single . on the ASCII side, which is in fact the length byte for 'Undo'. This means that the beginning of a menu item it the length byte for the string that will be its title. After the title come zero or more bytes of some sort of optional parameters. i don't know what they are or mean, but I want to add an item identical to the current final one, so I'll plan to just copy them and hope for the best.

Remember that I worried about a possible 'end of menu' section of the data? Since the length of the data at the end of a menu item is variable, we don't know how much of the stuff after 'Always Show Height Labels' is part of the menu item and must be duplicated and how much might be part of the menu and must be kept at the and of the block. Or do we? It happens that I know another bit of information about the menu that will resolve this. The second to last menu item, 'Kelandons Strict 2D Icon Adjusts'4 is identical to 'Always Show Height Labels', a plain menu item which can be checked, except for the names. The nonsense (to a human reading the raw binary) data following 'Adjusts' matches that following 'Labels' except for an extra 00 byte after the latter. Therefore 0000 1200 is probably the correct data to describe such a menu item, and the final 00 denotes the end of the menu (Since reading it literally would indicate that the next menu item has a name of length zero). A little typing and the new version is ready:

data 'MENU' (570) {
    $"023A 0000 0000 0000 0000 FFFF FEF7 0445"    /* .:........ˇˇ˛?E */
    $"6469 7404 556E 646F 005A 0000 0452 6564"    /* dit.Undo.Z...Red */
    $"6F00 5900 0001 2D00 0000 0003 4375 7400"    /* o.Y...-.....Cut. */
    $"5800 0004 436F 7079 0043 0000 0550 6173"    /* X...Copy.C...Pas */
    $"7465 0056 0000 0543 6C65 6172 0000 0000"    /* te.V...Clear.... */
    $"012D 0000 0000 0B50 6C61 7920 536F 756E"    /* .-.....Play Soun */
    $"6473 0000 1200 204B 656C 616E 646F 6E73"     /* ds.... Kelandons */
    $"2053 7472 6963 7420 3244 2049 636F 6E20"    /*  Strict 2D Icon  */
    $"4164 6A75 7374 7300 0012 0019 416C 7761"    /* Adjusts.....Alwa */
    $"7973 2053 686F 7720 4865 6967 6874 204C"    /* ys Show Height L */
    $"6162 656C 7300 0012 001A 416C 6C6F 7720"    /* abels.....Allow  */
    $"4172 726F 7720 4B65 7920 4E61 7669 6761"    /* Arrow Key Naviga */
    $"7469 6F6E 0000 1200 00"                     /* tion..... */
};

When I ran the program with this menu definition5, there was my new menu item, able to function exactly like the others which had been added by more normal means.

Please note that I present this description merely for the reader's entertainment and edification. If you take away nothing else, just remember: user interfaces stored as resources are a thing of the distant past. Do not seriously entertain the notion of resurrecting them to once again walk the earth unless you're sure of what you're in for.


  1. First thing I tried to do was use Rezilla. It can edit existing menu items, but for the life of me I can't get it to insert new ones. 

  2. There seem to be some issues here with PHP Markdown trying to handle the printed forms of the non-printable characters. Since this is also a near impossible task, which furthermore should come up very often, I'm not going to worry much about it. Also, I removed some of the white space so that less scrolling is necessary. 

  3. At a glance the strings may appear to be null terminated as well, and while I have heard of this being done (apparently in Oblivion savefiles), it's not actaully the case here. For a counter example, notice that the the byte directly following the 't' of the 'Edit' string (the second half of the second block on the second line of hex) is not a 00, but a 04

  4. Yes, that's really the label of a menu item in the edit menu of this program. Kelandon and I disagreed, so I suggested to put in a menu item just to toggle between his way and mine. I jokingly threatened to put his name on it, and he insisted. So, for the record: He made me do it. 

  5. It's worth noting that I wasn't having to use Rez and Derez myself here, actually. Someone who created the Xcode project for this application had used Derez on the original resource file, and the build sequence for the target includes running Rez on the derezzed file to recreate a resource file to insert into the application bundle. However, were it not for the data being actually stored in derezzed form, I find it doubtful that I would have succeeded in this undertaking. 

No Comments

Comment on this post