@@ -54,6 +54,327 @@ cdef struct pixel:
54
54
uint8_t damage_modifier_2 # modifier for damage (part 2)
55
55
56
56
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 = 0 b00000011
119
+ cdef uint8_t pixel_mask_even_2 = 0 b11110000
120
+ cdef uint8_t pixel_mask_even_3 = 0 b00111111
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 = 0 b00000011 & cmd
149
+
150
+ if lower_crumb == 0 b00000011:
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 == 0 b00000000:
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 == 0 b00000001:
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 == 0 b00000010:
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 == 0 b00000010:
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
+
57
378
class SMX :
58
379
"""
59
380
Class for reading/converting compressed SMP files (delivered
0 commit comments