Last active
September 22, 2024 21:50
-
-
Save windwakr/b7279f80a846dec97e11d7f966834acb to your computer and use it in GitHub Desktop.
Game Maker 4.1/4.2/4.3 decompiler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Game Maker 4.X decompiler | |
#for python 2.7 :^) | |
#Only tested on ~20 files | |
# | |
#As far as I can tell, there are no tools out there that actually support GM4(even if they claim to). | |
#Unlike later versions, image data is stored unencrypted. So we need to partially parse the GMD. | |
import struct | |
import io | |
import os | |
import sys | |
import string | |
GMDSTART = 0 | |
KEYADDR = 0 | |
VERSION = 0 | |
def detectVersion(fh): | |
global GMDSTART, KEYADDR, VERSION | |
tmp = fh.read() | |
if tmp.find('Game_Maker 4.1') != -1: | |
KEYADDR = 0x10C8E0 | |
GMDSTART = 0x10C8E4 | |
VERSION = 410 | |
elif tmp.find('Game_Maker 4.2') != -1: | |
KEYADDR = 0x10C8E0 | |
GMDSTART = 0x10C8E4 | |
VERSION = 420 | |
elif tmp.find('Game_Maker 4.3') != -1: | |
KEYADDR = 0x124F80 | |
GMDSTART = 0x124F84 | |
VERSION = 430 | |
else: | |
print 'Not a Game Maker 4.1/4.2/4.3 file' | |
raise SystemExit() | |
def readDword(fh): | |
return struct.unpack('<I', fh.read(4))[0] | |
def writeDword(fh, val): | |
fh.write(struct.pack('<I', val)) | |
def readDataWithLen(fh): | |
length = readDword(fh) | |
return fh.read(length) | |
def transferImageData(inf, outf): #transfer unencrypted image data from inf to outf | |
outfoffset = outf.tell() | |
inf.seek(GMDSTART + outfoffset) | |
tmp = io.BytesIO() | |
type = readDword(inf) | |
writeDword(tmp, type) | |
if type == 0: #plain BMP? Can anything else be here? | |
tmp.write(inf.read(2)) #'BM' | |
length = readDword(inf) #length of whole BMP | |
writeDword(tmp, length) | |
tmp.write(inf.read(length-6)) | |
elif type == 1: | |
width = readDword(inf) | |
writeDword(tmp, width) | |
height = readDword(inf) | |
writeDword(tmp, height) | |
for _ in xrange(height): | |
length = readDword(inf) | |
writeDword(tmp, length) | |
tmp.write(inf.read(length)) | |
tmp.write(inf.read(length * 3)) | |
elif type == 2: | |
length = readDword(inf) | |
writeDword(tmp, length) | |
tmp.write(inf.read(length * 3)) | |
width = readDword(inf) | |
writeDword(tmp, width) | |
height = readDword(inf) | |
writeDword(tmp, height) | |
for _ in xrange(height): | |
length = readDword(inf) | |
writeDword(tmp, length) | |
tmp.write(inf.read(length * 2)) | |
elif type == 0xFFFFFFFF: #empty | |
pass #nothing else to do here | |
else: | |
print 'Invalid image type?' | |
raise SystemExit() | |
outf.write(tmp.getvalue()) | |
#This function based off code at http://ismavatar.com/lgm/formats/gmkrypt1.html | |
def generateSwapTable(seed): | |
table = (range(0, 256), range(0, 256)) | |
for i in range(1, 10001): | |
j = 1 + ((i * seed) % 254) | |
table[0][j], table[0][j + 1] = table[0][j + 1], table[0][j] | |
for i in range(1, 256): | |
table[1][table[0][i]] = i | |
return table | |
def usage(): | |
print 'Usage:\n\t%s filetodecompile.exe' % (os.path.basename(sys.argv[0])) | |
raise SystemExit() | |
if len(sys.argv) < 2: | |
usage() | |
if os.path.exists(sys.argv[1]) == False: | |
usage() | |
out = io.BytesIO() | |
with open(sys.argv[1], 'rb') as f: | |
detectVersion(f) | |
f.seek(KEYADDR) | |
key = readDword(f) | |
table = generateSwapTable(key)[1] | |
#decrypt the gmd | |
#in GM4 the encryption is just a simple substitution cipher, so we'll use python's string translation functions to speed it up | |
table = string.maketrans(''.join([chr(x) for x in range(0,256)]), ''.join([chr(x) for x in table])) | |
out.write(f.read().translate(table)) | |
#start parsing the gmd looking for image data | |
#unique ID, stored unencrypted | |
out.seek(0x10) | |
f.seek(GMDSTART + 0x10) | |
out.write(f.read(0x10)) | |
#parse options | |
out.read(0x3C) | |
if VERSION >= 420: | |
out.read(0x10) | |
if readDword(out) > 0: | |
transferImageData(f, out) | |
out.read(0x0C) | |
readDataWithLen(out) | |
out.read(0x14) | |
if readDword(out) == 2: | |
transferImageData(f, out) | |
transferImageData(f, out) | |
if readDword(out) > 0: | |
transferImageData(f, out) | |
readDataWithLen(out) | |
if VERSION >= 420: | |
out.read(0x10) | |
#parse sounds, just passing through | |
if readDword(out) != 400: | |
print 'Error reading sound data' | |
raise SystemExit() | |
numsounds = readDword(out) | |
for _ in xrange(numsounds): | |
exists = readDword(out) | |
if exists: | |
readDataWithLen(out) | |
if readDword(out) != 400: | |
print 'Error reading sound data' | |
raise SystemExit() | |
sndtype = readDword(out) | |
readDataWithLen(out) | |
if sndtype != 0xFFFFFFFF: | |
readDataWithLen(out) | |
out.read(0x0C) | |
#parse sprites | |
if readDword(out) != 400: | |
print 'Error reading sprite data' | |
raise SystemExit() | |
numsprites = readDword(out) | |
for _ in xrange(numsprites): | |
exists = readDword(out) | |
if exists: | |
readDataWithLen(out) | |
if readDword(out) != 400: | |
print 'Error reading sprite data' | |
raise SystemExit() | |
out.read(0x34) | |
numframes = readDword(out) | |
for _ in xrange(numframes): | |
transferImageData(f, out) | |
#parse backgrounds | |
if readDword(out) != 400: | |
print 'Error reading background data' | |
raise SystemExit() | |
numbackgrounds = readDword(out) | |
for _ in xrange(numbackgrounds): | |
exists = readDword(out) | |
if exists: | |
readDataWithLen(out) | |
if readDword(out) != 400: | |
print 'Error reading background data' | |
raise SystemExit() | |
out.read(0x14) | |
imgexists = readDword(out) | |
if imgexists: | |
transferImageData(f, out) | |
#the rest of the file should all be encrypted, so we can stop parsing here | |
with open('%s_dec.gmd' % (os.path.splitext(os.path.basename(sys.argv[1]))[0]), 'wb') as f: | |
f.write(out.getvalue()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I'm getting a bunch of compilation errors. What version of Python should I be using to run this?