2011-07-25

Fuck You, OS X!

The joys of a commercial operating system! I was happy to play Diablo 2 on my shiny new MacBook Air. No, not one of the cool new-new MacBook Airs, the slightly older MacBook air that still has discreet graphics. Anyway, I decided to finally bite the bullet and pay the $5 for the latest Xcode. I was happy to discover that it's now free. Then I noticed that it was free for Lion, only. So I paid the $30 and upgraded. Then I discovered that I couldn't play Diablo 2 anymore. Then I discovered that I probably could have gotten the upgrade to Lion for free, through Apple, because I bought my system so close to the release date of Lion.

Well, bitterness aside, I decided to grab wine for OS X and use that to launch a windows install of Diablo 2. Then once that was working, I decided to package it up in a .app file so that I could at least pretend it was a native app. After that, I discovered there's something called wineskin that will let you package stuff up into .app bundles. Eh, whatever, I already made the damn script, put the right files in the right places, and through some guesswork figured out how to get it to all play nicely. Then I tweaked my script so that it would search for any old wine installation within the bundle, in case I upgrade the version of wine it's using. Then I tweaked the script a bit further to have it configure the 800x600 desktop so that the game would stop fucking up my graphics and displaying incorrectly in the process. It was weird, the game would launch, it looked great, but the window was getting pushed off the screen, ever so slightly. The emulated desktop fixed that.

Since I had it configure wine, I decided to remove the stupid gecko prompt for HTML support in windows applications. Maybe battle.net makes use of the HTML display, I don't know, the game certainly didn't need it for multi-player.

Anyway, here's the bizarre stuff that I learned in the process of creating the .app. I knew that .apps were folders, but I didn't know what the structure needed to look like, so I took a peek at Torchlight. Nice and simple. I mimicked the directory structure so that I had the following:
Diablo2.app/Contents/MacOS
Diablo2.app/Contents/MacOS/Info.plist
Diablo2.app/Contents/MacOS/diablo2.icns (stole from somewhere)
Diablo2.app/Contents/MacOS/diablo2.sh (script I wrote)
Diablo2.app/Contents/Resources
Diablo2.app/Contents/Resources/diablo2.icns ->
../MacOS/diablo2.icns
Diablo2.app/Contents/PkgInfo
Diablo2.app/Contents/Info.plist -> ./MacOS/Info.plist

When it was all said and done, I had the following additional items:
Diablo2.app/Contents/MacOS/1.3.24     (wine distro)
Diablo2.app/Contents/MacOS/Diablo II (from windows)
Diablo2.app/Contents/MacOS/prefix (generated)

[Edit: Here's where I got the icon set: http://www.iconeasy.com/icon/diablo-ii-icon/ No idea where they got it from, so I don't know that this is really proper attribution.]

So, here's the cool part. Parameter Expansion. The diablo2.sh I created was modeled after the Steam.app steam.sh script. And when I say modeled after, what I really mean is that I read the first executable line of the script and said, "What the hell is this?"

Here's the line in question:
STEAMROOT=$(cd "${0%/*}" && echo $PWD)

I gathered that PWD was feeding the PWD to the variable STEAMROOT, so I figured the ${0%/*} must be doing something crazy to $0. So the first thing I did was start issuing echo commands and trying things with different variables. Now, I could see that the ${0%/*} was taking whatever the value of the variable was and chopping off the trailing file name but I didn't understand what sorcery was allowing this. Turns out bash has some built-in awesomeness that I just didn't bother to notice in the 10 years or so that I've been using it. I'm so so sorry for ignoring you, bash! If you look at the man page for bash, it will explain (among other things) ${VAR%pattern}, ${VAR#pattern}, and ${VAR/pattern/substitute/}. They work much like you can imagine. The ${VAR%pattern) removes the pattern, starting its search from the right side of the value and searching towards the left, going for the least-greedy approach. The ${VAR#pattern} similarly removes the pattern, but starting from the left. The ${VAR/pattern/substitute} is something that I've needed ever since I started using Linux back in high school. It's like a sed expression. It replaces the specified pattern with the specified substition. It's all so bloody awesome.

Up until now, I had been doing this to get the directory part of a file name:
DIR="`echo $FULLPATH| rev | cut -f1- -d/ | rev`"

I just needed to do this:
DIR="${FULLPATH%/*}"

I cannot count the number of times that I've shelled out to imagemagick to process a directory and have done something foolish like this:
for f in *.jpg ; do NEW_NAME="`echo $f | rev | cut -f2- -d'/' | rev`.png" convert "$f" "$NEW_NAME" ; done

And I could have just done this:
for f in *.jpg ; do convert "${f}" "${f/\.jpg/.png}; done

So, getting back to the Diablo stuff. I don't know why the steam.sh does the cd and pwd when it could just evaluate the variable, but whatever; maybe they use a side-effect later. Here's my diablo2.sh file:


On to the other package details...

Info.plist, ties everything in the package together:

PkgInfo only contains this line:
APPL????

Here's a crazy script I threw together, simply invoke it and it will base64 decode the last line of it's content, reconstitute a tar.bz2 file, md5 it for integrity, and if all went well, it will extract a copy of my Diablo2.app folder, minus the wine and minus my Diablo 2 install folder. Just get any one of the pre-built wine for OS X from here: http://wine.playonlinux.com/darwin-i386/ and extract it into the MacOS folder of the Diablo2.app. Then, copy in your Diablo II from a windows install, and the script will locate all the pieces and launch. Be prepared for sounds, because the script speaks its status messages.

No comments: