Skip to content

Commit 5bf2694

Browse files
committed
Add generic function fox SMX file
1 parent bf0094c commit 5bf2694

File tree

1 file changed

+321
-0
lines changed
  • openage/convert/value_object/read/media

1 file changed

+321
-0
lines changed

openage/convert/value_object/read/media/smx.pyx

+321
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,327 @@ cdef struct pixel:
5454
uint8_t damage_modifier_2 # modifier for damage (part 2)
5555

5656

57+
cdef class SMXMainLayer8to5Variant:
58+
pass
59+
60+
cdef class SMXMainLayer4plus1Variant:
61+
pass
62+
63+
cdef class SMXOutlineLayerVariant:
64+
pass
65+
66+
cdef class SMXShadowLayerVariant:
67+
pass
68+
69+
70+
ctypedef fused SMXLayerVariant:
71+
SMXMainLayer8to5Variant
72+
SMXMainLayer4plus1Variant
73+
SMXOutlineLayerVariant
74+
SMXShadowLayerVariant
75+
76+
77+
cdef process_drawing_cmds(SMXLayerVariant variant,
78+
const uint8_t[:] &data_raw,
79+
vector[pixel] &row_data,
80+
Py_ssize_t rowid,
81+
Py_ssize_t first_cmd_offset,
82+
Py_ssize_t first_color_offset,
83+
int chunk_pos,
84+
size_t expected_size):
85+
"""
86+
TODO: docstring
87+
"""
88+
89+
# position in the command array, we start at the first command of this row
90+
cdef Py_ssize_t dpos_cmd = first_cmd_offset
91+
92+
# is the end of the current row reached?
93+
cdef bool eor = False
94+
95+
cdef uint8_t cmd = 0
96+
cdef uint8_t lower_crumb = 0
97+
cdef int pixel_count = 0
98+
99+
if SMXLayerVariant is SMXMainLayer8to5Variant
100+
# Position in the pixel data array
101+
cdef Py_ssize_t dpos_color = first_color_offset
102+
103+
# Position in the compression chunk.
104+
cdef bool odd = chunk_pos
105+
cdef int px_dpos = 0 # For loop iterator
106+
107+
cdef vector[uint8_t] pixel_data
108+
pixel_data.reserve(4)
109+
110+
# Pixel data temporary values that need further decompression
111+
cdef uint8_t pixel_data_odd_0 = 0
112+
cdef uint8_t pixel_data_odd_1 = 0
113+
cdef uint8_t pixel_data_odd_2 = 0
114+
cdef uint8_t pixel_data_odd_3 = 0
115+
116+
# Mask for even indices
117+
# cdef uint8_t pixel_mask_even_0 = 0xFF
118+
cdef uint8_t pixel_mask_even_1 = 0b00000011
119+
cdef uint8_t pixel_mask_even_2 = 0b11110000
120+
cdef uint8_t pixel_mask_even_3 = 0b00111111
121+
122+
if SMXLayerVariant is SMXMainLayer4plus1Variant:
123+
# Position in the pixel data array
124+
cdef Py_ssize_t dpos_color = first_color_offset
125+
126+
# Position in the compression chunk
127+
cdef uint8_t dpos_chunk = chunk_pos
128+
129+
cdef uint8_t palette_section_block = 0
130+
cdef uint8_t palette_section = 0
131+
132+
if SMXLayerVariant in (SMXShadowLayerVariant, SMXOutlineLayerVariant):
133+
cdef uint8_t nextbyte = 0
134+
135+
# work through commands till end of row.
136+
while not eor:
137+
if row_data.size() > expected_size:
138+
raise Exception(
139+
f"Only {expected_size:d} pixels should be drawn in row {rowid:d} "
140+
f"with layer type {self.info.layer_type:#x}, but we have {row_data.size():d} "
141+
f"already!"
142+
)
143+
144+
# fetch drawing instruction
145+
cmd = data_raw[dpos_cmd]
146+
147+
# Last 2 bits store command type
148+
lower_crumb = 0b00000011 & cmd
149+
150+
if lower_crumb == 0b00000011:
151+
# eor (end of row) command, this row is finished now.
152+
eor = True
153+
dpos_cmd += 1
154+
155+
if is SMXShadowLayerVariant:
156+
# shadows sometimes need an extra pixel at
157+
# the end
158+
if row_data.size() < expected_size:
159+
# copy the last drawn pixel
160+
# (still stored in nextbyte)
161+
#
162+
# TODO: confirm that this is the
163+
# right way to do it
164+
row_data.push_back(pixel(color_shadow,
165+
nextbyte, 0, 0, 0))
166+
continue
167+
168+
elif lower_crumb == 0b00000000:
169+
# skip command
170+
# draw 'count' transparent pixels
171+
# count = (cmd >> 2) + 1
172+
173+
pixel_count = (cmd >> 2) + 1
174+
175+
for _ in range(pixel_count):
176+
row_data.push_back(pixel(color_transparent, 0, 0, 0, 0))
177+
178+
elif lower_crumb == 0b00000001:
179+
# color_list command
180+
# draw the following 'count' pixels
181+
# pixels are stored in 5 byte chunks
182+
# even pixel indices have their info stored
183+
# in byte[0] - byte[3]. odd pixel indices have
184+
# their info stored in byte[1] - byte[4].
185+
# count = (cmd >> 2) + 1
186+
187+
pixel_count = (cmd >> 2) + 1
188+
189+
if is SMXMainLayer8to5Variant:
190+
for _ in range(pixel_count):
191+
# Start fetching pixel data
192+
if odd:
193+
# Odd indices require manual extraction of each of the 4 values
194+
195+
# Palette index. Essentially a rotation of (byte[1]byte[2])
196+
# by 6 to the left, then masking with 0x00FF.
197+
pixel_data_odd_0 = data_raw[dpos_color + 1]
198+
pixel_data_odd_1 = data_raw[dpos_color + 2]
199+
pixel_data.push_back((pixel_data_odd_0 >> 2) | (pixel_data_odd_1 << 6))
200+
201+
# Palette section. Described in byte[2] in bits 4-5.
202+
pixel_data.push_back((pixel_data_odd_1 >> 2) & 0x03)
203+
204+
# Damage mask 1. Essentially a rotation of (byte[3]byte[4])
205+
# by 6 to the left, then masking with 0x00F0.
206+
pixel_data_odd_2 = data_raw[dpos_color + 3]
207+
pixel_data_odd_3 = data_raw[dpos_color + 4]
208+
pixel_data.push_back(((pixel_data_odd_2 >> 2) | (pixel_data_odd_3 << 6)) & 0xF0)
209+
210+
# Damage mask 2. Described in byte[4] in bits 0-5.
211+
pixel_data.push_back((pixel_data_odd_3 >> 2) & 0x3F)
212+
213+
row_data.push_back(pixel(color_standard,
214+
pixel_data[0],
215+
pixel_data[1],
216+
pixel_data[2],
217+
pixel_data[3]))
218+
219+
# Go to next pixel
220+
dpos_color += 5
221+
222+
else:
223+
# Even indices can be read "as is". They just have to be masked.
224+
for px_dpos in range(4):
225+
pixel_data.push_back(data_raw[dpos_color + px_dpos])
226+
227+
row_data.push_back(pixel(color_standard,
228+
pixel_data[0],
229+
pixel_data[1] & pixel_mask_even_1,
230+
pixel_data[2] & pixel_mask_even_2,
231+
pixel_data[3] & pixel_mask_even_3))
232+
233+
odd = not odd
234+
pixel_data.clear()
235+
236+
if is SMXMainLayer4plus1Variant:
237+
palette_section_block = data_raw[dpos_color + (4 - dpos_chunk)]
238+
239+
for _ in range(pixel_count):
240+
# Start fetching pixel data
241+
palette_section = (palette_section_block >> (2 * dpos_chunk)) & 0x03
242+
row_data.push_back(pixel(color_standard,
243+
data_raw[dpos_color],
244+
palette_section,
245+
0,
246+
0))
247+
248+
dpos_color += 1
249+
dpos_chunk += 1
250+
251+
# Skip to next chunk
252+
if dpos_chunk > 3:
253+
dpos_chunk = 0
254+
dpos_color += 1 # Skip palette section block
255+
palette_section_block = data_raw[dpos_color + 4]
256+
257+
if SMXLayerVariant is SMXShadowLayerVariant:
258+
for _ in range(pixel_count):
259+
dpos_color += 1
260+
nextbyte = data_raw[dpos_color]
261+
262+
row_data.push_back(pixel(color_shadow,
263+
nextbyte, 0, 0, 0))
264+
265+
if SMXLayerVariant is SMXOutlineLayerVariant:
266+
# we don't know the color the game wants
267+
# so we just draw index 0
268+
row_data.push_back(pixel(color_outline,
269+
0, 0, 0, 0))
270+
271+
272+
elif lower_crumb == 0b00000010:
273+
if SMXLayerVariant is SMXMainLayer8to5Variant:
274+
# player_color command
275+
# draw the following 'count' pixels
276+
# pixels are stored in 5 byte chunks
277+
# even pixel indices have their info stored
278+
# in byte[0] - byte[3]. odd pixel indices have
279+
# their info stored in byte[1] - byte[4].
280+
# count = (cmd >> 2) + 1
281+
282+
pixel_count = (cmd >> 2) + 1
283+
284+
for _ in range(pixel_count):
285+
# Start fetching pixel data
286+
if odd:
287+
# Odd indices require manual extraction of each of the 4 values
288+
289+
# Palette index. Essentially a rotation of (byte[1]byte[2])
290+
# by 6 to the left, then masking with 0x00FF.
291+
pixel_data_odd_0 = data_raw[dpos_color + 1]
292+
pixel_data_odd_1 = data_raw[dpos_color + 2]
293+
pixel_data.push_back((pixel_data_odd_0 >> 2) | (pixel_data_odd_1 << 6))
294+
295+
# Palette section. Described in byte[2] in bits 4-5.
296+
pixel_data.push_back((pixel_data_odd_1 >> 2) & 0x03)
297+
298+
# Damage modifier 1. Essentially a rotation of (byte[3]byte[4])
299+
# by 6 to the left, then masking with 0x00F0.
300+
pixel_data_odd_2 = data_raw[dpos_color + 3]
301+
pixel_data_odd_3 = data_raw[dpos_color + 4]
302+
pixel_data.push_back(((pixel_data_odd_2 >> 2) | (pixel_data_odd_3 << 6)) & 0xF0)
303+
304+
# Damage modifier 2. Described in byte[4] in bits 0-5.
305+
pixel_data.push_back((pixel_data_odd_3 >> 2) & 0x3F)
306+
307+
row_data.push_back(pixel(color_player,
308+
pixel_data[0],
309+
pixel_data[1],
310+
pixel_data[2],
311+
pixel_data[3]))
312+
313+
# Go to next pixel
314+
dpos_color += 5
315+
316+
else:
317+
# Even indices can be read "as is". They just have to be masked.
318+
for px_dpos in range(4):
319+
pixel_data.push_back(data_raw[dpos_color + px_dpos])
320+
321+
row_data.push_back(pixel(color_player,
322+
pixel_data[0],
323+
pixel_data[1] & pixel_mask_even_1,
324+
pixel_data[2] & pixel_mask_even_2,
325+
pixel_data[3] & pixel_mask_even_3))
326+
327+
odd = not odd
328+
pixel_data.clear()
329+
330+
331+
elif lower_crumb == 0b00000010:
332+
if SMXLayerVariant is SMXMainLayer4plus1Variant:
333+
# player_color command
334+
# draw the following 'count' pixels
335+
# 4 pixels are stored in every 5 byte chunk.
336+
# palette indices are contained in byte[0] - byte[3]
337+
# palette sections are stored in byte[4]
338+
# count = (cmd >> 2) + 1
339+
340+
pixel_count = (cmd >> 2) + 1
341+
342+
for _ in range(pixel_count):
343+
# Start fetching pixel data
344+
palette_section = (palette_section_block >> (2 * dpos_chunk)) & 0x03
345+
row_data.push_back(pixel(color_player,
346+
data_raw[dpos_color],
347+
palette_section,
348+
0,
349+
0))
350+
351+
dpos_color += 1
352+
dpos_chunk += 1
353+
354+
# Skip to next chunk
355+
if dpos_chunk > 3:
356+
dpos_chunk = 0
357+
dpos_color += 1 # Skip palette section block
358+
palette_section_block = data_raw[dpos_color + 4]
359+
360+
if SMXLayerVariant is (SMXOutlineLayerVariant, SMXShadowLayerVariant):
361+
pass
362+
363+
else:
364+
raise Exception(
365+
f"unknown smx main graphics layer drawing command: " +
366+
f"{cmd:#x} in row {rowid:d}"
367+
)
368+
369+
# Process next command
370+
dpos_cmd += 1
371+
372+
if SMXLayerVariant in (SMXMainLayer8to5Variant, SMXMainLayer4plus1Variant):
373+
return dpos_cmd, dpos_color, odd, row_data
374+
if SMXLayerVariant in (SMXOutlineLayerVariant, SMXShadowLayerVariant):
375+
return dpos_cmd, dpos_cmd, chunk_pos, row_data
376+
377+
57378
class SMX:
58379
"""
59380
Class for reading/converting compressed SMP files (delivered

0 commit comments

Comments
 (0)