Skip to content

Instantly share code, notes, and snippets.

@phrohdoh
Created July 13, 2016 00:25
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save phrohdoh/2bbfe1ef324db9d7f4d24d23c2568938 to your computer and use it in GitHub Desktop.
Save phrohdoh/2bbfe1ef324db9d7f4d24d23c2568938 to your computer and use it in GitHub Desktop.
Age of Empires 1997 SLP format
What follows is a description of the SLP format.
The actual author wishes to remain unknown, please email bryce@lanset.com with
your questions. However, I did not write this document.
Header
This structure totals 32 bytes and is packed to 1-byte boundaries.
typedef struct Shape_File_Header
{
char Version[4]; // Usually '2.0N'
long Num_Shapes;
char Comment[24];
} Shape_File_Header;
The header is immediately followed by an array of size "Num_Shapes" of
structures with info for each shape.
typedef struct Shape_Info
{
UInt32 Shape_Data_Offsets;
UInt32 Shape_Outline_Offset;
UInt32 Palette_Offset;
UInt32 Properties;
int Width;
int Height;
int Hotspot_X;
int Hotspot_Y;
} Shape_Info;
The Shape_Data_Offsets and Shape_Outline_Offset point to arrays of data, each
of length "Height". The "Shape_Data_Offsets" array is an array of UInt32
values, the "Shape_Outline_Offset" array consists of 2 UInt16 values.
For each vertical line of data in the shape, the Shape_Data_Offsets array
points to the start of the data for that line (for fast lookup in the blitter).
The Shape_Outline_Offset array contians two x-values for each line of the
sprite. The first 16-bit value defines how far from the left-edge the sprite
data starts drawing, while the second value defines how far from the right-edge
the sprite stops drawing. The (offset right - offset left) value thus gives the
length in pixels of the vertical line of the sprite.
I haven't seen a SLP with a value for the palette offset, so I'm not sure if
it's used. The "Properties" field is more or less unused by the game. A value
of 0x10 indicates to use the default game palette, and a value of 0x00
indicates to use the "global" palette. I believe both are the same for our
purposes.
The Hotspot_X and _Y values are used (I think) to represent the center of the
SLP for purposes of moving and manipulating them. For SLPs that are cursors,
they represent the trigger point.
The actual SLP data starts at the "Shape_Data_Offset" for each line, and is
run-length encoded using a set of 15 commands. You start by taking the first
byte in the data for the line and acting on it as a command. The command code
itself is stored in the low-nibble of the byte, e.g. command = data & 0x0f.
case 0, 4, 8, 0x0c: Block Copy (short) (LLLL LL00)
In these cases, the length of the block copy is stored in the top 6 bits of the
command byte, e.g. length = command >> 2. The data to copy follows the command
byte and is "length" bytes long. Thus, these commands are good for small chunks
of non-repeating data, up to a length of 64 bytes.
case 1, 5, 9, 0x0d: Skip pixels (short) (LLLL LL01)
These commands skip a range of pixels, up to 64. The length is encoded like
with the prior command, i.e. length = command >> 2. This command is mainly used
to draw an empty space in the middle of a sprite, as it just moves the pointer
to the drawing buffer forward.
case 2: Block Copy (big) (0000 0010)
Like the first Block Copy, but supports ranges > 64 bytes. The top 4 bits of
the command byte are shifted left 4 bits and added to the next byte in the
command stream to get a length of 0-0xfff, i.e. length = ((command & 0xf0) <<
4) + next_byte. Following that is the stream of data to copy, of size "length".
case 3: Skip pixels (big) (0000 0011)
The length is determined just like the big block copy command, and it behaves
like the other skip command in that it just moves the pointer to the drawing
buffer.
case 6: Copy & Transform block (LLLL 0110)
The length of this block is determined by the high 4 bits of the command byte.
If they are non-zero, then the length is in the range of 1-15. If the high 4
bits are zero, then the next byte in the stream is read and used as the length.
After that, a range of "length" bytes is read, and each byte is or-ed with the
player color to determine the final color of the byte. The player color value
is an index into the palette, and I believe the range is 0-15.
case 7: Fill block (LLLL 0111)
The length is determined as with case 6. The next byte in the stream determines
the color of the run. The run length is then filled with this color for
"length" bytes.
case 0x0a: Transform block (LLLL 1010)
The length is determined as in cases 6 and 7. The next byte in the stream
determines the initial color of the block run, and it is and-ed to the shadow
"and" mask, and then or-ed to the shadow "or" mask. These masks are typically
something like 0xff00ff00 and 0x00ff00ff, and are used to draw shadow effects
in the game. This is typically used to overlay a checkerboard shadow sprite
onto the existing buffer.
case 0x0b: Shadow pixels (LLLL 1011)
The length is determined as in cases 6, 7 and 0x0a. For the length of the run,
the destination pixels already in the buffer are used as a lookup into a
"shadow table" and this lookup pixel is then used to draw into the buffer. The
shadow table is typically a color-tinted variation of the real color table, and
is generally used to draw things like the red-tinted checkerboard sprites when
you try to place a building in an area where it cannot be placed.
case 0x0e: Extended commands (0XXX 1110)
The high 4 bits are used to determine an extended command, so the entire
command byte is used. These commands are mainly used to draw the sprite
outlines that you see when you move behind trees. The subcommands are as
follows:
0x0e & 0x1e: (000X 1110)
These commands are used to hint to the renderer about the command that
follows. (The byte immediately following this command is just a regular command
byte.) If the special command is 0x0e, then the command that follows is only
drawn if the sprite is not x-flipped. If the special command is 0x1e, then the
command that follows is only drawn if the sprite is x-flipped.
0x2e & 0x3e: (001X 1110)
These set the transform color tables used in the regular commands. 0x2e
sets the renderer for the normal transform color table, 0x3e sets it for the
alternate transform color table.
0x4e & 0x6e: Draw "special color 1 or 2", 1 byte (01X0 1110)
The destination draw buffer is filled with 1 byte, which is the color table
value specified by "special color" 1 or 2 (depending on the command). These are
typically the colors that are used to draw the outline color that you see when
a sprite moves behind a tree. Special color 1 is usually the player color,
special color 2 is (in the case of SWGB) typically black to enhance the
outline.
0x5e & 0x7e: Draw "special color 1 or 2" as a run (01X1 1110)
The byte following the special command is used to determine the length of
the run. The destination buffer is filled with the special color 1 or 2 for
"length" bytes.
0x8e through 0xfe. These are unused. (1XXX 1110)
case 0x0f: End of Line (XXXX 1111)
Presence of this command indicates that the sprite commands for the current
line are finished and that the parser should move onto the next vertical line
in the sprite.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment