Created
November 3, 2023 20:27
-
-
Save MrBrax/75a23fe8d3b404df5a8041364d5774d8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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