====================
== felsqualle.com ==
====================
Welcome to the world of felsqualle.com

Saving the Masters of the Elements From Getting Lost to Time: Part 2

Published at , last update 2025-11-14 14:11:58
#windows #retrogaming #preservation

In Macromedia Director, everything is related to cinematography and theaters. A Director project consists of individual movies, timed and coordinated by the score. The canvas where you place all the objects is the stage; objects and scripts compose the cast, and each object and script is a cast member.

Then, there’s the Projector, the executable file that plays all movies in the defined order. And just like a real movie projector shines light through the film, we have to reverse this process: Splitting the beam of light to reveal the real source.

Introducing ProjectorRays

As briefly mentioned in my previous article, more or less complex Director movies contain scripts written in a small BASIC (and later JavaScript)-like language, called Lingo. The (human-readable) Lingo code is then compiled into a (machine-readable) bytecode. To protect their software and intellectual property from reverse-engineering efforts, Macromedia Director offers a feature to “protect” a movie. The protection consists of stripping down the plain text Lingo sources, leaving only the compiled parts intact, and on top of that, adding a checksum algorithm that prevents any tampering with the Director movie internals.

The protected movies get special file extensions, .DXR or .CXT, and are impossible to be opened with the Macromedia Director Authoring system.

Thankfully, there’s a project called ProjectorRays, which solves this problem. ProjectorRays takes the protected .DXR and .CXT files, and decompiles them to their editable .DIR and .CST counterparts. Except for the original Dutch release of the game, Masters of the Elements uses this kind of protection as well.

After downloading the latest version from the release page, we can start feeding the encrypted files to ProjectorRays itself.

For Windows, ProjectorRays ships as a single executable file, so we can run all .DXR and .CXT files in one go without having to install any additional dependencies.

Starting from the directory containing our working copy of the game files, we have to open a PowerShell terminal and pass all .DXR and .CXT files to ProjectorRays. While we’re at it, I highly recommend deleting the .DXR and .CXT files after the decompilation step—I noticed that the Director software gets really confused if both variants are present.

1Get-ChildItem -Recurse -Filter '*.dxr' | ForEach-Object { X:\Path\To\projectorrays.exe decompile $_.FullName }
2Get-ChildItem -Recurse -Filter '*.cxt' | ForEach-Object { X:\Path\To\projectorrays.exe decompile $_.FullName }
3Get-ChildItem -Recurse -Filter '*.dxr' | ForEach-Object { rm $_.FullName }
4Get-ChildItem -Recurse -Filter '*.cxt' | ForEach-Object { rm $_.FullName }

The debugging begins

After decompiling the Director files and deleting the original, protected files, we are ready to open the starting movie in Macromedia Director. The German variant I’m currently working with was built using Macromedia Director 6.0.2. To avoid any compatibility issues, I’m using this exact version of the authoring software.

And indeed, this is the correct file.

As soon as I hit the “Play” button in Director, the movie starts to play back, and eventually confronts me with the same error message. Well, not exactly the same, as this time, we get the option to start debugging!

Clicking the “Script” button takes us right to the point where everything starts to fail, and unsurprisingly, the problematic script is called Cursor Script.

Movie Script 8: Cursor Script
Movie Script 8: Cursor Script

Lingo? Lingo!

As I mentioned in my previous article, Director is using a scripting language called Lingo. When I first looked at it, it thought it was very readable, even without any prior experience with it.

Let’s break it down, shall we?

on definePlatform
  global platform
  if the machineType = 256 then
    set platform to #windows
  else
    set platform to #macintosh
  end if
end

The script begins by checking whether we are running on Windows or Macintosh. This check is important because we can’t load a Windows Xtra file or XObject on a Macintosh and vice versa.

on initCursorObject
  global myMouse, platform, objectPath
  if objectp(myMouse) then
    myMouse(mdispose)
  end if
  if platform = #windows then
    openXLib(objectPath & "Putcurs.dll")
    set myMouse to Putcurs(mnew)
  else
    openXLib(objectPath & "MoveMous.XOB")
    set myMouse to MoveMouse(mnew)
  end if
end

on exitCursorObject
  global myMouse, platform, objectPath
  if objectp(myMouse) then
    myMouse(mdispose)
  end if
  if platform = #windows then
    closeXLib(objectPath & "Putcurs.dll")
  else
    closeXLib(objectPath & "MoveMous.XOB")
  end if
end

Every time we want to use the myMouse object, we have to load (the broken) Putcurs.dll file on Windows or MoveMous.XOB, which is responsible for handling the cursor-related functions on the Macintosh. Upon initialization of myMouse, we load the XObject with openXLib, and when we don’t need the object anymore, we unload the XObject by using closeXLib.

on moveTheCursor X, y
  global myMouse, platform, offSetX, offSetY
  if not objectp(myMouse) then
    return 
  end if
  if platform = #windows then
    set offSetX to the stageLeft + (4 * (the runMode = "Author"))
    set offSetY to the stageTop + (42 * (the runMode = "Author"))
    myMouse(mSet, integer(X + offSetX), integer(y + offSetY))
  else
    myMouse(mSetMouseLoc, integer(X), integer(y))
  end if
  set a to the mouseH
end

Calling moveTheCursor uses the function mSet exported by Putcurs.dll. The values X and y refer to screen coordinates and are then passed to mSet. We don’t need to worry about mSetMouseLoc though, because this is only used by the Macintosh version we are not working on.

Bare minimum

We already know that Putcurs.dll is broken beyond repair, so if we want to do any further work about it, we first need to check what it does inside the game itself.

Looking at the Lingo code, we know that it controls the position of the mouse cursor, but not how the game uses it. And surely, the game will use it somehow, because otherwise, there would be no reason to include it in the first place.

To solve this mystery, I modified the Lingo script and removed all parts that are not absolutely necessary. All that’s left over are some stubs for the functions themselves, so Lingo won’t trip over when trying to call them.

on definePlatform
  global platform
  if the machineType = 256 then
    set platform to #windows
  else
    set platform to #macintosh
  end if
end

on initCursorObject
end

on exitCursorObject
end

on moveTheCursor X, y
end

I saved the modified file global/scripts.cst, and started the game, this time using the original game executable.

The moment of truth, the moment we all waited for.

As expected, the Tivola intro runs, but this time, I get past the error message, and the next movie (called INTRO.DXR/.DIR) is loaded. I kept the dialog with the mole in the video to check if playing back the audio samples works correctly, because the game uses some legacy DirectX components that are thankfully still included in Windows 11.

The conversation with the mole works just fine, and so does the transition to the Room of Gravity. And here is where the first quirks are visible.

So close, yet so far

When I try to turn the ring to the left (instead of the right), the cursor also jumps to the left. In the original game, the mouse cursor stays at the same position. This effect is also visible when I try to manipulate the handle controlling the crane in the first room.

Purely cosmetic at this point, so it’s not a deal breaker.

But a couple of seconds later, in the Room of Warmth, the game breaks. Here, you are supposed to light a match by striking it along the priming surface of the matchbox. You control the mouse cursor in two dimensions, vertically and horizontally. Horizontally, it works without problems, but vertically, it is impossible; the mouse cursor will always slowly creep up or down. The same problem also happens when trying to control the pan; there is no way to control vertical movement. Not included in the video is the second room with an unsolvable puzzle: The Room of Electricity, where you won’t be able to solve the maze due to a lack of control over the mouse cursor.

To summarize, every time the game tries to perform a drag-and-drop movement, it will break. With a working Putcurs.dll implementation, the game continuously resets the cursor position to the “real” starting location when it encounters said drag-and-drop operations. And without it, it cannot be completed.

Somehow, we need to find a way to replicate the features provided by Putcurs.dll; there’s no way around it. How, you might ask? Well, as I mentioned, the Macromedia Director community was very active back in the day…

…continue reading Part 3 of this series…

Do you have any comments or suggestions regarding this article? Please drop an e-mail to feedback@felsqualle.com!