Skip to content

Commit bc48f64

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

File tree

1 file changed

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

1 file changed

+307
-0
lines changed

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

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

5656

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

0 commit comments

Comments
 (0)