The making of a Banania clone

(play Banania online)

Getting started

Back in 2013, when the summer holidays started, I was in need of a side project. Being the hopeless nostalgic that I am, I wanted to recreate the old Windows 3.x game Banania in JavaScript for a long time but I didn't know how to get started. Unsure about how to approach things, I asked the Reverse Engineering StackExchange community which is very nice and helpful. You can see my question here. At first, I was only looking for the map data. But one user pointed me to the NE Dumper, a tool that extracts valuable information from old DOS/Windows era executables. Thanks to this handy tool, I was able to locate the level data, all the images and the sound files. That was the first step into the right direction.

The level data

The level files were encoded exactly as I predicted, a 50x21x13 16-bit integer array containing 50 levels that are 21 blocks wide and 13 blocks high. Firstly, I had to find out if the array was in row-major or column-major order. I used notepad++ to highlight certain blocks which helped me to visualise the level data.
Numeric level data
The original level data in a human-readable format with highlights on '03' bytes.
Numeric level data reordered
The actual positions of the blocks if they are encoded in column-major order.
Rendered level
The corresponding level to the map data. As it turned out, the array was in column-major order. Like this, it's easy to see that the block '00' is air (empty) and the block '03' is grey with a red pin. And so on.

Extracting the sound files

The sound files were WAVE. All I had to do was read the offsets and lengths from the NE dumper file and copy that block of the binaries into a new file. Rename it to .wav and it's done. I later converted the sounds to mp3 to avoid compatibility issues. Internet Explorer is incapable of playing back WAVE. Yes, you heard right.

Reverse-engineering the image format

A tougher nut to crack was the image format. I knew the offsets and lengths, but the format was entirely unknown (I wouldn't be surprised if someone knows the format and makes me look like a fool now). I didn't even know the header length. Trying to match a magic number was unsuccessful. What can you do in such a situation? Visualize! By adjusting the width of the notepad++ window, it is possible to find out the width of the image.
Numeric image data
Hmm... That doesn't look recognisable...
Numeric image data
Hey, that looks like a pattern! Let's go on.
Numeric image data
A rectangle. It cannot be a coincidence. Also note the wraparound.
Numeric image data
We found the header length. Now it's a perfect rectangle. A button?
Rendered OK button
It's a pressed OK button, in row-major order, upside down! I was lucky that there was no image compression. Note the trailing zeros. This confused me at first but they may be some padding. Also note that in all those images, we highlighted two colours at once. One colour is half a byte (4 bit) which means there are only 16 different colours in this game.

Of course, it took me quite a bit longer to figure things out, especially the header length was something that I got wrong many times. Some pictures are too large such that this trick becomes much harder. Other pictures don't reveal their shape quite as quickly.

Analysing the header was also something that took longer than necessary. I highlighted the differences in the headers and tried to figure out width and height.
Image headers

There were three numbers that stood out. The second one, I figured out quickly, was the height of the image. However, the first number didn't make sense, and neither did the third. 0x7801 = 30'721. There is no way this image has a width or size so high. I was stumped. But then I suddenly got it: The numbers were little-endian! That's right, I have to invert the bytes. Stupid me, how could I not see that! 0x0178 = 376. 376px is the width of the image! The lesson I learned from that has been very valuable and saved me a lot of time later in a project I did for university where a sensor would supply little-endian values. It's one thing to know the theory about endianness, another to realize that the values you get are completely wrong because you forgot to invert the byte order. The last number is the size of the body of the image, in bytes. I didn't find out the meaning of the other numbers.

Now I had everything to convert the image to something modern like PNG - hold that thought! We don't have the colour palette. There are 16 colours in Windows 3.x, remember?
Windows 3.0 colour palette
Colour palette of Windows 3.0
Windows 3.1 colour palette
Colour palette of Windows 3.1

I picked the brighter palette (Win 3.0) because bright colours are prettier. Also, changing palette is trivial - I considered adding a menu option to switch between the palettes but that didn't make it into the game (so far). I wrote a little PHP script (PHP because it is so easy and productive in handling images) that parses this obscure format and spits out PNG images. This is how the output looked like:
Berti spritemap

I sliced the image by hardcoding the offsets in PHP since I didn't find any metadata about sub-images. Also, the separating lines had different colours for different images and the lines only separated the image into vertical stripes. Some images didn't have separating lines at all despite having multiple sub-images. One image also had an issue with the colour palette, two colours were exactly swapped. Very obscure indeed. I'm still curious about this format.

Diving into JavaScript

Now I had all the raw game data and was ready to start writing the JavaScript code. It was my first reasonably sized JavaScript project. I didn't know about prototype, how 'this' worked, how closures are called, what requestAnimationFrame() is, that Internet Explorer can't play WAVE, what local storage is, that Internet Explorer cannot use local storage on local HTML files, that sound playback on mobile can only be triggered by an user interaction event like tapping... The list goes on.

Despite making massive mistakes, I went on and produced a monolithic block of roughly 3000 lines of code. I'm not proud of the quality of my code. But I learned a lot and it seems to work. I didn't read the assembly code of Banania to figure out the game mechanics, I largely drew conclusions from videos of gameplay and playing it myself. Ironically, the original game doesn't work on Windows 7 anymore but it does with Wine on Linux. It was also my first complete playthrough of the game; I was never able to beat Banania as a kid.

The game ran at roughly 4 frames per second on Windows 3.x - In my JavaScript clone, I bumped that up to 60 (15 on mobile). This had nontrivial consequences concerning the gameplay. Who moves when, what is the distance required between you and the monster such that you are caught. I wanted to recreate the game as accurately as possible, even specifically reproduced a bug that added depth to the gameplay. But because of the better temporal resolution, the gameplay feels a bit different overall. I hope the players don't mind. The original game feels quite finicky because of a bug that delayed keyboard input. Of course I didn't recreate that one in my clone ;)

Closing thoughts

Creating Banania in JavaScript was a fun project during which I learned a lot about old things like Windows 3.x but also new things like HTML5. I finally understood the DOM and got a good grasp of JavaScript. I may have spent a bit too much time on it, considering that the entire game is just silly and few people will appreciate it, but it was a work of love. Have fun collecting banana peels! (Do you also find it odd that collecting the peels makes an eating noise? It doesn't make any sense, but precisely things like that add to the charm of the game.)

Post comment

CAPTCHA
* required field

Comments

Nicolas wrote on 30 September 2020
How do I beat level three?
reply

Pixoto wrote on 24 February 2017
Can you put the game the lines of code for download
reply