-
Notifications
You must be signed in to change notification settings - Fork 3
Scumm Costume formats
Costumes store the animations used for characters in the game. The format is relatively complex. It basically consists of a bunch of frames and commands to make use of them :) I had a hard time really figuring out how this works.
size : 32le can be 0 (see num anim) or the size
(sometimes with an offset of one ??)
header : 2*8 always contain "CO"
num anim : 8 if(size) num_anim++
format : 8 bit 7 set means that west anims must NOT be mirrored,
bit 0 is the palette size (0: 16 colors, 1: 32 colors)
palette : 8*num colors coded in format
anim cmds offset : 16le access the anim cmds array
limbs offset : 16*16le access limb picture table
anim offsets : 16le*num anim access anim definitions
anim
limb mask : 16le
anim definitions : variable length, one definition for each bit set
to 1 in the limb mask.
0xFFFF : 16le disabled limb code
OR
start : 16le
noloop : 1
end offset : 7 offset of the last frame, or len-1
anim cmds
cmd : 8
limbs
pict offset : 16le
picts
width : 16le
height : 16le
rel_x : s16le
rel_y : s16le
move_x : s16le
move_y : s16le
redir_limb : 8 only present if((format & 0x7E) == 0x60)
redir_pict : 8 only present if((format & 0x7E) == 0x60)
rle data
Each animation can have up to 16 ’’limbs’’, each with its own sequence of pictures. The sequences are defined in the ’’anim’’ block. A definition starts with a 16le mask telling which limbs are used. Each bit of the mask stands for a limb. After the mask is a 16le index, giving the start position of the sequence in the ‘’anim cmds’’ array. An index of 0xFFFF is used to turn off the limb; if the index is not 0xFFFF, then it’s followed by the length of the sequence (8 bits). The highest bit of the length is used to indicate whether the sequence should loop, if it is set the animation doesn’t loop.
So an animation is defined by a part of the ‘’anim cmds’’ array. This array contains either picture numbers or some special values used to fire sounds, etc. Now to access the pictures, one has to look in the ’’limb’’ table. The table again contains offsets which finally allow us to find the right picture. So each limb has its own set of pictures, although sometimes several limbs share the same table.
As you can see, the format 0×60 has 2 extra fields in the picture header. If they are not 0xFF, they indicate a redirection to the picture ’’redir_pict’’ of the limb ’’redir_limb’’. Also note that sometimes several entries in the limb table will point to the same picture.
It seems the data is always properly ordered. That is, the first picture of the first limb comes right after the last limb table. The first limb table start right after the cmd array, and so on. Currently this seems to be the only way to determine how long the cmd array is, or how long the last limb table is. Clumsy but it works, however a simple decoder doesn’t need to compute these lengths :)
Note: All the offsets are relative to the block start (without the standard 8 byte SCUMM block header).
WARNING: When one numbers the limbs from their corresponding bit in the limb masks, they are then indexed in reverse order. This means the first entry in the limb table is limb 15, then comes limb 14, etc. The same holds for the anim definitions. Rendering should also be done in that order.
Note 2: The transparent color is always 0.
There are very few commands available and they have no arguments.
-
0×71-0×78: addSound()
In Lucas games add sound cmd-0×71, in Humongous games add sound 0×78-cmd. - 0×79: stop()
- 0×7A: start()
- 0×7B: hide()
- 0×7C: skipFrame()
The compression used for the costume data is a simple byte based RLE compression. However, it works by columns, not by lines. Each byte contains the color in the high bits and the repetition count in the low bits. If the repetition count is 0, then the next byte contains the actual repetition count. How many bits are used for the color depends on the palette size: for a 16 color palette, 4 bits are used for the color; for 32 colors, 5 bits are used.
if(palette_size == 16) {
shift = 4;
mask = 0xF;
} else {
shift = 3;
mask = 0x7;
}
while(1) {
rep = read_byte();
color = rep >> shift;
rep &= mask;
if(!rep)
rep = read_byte();
while(rep > 0) {
set_pixel(x,y,color);
rep--;
y++;
if(y >= height) {
y = 0;
x++;
if(x >= width) break;
}
}
}
Costume animations are always grouped in blocks of 4, one for each direction. The four directions are:
- 0: WEST (or right)
- 1: EAST (or left)
- 2: SOUTH (or down)
- 3: NORTH (or up)
Costumes used for normal actors have some predefined animations:
- 00-03: unknown
- 04-07: init
- 08-11: walk
- 12-15: stand
- 16-19: talk start
- 20-23: talk stop
ScummVM maps the following animations:
- 56: init frame (04)
- 57: walk frame (08)
- 58: stand frame (12)
- 59: talk start frame (16)
- 60: talk stop frame (20)
There is also an assert preventing 62 (0×3E), but I really have no clue why.
When actor animations are selected these have a special effect:
- 244-247: turn to new direction
- 248-251: change direction immediately
- 252-255: stop walking
So as far as I can tell, this leaves the ranges 24-55, 64-112 and perhaps 125-243 free for other animations. In DOTT, Hoaggi’s costume uses the following:
- 32-35: pick something up raising the arm
- 36-39: pick something up in front
- 40-43: pick something up off the ground
- 44-47: smile
AKOS, which are used in SCUMM 7 and 8, are much more powerful than the old COST and their data structure seems much cleaner. Could it be worst than the COST ? ;)
Note: some of this come from LucasHacks!
An AKOS block is made of some block unlike the COST.
- AKHD: Header
- AKPL: Palette (optional)
- RGBS: RGB Values (optional)
- AKSQ: Commands sequence
- AKCH: Anim offset table and definitions
- AKOF: Offset table
- AKCI: Frames definition
- AKCD: Frames data
I found these in the code but not in comi, so i suppose it’s only used by he engines.
- AKCT: Condition table ???
- AKST: Sequence table (set seq3, apparenly only used by he games) ???
- AKSF: Sequence table (set seq1 and seq2, apparenly only used by he games) ???
- AKFO: Sequence jump table, 16 bits list
unk1 : 16
flags : 8
unk2 : 8
num anims : 16le
num frames : 16le
codec : 16le
Flags: bit 0 is mirror, bit 1 is the number of directions the costume have (4 or 8)
AKPL blocks are just a list of 8 bits values.
RGBS blocks are just a list of 8 bits triplet coding colors.
frames
AKCD offset : 32le
AKCI offset : 16le
frames
width : 16le
height : 16le
rel_x : s16le
rel_y : s16le
move_x : s16le
move_y : s16le
Just all the commpressed frames packed together.
The AKCH block define the various animations, they are pretty similar to the anim definitions in the COST. It start with an offset table giving access to all entries, followed by all definitions. The definitions start with a mask indicating which limb are active, followed by the actual limb definitions.
offset table : num anims time
offset : 16le
definitions : num anims time
limb mask : 16le
mode : 8
start : 16le
len : 16le
The start and end are there only if mode is not equal to 1, 4 or 5.
mode
- 0: Disabled
- 1: Single frame, no commands ???
- 2: Loop
- 3: Play once
- 4: Stop limb
- 5: Start limb
- 6: Ignore first commande/pic mode ?
- 7: Unk
- 8: Same as mode 6 ?
In AKOS the picture index are mixed with the commands like in the COST, they are found in the AKSQ block. The AKOS have a lot more commands than the old COST so the stream is a mix of 8 and 16 bits values. It is read with something like this:
code = p[0];
if(code & 0x80)
code = (code << 8) | p[1];
8 bits values are always picture index. All 16 bits value with 0xC0 in the MSB are commands, the rest are picture index to which a mask of 0xFFF is applied.
- 0xC001: return()
- 0xC010: setVar(word value,byte *var)
- 0xC015: startSound(byte snd)
- 0xC016: ifVarSoundIsRunning(uword jmp,byte *snd)
- 0xC017: ifVarSoundIsNotRunning(uword jmp,byte *snd)
- 0xC018: ifSoundIsRunning(uword jmp,byte snd)
- 0xC019: ifSoundIsNotRunning(uword jmp,byte snd)
-
0xC020: complexChan(word ?,byte ?)
Use an alternative rendering mode, apparently it render the limb several time, moving it betwen each frame. -
0xC021: ???(byte narg,…)
load seq3Idx with the list and switch to complexChan limb rendering -
0xC022: ???(byte narg,…)
load seq3Idx with the list and switch to complexChan2 limb rendering -
0xC025: complexChan2(word ?,word ??)
Similar to complexChan but with a subtile difference, which ?? - 0xC030: jump(uword jmp)
- 0xC031: jumpIfSet(uword jmp,byte *var)
- 0xC040: addVar(word val,byte *var)
- 0xC042: startSound(byte snd)
- 0xC044: startVarSound(byte *snd)
- 0xC045: setUserCondition(byte narg,byte slot,byte *set)
- 0xC046: isUserConditionSet(byte narg,byte slot,byte *ret)
- 0xC047: setTalkCondition(byte narg,byte slot)
- 0xC048: isTalkConditionSet(byte narg,byte slot,byte *ret)
- 0xC050: ignore()
- 0xC060: incVar0()
- 0xC061: startSound0()
- 0xC070: jumpE(uword jmp,byte *a, word b)
- 0xC071: jumpNE(uword jmp,byte *a, word b)
- 0xC072: jumpL(uword jmp,byte *a, word b)
- 0xC073: jumpLE(uword jmp,byte *a, word b)
- 0xC074: jumpG(uword jmp,byte *a, word b)
- 0xC075: jumpGE(uword jmp,byte *a, word b)
- 0xC080: startAnim(byte anim)
- 0xC081: startVarAnim(byte *anim)
- 0xC082: random(word min,word max,byte *ret)
- 0xC083: setActorClip(byte val)
- 0xC084: startAnimInActor(byte *actor,byte *anim)
- 0xC085: setVarInActor(byte *actor,byte *var,word val)
- 0xC086: hideActor()
- 0xC087: setDrawOffs(word a,word b)
- 0xC088: jumpTable(byte *entry)
- 0xC089: soundStuff(byte snd,byte ?,byte cmd,byte ?,byte val)
- 0xC08A: flip(word val)
- 0xC08B: cmd3(byte ?,byte ?, byte ?, byte ?)
- 0xC08C: ignore3(byte ???)
- 0xC08D: ignore2(byte ???)
- 0xC08E: resetVolume(word snd)
- 0xC090: skipE(word a, byte *b)
- 0xC091: skipNE(word a, byte *b)
- 0xC092: skipL(word a, byte *b)
- 0xC093: skipLE(word a, byte *b)
- 0xC094: skipG(word a, byte *b)
- 0xC095: skipGE(word a, byte *b)
- 0xC09F: clearFlag()
- 0xC0A0: resetPan(byte snd,byte ???)
- 0xC0A1: jumpIfTalking(uword jmp)
- 0xC0A2: jumpIfNotTalking(uword jmp)
- 0xC0A3: resetVarPan(byte *snd)
- 0xC0A4: ???
- 0xC0A5: ???
- 0xC0A6: ???
- 0xC0A7: ???
- 0xC0FF: enqSeq
There are 4 known codec: 1, 5, 16 and 32. The latter is only supported by HE engines.
This codec seems identical to the one used in the old COST, except it also support 64 colors. I suppose it’s not very efficient in 64 colors mode as any repeat of more than 3 will need 2 bytes.
This one use the same encoding as BMOP image. See Scumm Image formats.
bpp = read_byte();
color = read_byte();
repeat = 0;
while(pos < width*height) {
write_pixel(pos%width,pos/width,color);
if(repeat > 0) repeat--;
else if(read_bit()) {
if(read_bit()) {
delta = read_bits(3);
if(delta != 4)
color += delta-4;
else
repeat = read_bits(8) - 1;
} else
color = read_bits(bpp);
}
pos++;
}