5 " ScummVM - Graphic Adventure Engine
7 " ScummVM is the legal property of its developers, whose names
8 " are too numerous to list here. Please refer to the COPYRIGHT
9 " file distributed with this source distribution.
11 " This program is free software; you can redistribute it and/or
12 " modify it under the terms of the GNU General Public License
13 " as published by the Free Software Foundation; either version 2
14 " of the License, or (at your option) any later version.
16 " This program is distributed in the hope that it will be useful,
17 " but WITHOUT ANY WARRANTY; without even the implied warranty of
18 " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 " GNU General Public License for more details.
21 " You should have received a copy of the GNU General Public License
22 " along with this program; if not, write to the Free Software
23 " Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
29 from __future__
import with_statement
31 import xml
.dom
.minidom
as DOM
34 FILTER
=''.join([(len(repr(chr(x
)))==3) and chr(x
) or '.' for x
in range(256)])
36 # adapted from Activestate Snippet Cookbook
37 def printBinaryDump(src
, length
=16):
40 s
,src
= src
[:length
],src
[length
:]
41 hexa
= ' '.join(["%02X"%ord(x
) for x
in s
])
42 s
= s
.translate(FILTER
)
43 result
+= "%04X %-*s %s\n" % (N
, length
*3, hexa
, s
)
48 return str(map(lambda c
: hex(ord(c
)), data
))
49 # return " ".join(["%0.2X" % ord(c) for c in data])
51 class STXBinaryFile(object):
52 class InvalidRGBColor(Exception):
55 class InvalidResolution(Exception):
58 class InvalidFontIdentifier(Exception):
61 class InvalidBitmapName(Exception):
64 class InvalidDialogOverlay(Exception):
67 class DrawStepData(object):
68 def __init__(self
, isDefault
, packFormat
, function
):
69 self
.isDefault
= isDefault
70 self
.packFormat
= packFormat
73 BYTEORDER
= '>' # big-endian format by default, platform independent
75 TRUTH_VALUES
= {"" : 0, "true" : 1, "false" : 0, "True" : 1, "False" : 0}
78 "bitmaps" : 0x0100000A,
80 "cursor" : 0x0100000C,
81 "drawdata" : 0x0100000D,
83 "globals" : 0x0200000A,
84 "dialog" : 0x0200000B,
87 DIALOG_shading
= {"none" : 0x0, "dim" : 0x1, "luminance" : 0x2}
89 DS_triangleOrientations
= {"top" : 0x1, "bottom" : 0x2, "left" : 0x3, "right" : 0x4}
90 DS_fillModes
= {"none" : 0x0, "foreground" : 0x1, "background" : 0x2, "gradient" : 0x3}
91 DS_vectorAlign
= {"left" : 0x1, "right" : 0x2, "bottom" : 0x3, "top" : 0x4, "center" : 0x5}
94 "void" : 0x0, "circle" : 0x1, "square" : 0x2, "roundedsq" : 0x3, "bevelsq" : 0x4,
95 "line" : 0x5, "triangle" : 0x6, "fill" : 0x7, "tab" : 0x8, "bitmap" : 0x9, "cross" : 0xA
99 "text_default" : 0x0, "text_hover" : 0x1, "text_disabled" : 0x2,
100 "text_inverted" : 0x3, "text_button" : 0x4, "text_button_hover" : 0x5, "text_normal" : 0x6
103 DS_text_alignV
= {"bottom" : 0x0, "center" : 0x1, "top" : 0x2}
104 DS_text_alignH
= {"left" : 0x0, "center" : 0x1, "right" : 0x2}
107 "func", "fill", "stroke", "gradient_factor",
108 "width", "height", "xpos", "ypos",
109 "radius", "bevel", "shadow", "orientation", "file",
110 "fg_color", "bg_color", "gradient_start", "gradient_end", "bevel_color"
113 def __init__(self
, themeName
, autoLoad
= True, verbose
= False):
114 self
._themeName
= themeName
116 self
._verbose
= verbose
119 # attribute name isDefault pack parse function
120 "func" : self
.DrawStepData(False, "B", lambda f
: self
.DS_functions
[f
]),
121 "fill" : self
.DrawStepData(True, "B", lambda f
: self
.DS_fillModes
[f
]),
122 "stroke" : self
.DrawStepData(True, "B", int),
123 "gradient_factor" : self
.DrawStepData(True, "B", int),
124 "width" : self
.DrawStepData(False, "i", lambda w
: -1 if w
== 'height' else 0 if w
== 'auto' else int(w
)),
125 "height" : self
.DrawStepData(False, "i", lambda h
: -1 if h
== 'width' else 0 if h
== 'auto' else int(h
)),
126 "xpos" : self
.DrawStepData(False, "i", lambda pos
: self
.DS_vectorAlign
[pos
] if pos
in self
.DS_vectorAlign
else int(pos
)),
127 "ypos" : self
.DrawStepData(False, "i", lambda pos
: self
.DS_vectorAlign
[pos
] if pos
in self
.DS_vectorAlign
else int(pos
)),
128 "radius" : self
.DrawStepData(False, "i", lambda r
: 0xFF if r
== 'auto' else int(r
)),
129 "bevel" : self
.DrawStepData(True, "B", int),
130 "shadow" : self
.DrawStepData(True, "B", int),
131 "orientation" : self
.DrawStepData(False, "B", lambda o
: self
.DS_triangleOrientations
[o
]),
132 "file" : self
.DrawStepData(False, "B", self
.__getBitmap
),
133 "fg_color" : self
.DrawStepData(True, "4s", self
.__parseColor
),
134 "bg_color" : self
.DrawStepData(True, "4s", self
.__parseColor
),
135 "gradient_start" : self
.DrawStepData(True, "4s", self
.__parseColor
),
136 "gradient_end" : self
.DrawStepData(True, "4s", self
.__parseColor
),
137 "bevel_color" : self
.DrawStepData(True, "4s", self
.__parseColor
)
141 if not os
.path
.isdir(themeName
) or not os
.path
.isfile(os
.path
.join(themeName
, "THEMERC")):
144 for filename
in os
.listdir(themeName
):
145 filename
= os
.path
.join(themeName
, filename
)
146 if os
.path
.isfile(filename
) and filename
.endswith('.stx'):
147 self
._stxFiles
.append(filename
)
149 def debug(self
, text
):
150 if self
._verbose
: print (text
)
152 def debugBinary(self
, data
):
154 print ("BINARY OUTPUT (%d bytes): %s" % (len(data
), " ".join(["%0.2X" % ord(c
) for c
in data
])))
156 def addSTXFile(self
, filename
):
157 if not os
.path
.isfile(filename
):
160 self
._stxFiles
.append(filename
)
163 if not self
._stxFiles
:
164 self
.debug("No files have been loaded for parsing on the theme.")
167 for f
in self
._stxFiles
:
168 self
.debug("Parsing %s." % f
)
169 with
open(f
) as stxFile
:
170 self
.__parseFile
(stxFile
)
172 def __parseFile(self
, xmlFile
):
173 stxDom
= DOM
.parse(xmlFile
)
175 for layout
in stxDom
.getElementsByTagName("layout_info"):
176 self
.__parseLayout
(layout
)
178 for render
in stxDom
.getElementsByTagName("render_info"):
179 self
.__parseRender
(render
)
183 def __getBitmap(self
, bmp
):
188 if bmp
not in self
._bitmaps
:
189 raise self
.InvalidBitmapName
191 return self
._bitmaps
[bmp
]
193 def __parseDrawStep(self
, drawstepDom
, localDefaults
= {}):
197 if drawstepDom
.tagName
== "defaults":
198 isGlobal
= drawstepDom
.parentNode
.tagName
== "render_info"
200 for ds
in self
.DS_list
:
201 if self
.DS_data
[ds
].isDefault
and drawstepDom
.hasAttribute(ds
):
202 dstable
[ds
] = self
.DS_data
[ds
].parse(drawstepDom
.getAttribute(ds
))
208 for ds
in self
.DS_data
:
209 if drawstepDom
.hasAttribute(ds
):
210 dstable
[ds
] = self
.DS_data
[ds
].parse(drawstepDom
.getAttribute(ds
))
211 elif self
.DS_data
[ds
].isDefault
:
212 dstable
[ds
] = localDefaults
[ds
] if ds
in localDefaults
else self
._globalDefaults
[ds
]
219 def __parseDrawStepToBin(self
, stepDict
):
221 /BBBBiiiiiBBB4s4s4s4s4sB/ ==
225 gradient_factor (byte)
237 gradient_start (4 byte)
238 gradient_end (4 byte)
245 for ds
in self
.DS_list
:
246 layout
= self
.DS_data
[ds
].packFormat
250 size
= struct
.calcsize(layout
)
251 packLayout
+= "B" * size
253 for d
in range(size
):
257 packData
.append(data
)
260 stepBin
= struct
.pack(self
.BYTEORDER
+ packLayout
, *tuple(packData
))
264 def __parseResolutionToBin(self
, resString
):
266 /B bHH bHH bHH/ == 1 byte + x * 9 bytes
267 number of resolution sections (byte)
268 exclude resolution (byte)
274 return struct
.pack(self
.BYTEORDER
+ "BbHH", 1, 0, 0, 0)
276 resolutions
= resString
.split(", ")
277 packFormat
= "B" + "bHH" * len(resolutions
)
278 packData
= [len(resolutions
)]
280 for res
in resolutions
:
287 x
, y
= res
.split('x')
288 x
= 0 if x
== 'X' else int(x
)
289 y
= 0 if y
== 'Y' else int(y
)
291 raise InvalidResolution
293 packData
.append(exclude
)
297 buff
= struct
.pack(self
.BYTEORDER
+ packFormat
, *tuple(packData
))
300 def __parseRGBToBin(self
, color
):
310 rgb
= tuple(map(int, color
.split(", ")))
312 raise self
.InvalidRGBColor
315 raise self
.InvalidRGBColor
319 raise self
.InvalidRGBColor
321 rgb
= struct
.pack(self
.BYTEORDER
+ "xBBB", *tuple(rgb
))
323 # self.debugBinary(rgb)
326 def __parseColor(self
, color
):
328 color
= self
.__parseRGBToBin
(color
)
329 except self
.InvalidRGBColor
:
330 if color
not in self
._colors
:
331 raise self
.InvalidRGBColor
332 color
= self
._colors
[color
]
337 def __parsePalette(self
, paletteDom
):
340 for color
in paletteDom
.getElementsByTagName("color"):
341 color_name
= color
.getAttribute('name')
342 color_rgb
= self
.__parseRGBToBin
(color
.getAttribute('rgb'))
344 self
._colors
[color_name
] = color_rgb
345 # self.debug("COLOR: %s" % (color_name))
348 def __parseBitmaps(self
, bitmapsDom
):
354 for bitmap
in bitmapsDom
.getElementsByTagName("bitmap"):
355 bmpName
= str(bitmap
.getAttribute("filename"))
356 self
._bitmaps
[bmpName
] = idCount
358 packLayout
+= "B%ds" % (len(bmpName
) + 1)
359 packData
.append(idCount
)
360 packData
.append(bmpName
)
364 bitmapBinary
= struct
.pack(
365 self
.BYTEORDER
+ "IB" + packLayout
,
366 self
.BLOCK_HEADERS
['bitmaps'],
367 len(bitmapsDom
.getElementsByTagName("bitmap")),
371 # self.debug("BITMAPS:\n%s\n\n" % pbin(bitmapBinary))
374 def __parseFonts(self
, fontsDom
):
377 section header (uint32)
378 number of font definitions (byte)
380 id for font definition (byte)
381 resolution for font (byte array)
382 color for font (4 bytes)
383 font filename (byte array, null terminated)
389 for font
in fontsDom
.getElementsByTagName("font"):
390 ident
= font
.getAttribute("id")
392 if ident
not in self
.DS_fonts
:
393 raise self
.InvalidFontIdentifier
395 color
= self
.__parseColor
(font
.getAttribute("color"))
396 filename
= str(font
.getAttribute("file"))
398 if filename
== 'default':
401 resolution
= self
.__parseResolutionToBin
(font
.getAttribute("resolution"))
403 packLayout
+= "B%ds4s%ds" % (len(resolution
), len(filename
) + 1)
404 packData
.append(self
.DS_fonts
[ident
])
405 packData
.append(resolution
)
406 packData
.append(color
)
407 packData
.append(filename
)
409 fontsBinary
= struct
.pack(self
.BYTEORDER
+ \
411 self
.BLOCK_HEADERS
['fonts'],
412 len(fontsDom
.getElementsByTagName("font")),
417 # self.debug("FONTS DATA:\n%s\n\n" % pbin(fontsBinary))
420 def __parseTextToBin(self
, textDom
):
423 font identifier (byte)
424 vertical alignment (byte)
425 horizontal alignment (byte)
426 padding until word (byte)
429 font
= textDom
.getAttribute("font")
430 if font
not in self
.DS_fonts
:
431 raise self
.InvalidFontIdentifier
433 textBin
= struct
.pack(self
.BYTEORDER
+ "BBBx",
435 self
.DS_text_alignV
[textDom
.getAttribute("vertical_align")],
436 self
.DS_text_alignH
[textDom
.getAttribute("horizontal_align")]
441 def __parseDrawData(self
, ddDom
):
444 Section Header (uint32)
445 Resolution (byte array, word-aligned)
446 DrawData id hash (uint32)
448 has text section? (byte)
449 number of DD sections (uint16)
450 ** text segment (4 bytes)
451 drawstep segments (byte array)
455 localDefaults
= ddDom
.getElementsByTagName("defaults")
456 localDefaults
= localDefaults
[0] if localDefaults
else {}
460 for ds
in ddDom
.getElementsByTagName("drawstep"):
461 dstable
= self
.__parseDrawStep
(ds
, localDefaults
)
462 dsbinary
= self
.__parseDrawStepToBin
(dstable
)
464 stepList
.append(dsbinary
)
466 stepByteArray
= "".join(stepList
)
468 resolution
= self
.__parseResolutionToBin
(ddDom
.getAttribute("resolution"))
470 text
= ddDom
.getElementsByTagName("text")
471 text
= self
.__parseTextToBin
(text
[0]) if text
else ""
473 id_hash
= str.__hash
__(str(ddDom
.getAttribute("id"))) & 0xFFFFFFFF
474 cached
= self
.TRUTH_VALUES
[ddDom
.getAttribute("cached")]
476 ddBinary
= struct
.pack(self
.BYTEORDER
+ \
477 "I%dsIBBH4s%ds" % (len(resolution
), len(stepByteArray
)),
479 self
.BLOCK_HEADERS
['drawdata'], # Section Header (uint32)
480 resolution
, # Resolution (byte array, word-aligned)
481 id_hash
, # DrawData id hash (uint32)
482 cached
, # Cached (byte)
483 0x1 if text
else 0x0, # has text section? (byte)
484 len(stepList
), # number of DD sections (uint16)
485 text
, # ** text segment (byte array)
486 stepByteArray
# drawstep segments (byte array)
489 # self.debug("DRAW DATA %s (%X): \n" % (ddDom.getAttribute("id"), id_hash) + pbin(ddBinary) + "\n\n")
492 def __parseCursor(self
, cursorDom
):
495 section header (uint32)
496 resolution string (byte array)
503 resolution
= self
.__parseResolutionToBin
(cursorDom
.getAttribute("resolution"))
504 scale
= int(cursorDom
.getAttribute("scale"))
505 hsX
, hsY
= cursorDom
.getAttribute("hotspot").split(", ")
507 cursorBin
= struct
.pack(self
.BYTEORDER
+ "I%dsBBhh" % len(resolution
),
508 self
.BLOCK_HEADERS
['cursor'],
510 self
.__getBitmap
(cursorDom
.getAttribute("file")),
516 # self.debug("CURSOR:\n%s\n\n" % pbin(cursorBin))
519 def __parseDialog(self
, dialogDom
):
521 dialog_id
= str(dialogDom
.getAttribute("name"))
522 resolution
= self
.__parseResolutionToBin
(dialogDom
.getAttribute("resolution"))
524 overlays
= str(dialogDom
.getAttribute("overlays"))
528 if overlays
== "screen":
530 elif overlays
== "screen_center":
534 overlay_parent
= str(overlays
)
537 if dialogDom
.hasAttribute("enabled"):
538 dialog_enabled
= self
.TRUTH_VALUES
[dialogDom
.getAttribute("enabled")]
541 if dialogDom
.hasAttribute("shading"):
542 dialog_shading
= self
.DIALOG_shading
[dialogDom
.getAttribute("shading")]
545 if dialogDom
.hasAttribute("inset"):
546 dialog_inset
= int(dialogDom
.getAttribute("inset"))
548 dialogBin
= struct
.pack(self
.BYTEORDER
+ \
549 "I%ds%dsBBBB%ds" % (len(resolution
), len(dialog_id
) + 1, len(overlay_parent
) + 1),
550 self
.BLOCK_HEADERS
['dialog'],
562 def __parseLayout(self
, layoutDom
):
563 self
.debug("GLOBAL SECTION: LAYOUT INFO.")
567 for dialog
in layoutDom
.getElementsByTagName("dialog"):
568 dialogBIN
+= self
.__parseDialog
(dialog
)
571 printBinaryDump(dialogBIN
)
575 def __parseRender(self
, renderDom
):
576 self
.debug("GLOBAL SECTION: RENDER INFO.")
583 # parse color palettes
584 paletteDom
= renderDom
.getElementsByTagName("palette")
586 self
.__parsePalette
(paletteDom
[0])
589 bitmapsDom
= renderDom
.getElementsByTagName("bitmaps")
591 bitmapBIN
= self
.__parseBitmaps
(bitmapsDom
[0])
594 fontsDom
= renderDom
.getElementsByTagName("fonts")[0]
595 fontsBIN
= self
.__parseFonts
(fontsDom
)
598 defaultsDom
= renderDom
.getElementsByTagName("defaults")
600 self
._globalDefaults
= self
.__parseDrawStep
(defaultsDom
[0])
602 self
._globalDefaults
= {}
605 for cur
in renderDom
.getElementsByTagName("cursor"):
606 cursorBIN
+= self
.__parseCursor
(cur
)
608 # parse drawdata sets
609 for dd
in renderDom
.getElementsByTagName("drawdata"):
610 drawdataBIN
+= self
.__parseDrawData
(dd
)
613 renderInfoBIN
= bitmapBIN
+ fontsBIN
+ cursorBIN
+ drawdataBIN
614 printBinaryDump(renderInfoBIN
)
618 if __name__
== '__main__':
619 bin
= STXBinaryFile('../gui/themes/scummclassic', True, True)