← trentontompkins.com

Cracking Open a 2004 Game: pulling every asset out of a Clickteam EXE

There's a particular kind of old PC game — the shareware-era, made-in-a-bedroom kind — that was built in Multimedia Fusion (later Clickteam Fusion). And one lovely thing about those games is that they are not really compiled in the way a C++ game is. The whole game — every sprite, every sound, the fonts, the icon, even the event logic — is packed into the .exe as structured data, sitting behind the runtime, waiting to be read back out.

This is a complete extraction of one such game: Mr Stump's Dentures, a Multimedia Fusion 1.5 title from 2004 that I'm rebuilding. By the end we'll have pulled out 1,010 graphics, 16 sounds, the application icon, the ten extension DLLs the runtime carries inside itself, and the Click-to-Create event scripts for all 79 levels. You can download the exact game file and the tool and follow along.

⇩ mrstumps.exe (the game, 2.2 MB) Mr Stump's on GitHub

The tool: Anaconda

The decompiler is Anaconda (also known by its parser library, mmfparser) — originally by Mathias Kærlev and kept alive by the Clickteam-modding community. It's written for Python 2.7, which is the one real setup hurdle: you'll want a Python 2.7 environment to run it. Grab the build I used here:

Anaconda decompiler (Python 2.7)

Version note. Classic Anaconda handles Multimedia Fusion 1.5 / 2.0 era games (which is what we want here). For modern Fusion 2.5+ titles it's been superseded by Nebula. If your target is a newer game, start with Nebula instead; for a 2004 MMF1.5 game like this one, classic Anaconda is exactly right.

Anaconda was built as an interactive tool, so the first thing a script needs is a little boilerplate to make it run headless — stub out the raw_input prompt and the editor it tries to spawn, then point Python at the Anaconda source:

# Python 2.7. Headless boilerplate (copied from Anaconda's own bimbam entry point).
import sys
sys.path.insert(0, r'C:\anaconda\Anaconda-main\Anaconda-main')   # wherever you unzipped it

import __builtin__ as _bi
_bi.raw_input = lambda *a, **kw: ''          # never block on a prompt
import subprocess as _sp                      # swallow the editor it tries to launch
_orig = _sp.Popen
def _pop(*a, **k):
    try: return _orig(*a, **k)
    except Exception:
        class _D(object):
            returncode = 0
            def communicate(self, *a, **k): return (b'', b'')
        return _D()
_sp.Popen = _pop

from mmfparser.data.exe import ExecutableData
from mmfparser.bytereader import ByteReader

Loading the game

One call parses the entire executable into an object tree. Ask for loadImages=True so the image bank comes along:

EXE = r'C:\MSD\src\mrstumps.exe'
game = ExecutableData(ByteReader(open(EXE, 'rb')), loadImages=True).gameData

print game.getProduct(), 'build', game.productBuild
# -> Multimedia Fusion 1.5  build 1026

That game object now has everything: game.images, game.sounds, game.fonts, game.icon, game.frames (the levels), and game.frameItems (the objects). We'll walk them one at a time.

Images — and a Python-2.7-shaped problem

Each image in the bank exposes its pixels as raw RGBA. The catch: the obvious way to save a PNG is Pillow, and dragging Pillow into a Python 2.7 install is its own afternoon. So skip it — a PNG is not hard to write by hand with nothing but the standard library's zlib and struct:

import struct, zlib

def write_png(path, w, h, rgba):
    # Minimal 8-bit RGBA PNG encoder — stdlib only, no Pillow.
    def chunk(tag, data):
        return (struct.pack('>I', len(data)) + tag + data +
                struct.pack('>I', zlib.crc32(tag + data) & 0xffffffff))
    ihdr = struct.pack('>IIBBBBB', w, h, 8, 6, 0, 0, 0)         # 8-bit, color type 6 = RGBA
    raw  = b''.join(b'\x00' + rgba[y*w*4:(y+1)*w*4] for y in range(h))  # filter byte 0 per row
    with open(path, 'wb') as f:
        f.write(b'\x89PNG\r\n\x1a\n' + chunk(b'IHDR', ihdr) +
                chunk(b'IDAT', zlib.compress(raw, 9)) + chunk(b'IEND', b''))

Now dump the bank. One gotcha worth knowing: getImageData() unloads the image after the first call, so make a single pass and don't read any image twice:

pal = next((f for f in game.frames if getattr(f, 'palette', None) is not None), None)

for img in game.images.items:
    img.load()
    w, h = int(img.width), int(img.height)
    rgba = img.getImageData(frame=pal)          # RGBA bytes; consumes the image
    if not rgba or w <= 0 or h <= 0:
        continue
    rgba = (rgba + b'\x00' * (w*h*4))[:w*h*4]    # pad/clip to exactly w*h*4
    write_png('images/img_%05d_%dx%d.png' % (img.handle, w, h), w, h, rgba)

That alone pulled 1,010 sprites out of the game — characters, tiles, UI, every frame of every animation.

Sounds, fonts, and the icon

Audio is even simpler — the sound bank hands you WAV bytes directly:

for snd in game.sounds.items:
    open('sounds/%s.wav' % snd.name, 'wb').write(snd.get_wav())

The application icon lives at game.icon as an AppIcon object (a little 256-color image). If pulling its pixels through the parser fights you, there's a no-nonsense Windows fallback that just lifts the icon straight off the file:

# PowerShell fallback for the icon — straight off the exe:
Add-Type -AssemblyName System.Drawing
[System.Drawing.Icon]::ExtractAssociatedIcon("C:\MSD\src\mrstumps.exe").ToBitmap().Save("icon.png")

The hidden cargo: extension DLLs inside the EXE

Here's the part that surprises people. A Multimedia Fusion exe doesn't just bundle your assets — it bundles the runtime extension DLLs the game depends on, embedded whole, right inside the file. (In MMF1.5 these carry the .cox extension; the same idea in MMF2 uses .mfx.) They're complete PE files, so you can carve them out by scanning for the DOS header, then reading the real file size out of the PE section table:

import re
data = open(EXE, 'rb').read()

for off in (m.start() for m in re.finditer(b'MZ\x90\x00\x03', data)):
    if off == 0:
        continue                                       # the host exe itself
    e_lfanew = struct.unpack_from('<I', data, off + 0x3C)[0]
    pe = off + e_lfanew
    if data[pe:pe+4] != b'PE\x00\x00':
        continue
    numsec  = struct.unpack_from('<H', data, pe + 6)[0]
    optsize = struct.unpack_from('<H', data, pe + 20)[0]
    sec = pe + 24 + optsize
    end = max(struct.unpack_from('<I', data, sec + i*40 + 20)[0] +   # raw pointer
              struct.unpack_from('<I', data, sec + i*40 + 16)[0]      # + raw size
              for i in range(numsec))
    open('dlls/embedded_%07d.dll' % off, 'wb').write(data[off:off+end])

That recovered ten embedded modules — the box-collision extension, an INI reader, a clock, a platform-movement engine, a save-game module, and so on. Read each one's PE export directory and you get its real name back (KcBoxB.cox, KcBoxA.cox, Platform.cox, SaveGame.cox…) — a complete parts list of how the game was actually assembled.

The real prize: the event logic

Sprites and sounds are nice. The thing you usually can't get back is the game's behavior — and Anaconda gives you that too. Each level is a frame, and each frame carries its events in Clickteam's Click-to-Create format: a list of condition→action rules. Pair that with the object table (handle → name) and you can read out what every object does:

# handle -> object name, so events read in plain language
handle_to_name = {h: info.name for h, info in game.frameItems.itemDict.items()}

for i, frame in enumerate(game.frames):
    events = getattr(frame, 'events', None)
    if not events:
        continue
    for group in events.items:               # each group = one condition -> action rule
        conditions = list(group.conditions)  # e.g. "collision between Stump and Denture"
        actions    = list(group.actions)     # e.g. "add 1 to Score; play sound 3"
        # ...serialize each condition/action with its parameters to JSON...

Across the game that's 79 frames of original event logic — the actual level layouts and rules, not a guess at them. For a remake, that's the difference between recreating a game from memory and reading its real source design straight out of the binary.

What you end up with. A flat folder of img_*.png sprites, a folder of .wav sounds, the icon, the carved .cox DLLs, and per-frame JSON of every object and event. From a single 2.2 MB exe with no source code, the entire game laid back open on the table.

Is this OK to do?

Reverse-engineering a binary you own, to recover assets and understand how it was built, is the classic preservation / personal-research case — the same spirit as ROM dumping a cartridge you own. Mr Stump's is a project I'm actively rebuilding, so here the “owner” and the “archaeologist” are the same person. The line to keep: don't redistribute someone else's copyrighted assets as if they were yours, and don't relaunch a commercial game's art and sound as a product. Pull apart the things you have a right to pull apart; learn the format; build your own thing with what you learn.

The lesson

“Compiled” doesn't always mean “sealed.” A whole class of older games are, underneath the runtime, just a well-structured archive of data — and when the format is documented and the tooling exists, the exe is an open book. Point the right parser at it and a twenty-year-old game hands you its sprites, its sounds, its icon, the DLLs it shipped with, and the rules it played by. That's not a hack so much as reading — the file was always willing to tell you, if you knew how to ask.

Extraction scripts released under the MIT License — Copyright © 2026 Trent Tompkins. Anaconda / mmfparser is the work of Mathias Kærlev and the Clickteam-modding community. Reverse-engineer what you own; don't redistribute others' copyrighted assets.