#!/usr/bin/env python # -*- coding: utf-8 -*- # # Supports: # CASt # sound # bitmap # field # # KEY* (file pointers) # STXT (field/text) # BITD (bitmap) # sndS (sound data) # sndH (sound metadata) # cupt (cue points) # CAS* (cast pointers) from subprocess import call import sys, os, getopt import struct import wave import aifc import ntpath import json from PIL import Image, ImageDraw, ImagePalette import bitstring PALETTE_MAC = [ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCC, 0xFF, 0xFF, 0x99, 0xFF, 0xFF, 0x66, 0xFF, 0xFF, 0x33, 0xFF, 0xFF, 0x00, 0xFF, 0xCC, 0xFF, 0xFF, 0xCC, 0xCC, 0xFF, 0xCC, 0x99, 0xFF, 0xCC, 0x66, 0xFF, 0xCC, 0x33, 0xFF, 0xCC, 0x00, 0xFF, 0x99, 0xFF, 0xFF, 0x99, 0xCC, 0xFF, 0x99, 0x99, 0xFF, 0x99, 0x66, 0xFF, 0x99, 0x33, 0xFF, 0x99, 0x00, 0xFF, 0x66, 0xFF, 0xFF, 0x66, 0xCC, 0xFF, 0x66, 0x99, 0xFF, 0x66, 0x66, 0xFF, 0x66, 0x33, 0xFF, 0x66, 0x00, 0xFF, 0x33, 0xFF, 0xFF, 0x33, 0xCC, 0xFF, 0x33, 0x99, 0xFF, 0x33, 0x66, 0xFF, 0x33, 0x33, 0xFF, 0x33, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xCC, 0xFF, 0x00, 0x99, 0xFF, 0x00, 0x66, 0xFF, 0x00, 0x33, 0xFF, 0x00, 0x00, 0xCC, 0xFF, 0xFF, 0xCC, 0xFF, 0xCC, 0xCC, 0xFF, 0x99, 0xCC, 0xFF, 0x66, 0xCC, 0xFF, 0x33, 0xCC, 0xFF, 0x00, 0xCC, 0xCC, 0xFF, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x99, 0xCC, 0xCC, 0x66, 0xCC, 0xCC, 0x33, 0xCC, 0xCC, 0x00, 0xCC, 0x99, 0xFF, 0xCC, 0x99, 0xCC, 0xCC, 0x99, 0x99, 0xCC, 0x99, 0x66, 0xCC, 0x99, 0x33, 0xCC, 0x99, 0x00, 0xCC, 0x66, 0xFF, 0xCC, 0x66, 0xCC, 0xCC, 0x66, 0x99, 0xCC, 0x66, 0x66, 0xCC, 0x66, 0x33, 0xCC, 0x66, 0x00, 0xCC, 0x33, 0xFF, 0xCC, 0x33, 0xCC, 0xCC, 0x33, 0x99, 0xCC, 0x33, 0x66, 0xCC, 0x33, 0x33, 0xCC, 0x33, 0x00, 0xCC, 0x00, 0xFF, 0xCC, 0x00, 0xCC, 0xCC, 0x00, 0x99, 0xCC, 0x00, 0x66, 0xCC, 0x00, 0x33, 0xCC, 0x00, 0x00, 0x99, 0xFF, 0xFF, 0x99, 0xFF, 0xCC, 0x99, 0xFF, 0x99, 0x99, 0xFF, 0x66, 0x99, 0xFF, 0x33, 0x99, 0xFF, 0x00, 0x99, 0xCC, 0xFF, 0x99, 0xCC, 0xCC, 0x99, 0xCC, 0x99, 0x99, 0xCC, 0x66, 0x99, 0xCC, 0x33, 0x99, 0xCC, 0x00, 0x99, 0x99, 0xFF, 0x99, 0x99, 0xCC, 0x99, 0x99, 0x99, 0x99, 0x99, 0x66, 0x99, 0x99, 0x33, 0x99, 0x99, 0x00, 0x99, 0x66, 0xFF, 0x99, 0x66, 0xCC, 0x99, 0x66, 0x99, 0x99, 0x66, 0x66, 0x99, 0x66, 0x33, 0x99, 0x66, 0x00, 0x99, 0x33, 0xFF, 0x99, 0x33, 0xCC, 0x99, 0x33, 0x99, 0x99, 0x33, 0x66, 0x99, 0x33, 0x33, 0x99, 0x33, 0x00, 0x99, 0x00, 0xFF, 0x99, 0x00, 0xCC, 0x99, 0x00, 0x99, 0x99, 0x00, 0x66, 0x99, 0x00, 0x33, 0x99, 0x00, 0x00, 0x66, 0xFF, 0xFF, 0x66, 0xFF, 0xCC, 0x66, 0xFF, 0x99, 0x66, 0xFF, 0x66, 0x66, 0xFF, 0x33, 0x66, 0xFF, 0x00, 0x66, 0xCC, 0xFF, 0x66, 0xCC, 0xCC, 0x66, 0xCC, 0x99, 0x66, 0xCC, 0x66, 0x66, 0xCC, 0x33, 0x66, 0xCC, 0x00, 0x66, 0x99, 0xFF, 0x66, 0x99, 0xCC, 0x66, 0x99, 0x99, 0x66, 0x99, 0x66, 0x66, 0x99, 0x33, 0x66, 0x99, 0x00, 0x66, 0x66, 0xFF, 0x66, 0x66, 0xCC, 0x66, 0x66, 0x99, 0x66, 0x66, 0x66, 0x66, 0x66, 0x33, 0x66, 0x66, 0x00, 0x66, 0x33, 0xFF, 0x66, 0x33, 0xCC, 0x66, 0x33, 0x99, 0x66, 0x33, 0x66, 0x66, 0x33, 0x33, 0x66, 0x33, 0x00, 0x66, 0x00, 0xFF, 0x66, 0x00, 0xCC, 0x66, 0x00, 0x99, 0x66, 0x00, 0x66, 0x66, 0x00, 0x33, 0x66, 0x00, 0x00, 0x33, 0xFF, 0xFF, 0x33, 0xFF, 0xCC, 0x33, 0xFF, 0x99, 0x33, 0xFF, 0x66, 0x33, 0xFF, 0x33, 0x33, 0xFF, 0x00, 0x33, 0xCC, 0xFF, 0x33, 0xCC, 0xCC, 0x33, 0xCC, 0x99, 0x33, 0xCC, 0x66, 0x33, 0xCC, 0x33, 0x33, 0xCC, 0x00, 0x33, 0x99, 0xFF, 0x33, 0x99, 0xCC, 0x33, 0x99, 0x99, 0x33, 0x99, 0x66, 0x33, 0x99, 0x33, 0x33, 0x99, 0x00, 0x33, 0x66, 0xFF, 0x33, 0x66, 0xCC, 0x33, 0x66, 0x99, 0x33, 0x66, 0x66, 0x33, 0x66, 0x33, 0x33, 0x66, 0x00, 0x33, 0x33, 0xFF, 0x33, 0x33, 0xCC, 0x33, 0x33, 0x99, 0x33, 0x33, 0x66, 0x33, 0x33, 0x33, 0x33, 0x33, 0x00, 0x33, 0x00, 0xFF, 0x33, 0x00, 0xCC, 0x33, 0x00, 0x99, 0x33, 0x00, 0x66, 0x33, 0x00, 0x33, 0x33, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xCC, 0x00, 0xFF, 0x99, 0x00, 0xFF, 0x66, 0x00, 0xFF, 0x33, 0x00, 0xFF, 0x00, 0x00, 0xCC, 0xFF, 0x00, 0xCC, 0xCC, 0x00, 0xCC, 0x99, 0x00, 0xCC, 0x66, 0x00, 0xCC, 0x33, 0x00, 0xCC, 0x00, 0x00, 0x99, 0xFF, 0x00, 0x99, 0xCC, 0x00, 0x99, 0x99, 0x00, 0x99, 0x66, 0x00, 0x99, 0x33, 0x00, 0x99, 0x00, 0x00, 0x66, 0xFF, 0x00, 0x66, 0xCC, 0x00, 0x66, 0x99, 0x00, 0x66, 0x66, 0x00, 0x66, 0x33, 0x00, 0x66, 0x00, 0x00, 0x33, 0xFF, 0x00, 0x33, 0xCC, 0x00, 0x33, 0x99, 0x00, 0x33, 0x66, 0x00, 0x33, 0x33, 0x00, 0x33, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xCC, 0x00, 0x00, 0x99, 0x00, 0x00, 0x66, 0x00, 0x00, 0x33, 0xEE, 0x00, 0x00, 0xDD, 0x00, 0x00, 0xBB, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x88, 0x00, 0x00, 0x77, 0x00, 0x00, 0x55, 0x00, 0x00, 0x44, 0x00, 0x00, 0x22, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0xEE, 0x00, 0x00, 0xDD, 0x00, 0x00, 0xBB, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x88, 0x00, 0x00, 0x77, 0x00, 0x00, 0x55, 0x00, 0x00, 0x44, 0x00, 0x00, 0x22, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0xEE, 0x00, 0x00, 0xDD, 0x00, 0x00, 0xBB, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x88, 0x00, 0x00, 0x77, 0x00, 0x00, 0x55, 0x00, 0x00, 0x44, 0x00, 0x00, 0x22, 0x00, 0x00, 0x11, 0xEE, 0xEE, 0xEE, 0xDD, 0xDD, 0xDD, 0xBB, 0xBB, 0xBB, 0xAA, 0xAA, 0xAA, 0x88, 0x88, 0x88, 0x77, 0x77, 0x77, 0x55, 0x55, 0x55, 0x44, 0x44, 0x44, 0x22, 0x22, 0x22, 0x11, 0x11, 0x11, 0x00, 0x00, 0x00 ] PALETTE_MAC.reverse() PALETTE_WIN = [ 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0xC0, 0xDC, 0xC0, 0xA6, 0xCA, 0xF0, 0x2A, 0x3F, 0xAA, 0x2A, 0x3F, 0xFF, 0x2A, 0x5F, 0x00, 0x2A, 0x5F, 0x55, 0x2A, 0x5F, 0xAA, 0x2A, 0x5F, 0xFF, 0x2A, 0x7F, 0x00, 0x2A, 0x7F, 0x55, 0x2A, 0x7F, 0xAA, 0x2A, 0x7F, 0xFF, 0x2A, 0x9F, 0x00, 0x2A, 0x9F, 0x55, 0x2A, 0x9F, 0xAA, 0x2A, 0x9F, 0xFF, 0x2A, 0xBF, 0x00, 0x2A, 0xBF, 0x55, 0x2A, 0xBF, 0xAA, 0x2A, 0xBF, 0xFF, 0x2A, 0xDF, 0x00, 0x2A, 0xDF, 0x55, 0x2A, 0xDF, 0xAA, 0x2A, 0xDF, 0xFF, 0x2A, 0xFF, 0x00, 0x2A, 0xFF, 0x55, 0x2A, 0xFF, 0xAA, 0x2A, 0xFF, 0xFF, 0x55, 0x00, 0x00, 0x55, 0x00, 0x55, 0x55, 0x00, 0xAA, 0x55, 0x00, 0xFF, 0x55, 0x1F, 0x00, 0x55, 0x1F, 0x55, 0x55, 0x1F, 0xAA, 0x55, 0x1F, 0xFF, 0x55, 0x3F, 0x00, 0x55, 0x3F, 0x55, 0x55, 0x3F, 0xAA, 0x55, 0x3F, 0xFF, 0x55, 0x5F, 0x00, 0x55, 0x5F, 0x55, 0x55, 0x5F, 0xAA, 0x55, 0x5F, 0xFF, 0x55, 0x7F, 0x00, 0x55, 0x7F, 0x55, 0x55, 0x7F, 0xAA, 0x55, 0x7F, 0xFF, 0x55, 0x9F, 0x00, 0x55, 0x9F, 0x55, 0x55, 0x9F, 0xAA, 0x55, 0x9F, 0xFF, 0x55, 0xBF, 0x00, 0x55, 0xBF, 0x55, 0x55, 0xBF, 0xAA, 0x55, 0xBF, 0xFF, 0x55, 0xDF, 0x00, 0x55, 0xDF, 0x55, 0x55, 0xDF, 0xAA, 0x55, 0xDF, 0xFF, 0x55, 0xFF, 0x00, 0x55, 0xFF, 0x55, 0x55, 0xFF, 0xAA, 0x55, 0xFF, 0xFF, 0x7F, 0x00, 0x00, 0x7F, 0x00, 0x55, 0x7F, 0x00, 0xAA, 0x7F, 0x00, 0xFF, 0x7F, 0x1F, 0x00, 0x7F, 0x1F, 0x55, 0x7F, 0x1F, 0xAA, 0x7F, 0x1F, 0xFF, 0x7F, 0x3F, 0x00, 0x7F, 0x3F, 0x55, 0x7F, 0x3F, 0xAA, 0x7F, 0x3F, 0xFF, 0x7F, 0x5F, 0x00, 0x7F, 0x5F, 0x55, 0x7F, 0x5F, 0xAA, 0x7F, 0x5F, 0xFF, 0x7F, 0x7F, 0x00, 0x7F, 0x7F, 0x55, 0x7F, 0x7F, 0xAA, 0x7F, 0x7F, 0xFF, 0x7F, 0x9F, 0x00, 0x7F, 0x9F, 0x55, 0x7F, 0x9F, 0xAA, 0x7F, 0x9F, 0xFF, 0x7F, 0xBF, 0x00, 0x7F, 0xBF, 0x55, 0x7F, 0xBF, 0xAA, 0x7F, 0xBF, 0xFF, 0x7F, 0xDF, 0x00, 0x7F, 0xDF, 0x55, 0x7F, 0xDF, 0xAA, 0x7F, 0xDF, 0xFF, 0x7F, 0xFF, 0x00, 0x7F, 0xFF, 0x55, 0x7F, 0xFF, 0xAA, 0x7F, 0xFF, 0xFF, 0xAA, 0x00, 0x00, 0xAA, 0x00, 0x55, 0xAA, 0x00, 0xAA, 0xAA, 0x00, 0xFF, 0xAA, 0x1F, 0x00, 0xAA, 0x1F, 0x55, 0xAA, 0x1F, 0xAA, 0xAA, 0x1F, 0xFF, 0xAA, 0x3F, 0x00, 0xAA, 0x3F, 0x55, 0xAA, 0x3F, 0xAA, 0xAA, 0x3F, 0xFF, 0xAA, 0x5F, 0x00, 0xAA, 0x5F, 0x55, 0xAA, 0x5F, 0xAA, 0xAA, 0x5F, 0xFF, 0xAA, 0x7F, 0x00, 0xAA, 0x7F, 0x55, 0xAA, 0x7F, 0xAA, 0xAA, 0x7F, 0xFF, 0xAA, 0x9F, 0x00, 0xAA, 0x9F, 0x55, 0xAA, 0x9F, 0xAA, 0xAA, 0x9F, 0xFF, 0xAA, 0xBF, 0x00, 0xAA, 0xBF, 0x55, 0xAA, 0xBF, 0xAA, 0xAA, 0xBF, 0xFF, 0xAA, 0xDF, 0x00, 0xAA, 0xDF, 0x55, 0xAA, 0xDF, 0xAA, 0xAA, 0xDF, 0xFF, 0xAA, 0xFF, 0x00, 0xAA, 0xFF, 0x55, 0xAA, 0xFF, 0xAA, 0xAA, 0xFF, 0xFF, 0xD4, 0x00, 0x00, 0xD4, 0x00, 0x55, 0xD4, 0x00, 0xAA, 0xD4, 0x00, 0xFF, 0xD4, 0x1F, 0x00, 0xD4, 0x1F, 0x55, 0xD4, 0x1F, 0xAA, 0xD4, 0x1F, 0xFF, 0xD4, 0x3F, 0x00, 0xD4, 0x3F, 0x55, 0xD4, 0x3F, 0xAA, 0xD4, 0x3F, 0xFF, 0xD4, 0x5F, 0x00, 0xD4, 0x5F, 0x55, 0xD4, 0x5F, 0xAA, 0xD4, 0x5F, 0xFF, 0xD4, 0x7F, 0x00, 0xD4, 0x7F, 0x55, 0xD4, 0x7F, 0xAA, 0xD4, 0x7F, 0xFF, 0xD4, 0x9F, 0x00, 0xD4, 0x9F, 0x55, 0xD4, 0x9F, 0xAA, 0xD4, 0x9F, 0xFF, 0xD4, 0xBF, 0x00, 0xD4, 0xBF, 0x55, 0xD4, 0xBF, 0xAA, 0xD4, 0xBF, 0xFF, 0xD4, 0xDF, 0x00, 0xD4, 0xDF, 0x55, 0xD4, 0xDF, 0xAA, 0xD4, 0xDF, 0xFF, 0xD4, 0xFF, 0x00, 0xD4, 0xFF, 0x55, 0xD4, 0xFF, 0xAA, 0xD4, 0xFF, 0xFF, 0xFF, 0x00, 0x55, 0xFF, 0x00, 0xAA, 0xFF, 0x1F, 0x00, 0xFF, 0x1F, 0x55, 0xFF, 0x1F, 0xAA, 0xFF, 0x1F, 0xFF, 0xFF, 0x3F, 0x00, 0xFF, 0x3F, 0x55, 0xFF, 0x3F, 0xAA, 0xFF, 0x3F, 0xFF, 0xFF, 0x5F, 0x00, 0xFF, 0x5F, 0x55, 0xFF, 0x5F, 0xAA, 0xFF, 0x5F, 0xFF, 0xFF, 0x7F, 0x00, 0xFF, 0x7F, 0x55, 0xFF, 0x7F, 0xAA, 0xFF, 0x7F, 0xFF, 0xFF, 0x9F, 0x00, 0xFF, 0x9F, 0x55, 0xFF, 0x9F, 0xAA, 0xFF, 0x9F, 0xFF, 0xFF, 0xBF, 0x00, 0xFF, 0xBF, 0x55, 0xFF, 0xBF, 0xAA, 0xFF, 0xBF, 0xFF, 0xFF, 0xDF, 0x00, 0xFF, 0xDF, 0x55, 0xFF, 0xDF, 0xAA, 0xFF, 0xDF, 0xFF, 0xFF, 0xFF, 0x55, 0xFF, 0xFF, 0xAA, 0xCC, 0xCC, 0xFF, 0xFF, 0xCC, 0xFF, 0x33, 0xFF, 0xFF, 0x66, 0xFF, 0xFF, 0x99, 0xFF, 0xFF, 0xCC, 0xFF, 0xFF, 0x00, 0x7F, 0x00, 0x00, 0x7F, 0x55, 0x00, 0x7F, 0xAA, 0x00, 0x7F, 0xFF, 0x00, 0x9F, 0x00, 0x00, 0x9F, 0x55, 0x00, 0x9F, 0xAA, 0x00, 0x9F, 0xFF, 0x00, 0xBF, 0x00, 0x00, 0xBF, 0x55, 0x00, 0xBF, 0xAA, 0x00, 0xBF, 0xFF, 0x00, 0xDF, 0x00, 0x00, 0xDF, 0x55, 0x00, 0xDF, 0xAA, 0x00, 0xDF, 0xFF, 0x00, 0xFF, 0x55, 0x00, 0xFF, 0xAA, 0x2A, 0x00, 0x00, 0x2A, 0x00, 0x55, 0x2A, 0x00, 0xAA, 0x2A, 0x00, 0xFF, 0x2A, 0x1F, 0x00, 0x2A, 0x1F, 0x55, 0x2A, 0x1F, 0xAA, 0x2A, 0x1F, 0xFF, 0x2A, 0x3F, 0x00, 0x2A, 0x3F, 0x55, 0xFF, 0xFB, 0xF0, 0xA0, 0xA0, 0xA4, 0x80, 0x80, 0x80, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF ] PALETTE_WIN.reverse() # pretty hardcoded function for converting the bitd images def convertBITD( w, h, f, entry ): bitmapValues = [[0 for x in range( w )] for y in range( h )] draw_x = 0 draw_y = 0 start = entry['dataOffset'] + 8 # +8 bytes to skip fourcc and length size = entry['dataLength'] readMode = 0 print(entry) if entry["meta"]["bitDepth"] > 8: # this is bad, there's actually no value if it's a 1-bit image, so it's reading the next byte instead, but it works so hey readMode = 1 pad = 0 if w % 2: pad = h if ( ( w * h ) + pad ) == size: readMode = 2 print("readMode: " + str(readMode)) #padbytes = -1 # seek to the bitd image data f.seek( start ) while f.tell() <= start + size: if readMode == 1: msk = f.read(1) bt = bitstring.BitArray( msk ).bin i = 0 for c in bt: bitmapValues[ draw_y ][ draw_x ] = 1 - int(c) draw_x += 1 if draw_x >= w: draw_x = i-7 # 8-byte offset somehow draw_y += 1 if draw_y >= h: return bitmapValues i += 1 elif readMode == 2: col = struct.unpack('B', f.read(1) )[0] bitmapValues[ draw_y ][ draw_x ] = 0xFF - col draw_x += 1 if draw_x >= w: # print("reached line end at " + str( f.tell() - start ) ) if pad: f.read(1) # padding byte? draw_x = 0 draw_y += 1 if draw_y >= h: print("reached end at " + str( f.tell() - start ) ) return bitmapValues else: rLen = struct.unpack('B', f.read(1) )[0] if 0x100 - rLen > 0x7F: #if 0x101 - rLen > 0x7F: # this is interesting, for some bitmaps it works, but some need 0x100 #doLog(" lin (" + str(draw_x) + "," + str(draw_y) + " - len " + str(rLen) + ")" ) rLen += 1 for j in range(0, rLen): #if f.tell() >= l['offset'] + l['length']: # break val = struct.unpack('B', f.read(1) )[0] #doLog(" lin - value (" + str( 0xFF - val ) + ")" ) #doLog(" lin - put pixel (" + str( draw_x ) + "," + str(draw_y) + "=" + str( 0xFF - val ) + ")") bitmapValues[ draw_y ][ draw_x ] = 0xFF - val draw_x += 1 if draw_x >= w: #doLog(" lin - line change (x" + str( draw_x-1 ) + "/y" + str(draw_y+1) + "@p" + str( f.tell() - start ) + ")") if w % 2: draw_x = -1 else: draw_x = 0 draw_y += 1 if draw_y >= h: #doLog(" lin - exceeded height (" + str( (start+size) - f.tell() ) + " bytes left)") return bitmapValues else: rLen = 0x101 - rLen val = struct.unpack('B', f.read(1) )[0] #doLog(" rle (" + str(draw_x) + "," + str(draw_y) + " - len " + str(rLen) + ")" ) #doLog(" rle - value (" + str( 0xFF - val ) + ")" ) for j in range(0, rLen): #if f.tell() >= l['offset'] + l['length']: # break #doLog(" rle - put pixel (" + str( draw_x ) + "," + str(draw_y) + "=" + str( 0xFF - val ) + ")") bitmapValues[ draw_y ][ draw_x ] = 0xFF - val draw_x += 1 if draw_x >= w: #doLog(" rle - line change (x" + str( draw_x-1 ) + "/y" + str(draw_y+1) + "@p" + str( f.tell() - start ) + ")") if w % 2: draw_x = -1 else: draw_x = 0 draw_y += 1 if draw_y >= h: #doLog(" rle - exceeded height (" + str( (start+size) - f.tell() ) + " bytes left)") return bitmapValues return bitmapValues writeFiles = False writeRaw = False fileNum = 1 entries = {} castList = [] metaList = {} BigEndian = False fileEntries = [] castLibraries = [] def doLog(t): global logfile logfile.write(t + "\n") print(t) # f = open(sys.argv[1], "rb") def readCST(f): global entries, fileEntries, castLibraries, castList, metaList, BigEndian, PALETTE_MAC, PALETTE_WIN # fourcc # pos 0-4 (XFIR) RIFX_SIGN = f.read(4).decode("ansi") doLog( "RIFX_SIGN: " + str( RIFX_SIGN ) ) # this is a mess tbh if RIFX_SIGN == "RIFX": BigEndian = False else: BigEndian = True # file size/length # pos 4-8 (Length) SIZE = struct.unpack( ('i' if BigEndian else '>i'), f.read(4) )[0] doLog( "SIZE: " + str( SIZE ) + " (" + str( round( SIZE / 1024 ) ) + "kb)" ) # some signage related to cst/cxt & dir/dxr # pos 8-12 SIGN = f.read(4) doLog( "SIGN: " + str( SIGN ) ) # skip to offset 60, just for convenience f.seek(60) # pos 60 # get file count for pointer list rawFileNum = struct.unpack( ('i' if BigEndian else '>i'), f.read(4) )[0] doLog( "File num: " + str( rawFileNum ) ) # skip 12 bytes to beginning of file pointers f.read(12) # pos 76, file block begin doLog("\n\n--- READ MMAP ---") for i in range(0, rawFileNum): # save beginning of pointer pointerOffset = f.tell() # file type fourcc (cast/sound/bitmap etc) if BigEndian: entryType = f.read(4).decode("ansi")[::-1] # 4 else: entryType = f.read(4).decode("ansi") # 4 # file size entryLength = struct.unpack( ('i' if BigEndian else '>i'), f.read(4) )[0] # 8 # file offset entryOffset = struct.unpack( ('i' if BigEndian else '>i'), f.read(4) )[0] # 12 ''' entries.append({ 'num': i, 'name': '', 'type': entryType, 'length': entryLength, 'offset': entryOffset, 'poffset': pointerOffset, 'files': [], 'friendlyType': '' }) ''' entries[i] = { 'num': i, 'name': '', 'type': entryType, 'length': entryLength, 'offset': entryOffset, 'poffset': pointerOffset, 'files': [], 'friendlyType': '' } fileEntry = {} fileEntry['id'] = i fileEntry['type'] = entryType fileEntry['dataLength'] = entryLength fileEntry['dataOffset'] = entryOffset fileEntry['pointerOffset'] = pointerOffset fileEntry['linkedEntries'] = [] fileEntry['meta'] = {} fileEntries.append( fileEntry ) unknown1 = struct.unpack('i', f.read(4) )[0] unknown2 = struct.unpack('i', f.read(4) )[0] if entryType != "free": doLog("[POINT " + str(i) + " @ " + str(pointerOffset) + "][" + (entryType) + "] " + str( entryLength ) + "b, Offset: " + str( entryOffset ) + ", U1: " + str(unknown1) + ", U2: " + str(unknown2) ) else: doLog("[POINT " + str(i) + " @ " + str(pointerOffset) + "][----]") # doLog( " U1: " + str(unknown1) + ", U2: " + str(unknown2) ) # f.read(8) # padding data? # read cast library names doLog("\n\n--- READ MCSL* ---") for e in fileEntries: if e['type'] == "MCsL": doLog("-- MCsL --") f.seek( e['dataOffset'], 0 ) f.seek(8,1) # fourcc, size unknown1 = struct.unpack('>i', f.read(4) )[0] castCount = struct.unpack('>i', f.read(4) )[0] unknown2 = struct.unpack('>i', f.read(4) )[0] arraySize = struct.unpack('>i', f.read(4) )[0] doLog( " [Meta] Count: " + str(castCount) + ", Size: " + str(arraySize) ) for j in range( 0, castCount ): ar0 = struct.unpack('>i', f.read(4) )[0] ar1 = struct.unpack('>i', f.read(4) )[0] ar2 = struct.unpack('>i', f.read(4) )[0] ar3 = struct.unpack('>i', f.read(4) )[0] doLog( " [Off" + str(j) + "] 0: " + str(ar0) + ", 1: " + str(ar1) + ", 2: " + str(ar2) + ", 3: " + str(ar3) ) unknown3 = struct.unpack('>h', f.read(2) )[0] castLibrariesLength = struct.unpack('>i', f.read(4) )[0] for j in range( 0, castCount ): cNameLen = struct.unpack('>b', f.read(1) )[0] cName = f.read( cNameLen ).decode('ansi'); f.seek(1,1) cPathLen = struct.unpack('>b', f.read(1) )[0] if cPathLen > 0: cPath = f.read( cPathLen ).decode('ansi'); f.seek(2,1) else: cPath = "" f.seek(1,1) cPreloadSettings = struct.unpack('>b', f.read(1) )[0] cStorageType = struct.unpack('>b', f.read(1) )[0] # cUnknown1 = struct.unpack('>b', f.read(1) )[0] cMemberCount = struct.unpack('>h', f.read(2) )[0] cNumId = struct.unpack('>i', f.read(4) )[0] # cUnknown2 = struct.unpack('>h', f.read(2) )[0] doLog(" [Lib" + str(j) + "]") doLog(" Name: " + str(cName) ) doLog(" Path: " + str(cPath) ) doLog(" Members: " + str(cMemberCount) ) doLog(" Preload: " + str(cPreloadSettings) ) doLog(" Storage: " + str(cStorageType) ) doLog(" Id: " + str(cNumId) ) castLib = {} castLib['id'] = j castLib['name'] = cName castLib['external'] = False castLib['members'] = {} castLibraries.append(castLib) directory = outFolder + "/" + cName if not os.path.exists(directory): os.makedirs(directory) doLog("") if len(castLibraries) == 0: cName = "Standalone" castLibraries.append({ 'id': 0, 'name': cName, 'members': {} }) directory = outFolder + "/" + cName if not os.path.exists(directory): os.makedirs(directory) # loop through all found entries, skip all but the KEY* list doLog("\n\n--- READ KEY* ---") for e in fileEntries: # e = entries[i] if e['type'] != "KEY*": continue f.seek( e['dataOffset'], 0 ) fEntryHeaderRaw = f.read(4) # fourcc header fEntryLengthRaw = f.read(4) # length # parse header, and reverse it if applicable if BigEndian: fEntryHeader = fEntryHeaderRaw.decode("ansi")[::-1] else: fEntryHeader = fEntryHeaderRaw.decode("ansi") # parse length fEntryLength = struct.unpack( ('i' if BigEndian else '>i'), fEntryLengthRaw )[0] # put into entry data # e['headerRaw'] = fEntryHeaderRaw # e['lengthRaw'] = fEntryLengthRaw doLog("--- KEY @ " + str( e['dataOffset'] ) + " ---") # no idea what these do fUnknownNum1 = struct.unpack( ('i' if BigEndian else '>i'), f.read(4) )[0] fUnknownNum2 = struct.unpack( ('i' if BigEndian else '>i'), f.read(4) )[0] # total list of entries in key list fEntryNum = struct.unpack( ('i' if BigEndian else '>i'), f.read(4) )[0] doLog(" fUnknownNum1: " + str( fUnknownNum1 ) + ", fUnknownNum2: " + str( fUnknownNum2 ) + ", fEntryNum: " + str( fEntryNum ) ) for i in range(0, fEntryNum): # save offset kPos = f.tell() # slot in entries pointing to a file (bitd/snd/script ex.) castFileSlot = struct.unpack( ('i' if BigEndian else '>i'), f.read(4) )[0] # slot in entries pointing to the cast castSlot = struct.unpack( ('i' if BigEndian else '>i'), f.read(4) )[0] if BigEndian: castType = f.read(4).decode("ansi")[::-1] else: castType = f.read(4).decode("ansi") # castEntry = fileEntries[ castSlot ] if castType == "CAS*": # if castSlot >= 1024: castNum = castSlot - 1024 doLog("[KEY " + str(i) + "] CASTLIB #" + str( castNum ) + " @ " + str(castFileSlot) ) castLibraries[ castNum ]['libSlot'] = castFileSlot else: doLog("[KEY " + str(i) + "] Link file entry #" + str( castFileSlot ) + " (" + str( castType ) + ") to #" + str( castSlot ) + "" ) if not fileEntries[castSlot]: doLog(" INVALID KEY CAST SLOT: " + str( castFileSlot ) + "->" + str( castSlot ) + " (" + str( castType ) + ") @ " + str(kPos) ) return elif not fileEntries[castFileSlot]: doLog(" INVALID KEY FILE SLOT: " + str( castFileSlot ) + "->" + str( castSlot ) + " (" + str( castType ) + ") @ " + str(kPos) ) return else: # entries[ castFileSlot ]['parent'] = entries[ castSlot ] # entries[ castSlot ]['files'].append( entries[ castFileSlot ] ) fileEntries[ castSlot ]['linkedEntries'].append( castFileSlot ) # fileEntries[ castSlot ]['linkedEntries'].append( fileEntries[ castFileSlot ] ) # doLog(" KeyCastOffset: " + str( castOffset ) + ", KeyCastId: " + str( castId ) + ", KeyCastType: " + str( castType ) ) doLog("\n\n--- ADD CAST MEMBERS FROM CAS* ---") for e in castLibraries: # doLog( e['name'] + ": " + str( e['libSlot'] ) ) if not 'libSlot' in e: doLog(" No library slot for " + e["name"]) continue castEntry = fileEntries[ e['libSlot'] ] f.seek( castEntry['dataOffset'], 0 ) f.seek(8,1) # skip fourcc, length for i in range(0, round( castEntry['dataLength'] / 4 ) ): #two values, so divide by 4 (bytes) # cast slot is an int castSlot = struct.unpack('>i', f.read(4) )[0] castNum = i + 2 if castSlot == 0: continue doLog( "[" + e['name'] + "," + str(castNum) + "] " + str(castSlot) ) castMember = {} castMember['slot'] = castSlot castMember['name'] = '' castMember['dataOffset'] = fileEntries[ castSlot ]['dataOffset'] castMember['dataLength'] = fileEntries[ castSlot ]['dataLength'] castMember['linkedEntries'] = fileEntries[ castSlot ]['linkedEntries'] castMember['meta'] = {} castMember['fields'] = {} e['members'][ castNum ] = castMember # loop through all the rest of the files doLog("\n\n--- READ CAST MEMBERS ---") for cLib in castLibraries: doLog( "[CASTLIB] " + cLib['name'] + " (" + str( len( cLib['members'] ) ) + ")" ) for mNum in cLib['members']: e = cLib['members'][mNum] f.seek( e['dataOffset'], 0 ) fEntryHeaderRaw = f.read(4) fEntryLengthRaw = f.read(4) fEntryHeader = fEntryHeaderRaw.decode("ansi")[::-1] fEntryLength = struct.unpack( ('i' if BigEndian else '>i'), fEntryLengthRaw )[0] doLog(" [CASTMEMBER " + str(mNum) + "][" + fEntryHeader + "]") doLog(" [.HEADER] " + fEntryHeader ) doLog(" [.LENGTH] " + str(fEntryLength) ) doLog(" [OFFSET] " + str(e['dataOffset']) ) doLog(" [LENGTH] " + str(e['dataLength']) ) # cast type, e.g. 1 = bitmap, 3 = field, 6 = audio castType = struct.unpack('>i', f.read(4) )[0] e['castType'] = castType # data length castDataLen = struct.unpack('>i', f.read(4) )[0] e['dataLength'] = castDataLen # data at the end of the data, good description castDataEnd = struct.unpack('>i', f.read(4) )[0] e['dataEnd'] = castDataEnd meta = e['meta'] if castDataLen > 0: # skip for some reason skipLen = struct.unpack('>i', f.read(4) )[0] f.seek(skipLen - 4, 1) # cast fields, no idea what they're used for castFieldNum = struct.unpack('>h', f.read(2) )[0] for k in range(0, castFieldNum): e['fields'][k] = struct.unpack('>i', f.read(4) )[0] castFinalSize = struct.unpack('>i', f.read(4) )[0] # rest of the size # cast name castNameLength = struct.unpack('>b', f.read(1) )[0] if castNameLength > 0: castInfoName = f.read( castNameLength ).decode('ansi') e['name'] = castInfoName # null byte f.seek(1,1) doLog(" [NAME] " + e['name'] ) if castType == 1: # bitmap # e['friendlyType'] = 'bitmap' doLog(" [TYPE] Bitmap") # f.read(1) # e['ink'] = struct.unpack('>b', f.read(1) )[0] # unknown f.seek(2,1) # position on stage meta['posY'] = struct.unpack('>h', f.read(2) )[0] meta['posX'] = struct.unpack('>h', f.read(2) )[0] # to note with all of these, they're in "height, width" order heightRaw = struct.unpack('>h', f.read(2) )[0] widthRaw = struct.unpack('>h', f.read(2) )[0] # to get the proper width/height, the padding has to be subtracted off values, no idea what purpose it serves meta['height'] = heightRaw - meta['posY'] meta['width'] = widthRaw - meta['posX'] # no clue what this is # e['constant'] = f.read(4) f.seek(4,1) # neither this f.seek(4,1) # reg point, for having something else than 0,0 as the center, same subtracting stuff here regyRaw = struct.unpack('>h', f.read(2) )[0] regxRaw = struct.unpack('>h', f.read(2) )[0] meta['regY'] = regyRaw - meta['posY'] meta['regX'] = regxRaw - meta['posX'] # THE DATA ENDS HERE IF THE BITMAP IS 1-BIT meta['bitAlpha'] = struct.unpack('b', f.read(1) )[0] # not sure at all meta['bitDepth'] = struct.unpack('b', f.read(1) )[0] f.seek(2,1) # unknown meta['palette'] = struct.unpack('>h', f.read(2) )[0] # i have only seen -1 being used here doLog(" [SIZE] " + str(meta['width']) + "x" + str(meta['height']) ) elif castType == 3: # field doLog(" [TYPE] Field") # e['friendlyType'] = 'field' elif castType == 4: # xtra/unknown doLog(" [TYPE] Palette") # e['friendlyType'] = 'palette' elif castType == 6: # sound doLog(" [TYPE] Sound") # e['friendlyType'] = 'sound' castInfoCodec = f.read( struct.unpack('b', f.read(1) )[0] ).decode("ansi") meta['soundCodec'] = castInfoCodec doLog(" [CODEC] " + str(meta['soundCodec']) ) elif castType == 8: # vector doLog(" [TYPE] Vector") # e['friendlyType'] = 'vector' elif castType == 11: # script doLog(" [TYPE] Script") # e['friendlyType'] = 'script' elif castType == 14: # transition doLog(" [TYPE] Transition") # e['friendlyType'] = 'transition' elif castType == 15: # xtra/unknown doLog(" [TYPE] Xtra") # e['friendlyType'] = 'misc/xtra' else: doLog(" [TYPE] Unknown (" + str(castType) + ")") if writeRaw: f.seek( e['dataOffset'], 0 ) fileName = outFolder + "/" + cLib['name'] + "/" + str(mNum) + ".cast" rawdata = open( fileName, "wb") rawdata.write( f.read( e['dataLength'] ) ) rawdata.close() doLog(" [SAVE CAST] " + fileName ) doLog(" [LINKED] " + str( len(e['linkedEntries']) ) ) for lNum in e['linkedEntries']: linkedEntry = fileEntries[ lNum ] doLog(" [LINKED] #" + str(lNum) + ": " + linkedEntry['type'] ) # sound metadata if linkedEntry['type'] == "sndH": f.seek( linkedEntry['dataOffset'], 0 ) f.seek(8,1) # skip fourcc & length f.seek(4,1) # unknown soundLength = struct.unpack('>i', f.read(4) )[0] f.seek(4,1) # what f.seek(20,1) # null? f.seek(4,1) # sound length plus what f.seek(4,1) # sound length again? f.seek(4,1) # sound length again? sampleRate = struct.unpack('>i', f.read(4) )[0] f.seek(4,1) # sample rate again? meta['soundLength'] = soundLength meta['soundSampleRate'] = sampleRate doLog(" [LENGTH] " + str(meta['soundLength']) ) doLog(" [SAMPLERATE] " + str(meta['soundSampleRate']) ) # cue points if linkedEntry['type'] == 'cupt': f.seek( linkedEntry['dataOffset'], 0 ) f.seek(8,1) # skip fourcc & length cuptEntries = struct.unpack('>i', f.read(4) )[0] meta['cuePoints'] = [] for i in range(0, cuptEntries): something = struct.unpack('>h', f.read(2) )[0] sampleOffset = struct.unpack('>h', f.read(2) )[0] textLength = struct.unpack('>b', f.read(1) )[0] if textLength > 0: cueName = f.read(textLength).decode("ansi") else: cueName = "" padLength = 31 - textLength f.seek(padLength, 1) meta['cuePoints'].append([ sampleOffset, cueName ]) doLog(" [CUEPOINTS] " + str( meta['cuePoints'] ) ) # palette data if linkedEntry['type'] == 'CLUT': num = round( linkedEntry['dataLength'] / 6 ) meta['palette'] = [] for p in range(0, num ): red1 = struct.unpack('B', f.read(1) )[0] red2 = struct.unpack('B', f.read(1) )[0] green1 = struct.unpack('B', f.read(1) )[0] green2 = struct.unpack('B', f.read(1) )[0] blue1 = struct.unpack('B', f.read(1) )[0] blue2 = struct.unpack('B', f.read(1) )[0] col = ( red1, green1, blue1 ) meta['palette'].append( col ) # clut.close() meta['palette'].reverse() if writeRaw: f.seek( linkedEntry['dataOffset'], 0 ) fileName = outFolder + "/" + cLib['name'] + "/" + str(mNum) + "." + str(lNum) + ".dat" rawdata = open( fileName, "wb") rawdata.write( f.read( linkedEntry['dataLength'] ) ) rawdata.close() doLog(" [SAVE LINKED] " + fileName ) if writeFiles: f.seek( linkedEntry['dataOffset'], 0 ) f.seek(8,1) # skip fourcc & length if linkedEntry['type'] == "STXT": # unknown f.read(4) # length of the text textLength = struct.unpack('>i', f.read(4) )[0] # data at the end of the content, no idea what textPadding = struct.unpack('>i', f.read(4) )[0] # read text content textContent = f.read( textLength ) fileName = outFolder + "/" + cLib['name'] + "/" + str(mNum) + ".txt" txts = open( fileName, "wb") txts.write( textContent ) txts.close() if linkedEntry['type'] == "sndS": snd = wave.open( outFolder + "/" + cLib['name'] + "/" + str(mNum) + ".wav", "w") snd.setnchannels(1) snd.setsampwidth(1) snd.setframerate( meta['soundSampleRate'] ) snd.writeframes( f.read( linkedEntry['dataLength'] ) ) snd.close() if linkedEntry['type'] == "BITD": imWidth = meta["width"] imHeight = meta["height"] if imWidth <= 0 or imHeight <= 0: continue if imWidth > 4096 or imHeight > 4096: continue linkedEntry["meta"]["bitDepth"] = meta["bitDepth"] # metaList[ e['memberNum'] ]['size'] = l["length"] if meta["bitDepth"] > 8: im = Image.new("1", (imWidth, imHeight) ) # 1-bit 0/1 image else: im = Image.new("P", (imWidth, imHeight) ) # 8-bit palette image pal = [] # system palette, it's in reverse twice for b in range(0,255): l = b * 3 pal.append( PALETTE_MAC[l+2] ) pal.append( PALETTE_MAC[l+1] ) pal.append( PALETTE_MAC[l] ) ''' for pc in castLibraries[1]['members'][2]['palette']: pal.append(pc[0]) pal.append(pc[1]) pal.append(pc[2]) ''' im.putpalette( pal ) dr = ImageDraw.Draw(im) bitmapValues = convertBITD( imWidth, imHeight, f, linkedEntry ) x = 0 y = 0 # draw the image for y in range( 0, imHeight ): for x in range( 0, imWidth ): dr.point( (x, y), bitmapValues[y][x] ) # save as bmp # im.save( os.environ['temp'] + "/cstPython.bmp", "BMP") im.save( outFolder + "/" + (cLib['name']) + "/" + str(mNum) + ".bmp", "BMP") # use magick to convert one opaque and one transparent (from white colour) png # call("magick convert " + os.environ['temp'] + "/cstPython.bmp " + outFolder + "/" + (cLib['name']) + "/" + str(mNum) + ".png") # call("magick convert " + outFolder + "/tmp.bmp " + outFolder + "/" + str(e['memberNum']) + "O.png") # call("magick convert " + outFolder + "/tmp.bmp -transparent \"#FFFFFF\" " + outFolder + "/" + str(e['memberNum']) + "T.png") del dr # palette data if linkedEntry['type'] == 'CLUT': clut = open( outFolder + "/" + cLib['name'] + "/" + str(mNum) + ".aco", "wb") clut.write( struct.pack('>h', 1) ) clut.write( struct.pack('>h', num) ) for p in meta['palette']: clut.write( struct.pack('>H', 0) ) # nc clut.write( struct.pack('>H', p[0] * 256 ) ) # w clut.write( struct.pack('>H', p[1] * 256 ) ) # x clut.write( struct.pack('>H', p[2] * 256 ) ) # y clut.write( struct.pack('>H', 0) ) # z clut.close() doLog("") ''' for i in entries: e = entries[i] # skip junk if e['type'] == "free" or e['type'] == "junk": doLog("[---- " + str(e['num']) + " @ " + str(e['offset']) + "][" + e['type'] + "]") continue f.seek( e['offset'], 0 ) fEntryHeaderRaw = f.read(4) fEntryLengthRaw = f.read(4) if BigEndian: fEntryHeader = fEntryHeaderRaw.decode("ansi")[::-1] else: fEntryHeader = fEntryHeaderRaw.decode("ansi") fEntryLength = struct.unpack( ('i' if BigEndian else '>i'), fEntryLengthRaw )[0] e['headerRaw'] = fEntryHeaderRaw e['lengthRaw'] = fEntryLengthRaw doLog("[FILE " + str(e['num']) + " @ " + str(e['offset']) + ":" + str( fEntryLength ) + "][" + e['type'] + "->" + fEntryHeader + "]") elif castType == 6: # sound doLog(" [TYPE] Sound") e['friendlyType'] = 'sound' castInfoCodec = f.read( struct.unpack('b', f.read(1) )[0] ).decode("ansi") e['name'] = castInfoName e['codec'] = castInfoCodec # project metadata if e['type'] == "VWFI": skipLen = struct.unpack('>i', f.read(4) )[0] f.seek(skipLen - 4, 1) fieldNum = struct.unpack('>h', f.read(2) )[0] f.seek(4, 1) for i in range(0, fieldNum): # f.seek(4, 1) unknown = struct.unpack('>i', f.read(4) )[0] doLog( "Unknown value " + str(i) + ": " + str(unknown) ) textLength = struct.unpack('>b', f.read(1) )[0] createdBy = f.read(textLength).decode("ansi") doLog( "Created: " + createdBy ) f.seek(1,1) textLength = struct.unpack('>b', f.read(1) )[0] modifiedBy = f.read(textLength).decode("ansi") doLog( "Modified: " + modifiedBy ) f.seek(1,1) textLength = struct.unpack('>b', f.read(1) )[0] filePath = f.read(textLength).decode("ansi") doLog( "Path: " + filePath ) #return ''' #tmp = Image.open( "pal.bmp" ) #mullePalette = tmp.palette #tmp.close() # mostly metadata in json here def parseCast( num, f ): # doLog("[CAST " + str(e['num']) + "]") global entries, castList, metaList, writeRaw, writeFiles # e = entries[num] e = castList[ num ] if e['type'] != 'CASt': # doLog("[" + e['type'] + "] ???\n") return False metaList[ e['memberNum'] ] = {} metaList[ e['memberNum'] ]['num'] = e['memberNum'] doLog("[CAST " + str(e['memberNum']) + "]") doLog(" [TYPE] " + str( e["castType"] ) + " (" + str( e["friendlyType"] ) + ")" ) metaList[ e['memberNum'] ]['castType'] = e['castType'] metaList[ e['memberNum'] ]['castTypeF'] = e['friendlyType'] if 'codec' in e: doLog(" [CODEC] " + str( e["codec"] ) ) metaList[ e['memberNum'] ]['soundCodec'] = e['codec'] if e["castType"] == 1: # bitmap doLog(" [SIZE] " + str( e["width"] ) + "x" + str( e["height"] ) + " (" + str( e["widthRaw"] ) + "x" + str( e["heightRaw"] ) + ")" ) metaList[ e['memberNum'] ]['imageWidth'] = e['width'] metaList[ e['memberNum'] ]['imageHeight'] = e['height'] doLog(" [POS] " + str( e["posX"] ) + "x, " + str( e["posY"] ) + "y" ) metaList[ e['memberNum'] ]['imagePosX'] = e['posX'] metaList[ e['memberNum'] ]['imagePosY'] = e['posY'] doLog(" [REG] " + str( e["regx"] ) + "," + str( e["regy"] ) ) metaList[ e['memberNum'] ]['imageRegX'] = e['regx'] metaList[ e['memberNum'] ]['imageRegY'] = e['regy'] # doLog(" [CONSTANT] " + str( e["constant"] ) + " / " + str( struct.unpack('i', e["constant"] )[0] ) ) # doLog(" [INK] " + str( e["ink"] ) ) doLog(" [BITDEPTH] " + str( e["bitdepth"] ) ) doLog(" [BITALPHA] " + str( e["bitalpha"] ) ) doLog(" [PALETTE] " + str( e["palette"] ) ) if e["castType"] == 6: # sound doLog(" [SAMPLERATE] " + str( e["sampleRate"] ) ) metaList[ e['memberNum'] ]['sampleRate'] = e['sampleRate'] doLog(" [SYS] POffset: " + str( e["poffset"] ) + ", Offset: " + str( e["offset"] ) + ", Length: " + str( e["length"] ) + ", Data length: " + str( e["dataLength"] ) + ", Data end: " + str( e["dataEnd"] ) ) if 'name' in e: doLog(" [INFO] Name: " + str( e["name"] ) ) metaList[ e['memberNum'] ]['name'] = e['name'] for l in e['files']: # doLog(" [INFO] Codec: " + str( castInfoCodec ) ) doLog(" [LINKED] Num: " + str(l["num"]) + ", Type: " + l['type'] + ", POffset: " + str( l['poffset'] ) + ", Offset: " + str( l['offset'] ) + ", Length: " + str( l['length'] ) ) if l['type'] == "sndS": # if e['codec'] == "kMoaCfFormat_WAVE" or e['codec'] == "kMoaCfFormat_snd": # using a python library here to write the wav data, even if it's an aiff file, dunno why that works if writeFiles: snd = wave.open( outFolder + "/" + str(e['memberNum']) + ".wav", "w") snd.setnchannels(1) snd.setsampwidth(1) snd.setframerate( e["sampleRate"] ) snd.writeframes( l['content'] ) snd.close() ''' elif e['codec'] == "kMoaCfFormat_AIFF": # using a python library here to write the wav data, even if it's an aiff file, dunno why that works snd = aifc.open( outFolder + "/" + str(e['memberNum']) + ".aifc", "w") # snd.aiff() snd.setnchannels(1) snd.setsampwidth(1) snd.setframerate( e["sampleRate"] ) snd.writeframes( l['content'] ) snd.close() ''' doLog(" [DURATION] " + str( l["length"] / e["sampleRate"] ) ) metaList[ e['memberNum'] ]['duration'] = l["length"] / e["sampleRate"] metaList[ e['memberNum'] ]['size'] = l["length"] if l['type'] == "BITD": if e["width"] <= 0 or e["height"] <= 0: continue if e["width"] > 4096 or e["height"] > 4096: continue l["bitdepth"] = e["bitdepth"] metaList[ e['memberNum'] ]['size'] = l["length"] if writeRaw: bitm = open( outFolder + "/" + str(e['memberNum']) + ".bitd", "wb") bitm.write( l['content'] ) bitm.close() if writeFiles: if e["bitdepth"] > 8: im = Image.new("1", (e["width"], e["height"]) ) # 1-bit 0/1 image else: im = Image.new("P", (e["width"], e["height"]) ) # 8-bit palette image tmp = Image.open( "pal.bmp" ) im.palette = tmp.palette tmp.close() dr = ImageDraw.Draw(im) bitmapValues = convertBITD( e['width'], e['height'], f, l ) x = 0 y = 0 # doLog( str(len(colours[0])) + ", " + str(len(colours[1])) + ", " + str(len(colours[2])) ) # draw the image for y in range( 0, e['height'] ): for x in range( 0, e['width'] ): dr.point( (x, y), bitmapValues[y][x] ) # save as bmp im.save( outFolder + "/tmp.bmp", "BMP") # use magick to convert one opaque and one transparent (from white colour) png call("magick convert " + outFolder + "/tmp.bmp " + outFolder + "/" + str(e['memberNum']) + ".png") # call("magick convert " + outFolder + "/tmp.bmp " + outFolder + "/" + str(e['memberNum']) + "O.png") # call("magick convert " + outFolder + "/tmp.bmp -transparent \"#FFFFFF\" " + outFolder + "/" + str(e['memberNum']) + "T.png") del dr if l['type'] == "STXT": fileFormat = "txt" testContent = l['content'].decode("ansi")[:2] if testContent == "[#": fileFormat = "swlist" # print("Contents: " + testContent) metaList[ e['memberNum'] ]['fieldFormat'] = fileFormat # simply write the text data if writeFiles: txt = open( outFolder + "/" + str(e['memberNum']) + "." + fileFormat, "wb") txt.write( l['content'] ) txt.close() if l['type'] == "cupt": print(" [CUEPOINTS] " + str(l['cuePoints']) ) metaList[ e['memberNum'] ]['cuePoints'] = l['cuePoints'] # cupt = open( outFolder + "/" + str(e['memberNum']) + ".cupt", "wb") # cupt.write( l['content'] ) # cupt.close() if writeRaw: cst = open( outFolder + "/" + str(e['memberNum']) + ".cast", "wb") f.seek( e['offset'], 0 ) cst.write( f.read( e['length'] + 8 ) ) cst.close() # test if e["castType"] == 4: doLog("PALETTE!!") return doLog("") def main(argv): global logfile, outFolder, fileEntries if len(sys.argv) <= 1: print("Usage: python cst_python.py <filename> <optional cast number>") return cfgInputCST = sys.argv[1] cfgFileName = ntpath.basename(cfgInputCST) # cfgCastNum = ( int(sys.argv[2]) - 1 ) if sys.argv[2] else 0 if len(sys.argv) >= 3: cfgCastNum = ( int(sys.argv[2]) - 1 ) else: cfgCastNum = 0 logfile = open("cst_" + cfgFileName + ".log", "w") f = open(cfgInputCST, "rb") outFolder = "cst_out/" + cfgFileName if not os.path.exists(outFolder): print("MAKE FOLDER") os.makedirs(outFolder) readCST(f) ''' doLog("\n\n--- READ CASTS ---") if cfgCastNum > 0: parseCast( cfgCastNum, f ) else: for idx, val in enumerate(castList): parseCast(idx, f) meta = open( outFolder + "/metadata.json", "w") meta.write( json.dumps( metaList ) ) meta.close() ''' f.close() logfile.close() fDesc = {} fDesc['fileName'] = cfgFileName fDesc['fileEntries'] = fileEntries fDesc['castLibraries'] = castLibraries fEntryOut = open( "cst_" + cfgFileName + ".json", "w") fEntryOut.write( json.dumps( fDesc ) ) fEntryOut.close() if __name__ == "__main__": main(sys.argv[1:])