-
-
Save Quackster/6959438c23fbbfea89d28a38c84423d9 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 -*- | |
| # | |
| from subprocess import call | |
| import sys, os, getopt | |
| import struct | |
| import wave | |
| import ntpath | |
| import json | |
| from PIL import Image, ImageDraw, ImagePalette | |
| def convertBITD( w, h, f, start, size ): | |
| 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 = 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 (" + str( draw_x-1 ) + "/" + str(draw_y+1) + "@" + 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 (" + str( draw_x-1 ) + "/" + str(draw_y+1) + "@" + 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 | |
| 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: | |
| 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] | |
| castInfoName = f.read( struct.unpack('b', f.read(1) )[0] ).decode('utf-8') | |
| f.read(1) | |
| castInfoCodec = f.read( struct.unpack('b', f.read(1) )[0] ).decode('utf-8') | |
| e['name'] = castInfoName | |
| e['codec'] = castInfoCodec | |
| # doLog(" [INFO] Type: " + str(castType) + ", Length: " + str(castInfoLen) ) | |
| if castType == 1: | |
| f.read(46) | |
| castInfoName = f.read( struct.unpack('b', f.read(1) )[0] ).decode('ansi') | |
| e['name'] = castInfoName | |
| 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] | |
| if castType == 3: | |
| e['friendlyType'] = 'field' | |
| f.read(70) | |
| castInfoName = f.read( struct.unpack('b', f.read(1) )[0] ).decode('ansi') | |
| e['name'] = castInfoName | |
| if e['type'] == "CAS*": | |
| 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 | |
| ''' | |
| bitm = open( outFolder + "/" + str(e['memberNum']) + ".bitd", "wb") | |
| bitm.write( l['content'] ) | |
| bitm.close() | |
| ''' | |
| im = Image.new("P", (e["width"], e["height"]) ) | |
| dr = ImageDraw.Draw(im) | |
| tmp = Image.open( "pal.bmp" ) | |
| im.palette = tmp.palette | |
| tmp.close() | |
| bitmapValues = convertBITD( e['width'], e['height'], f, l['offset'] + 8, l['length'] ) | |
| 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])) ) | |
| for y in range( 0, e['height'] ): | |
| for x in range( 0, e['width'] ): | |
| dr.point( (x, y), bitmapValues[y][x] ) | |
| im.save( outFolder + "/" + str(e['memberNum']) + ".bmp", "BMP") | |
| call("magick convert " + outFolder + "/" + str(e['memberNum']) + ".bmp " + outFolder + "/" + str(e['memberNum']) + "O.png") | |
| call("magick convert " + outFolder + "/" + str(e['memberNum']) + ".bmp -transparent \"#FFFFFF\" " + outFolder + "/" + str(e['memberNum']) + "T.png") | |
| 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