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)
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.
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.