Skip to content

Instantly share code, notes, and snippets.

@amaendle
Forked from MrBrax/cst_python.py
Last active April 25, 2020 17:21
Show Gist options
  • Save amaendle/49f9c27dc8d175fddc2483e719f721cf to your computer and use it in GitHub Desktop.
Save amaendle/49f9c27dc8d175fddc2483e719f721cf to your computer and use it in GitHub Desktop.
extracts assets from a shockwave director file
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from subprocess import call
import sys, os, getopt
import struct
import wave
import ntpath
import json
import re
from PIL import Image, ImageDraw, ImagePalette
def convertBITD( w, h, f, start, size, bpp ):
bitmapValues = [[0 for x in range( w )] for y in range( h )]
draw_x = 0
draw_y = 0
f.seek(start)
while f.tell() <= start + size:
rLen = struct.unpack('>B', f.read(1) )[0]
if 0x101 - rLen > 0x7F:
#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 = int(struct.unpack('>B', f.read(1) )[0])
#doLog(" lin - put pixel (" + str( draw_x ) + "," + str(draw_y) + "=" + str( 0xFF - val ) + ")")
bitmapValues[ draw_y ][ draw_x ] = val
draw_x += 1
if draw_x >= w:
#doLog(" lin - line change (" + str( draw_x-1 ) + "/" + str(draw_y+1) + "@" + str( f.tell() - start ) + ")")
if w % 2 and bpp < 16:
draw_x = 0
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 = int(struct.unpack('>B', f.read(1) )[0])
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 ) + ")")
val2 = "171"
val2 = int(val2)
bitmapValues[ draw_y ][ draw_x ] = val
draw_x += 1
if draw_x >= w:
# doLog(" rle - line change (" + str( draw_x-1 ) + "/" + str(draw_y+1) + "@" + str( f.tell() - start ) + ")")
if w % 2 and bpp < 16:
draw_x = 0
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
fileNum = 1
entries = []
castList = []
metaList = {}
cfgInputCST = sys.argv[1]
cfgFileName = ntpath.basename(cfgInputCST)
# cfgCastNum = int(sys.argv[2]) - 1
logfile = open("cst_" + cfgFileName + ".log", "w")
def doLog(t):
global logfile
logfile.write(t + "\n")
print(t)
f = open(cfgInputCST, "rb")
# f = open(sys.argv[1], "rb")
outFolder = "cst_out/" + cfgFileName
if not os.path.exists(outFolder):
print("MAKE FOLDER")
os.makedirs(outFolder)
# pos 0-4 (XFIR)
RIFX_SIGN = f.read(4).decode("utf-8")
doLog( "RIFX_SIGN: " + str( RIFX_SIGN ) )
# pos 4-8 (Length)
SIZE = struct.unpack('i', f.read(4) )[0]
doLog( "SIZE: " + str( SIZE ) + " (" + str( round( SIZE / 1024 ) ) + "kb)" )
# pos 8-12
SIGN = f.read(4)
doLog( "SIGN: " + str( SIGN ) )
f.seek(60) # pos 60
rawFileNum = struct.unpack('i', f.read(4) )[0]
doLog( "File num: " + str( rawFileNum ) )
f.read(12) # pos 76, file block begin
doLog("\n\n--- READ POINTERS ---")
for i in range(0, rawFileNum):
pointerOffset = f.tell()
entryType = f.read(4).decode("utf-8")[::-1] # 4
entryLength = struct.unpack('i', f.read(4) )[0] # 8
entryOffset = struct.unpack('i', f.read(4) )[0] # 12
#if entryType != "free":
# doLog("[POINT " + str(i) + " @ " + str(pointerOffset) + "][" + (entryType) + "] Length: " + str( entryLength ) + ", Offset: " + str( entryOffset ) )
#else:
# doLog("[POINT " + str(i) + " @ " + str(pointerOffset) + "][----]")
entries.append({
'num': i,
'name': '',
'type': entryType,
'length': entryLength,
'offset': entryOffset,
'poffset': pointerOffset,
'files': [],
'friendlyType': ''
})
f.read(8)
doLog("\n\n--- READ FILES ---")
for e in entries:
#if e['type'] == "free":
# continue
f.seek( e['offset'], 0 )
fEntryHeaderRaw = f.read(4)
fEntryLengthRaw = f.read(4)
fEntryHeader = fEntryHeaderRaw.decode("utf-8")[::-1]
fEntryLength = struct.unpack('i', fEntryLengthRaw )[0]
e['headerRaw'] = fEntryHeaderRaw
e['lengthRaw'] = fEntryLengthRaw
doLog("[FILE " + str(e['num']) + " @ " + str(e['offset']) + "][" + e['type'] + "->" + fEntryHeader + "]")
if e['type'] == 'KEY*':
doLog("--- KEY @ " + str( e['offset'] ) + " ---")
fUnknownNum1 = struct.unpack('i', f.read(4) )[0]
fUnknownNum2 = struct.unpack('i', f.read(4) )[0]
fEntryNum = struct.unpack('i', f.read(4) )[0]
# doLog(" fUnknownNum1: " + str( fUnknownNum1 ) + ", fUnknownNum2: " + str( fUnknownNum2 ) + ", fEntryNum: " + str( fEntryNum ) )
for i in range(0, fEntryNum):
castFileSlot = struct.unpack('i', f.read(4) )[0]
castSlot = struct.unpack('i', f.read(4) )[0]
castType = f.read(4).decode("utf-8")
# doLog("[KEY " + str(i) + "] Cast file slot offset: " + str( castFileSlot ) + ", Cast slot offset: " + str( castSlot ) + ", Type: " + str( castType ) )
# entries[ castSlot ]['slotNum'] = castSlot
# entries[ castSlot ]['fileSlot'] = castFileSlot
# entries[ castSlot ]['fileObj'] = entries[ castFileSlot ]
entries[ castSlot ]['files'].append( entries[ castFileSlot ] )
# doLog(" KeyCastOffset: " + str( castOffset ) + ", KeyCastId: " + str( castId ) + ", KeyCastType: " + str( castType ) )
if e['type'] == 'STXT':
f.read(4) # content
textLength = struct.unpack('>i', f.read(4) )[0]
textPadding = struct.unpack('>i', f.read(4) )[0]
# fPad = struct.unpack('i', f.read(1) )[0]
textContent = f.read( textLength )
e['content'] = textContent
if e['type'] == 'BITD':
e['content'] = f.read(fEntryLength)
if e['type'] == 'sndS':
e['content'] = f.read(fEntryLength)
if e['type'] == 'CASt':
# 3 - field
# 6 - audio
castType = struct.unpack('>i', f.read(4) )[0]
e['castType'] = castType
castDataLen = struct.unpack('>i', f.read(4) )[0]
e['dataLength'] = castDataLen
castDataEnd = struct.unpack('>i', f.read(4) )[0]
e['dataEnd'] = castDataEnd
if castType == 6:
doLog(" [INFO] Type: " + str(castType) + ", tellOffset: " + str(f.tell()))
e['friendlyType'] = 'sound'
castSub1 = struct.unpack('>i', f.read(4) )[0]
f.read(8)
castSub2 = struct.unpack('>i', f.read(4) )[0]
f.read( castSub1 - castSub2 )
castFields = struct.unpack('>h', f.read(2) )[0]
doLog(" [INFO] Fields: " + str(castFields) )
for i in range(0, castFields):
f.read(4)
castInfoLen = struct.unpack('>i', f.read(4) )[0]
doLog(" [INFO] castInfoLen: " + str(castInfoLen) + ", castSub2: " + str(castSub2) + ", tellOffset: " + str(f.tell()))
try:
castInfoName = f.read( struct.unpack('b', f.read(1) )[0] ).decode('utf-8')
except:
castInfoName = ""
f.read(1)
try:
castInfoCodec = f.read( struct.unpack('b', f.read(1) )[0] ).decode('utf-8')
except:
castInfoCodec = ""
e['name'] = castInfoName
e['codec'] = castInfoCodec
doLog(" [INFO] castInfoLen: " + str(castInfoLen) + ", castInfoName: " + castInfoName + ", tellOffset: " + str(f.tell()))
# doLog(" [INFO] Type: " + str(castType) + ", Length: " + str(castInfoLen) )
if castType == 1:
f.read(34) #f.read(46)
n2read = struct.unpack('b', f.read(1) )[0] #n2read = struct.unpack('>i', f.read(4) )[0] #n2read = struct.unpack('b', f.read(1) )[0]
castInfoName = f.read( n2read ).decode('ansi')
e['name'] = castInfoName
doLog(" [INFO] Type: " + str(castType) + ", Length: " + str(n2read) + ", name: " + e['name'])
e['friendlyType'] = 'bitmap'
f.read(3)
e['paddingH'] = struct.unpack('>h', f.read(2) )[0]
e['paddingW'] = struct.unpack('>h', f.read(2) )[0]
e['height'] = struct.unpack('>h', f.read(2) )[0] - e['paddingH']
e['width'] = struct.unpack('>h', f.read(2) )[0] - e['paddingW']
e['constant'] = f.read(4) # struct.unpack('>i', f.read(4) )[0]
f.read(4)
e['regy'] = struct.unpack('>h', f.read(2) )[0] - e['paddingH']
e['regx'] = struct.unpack('>h', f.read(2) )[0] - e['paddingW']
e['bitdepth'] = struct.unpack('>h', f.read(2) )[0]
e['palette'] = struct.unpack('>h', f.read(2) )[0]
doLog(" [INFO] constant: " + str(e['constant']) + ", palette: " + str(e['palette']) + ", name: " + e['name'])
if castType == 3:
doLog(" [INFO] Type: " + str(castType))
e['friendlyType'] = 'field'
f.read(70)
castInfoName = f.read( struct.unpack('b', f.read(1) )[0] ).decode('ansi')
e['name'] = castInfoName
doLog(" [INFO] Type: " + str(castType) + ", InfoNamegth: " + castInfoName)
if e['type'] == "CAS*":
doLog(" [INFO] Type: " + str(castType))
for i in range(0, round(fEntryLength/4) ):
castSlot = struct.unpack('>i', f.read(4) )[0]
entries[ castSlot ]['memberNum'] = i + 1
castList.append( entries[ castSlot ] )
#metaList[ castSlot ] = {
#}
'''
for i in range(0, 64):
e = castList[i]
if 'fileObj' in e:
doLog( str(i) + ": " + e['fileObj']['type'] );
'''
#e = castList[ cfgCastNum ]
tmp = Image.open( "pal.bmp" )
mullePalette = tmp.palette
tmp.close()
for e in castList:
# doLog("[CAST " + str(e['num']) + "]")
#if e['type'] != 'CASt':
# continue
metaList[ 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 'width' in e:
doLog(" [SIZE] " + str( e["width"] ) + "x" + str( e["height"] ) )
metaList[ e['memberNum'] ]['imageWidth'] = e['width']
metaList[ e['memberNum'] ]['imageHeight'] = e['height']
if 'paddingW' in e:
doLog(" [PAD] " + str( e["paddingW"] ) + "x" + str( e["paddingH"] ) )
if 'regx' in e:
doLog(" [REG] " + str( e["regx"] ) + "," + str( e["regy"] ) )
metaList[ e['memberNum'] ]['imageRegX'] = e['regx']
metaList[ e['memberNum'] ]['imageRegY'] = e['regy']
if 'bitdepth' in e:
doLog(" [BITDEPTH] " + str( e["bitdepth"] ) )
if 'palette' in e:
doLog(" [PALETTE] " + str( e["palette"] ) )
#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":
snd = wave.open( outFolder + "/" + str(e['memberNum']) + ".wav", "w")
snd.setnchannels(1)
snd.setsampwidth(1)
snd.setframerate(22050.0)
snd.writeframesraw( l['content'] )
snd.close()
if l['type'] == "BITD":
#if e["width"] <= 0 or e["height"] <= 0:
# continue
#if e["height"] >= 0:
# continue
if e['bitdepth'] == 0:
doLog(" skip " )
continue
if e['bitdepth'] > 32:
doLog(" skip " )
continue
#if e['bitdepth'] == 32:
# continue
#if e['paddingH']+e['paddingW']==0:
# continue
if e["width"] < 0:
e["width"] =-e["width"]
if e["height"] <= 0:
e["height"]=-e["height"]
'''
bitm = open( outFolder + "/" + str(e['memberNum']) + ".bitd", "wb")
bitm.write( l['content'] )
bitm.close()
'''
if e['palette']==-1:
if e['bitdepth']==32:
im = Image.new("RGBA", (e["width"], int(e['height'])) )
dr = ImageDraw.Draw(im)
elif e['bitdepth']==16:
file_name = e['name'] + '(' + str(e['memberNum']) +')-' + str(e['width']) + "x" + str(e['height']) + "x" + str(e['bitdepth'])
file_name = file_name.replace("/", "_")
file_name = re.sub(r"[^A-Za-z0-9\-_\.]", "_", file_name)
im = open(outFolder + "/" + file_name + ".bmp", 'wb') #im = Image.new("RGB", (e["width"], int(e['height'])) )
im.write('BM'.encode('ascii'))
hsize = 124
values = (e["width"]*e['height']*2+hsize+14, # The size of the BMP file in bytes
0, # Reserved
0, # Reserved
(hsize+14) # Data offset
)
s = struct.Struct('<ihhi')
packed_data = s.pack(*values)
im.write(packed_data)
# Write BITMAPINFOHEADER
values = (hsize, # the size of this header (hsize bytes)
e["width"], # the bitmap width in pixels (signed integer)
e['height'], # the bitmap height in pixels (signed integer)
1, # the number of color planes (must be 1)
16, # the number of bits per pixel, which is the color depth of the image. Typical values are 1, 4, 8, 16, 24 and 32.
3, # the compression method being used (BI_BITFIELDS)
0,#e["width"]*e['height']*e['bitdepth']/8, #0, # the image size. This is the size of the raw bitmap data; a dummy 0 can be given for BI_RGB bitmaps.
0, # the horizontal resolution of the image. (pixel per meter, signed integer)
0, # the vertical resolution of the image. (pixel per meter, signed integer)
0, # the number of colors in the color palette, or 0 to default to 2n
0, # the number of important colors used, or 0 when every color is important; generally ignored
0x00007C00, # Red channel bitmask
0x000003E0, # Green channel bitmask
0x0000001F, # Blue channel bitmask
0x00000000, # Alpha channel bitmask
0x73524742, # "BGRs"
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, # CIEXYZTRIPLE Color Space endpoints
0, # Red Gamma
0, # Green Gamma
0, # Blue Gamma
)
s = struct.Struct('<iiihhIIIIIIIIIIIIIIIIIIIIIIIIIII')
packed_data = s.pack(*values)
im.write(packed_data)
else:
im = Image.new("P", (e["width"], e["height"]) )
im.palette=e['palette']
dr = ImageDraw.Draw(im)
if e['bitdepth']==32:
bitmapValues = convertBITD( e['width'], e['height']*4, f, l['offset'] + 8, l['length'], e['bitdepth'] )
elif e['bitdepth']==16:
bitmapValues = convertBITD( e['width'], e['height']*2, f, l['offset'] + 8, l['length'], e['bitdepth'] )
else:
bitmapValues = convertBITD( e['width'], e['height'], f, l['offset'] + 8, l['length'], e['bitdepth'] )
draw_x = 0
draw_y = 0
doit = True
# doLog( str( colours ) )
x = 0
y = 0
# doLog( str(len(colours[0])) + ", " + str(len(colours[1])) + ", " + str(len(colours[2])) )
if e['bitdepth']==32:
for y in range( 0, int(e['height']) ):
for x in range( 0, e['width'] ):
dr.point( (x, y), (bitmapValues[4*y+1][x],bitmapValues[4*y+2][x],bitmapValues[4*y+3][x],bitmapValues[4*y][x]) )
elif e['bitdepth']==16:
# Order lower and upper bytes
w1 = e['width'] + (e['width'] % 2)
w2 = w1*2
w0 = 0
castDatamix = [0 for x in range(w1*2*e['height'])]
for y in range( 0, int(e['height']) ):
yw1 = y*w1
yw2 = y*w1*2
for x in range( 0, w1 ):
if e['width'] % 2 and w1-1<=x:
castDatamix[yw2 + x*2 + 0] = 0 # Upper
castDatamix[yw2 + x*2 + 1] = 0 # Lower
else:
castDatamix[yw2 + x*2 + 0] = bitmapValues[2*e['height']-1-(2*y)][x] # Upper
castDatamix[yw2 + x*2 + 1] = bitmapValues[2*e['height']-1-(2*y+1)][x] # Lower
#castDatamix = reversed(castDatamix)
else:
for y in range( 0, e['height'] ):
for x in range( 0, int(e['width']) ):
dr.point( (x, y), (bitmapValues[y][x]) )
if e['bitdepth']==16:
# Write the pixel information
im.write(struct.pack("B"*(w1*2*e['height']), *castDatamix))
im.close()
else:
file_name = e['name'] + '(' + str(e['memberNum']) +')-' + str(e['width']) + "x" + str(e['height']) + "x" + str(e['bitdepth'])
file_name = file_name.replace("/", "_")
file_name = re.sub(r"[^A-Za-z0-9\-_\.]", "_", file_name)
im.save( outFolder + "/" + file_name + ".bmp", "BMP")
if e['bitdepth']!=16:
del dr
if l['type'] == "STXT":
txt = open( outFolder + "/" + str(e['memberNum']) + ".txt", "wb")
txt.write( l['content'] )
txt.close()
'''
cst = open( outFolder + "/" + str(e['memberNum']) + ".cast", "wb")
f.seek( e['offset'], 0 )
cst.write( f.read( e['length'] + 8 ) )
cst.close()
'''
#if e["castType"] == 4:
# doLog("PALETTE!!")
# break
doLog("")
logfile.close()
f.close()
meta = open( outFolder + "/metadata.json", "w")
meta.write( json.dumps( metaList ) )
meta.close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment