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