Skip to content

Instantly share code, notes, and snippets.

@Yepoleb
Created August 30, 2016 02:00
Show Gist options
  • Save Yepoleb/ce1a16e163993494df9bf0300c9f2f01 to your computer and use it in GitHub Desktop.
Save Yepoleb/ce1a16e163993494df9bf0300c9f2f01 to your computer and use it in GitHub Desktop.
Attempt to extract images from Macromedia Director dxr/dir files
import struct
import collections
import PIL.Image
# Attempt to extract images from Macromedia Director dxr/dir files
PATH = "ber.dxr"
CHUNK_FMT = ">4sL"
CHUNK_SIZE = struct.calcsize(CHUNK_FMT)
Chunk = collections.namedtuple("Chunk", ["section", "data", "size", "name"])
with open(PATH, "rb") as f:
binary = f.read()
def get_chunks(tree, section):
return filter(lambda c: c.section == section, tree.data)
# File format is based on the RIFF structure
def read_chunk(binary, pos=0):
section, size = struct.unpack_from(CHUNK_FMT, binary, pos)
section = section.decode("ASCII")
data_pos = pos + CHUNK_SIZE
# Lists contain only subelements instead of raw data
if section in ("RIFF", "RIFX", "LIST"):
# Read 4 byte long name
name = struct.unpack_from("4s", binary, pos + CHUNK_SIZE)[0]
name = name.decode("ASCII")
data_pos += 4
data = []
cur_pos = data_pos
# Subtract name length from the data size
while cur_pos < data_pos + size - 4:
chunk = read_chunk(binary, cur_pos)
data.append(chunk)
cur_pos += CHUNK_SIZE + chunk[2]
# Padding if size is uneven
if chunk.size % 2:
cur_pos += 1
return Chunk(section, data, size, name)
else:
data = binary[data_pos:data_pos + size]
return Chunk(section, data, size, None)
tree = read_chunk(binary)
for x in tree.data:
print(x.section, x.size)
# CAS* contains a 4 byte int for each cast, meaning unknown
cas = next(get_chunks(tree, "CAS*"))
cas_entries = [x[0] for x in struct.iter_unpack(">l", cas.data)]
print(cas_entries)
casts = get_chunks(tree, "CASt")
cast_details = []
for cast in casts:
cast_type = struct.unpack_from(">L", cast.data)[0]
# Image
if cast_type == 1:
# Length of the name is in the byte before it
name_len_offset = 0x7E
name_offset = name_len_offset + 1
name_len = cast.data[name_len_offset]
name = cast.data[name_offset:name_offset+name_len].decode("ASCII")
# Position of the image size depends on the name length
size_offset = name_offset + name_len + 0xA
height, width = struct.unpack_from(">HH", cast.data, size_offset)
details = {"name": name, "width": width, "height": height}
cast_details.append(details)
bitds = get_chunks(tree, "BITD")
alfas = get_chunks(tree, "ALFA")
for details, bitd, alfa in zip(cast_details, bitds, alfas):
# BITD content is not pure RGB values or any common file format
# Its entropy is too low for compression or proper encryption, it also stays
# constant even when there is empty space in the image.
#img = PIL.Image.new("RGB", (details["width"], details["height"]))
#img.putdata(bitd.data)
#img.save(details.name + ".tga")
with open(details["name"], "wb") as f:
f.write(bitd.data)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment