2 # Amiga AGI game palette extractor.
3 # Extracts palette from an Amiga AGI game's executable file.
4 # Initial version written during summer of 2007 by Buddha^.
5 # Somewhat optimized. Adding the preliminary palette test helped speed a lot.
6 # FIXME: Doesn't report anything about not found files when some files are found.
7 # An example: palex.py SQ2 PQ1 (When file SQ2 exists but PQ1 doesn't)
8 import struct
, sys
, os
.path
, glob
11 maxComponentValue
= 0xF
13 componentsPerColor
= 3
15 bytesPerColor
= componentsPerColor
* bytesPerComponent
16 componentsPerPalette
= colorsPerPalette
* componentsPerColor
17 bytesPerPalette
= componentsPerPalette
* bytesPerComponent
18 encodedBlack
= '\x00' * bytesPerColor
19 encodedWhite
= (('\x00' * (bytesPerComponent
- 1)) + ("%c" % maxComponentValue
)) * componentsPerColor
20 decodedBlack
= tuple([0 for x
in range(componentsPerColor
)])
21 decodedWhite
= tuple([maxComponentValue
for x
in range(componentsPerColor
)])
23 whiteColorNum
= colorsPerPalette
- 1
24 encodedBlackStart
= blackColorNum
* bytesPerColor
25 encodedBlackEnd
= encodedBlackStart
+ bytesPerColor
26 encodedWhiteStart
= whiteColorNum
* bytesPerColor
27 encodedWhiteEnd
= encodedWhiteStart
+ bytesPerColor
28 componentPrintFormat
= "0x%1X"
29 arraynamePrefix
= "amigaPalette"
31 def isColor12Bit(color
):
32 """Is the color 12-bit (i.e. 4 bits per color component)?"""
33 for component
in color
:
34 if not (0 <= component
<= maxComponentValue
):
38 def printCommentLineList(lines
):
39 """Prints list of lines inside a comment"""
42 print "// " + lines
[0]
49 def printColor(color
, tabulate
= True, printLastComma
= True, newLine
= True):
50 """Prints color with optional start tabulation, comma in the end and a newline"""
54 for component
in color
[:-1]:
55 result
+= ((componentPrintFormat
+ ", ") % component
)
56 result
+= (componentPrintFormat
% color
[-1])
64 def printPalette(palette
, filename
, arrayname
):
65 """Prints out palette as a C-style array"""
66 # Print comments about the palette
67 comments
= ["A 16-color, 12-bit RGB palette from an Amiga AGI game."]
68 comments
.append("Extracted from file " + os
.path
.basename(filename
))
69 printCommentLineList(comments
)
71 # Print the palette as a C-style array
72 print "static const unsigned char " + arrayname
+ "[] = {"
73 for color
in palette
[:-1]:
75 printColor(palette
[-1], printLastComma
= False)
78 def isAmigaPalette(palette
):
79 """Test if the given palette is an Amiga-style palette"""
80 # Palette must be of correct size
81 if len(palette
) != colorsPerPalette
:
84 # First palette color must be black and last palette color must be black
85 if palette
[whiteColorNum
] != decodedWhite
or palette
[blackColorNum
] != decodedBlack
:
88 # All colors must be 12-bit (i.e. 4 bits per color component)
90 if not isColor12Bit(color
):
93 # All colors must be unique
94 if len(set(palette
)) != colorsPerPalette
:
99 def preliminaryPaletteTest(data
, pos
):
100 """Preliminary test for a palette (For speed's sake)."""
101 # Test that palette's last color is white
102 if data
[pos
+ encodedWhiteStart
: pos
+ encodedWhiteEnd
] != encodedWhite
:
104 # Test that palette's first color is black
105 if data
[pos
+ encodedBlackStart
: pos
+ encodedBlackEnd
] != encodedBlack
:
109 def searchForAmigaPalettes(filename
):
110 """Search file for Amiga AGI game palettes and return any found unique palettes"""
114 # Open file and read it into memory
115 file = open(filename
, 'rb')
117 palette
= [decodedBlack
for x
in range(colorsPerPalette
)]
118 # Search through the whole file
119 for searchPosition
in range(len(data
) - bytesPerPalette
+ 1):
120 if preliminaryPaletteTest(data
, searchPosition
):
121 # Parse possible palette from byte data
122 for colorNum
in range(colorsPerPalette
):
123 colorStart
= searchPosition
+ colorNum
* bytesPerColor
124 colorEnd
= colorStart
+ bytesPerColor
125 # Parse color components as unsigned 16-bit big endian integers
126 color
= struct
.unpack('>' + 'H' * componentsPerColor
, data
[colorStart
:colorEnd
])
127 palette
[colorNum
] = color
128 # Save good candidates to a list
129 if isAmigaPalette(palette
):
130 foundPalettes
.append(tuple(palette
))
131 # Close source file and return unique found palettes
133 return set(foundPalettes
)
139 # The main program starts here
140 if len(sys
.argv
) < 2 or sys
.argv
[1] == "-h" or sys
.argv
[1] == "--help":
141 quit("Usage: " + os
.path
.basename(sys
.argv
[0]) + " FILE [[FILE] ... [FILE]]\n" \
142 " Searches all FILE parameters for Amiga AGI game palettes\n" \
143 " and prints out any found candidates as C-style arrays\n" \
144 " with sequentially numbered names (" + arraynamePrefix
+ "1, " + arraynamePrefix
+ "2 etc).\n" \
145 " Supports wildcards.")
147 # Get the list of filenames (Works with wildcards too)
149 for parameter
in sys
.argv
[1:]:
150 filenameList
.extend(glob
.glob(parameter
))
152 # Go through all the files and search for palettes
153 totalPalettesCount
= 0
154 if len(filenameList
) > 0:
157 for filename
in filenameList
:
158 foundPalettes
= searchForAmigaPalettes(filename
)
159 if foundPalettes
== None:
160 errorFiles
.append(filename
)
161 elif len(foundPalettes
) == 0:
162 negativeFiles
.append(filename
)
164 # Print out the found palettes
165 for palette
in foundPalettes
:
166 # Print palettes with sequentially numbered array names
167 totalPalettesCount
= totalPalettesCount
+ 1
168 printPalette(palette
, filename
, arraynamePrefix
+ str(totalPalettesCount
))
169 print "" # Print an extra newline to separate things
170 # Print comment about files we couldn't find any palettes in
171 if len(negativeFiles
) > 0:
173 comments
.append("Couldn't find any palettes in the following files:")
174 comments
.extend(negativeFiles
)
175 printCommentLineList(comments
)
176 print "" # Print an extra newline to separate things
177 # Print comment about errors handling files
178 if len(errorFiles
) > 0:
180 comments
.append("Error handling the following files:")
181 comments
.extend(errorFiles
)
182 printCommentLineList(comments
)
183 print "" # Print an extra newline to separate things
185 printCommentLineList(["No files found"])