Skip to content

Instantly share code, notes, and snippets.

@MrBrax
Created November 3, 2023 20:27
Show Gist options
  • Save MrBrax/75a23fe8d3b404df5a8041364d5774d8 to your computer and use it in GitHub Desktop.
Save MrBrax/75a23fe8d3b404df5a8041364d5774d8 to your computer and use it in GitHub Desktop.
#!/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:])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment