#!/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:])