SLP files¶
SLP files are stored in the DRS files. They contain all the (animation) textures. Like the DRS format, it can also be read sequentially.
SLP file format¶
Header¶
The SLP file starts with a header:
Length | Type | Description | Example |
---|---|---|---|
4 bytes | string | Version | 2.0N |
4 bytes | int32 | Number of frames | 1, 0x00000001 |
24 bytes | string | Comment | ArtDesk 1.00 SLP Writer |
struct slp_header {
char version[4];
int32 num_frames;
char comment[24];
};
Python format: Struct("< 4s i 24s")
SLP Frame info¶
After the header, there are num_frames
entries of slp_frame_info
:
Every slp_frame_info
stores meta-information about a single frame (texture)
within the SLP.
Length | Type | Description | Example |
---|---|---|---|
4 bytes | uint32 | Command table offset | 2464, 0x000009A0 |
4 bytes | uint32 | Outline table offset | 64, 0x00000040 |
4 bytes | uint32 | Palette offset (unused) | 0, 0x00000000 |
4 bytes | uint32 | Properties (likely unused) | 16, 0x00000010 |
4 bytes | int32 | Width of image | 800, 0x00000320 |
4 bytes | int32 | Height of image | 600, 0x00000258 |
4 bytes | int32 | Centre of sprite (X coord) | 0, 0x00000000 |
4 bytes | int32 | Centre of sprite (Y coord) | 0, 0x00000000 |
struct slp_frame_info {
uint32 cmd_table_offset;
uint32 outline_table_offset;
uint32 palette_offset;
uint32 properties;
int32 width;
int32 height;
int32 hotspot_x;
int32 hotspot_y;
};
Python format: Struct("< I I I I i i i i")
palette_offset
is apparently never used.properties
is only used to indicate which palette for the image to use. However, these seem to be the same, so can generally be ignored.0x10 - “default game palette”
0x00 - “global color palette”
hotspot_x
&hotspot_y
tell the engine where the centre of the unit is.
One image of size width
*height
has height
rows, of course.
SLP Frame row edge¶
At outline_table_offset
(after the slp_frame_info
structs), an array of
slp_frame_row_edge
(of length height
) structs begins.
Length | Type | Description | Example |
---|---|---|---|
2 bytes | uint16 | Left spacing | 20, 0x0014 |
2 bytes | uint16 | Right spacing | 3, 0x0003 |
struct slp_frame_row_edge {
uint16 left_space;
uint16 right_space;
};
Python format: Struct("< H H")
For every row, left_space
and right_space
specify the number of transparent
pixels, from each side to the center. For example, in a 50 pixels wide row, with
a slp_frame_row_edge
of { .left_space = 20, .right_space = 3 }
, the leftmost
20 pixels will be transparent, the rightmost 3 will be transparent and there
will be 27 pixels of graphical data provided through some number of commands.
If the right or left value is 0x8000
, the row is completely transparent.
Note that there are no command bytes for these rows, so will have to be skipped
“manually”.
width - left_space - right_space
= number of pixels in this line.
After those padding frames, at slp_frame_info.cmd_table_offset
, an array of
uint32 (of length height
) begins:
struct slp_command_offset {
uint32 offset;
}
Python format: Struct("< I")
Each offset
defines the offset (beginning) of the first command of a row.
The first offset
in this array is the first drawing command for the image.
These are not actually necessary to use (but obviously are necessary to read), since the commands can be read sequentially, although they can be used for validation purposes.
The actual command data starts after the end of the command offsets, or:
slp_frame_info.cmd_table_offset + 4 * slp_frame_info.height
.
SLP drawing commands¶
The image is drawn line by line, a line is finished with the “End of line”
command (0x0F), where a command is a one-byte number (stored in the
least-significant bits of the byte, e.g. command = data & 0x0F
), followed by
command-specific data with a length varying depending on the command. The next
command immediately follows the previous command’s data.
Each command triggers a drawing method for n = “Count” pixels.
All the commands tell you to draw a palette_index, for n pixels.
The Pixel count value is stored at these locations: (where cmd_byte
is the
value of the command byte, and next_byte
is the value of the next byte)
Value name | Pixel count |
---|---|
next | next_byte |
>> 2 |
cmd_byte >> 2 (i.e., most significant bits used as data value) |
>> n or next |
pixel_count = cmd_byte >> n; if pixel_count == 0: pixel_count = next_byte . i.e., the 8 - n most significant bits of cmd_byte if they are != 0 , else the next byte. |
<< 4 + next |
((cmd_byte & 0xf0) << 4) + next_byte |
Commands where Count is >> i
only reuse the most significant bits of cmd_byte
.
Command Name | Byte value | Count | Description |
---|---|---|---|
Lesser block copy | 0x00 |
>> 2 |
An array of length Count filled with 1-byte palette indices follows, 1 index per pixel. |
Lesser skip | 0x01 |
>> 2 or next |
Count transparent pixels should be drawn from the current position. |
Greater block copy | 0x02 |
<< 4 + next |
An array of length Count filled with 1-byte palette indices follows, 1 index per pixel. |
Greater skip | 0x03 |
<< 4 + next |
Count transparent pixels should be drawn from the current position. |
Player color block copy | 0x06 |
>> 4 or next |
An array of length "Count" filled with 1-byte player color palette indices follow, 1 index per pixel. The real palette index is player_color_palette_index + player * 16 , where player is the player ID you're drawing for (1-8). |
Fill | 0x07 |
>> 4 or next |
One palette index byte follows. This color should be drawn pixel_count times from the current position. |
Fill player color | 0x0A |
>> 4 or next |
One player palette index byte follows. This color should be drawn pixel_count times. See player_color_list (0x06) |
Shadow | 0x0B |
>> 4 or next |
Draw Count shadow pixels (The pixels to draw when a unit is behind another object). |
Extended command | 0x0E |
depends | Get the specific extended command by looking at the most significant bits of the command. (See the table below for details) |
End of line | 0x0F |
0 | End of commands for this row. If more commands follow, they are for the next row. |
Extended commands, for outlines behind trees/buildings:
Command name | Byte value | Count | Description |
---|---|---|---|
render_hint_xflip | 0x0E |
0 | Draw the following command if sprite is not flipped right to left. |
render_h_notxflip | 0x1E |
0 | Draw following command if this sprite is x-flipped. |
table_use_normal | 0x2E |
0 | Set color transform table to normal. |
table_use_alternate | 0x3E |
0 | Set color transform table to alternate. |
outline_1 | 0x4E |
1 | palette_index = player_index = player * 16 , if obstructed, draw player color, else transparent. This is the player color outline you see when a unit is behind a building. (special color = 1) |
outline_span_1 | 0x5E |
next | palette_index = player_index = player * 16 , can be >=1 pixel |
outline_2 | 0x6E |
1 | palette_index = 0 , if pixel obstructed, draw a pixel as black outline, else transparent. (special color = 2) |
outline_span_2 | 0x7E |
next | palette_index = 0 , same as obstruct_black, >=1 pixel. |
dither | 0x8E |
? | ? |
premulti_alpha | 0x9E |
? | Premultiplied alpha? |
orig_alph | 0xA0 |
? | Original alpha? |
“Lesser block copy” (0x00) also takes the values
0x04, 0x08 & 0x0C
“Lesser skip” (0x01) also takes the values
0x05, 0x09, 0x0D
.“Player color block copy” (0x06) is an index into the palette, in range 0-15.
“Shadow” (0x0B) destination “pixels” are used as a lookup into a shadow_table. This lookup pixel is then used to draw into the buffer. The shadow table is a color-tinted variation of the real color table. It’s also used to draw things like the red-tinted checkerboard when placing buildings at forbidden places.
“special color” - 2 means black (enhance the outline, cartoonlike), 1 is player color.
When a unit is behind an object (Commands
0x{4,5,6,7}E
used), only the outline of the corresponding part of the unit is drawn, the rest of the unit is transparent. So, the left outline is one pixel more to the left than the first color.Usually, these outline pixels should be stored to a second image, which is rendered on top of the tree/building by the fragment shader.
For later drawing, a graphics file needs flags for:
object has outline
can show objects behind by outline
both of them
Now the palette indices for all the colors of the unit are known, but a palette is needed for them to be drawn.
Palette Files¶
The drawing palette is stored inside interfac.drs
. It’s basically an array of
(r, g, b)
tuples.
The file id is 50500+x
, the palettes (color tables) are stored the same way
as .SLPs are. (see .DRS Files) Here x
is the palette index,
which should be 0, experiment with [1,10]
…
interfac.drs
contains many of these files, but the ingame art uses id 50500.
The palette is a (text-based) JASC Paint Shop Pro file, starting with
JASC-PAL\r\n
. Read this line at the very start of a .BIN file to see if it’s a
palette file. The second line stores version information, should be 0100
. The
third line stores the number of entries, as text.
The rest is that many r g b\r\n
lines. Again, r
, g
and b
are stored as
text (range 0-255
).
They are referenced in the SLP files, so should be indexed. It’s simply an array, so line 3=>index 0, line 4=>index 1 etc.
SLP types¶
SLP files for moving objects¶
Unit .SLP files contain 5 states:
Attacking
Standing Ground
Dying
Rotting
Moving
On average, each state has roughly 50 animation frames -> ~250 textures per unit
For each animation, 5 directions of it are stored, the other 3 directions are generated later by flipping the sprite on the y axis.
Villagers have way more than the 5 animations (for the different resources), boats have 2 (boat & sail).
SLP files for static objects¶
Contain the image of the building. Some static objects have multiple chunks, like the Town Center, where units can be under one arm and ‘in front of’ the main building.
SLP files for weapons¶
Dust, arrows, etc.
SLP files for shadows¶
Every object in game has a shadow. Moving units have frames for their shadow in the same SLP file, but buildings and other objects have their shadows in separate SLPs.
Large numbers of shadow SLPs are difficult to identify, due to the image only being in a single color.