Created
          August 30, 2016 02:00 
        
      - 
      
- 
        Save Yepoleb/ce1a16e163993494df9bf0300c9f2f01 to your computer and use it in GitHub Desktop. 
    Attempt to extract images from Macromedia Director dxr/dir files
  
        
  
    
      This file contains hidden or 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
    
  
  
    
  | 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