aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tools/palex.py179
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"])