---
title: "Getting old Macromedia Director Games to run on modern Hardware"
slug: getting-old-macromedia-director-games-to-run-on-modern-hardware
url: https://listedarticles.com/articles/getting-old-macromedia-director-games-to-run-on-modern-hardware
canonical_url: https://werwolv.net/posts/macromedia_copy_protection/
content_type: blog_post
language: en
published_at: 2026-10-11T02:08:36.980Z
updated_at: 2026-10-11T02:08:36.980Z
author: "WerWolv"
authored_by: human
publisher: "WerWolv"
publisher_url: https://werwolv.net/
topics: ["Reverse Engineering", "Retro Computing", "Games"]
license: CC-BY-NC-SA-4.0
word_count: 2806
reading_minutes: 12
citation: "WerWolv, WerWolv. \"Getting old Macromedia Director Games to run on modern Hardware.\" 11 Oct 2026. https://werwolv.net/posts/macromedia_copy_protection/ (CC-BY-NC-SA-4.0)"
# The full text follows. The web page shows an extract and sends readers
# to the source above; quote the citation and link the canonical URL.
---

# Getting old Macromedia Director Games to run on modern Hardware

> WerWolv revives German point-and-click adventure games from the early 2000s, such as "Findus bei den Mucklas", by reverse engineering the CD-check copy protection that Macromedia Director games shared, patching it so the games run from copies on modern Windows, and notes many similar titles used the same mechanism.

# Introduction

When I was a kid in the early 2000s, I spent a lot of time playing games that I borrowed from our local library. Many of them were German point-and-click adventure games, since those were the games that ran on my dad’s Windows 95 computer. Even back then, I was interested in tech and always tried to figure out how I could keep playing these games after giving them back to the library, but I, of course, never managed to do it.
A few days ago, I stumbled across one of those failed attempts again: a set of CDs onto which I tried to burn a copy of “Findus bei den Mucklas.” I figured that maybe now was the time where I finally have the skills to do it!

# Getting a Disk Image

Obviously, the CD I burned back then was unusable, both because CD burners at the time often weren’t able to properly read or write copy-protected CDs and because I haven’t had a computer with a disc drive in the past 10 years.

![disappointment.png](/assets/limited_by_the_technology_of_my_time.CTomc4_G.png)

disappointment.png

Thankfully, some helpful soul uploaded a set of full disk images for that and all the other games in the series to [![](https://www.google.com/s2/favicons?domain=archive.org&sz=128)archive.org](https://archive.org/details/cd-01_202304). It’s not piracy if it’s on there, right?

## Unpacking the Image

The game came as a set of three files: a `.ccd`, a `.cue` and a `.img` file. Those are apparently produced by the old [![](https://www.google.com/s2/favicons?domain=en.wikipedia.org&sz=128)CloneCD program](https://en.wikipedia.org/wiki/CloneCD) and contain the raw data from the disc, plus additional out-of-band information such as track metadata. Games from that time often came with audio tracks as well, so you could just pop these CDs into a CD player and listen to the soundtrack or other bundled media.

Looking at the `.cue` file shows that there’s a MODE1 trackThese are tracks meant for storing regular data, such as a file system. They are usually skipped by audio players. at the start containing the game files, followed by four `AUDIO` tracks:

CD01.cue

```
FILE "CD01.img" BINARY

TRACK 1 MODE1/2352

INDEX 1 00:00:00

TRACK 2 AUDIO

INDEX 1 45:08:37

TRACK 3 AUDIO

INDEX 1 46:38:44

TRACK 4 AUDIO

INDEX 1 47:08:50

TRACK 5 AUDIO

INDEX 1 47:11:58
```

To access the data, I just used the old `ccd2iso` tool, which failed when it hit the first `AUDIO` track but was still able to convert the start of the file into a regular ISO that could be mounted.

# Copy Protection 1

Mounting the file and trying to copy the data over, I ran into what I believe to be the first copy protection measure:

![Duplicate files popup while extracting the archive](/assets/duplicate_files.CKhQrlWW.png)

Duplicate files popup while extracting the archive

Some of the files seem to be in the disk image multiple times: once as the real copy and once as a second copy that was either 0 bytes or 256 bytes long and contained random garbage.
My guess is that they did that on purpose to trick some tools into displaying only the fake decoy copy of the file instead of the real one by creating deliberately malformed directory entries on the disk. Windows seems to be able to always read the correct file here (otherwise it wouldn’t work there either of course) but with the different tools I tried, each one of them showed a different combination of files in the end.

Using a combination of `PeaZip`, `Ark`, `Dolphin` and `7z`, I ultimately managed to extract the right files from the ISO, but it was mostly manual labour: choosing the files that were the right size and skipping the decoy ones.

![Final files that were extracted from the ISO image](/assets/extracted_files.BPaeBJGH.png)

Final files that were extracted from the ISO image

At this point, just running the installer seemed easiest, and it surprisingly worked. I ran it using `WINEPREFIX=$(pwd)/prefix wine "Installiere Findus4.exe"` to make it install into a separate Wine prefix, from which I could copy the installed data afterwards.

# Copy Protection 2 - Electric Boogaloo

Executing `Findus4.exe` now, of course, didn’t just work, but instead showed a nice error popup saying that the CD was missing.

![Missing CD Error popup](/assets/missing_cd.Bywsoz1T.png)

Missing CD Error popup

Simply searching for that message using `rg -a "Lege die CD"` showed that they just read it from the `Pettson3.ini` file. *They apparently didn’t even bother renaming that file from the previous game.*

Pettson3.ini

```
[CDROM]

CDROM=?

[GAME1]

Vind=0

[ALERT1]

MESSG=Lege die CD Findus4 ein und versuche es noch einmal.

[SCREEN]

FULLSCREEN=1
```

Searching for a few strings ultimately led to `messg` being found in `Findus4.exe`. My first instinct was, of course, to just throw the binary into Ghidra and start looking for it. Surprisingly, the string didn’t show up anywhere using the `Search -> For Strings...` option, which usually means that the string isn’t actually part of the PE data but some sort of extra payload appended to the end of the executable file.

## Extracting the Embedded Script

After some more digging, I found that these games are all `Macromedia Director Projector` executables. These were generated by Macromedia Director and basically contain the whole runtime for interpreting Director files, with a `.dxr` file concatenated onto the end. That file is what gets executed when the program runs. It made sense that the CD check would be part of the embedded Director file, because why would the runtime read from a config file called `Pettson3.ini`?

During my research, I stumbled across [![](https://www.google.com/s2/favicons?domain=github.com&sz=128)ProjectorRays](https://github.com/ProjectorRays/ProjectorRays), which is a disassembler and decompiler for exactly these files. They have the file signature `RIFX` or `XFIR`, depending on the endianness of the target system, which makes them pretty easy to identify. Using ImHex, I found a few occurrences of those strings in the binary. Based on the ProjectorRays source code, the format of these `.dxr` files starts with something like this:

director.hexpat

```
enum RifxType : u32 {

MV93 = le u32("MV93"),

MV95 = le u32("MV95"),

FGDM = le u32("FGDM"),

FGDC = le u32("FGDC"),

APPL = le u32("APPL")

};

struct Content {

type::Size<u32> length;

RifxType type;

// ...

};

struct DirectorFile {

char magic[4];

if (magic == "RIFX")

be Content content [[inline]];

else if (magic == "XFIR")

le Content content [[inline]];

else

std::error("Invalid Director File!");

};
```

Only two of the `XFIR` occurrences had a valid type after them: one was `APPL` at address `0x00222151`, which, according to ScummVM, identifies a Director application wrapper (the executable file we are working with), and the other was `MV93` at address `0x002222F0`, which seems to be the regular, uncompressed Director movie script.

I initially tried to copy that chunk out of the executable and process it separately using ProjectorRays, but it turned out that all the offsets used inside the appended file are absolute and account for the `.dxr` file not starting at the beginning of the executable. Instead, I ended up adding an `--offset` option to the CLI that lets you seek forward to the actual offset of the embedded binary. The PR can be found here in case it’s useful to somebody else: [![](https://www.google.com/s2/favicons?domain=github.com&sz=128)ProjectorRays#63](https://github.com/ProjectorRays/ProjectorRays/pull/63).

## Disassembling the Director File

Finally, after all this trouble, we can dump the embedded Director file’s contents using the following command.

Terminal window

```
./projectorrays decompile Findus4.exe -o output --dump-scripts --offset 0x002222F0

output

├── Findus4

│   └── casts

│       └── Internal

│           ├── MovieScript 1.lasm

│           └── MovieScript 1.ls

└── Findus4.dir
```

To my surprise, this generated both a disassembly file (the `.lasm` file) **and** a decompiled version of the script (the `.ls` file), which still contains all the original function and variable names!

![Decompiled Director Script](/assets/decompiled_script.DEUGyPJA.png)

Decompiled Director Script

## Analyzing the Script

The generated file is only around 150 lines long and contains the game’s initialization logic. It’s written in a programming language called [![](https://www.google.com/s2/favicons?domain=en.wikipedia.org&sz=128)Lingo](https://en.wikipedia.org/wiki/Lingo_(programming_language)), which is what Macromedia Director uses. Even without knowing the language, it’s pretty straightforward to read. It didn’t take long to find the function from which that `"messg"` string originally came. As seen here, they implemented a primitive `.ini` file parser to parse the `Pettson3.ini` file and get a few config values out of it, one being the error message displayed when no CD can be found.

MovieScript 1.lasm

```
-- ...

on analyseIniText iniText

repeat with mening = 1 to iniText.line.count

ReddString = StripString(iniText.line[mening])

if ReddString contains "[" then

ReddString = StripString(iniText.line[mening + 1])

antalBokstaver = ReddString.char.count

if ReddString contains "cdrom" then

cdromenhet = ReddString.char[7..antalBokstaver]

end if

if ReddString contains "Vind" then

antalBokstaver = ReddString.char.count

vindspelAktivt = integer(ReddString.char[6])

end if

if ReddString contains "messg" then

antalBokstaver = ReddString.char.count

alertmessage = ReddString.char[7..antalBokstaver]

end if

if ReddString contains "fullscreen" then

antalBokstaver = ReddString.char.count

fullscreen = integer(ReddString.char[12])

end if

end if

end repeat

resultat = [#cdromenhet: cdromenhet, #vindspelAktivt: vindspelAktivt, #alertmessage: alertmessage, #screen: fullscreen]

return resultat

end

-- ...
```

#### 

I always find it super fun to read these old scripts and look at the weird mix of broken English, identifiers written in foreign languages (Swedish in this case, I think?) and probably extremely buggy code.

A bit further down, we can also find the function where the error text is actually being used:

MovieScript 1.ls

```
on checkpccdrompath

seperator = the last char in the applicationPath

drivelista = ["d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]

Retvar = VOID

repeat with Drive in drivelista

iscdrom = baDiskInfo(Drive, "type")

if iscdrom = "CD-ROM" then

Path = Drive & ":" & seperator & "p3media"

if checkoncd(Path) then

Retvar = Drive

exit repeat

end if

end if

end repeat

return Retvar

end

-- ... Part of setSearchPaths function

driveletter = checkpccdrompath()

if not voidp(driveletter) then

ini[#cdromenhet] = driveletter & ":"

hdMedia = mp & "p3media"

hdljud = mp & "p3media\p3ljud"

hdcast = mp & "p3media\p3cast"

cdMedia = string(ini.cdromenhet & "\p3media")

cdLjud = string(ini.cdromenhet & "\p3media\p3ljud")

cdCast = string(ini.cdromenhet & "\p3media\p3cast")

the searchPaths = [hdMedia, hdljud, hdcast, cdMedia, cdLjud, cdCast]

else

alert(string(ini.alertmessage))

quit()

end if

-- ...
```

Again, pretty straightforward code. They just loop over all possible Windows drives, trying to find one that contains a CD-ROM with the `p3media` folder on it. If one is found, the search paths are set up. Otherwise, the error message we saw is displayed and the game calls `quit()`.

## Patching the Script

Analyzing the `setSearchPaths` function a bit, I figured the easiest way to avoid executing the `quit()` branch while still allowing it to set up the search paths correctly would be to patch the `if` to jump to the next instruction instead of the `else` branch if the check failed. That would result in the CD paths being a bit weird, but I figured it would probably still work, since the game lets you do a “Full Install” where it copies all the required data to the hard disk and presumably loads the files from there.

In the assembly file, the instructions we want to patch are the following:

MovieScript 1.lasm

```
< 95 00 60       > [246] jmpifz [342] ......... if not voidp(driveletter) then / else

< 4C 08          > [249] getlocal 8 ........... <ini>
```

ProjectorRays sadly doesn’t yet support assembling the code back, but thankfully, the Lingo bytecode is incredibly simple, so we can do it manually with ImHex. Every instruction starts with a single opcode byte, followed by a big-endian integer that’s either 1, 2 or 4 bytes long. To make the manual patching a bit easier, I also made ProjectorRays output the bytes that make up each instruction (also submitted upstream to ProjectorRays).

Now for the patching: `jmpifz` has the opcode byte `0x95`, followed by the big-endian 16-bit relative offset `0x0060`, or 96 bytes, indicating where to jump. To simply jump to the next instruction, we need to jump 3 bytes forward (from offset 246 to offset 249), so we want to patch the instruction bytes to be `< 95 00 03 >`. Searching for the original byte sequence yielded only a single result at address `0x002252E8`, which I overwrote with the new instruction.

And with that, the game’s intro video was playing! 🎉

![The game's intro video playing](/assets/game_starting.B9PgIx08.png)

The game's intro video playing

# Copy Protection 3 - Rise of the Machines

Sadly, there was more to it.

![disappointment2.png](/assets/copy_protection.C1PIXWDL.png)

disappointment2.png

Again, simply searching for that string yielded two results, and I decided to start with `intro.dxr` for now.

Grepping Error Message 2

```
➜  rg -a 'Disc Error !'

P3media/intro.dxr

...

P3media/huset.dxr

...
```

Throwing it into ProjectorRays again resulted in a few more scripts, one of which was straightforwardly called `BehaviorScript 36 - Call Copy-X CD protection DLL (OPTGRAPH.DLL).ls`. *I didn’t need that much handholding, devs, but thanks either way.*

Inside was, as the filename suggested, some code that loads the `optgraph.dll` located next to the game executable and calls its `initdisplay()` function. It then checks the return value of that function, and if it doesn’t match some magic value, the game is once again aborted.

BehaviorScript 36 - Call Copy-X CD protection DLL (OPTGRAPH.DLL).ls

```
on exitFrame me

if the platform contains "Mac" then

nothing()

else

if Ok = 0 then

alert("Disc Error !")

avslutaSpelet()

else

if getReturnVal(me) <> 26079537 then

alert("Disc Error !")

avslutaSpelet()

end if

end if

end if

end
```

Interestingly, they didn’t even bother implementing the copy protection for Mac…

To get past this check, we have a few options. We could make the game believe that it’s running on a Mac, patch the comparison against the magic number or patch the DLL to make it always return that constant. Given that the identical check is implemented again in `huset.dxr` and that I didn’t really want to reverse engineer the runtime and patch the platform detection, I decided to patch `optgraph.dll` instead.

## Patching the Support DLL

Opening the DLL in Ghidra showed that it essentially contained only that one function and nothing else.

`initdisplay()` itself is a bit of a convoluted mess where they send a bunch of low-level commands to the CD drive to somehow construct that magic value from it.

Ghidra Decompile - OPTGRAPH.DLL

```
// ...

FUN_10001e50(&local_44,0,0x10);

local_3c = 3;

mciSendCommandA(local_c,0x814,0x100);

local_3c = 1;

local_2c = local_40;

mciSendCommandA(local_c,0x814,0x102,&local_44);

local_28 = (uint *)(local_40 & 0xff);

local_3c = 1;

local_48 = (char *)(local_40 >> 8 & 0xff);

local_38 = 4;

local_40._0_2_ = local_40._1_2_;

mciSendCommandA(local_c,0x814,0x112,&local_44);

// ...
```

I didn’t spend too much time reversing any of this but instead just used Ghidra’s wonderful `Patch Instruction` option to replace the first few instructions with a plain and boring `MOV` and `RET`.

Ghidra Assembly Listing - OPTGRAPH.DLL

```
**************************************************************

*                          FUNCTION                          *

**************************************************************

int __stdcall initdisplay(void)

assume FS_OFFSET = 0xffdff000

int               EAX:4          <RETURN>

0x1060  1  initdisplay

initdisplay

10001060 b8 31 f1        MOV        EAX, 26079537

8d 01

10001065 c3              RET

10001066 05              ??         05h

10001067 00              ??         00h

10001068 00              ??         00h
```

After struggling for an embarrassingly long time to get Ghidra to properly export the DLL again, I finally managed to do it through `File -> Export -> Original File`.

This seemed to have been the last thing needed, since the game now gets all the way to the menu screen!

![Start menu screen of the game](/assets/start_menu.BS9j7iUD.png)

Start menu screen of the game

## Writing an auto patcher

After I was done, I wrote a quick Python script that can apply the patches discussed in this post. This exact script will only work for the German version of the Game that I linked above. However, it shouldn’t be hard to adapt to other versions as well (probably only requires fixing the hash and the offset)

patch\_findus4.py

```
#!/usr/bin/env python3

import hashlib

import sys

from pathlib import Path

game_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else Path.cwd()

patches = [

(

game_dir / "Findus4.exe",

"b33312ff9086c1cbc09d9cde6baa0eff4220a1ed354c9a3211ff6787fff46e39",

0x2252E8,

bytes.fromhex("95 00 60"),

bytes.fromhex("95 00 03"),

),

(

game_dir / "optgraph.dll",

"9897563fd6447976e2780ba0711f6e4e0a5a98a5c5cbecc4cb9bd608d856bd50",

0x1060,

bytes.fromhex("55 8B EC 81 EC F4"),

bytes.fromhex("B8 31 F1 8D 01 C3"),

),

]

verified = []

for path, expected_hash, offset, original, replacement in patches:

data = bytearray(path.read_bytes())

actual_hash = hashlib.sha256(data).hexdigest()

if actual_hash != expected_hash:

raise SystemExit(f"Unexpected SHA-256 for {path}: {actual_hash}")

if data[offset : offset + len(original)] != original:

raise SystemExit(f"Unexpected bytes at 0x{offset:X} in {path}")

data[offset : offset + len(replacement)] = replacement

verified.append((path, data))

for path, data in verified:

path.chmod(path.stat().st_mode | 0o200)

path.write_bytes(data)

print(f"Patched {path}")
```

# Wrapping Up

After spending the evening playing through that game again, I also looked through a few other, similar games that I used to play. It turns out that a surprisingly large number of them used Macromedia and had essentially the same basic copy-protection mechanism. Sadly, there’s no easy way to make a common patch that works for all of these games, but with a little bit of effort, the same process can be applied to them as well.

Being able to play these games again after over 20 years, and maybe even having my future kid play them, makes me really happy :)

I really hope this post can bring the same joy to others as well!

---

Author: WerWolv

Post: Getting old Macromedia Director Games to run on modern Hardware

Link: <https://werwolv.net/posts/macromedia_copy_protection/>

Source: [@WerWolv/Blog | posts//index.mdx](https://github.dev/WerWolv/Blog/blob/main/src/content/posts//index.mdx)

License: [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)
