Matt's Blog

Fixing a bug in an 18 year old Shockwave game

The story of changing a single byte

October 6th, 2018 - 13 min read

It felt like unearthing a buried tomb and finding an untouched puzzle inside. One that would have otherwise been lost to time without ever being solved.

Cartoon Cartoon Summer Resort

It was the summer of 2000. I was 6 years old, and I had just passed the 1st grade and starting summer break. This meant long days of playing outside, watching cartoon marathons, and booting up my dad’s Windows 98 computer to browse for games to play on a brand new frontier called the “Internet”. Cartoon Network’s website was one of my favorites. They had immersive flash games that were designed around the cartoons on TV. That summer they released a series of games called “Cartoon Cartoon Summer Resort”.

Gameplay of the first Episode of Cartoon Cartoon Summer Resort

The game was a top-down 2D RPG/adventure game that had 4 episodes. You played as a cartoon character who is on vacation at a resort with other cartoon characters. In each episode there is a problem to solve at the resort. You must help solve it by interacting with the characters and finding/trading items.

Fast forward 18 years

I remembered this game a while ago during a nostalga trip and I knew that I had to play it again. The game is nearly 2 decades old so it was hard to find a working link.

Also, no modern browser would run the ancient and vulnerability prone Shockwave player… except for Internet Explorer. I may be the first person to have finally found a legitimate reason to use Internet Exploerer in 2018.

After playing the game for a bit, There were some things that became impossible to ignore. For example, the repetitive background music that plays in an infinite loop, and the poor collision detection.

The Bug

After a while I discovered a bug in the game:

Walking in areas where nothing should happen would sometimes prompt game dialogue to appear.

As you can see below, you are able to rent a boat in the game to travel on the water. When trying to rent another boat, it says “No more boats to rent today!”. If you travel north and walk along the right edge of the island, it will randomly trigger the same text that was meant for the boat dock.

Now at this point, any sane person with a healthy respsect for their time and energy would write this off as a mild annoyance, remind themselves that this was a low-budget web game made for children 2 decades ago, and continue playing. But not me.

Looking at the game with the knowledge I now have of programming, I found this ancient bug oddly mesmerizing. It felt like unearthing a buried tomb and finding an untouched puzzle inside. One that would have otherwise been lost to time without ever being solved. For me, fixing this bug was an opportunity for learning and discovery. Interestingly, this is exactly what playing the game offered to me as a kid. It’s poetic how something can unintentionally offer entirely new challenges depending on how you look at it.

Deconstructing the Game

In order to fix the bug, I needed to figure out how the game worked under the hood. After doing some research, I learned that the game was a Shockwave game made with Director. When using Director, projects are saved as a .dir (Director) file. This file is similar to a PSD file for Photoshop. In the same way that a PSD file would hold non-destructive layer and text information, the .dir project saves all assets, raw source code, and other information to aide in the development process. Director used a proprietary scripting language called Lingo to animate scenes.

If the game was saved as a .dir file, I would simply be able to open it in Director and easily see how the game works. However, the game was published as a .dcr file. The .dcr file is a compiled version of a Director project. This means that all of the source code has been compiled into bytecode that runs on the Shockwave platform. This process is similar to how a PSD file can be flattened into a PNG image. The PNG image (DCR file in this case) is smaller, has no layer or editing information, and is only meant to be distributed.

This meant that I was stuck with a 500kB binary blob with no documentation on how it was structured. Even if I did figure out how to find the low-level bytecode, it didn’t look like there was anyone who had reverse engineered Lingo bytecode or even documented how the Shockwave platform works. All of this information is proprietary and owned by Adobe who has no reason to release it. The prospects of figuring out how this game worked were looking pretty grim.

Decompression

After feeling defeated by the fact that I wasn’t likely going to be able to solve the bug, I decided to see if there was any possible way to extract assets out of the game. I figured that there might be a chance I could find a compressed data section or something similar. After looking around, I found a few programs called offzip and packzip. These tools are able to search for zlib data in arbitrary binary files, show you the offsets, and extract them to separate files if they exist.

I ran offzip on the DCR file and to my amazement it actually found archives! 249 to be exact.

$ ./offzip.exe -a 1.dcr

- open input file: 1.dcr
- zip data to check: 32 bytes
- zip windowBits: 15
- seek offset: 0x00000000 (0)
+------------+-----+----------------------------+----------------------+
| hex_offset | ... | zip -> unzip size / offset | spaces before | info |
+------------+-----+----------------------------+----------------------+
0x00000026 . 164 -> 214 / 0x000000ca _ 38 8:7:26:0:1:7b6349f6
0x000000d3 .. 3932 -> 9169 / 0x0000102f _ 9 8:7:26:0:1:c1079d84
...
0x00080490 . 265 -> 472 / 0x00080599 _ 0 8:7:26:0:1:04d6b43f
0x00080599 . 209 -> 366 / 0x0008066a _ 0 8:7:26:0:1:7da3ba08


- 249 valid compressed streams found
- 0x0004040d -> 0x001565c8 bytes covering the 50% of the file

I extracted all of these files into a folder, and started looking at the results. There were 206 .dat files, 38 .fff files, 4 .atn files, and a single .ini file.

Discoveries

I started with the INI file, but it had no value. It was just a font mapping table for Directory 7.0 between Windows and Mac. Next I moved on to the DAT files. Most of these were 1KB in size, so I started with the huge one that was 144KB in size. I opened it in a hex editor and looked around. Most of it was unintelligible data. However, eventually I found some words mixed in that appeared to be Lingo identifiers

Sifting through the large DAT files provided me with a few clues, and there were some interesting messages littered throughout it. I found out that they likely used Photoshop 3.0 for the graphics. They also had an internal map editing tool that was named Map-O-Matic v1. I wish I could see what that looked like and how it was made.

I also discovered the name of the company who made the game: Funny Garbage. The lead developer’s name was also in the file, who’s name I will omit. It felt good to solve the mystery of who made the game that I was furiously trying to fix nearly 20 years later and to put a face to the person who likely caused me this agony. All of these tidbits of information were nice to find, but they weren’t that helpful.

Breakthrough

Next, I looked at the .fff files in the hex editor. To my great surprise, all of the data in these files was legible AND it looked like map data:

I manually extracted some of this data and prettied it up in a text editor. What I was left with was something that looks very similar to a JSON array:

[
#member: "block.104",
#type: #FLOR,
#location: [16, 9],
#width: 64,
#WSHIFT: 0,
#height: 32,
#HSHIFT: 0,
#data: [
#item: [
#name: "",
#type: #WALL,
#visi: [
#visiObj: "",
#visiAct: "",
#inviObj: "",
#inviAct: ""
],
#COND: [[#hasObj: "", #hasAct: "", #giveObj: "sunscreen", #giveAct: "gotscreen"], #none, #none, #none]
],
#move: [#U: 0, #d: 0, #L: 0, #R: 0, #COND: 1, #TIMEA: 0, #TIMEB: 0],
#message: [
[#text: "You bought the sun screen.", #plrObj: "", #plrAct: ""],
[#text: "No more sunscreen today!", #plrObj: "", #plrAct: "gotscreen"]
]
]
]

This was critical, because I was able to deduce a lot about how the game worked from this.

  1. The game expects its map, text, and event data in JSON-like Lingo Objects
  2. Each #member entry is a tile, block, or character to be rendered.
  3. The #member‘s location offsets and dimensions are editable.

Knowing that the game dialoge is saved in these files, I wrote a quick line to extract only the dialogue into a file: grep -a -o '#text: "[^"]*' Uncompressed/*.fff | awk '{print $0,"\r"}' > Dialogue.txt

Using this file, I could easily search for the offending text, and also see which file it was in:

The offending text is either in 0004eda0.fff or 0004f396.fff. In this case, the bugged text is in the first file. We know this because right under it is the message that you get when interacting with Og, who is the character in the same map as the bugged tile.

Fixing the Bug

Now, I can open 0004eda0.fff and search for the boat string in the hex editor. Once I find that, I can find the #member object that’s associated with it. I can now change its properties and save the file. Afterwards, I can recompress and patch it back into the original game DCR by using packzip.

$ ./packzip -o 0x0004EDA0 Uncompressed/0004eda0.fff test.dcr

When I change the block type from block.11 to block.13 and patch the game, we can clearly see the outline of the messed up tile:

Changing the tile ID shows the bounds of the bugged area

The actual fix for the bug is laughably simple. All I had to do was change the identifier for the #message to #fessage for that bugged tile:

Now, when we patch this in and walk back to that area, THERE ARE NO MORE MESSAGES!!!

Fixing a bug in the game by literally editing 1 byte

How does that fix it?

In my best guess, the game engine probably checks the data for the tile that the player is standing on when he moves. If some condition passes, it will display the appropriate message in the #message array for that tile. By changing #message to #fessage, there is no longer any reference to a #message array when the code looks for it. It treats it as an empty (or undefined?) object and happily displays nothing.

Consider this JS example:

function foo(bar) {
if (bar["message"] !== undefined) {
// display the message
}
}

Suppose we can’t change the function foo(), but we want to change the outcome. We do have access to the data that is passed to it. We can rename the passing object’s message property and the function will think it was never there.

How did the bug happen?

My best guess is that it happened because of a simple oversight. They used a map editing tool to create each area. They very likely copied and pasted already finished maps to new areas and then tweaked them. That would have been easier than making them from scratch. But since the text and event data were mixed in, they likely overlooked it in some spots and forgot to remove it or change it. This makes the most sense, because the bugged out dialogue often originates from an adjacent map where it is used properly.

Why did you put so much effort into something so insignificant?

I don’t know. Nostalgia?