-
-
Save amaendle/49f9c27dc8d175fddc2483e719f721cf to your computer and use it in GitHub Desktop.
extracts assets from a shockwave director file
This file contains 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 -*- | |
# | |
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