Riven SFXE resources
This document talks about Riven resources controlling where to apply the water special effect.
Contents
What is an SFXE resource?
SFXE data has to do with the water special effect, which works by taking pixels from the rendered card picture and slightly moving them in periodic motion according to precomputed animation frames. Each frame consists of "displacement commands" that take pixel rows from the original picture and copy them at specified positions over a copy of the same picture. Displacement commands in each frame are arranged in a way that already takes into account periodic motion: the rendering code just loops interpreting frame after frame, blindly copying pixel rows over a copy of the original image.
SFXE data is divided in 4 sections: the header, an unknown and apparently unused section, an offset table and a frame section. The offset table points to frames in the frame section. Everything is in big-endian byte order.
The header
First of all comes a 52-byte header:
char | magic[2] |
unsigned short | frame_count |
unsigned long | offset_table_position |
unsigned short | left, top, right, bottom |
unsigned short | effect_speed |
unsigned short | u0 |
unsigned short | alt_top, alt_left, alt_bottom, alt_right |
unsigned short | u1 |
unsigned short | alt_frame_count |
unsigned long | u2 |
unsigned long | u3 |
unsigned long | u4 |
unsigned long | u5 |
unsigned long | u6 |
- magic is the string "SL". Maybe it identifies a known format, but I've found no info;
- frame_count is the number of animation frames;
- offset_table_position seems always 164. Probably it is the position of the offset table relative to the beginning of the resource, which is exactly 52+112=164. Altering this field makes Riven crash.
- left, top, right, bottom define the global rectangle of the effect area within the 608x392 game window. Note that right and bottom exceed by 1 pixel (that is, fullscreen SFXE resources have right = 608 and bottom = 392, not 607 and 391);
- effect_speed seems always 15. It controls the speed of the water effect, and seems in units of frames per second;
- alt_top, alt_left, alt_bottom, alt_right is a copy of the previous rectangle. It seems ignored by the engine, changing it doesn't produce effects;
- u1 seems always 0;
- alt_frame_count is a copy of frame_count;
- u2, u3, u4, u5 seem always 4, 0, 0x19 and 1 respectively;
- u6 seems always 0 or 0x10000000;
Altering any of the unknown fields u0...u6 produces no apparent change at all, nor makes the engine crash. They look really like a waste of space.
The unknown section
This is a 112-byte block of unknown purpose. It seems divided into 8-byte rows, and looks like an encoded mask or a tBMP command stream. The values don't change very much between resources and show the same structure. Incredibly enough, altering any of these values doesn't produce observable effects! Well, I've tried only on a few resources, but this behavior is quite disappointing. This data looks like a structure that was discarded in the final version of the engine, but was left in SFXE resources.
The offset table
This is just an array of frame_count unsigned long offsets, relative to the beginning of the resource. Each offset points to a frame in the frame section.
The frame section
This section contains frame_count data blocks, each representing an animation frame. Each block is made of a variable number of commands. Each command is an unsigned short opcode optionally followed by arguments. 3 opcodes have been observed:
- Command 1 has no arguments. It increments current_row by one pixel; current_row is a variable that scans the effect area row by row, starting from top. It's used by command 3 to help copying pixel rows. The number of "1" commands inside a frame is equal to the effect area height in pixels, that is, the number of pixel rows affected.
- Command 3 has 4 unsigned short arguments, dst_left src_left src_top row_width. It defines the displacement: take a row of row_width pixels from the original picture at (src_left,src_top) and copy it at (dst_left,current_row).
- Command 4 is the last command and ends the frame.