diff options
| -rw-r--r-- | tools/palex.py | 179 | 
1 files changed, 129 insertions, 50 deletions
diff --git a/tools/palex.py b/tools/palex.py index 6cff14dbda..8a2408105e 100644 --- a/tools/palex.py +++ b/tools/palex.py @@ -2,24 +2,49 @@  # Amiga AGI game palette extractor.  # Extracts palette from an Amiga AGI game's executable file.  # Initial version written during summer of 2007 by Buddha^. -# Unoptimized. -import struct, sys, os.path +# Somewhat optimized. Adding the preliminary palette test helped speed a lot. +# FIXME: Doesn't report anything about not found files when some files are found. +#        An example: palex.py SQ2 PQ1 (When file SQ2 exists but PQ1 doesn't) +import struct, sys, os.path, glob  # Constants -maxComponentValue     = 0x0F +maxComponentValue     = 0xF  colorsPerPalette      = 16  componentsPerColor    = 3  bytesPerComponent     = 2 -bytesPerColor         = componentsPerColor    * bytesPerComponent -paletteComponentCount = colorsPerPalette      * componentsPerColor -paletteByteCount      = paletteComponentCount * bytesPerComponent -black                 = tuple([0 for x in range(componentsPerColor)]) -white                 = tuple([maxComponentValue for x in range(componentsPerColor)]) +bytesPerColor         = componentsPerColor   * bytesPerComponent +componentsPerPalette  = colorsPerPalette     * componentsPerColor +bytesPerPalette       = componentsPerPalette * bytesPerComponent +encodedBlack          = '\x00' * bytesPerColor +encodedWhite          = (('\x00' * (bytesPerComponent - 1)) + ("%c" % maxComponentValue)) * componentsPerColor +decodedBlack          = tuple([0 for x in range(componentsPerColor)]) +decodedWhite          = tuple([maxComponentValue for x in range(componentsPerColor)]) +blackColorNum         = 0 +whiteColorNum         = colorsPerPalette - 1 +encodedBlackStart     = blackColorNum * bytesPerColor +encodedBlackEnd       = encodedBlackStart + bytesPerColor +encodedWhiteStart     = whiteColorNum * bytesPerColor +encodedWhiteEnd       = encodedWhiteStart + bytesPerColor  componentPrintFormat  = "0x%1X" +arraynamePrefix       = "amigaPalette" -def is12BitColor(color): -	"""Is color 12-bit (i.e. 4 bits per color component)?""" -	return color == filter(lambda component: 0 <= component <= maxComponentValue, color) +def isColor12Bit(color): +	"""Is the color 12-bit (i.e. 4 bits per color component)?""" +	for component in color: +		if not (0 <= component <= maxComponentValue): +			return False +	return True + +def printCommentLineList(lines): +	"""Prints list of lines inside a comment""" +	if len(lines) > 0: +		if len(lines) == 1: +			print "// " + lines[0] +		else: +			print "/**" +			for line in lines: +				print " * " + line +			print " */"  def printColor(color, tabulate = True, printLastComma = True, newLine = True):  	"""Prints color with optional start tabulation, comma in the end and a newline""" @@ -36,10 +61,15 @@ def printColor(color, tabulate = True, printLastComma = True, newLine = True):  	else:  		print result, -def printPalette(palette, arrayName): +def printPalette(palette, filename, arrayname):  	"""Prints out palette as a C-style array""" -	print "/** A 16-color, 12-bit RGB palette from an Amiga AGI game. */" -	print "static const unsigned char " + arrayName + "[] = {" +	# Print comments about the palette +	comments = ["A 16-color, 12-bit RGB palette from an Amiga AGI game."] +	comments.append("Extracted from file " + os.path.basename(filename)) +	printCommentLineList(comments) +	 +	# Print the palette as a C-style array +	print "static const unsigned char " + arrayname + "[] = {"  	for color in palette[:-1]:  		printColor(color)  	printColor(palette[-1], printLastComma = False) @@ -51,13 +81,13 @@ def isAmigaPalette(palette):  	if len(palette) != colorsPerPalette:  		return False -	# First palette color must be black, last color white -	if palette[0] != black or palette[-1] != white: +	# First palette color must be black and last palette color must be black +	if palette[whiteColorNum] != decodedWhite or palette[blackColorNum] != decodedBlack:  		return False  	# All colors must be 12-bit (i.e. 4 bits per color component)  	for color in palette: -		if not is12BitColor(color): +		if not isColor12Bit(color):  			return False  	# All colors must be unique @@ -66,41 +96,90 @@ def isAmigaPalette(palette):  	return True -# The main program starts here -if len(sys.argv) <= 2 or sys.argv[1] == "-h" or sys.argv[1] == "--help": -	quit("Usage: " + os.path.basename(sys.argv[0]) + " FILE ARRAYNAME\n" \ -		"  Searches FILE for an Amiga AGI game palette\n" \ -		"  and prints out any found candidates as C-array style arrays with the name ARRAYNAME."); - -# Open file and read it into memory -file = open(sys.argv[1], 'rb') -data = file.read() -foundPalettes = [] -palette = [black for x in range(colorsPerPalette)] -# Search through the whole file -for searchPosition in range(len(data) - paletteByteCount + 1): -	# Parse possible palette from byte data -	for colorNum in range(colorsPerPalette): -		paletteStart = searchPosition + colorNum * bytesPerColor -		paletteEnd   = paletteStart + bytesPerColor -		# Parse color components as unsigned 16-bit big endian integers -		color = struct.unpack('>' + 'H' * componentsPerColor, data[paletteStart:paletteEnd]); -		palette[colorNum] = color; +def preliminaryPaletteTest(data, pos): +	"""Preliminary test for a palette (For speed's sake).""" +	# Test that palette's last color is white +	if data[pos + encodedWhiteStart : pos + encodedWhiteEnd] != encodedWhite: +		return False +	# Test that palette's first color is black +	if data[pos + encodedBlackStart : pos + encodedBlackEnd] != encodedBlack: +		return False +	return True -	# Save good candidates to a list -	if isAmigaPalette(palette): -		foundPalettes.append(tuple(palette)); +def searchForAmigaPalettes(filename): +	"""Search file for Amiga AGI game palettes and return any found unique palettes""" +	try: +		file = None +		foundPalettes = [] +		# Open file and read it into memory +		file = open(filename, 'rb') +		data = file.read() +		palette = [decodedBlack for x in range(colorsPerPalette)] +		# Search through the whole file +		for searchPosition in range(len(data) - bytesPerPalette + 1): +			if preliminaryPaletteTest(data, searchPosition): +				# Parse possible palette from byte data +				for colorNum in range(colorsPerPalette): +					colorStart = searchPosition + colorNum * bytesPerColor +					colorEnd   = colorStart + bytesPerColor +					# Parse color components as unsigned 16-bit big endian integers +					color = struct.unpack('>' + 'H' * componentsPerColor, data[colorStart:colorEnd]) +					palette[colorNum] = color +				# Save good candidates to a list +				if isAmigaPalette(palette): +					foundPalettes.append(tuple(palette)) +		# Close source file and return unique found palettes	 +		file.close() +		return set(foundPalettes) +	except IOError: +		if file != None: +			file.close() +		return None -# Close file -file.close() +# The main program starts here +if len(sys.argv) < 2 or sys.argv[1] == "-h" or sys.argv[1] == "--help": +	quit("Usage: " + os.path.basename(sys.argv[0]) + " FILE [[FILE] ... [FILE]]\n" \ +		"  Searches all FILE parameters for Amiga AGI game palettes\n" \ +		"  and prints out any found candidates as C-style arrays\n" \ +		"  with sequentially numbered names (" + arraynamePrefix + "1, " + arraynamePrefix + "2 etc).\n" \ +		"  Supports wildcards.") -# Remove duplicates from found palettes list -uniqueFoundPalettes = set(foundPalettes) +# Get the list of filenames (Works with wildcards too) +filenameList = [] +for parameter in sys.argv[1:]: +	filenameList.extend(glob.glob(parameter)) -# Print out the found unique palettes -if len(uniqueFoundPalettes) > 0: -	for palette in uniqueFoundPalettes: -		printPalette(palette, sys.argv[2]) +# Go through all the files and search for palettes +totalPalettesCount = 0 +if len(filenameList) > 0: +	negativeFiles = [] +	errorFiles = [] +	for filename in filenameList: +		foundPalettes = searchForAmigaPalettes(filename) +		if foundPalettes == None: +			errorFiles.append(filename) +		elif len(foundPalettes) == 0: +			negativeFiles.append(filename) +		else: +			# Print out the found palettes +			for palette in foundPalettes: +				# Print palettes with sequentially numbered array names +				totalPalettesCount = totalPalettesCount + 1 +				printPalette(palette, filename, arraynamePrefix + str(totalPalettesCount)) +				print "" # Print an extra newline to separate things +	# Print comment about files we couldn't find any palettes in +	if len(negativeFiles) > 0: +		comments = [] +		comments.append("Couldn't find any palettes in the following files:") +		comments.extend(negativeFiles) +		printCommentLineList(comments) +		print "" # Print an extra newline to separate things +	# Print comment about errors handling files +	if len(errorFiles) > 0: +		comments = [] +		comments.append("Error handling the following files:") +		comments.extend(errorFiles) +		printCommentLineList(comments) +		print "" # Print an extra newline to separate things  else: -	print "Didn't find any Amiga AGI game palettes in file " + sys.argv[1] -	print "Heuristic isn't perfect though, so caveat emptor" +	printCommentLineList(["No files found"])  | 
