1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
|
#!/usr/bin/python
# Amiga AGI game palette extractor.
# Extracts palette from an Amiga AGI game's executable file.
# Initial version written during summer of 2007 by Buddha^.
# 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 = 0xF
colorsPerPalette = 16
componentsPerColor = 3
bytesPerComponent = 2
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 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"""
result = ""
if tabulate:
result += "\t"
for component in color[:-1]:
result += ((componentPrintFormat + ", ") % component)
result += (componentPrintFormat % color[-1])
if printLastComma:
result += ","
if newLine:
print result
else:
print result,
def printPalette(palette, filename, arrayname):
"""Prints out palette as a C-style array"""
# 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)
print("};")
def isAmigaPalette(palette):
"""Test if the given palette is an Amiga-style palette"""
# Palette must be of correct size
if len(palette) != colorsPerPalette:
return False
# 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 isColor12Bit(color):
return False
# All colors must be unique
if len(set(palette)) != colorsPerPalette:
return False
return True
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
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
# 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.")
# Get the list of filenames (Works with wildcards too)
filenameList = []
for parameter in sys.argv[1:]:
filenameList.extend(glob.glob(parameter))
# 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:
printCommentLineList(["No files found"])
|