self->
Every variable any object uses lives in one struct Instance, exactly as GM gives every instance
one bag of variables. Cross-object access (other.life, objTimer.start) just works.
The making-of · KDFW Decomp Studio
Keyboard Drumset Fucking Werewolf is a free game from 2011: one song, seven levels, three minutes. This is how its executable became 9,300 lines of readable C, a runtime that never existed, and builds for the desktop, the browser and phones.
ScrollThe first surprise was in the code, not the binary. Every level ends when the music says so.
An invisible object, objTimer, starts the song and then checks, sixty times a second,
how many milliseconds have passed since:
if current_time-start>=80960 and current_time-start<93980 {
if room=level3 {
if global.headcount>=6 room=level3D else {global.deathcause=2 game_over() ...}
Reach the goal before the song moves on, or it's game over. Hover the song to see each window.
This is also why the original could feel bugged: when a 2011 runner stutters on a 2020s
machine, the wall clock keeps running and the level's deadline arrives with less of the level played. Our runtime can
lock current_time to the music's own playback position.
FWA.exe is 12 MB of x86, so the plan was a classic disassembly. Then strings printed
\Software\Game Maker\ and d3d_transform_stack_push. The executable is a generic
Game Maker 7 runner, written in Delphi, with the actual game glued to its end: sprites, sounds,
rooms, and the code as GML source text. Nothing to disassemble — something to unwrap.
Inflated, the 10 MB stream starts with 1,579 random dwords, a seed (25249), then 366 more. The seed shuffles a 256-byte table with ten thousand swaps; each byte is then decoded through the inverse table minus its position in the stream, so the same letter never looks the same twice.
Press the button: this is that exact algorithm, running in your browser on the first record of the game.
Script bodies came out as noise again — but structured noise. The same 16 bytes opened four different
scripts. Guessing they read draw_sprite_ext(, a brute force over all 63,500 possible tables
took two seconds and returned a family of candidates. The first one almost worked:
draw_sprite_ext(sprite_indexßimage_indexßx.1ßy …
Commas had become ß, minus signs dots. The right member of the family decoded everything:
a = 101, b = 49, which is the seed 49 × 250 + 95 = 12345. A fixed seed, the
same for every Game Maker 7 game.
// For every table the generator can make, check the known plaintext.
for (int a = 6; a < 256; a++) for (int b = 0; b < 254; b++) {
shuffle(t, a, b);
if (matches(t, "draw_sprite_ext(")) printf("a=%d b=%d\n", a, b);
}
a=101 b=0 a=101 b=49 a=101 b=101 …
real 0m2.159s
GML is forgiving: no semicolons, = is also a comparison, every variable is created on first use
and secretly an array. The decompiler parses all 328 blocks of code, keeps the author's comments and blank lines,
infers a C type for every variable, then writes C that a person could have written. Same function names, same
variable names, same comments. Here is the eskimo meeting a bat:
if hurt>0 exit
times=global.pickups
if times>5 time=5
sound_play(sndBat1)
repeat (times) {
dir=random(360)
my_id=instance_create(x+lengthdir_x(32,dir),y+lengthdir_y(32,dir),objLostPickup)
my_id.direction=dir
my_id.speed=6+random(4)
}
hurt=60
global.pickups-=5
if (self->hurt > 0) return;
self->times = global.pickups;
// [studio fix] the original caps a variable named `time`,
// a typo for `times`
if (self->times > 5) {
if (fix_enabled()) self->times = 5;
else self->time = 5;
}
sound_play(sndBat1);
repeat (self->times) {
self->dir = random(360);
self->my_id = instance_create(self->x + lengthdir_x(32, self->dir),
self->y + lengthdir_y(32, self->dir), objLostPickup);
set_direction(inst(self->my_id), self->dir);
set_speed(inst(self->my_id), 6 + random(4));
}
self->hurt = 60;
global.pickups -= 5;
self->Every variable any object uses lives in one struct Instance, exactly as GM gives every instance
one bag of variables. Cross-object access (other.life, objTimer.start) just works.
with (objBlock) { … }A real C macro: two nested for loops whose inner declaration shadows
self and other. The body reads exactly like the GML.
union { double my_x; double my_x_array[16]; }In GM, my_x and my_x[0] are the same storage. Ten objects use it as a number,
one as an array; a C11 anonymous union says precisely that.
set_speed(self, 6)Speed, direction, hspeed and vspeed are four views of one motion. Writes go through setters so the other three stay in sync; reads stay plain fields.
round(2.5) == 2GML's round is Delphi's: halves go to the even neighbour. The runtime maps the name onto
rint(), keeping the spelling and the behaviour.
keyboard_lastkey = 0; /* GML assigned "" */When the source is genuinely odd — a string stored in a number — the C does what GM did and says so.
The decompiled C calls draw_sprite_ext, place_free, d3d_draw_wall…
so something has to answer. gmrt is a small Game Maker 7 runtime in C11 on raylib:
instances and the GM event order, motion and friction, pixel-precise collisions, rooms and views, fonts,
surfaces, blend modes, the 3D mode, sound, and a string garbage collector that the decompiler feeds with the
location of every String variable.
One of Game Maker's favourite tricks — d3d_set_fog(1, c_white, 0, 0) to paint a sprite as a
flat silhouette — rides along each vertex's normal, so it costs no extra draw calls.
screen_redraw() was drawing into the screen instead of the
quarter-size surface the game had selected — the scene vanished behind the vignette.Level four switches Game Maker into 3D, renders the city into a quarter-size surface for a chunky look, scales
it up, and leans on SurfaceFix.dll — the DLL shipped next to FWA.exe — to give surfaces a depth
buffer. On machines where that patch or Direct3D 8 surfaces misbehave, the level is the first to break.
The rebuild needs no DLL: its surfaces simply have depth. Drag to compare with a recording of the original.
That comparison caught two last differences. The claws' invisible pixels were still writing depth, cutting
the buildings behind them along the arm; the original discards fully transparent pixels (an alpha test).
And its black fog, d3d_set_fog(1, c_black, 600, 700), never shows in the recording: the range
behaves like depth-buffer values between 0 and 1, so it never kicks in — and the red sky stays visible.

Pixels, smooth, sharp-bilinear for crisp non-integer scaling, or a gentle CRT.
The sides of wide screens get a blurred, darkened echo of the game instead of black bars.
Scale2x / Scale3x smoothing at export time; collisions still use the original pixels.
The song can't be slowed, so difficulty scales the goals: fewer cubes, victims, bones, boss HP.
Two genuine bugs of the original, each fixed behind fix_enabled() and marked in the C.
Rebuilds what earlier rooms leave behind, then jumps the song — and the clock — to the level.
current_time follows the music's playback position, so a hitch can't steal a deadline.
On-screen arrows, Z, X and Start for phones, on by default on touch screens.
The studio ships no game — no code, no sprites, no music; the only pictures of it are the screenshots on this page. The studio, a command line and a desktop app, turns your FWA.exe into a self-contained CMake project, then builds whatever your machine can build.