Skip to content

Commit

Permalink
Add generic function fox SMX file
Browse files Browse the repository at this point in the history
  • Loading branch information
9a24f0 committed Oct 25, 2021
1 parent bf0094c commit bc48f64
Showing 1 changed file with 307 additions and 0 deletions.
307 changes: 307 additions & 0 deletions openage/convert/value_object/read/media/smx.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,313 @@ cdef struct pixel:
uint8_t damage_modifier_2 # modifier for damage (part 2)


cdef fused ProcessDrawingCmdsVariant:
SMXLayerVariant
SMXMainLayer8to5Variant
SMXMainLayer4plus1Variant
SMXOutlineLayerVariant
SMXShadowLayerVariant


cdef process_drawing_cmds(ProcessDrawingCmdsVariant variant,
const uint8_t[:] &data_raw,
vector[pixel] &row_data,
Py_ssize_t rowid,
Py_ssize_t first_cmd_offset,
Py_ssize_t first_color_offset,
int chunk_pos,
size_t expected_size):
"""
TODO: docstring
"""

if ProcessDrawingCmdsVariant is SMXLayerVariant:
pass

# position in the command array, we start at the first command of this row
cdef Py_ssize_t dpos_cmd = first_cmd_offset

# is the end of the current row reached?
cdef bool eor = False

cdef uint8_t cmd = 0
cdef uint8_t lower_crumb = 0
cdef int pixel_count = 0

if ProcessDrawingCmdsVariant is SMXMainLayer8to5Variant
# Position in the pixel data array
cdef Py_ssize_t dpos_color = first_color_offset

# Position in the compression chunk.
cdef bool odd = chunk_pos
cdef int px_dpos = 0 # For loop iterator

cdef vector[uint8_t] pixel_data
pixel_data.reserve(4)

# Pixel data temporary values that need further decompression
cdef uint8_t pixel_data_odd_0 = 0
cdef uint8_t pixel_data_odd_1 = 0
cdef uint8_t pixel_data_odd_2 = 0
cdef uint8_t pixel_data_odd_3 = 0

# Mask for even indices
# cdef uint8_t pixel_mask_even_0 = 0xFF
cdef uint8_t pixel_mask_even_1 = 0b00000011
cdef uint8_t pixel_mask_even_2 = 0b11110000
cdef uint8_t pixel_mask_even_3 = 0b00111111

if ProcessDrawingCmdsVariant is SMXMainLayer4plus1Variant:
# Position in the pixel data array
cdef Py_ssize_t dpos_color = first_color_offset

# Position in the compression chunk
cdef uint8_t dpos_chunk = chunk_pos

cdef uint8_t palette_section_block = 0
cdef uint8_t palette_section = 0

if ProcessDrawingCmdsVariant is SMXShadowLayerVariant || ProcessDrawingCmdsVariant is SMXOutlineLayerVariant:
cdef uint8_t nextbyte = 0


# work through commands till end of row.
while not eor:
if row_data.size() > expected_size:
raise Exception(
f"Only {expected_size:d} pixels should be drawn in row {rowid:d} "
f"with layer type {self.info.layer_type:#x}, but we have {row_data.size():d} "
f"already!"
)

# fetch drawing instruction
cmd = data_raw[dpos_cmd]

# Last 2 bits store command type
lower_crumb = 0b00000011 & cmd

if lower_crumb == 0b00000011:
# eor (end of row) command, this row is finished now.
eor = True
dpos_cmd += 1

if ProcessDrawingCmdsVariant is SMXShadowLayerVariant:
# shadows sometimes need an extra pixel at
# the end
if row_data.size() < expected_size:
# copy the last drawn pixel
# (still stored in nextbyte)
#
# TODO: confirm that this is the
# right way to do it
row_data.push_back(pixel(color_shadow,
nextbyte, 0, 0, 0))
continue

elif lower_crumb == 0b00000000:
# skip command
# draw 'count' transparent pixels
# count = (cmd >> 2) + 1

pixel_count = (cmd >> 2) + 1

for _ in range(pixel_count):
row_data.push_back(pixel(color_transparent, 0, 0, 0, 0))

elif lower_crumb == 0b00000001:
# color_list command
# draw the following 'count' pixels
# pixels are stored in 5 byte chunks
# even pixel indices have their info stored
# in byte[0] - byte[3]. odd pixel indices have
# their info stored in byte[1] - byte[4].
# count = (cmd >> 2) + 1

pixel_count = (cmd >> 2) + 1

if ProcessDrawingCmdsVariant is SMXMainLayer8to5Variant:
for _ in range(pixel_count):
# Start fetching pixel data
if odd:
# Odd indices require manual extraction of each of the 4 values

# Palette index. Essentially a rotation of (byte[1]byte[2])
# by 6 to the left, then masking with 0x00FF.
pixel_data_odd_0 = data_raw[dpos_color + 1]
pixel_data_odd_1 = data_raw[dpos_color + 2]
pixel_data.push_back((pixel_data_odd_0 >> 2) | (pixel_data_odd_1 << 6))

# Palette section. Described in byte[2] in bits 4-5.
pixel_data.push_back((pixel_data_odd_1 >> 2) & 0x03)

# Damage mask 1. Essentially a rotation of (byte[3]byte[4])
# by 6 to the left, then masking with 0x00F0.
pixel_data_odd_2 = data_raw[dpos_color + 3]
pixel_data_odd_3 = data_raw[dpos_color + 4]
pixel_data.push_back(((pixel_data_odd_2 >> 2) | (pixel_data_odd_3 << 6)) & 0xF0)

# Damage mask 2. Described in byte[4] in bits 0-5.
pixel_data.push_back((pixel_data_odd_3 >> 2) & 0x3F)

row_data.push_back(pixel(color_standard,
pixel_data[0],
pixel_data[1],
pixel_data[2],
pixel_data[3]))

# Go to next pixel
dpos_color += 5

else:
# Even indices can be read "as is". They just have to be masked.
for px_dpos in range(4):
pixel_data.push_back(data_raw[dpos_color + px_dpos])

row_data.push_back(pixel(color_standard,
pixel_data[0],
pixel_data[1] & pixel_mask_even_1,
pixel_data[2] & pixel_mask_even_2,
pixel_data[3] & pixel_mask_even_3))

odd = not odd
pixel_data.clear()

if ProcessDrawingCmdsVariant is SMXMainLayer4plus1Variant:
palette_section_block = data_raw[dpos_color + (4 - dpos_chunk)]

for _ in range(pixel_count):
# Start fetching pixel data
palette_section = (palette_section_block >> (2 * dpos_chunk)) & 0x03
row_data.push_back(pixel(color_standard,
data_raw[dpos_color],
palette_section,
0,
0))

dpos_color += 1
dpos_chunk += 1

# Skip to next chunk
if dpos_chunk > 3:
dpos_chunk = 0
dpos_color += 1 # Skip palette section block
palette_section_block = data_raw[dpos_color + 4]

if ProcessDrawingCmdsVariant is SMXShadowLayerVariant:
for _ in range(pixel_count):
dpos_color += 1
nextbyte = data_raw[dpos_color]

row_data.push_back(pixel(color_shadow,
nextbyte, 0, 0, 0))

if ProcessDrawingCmdsVariant is SMXOutlineLayerVariant:
# we don't know the color the game wants
# so we just draw index 0
row_data.push_back(pixel(color_outline,
0, 0, 0, 0))

if ProcessDrawingCmdsVariant is SMXMainLayer8to5Variant:
elif lower_crumb == 0b00000010:
# player_color command
# draw the following 'count' pixels
# pixels are stored in 5 byte chunks
# even pixel indices have their info stored
# in byte[0] - byte[3]. odd pixel indices have
# their info stored in byte[1] - byte[4].
# count = (cmd >> 2) + 1

pixel_count = (cmd >> 2) + 1

for _ in range(pixel_count):
# Start fetching pixel data
if odd:
# Odd indices require manual extraction of each of the 4 values

# Palette index. Essentially a rotation of (byte[1]byte[2])
# by 6 to the left, then masking with 0x00FF.
pixel_data_odd_0 = data_raw[dpos_color + 1]
pixel_data_odd_1 = data_raw[dpos_color + 2]
pixel_data.push_back((pixel_data_odd_0 >> 2) | (pixel_data_odd_1 << 6))

# Palette section. Described in byte[2] in bits 4-5.
pixel_data.push_back((pixel_data_odd_1 >> 2) & 0x03)

# Damage modifier 1. Essentially a rotation of (byte[3]byte[4])
# by 6 to the left, then masking with 0x00F0.
pixel_data_odd_2 = data_raw[dpos_color + 3]
pixel_data_odd_3 = data_raw[dpos_color + 4]
pixel_data.push_back(((pixel_data_odd_2 >> 2) | (pixel_data_odd_3 << 6)) & 0xF0)

# Damage modifier 2. Described in byte[4] in bits 0-5.
pixel_data.push_back((pixel_data_odd_3 >> 2) & 0x3F)

row_data.push_back(pixel(color_player,
pixel_data[0],
pixel_data[1],
pixel_data[2],
pixel_data[3]))

# Go to next pixel
dpos_color += 5

else:
# Even indices can be read "as is". They just have to be masked.
for px_dpos in range(4):
pixel_data.push_back(data_raw[dpos_color + px_dpos])

row_data.push_back(pixel(color_player,
pixel_data[0],
pixel_data[1] & pixel_mask_even_1,
pixel_data[2] & pixel_mask_even_2,
pixel_data[3] & pixel_mask_even_3))

odd = not odd
pixel_data.clear()

if ProcessDrawingCmdsVariant is SMXMainLayer4plus1Variant:
elif lower_crumb == 0b00000010:
# player_color command
# draw the following 'count' pixels
# 4 pixels are stored in every 5 byte chunk.
# palette indices are contained in byte[0] - byte[3]
# palette sections are stored in byte[4]
# count = (cmd >> 2) + 1

pixel_count = (cmd >> 2) + 1

for _ in range(pixel_count):
# Start fetching pixel data
palette_section = (palette_section_block >> (2 * dpos_chunk)) & 0x03
row_data.push_back(pixel(color_player,
data_raw[dpos_color],
palette_section,
0,
0))

dpos_color += 1
dpos_chunk += 1

# Skip to next chunk
if dpos_chunk > 3:
dpos_chunk = 0
dpos_color += 1 # Skip palette section block
palette_section_block = data_raw[dpos_color + 4]

else:
raise Exception(
f"unknown smx main graphics layer drawing command: " +
f"{cmd:#x} in row {rowid:d}"
)

# Process next command
dpos_cmd += 1
if ProcessDrawingCmdsVariant is SMXMainLayer8to5Variant || ProcessDrawingCmdsVariant is SMXMainLayer4plus1Variant:
return dpos_cmd, dpos_color, odd, row_data
if ProcessDrawingCmdsVariant is SMXOutlineLayerVariant || ProcessDrawingCmdsVariant is SMXShadowLayerVariant:
return dpos_cmd, dpos_cmd, chunk_pos, row_data


class SMX:
"""
Class for reading/converting compressed SMP files (delivered
Expand Down

0 comments on commit bc48f64

Please sign in to comment.