Reverse engineering 3D Movie Maker - Part 2

A while ago, I started reverse engineering Microsoft 3D Movie Maker to understand how it works and to develop my game reversing skills. This blog series is about my adventures in reversing 3D Movie Maker and some of the interesting things I learnt along the way.

Previously, I recovered the class hierarchy by reversing the game’s custom runtime type identification code. How do we identify the interesting classes to start reversing first? I started looking at classes that I could recognise from their type tags (eg. MVIE for movie, STIO for the Studio, TATR for the Theatre), but these objects are quite complex. So, I decided to look at common base classes instead.

Graphical objects

I found from my reconstructed class hierarchy that all of the UI-related objects derive from the GOB class. I guessed this was the base class for graphical objects. GOBs have some of the basic properties you would expect an entity in a 2D game engine to have: a unique ID, a position and a size. GOBs are hierarchical: a GOB has a pointer to a single child GOB, and has a pointer to a sibling GOB that acts as a singly-linked list of GOBs at the same level of the hierarchy.

The game has a global pointer to the root GOB which contains all of the GOBs for the game’s UI. GOBs also hold a handle to the window (hWnd) on which they are drawn. In 3DMM everything is rendered on a single window object, so all of the objects have the same hWnd. There’s a bunch of code in the binary for handling MDI windows which doesn’t seem to be used. I suspect this may be a remnant of development versions of the game engine - or possibly they added the feature and just never used it.

I found another object, GTE, was often referenced when the global GOB was accessed. I noticed a pattern with how the GTE object was used: the object would be initialized, then another GTE function would be repeatedly called in a loop. The result would then be compared with something. I realised this looked like an iterator.

Annotated decompilation of 'find graphical object by hWnd' function

The iterator class probably isn’t the most interesting thing to reverse engineer in the binary, but mapping out these kinds of basic classes is important as they are often referenced in more complex functions. Similarly, I spent a bunch of time looking at string handling functions, list functions and file IO classes - they aren’t very interesting, but definitely helped to provide context when reversing more complicated parts of the game engine.

Message handling

Many of the game’s classes, including the GOB class, inherit from a base class that provides functions for handling messages. These messages handle low-level user interaction (eg. the user moved the mouse) and high-level game logic (eg. the user requested to open a movie file). The CMH base class includes a function that returns a pointer to a structure that maps message IDs to functions that handle the message. The structure also has a pointer to the parent class’s message handler map. The CMH class has a message dispatch function that searches for the message ID in the handler map and then tries the parent’s message handler map if it can’t find the message ID.

I created structures in Ghidra for the message handler maps and the messages, and created enums for the message IDs. I also named the “get message handler map” function in the CMH vtable. Creating enums made it easier to identify other usages of the message IDs.

Message handler map for the Theatre class

Where do the messages come from? I found out by using WinDbg to run the game and put a breakpoint on the message handler functions. From there I could walk up the stack trace and find what was calling the message handler functions. This led me to a global instance of the CEX class which manages the message queue and a list of objects that can receive messages.

The CEX class has a function that adds a message to the queue. Each message has a message type ID and up to four parameters. There is also a function that fetches the next message from the queue, finds the recipient for the message, and then asks the recipient object for the message handler for this message type. If the object can handle the message, the message handler is called with the struct containing the message.

Annotated decompilation of the 'find message handler' function

The game loop

The message dispatch function is called from the game loop. In 3DMM the game loop is implemented in the APPB (app base?) class. Firstly, the game loop repaints the screen. The app base class maintains a list of dirty regions such that only parts of the screen that have changed are repainted. This would have been very important when running on the minimum supported hardware, an 80486 at 50MHz with 8MB of RAM.

Once the screen has been repainted, the app class calls the CEX global object’s dispatch-next-message function, which will dispatch the next message to a graphical object. If there are no more messages, the app fetches and dispatches the next message from the main window’s message queue. This is the message queue used by Windows to dispatch messages for low-level input (eg. WM_KEYDOWN to indicate a key being pressed) and high-level events (eg. WM_DISPLAYCHANGE to indicate that the display resolution has changed). Fetching and dispatching window messages uses the standard Win32 APIs: GetMessage to get the next message from the queue, TranslateMessage to convert virtual key codes into character messages, and DispatchMessage to call the window procedure to handle the message. The user input messages (eg. WM_MOUSEMOVE, WM_KEYDOWN, etc.) are handled by creating a new message for the root GOB and adding it to the CEX global object’s message queue. The message is then dispatched to the GOB’s message handler for mouse/keyboard events.

Annotated decompilation of the game loop

Hunting for Easter eggs

Back in the 90’s, Microsoft developers often added Easter eggs to their products. Some of these Easter eggs were quite elaborate: Word 97 included a pinball game, Excel 97 included a 3D flight simulator, and many other products included some kind of credits sequence.

3DMM had two Easter eggs: one was a credits movie that contained the names of the product team, animated in 3DMM using cool 3D text. The other was a full motion video of the Microsoft offices where 3DMM was being developed. This can be played by opening the Talent Book and typing “socrates”, which was the codename for the 3DMM project.

The 3D Movie Maker CD-ROM contained approximately 320MB1 of content. The Easter egg video was 65MB, or about 20% of the CD image!

Both of these Easter egg files were stored on the game’s CD with renamed file extensions to hide them. The video is called BLDGDATA.CHK, but is really an AVI file. The credits movie is called 3DMOVIE.MS. To play the movie you need to rename it to have the .3MM file extension so 3DMM can load it.

I remember finding the 3DMOVIE.MS string early on in my reversing of the binary. I was curious to know why it was there: was there some way to trigger the Easter egg from within the game? The string was referenced in exactly one function, so I started looking there. It wasn’t immediately clear what the function was doing - it appeared to be checking some class variables and calling some virtual functions, but I didn’t yet know which class and what all those field references meant.

After mapping out the message handler maps for the classes, I found that the function was a message handler for the Studio (STIO) class. I then looked at cross-references from the function and found it calls a function that sets up the load file dialog. Some further reversing found that there were two messages handled by this message handler: creating a new movie and opening an existing movie.

I wanted to confirm that the message handler was called when I created a new movie, so I just ran the game with WinDbg attached and set a breakpoint on the function. The breakpoint was reached as soon as I clicked the New button. It was also reached when I clicked the Open button. Cool.

Eventually I found that the message handler would check some flag variable in the Studio object, then call some virtual function on the APP class, and if both of those were successful it would pass the 3DMOVIE.MS string to some other function.

I decided to cheat and use WinDbg to patch out the flag variable check and the virtual function call to see what would happen. Sure enough, when I clicked the New button the credits movie started playing! I had confirmation that this was definitely the Easter egg: now I just had to figure out how to trigger it.

I re-ran WinDbg and set a breakpoint on the virtual function call into the APP object, then found the virtual function in the disassembler. This function called the Win32 GetKeyState() function and returned a bitmask of which modifier keys were held down at the time of the call. The message handler would check if a bit was set in the result indicating that the SHIFT key was down. So, I knew you had to hold down the SHIFT key when clicking New, but what about that flag variable?

Annotated decompilation of the message handler, including the check for the keyboard state and the Easter egg flag

I guessed that the flag would be set by some other UI widget. I used WinDbg to set a breakpoint on writing to the address of the flag variable. I then used a highly-advanced 31337 h4X0r reversing technique of … clicking random UI elements to see if I could get something to write to the variable.

Using WinDbg to break on write to a variable with the ba command

I opened the Scene Choices dialog, which is where you choose a scene to add to your movie. When I clicked the Cancel button instead, the breakpoint hit. I jumped to the same location in the disassembler and found that the function also called the APP class’s keyboard state function. If the CTRL key was down, it would set the flag variable in the Studio object to TRUE. Putting all of this together, I found that I could trigger the Easter egg by:

  1. Opening the Studio (without loading a movie)
  2. Clicking Scene Choices
  3. Holding down CTRL and clicking Cancel
  4. Clicking the Portfolio
  5. Holding down SHIFT and clicking New

Here is the Easter egg in action:

At the time that I found this (in early 20162), I could not find any other reference on the internet to being able to access the credits movie from inside the game. This was pretty cool: I had found an Easter egg that had somehow gone unnoticed for 20 years!

Next time: Reversing the game’s custom scripting engine

  1. The size of 3DMM varies depending on which language version it is. I’m using the World English (UK) version for most of my analysis. ↩︎

  2. I have been procrastinating writing these blogs for a long time! ↩︎

comments powered by Disqus